helium3@sina.com 2 hónapja
szülő
commit
456a396619
61 módosított fájl, 15848 hozzáadás és 63 törlés
  1. 41 3
      package.json
  2. 1 0
      src/accounts/accountManager.ts
  3. 15 0
      src/alert/alertManager.ts
  4. 4707 0
      src/aster-finance-futures-api-v3_CN.md
  5. 527 0
      src/cex/binance/futuresConnector.ts
  6. 0 0
      src/config.ts
  7. 102 0
      src/config/asterConfig.ts
  8. 41 0
      src/constants/index.ts
  9. 21 0
      src/core/app.ts
  10. 48 0
      src/core/hedging/hedgeCalculator.ts
  11. 38 0
      src/core/hedging/hedgingExecutor.ts
  12. 59 0
      src/core/hedging/router.ts
  13. 69 0
      src/core/hedging/types.ts
  14. 58 0
      src/core/market/aggregator.ts
  15. 6 0
      src/core/market/index.ts
  16. 46 0
      src/core/market/marketConfig.ts
  17. 25 0
      src/core/market/marketDataFetcher.ts
  18. 29 0
      src/core/market/marketDataService.ts
  19. 34 0
      src/core/market/sources/ccxtSource.ts
  20. 20 0
      src/core/market/sources/chainlinkSource.ts
  21. 20 0
      src/core/market/sources/uniswapV3Source.ts
  22. 19 0
      src/core/strategy/positionStrategy.ts
  23. 184 0
      src/dex/aster/asterAdapter.ts
  24. 170 0
      src/dex/aster/dualAccountHedgeExecutor.ts
  25. 322 0
      src/dex/aster/gridConfig.ts
  26. 1641 0
      src/dex/aster/gridDualExecutor.ts
  27. 31 0
      src/dex/aster/index.ts
  28. 203 0
      src/dex/aster/listenKeyManager.ts
  29. 62 0
      src/dex/aster/marketMeta.ts
  30. 335 0
      src/dex/aster/orderBook.ts
  31. 282 0
      src/dex/aster/orderBookManager.ts
  32. 372 0
      src/dex/aster/pointSystem.ts
  33. 733 0
      src/dex/aster/rhPointStrategyManager.ts
  34. 144 0
      src/dex/aster/signatureGenerator.ts
  35. 439 0
      src/dex/aster/tradingClient.ts
  36. 134 0
      src/dex/aster/tradingConfig.ts
  37. 274 0
      src/dex/aster/tradingTypes.ts
  38. 90 0
      src/dex/aster/types.ts
  39. 362 0
      src/dex/aster/userStreamClient.ts
  40. 161 0
      src/dex/aster/userStreamTypes.ts
  41. 365 0
      src/dex/aster/wsClient.ts
  42. 37 0
      src/infrastructure/config/configManager.ts
  43. 31 0
      src/infrastructure/config/default.example.yaml
  44. 14 0
      src/infrastructure/database/index.ts
  45. 30 0
      src/infrastructure/wallet/walletManager.ts
  46. 342 0
      src/market/README.md
  47. 428 0
      src/market/enhancedMarketManager.ts
  48. 13 0
      src/market/index.ts
  49. 332 0
      src/market/marketDataCache.ts
  50. 583 0
      src/market/marketDataManager.ts
  51. 19 0
      src/risk/riskAssessor.ts
  52. 45 0
      src/strategies/dualGridBot.ts
  53. 85 0
      src/tester.ts
  54. 188 0
      src/types/core/index.ts
  55. 364 0
      src/types/infrastructure/index.ts
  56. 268 0
      src/types/risk/index.ts
  57. 5 0
      src/utils/events.ts
  58. 17 0
      src/utils/logger.ts
  59. 20 0
      src/utils/math.ts
  60. 9 0
      src/utils/web3.ts
  61. 788 60
      yarn.lock

+ 41 - 3
package.json

@@ -8,16 +8,24 @@
   },
   "devDependencies": {
     "@types/jest": "~29.5",
-    "@types/node": "~18",
+    "@types/lodash": "^4.14.202",
+    "@types/node": "^18.19.112",
+    "@types/node-cron": "^3.0.11",
+    "@types/pg": "^8.10.9",
+    "@types/ws": "^8.5.10",
     "@typescript-eslint/eslint-plugin": "~6.2",
     "@typescript-eslint/parser": "~6.2",
     "eslint": "~8.46",
     "eslint-config-prettier": "~9.0",
     "eslint-plugin-prettier": "^5.1.1",
+    "jest": "^29.7.0",
     "prettier": "~3.0",
     "rimraf": "~5.0",
     "ts-api-utils": "~1.0",
+    "ts-jest": "^29.1.1",
+    "ts-node": "^10.9.2",
     "tsc-alias": "1.8.8",
+    "tsx": "^4.20.5",
     "typescript": "~5.1.0"
   },
   "scripts": {
@@ -25,13 +33,43 @@
     "clean": "rimraf coverage build tmp",
     "build": "tsc -p tsconfig.json && tsc-alias",
     "build:watch": "tsc -w -p tsconfig.json && tsc-alias",
+    "test": "jest",
+    "test:watch": "jest --watch",
+    "test:coverage": "jest --coverage",
+    "test:unit": "jest --testPathPattern=connector",
+    "test:integration": "jest --testPathPattern=integration",
+    "test:performance": "jest --testPathPattern=performance",
+    "test:verbose": "jest --verbose",
+    "test:ci": "jest --ci --coverage --watchAll=false",
+    "test:build": "yarn build && node build/src/tester.js",
     "build:release": "npm run clean && tsc -p tsconfig.release.json && tsc-alias",
     "lint": "eslint . --ext .ts --ext .mts",
-    "prettier": "prettier --config .prettierrc --write ."
+    "prettier": "prettier --config .prettierrc --write .",
+    "strategy:dual-grid": "tsx src/strategies/dualGridBot.ts"
   },
   "license": "Apache-2.0",
   "dependencies": {
-    "tslib": "~2.6.2"
+    "@binance/algo": "^2.0.4",
+    "@binance/derivatives-trading-coin-futures": "^6.0.2",
+    "@binance/derivatives-trading-usds-futures": "^10.0.1",
+    "@binance/margin-trading": "^6.0.2",
+    "@binance/spot": "^8.0.1",
+    "axios": "^1.6.2",
+    "bip39": "^3.1.0",
+    "ccxt": "^4.1.77",
+    "dotenv": "^17.2.2",
+    "ethers": "^6.8.1",
+    "https-proxy-agent": "^7.0.2",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.4",
+    "node-cron": "^3.0.3",
+    "pg": "^8.11.3",
+    "redis": "^4.6.10",
+    "sequelize": "^6.35.1",
+    "tslib": "~2.6.2",
+    "winston": "^3.11.0",
+    "ws": "^8.16.0",
+    "yaml": "^2.3.4"
   },
   "volta": {
     "node": "18.12.1"

+ 1 - 0
src/accounts/accountManager.ts

@@ -0,0 +1 @@
+class AccountManager {}

+ 15 - 0
src/alert/alertManager.ts

@@ -0,0 +1,15 @@
+import { logger } from '../utils/logger';
+
+export class AlertManager {
+  alert(message: string, severity: 'low' | 'medium' | 'high' | 'critical' = 'low') {
+    const prefix = `[ALERT:${severity}]`;
+    if (severity === 'high' || severity === 'critical') {
+      logger.warn(`${prefix} ${message}`);
+    } else {
+      logger.info(`${prefix} ${message}`);
+    }
+    // TODO: Telegram/Slack 集成
+  }
+}
+
+export const alertManager = new AlertManager();

+ 4707 - 0
src/aster-finance-futures-api-v3_CN.md

@@ -0,0 +1,4707 @@
+- [基本信息](#基本信息)
+	- [Rest 基本信息](#rest-基本信息)
+		- [HTTP 返回代码](#http-返回代码)
+		- [接口错误代码](#接口错误代码)
+		- [接口的基本信息](#接口的基本信息)
+	- [访问限制](#访问限制)
+		- [IP 访问限制](#ip-访问限制)
+		- [下单频率限制](#下单频率限制)
+	- [接口鉴权类型](#接口鉴权类型)
+	- [鉴权签名体](#鉴权签名体)
+	- [需要签名的接口](#需要签名的接口)
+	- [时间同步安全](#时间同步安全)
+	- [POST /fapi/v3/order 的示例](#post-fapiv3order-的示例)
+	- [GET /fapi/v3/order 的示例](#get-fapiv3order-的示例)
+	- [完整python脚本示例](#完整python脚本示例)
+	- [公开API参数](#公开api参数)
+		- [术语解释](#术语解释)
+		- [枚举定义](#枚举定义)
+	- [过滤器](#过滤器)
+		- [交易对过滤器](#交易对过滤器)
+			- [PRICE_FILTER 价格过滤器](#price_filter-价格过滤器)
+			- [LOT_SIZE 订单尺寸](#lot_size-订单尺寸)
+			- [MARKET_LOT_SIZE 市价订单尺寸](#market_lot_size-市价订单尺寸)
+			- [MAX_NUM_ORDERS 最多订单数](#max_num_orders-最多订单数)
+			- [MAX_NUM_ALGO_ORDERS 最多条件订单数](#max_num_algo_orders-最多条件订单数)
+			- [PERCENT_PRICE 价格振幅过滤器](#percent_price-价格振幅过滤器)
+			- [MIN_NOTIONAL 最小名义价值](#min_notional-最小名义价值)
+- [行情接口](#行情接口)
+	- [测试服务器连通性 PING](#测试服务器连通性-ping)
+	- [获取服务器时间](#获取服务器时间)
+	- [获取交易规则和交易对](#获取交易规则和交易对)
+	- [深度信息](#深度信息)
+	- [近期成交](#近期成交)
+	- [查询历史成交(MARKET_DATA)](#查询历史成交market_data)
+	- [近期成交(归集)](#近期成交归集)
+	- [K线数据](#k线数据)
+	- [价格指数K线数据](#价格指数k线数据)
+	- [标记价格K线数据](#标记价格k线数据)
+	- [最新标记价格和资金费率](#最新标记价格和资金费率)
+	- [查询资金费率历史](#查询资金费率历史)
+	- [24hr价格变动情况](#24hr价格变动情况)
+	- [最新价格](#最新价格)
+	- [当前最优挂单](#当前最优挂单)
+- [Websocket 行情推送](#websocket-行情推送)
+	- [实时订阅/取消数据流](#实时订阅取消数据流)
+		- [订阅一个信息流](#订阅一个信息流)
+		- [取消订阅一个信息流](#取消订阅一个信息流)
+		- [已订阅信息流](#已订阅信息流)
+		- [设定属性](#设定属性)
+		- [检索属性](#检索属性)
+		- [错误信息](#错误信息)
+	- [最新合约价格](#最新合约价格)
+	- [归集交易](#归集交易)
+	- [最新标记价格](#最新标记价格)
+	- [全市场最新标记价格](#全市场最新标记价格)
+	- [K线](#k线)
+	- [按Symbol的精简Ticker](#按symbol的精简ticker)
+	- [全市场的精简Ticker](#全市场的精简ticker)
+	- [按Symbol的完整Ticker](#按symbol的完整ticker)
+	- [全市场的完整Ticker](#全市场的完整ticker)
+	- [按Symbol的最优挂单信息](#按symbol的最优挂单信息)
+	- [全市场最优挂单信息](#全市场最优挂单信息)
+	- [有限档深度信息](#有限档深度信息)
+	- [增量深度信息](#增量深度信息)
+	- [如何正确在本地维护一个orderbook副本](#如何正确在本地维护一个orderbook副本)
+- [账户和交易接口](#账户和交易接口)
+	- [更改持仓模式(TRADE)](#更改持仓模式trade)
+	- [查询持仓模式(USER_DATA)](#查询持仓模式user_data)
+	- [更改联合保证金模式(TRADE)](#更改联合保证金模式trade)
+	- [查询联合保证金模式(USER_DATA)](#查询联合保证金模式user_data)
+	- [下单 (TRADE)](#下单-trade)
+	- [测试下单接口 (TRADE)](#测试下单接口-trade)
+	- [批量下单 (TRADE)](#批量下单-trade)
+	- [查询订单 (USER_DATA)](#查询订单-user_data)
+	- [撤销订单 (TRADE)](#撤销订单-trade)
+	- [撤销全部订单 (TRADE)](#撤销全部订单-trade)
+	- [批量撤销订单 (TRADE)](#批量撤销订单-trade)
+	- [倒计时撤销所有订单 (TRADE)](#倒计时撤销所有订单-trade)
+	- [查询当前挂单 (USER_DATA)](#查询当前挂单-user_data)
+	- [查看当前全部挂单 (USER_DATA)](#查看当前全部挂单-user_data)
+	- [查询所有订单(包括历史订单) (USER_DATA)](#查询所有订单包括历史订单-user_data)
+	- [账户余额v3 (USER_DATA)](#账户余额v3-user_data)
+	- [账户信息v3 (USER_DATA)](#账户信息v3-user_data)
+	- [调整开仓杠杆 (TRADE)](#调整开仓杠杆-trade)
+	- [变换逐全仓模式 (TRADE)](#变换逐全仓模式-trade)
+	- [调整逐仓保证金 (TRADE)](#调整逐仓保证金-trade)
+	- [逐仓保证金变动历史 (TRADE)](#逐仓保证金变动历史-trade)
+	- [用户持仓风险v3 (USER_DATA)](#用户持仓风险v3-user_data)
+	- [账户成交历史 (USER_DATA)](#账户成交历史-user_data)
+	- [获取账户损益资金流水(USER_DATA)](#获取账户损益资金流水user_data)
+	- [杠杆分层标准 (USER_DATA)](#杠杆分层标准-user_data)
+	- [持仓ADL队列估算 (USER_DATA)](#持仓adl队列估算-user_data)
+	- [用户强平单历史 (USER_DATA)](#用户强平单历史-user_data)
+	- [用户手续费率 (USER_DATA)](#用户手续费率-user_data)
+- [Websocket 账户信息推送](#websocket-账户信息推送)
+	- [生成listenKey (USER_STREAM)](#生成listenkey-user_stream)
+	- [延长listenKey有效期 (USER_STREAM)](#延长listenkey有效期-user_stream)
+	- [关闭listenKey (USER_STREAM)](#关闭listenkey-user_stream)
+	- [listenKey 过期推送](#listenkey-过期推送)
+	- [追加保证金通知](#追加保证金通知)
+	- [Balance和Position更新推送](#balance和position更新推送)
+	- [订单/交易 更新推送](#订单交易-更新推送)
+	- [杠杆倍数等账户配置 更新推送](#杠杆倍数等账户配置-更新推送)
+- [错误代码](#错误代码)
+	- [10xx - 常规服务器或网络问题](#10xx---常规服务器或网络问题)
+	- [11xx - Request issues](#11xx---request-issues)
+	- [20xx - Processing Issues](#20xx---processing-issues)
+	- [40xx - Filters and other Issues](#40xx---filters-and-other-issues)
+
+# 基本信息
+
+
+## Rest 基本信息
+
+* 接口可能需要用户的AGENT,如何创建AGENT请参考[这里](https://www.asterdex.com/)
+* 本篇列出REST接口的baseurl **https://fapi.asterdex.com**
+* 所有接口的响应都是JSON格式
+* 响应中如有数组,数组元素以时间升序排列,越早的数据越提前。
+* 所有时间、时间戳均为UNIX时间,单位为毫秒
+* 所有数据类型采用JAVA的数据类型定义
+
+### HTTP 返回代码
+* HTTP `4XX` 错误码用于指示错误的请求内容、行为、格式。
+* HTTP `403` 错误码表示违反WAF限制(Web应用程序防火墙)。
+* HTTP `429` 错误码表示警告访问频次超限,即将被封IP
+* HTTP `418` 表示收到429后继续访问,于是被封了。
+* HTTP `5XX` 错误码用于指示Aster Finance服务侧的问题。    
+* HTTP `503` 表示API服务端已经向业务核心提交了请求但未能获取响应,特别需要注意的是其不代表请求失败,而是未知。很可能已经得到了执行,也有可能执行失败,需要做进一步确认。
+
+
+### 接口错误代码
+* 每个接口都有可能抛出异常
+
+> 异常响应格式如下:
+
+```javascript
+{
+  "code": -1121,
+  "msg": "Invalid symbol."
+}
+```
+
+* 具体的错误码及其解释在[错误代码](#错误代码)
+
+### 接口的基本信息
+* `GET`方法的接口, 参数必须在`query string`中发送.
+* `POST`, `PUT`, 和 `DELETE` 方法的接口, 在 `request body`中发送(content type `application/x-www-form-urlencoded`)
+* 对参数的顺序不做要求。
+
+## 访问限制
+* 在 `/fapi/v3/exchangeInfo`接口中`rateLimits`数组里包含有REST接口(不限于本篇的REST接口)的访问限制。包括带权重的访问频次限制、下单速率限制。本篇`枚举定义`章节有限制类型的进一步说明。
+* 违反上述任何一个访问限制都会收到HTTP 429,这是一个警告.
+
+<aside class="notice">
+请注意,若用户被认定利用频繁挂撤单且故意低效交易意图发起攻击行为,Aster Finance有权视具体情况进一步加强对其访问限制。
+</aside>
+
+
+### IP 访问限制
+* 每个请求将包含一个`X-MBX-USED-WEIGHT-(intervalNum)(intervalLetter)`的头,其中包含当前IP所有请求的已使用权重。
+* 每个路由都有一个"权重",该权重确定每个接口计数的请求数。较重的接口和对多个交易对进行操作的接口将具有较重的"权重"。
+* 收到429时,您有责任作为API退回而不向其发送更多的请求。
+* **如果屡次违反速率限制和/或在收到429后未能退回,将导致API的IP被禁(http状态418)。**
+* 频繁违反限制,封禁时间会逐渐延长 ,**对于重复违反者,将会被封从2分钟到3天**。
+* **访问限制是基于IP的,而不是AGENT**
+
+<aside class="notice">
+强烈建议您尽可能多地使用websocket消息获取相应数据,既可以保障消息的及时性,也可以减少请求带来的访问限制压力。
+</aside>
+
+
+### 下单频率限制
+* 每个下单请求回报将包含一个`X-MBX-ORDER-COUNT-(intervalNum)(intervalLetter)`的头,其中包含当前账户已用的下单限制数量。
+* 被拒绝或不成功的下单并不保证回报中包含以上头内容。
+* **下单频率限制是基于每个账户计数的。**
+
+**关于交易时效性** 
+互联网状况并不100%可靠,不可完全依赖,因此你的程序本地到服务器的时延会有抖动.
+这是我们设置`recvWindow`的目的所在,如果你从事高频交易,对交易时效性有较高的要求,可以灵活设置recvWindow以达到你的要求。
+
+<aside class="notice">
+不推荐使用5秒以上的recvWindow
+</aside>
+
+## 接口鉴权类型
+* 每个接口都有自己的鉴权类型,鉴权类型决定了访问时应当进行何种鉴权
+* 如果需要鉴权,应当在请求体中添加signer
+
+鉴权类型 | 描述
+------------ | ------------
+NONE | 不需要鉴权的接口
+TRADE | 需要有效的signer和签名
+USER_DATA | 需要有效的signer和签名
+USER_STREAM | 需要有效的signer和签名
+MARKET_DATA | 需要有效的signer和签名
+
+## 鉴权签名体
+参数 | 描述
+------------ | ------------
+user | 主账户钱包地址
+signer | API钱包地址
+nonce | 当前时间戳,单位为微秒
+signature | 签名
+
+## 需要签名的接口 
+* TRADE 与 USER_DATA,USER_STREAM,MARKET_DATA
+* 接口参数转字符串后按照key值ASCII编码后生成的字符串 请注意所有参数取值请以字符串的方式进行签名
+* 生成字符串后在与鉴权签名参数的user,signer,nonce使用web3的abi参数编码生成字节码
+* 生成字节码后使用Keccak算法生成hash
+* 使用派生地址的私钥用web3的ecdsa签名算法对该hash进行签名生成signature
+
+### 时间同步安全
+* 签名接口均需要传递`timestamp`参数,其值应当是请求发送时刻的unix时间戳(毫秒)
+* 服务器收到请求时会判断请求中的时间戳,如果是5000毫秒之前发出的,则请求会被认为无效。这个时间窗口值可以通过发送可选参数`recvWindow`来自定义。
+
+> 逻辑伪代码:
+  
+  ```javascript
+  if (timestamp < (serverTime + 1000) && (serverTime - timestamp) <= recvWindow) {
+    // process request
+  } else {
+    // reject request
+  }
+  ```
+
+## POST /fapi/v3/order 的示例 
+
+#### 所有参数均通过from body请求(Python 3.9.6)
+
+#### 示例 : 以下参数为api注册信息,user,signer,privateKey仅供示范(privateKey为signer的私钥)
+
+Key | Value
+------------ | ------------
+user | 0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e
+signer | 0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0
+privateKey | 0x4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1
+
+#### 示例 : nonce参数为当前系统微秒值,超过系统时间,或者落后系统时间超过5s为非法请求
+```python
+#python
+nonce = math.trunc(time.time()*1000000)
+print(nonce)
+#1748310859508867
+```
+```java
+//java
+Instant now = Instant.now();
+long microsecond = now.getEpochSecond() * 1000000 + now.getNano() / 1000;
+```
+
+#### 示例 : 以下参数为业务请求参数 
+
+```python
+    my_dict = {'symbol': 'SANDUSDT', 'positionSide': 'BOTH', 'type': 'LIMIT', 'side': 'BUY',
+	         'timeInForce': 'GTC', 'quantity': "190", 'price': 0.28694}
+```
+
+#### 示例 : 所有参数通过 form body 发送(方法以python为例) 
+
+> **第一步将所有业务参数转字符串后按照ascII排序生成字符串:**
+
+```python
+    #定义所有元素取值转换为字符串
+    def _trim_dict(my_dict) :
+    # 假设待删除的字典为d
+     for key in my_dict:
+        value = my_dict[key]
+        if isinstance(value, list):
+            new_value = []
+            for item in value:
+                if isinstance(item, dict):
+                    new_value.append(json.dumps(_trim_dict(item)))
+                else:
+                    new_value.append(str(item))
+            my_dict[key] = json.dumps(new_value)
+            continue
+        if isinstance(value, dict):
+            my_dict[key] = json.dumps(_trim_dict(value))
+            continue
+        my_dict[key] = str(value)
+
+    return my_dict
+
+    #移除空值元素
+    my_dict = {key: value for key, value in my_dict.items() if  value is not None}
+    my_dict['recvWindow'] = 50000
+    my_dict['timestamp'] = int(round(time.time()*1000))
+    # my_dict['timestamp'] = 1749545309665
+    #将元素转换为字符串
+    _trim_dict(my_dict)
+    #根据ASCII排序生成字符串并移除特殊字符
+    json_str = json.dumps(my_dict, sort_keys=True).replace(' ', '').replace('\'','\"')
+    print(json_str)
+    {"positionSide":"BOTH","price":"0.28694","quantity":"190","recvWindow":"50000","side":"BUY","symbol":"SANDUSDT","timeInForce":"GTC","timestamp":"1749545309665","type":"LIMIT"}
+```
+
+> **第二步将第一步生成的字符串与账户信息以及nonce进行abi编码生成hash字符串:**
+
+```python
+   from eth_abi import encode
+   from web3 import Web3
+   #使用WEB3 ABI对生成的字符串和user, signer, nonce进行编码
+   encoded = encode(['string', 'address', 'address', 'uint256'], [json_str, user, signer, nonce])
+   print(encoded.hex())
+   #000000000000000000000000000000000000000000000000000000000000008000000000000000000000000063dd5acc6b1aa0f563956c0e534dd30b6dcf7c4e00000000000000000000000021cf8ae13bb72632562c6fff438652ba1a151bb00000000000000000000000000000000000000000000000000006361457bcec8300000000000000000000000000000000000000000000000000000000000000af7b22706f736974696f6e53696465223a22424f5448222c227072696365223a22302e3238363934222c227175616e74697479223a22313930222c227265637657696e646f77223a223530303030222c2273696465223a22425559222c2273796d626f6c223a2253414e4455534454222c2274696d65496e466f726365223a22475443222c2274696d657374616d70223a2231373439353435333039363635222c2274797065223a224c494d4954227d0000000000000000000000000000000000
+   #keccak hex
+   keccak_hex =Web3.keccak(encoded).hex()
+   print(keccak_hex)
+   #9e0273fc91323f5cdbcb00c358be3dee2854afb2d3e4c68497364a2f27a377fc
+```
+> **第三步将第二步生成的hash用privateKey进行签名:**
+```python
+    from eth_account import Account
+    from eth_abi import encode
+    from web3 import Web3, EthereumTesterProvider
+    from eth_account.messages import encode_defunct
+
+    signable_msg = encode_defunct(hexstr=keccak_hex)
+    signed_message = Account.sign_message(signable_message=signable_msg, private_key=priKey)
+    signature =  '0x'+signed_message.signature.hex()
+    print(signature)
+    #0x0337dd720a21543b80ff861cd3c26646b75b3a6a4b5d45805d4c1d6ad6fc33e65f0722778dd97525466560c69fbddbe6874eb4ed6f5fa7e576e486d9b5da67f31b
+```
+> **第四步将所有参数以及第三步生成的signature组装成请求体:**
+
+```python
+    my_dict['nonce'] = nonce
+    my_dict['user'] = user
+    my_dict['signer'] = signer
+    my_dict['signature'] = '0x'+signed_message.signature.hex()
+    url ='https://fapi.asterdex.com/fapi/v3/order'
+    headers = {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'User-Agent': 'PythonApp/1.0'
+    }
+    res = requests.post(url,data=my_dict,headers=headers)
+    print(url)
+    #curl  -X POST 'https://fapi.asterdex.com/fapi/v3/order' -d 'symbol=SANDUSDT&positionSide=BOTH&type=LIMIT&side=BUY&timeInForce=GTC&quantity=190&price=0.28694&recvWindow=50000&timestamp=1749545309665&nonce=1748310859508867&user=0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e&signer=0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0&signature=0x0337dd720a21543b80ff861cd3c26646b75b3a6a4b5d45805d4c1d6ad6fc33e65f0722778dd97525466560c69fbddbe6874eb4ed6f5fa7e576e486d9b5da67f31b'   
+
+```
+
+## GET /fapi/v3/order 的示例 
+#### 示例 : 所有参数通过 query string 发送(Python 3.9.6) 
+
+#### 示例 : 以下参数为api注册信息,user,signer,privateKey仅供示范(privateKey为agent的私钥)
+
+Key | Value
+------------ | ------------
+user | 0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e
+signer | 0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0
+privateKey | 0x4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1
+
+#### 示例 : nonce参数为当前系统微秒值,超过系统时间50s,或者落后系统时间超过5s为非法请求
+```python
+#python
+nonce = math.trunc(time.time()*1000000)
+print(nonce)
+#1748310859508867
+```
+```java
+//java
+Instant now = Instant.now();
+long microsecond = now.getEpochSecond() * 1000000 + now.getNano() / 1000;
+```
+
+#### 示例 : 以下参数为业务请求参数 
+```python
+my_dict = {'symbol':'SANDUSDT','side':"SELL","type":'LIMIT','orderId':2194215}
+```
+
+> **第一步将所有业务参数转字符串后按照ascII排序生成字符串:**
+
+```python
+    #定义所有元素取值转换为字符串
+    def _trim_dict(my_dict) :
+      for key in my_dict:
+        value = my_dict[key]
+        if isinstance(value, list):
+            new_value = []
+            for item in value:
+                if isinstance(item, dict):
+                    new_value.append(json.dumps(_trim_dict(item)))
+                else:
+                    new_value.append(str(item))
+            my_dict[key] = json.dumps(new_value)
+            continue
+        if isinstance(value, dict):
+            my_dict[key] = json.dumps(_trim_dict(value))
+            continue
+        my_dict[key] = str(value)
+
+    return my_dict
+
+    #移除空值元素
+    my_dict = {key: value for key, value in my_dict.items() if  value is not None}
+    my_dict['recvWindow'] = 50000
+    my_dict['timestamp'] = int(round(time.time()*1000))
+    # my_dict['timestamp'] = 1749545309665
+    #将元素转换为字符串
+    _trim_dict(my_dict)
+    #根据ASCII排序生成字符串并移除特殊字符
+    json_str = json.dumps(my_dict, sort_keys=True).replace(' ', '').replace('\'','\"')
+    print(json_str)
+    #{"orderId":"2194215","recvWindow":"50000","side":"BUY","symbol":"SANDUSDT","timestamp":"1749545309665","type":"LIMIT"}
+```
+
+> **第二步将第一步生成的字符串与账户信息以及nonce进行abi编码生成hash字符串:**
+
+```python
+   from eth_abi import encode
+   from web3 import Web3
+
+   #使用WEB3 ABI对生成的字符串和user, signer, nonce进行编码
+   encoded = encode(['string', 'address', 'address', 'uint256'], [json_str, user, signer, nonce])
+   print(encoded.hex())
+   #000000000000000000000000000000000000000000000000000000000000008000000000000000000000000063dd5acc6b1aa0f563956c0e534dd30b6dcf7c4e00000000000000000000000021cf8ae13bb72632562c6fff438652ba1a151bb00000000000000000000000000000000000000000000000000006361457bcec8300000000000000000000000000000000000000000000000000000000000000767b226f726465724964223a2232313934323135222c227265637657696e646f77223a223530303030222c2273696465223a22425559222c2273796d626f6c223a2253414e4455534454222c2274696d657374616d70223a2231373439353435333039363635222c2274797065223a224c494d4954227d00000000000000000000
+   keccak_hex =Web3.keccak(encoded).hex()
+   print(keccak_hex)
+   #6ad9569ea1355bf62de1b09b33b267a9404239af6d9227fa59e3633edae19e2a
+```
+> **第三步将第二步生成的hash用privateKey进行签名:**
+```python
+    from eth_account import Account
+    from eth_abi import encode
+    from web3 import Web3, EthereumTesterProvider
+    from eth_account.messages import encode_defunct
+
+    signable_msg = encode_defunct(hexstr=keccak_hex)
+    signed_message = Account.sign_message(signable_message=signable_msg, private_key=priKey)
+    signature =  '0x'+signed_message.signature.hex()
+    print(signature)
+    #0x4f5e36e91f0d4cf5b29b6559ebc2c808d3c808ebb13b2bcaaa478b98fb4195642c7473f0d1aa101359aaf278126af1a53bcb482fb05003bfb6bdc03de03c63151b
+```
+> **第四步将所有参数以及第三步生成的signature组装成请求体:**
+
+```python
+    my_dict['nonce'] = nonce
+    my_dict['user'] = user
+    my_dict['signer'] = signer
+    my_dict['signature'] = '0x'+signed_message.signature.hex()
+
+    url ='https://fapi.asterdex.com/fapi/v3/order'
+
+    res = requests.get(url, params=my_dict)
+    print(url)
+    #curl  -X GET 'https://fapi.asterdex.com/fapi/v3/order?symbol=SANDUSDT&side=BUY&type=LIMIT&orderId=2194215&recvWindow=50000&timestamp=1749545309665&nonce=1748310859508867&user=0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e&signer=0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0&signature=0x4f5e36e91f0d4cf5b29b6559ebc2c808d3c808ebb13b2bcaaa478b98fb4195642c7473f0d1aa101359aaf278126af1a53bcb482fb05003bfb6bdc03de03c63151b'
+
+```
+## 完整python脚本示例
+```python
+#Python 3.9.6
+#eth-account~=0.13.7
+#eth-abi~=5.2.0
+#web3~=7.11.0
+#requests~=2.32.3
+
+import json
+import math
+import time
+import requests
+
+from eth_abi import encode
+from eth_account import Account
+from eth_account.messages import encode_defunct
+from web3 import Web3
+
+user = '0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e'
+signer='0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0'
+priKey = "0x4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1"
+host = 'https://fapi.asterdex.com'
+placeOrder = {'url': '/fapi/v3/order', 'method': 'POST',
+              'params':{'symbol': 'SANDUSDT', 'positionSide': 'BOTH', 'type': 'LIMIT', 'side': 'BUY',
+	         'timeInForce': 'GTC', 'quantity': "30", 'price': 0.325,'reduceOnly': True}}
+getOrder = {'url':'/fapi/v3/order','method':'GET','params':{'symbol':'SANDUSDT','side':"BUY","type":'LIMIT','orderId':2194215}}
+
+def call(api):
+    nonce = math.trunc(time.time() * 1000000)
+    my_dict = api['params']
+    send(api['url'],api['method'],sign(my_dict,nonce))
+
+def sign(my_dict,nonce):
+    my_dict = {key: value for key, value in my_dict.items() if  value is not None}
+    my_dict['recvWindow'] = 50000
+    my_dict['timestamp'] = int(round(time.time()*1000))
+    msg = trim_param(my_dict,nonce)
+    signable_msg = encode_defunct(hexstr=msg)
+    signed_message = Account.sign_message(signable_message=signable_msg, private_key=priKey)
+    my_dict['nonce'] = nonce
+    my_dict['user'] = user
+    my_dict['signer'] = signer
+    my_dict['signature'] = '0x'+signed_message.signature.hex()
+
+    print(my_dict['signature'])
+    return  my_dict
+
+def trim_param(my_dict,nonce) -> str:
+    _trim_dict(my_dict)
+    json_str = json.dumps(my_dict, sort_keys=True).replace(' ', '').replace('\'','\"')
+    print(json_str)
+    encoded = encode(['string', 'address', 'address', 'uint256'], [json_str, user, signer, nonce])
+    print(encoded.hex())
+    keccak_hex =Web3.keccak(encoded).hex()
+    print(keccak_hex)
+    return keccak_hex
+
+def _trim_dict(my_dict) :
+    for key in my_dict:
+        value = my_dict[key]
+        if isinstance(value, list):
+            new_value = []
+            for item in value:
+                if isinstance(item, dict):
+                    new_value.append(json.dumps(_trim_dict(item)))
+                else:
+                    new_value.append(str(item))
+            my_dict[key] = json.dumps(new_value)
+            continue
+        if isinstance(value, dict):
+            my_dict[key] = json.dumps(_trim_dict(value))
+            continue
+        my_dict[key] = str(value)
+
+    return my_dict
+
+def send(url, method, my_dict):
+    url = host + url
+    print(url)
+    print(my_dict)
+    if method == 'POST':
+        headers = {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'User-Agent': 'PythonApp/1.0'
+        }
+        res = requests.post(url, data=my_dict, headers=headers)
+        print(res.text)
+    if method == 'GET':
+        res = requests.get(url, params=my_dict)
+        print(res.text)
+    if method == 'DELETE':
+        res = requests.delete(url, data=my_dict)
+        print(res.text)
+
+if __name__ == '__main__':
+    call(placeOrder)
+    # call(getOrder)
+
+```
+
+## 公开API参数
+### 术语解释
+* `base asset` 指一个交易对的交易对象,即写在靠前部分的资产名
+* `quote asset` 指一个交易对的定价资产,即写在靠后部分资产名
+
+
+### 枚举定义
+
+**交易对类型:**
+
+* FUTURE 期货
+
+**合约类型 (contractType):**
+
+* PERPETUAL 永续合约
+
+
+**合约状态 (contractStatus, status):**
+
+* PENDING_TRADING   待上市
+* TRADING          	交易中
+* PRE_SETTLE			预结算
+* SETTLING			结算中
+* CLOSE				已下架
+
+
+**订单状态 (status):**
+
+* NEW 新建订单
+* PARTIALLY_FILLED  部分成交
+* FILLED  全部成交
+* CANCELED  已撤销
+* REJECTED 订单被拒绝
+* EXPIRED 订单过期(根据timeInForce参数规则)
+
+**订单种类 (orderTypes, type):**
+
+* LIMIT 限价单
+* MARKET 市价单
+* STOP 止损限价单
+* STOP_MARKET 止损市价单
+* TAKE_PROFIT 止盈限价单
+* TAKE_PROFIT_MARKET 止盈市价单
+* TRAILING_STOP_MARKET 跟踪止损单
+
+**订单方向 (side):**
+
+* BUY 买入
+* SELL 卖出
+
+**持仓方向:**
+
+* BOTH 单一持仓方向
+* LONG 多头(双向持仓下)
+* SHORT 空头(双向持仓下)
+
+**有效方式 (timeInForce):**
+
+* GTC - Good Till Cancel 成交为止
+* IOC - Immediate or Cancel 无法立即成交(吃单)的部分就撤销
+* FOK - Fill or Kill 无法全部立即成交就撤销
+* GTX - Good Till Crossing 无法成为挂单方就撤销
+
+**条件价格触发类型 (workingType)**
+
+* MARK_PRICE
+* CONTRACT_PRICE 
+
+**响应类型 (newOrderRespType)**
+
+* ACK
+* RESULT
+
+**K线间隔:**
+
+m -> 分钟; h -> 小时; d -> 天; w -> 周; M -> 月
+
+* 1m
+* 3m
+* 5m
+* 15m
+* 30m
+* 1h
+* 2h
+* 4h
+* 6h
+* 8h
+* 12h
+* 1d
+* 3d
+* 1w
+* 1M
+
+**限制种类 (rateLimitType)**
+
+> REQUEST_WEIGHT
+
+```javascript
+  {
+  	"rateLimitType": "REQUEST_WEIGHT",
+  	"interval": "MINUTE",
+  	"intervalNum": 1,
+  	"limit": 2400
+  }
+```
+
+> ORDERS
+
+```javascript
+  {
+  	"rateLimitType": "ORDERS",
+  	"interval": "MINUTE",
+  	"intervalNum": 1,
+  	"limit": 1200
+   }
+```
+
+* REQUESTS_WEIGHT  单位时间请求权重之和上限
+
+* ORDERS    单位时间下单(撤单)次数上限
+
+
+**限制间隔**
+
+* MINUTE
+
+
+
+## 过滤器
+过滤器,即Filter,定义了一系列交易规则。
+共有两类,分别是针对交易对的过滤器`symbol filters`,和针对整个交易所的过滤器`exchange filters`(暂不支持)
+
+### 交易对过滤器
+#### PRICE_FILTER 价格过滤器
+
+> **/exchangeInfo 响应中的格式:**
+
+```javascript
+  {
+    "filterType": "PRICE_FILTER",
+    "minPrice": "0.00000100",
+    "maxPrice": "100000.00000000",
+    "tickSize": "0.00000100"
+  }
+```
+
+价格过滤器用于检测order订单中price参数的合法性
+
+* `minPrice` 定义了 `price`/`stopPrice` 允许的最小值
+* `maxPrice` 定义了 `price`/`stopPrice` 允许的最大值。
+* `tickSize` 定义了 `price`/`stopPrice` 的步进间隔,即price必须等于minPrice+(tickSize的整数倍)
+以上每一项均可为0,为0时代表这一项不再做限制。
+
+逻辑伪代码如下:
+
+* `price` >= `minPrice`
+* `price` <= `maxPrice`
+* (`price`-`minPrice`) % `tickSize` == 0
+
+
+
+#### LOT_SIZE 订单尺寸
+
+> */exchangeInfo 响应中的格式:**
+
+```javascript
+  {
+    "filterType": "LOT_SIZE",
+    "minQty": "0.00100000",
+    "maxQty": "100000.00000000",
+    "stepSize": "0.00100000"
+  }
+```
+
+lots是拍卖术语,这个过滤器对订单中的`quantity`也就是数量参数进行合法性检查。包含三个部分:
+
+* `minQty` 表示 `quantity` 允许的最小值.
+* `maxQty` 表示 `quantity` 允许的最大值
+* `stepSize` 表示 `quantity`允许的步进值。
+
+逻辑伪代码如下:
+
+* `quantity` >= `minQty`
+* `quantity` <= `maxQty`
+* (`quantity`-`minQty`) % `stepSize` == 0
+
+
+#### MARKET_LOT_SIZE 市价订单尺寸
+参考LOT_SIZE,区别仅在于对市价单还是限价单生效
+
+#### MAX_NUM_ORDERS 最多订单数
+
+
+> **/exchangeInfo 响应中的格式:**
+
+```javascript
+  {
+    "filterType": "MAX_NUM_ORDERS",
+    "limit": 200
+  }
+```
+
+定义了某个交易对最多允许的挂单数量(不包括已关闭的订单)
+
+普通订单与条件订单均计算在内
+
+
+#### MAX_NUM_ALGO_ORDERS 最多条件订单数
+
+> **/exchangeInfo format:**
+
+```javascript
+  {
+    "filterType": "MAX_NUM_ALGO_ORDERS",
+    "limit": 100
+  }
+```
+
+定义了某个交易对最多允许的条件订单的挂单数量(不包括已关闭的订单)。   
+
+条件订单目前包括`STOP`, `STOP_MARKET`, `TAKE_PROFIT`, `TAKE_PROFIT_MARKET`, 和 `TRAILING_STOP_MARKET`
+
+
+#### PERCENT_PRICE 价格振幅过滤器
+
+> **/exchangeInfo 响应中的格式:**
+
+```javascript
+  {
+    "filterType": "PERCENT_PRICE",
+    "multiplierUp": "1.1500",
+    "multiplierDown": "0.8500",
+    "multiplierDecimal": 4
+  }
+```
+
+`PERCENT_PRICE` 定义了基于标记价格计算的挂单价格的可接受区间.
+
+挂单价格必须同时满足以下条件:
+
+* 买单: `price` <= `markPrice` * `multiplierUp`
+* 卖单: `price` >= `markPrice` * `multiplierDown`
+
+
+#### MIN_NOTIONAL 最小名义价值
+
+> **/exchangeInfo 响应中的格式:**
+
+```javascript
+  {
+    "filterType": "MIN_NOTIONAL",
+    "notioanl": "1"
+  }
+```
+
+MIN_NOTIONAL过滤器定义了交易对订单所允许的最小名义价值(成交额)。
+订单的名义价值是`价格`*`数量`。 
+由于`MARKET`订单没有价格,因此会使用 mark price 计算。   
+
+
+
+
+
+
+---
+
+
+# 行情接口
+## 测试服务器连通性 PING
+``
+GET /fapi/v3/ping
+``
+
+> **响应:**
+
+```javascript
+{}
+```
+
+测试能否联通
+
+**权重:**
+1
+
+**参数:**
+NONE
+
+
+
+## 获取服务器时间
+
+> **响应:**
+
+```javascript
+{
+  "serverTime": 1499827319559 // 当前的系统时间
+}
+```
+
+``
+GET /fapi/v3/time
+``
+
+获取服务器时间
+
+**权重:**
+1
+
+**参数:**
+NONE
+
+
+## 获取交易规则和交易对
+
+> **响应:**
+
+```javascript
+{
+	"exchangeFilters": [],
+ 	"rateLimits": [ // API访问的限制
+ 		{
+ 			"interval": "MINUTE", // 按照分钟计算
+   			"intervalNum": 1, // 按照1分钟计算
+   			"limit": 2400, // 上限次数
+   			"rateLimitType": "REQUEST_WEIGHT" // 按照访问权重来计算
+   		},
+  		{
+  			"interval": "MINUTE",
+   			"intervalNum": 1,
+   			"limit": 1200,
+   			"rateLimitType": "ORDERS" // 按照订单数量来计算
+   		}
+   	],
+ 	"serverTime": 1565613908500, // 请忽略。如果需要获取当前系统时间,请查询接口 “GET /fapi/v3/time”
+ 	"assets": [ // 资产信息
+ 		{
+ 			"asset": "BUSD",
+   			"marginAvailable": true, // 是否可用作保证金
+   			"autoAssetExchange": 0 // 保证金资产自动兑换阈值
+   		},
+ 		{
+ 			"asset": "USDT",
+   			"marginAvailable": true, // 是否可用作保证金
+   			"autoAssetExchange": 0 // 保证金资产自动兑换阈值
+   		},
+ 		{
+ 			"asset": "BNB",
+   			"marginAvailable": false, // 是否可用作保证金
+   			"autoAssetExchange": null // 保证金资产自动兑换阈值
+   		}
+   	],
+ 	"symbols": [ // 交易对信息
+ 		{
+ 			"symbol": "BLZUSDT",  // 交易对
+ 			"pair": "BLZUSDT",  // 标的交易对
+ 			"contractType": "PERPETUAL",	// 合约类型
+ 			"deliveryDate": 4133404800000,  // 交割日期
+ 			"onboardDate": 1598252400000,	  // 上线日期
+ 			"status": "TRADING",  // 交易对状态
+ 			"maintMarginPercent": "2.5000",  // 请忽略
+ 			"requiredMarginPercent": "5.0000", // 请忽略
+ 			"baseAsset": "BLZ",  // 标的资产
+ 			"quoteAsset": "USDT", // 报价资产
+ 			"marginAsset": "USDT", // 保证金资产
+ 			"pricePrecision": 5,  // 价格小数点位数(仅作为系统精度使用,注意同tickSize 区分)
+ 			"quantityPrecision": 0,  // 数量小数点位数(仅作为系统精度使用,注意同stepSize 区分)
+ 			"baseAssetPrecision": 8,  // 标的资产精度
+ 			"quotePrecision": 8,  // 报价资产精度
+ 			"underlyingType": "COIN",
+ 			"underlyingSubType": ["STORAGE"],
+ 			"settlePlan": 0,
+ 			"triggerProtect": "0.15", // 开启"priceProtect"的条件订单的触发阈值
+ 			"filters": [
+ 				{
+ 					"filterType": "PRICE_FILTER", // 价格限制
+     				"maxPrice": "300", // 价格上限, 最大价格
+     				"minPrice": "0.0001", // 价格下限, 最小价格
+     				"tickSize": "0.0001" // 订单最小价格间隔
+     			},
+    			{
+    				"filterType": "LOT_SIZE", // 数量限制
+     				"maxQty": "10000000", // 数量上限, 最大数量
+     				"minQty": "1", // 数量下限, 最小数量
+     				"stepSize": "1" // 订单最小数量间隔
+     			},
+    			{
+    				"filterType": "MARKET_LOT_SIZE", // 市价订单数量限制
+     				"maxQty": "590119", // 数量上限, 最大数量
+     				"minQty": "1", // 数量下限, 最小数量
+     				"stepSize": "1" // 允许的步进值
+     			},
+     			{
+    				"filterType": "MAX_NUM_ORDERS", // 最多订单数限制
+    				"limit": 200
+  				},
+  				{
+    				"filterType": "MAX_NUM_ALGO_ORDERS", // 最多条件订单数限制
+    				"limit": 100
+  				},
+  				{
+  					"filterType": "MIN_NOTIONAL",  // 最小名义价值
+  					"notional": "1", 
+  				},
+  				{
+    				"filterType": "PERCENT_PRICE", // 价格比限制
+    				"multiplierUp": "1.1500", // 价格上限百分比
+    				"multiplierDown": "0.8500", // 价格下限百分比
+    				"multiplierDecimal": 4
+    			}
+   			],
+ 			"OrderType": [ // 订单类型
+   				"LIMIT",  // 限价单
+   				"MARKET",  // 市价单
+   				"STOP", // 止损单
+   				"STOP_MARKET", // 止损市价单
+   				"TAKE_PROFIT", // 止盈单
+   				"TAKE_PROFIT_MARKET", // 止盈暑市价单
+   				"TRAILING_STOP_MARKET" // 跟踪止损市价单
+   			],
+   			"timeInForce": [ // 有效方式
+   				"GTC", // 成交为止, 一直有效
+   				"IOC", // 无法立即成交(吃单)的部分就撤销
+   				"FOK", // 无法全部立即成交就撤销
+   				"GTX" // 无法成为挂单方就撤销
+ 			],
+ 			"liquidationFee": "0.010000",	// 强平费率
+   			"marketTakeBound": "0.30",	// 市价吃单(相对于标记价格)允许可造成的最大价格偏离比例
+ 		}
+   	],
+	"timezone": "UTC" // 服务器所用的时间区域
+}
+
+```
+
+``
+GET /fapi/v3/exchangeInfo
+``
+
+获取交易规则和交易对
+
+**权重:**
+1
+
+**参数:**
+NONE
+
+
+
+## 深度信息
+
+> **响应:**
+
+```javascript
+{
+  "lastUpdateId": 1027024,
+  "E": 1589436922972,   // 消息时间
+  "T": 1589436922959,   // 撮合引擎时间
+  "bids": [				// 买单
+    [
+      "4.00000000",     // 价格
+      "431.00000000"    // 数量
+    ]
+  ],
+  "asks": [				// 卖单
+    [
+      "4.00000200",		// 价格
+      "12.00000000"		// 数量
+    ]
+  ]
+}
+```
+
+``
+GET /fapi/v3/depth
+``
+
+**权重:**
+
+limit         | 权重
+------------  | ------------
+5, 10, 20, 50 | 2
+100           | 5
+500           | 10
+1000          | 20
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |                            描述
+------ | ------ | -------- | -----------------------------------------------------------
+symbol | STRING | YES      | 交易对
+limit  | INT    | NO       | 默认 500; 可选值:[5, 10, 20, 50, 100, 500, 1000]
+
+
+
+## 近期成交
+
+> **响应:**
+
+```javascript
+[
+  {
+    "id": 28457,				// 成交ID
+    "price": "4.00000100",		// 成交价格
+    "qty": "12.00000000",		// 成交量
+    "quoteQty": "48.00",		// 成交额
+    "time": 1499865549590,		// 时间
+    "isBuyerMaker": true		// 买方是否为挂单方
+  }
+]
+```
+
+``
+GET /fapi/v3/trades
+``
+
+获取近期订单簿成交
+
+**权重:**
+1
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |          描述
+------ | ------ | -------- | ----------------------
+symbol | STRING | YES      | 交易对
+limit  | INT    | NO       | 默认:500,最大1000 
+
+* 仅返回订单簿成交,即不会返回保险基金和自动减仓(ADL)成交
+
+## 查询历史成交(MARKET_DATA)
+
+> **响应:**
+
+```javascript
+[
+  {
+    "id": 28457,				// 成交ID
+    "price": "4.00000100",		// 成交价格
+    "qty": "12.00000000",		// 成交量
+    "quoteQty": "48.00",		// 成交额
+    "time": 1499865549590,		// 时间
+    "isBuyerMaker": true		// 买方是否为挂单方
+  }
+]
+```
+
+``
+GET /fapi/v3/historicalTrades
+``
+
+查询订单簿历史成交
+
+**权重:**
+20
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |                      描述
+------ | ------ | -------- | ----------------------------------------------
+symbol | STRING | YES      | 交易对
+limit  | INT    | NO       | 默认值:500 最大值:1000.
+fromId | LONG   | NO       | 从哪一条成交id开始返回. 缺省返回最近的成交记录
+
+* 仅返回订单簿成交,即不会返回保险基金和自动减仓(ADL)成交
+
+## 近期成交(归集)
+
+> **响应:**
+
+```javascript
+[
+  {
+    "a": 26129,         // 归集成交ID
+    "p": "0.01633102",  // 成交价
+    "q": "4.70443515",  // 成交量
+    "f": 27781,         // 被归集的首个成交ID
+    "l": 27781,         // 被归集的末个成交ID
+    "T": 1498793709153, // 成交时间
+    "m": true,          // 是否为主动卖出单
+  }
+]
+```
+
+``
+GET /fapi/v3/aggTrades
+``
+
+归集交易与逐笔交易的区别在于,同一价格、同一方向、同一时间(按秒计算)的订单簿trade会被聚合为一条
+
+**权重:**
+20
+
+**参数:**
+
+  名称    |  类型  | 是否必需 |                描述
+--------- | ------ | -------- | ----------------------------------
+symbol    | STRING | YES      | 交易对
+fromId    | LONG   | NO       | 从包含fromID的成交开始返回结果
+startTime | LONG   | NO       | 从该时刻之后的成交记录开始返回结果
+endTime   | LONG   | NO       | 返回该时刻为止的成交记录
+limit     | INT    | NO       | 默认 500; 最大 1000.
+
+* 如果同时发送`startTime`和`endTime`,间隔必须小于一小时
+* 如果没有发送任何筛选参数(`fromId`, `startTime`, `endTime`),默认返回最近的成交记录
+* 保险基金和自动减仓(ADL)成交不属于订单簿成交,故不会被归并聚合
+
+
+## K线数据
+
+> **响应:**
+
+```javascript
+[
+  [
+    1499040000000,      // 开盘时间
+    "0.01634790",       // 开盘价
+    "0.80000000",       // 最高价
+    "0.01575800",       // 最低价
+    "0.01577100",       // 收盘价(当前K线未结束的即为最新价)
+    "148976.11427815",  // 成交量
+    1499644799999,      // 收盘时间
+    "2434.19055334",    // 成交额
+    308,                // 成交笔数
+    "1756.87402397",    // 主动买入成交量
+    "28.46694368",      // 主动买入成交额
+    "17928899.62484339" // 请忽略该参数
+  ]
+]
+```
+
+``
+GET /fapi/v3/klines
+``
+
+每根K线的开盘时间可视为唯一ID
+
+**权重:** 取决于请求中的LIMIT参数
+
+LIMIT参数 | 权重
+---|---
+[1,100) | 1
+[100, 500) | 2
+[500, 1000] | 5
+> 1000 | 10
+
+**参数:**
+
+  名称    |  类型  | 是否必需 |          描述
+--------- | ------ | -------- | ----------------------
+symbol    | STRING | YES      | 交易对
+interval  | ENUM   | YES      | 时间间隔
+startTime | LONG   | NO       | 起始时间
+endTime   | LONG   | NO       | 结束时间
+limit     | INT    | NO       | 默认值:500 最大值:1500.
+
+* 缺省返回最近的数据
+
+
+
+## 价格指数K线数据
+
+> **响应:**
+
+```javascript
+[
+  [
+    1591256400000,      	// 开盘时间
+    "9653.69440000",    	// 开盘价
+    "9653.69640000",     	// 最高价
+    "9651.38600000",     	// 最低价
+    "9651.55200000",     	// 收盘价(当前K线未结束的即为最新价)
+    "0	", 					// 请忽略
+    1591256459999,      	// 收盘时间
+    "0",    				// 请忽略
+    60,                		// 构成记录数
+    "0",    				// 请忽略
+    "0",      				// 请忽略
+    "0" 					// 请忽略
+  ]
+]
+```
+
+``
+GET /fapi/v3/indexPriceKlines
+``
+
+每根K线的开盘时间可视为唯一ID
+
+**权重:** 取决于请求中的LIMIT参数
+
+LIMIT参数 | 权重
+---|---
+[1,100) | 1
+[100, 500) | 2
+[500, 1000] | 5
+> 1000 | 10
+
+**参数:**
+
+  名称    |  类型  | 是否必需 |          描述
+--------- | ------ | -------- | ----------------------
+pair    	| STRING | YES      | 标的交易对
+interval  | ENUM   | YES      | 时间间隔
+startTime | LONG   | NO       | 起始时间
+endTime   | LONG   | NO       | 结束时间
+limit     | INT    | NO       | 默认值:500 最大值:1500
+
+* 缺省返回最近的数据
+
+
+## 标记价格K线数据
+
+> **响应:**
+
+```javascript
+[
+  [
+    1591256400000,      	// 开盘时间
+    "9653.69440000",    	// 开盘价
+    "9653.69640000",     	// 最高价
+    "9651.38600000",     	// 最低价
+    "9651.55200000",     	// 收盘价(当前K线未结束的即为最新价)
+    "0	", 					// 请忽略
+    1591256459999,      	// 收盘时间
+    "0",    				// 请忽略
+    60,                		// 构成记录数
+    "0",    				// 请忽略
+    "0",      				// 请忽略
+    "0" 					// 请忽略
+  ]
+]
+```
+
+``
+GET /fapi/v3/markPriceKlines
+``
+每根K线的开盘时间可视为唯一ID
+
+**权重:** 取决于请求中的LIMIT参数
+
+LIMIT参数 | 权重
+---|---
+[1,100) | 1
+[100, 500) | 2
+[500, 1000] | 5
+> 1000 | 10
+
+**参数:**
+
+  名称    |  类型  | 是否必需 |          描述
+--------- | ------ | -------- | ----------------------
+symbol   	| STRING | YES      | 交易对
+interval  | ENUM   | YES      | 时间间隔
+startTime | LONG   | NO       | 起始时间
+endTime   | LONG   | NO       | 结束时间
+limit     | INT    | NO       | 默认值:500 最大值:1500
+
+* 缺省返回最近的数据
+
+
+## 最新标记价格和资金费率 
+
+> **响应:**
+
+```javascript
+{
+    "symbol": "BTCUSDT",				// 交易对
+    "markPrice": "11793.63104562",		// 标记价格
+    "indexPrice": "11781.80495970",		// 指数价格
+    "estimatedSettlePrice": "11781.16138815",  // 预估结算价,仅在交割开始前最后一小时有意义
+    "lastFundingRate": "0.00038246",	// 最近更新的资金费率
+    "nextFundingTime": 1597392000000,	// 下次资金费时间
+    "interestRate": "0.00010000",		// 标的资产基础利率
+    "time": 1597370495002				// 更新时间
+}
+```
+
+> **当不指定symbol时相应**
+
+```javascript
+[
+	{
+    	"symbol": "BTCUSDT",			// 交易对
+    	"markPrice": "11793.63104562",	// 标记价格
+    	"indexPrice": "11781.80495970",	// 指数价格
+    	"estimatedSettlePrice": "11781.16138815",  // 预估结算价,仅在交割开始前最后一小时有意义
+    	"lastFundingRate": "0.00038246",	// 最近更新的资金费率
+    	"nextFundingTime": 1597392000000,	// 下次资金费时间
+    	"interestRate": "0.00010000",		// 标的资产基础利率
+    	"time": 1597370495002				// 更新时间
+	}
+]
+```
+
+
+``
+GET /fapi/v3/premiumIndex
+``
+
+采集各大交易所数据加权平均
+
+**权重:**
+1
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |  描述
+------ | ------ | -------- | ------
+symbol | STRING | NO       | 交易对
+
+
+## 查询资金费率历史
+
+> **响应:**
+
+```javascript
+[
+	{
+    	"symbol": "BTCUSDT",			// 交易对
+    	"fundingRate": "-0.03750000",	// 资金费率
+    	"fundingTime": 1570608000000,	// 资金费时间
+	},
+	{
+   		"symbol": "BTCUSDT",
+    	"fundingRate": "0.00010000",
+    	"fundingTime": 1570636800000,
+	}
+]
+```
+
+``
+GET /fapi/v3/fundingRate
+``
+
+**权重:**
+1
+
+**参数:**
+
+  名称    |  类型  | 是否必需 |                         描述
+--------- | ------ | -------- | -----------------------------------------------------
+symbol    | STRING | NO      | 交易对
+startTime | LONG   | NO       | 起始时间
+endTime   | LONG   | NO       | 结束时间
+limit     | INT    | NO       | 默认值:100 最大值:1000
+
+* 如果 `startTime` 和 `endTime` 都未发送, 返回最近 `limit` 条数据.
+* 如果 `startTime` 和 `endTime` 之间的数据量大于 `limit`, 返回 `startTime` + `limit`情况下的数据。
+
+
+## 24hr价格变动情况
+
+> **响应:**
+
+```javascript
+{
+  "symbol": "BTCUSDT",
+  "priceChange": "-94.99999800",    //24小时价格变动
+  "priceChangePercent": "-95.960",  //24小时价格变动百分比
+  "weightedAvgPrice": "0.29628482", //加权平均价
+  "lastPrice": "4.00000200",        //最近一次成交价
+  "lastQty": "200.00000000",        //最近一次成交额
+  "openPrice": "99.00000000",       //24小时内第一次成交的价格
+  "highPrice": "100.00000000",      //24小时最高价
+  "lowPrice": "0.10000000",         //24小时最低价
+  "volume": "8913.30000000",        //24小时成交量
+  "quoteVolume": "15.30000000",     //24小时成交额
+  "openTime": 1499783499040,        //24小时内,第一笔交易的发生时间
+  "closeTime": 1499869899040,       //24小时内,最后一笔交易的发生时间
+  "firstId": 28385,   // 首笔成交id
+  "lastId": 28460,    // 末笔成交id
+  "count": 76         // 成交笔数
+}
+```
+
+> 或(当不发送交易对信息)
+
+```javascript
+[
+	{
+  		"symbol": "BTCUSDT",
+  		"priceChange": "-94.99999800",    //24小时价格变动
+  		"priceChangePercent": "-95.960",  //24小时价格变动百分比
+  		"weightedAvgPrice": "0.29628482", //加权平均价
+  		"lastPrice": "4.00000200",        //最近一次成交价
+  		"lastQty": "200.00000000",        //最近一次成交额
+  		"openPrice": "99.00000000",       //24小时内第一次成交的价格
+  		"highPrice": "100.00000000",      //24小时最高价
+  		"lowPrice": "0.10000000",         //24小时最低价
+  		"volume": "8913.30000000",        //24小时成交量
+  		"quoteVolume": "15.30000000",     //24小时成交额
+  		"openTime": 1499783499040,        //24小时内,第一笔交易的发生时间
+  		"closeTime": 1499869899040,       //24小时内,最后一笔交易的发生时间
+  		"firstId": 28385,   // 首笔成交id
+  		"lastId": 28460,    // 末笔成交id
+  		"count": 76         // 成交笔数
+    }
+]
+```
+
+``
+GET /fapi/v3/ticker/24hr
+``
+
+请注意,不携带symbol参数会返回全部交易对数据,不仅数据庞大,而且权重极高
+
+**权重:**
+* 带symbol为`1`
+* 不带为`40`
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |  描述
+------ | ------ | -------- | ------
+symbol | STRING | NO       | 交易对
+
+* 不发送交易对参数,则会返回所有交易对信息
+
+
+## 最新价格
+
+> **响应:**
+
+```javascript
+{
+  "symbol": "LTCBTC",		// 交易对
+  "price": "4.00000200",		// 价格
+  "time": 1589437530011   // 撮合引擎时间
+}
+```
+
+> 或(当不发送symbol)
+
+```javascript
+[
+	{
+  		"symbol": "BTCUSDT",	// 交易对
+  		"price": "6000.01",		// 价格
+  		"time": 1589437530011   // 撮合引擎时间
+	}
+]
+```
+
+``
+GET /fapi/v3/ticker/price
+``
+
+返回最近价格
+
+**权重:**
+* 单交易对`1`
+* 无交易对`2`
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |  描述
+------ | ------ | -------- | ------
+symbol | STRING | NO       | 交易对
+
+* 不发送交易对参数,则会返回所有交易对信息
+
+
+## 当前最优挂单
+
+> **响应:**
+
+```javascript
+{
+  "symbol": "BTCUSDT", // 交易对
+  "bidPrice": "4.00000000", //最优买单价
+  "bidQty": "431.00000000", //挂单量
+  "askPrice": "4.00000200", //最优卖单价
+  "askQty": "9.00000000", //挂单量
+  "time": 1589437530011   // 撮合引擎时间
+}
+```
+> 或(当不发送symbol)
+
+```javascript
+[
+	{
+  		"symbol": "BTCUSDT", // 交易对
+  		"bidPrice": "4.00000000", //最优买单价
+  		"bidQty": "431.00000000", //挂单量
+  		"askPrice": "4.00000200", //最优卖单价
+  		"askQty": "9.00000000", //挂单量
+  		"time": 1589437530011   // 撮合引擎时间
+	}
+]
+```
+
+``
+GET /fapi/v3/ticker/bookTicker
+``
+
+返回当前最优的挂单(最高买单,最低卖单)
+
+**权重:**
+* 单交易对`1`   
+* 无交易对`2`
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |  描述
+------ | ------ | -------- | ------
+symbol | STRING | NO       | 交易对
+
+* 不发送交易对参数,则会返回所有交易对信息
+
+
+
+
+# Websocket 行情推送
+
+* 本篇所列出的所有wss接口baseurl: **wss://fstream.asterdex.com**
+* 订阅单一stream格式为 **/ws/\<streamName\>**
+* 组合streams的URL格式为 **/stream?streams=\<streamName1\>/\<streamName2\>/\<streamName3\>**
+* 订阅组合streams时,事件payload会以这样的格式封装 **{"stream":"\<streamName\>","data":\<rawPayload\>}**
+* stream名称中所有交易对均为**小写**
+* 每个链接有效期不超过24小时,请妥善处理断线重连。
+* 服务端每5分钟会发送ping帧,客户端应当在15分钟内回复pong帧,否则服务端会主动断开链接。允许客户端发送不成对的pong帧(即客户端可以以高于15分钟每次的频率发送pong帧保持链接)。
+* Websocket服务器每秒最多接受10个订阅消息。
+* 如果用户发送的消息超过限制,连接会被断开连接。反复被断开连接的IP有可能被服务器屏蔽。
+* 单个连接最多可以订阅 **200** 个Streams。
+
+
+
+
+## 实时订阅/取消数据流
+
+* 以下数据可以通过websocket发送以实现订阅或取消订阅数据流。示例如下。
+* 响应内容中的`id`是无符号整数,作为往来信息的唯一标识。
+
+### 订阅一个信息流
+
+> **响应**
+
+  ```javascript
+  {
+    "result": null,
+    "id": 1
+  }
+  ```
+
+* **请求**
+
+  	{    
+    	"method": "SUBSCRIBE",    
+    	"params":     
+    	[   
+      	"btcusdt@aggTrade",    
+      	"btcusdt@depth"     
+    	],    
+    	"id": 1   
+  	}
+
+
+
+### 取消订阅一个信息流
+
+> **响应**
+  
+  ```javascript
+  {
+    "result": null,
+    "id": 312
+  }
+  ```
+
+* **请求**
+
+  {   
+    "method": "UNSUBSCRIBE",    
+    "params":     
+    [    
+      "btcusdt@depth"   
+    ],    
+    "id": 312   
+  }
+
+
+
+### 已订阅信息流
+
+> **响应**
+  
+  ```javascript
+  {
+    "result": [
+      "btcusdt@aggTrade"
+    ],
+    "id": 3
+  }
+  ```
+
+
+* **请求**
+
+  {   
+    "method": "LIST_SUBSCRIPTIONS",    
+    "id": 3   
+  }     
+ 
+
+
+### 设定属性
+当前,唯一可以设置的属性是设置是否启用`combined`("组合")信息流。   
+当使用`/ws/`("原始信息流")进行连接时,combined属性设置为`false`,而使用 `/stream/`进行连接时则将属性设置为`true`。
+
+
+> **响应**
+  
+  ```javascript
+  {
+    "result": null
+    "id": 5
+  }
+  ```
+
+* **请求**
+
+  {    
+    "method": "SET_PROPERTY",    
+    "params":     
+    [   
+      "combined",    
+      true   
+    ],    
+    "id": 5   
+  }
+
+
+
+
+### 检索属性
+
+> **响应**
+
+  ```javascript
+  {
+    "result": true, // Indicates that combined is set to true.
+    "id": 2
+  }
+  ```
+  
+* **请求**
+  
+  {   
+    "method": "GET_PROPERTY",    
+    "params":     
+    [   
+      "combined"   
+    ],    
+    "id": 2   
+  }   
+ 
+
+
+
+### 错误信息
+
+错误信息 | 描述
+---|---
+{"code": 0, "msg": "Unknown property"} |  `SET_PROPERTY` 或 `GET_PROPERTY`中应用的参数无效
+{"code": 1, "msg": "Invalid value type: expected Boolean"} | 仅接受`true`或`false`
+{"code": 2, "msg": "Invalid request: property name must be a string"}| 提供的属性名无效
+{"code": 2, "msg": "Invalid request: request ID must be an unsigned integer"}| 参数`id`未提供或`id`值是无效类型
+{"code": 2, "msg": "Invalid request: unknown variant %s, expected one of `SUBSCRIBE`, `UNSUBSCRIBE`, `LIST_SUBSCRIPTIONS`, `SET_PROPERTY`, `GET_PROPERTY` at line 1 column 28"} | 错字提醒,或提供的值不是预期类型
+{"code": 2, "msg": "Invalid request: too many parameters"}| 数据中提供了不必要参数
+{"code": 2, "msg": "Invalid request: property name must be a string"} | 未提供属性名
+{"code": 2, "msg": "Invalid request: missing field `method` at line 1 column 73"} | 数据未提供`method`
+{"code":3,"msg":"Invalid JSON: expected value at line %s column %s"} | JSON 语法有误.
+
+
+
+
+## 最新合约价格
+aggTrade中的价格'p'或ticker/miniTicker中的价格'c'均可以作为最新成交价。
+
+## 归集交易
+
+> **Payload:**
+
+```javascript
+{
+  "e": "aggTrade",  // 事件类型
+  "E": 123456789,   // 事件时间
+  "s": "BNBUSDT",    // 交易对
+  "a": 5933014,		// 归集成交 ID
+  "p": "0.001",     // 成交价格
+  "q": "100",       // 成交量
+  "f": 100,         // 被归集的首个交易ID
+  "l": 105,         // 被归集的末次交易ID
+  "T": 123456785,   // 成交时间
+  "m": true         // 买方是否是做市方。如true,则此次成交是一个主动卖出单,否则是一个主动买入单。
+}
+```
+
+同一价格、同一方向、同一时间(100ms计算)的trade会被聚合为一条.
+
+**Stream Name:**       
+``<symbol>@aggTrade``
+
+**Update Speed:** 100ms
+
+
+
+
+
+## 最新标记价格
+
+> **Payload:**
+
+```javascript
+  {
+    "e": "markPriceUpdate",  	// 事件类型
+    "E": 1562305380000,      	// 事件时间
+    "s": "BTCUSDT",          	// 交易对
+    "p": "11794.15000000",   	// 标记价格
+    "i": "11784.62659091",		// 现货指数价格
+    "P": "11784.25641265",		// 预估结算价,仅在结算前最后一小时有参考价值
+    "r": "0.00038167",       	// 资金费率
+    "T": 1562306400000       	// 下次资金时间
+  }
+```
+
+
+**Stream Name:**    
+``<symbol>@markPrice`` 或 ``<symbol>@markPrice@1s``
+
+**Update Speed:** 3000ms 或 1000ms
+
+
+
+
+
+
+## 全市场最新标记价格
+
+> **Payload:**
+
+```javascript
+[
+  {
+    "e": "markPriceUpdate",  	// 事件类型
+    "E": 1562305380000,      	// 事件时间
+    "s": "BTCUSDT",          	// 交易对
+    "p": "11185.87786614",   	// 标记价格
+    "i": "11784.62659091"		// 现货指数价格
+    "P": "11784.25641265",		// 预估结算价,仅在结算前最后一小时有参考价值
+    "r": "0.00030000",       	// 资金费率
+    "T": 1562306400000       	// 下个资金时间
+  }
+]
+```
+
+
+**Stream Name:**    
+``!markPrice@arr`` 或 ``!markPrice@arr@1s``
+
+**Update Speed:** 3000ms 或 1000ms
+
+
+
+
+
+## K线
+
+> **Payload:**
+
+```javascript
+{
+  "e": "kline",     // 事件类型
+  "E": 123456789,   // 事件时间
+  "s": "BNBUSDT",    // 交易对
+  "k": {
+    "t": 123400000, // 这根K线的起始时间
+    "T": 123460000, // 这根K线的结束时间
+    "s": "BNBUSDT",  // 交易对
+    "i": "1m",      // K线间隔
+    "f": 100,       // 这根K线期间第一笔成交ID
+    "L": 200,       // 这根K线期间末一笔成交ID
+    "o": "0.0010",  // 这根K线期间第一笔成交价
+    "c": "0.0020",  // 这根K线期间末一笔成交价
+    "h": "0.0025",  // 这根K线期间最高成交价
+    "l": "0.0015",  // 这根K线期间最低成交价
+    "v": "1000",    // 这根K线期间成交量
+    "n": 100,       // 这根K线期间成交笔数
+    "x": false,     // 这根K线是否完结(是否已经开始下一根K线)
+    "q": "1.0000",  // 这根K线期间成交额
+    "V": "500",     // 主动买入的成交量
+    "Q": "0.500",   // 主动买入的成交额
+    "B": "123456"   // 忽略此参数
+  }
+}
+```
+
+K线stream逐秒推送所请求的K线种类(最新一根K线)的更新。推送间隔250毫秒(如有刷新)
+
+**订阅Kline需要提供间隔参数,最短为分钟线,最长为月线。支持以下间隔:**
+
+m -> 分钟; h -> 小时; d -> 天; w -> 周; M -> 月
+
+* 1m
+* 3m
+* 5m
+* 15m
+* 30m
+* 1h
+* 2h
+* 4h
+* 6h
+* 8h
+* 12h
+* 1d
+* 3d
+* 1w
+* 1M
+
+**Stream Name:**    
+``<symbol>@kline_<interval>``
+
+**Update Speed:** 250ms
+
+
+
+
+## 按Symbol的精简Ticker
+
+> **Payload:**
+
+```javascript
+  {
+    "e": "24hrMiniTicker",  // 事件类型
+    "E": 123456789,         // 事件时间(毫秒)
+    "s": "BNBUSDT",          // 交易对
+    "c": "0.0025",          // 最新成交价格
+    "o": "0.0010",          // 24小时前开始第一笔成交价格
+    "h": "0.0025",          // 24小时内最高成交价
+    "l": "0.0010",          // 24小时内最低成交价
+    "v": "10000",           // 成交量
+    "q": "18"               // 成交额
+  }
+```
+
+按Symbol刷新的24小时精简ticker信息.
+
+**Stream Name:**     
+``<symbol>@miniTicker`
+
+**Update Speed:** 500ms
+
+
+
+## 全市场的精简Ticker
+
+> **Payload:**
+
+```javascript
+[  
+  {
+    "e": "24hrMiniTicker",  // 事件类型
+    "E": 123456789,         // 事件时间(毫秒)
+    "s": "BNBUSDT",          // 交易对
+    "c": "0.0025",          // 最新成交价格
+    "o": "0.0010",          // 24小时前开始第一笔成交价格
+    "h": "0.0025",          // 24小时内最高成交价
+    "l": "0.0010",          // 24小时内最低成交价
+    "v": "10000",           // 成交量
+    "q": "18"               // 成交额
+  }
+]
+```
+
+所有symbol24小时精简ticker信息.需要注意的是,只有发生变化的ticker更新才会被推送。
+
+**Stream Name:**     
+`!miniTicker@arr`
+
+**Update Speed:** 1000ms
+
+
+
+
+## 按Symbol的完整Ticker
+
+
+> **Payload:**
+
+```javascript
+{
+  "e": "24hrTicker",  // 事件类型
+  "E": 123456789,     // 事件时间
+  "s": "BNBUSDT",      // 交易对
+  "p": "0.0015",      // 24小时价格变化
+  "P": "250.00",      // 24小时价格变化(百分比)
+  "w": "0.0018",      // 平均价格
+  "c": "0.0025",      // 最新成交价格
+  "Q": "10",          // 最新成交价格上的成交量
+  "o": "0.0010",      // 24小时内第一比成交的价格
+  "h": "0.0025",      // 24小时内最高成交价
+  "l": "0.0010",      // 24小时内最低成交价
+  "v": "10000",       // 24小时内成交量
+  "q": "18",          // 24小时内成交额
+  "O": 0,             // 统计开始时间
+  "C": 86400000,      // 统计关闭时间
+  "F": 0,             // 24小时内第一笔成交交易ID
+  "L": 18150,         // 24小时内最后一笔成交交易ID
+  "n": 18151          // 24小时内成交数
+}
+```
+
+按Symbol刷新的24小时完整ticker信息
+
+**Stream Name:**     
+``<symbol>@ticker``
+
+**Update Speed:** 500ms
+
+
+
+## 全市场的完整Ticker
+
+
+> **Payload:**
+
+```javascript
+[
+	{
+	  "e": "24hrTicker",  // 事件类型
+	  "E": 123456789,     // 事件时间
+	  "s": "BNBUSDT",      // 交易对
+	  "p": "0.0015",      // 24小时价格变化
+	  "P": "250.00",      // 24小时价格变化(百分比)
+	  "w": "0.0018",      // 平均价格
+	  "c": "0.0025",      // 最新成交价格
+	  "Q": "10",          // 最新成交价格上的成交量
+	  "o": "0.0010",      // 24小时内第一比成交的价格
+	  "h": "0.0025",      // 24小时内最高成交价
+	  "l": "0.0010",      // 24小时内最低成交价
+	  "v": "10000",       // 24小时内成交量
+	  "q": "18",          // 24小时内成交额
+	  "O": 0,             // 统计开始时间
+	  "C": 86400000,      // 统计结束时间
+	  "F": 0,             // 24小时内第一笔成交交易ID
+	  "L": 18150,         // 24小时内最后一笔成交交易ID
+	  "n": 18151          // 24小时内成交数
+	}
+]	
+```
+
+所有symbol 24小时完整ticker信息.需要注意的是,只有发生变化的ticker更新才会被推送。
+
+**Stream Name:**     
+``!ticker@arr``
+
+**Update Speed:** 1000ms
+
+
+## 按Symbol的最优挂单信息
+
+> **Payload:**
+
+```javascript
+{
+  "e":"bookTicker",		// 事件类型
+  "u":400900217,     	// 更新ID
+  "E": 1568014460893,	// 事件推送时间
+  "T": 1568014460891,	// 撮合时间
+  "s":"BNBUSDT",     	// 交易对
+  "b":"25.35190000", 	// 买单最优挂单价格
+  "B":"31.21000000", 	// 买单最优挂单数量
+  "a":"25.36520000", 	// 卖单最优挂单价格
+  "A":"40.66000000"  	// 卖单最优挂单数量
+}
+```
+
+
+实时推送指定交易对最优挂单信息
+
+**Stream Name:** `<symbol>@bookTicker`
+
+**Update Speed:** 实时
+
+
+
+
+
+## 全市场最优挂单信息
+
+> **Payload:**
+
+```javascript
+{
+  // Same as <symbol>@bookTicker payload
+}
+```
+
+所有交易对交易对最优挂单信息
+
+**Stream Name:** `!bookTicker`
+
+**Update Speed:** 实时
+
+
+
+##强平订单
+
+> **Payload:**
+
+```javascript
+{
+
+	"e":"forceOrder",                   // 事件类型
+	"E":1568014460893,                  // 事件时间
+	"o":{
+	
+		"s":"BTCUSDT",                   // 交易对
+		"S":"SELL",                      // 订单方向
+		"o":"LIMIT",                     // 订单类型
+		"f":"IOC",                       // 有效方式
+		"q":"0.014",                     // 订单数量
+		"p":"9910",                      // 订单价格
+		"ap":"9910",                     // 平均价格
+		"X":"FILLED",                    // 订单状态
+		"l":"0.014",                     // 订单最近成交量
+		"z":"0.014",                     // 订单累计成交量
+		"T":1568014460893,          	 // 交易时间
+	
+	}
+
+}
+```
+
+推送特定`symbol`的强平订单快照信息。
+
+1000ms内至多仅推送一条最近的强平订单作为快照
+
+**Stream Name:**  ``<symbol>@forceOrder``
+
+**Update Speed:** 1000ms
+
+
+
+
+
+## 有限档深度信息
+
+> **Payload:**
+
+```javascript
+{
+  "e": "depthUpdate", 			// 事件类型
+  "E": 1571889248277, 			// 事件时间
+  "T": 1571889248276, 			// 交易时间
+  "s": "BTCUSDT",
+  "U": 390497796,
+  "u": 390497878,
+  "pu": 390497794,
+  "b": [          				// 买方
+    [
+      "7403.89",  				// 价格
+      "0.002"     				// 数量
+    ],
+    [
+      "7403.90",
+      "3.906"
+    ],
+    [
+      "7404.00",
+      "1.428"
+    ],
+    [
+      "7404.85",
+      "5.239"
+    ],
+    [
+      "7405.43",
+      "2.562"
+    ]
+  ],
+  "a": [          				// 卖方
+    [
+      "7405.96",  				// 价格
+      "3.340"     				// 数量
+    ],
+    [
+      "7406.63",
+      "4.525"
+    ],
+    [
+      "7407.08",
+      "2.475"
+    ],
+    [
+      "7407.15",
+      "4.800"
+    ],
+    [
+      "7407.20",
+      "0.175"
+    ]
+  ]
+}
+```
+
+推送有限档深度信息。levels表示几档买卖单信息, 可选 5/10/20档
+
+**Stream Names:** `<symbol>@depth<levels>` 或 `<symbol>@depth<levels>@500ms` 或 `<symbol>@depth<levels>@100ms`.  
+
+**Update Speed:** 250ms 或 500ms 或 100ms
+
+
+
+
+## 增量深度信息
+
+> **Payload:**
+
+```javascript
+{
+  "e": "depthUpdate", 	// 事件类型
+  "E": 123456789,     	// 事件时间
+  "T": 123456788,     	// 撮合时间
+  "s": "BNBUSDT",      	// 交易对
+  "U": 157,           	// 从上次推送至今新增的第一个 update Id
+  "u": 160,           	// 从上次推送至今新增的最后一个 update Id
+  "pu": 149,          	// 上次推送的最后一个update Id(即上条消息的‘u’)
+  "b": [              	// 变动的买单深度
+    [
+      "0.0024",       	// 价格
+      "10"           	// 数量
+    ]
+  ],
+  "a": [              	// 变动的卖单深度
+    [
+      "0.0026",       	// 价格
+      "100"          	// 数量
+    ]
+  ]
+}
+```
+
+orderbook的变化部分,推送间隔250毫秒,500毫秒,100毫秒(如有刷新)
+
+**Stream 名称:**     
+``<symbol>@depth`` OR ``<symbol>@depth@500ms`` OR ``<symbol>@depth@100ms``
+
+**Update Speed:** 250ms 或 500ms 或 100ms
+
+
+## 如何正确在本地维护一个orderbook副本
+1. 订阅 **wss://fstream.asterdex.com/stream?streams=btcusdt@depth**
+2. 开始缓存收到的更新。同一个价位,后收到的更新覆盖前面的。
+3. 访问Rest接口 **https://fapi.asterdex.com/fapi/v3/depth?symbol=BTCUSDT&limit=1000**获得一个1000档的深度快照
+4. 将目前缓存到的信息中`u`< 步骤3中获取到的快照中的`lastUpdateId`的部分丢弃(丢弃更早的信息,已经过期)。
+5. 将深度快照中的内容更新到本地orderbook副本中,并从websocket接收到的第一个`U` <= `lastUpdateId` **且** `u` >= `lastUpdateId` 的event开始继续更新本地副本。
+6. 每一个新event的`pu`应该等于上一个event的`u`,否则可能出现了丢包,请从step3重新进行初始化。
+7. 每一个event中的挂单量代表这个价格目前的挂单量**绝对值**,而不是相对变化。
+8. 如果某个价格对应的挂单量为0,表示该价位的挂单已经撤单或者被吃,应该移除这个价位。
+
+
+
+
+# 账户和交易接口
+
+<aside class="warning">
+考虑到剧烈行情下, RESTful接口可能存在查询延迟,我们强烈建议您优先从Websocket user data stream推送的消息来获取订单,成交,仓位等信息。
+</aside>
+
+
+## 更改持仓模式(TRADE)
+
+> **响应:**
+
+```javascript
+{
+	"code": 200,
+	"msg": "success"
+}
+```
+
+``
+POST /fapi/v3/positionSide/dual (HMAC SHA256)
+``
+
+变换用户在 ***所有symbol*** 合约上的持仓模式:双向持仓或单向持仓。   
+
+**权重:**
+1
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |       描述
+---------- | ------ | -------- | -----------------
+dualSidePosition | STRING   | YES      | "true": 双向持仓模式;"false": 单向持仓模式
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+
+## 查询持仓模式(USER_DATA)
+
+> **响应:**
+
+```javascript
+{
+	"dualSidePosition": true // "true": 双向持仓模式;"false": 单向持仓模式
+}
+```
+
+``
+GET /fapi/v3/positionSide/dual (HMAC SHA256)
+``
+
+查询用户目前在 ***所有symbol*** 合约上的持仓模式:双向持仓或单向持仓。     
+
+**权重:**
+30
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |       描述
+---------- | ------ | -------- | -----------------
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+## 更改联合保证金模式(TRADE)
+
+> **响应:**
+
+```javascript
+{
+	"code": 200,
+	"msg": "success"
+}
+```
+
+``
+POST /fapi/v3/multiAssetsMargin (HMAC SHA256)
+``
+
+变换用户在 ***所有symbol*** 合约上的联合保证金模式:开启或关闭联合保证金模式。   
+
+**权重:**
+1
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |       描述
+---------- | ------ | -------- | -----------------
+multiAssetsMargin | STRING   | YES      | "true": 联合保证金模式开启;"false": 联合保证金模式关闭
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+
+## 查询联合保证金模式(USER_DATA)
+
+> **响应:**
+
+```javascript
+{
+	"multiAssetsMargin": true // "true": 联合保证金模式开启;"false": 联合保证金模式关闭
+}
+```
+
+``
+GET /fapi/v3/multiAssetsMargin (HMAC SHA256)
+``
+
+查询用户目前在 ***所有symbol*** 合约上的联合保证金模式。      
+
+**权重:**
+30
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |       描述
+---------- | ------ | -------- | -----------------
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+## 下单 (TRADE)
+
+
+> **响应:**
+
+```javascript
+{
+ 	"clientOrderId": "testOrder", // 用户自定义的订单号
+ 	"cumQty": "0",
+ 	"cumQuote": "0", // 成交金额
+ 	"executedQty": "0", // 成交量
+ 	"orderId": 22542179, // 系统订单号
+ 	"avgPrice": "0.00000",	// 平均成交价
+ 	"origQty": "10", // 原始委托数量
+ 	"price": "0", // 委托价格
+ 	"reduceOnly": false, // 仅减仓
+ 	"side": "SELL", // 买卖方向
+ 	"positionSide": "SHORT", // 持仓方向
+ 	"status": "NEW", // 订单状态
+ 	"stopPrice": "0", // 触发价,对`TRAILING_STOP_MARKET`无效
+ 	"closePosition": false,   // 是否条件全平仓
+ 	"symbol": "BTCUSDT", // 交易对
+ 	"timeInForce": "GTC", // 有效方法
+ 	"type": "TRAILING_STOP_MARKET", // 订单类型
+ 	"origType": "TRAILING_STOP_MARKET",  // 触发前订单类型
+ 	"activatePrice": "9020", // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"priceRate": "0.3",	// 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+ 	"updateTime": 1566818724722, // 更新时间
+ 	"workingType": "CONTRACT_PRICE", // 条件价格触发类型
+ 	"priceProtect": false            // 是否开启条件单触发保护
+}
+```
+
+``
+POST /fapi/v3/order  (HMAC SHA256)
+``
+
+**权重:**
+1
+
+**参数:**
+
+名称              |  类型   | 是否必需   | 描述
+---------------- | ------- | -------- | ---
+symbol           | STRING  | YES      | 交易对
+side             | ENUM    | YES      | 买卖方向 `SELL`, `BUY`
+positionSide     | ENUM	    | NO       | 持仓方向,单向持仓模式下非必填,默认且仅可填`BOTH`;在双向持仓模式下必填,且仅可选择 `LONG` 或 `SHORT`  
+type             | ENUM    | YES      | 订单类型 `LIMIT`, `MARKET`, `STOP`, `TAKE_PROFIT`, `STOP_MARKET`, `TAKE_PROFIT_MARKET`, `TRAILING_STOP_MARKET`
+reduceOnly       | STRING  | NO       | `true`, `false`; 非双开模式下默认`false`;双开模式下不接受此参数; 使用`closePosition`不支持此参数。
+quantity         | DECIMAL | NO     	 | 下单数量,使用`closePosition`不支持此参数。
+price            | DECIMAL | NO       | 委托价格
+newClientOrderId | STRING  | NO       | 用户自定义的订单号,不可以重复出现在挂单中。如空缺系统会自动赋值。必须满足正则规则 `^[\.A-Z\:/a-z0-9_-]{1,36}$`
+stopPrice        | DECIMAL | NO       | 触发价, 仅 `STOP`, `STOP_MARKET`, `TAKE_PROFIT`, `TAKE_PROFIT_MARKET` 需要此参数
+closePosition    | STRING  | NO       | `true`, `false`;触发后全部平仓,仅支持`STOP_MARKET`和`TAKE_PROFIT_MARKET`;不与`quantity`合用;自带只平仓效果,不与`reduceOnly` 合用
+activationPrice  | DECIMAL | NO       | 追踪止损激活价格,仅`TRAILING_STOP_MARKET` 需要此参数, 默认为下单当前市场价格(支持不同`workingType`)
+callbackRate     | DECIMAL | NO       | 追踪止损回调比例,可取值范围[0.1, 5],其中 1代表1% ,仅`TRAILING_STOP_MARKET` 需要此参数
+timeInForce      | ENUM    | NO       | 有效方法
+workingType      | ENUM    | NO       | stopPrice 触发类型: `MARK_PRICE`(标记价格), `CONTRACT_PRICE`(合约最新价). 默认 `CONTRACT_PRICE`
+priceProtect | STRING | NO | 条件单触发保护:"TRUE","FALSE", 默认"FALSE". 仅 `STOP`, `STOP_MARKET`, `TAKE_PROFIT`, `TAKE_PROFIT_MARKET` 需要此参数
+newOrderRespType | ENUM    | NO       | "ACK", "RESULT", 默认 "ACK"
+recvWindow       | LONG    | NO       |
+timestamp        | LONG    | YES      |
+
+根据 order `type`的不同,某些参数强制要求,具体如下:
+
+Type                 |           强制要求的参数
+----------------------------------- | ----------------------------------
+`LIMIT`                             | `timeInForce`, `quantity`, `price`
+`MARKET`                            | `quantity`
+`STOP`, `TAKE_PROFIT`               | `quantity`,  `price`, `stopPrice`
+`STOP_MARKET`, `TAKE_PROFIT_MARKET` | `stopPrice`
+`TRAILING_STOP_MARKET`              | `callbackRate`
+
+
+
+* 条件单的触发必须:
+	
+	* 如果订单参数`priceProtect`为true:
+		* 达到触发价时,`MARK_PRICE`(标记价格)与`CONTRACT_PRICE`(合约最新价)之间的价差不能超过改symbol触发保护阈值
+		* 触发保护阈值请参考接口`GET /fapi/v3/exchangeInfo` 返回内容相应symbol中"triggerProtect"字段
+
+	* `STOP`, `STOP_MARKET` 止损单:
+		* 买入: 最新合约价格/标记价格高于等于触发价`stopPrice`
+		* 卖出: 最新合约价格/标记价格低于等于触发价`stopPrice`
+	* `TAKE_PROFIT`, `TAKE_PROFIT_MARKET` 止盈单:
+		* 买入: 最新合约价格/标记价格低于等于触发价`stopPrice`
+		* 卖出: 最新合约价格/标记价格高于等于触发价`stopPrice`
+
+	* `TRAILING_STOP_MARKET` 跟踪止损单:
+		* 买入: 当合约价格/标记价格区间最低价格低于激活价格`activationPrice`,且最新合约价格/标记价高于等于最低价设定回调幅度。
+		* 卖出: 当合约价格/标记价格区间最高价格高于激活价格`activationPrice`,且最新合约价格/标记价低于等于最高价设定回调幅度。
+
+* `TRAILING_STOP_MARKET` 跟踪止损单如果遇到报错 ``{"code": -2021, "msg": "Order would immediately trigger."}``    
+表示订单不满足以下条件:
+	* 买入: 指定的`activationPrice` 必须小于 latest price
+	* 卖出: 指定的`activationPrice` 必须大于 latest price
+
+* `newOrderRespType` 如果传 `RESULT`:
+	* `MARKET` 订单将直接返回成交结果;
+	* 配合使用特殊 `timeInForce` 的 `LIMIT` 订单将直接返回成交或过期拒绝结果。
+
+* `STOP_MARKET`, `TAKE_PROFIT_MARKET` 配合 `closePosition`=`true`:
+	* 条件单触发依照上述条件单触发逻辑
+	* 条件触发后,平掉当时持有所有多头仓位(若为卖单)或当时持有所有空头仓位(若为买单)
+	* 不支持 `quantity` 参数
+	* 自带只平仓属性,不支持`reduceOnly`参数
+	* 双开模式下,`LONG`方向上不支持`BUY`; `SHORT` 方向上不支持`SELL`
+
+
+## 测试下单接口 (TRADE)
+
+
+> **响应:**
+
+```javascript
+字段与下单接口一致,但均为无效值
+```
+
+
+``
+POST /fapi/v3/order/test (HMAC SHA256)
+``
+
+用于测试订单请求,但不会提交到撮合引擎
+
+**权重:**
+1
+
+**参数:**
+
+参考 `POST /fapi/v3/order`
+
+
+
+## 批量下单 (TRADE)
+
+
+> **响应:**
+
+```javascript
+[
+	{
+	 	"clientOrderId": "testOrder", // 用户自定义的订单号
+	 	"cumQty": "0",
+	 	"cumQuote": "0", // 成交金额
+	 	"executedQty": "0", // 成交量
+	 	"orderId": 22542179, // 系统订单号
+	 	"avgPrice": "0.00000",	// 平均成交价
+	 	"origQty": "10", // 原始委托数量
+	 	"price": "0", // 委托价格
+	 	"reduceOnly": false, // 仅减仓
+	 	"side": "SELL", // 买卖方向
+	 	"positionSide": "SHORT", // 持仓方向
+	 	"status": "NEW", // 订单状态
+	 	"stopPrice": "0", // 触发价,对`TRAILING_STOP_MARKET`无效
+	 	"closePosition": false,   // 是否条件全平仓
+	 	"symbol": "BTCUSDT", // 交易对
+	 	"timeInForce": "GTC", // 有效方法
+	 	"type": "TRAILING_STOP_MARKET", // 订单类型
+	 	"origType": "TRAILING_STOP_MARKET",  // 触发前订单类型
+	 	"activatePrice": "9020", // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+	  	"priceRate": "0.3",	// 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+	 	"updateTime": 1566818724722, // 更新时间
+	 	"workingType": "CONTRACT_PRICE", // 条件价格触发类型
+	 	"priceProtect": false            // 是否开启条件单触发保护
+	},
+	{
+		"code": -2022, 
+		"msg": "ReduceOnly Order is rejected."
+	}
+]
+```
+
+``
+POST /fapi/v3/batchOrders  (HMAC SHA256)
+``
+
+**权重:**
+5
+
+**参数:**
+
+
+名称              |  类型   | 是否必需   | 描述
+---------------- | ------- | -------- | ----
+batchOrders |	list<JSON> | 	YES |	订单列表,最多支持5个订单
+recvWindow |	LONG |	NO	
+timestamp	| LONG | YES	
+
+**其中``batchOrders``应以list of JSON格式填写订单参数**
+
+名称              |  类型   | 是否必需   | 描述
+---------------- | ------- | -------- | ----
+symbol           | STRING  | YES      | 交易对
+side             | ENUM    | YES      | 买卖方向 `SELL`, `BUY`
+positionSide     | ENUM	    | NO       | 持仓方向,单向持仓模式下非必填,默认且仅可填`BOTH`;在双向持仓模式下必填,且仅可选择 `LONG` 或 `SHORT`   
+type             | ENUM    | YES      | 订单类型 `LIMIT`, `MARKET`, `STOP`, `TAKE_PROFIT`, `STOP_MARKET`, `TAKE_PROFIT_MARKET`, `TRAILING_STOP_MARKET`
+reduceOnly       | STRING  | NO       | `true`, `false`; 非双开模式下默认`false`;双开模式下不接受此参数。
+quantity         | DECIMAL | YES      | 下单数量
+price            | DECIMAL | NO       | 委托价格
+newClientOrderId | STRING  | NO       | 用户自定义的订单号,不可以重复出现在挂单中。如空缺系统会自动赋值. 必须满足正则规则 `^[\.A-Z\:/a-z0-9_-]{1,36}$`
+stopPrice        | DECIMAL | NO       | 触发价, 仅 `STOP`, `STOP_MARKET`, `TAKE_PROFIT`, `TAKE_PROFIT_MARKET` 需要此参数
+activationPrice  | DECIMAL | NO       | 追踪止损激活价格,仅`TRAILING_STOP_MARKET` 需要此参数, 默认为下单当前市场价格(支持不同`workingType`)
+callbackRate     | DECIMAL | NO       | 追踪止损回调比例,可取值范围[0.1, 4],其中 1代表1% ,仅`TRAILING_STOP_MARKET` 需要此参数
+timeInForce      | ENUM    | NO       | 有效方法
+workingType      | ENUM    | NO       | stopPrice 触发类型: `MARK_PRICE`(标记价格), `CONTRACT_PRICE`(合约最新价). 默认 `CONTRACT_PRICE`
+priceProtect | STRING | NO | 条件单触发保护:"TRUE","FALSE", 默认"FALSE". 仅 `STOP`, `STOP_MARKET`, `TAKE_PROFIT`, `TAKE_PROFIT_MARKET` 需要此参数
+newOrderRespType | ENUM    | NO       | "ACK", "RESULT", 默认 "ACK"
+
+
+* 具体订单条件规则,与普通下单一致
+* 批量下单采取并发处理,不保证订单撮合顺序
+* 批量下单的返回内容顺序,与订单列表顺序一致
+
+
+
+
+## 查询订单 (USER_DATA)
+
+
+> **响应:**
+
+```javascript
+{
+  	"avgPrice": "0.00000",				// 平均成交价
+  	"clientOrderId": "abc",				// 用户自定义的订单号
+  	"cumQuote": "0",					// 成交金额
+  	"executedQty": "0",					// 成交量
+  	"orderId": 1573346959,				// 系统订单号
+  	"origQty": "0.40",					// 原始委托数量
+  	"origType": "TRAILING_STOP_MARKET",	// 触发前订单类型
+  	"price": "0",						// 委托价格
+  	"reduceOnly": false,				// 是否仅减仓
+  	"side": "BUY",						// 买卖方向
+  	"positionSide": "SHORT", 			// 持仓方向
+  	"status": "NEW",					// 订单状态
+  	"stopPrice": "9300",					// 触发价,对`TRAILING_STOP_MARKET`无效
+  	"closePosition": false,   // 是否条件全平仓
+  	"symbol": "BTCUSDT",				// 交易对
+  	"time": 1579276756075,				// 订单时间
+  	"timeInForce": "GTC",				// 有效方法
+  	"type": "TRAILING_STOP_MARKET",		// 订单类型
+  	"activatePrice": "9020",			// 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"priceRate": "0.3",					// 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"updateTime": 1579276756075,		// 更新时间
+  	"workingType": "CONTRACT_PRICE", // 条件价格触发类型
+ 	"priceProtect": false            // 是否开启条件单触发保护
+}
+```
+
+``
+GET /fapi/v3/order (HMAC SHA256)
+``
+
+查询订单状态
+
+* 请注意,如果订单满足如下条件,不会被查询到:
+	* 订单的最终状态为 `CANCELED` 或者 `EXPIRED`, **并且** 
+	* 订单没有任何的成交记录, **并且**
+	* 订单生成时间 + 7天 < 当前时间
+
+**权重:**
+1
+
+**参数:**
+
+名称        |  类型  | 是否必需 | 描述
+----------------- | ------ | -------- | ----
+symbol            | STRING | YES      | 交易对
+orderId           | LONG   | NO       | 系统订单号
+origClientOrderId | STRING | NO       | 用户自定义的订单号
+recvWindow        | LONG   | NO       |
+timestamp         | LONG   | YES      |
+
+注意:
+
+* 至少需要发送 `orderId` 与 `origClientOrderId`中的一个
+
+
+## 撤销订单 (TRADE)
+
+> **响应:**
+
+```javascript
+{
+ 	"clientOrderId": "myOrder1", // 用户自定义的订单号
+ 	"cumQty": "0",
+ 	"cumQuote": "0", // 成交金额
+ 	"executedQty": "0", // 成交量
+ 	"orderId": 283194212, // 系统订单号
+ 	"origQty": "11", // 原始委托数量
+ 	"price": "0", // 委托价格
+	"reduceOnly": false, // 仅减仓
+	"side": "BUY", // 买卖方向
+	"positionSide": "SHORT", // 持仓方向
+ 	"status": "CANCELED", // 订单状态
+ 	"stopPrice": "9300", // 触发价,对`TRAILING_STOP_MARKET`无效
+ 	"closePosition": false,   // 是否条件全平仓
+ 	"symbol": "BTCUSDT", // 交易对
+ 	"timeInForce": "GTC", // 有效方法
+ 	"origType": "TRAILING_STOP_MARKET",	// 触发前订单类型
+ 	"type": "TRAILING_STOP_MARKET", // 订单类型
+ 	"activatePrice": "9020", // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"priceRate": "0.3",	// 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+ 	"updateTime": 1571110484038, // 更新时间
+ 	"workingType": "CONTRACT_PRICE", // 条件价格触发类型
+ 	"priceProtect": false            // 是否开启条件单触发保护
+}
+```
+
+``
+DELETE /fapi/v3/order  (HMAC SHA256)
+``
+
+**权重:**
+1
+
+**Parameters:**
+
+名称               |  类型   | 是否必需  |        描述
+----------------- | ------ | -------- | ------------------
+symbol            | STRING | YES      | 交易对
+orderId           | LONG   | NO       | 系统订单号
+origClientOrderId | STRING | NO       | 用户自定义的订单号
+recvWindow        | LONG   | NO       |
+timestamp         | LONG   | YES      |
+
+`orderId` 与 `origClientOrderId` 必须至少发送一个
+
+
+## 撤销全部订单 (TRADE)
+
+> **响应:**
+
+```javascript
+{
+	"code": "200", 
+	"msg": "The operation of cancel all open order is done."
+}
+```
+
+``
+DELETE /fapi/v3/allOpenOrders  (HMAC SHA256)
+``
+
+**权重:**
+1
+
+**Parameters:**
+
+   名称    |  类型  | 是否必需 |  描述
+---------- | ------ | -------- | ------
+symbol     | STRING | YES      | 交易对
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+## 批量撤销订单 (TRADE)
+
+> **响应:**
+
+```javascript
+[
+	{
+	 	"clientOrderId": "myOrder1", // 用户自定义的订单号
+	 	"cumQty": "0",
+	 	"cumQuote": "0", // 成交金额
+	 	"executedQty": "0", // 成交量
+	 	"orderId": 283194212, // 系统订单号
+	 	"origQty": "11", // 原始委托数量
+	 	"price": "0", // 委托价格
+		"reduceOnly": false, // 仅减仓
+		"side": "BUY", // 买卖方向
+		"positionSide": "SHORT", // 持仓方向
+	 	"status": "CANCELED", // 订单状态
+	 	"stopPrice": "9300", // 触发价,对`TRAILING_STOP_MARKET`无效
+	 	"closePosition": false,   // 是否条件全平仓
+	 	"symbol": "BTCUSDT", // 交易对
+	 	"timeInForce": "GTC", // 有效方法
+	 	"origType": "TRAILING_STOP_MARKET", // 触发前订单类型
+ 		"type": "TRAILING_STOP_MARKET", // 订单类型
+	 	"activatePrice": "9020", // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  		"priceRate": "0.3",	// 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+	 	"updateTime": 1571110484038, // 更新时间
+	 	"workingType": "CONTRACT_PRICE", // 条件价格触发类型
+	 	"priceProtect": false            // 是否开启条件单触发保护
+	},
+	{
+		"code": -2011,
+		"msg": "Unknown order sent."
+	}
+]
+```
+
+``
+DELETE /fapi/v3/batchOrders  (HMAC SHA256)
+``
+
+**权重:**
+1
+
+**Parameters:**
+
+  名称          |      类型      | 是否必需 |       描述
+--------------------- | -------------- | -------- | ----------------
+symbol                | STRING         | YES      | 交易对
+orderIdList           | LIST\<LONG\>   | NO       | 系统订单号, 最多支持10个订单 <br/> 比如`[1234567,2345678]`
+origClientOrderIdList | LIST\<STRING\> | NO       | 用户自定义的订单号, 最多支持10个订单 <br/> 比如`["my_id_1","my_id_2"]` 需要encode双引号。逗号后面没有空格。
+recvWindow            | LONG           | NO       |
+timestamp             | LONG           | YES      |
+
+`orderIdList` 与 `origClientOrderIdList` 必须至少发送一个,不可同时发送
+
+
+## 倒计时撤销所有订单 (TRADE)
+
+> **响应:**
+
+```javascript
+{
+	"symbol": "BTCUSDT", 
+	"countdownTime": "100000"
+}
+```
+
+
+``
+POST /fapi/v3/countdownCancelAll  (HMAC SHA256)
+``
+
+**权重:**
+10
+
+**Parameters:**
+
+  名称          |      类型      | 是否必需 |       描述
+--------------------- | -------------- | -------- | ----------------
+symbol | STRING | YES |
+countdownTime | LONG | YES | 倒计时。 1000 表示 1 秒; 0 表示取消倒计时撤单功能。
+recvWindow | LONG | NO |
+timestamp | LONG | YES |
+
+* 该接口可以被用于确保在倒计时结束时撤销指定symbol上的所有挂单。 在使用这个功能时,接口应像心跳一样在倒计时内被反复调用,以便可以取消既有的倒计时并开始新的倒数计时设置。
+
+* 用法示例:
+	以30s的间隔重复此接口,每次倒计时countdownTime设置为120000(120s)。   
+	如果在120秒内未再次调用此接口,则您指定symbol上的所有挂单都会被自动撤销。   
+	如果在120秒内以将countdownTime设置为0,则倒数计时器将终止,自动撤单功能取消。
+	
+* 系统会**大约每10毫秒**检查一次所有倒计时情况,因此请注意,使用此功能时应考虑足够的冗余。    
+我们不建议将倒记时设置得太精确或太小。
+
+
+
+
+
+## 查询当前挂单 (USER_DATA)
+
+> **响应:**
+
+```javascript
+
+{
+  	"avgPrice": "0.00000",				// 平均成交价
+  	"clientOrderId": "abc",				// 用户自定义的订单号
+  	"cumQuote": "0",						// 成交金额
+  	"executedQty": "0",					// 成交量
+  	"orderId": 1917641,					// 系统订单号
+  	"origQty": "0.40",					// 原始委托数量
+  	"origType": "TRAILING_STOP_MARKET",	// 触发前订单类型
+  	"price": "0",					// 委托价格
+  	"reduceOnly": false,				// 是否仅减仓
+  	"side": "BUY",						// 买卖方向
+  	"status": "NEW",					// 订单状态
+  	"positionSide": "SHORT", // 持仓方向
+  	"stopPrice": "9300",					// 触发价,对`TRAILING_STOP_MARKET`无效
+  	"closePosition": false,   // 是否条件全平仓
+  	"symbol": "BTCUSDT",				// 交易对
+  	"time": 1579276756075,				// 订单时间
+  	"timeInForce": "GTC",				// 有效方法
+  	"type": "TRAILING_STOP_MARKET",		// 订单类型
+  	"activatePrice": "9020", // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"priceRate": "0.3",	// 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"updateTime": 1579276756075,		// 更新时间
+  	"workingType": "CONTRACT_PRICE", // 条件价格触发类型
+ 	"priceProtect": false            // 是否开启条件单触发保护
+}
+```
+
+``
+GET /fapi/v3/openOrder  (HMAC SHA256)
+``
+
+请小心使用不带symbol参数的调用
+
+**权重: 1**
+
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |  描述
+---------- | ------ | -------- | ------
+symbol | STRING | YES | 交易对
+orderId | LONG | NO | 系统订单号
+origClientOrderId | STRING | NO | 用户自定义的订单号
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+* `orderId` 与 `origClientOrderId` 中的一个为必填参数
+* 查询的订单如果已经成交或取消,将返回报错 "Order does not exist."
+
+
+## 查看当前全部挂单 (USER_DATA)
+
+> **响应:**
+
+```javascript
+[
+  {
+  	"avgPrice": "0.00000",				// 平均成交价
+  	"clientOrderId": "abc",				// 用户自定义的订单号
+  	"cumQuote": "0",						// 成交金额
+  	"executedQty": "0",					// 成交量
+  	"orderId": 1917641,					// 系统订单号
+  	"origQty": "0.40",					// 原始委托数量
+  	"origType": "TRAILING_STOP_MARKET",	// 触发前订单类型
+  	"price": "0",					// 委托价格
+  	"reduceOnly": false,				// 是否仅减仓
+  	"side": "BUY",						// 买卖方向
+  	"positionSide": "SHORT", // 持仓方向
+  	"status": "NEW",					// 订单状态
+  	"stopPrice": "9300",					// 触发价,对`TRAILING_STOP_MARKET`无效
+  	"closePosition": false,   // 是否条件全平仓
+  	"symbol": "BTCUSDT",				// 交易对
+  	"time": 1579276756075,				// 订单时间
+  	"timeInForce": "GTC",				// 有效方法
+  	"type": "TRAILING_STOP_MARKET",		// 订单类型
+  	"activatePrice": "9020", // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"priceRate": "0.3",	// 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"updateTime": 1579276756075,		// 更新时间
+  	"workingType": "CONTRACT_PRICE", // 条件价格触发类型
+ 	"priceProtect": false            // 是否开启条件单触发保护
+  }
+]
+```
+
+``
+GET /fapi/v3/openOrders  (HMAC SHA256)
+``
+
+请小心使用不带symbol参数的调用
+
+**权重:**
+- 带symbol ***1***
+- 不带 ***40***
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |  描述
+---------- | ------ | -------- | ------
+symbol     | STRING | NO      | 交易对
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+* 不带symbol参数,会返回所有交易对的挂单
+
+
+
+## 查询所有订单(包括历史订单) (USER_DATA)
+
+
+> **响应:**
+
+```javascript
+[
+  {
+   	"avgPrice": "0.00000",				// 平均成交价
+  	"clientOrderId": "abc",				// 用户自定义的订单号
+  	"cumQuote": "0",						// 成交金额
+  	"executedQty": "0",					// 成交量
+  	"orderId": 1917641,					// 系统订单号
+  	"origQty": "0.40",					// 原始委托数量
+  	"origType": "TRAILING_STOP_MARKET",	// 触发前订单类型
+  	"price": "0",					// 委托价格
+  	"reduceOnly": false,				// 是否仅减仓
+  	"side": "BUY",						// 买卖方向
+  	"positionSide": "SHORT", // 持仓方向
+  	"status": "NEW",					// 订单状态
+  	"stopPrice": "9300",					// 触发价,对`TRAILING_STOP_MARKET`无效
+  	"closePosition": false,  			// 是否条件全平仓
+  	"symbol": "BTCUSDT",				// 交易对
+  	"time": 1579276756075,				// 订单时间
+  	"timeInForce": "GTC",				// 有效方法
+  	"type": "TRAILING_STOP_MARKET",		// 订单类型
+  	"activatePrice": "9020", // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"priceRate": "0.3",	// 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段
+  	"updateTime": 1579276756075,		// 更新时间
+  	"workingType": "CONTRACT_PRICE", // 条件价格触发类型
+ 	"priceProtect": false            // 是否开启条件单触发保护
+  }
+]
+```
+
+``
+GET /fapi/v3/allOrders (HMAC SHA256)
+``
+
+* 请注意,如果订单满足如下条件,不会被查询到:
+	* 订单的最终状态为 `CANCELED` 或者 `EXPIRED`, **并且** 
+	* 订单没有任何的成交记录, **并且**
+	* 订单生成时间 + 7天 < 当前时间
+
+**权重:**
+5 
+
+**Parameters:**
+
+   名称    |  类型  | 是否必需 |                      描述
+---------- | ------ | -------- | -----------------------------------------------
+symbol     | STRING | YES      | 交易对
+orderId    | LONG   | NO       | 只返回此orderID及之后的订单,缺省返回最近的订单
+startTime  | LONG   | NO       | 起始时间
+endTime    | LONG   | NO       | 结束时间
+limit      | INT    | NO       | 返回的结果集数量 默认值:500 最大值:1000
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+* 查询时间范围最大不得超过7天
+* 默认查询最近7天内的数据
+
+
+
+## 账户余额v3 (USER_DATA)
+
+> **响应:**
+
+```javascript
+[
+ 	{
+ 		"accountAlias": "SgsR",    // 账户唯一识别码
+ 		"asset": "USDT",		// 资产
+ 		"balance": "122607.35137903",	// 总余额
+ 		"crossWalletBalance": "23.72469206", // 全仓余额
+  		"crossUnPnl": "0.00000000"  // 全仓持仓未实现盈亏
+  		"availableBalance": "23.72469206",       // 下单可用余额
+  		"maxWithdrawAmount": "23.72469206",     // 最大可转出余额
+  		"marginAvailable": true,    // 是否可用作联合保证金
+  		"updateTime": 1617939110373
+	}
+]
+```
+
+``
+GET /fapi/v3/balance (HMAC SHA256)
+``
+
+**Weight:**
+5
+
+**Parameters:**
+
+名称 | 类型 | 是否必需 | 描述
+------------ | ------------ | ------------ | ------------
+recvWindow | LONG | NO |
+timestamp | LONG | YES
+
+
+
+
+## 账户信息v3 (USER_DATA)
+
+> **响应:**
+
+```javascript
+
+{
+	"feeTier": 0,  // 手续费等级
+ 	"canTrade": true,  // 是否可以交易
+ 	"canDeposit": true,  // 是否可以入金
+ 	"canWithdraw": true, // 是否可以出金
+ 	"updateTime": 0,
+ 	"totalInitialMargin": "0.00000000",  // 但前所需起始保证金总额(存在逐仓请忽略), 仅计算usdt资产
+ 	"totalMaintMargin": "0.00000000",  // 维持保证金总额, 仅计算usdt资产
+ 	"totalWalletBalance": "23.72469206",   // 账户总余额, 仅计算usdt资产
+ 	"totalUnrealizedProfit": "0.00000000",  // 持仓未实现盈亏总额, 仅计算usdt资产
+ 	"totalMarginBalance": "23.72469206",  // 保证金总余额, 仅计算usdt资产
+ 	"totalPositionInitialMargin": "0.00000000",  // 持仓所需起始保证金(基于最新标记价格), 仅计算usdt资产
+ 	"totalOpenOrderInitialMargin": "0.00000000",  // 当前挂单所需起始保证金(基于最新标记价格), 仅计算usdt资产
+ 	"totalCrossWalletBalance": "23.72469206",  // 全仓账户余额, 仅计算usdt资产
+ 	"totalCrossUnPnl": "0.00000000",	// 全仓持仓未实现盈亏总额, 仅计算usdt资产
+ 	"availableBalance": "23.72469206",       // 可用余额, 仅计算usdt资产
+ 	"maxWithdrawAmount": "23.72469206"     // 最大可转出余额, 仅计算usdt资产
+ 	"assets": [
+ 		{
+ 			"asset": "USDT",	 	//资产
+ 			"walletBalance": "23.72469206",  //余额
+		   	"unrealizedProfit": "0.00000000",  // 未实现盈亏
+		   	"marginBalance": "23.72469206",  // 保证金余额
+		   	"maintMargin": "0.00000000",	// 维持保证金
+		   	"initialMargin": "0.00000000",  // 当前所需起始保证金
+		   	"positionInitialMargin": "0.00000000",  // 持仓所需起始保证金(基于最新标记价格)
+		   	"openOrderInitialMargin": "0.00000000", // 当前挂单所需起始保证金(基于最新标记价格)
+		   	"crossWalletBalance": "23.72469206",  //全仓账户余额
+		   	"crossUnPnl": "0.00000000" // 全仓持仓未实现盈亏
+		   	"availableBalance": "23.72469206",       // 可用余额
+		   	"maxWithdrawAmount": "23.72469206",     // 最大可转出余额
+		   	"marginAvailable": true,   // 是否可用作联合保证金
+		   	"updateTime": 1625474304765  //更新时间
+		},
+		{
+ 			"asset": "BUSD",	 	//资产
+ 			"walletBalance": "103.12345678",  //余额
+		   	"unrealizedProfit": "0.00000000",  // 未实现盈亏
+		   	"marginBalance": "103.12345678",  // 保证金余额
+		   	"maintMargin": "0.00000000",	// 维持保证金
+		   	"initialMargin": "0.00000000",  // 当前所需起始保证金
+		   	"positionInitialMargin": "0.00000000",  // 持仓所需起始保证金(基于最新标记价格)
+		   	"openOrderInitialMargin": "0.00000000", // 当前挂单所需起始保证金(基于最新标记价格)
+		   	"crossWalletBalance": "103.12345678",  //全仓账户余额
+		   	"crossUnPnl": "0.00000000" // 全仓持仓未实现盈亏
+		   	"availableBalance": "103.12345678",       // 可用余额
+		   	"maxWithdrawAmount": "103.12345678",     // 最大可转出余额
+		   	"marginAvailable": true,   // 否可用作联合保证金
+		   	"updateTime": 0  // 更新时间
+	       }
+	],
+ 	"positions": [  // 头寸,将返回所有市场symbol。
+ 		//根据用户持仓模式展示持仓方向,即单向模式下只返回BOTH持仓情况,双向模式下只返回 LONG 和 SHORT 持仓情况
+ 		{
+		 	"symbol": "BTCUSDT",  // 交易对
+		   	"initialMargin": "0",	// 当前所需起始保证金(基于最新标记价格)
+		   	"maintMargin": "0",	//维持保证金
+		   	"unrealizedProfit": "0.00000000",  // 持仓未实现盈亏
+		   	"positionInitialMargin": "0",  // 持仓所需起始保证金(基于最新标记价格)
+		   	"openOrderInitialMargin": "0",  // 当前挂单所需起始保证金(基于最新标记价格)
+		   	"leverage": "100",	// 杠杆倍率
+		   	"isolated": true,  // 是否是逐仓模式
+		   	"entryPrice": "0.00000",  // 持仓成本价
+		   	"maxNotional": "250000",  // 当前杠杆下用户可用的最大名义价值
+		   	"positionSide": "BOTH",  // 持仓方向
+		   	"positionAmt": "0",		 // 持仓数量
+		   	"updateTime": 0         // 更新时间 
+		}
+  	]
+}
+```
+
+
+``
+GET /fapi/v3/account (HMAC SHA256)
+``
+
+**权重:**
+5
+
+**参数:**
+
+名称 | 类型 | 是否必需 | 描述
+------------ | ------------ | ------------ | ------------
+recvWindow | LONG | NO |
+timestamp | LONG | YES |
+
+
+
+
+## 调整开仓杠杆 (TRADE)
+
+> **响应:**
+
+```javascript
+{
+ 	"leverage": 21,	// 杠杆倍数
+ 	"maxNotionalValue": "1000000", // 当前杠杆倍数下允许的最大名义价值
+ 	"symbol": "BTCUSDT"	// 交易对
+}
+```
+
+``
+POST /fapi/v3/leverage (HMAC SHA256)
+``
+
+调整用户在指定symbol合约的开仓杠杆。
+
+**权重:**
+1
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |            描述
+---------- | ------ | -------- | ---------------------------
+symbol     | STRING | YES      | 交易对
+leverage   | INT    | YES      | 目标杠杆倍数:1 到 125 整数
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+## 变换逐全仓模式 (TRADE)
+
+> **响应:**
+
+```javascript
+{
+	"code": 200,
+	"msg": "success"
+}
+```
+
+``
+POST /fapi/v3/marginType (HMAC SHA256)
+``
+
+变换用户在指定symbol合约上的保证金模式:逐仓或全仓。
+
+**权重:**
+1
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |       描述
+---------- | ------ | -------- | -----------------
+symbol     | STRING | YES      | 交易对
+marginType | ENUM   | YES      | 保证金模式 ISOLATED(逐仓), CROSSED(全仓)
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+## 调整逐仓保证金 (TRADE)
+
+> **响应:**
+
+```javascript
+{
+	"amount": 100.0,
+  	"code": 200,
+  	"msg": "Successfully modify position margin.",
+  	"type": 1
+}
+```
+
+``
+POST /fapi/v3/positionMargin (HMAC SHA256)
+``
+
+针对逐仓模式下的仓位,调整其逐仓保证金资金。
+
+**权重:**
+1
+
+**参数:**
+
+   名称    |  类型   | 是否必需 |                 描述
+---------- | ------- | -------- | ------------------------------------
+symbol     | STRING  | YES      | 交易对
+positionSide| ENUM   | NO		  | 持仓方向,单向持仓模式下非必填,默认且仅可填`BOTH`;在双向持仓模式下必填,且仅可选择 `LONG` 或 `SHORT` 
+amount     | DECIMAL | YES      | 保证金资金
+type       | INT     | YES      | 调整方向 1: 增加逐仓保证金,2: 减少逐仓保证金
+recvWindow | LONG    | NO       |
+timestamp  | LONG    | YES      |
+
+* 只针对逐仓symbol 与 positionSide(如有)
+
+
+## 逐仓保证金变动历史 (TRADE)
+
+> **响应:**
+
+```javascript
+[
+	{
+		"amount": "23.36332311", // 数量
+	  	"asset": "USDT", // 资产
+	  	"symbol": "BTCUSDT", // 交易对
+	  	"time": 1578047897183, // 时间
+	  	"type": 1,	// 调整方向
+	  	"positionSide": "BOTH"  // 持仓方向
+	},
+	{
+		"amount": "100",
+	  	"asset": "USDT",
+	  	"symbol": "BTCUSDT",
+	  	"time": 1578047900425,
+	  	"type": 1,
+	  	"positionSide": "LONG" 
+	}
+]
+```
+
+``
+GET /fapi/v3/positionMargin/history (HMAC SHA256)
+``
+
+
+
+**权重:**
+1
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |                 描述
+---------- | ------ | -------- | ------------------------------------
+symbol     | STRING | YES      | 交易对
+type       | INT    | NO       | 调整方向 1: 增加逐仓保证金,2: 减少逐仓保证金
+startTime  | LONG   | NO       | 起始时间
+endTime    | LONG   | NO       | 结束时间
+limit      | INT    | NO       | 返回的结果集数量 默认值: 500
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+
+
+## 用户持仓风险v3 (USER_DATA)
+
+> **响应:**
+
+> 单向持仓模式下:
+
+```javascript
+[
+  	{
+  		"entryPrice": "0.00000", // 开仓均价
+  		"marginType": "isolated", // 逐仓模式或全仓模式
+  		"isAutoAddMargin": "false",
+  		"isolatedMargin": "0.00000000",	// 逐仓保证金
+  		"leverage": "10", // 当前杠杆倍数
+  		"liquidationPrice": "0", // 参考强平价格
+  		"markPrice": "6679.50671178",	// 当前标记价格
+  		"maxNotionalValue": "20000000", // 当前杠杆倍数允许的名义价值上限
+  		"positionAmt": "0.000", // 头寸数量,符号代表多空方向, 正数为多,负数为空
+  		"symbol": "BTCUSDT", // 交易对
+  		"unRealizedProfit": "0.00000000", // 持仓未实现盈亏
+  		"positionSide": "BOTH", // 持仓方向
+  		"updateTime": 1625474304765   // 更新时间
+  	}
+]
+```
+
+> 双向持仓模式下:
+
+```javascript
+[
+  	{
+  		"entryPrice": "6563.66500", // 开仓均价
+  		"marginType": "isolated", // 逐仓模式或全仓模式
+  		"isAutoAddMargin": "false",
+  		"isolatedMargin": "15517.54150468", // 逐仓保证金
+  		"leverage": "10", // 当前杠杆倍数
+  		"liquidationPrice": "5930.78", // 参考强平价格
+  		"markPrice": "6679.50671178",	// 当前标记价格
+  		"maxNotionalValue": "20000000", // 当前杠杆倍数允许的名义价值上限
+  		"positionAmt": "20.000", // 头寸数量,符号代表多空方向, 正数为多,负数为空
+  		"symbol": "BTCUSDT", // 交易对
+  		"unRealizedProfit": "2316.83423560" // 持仓未实现盈亏
+  		"positionSide": "LONG", // 持仓方向
+  		"updateTime": 1625474304765  // 更新时间
+  	},
+  	{
+  		"entryPrice": "0.00000", // 开仓均价
+  		"marginType": "isolated", // 逐仓模式或全仓模式
+  		"isAutoAddMargin": "false",
+  		"isolatedMargin": "5413.95799991", // 逐仓保证金
+  		"leverage": "10", // 当前杠杆倍数
+  		"liquidationPrice": "7189.95", // 参考强平价格
+  		"markPrice": "6679.50671178",	// 当前标记价格
+  		"maxNotionalValue": "20000000", // 当前杠杆倍数允许的名义价值上限
+  		"positionAmt": "-10.000", // 头寸数量,符号代表多空方向, 正数为多,负数为空
+  		"symbol": "BTCUSDT", // 交易对
+  		"unRealizedProfit": "-1156.46711780" // 持仓未实现盈亏
+  		"positionSide": "SHORT", // 持仓方向
+  		"updateTime": 1625474304765  //更新时间
+  	}  	
+]
+```
+
+``
+GET /fapi/v3/positionRisk (HMAC SHA256)
+``
+
+**权重:**
+5
+
+**参数:**
+
+   名称    | 类型 | 是否必需 | 描述
+---------- | ---- | -------- | ----
+symbol     | STRING | NO     |
+recvWindow | LONG | NO       |
+timestamp  | LONG | YES      |
+
+
+**注意**    
+请与账户推送信息`ACCOUNT_UPDATE`配合使用,以满足您的及时性和准确性需求。
+
+
+
+
+## 账户成交历史 (USER_DATA)
+
+
+> **响应:**
+
+```javascript
+[
+  {
+  	"buyer": false,	// 是否是买方
+  	"commission": "-0.07819010", // 手续费
+  	"commissionAsset": "USDT", // 手续费计价单位
+  	"id": 698759,	// 交易ID
+  	"maker": false,	// 是否是挂单方
+  	"orderId": 25851813, // 订单编号
+  	"price": "7819.01",	// 成交价
+  	"qty": "0.002",	// 成交量
+  	"quoteQty": "15.63802",	// 成交额
+  	"realizedPnl": "-0.91539999",	// 实现盈亏
+  	"side": "SELL",	// 买卖方向
+  	"positionSide": "SHORT",  // 持仓方向
+  	"symbol": "BTCUSDT", // 交易对
+  	"time": 1569514978020 // 时间
+  }
+]
+```
+
+``
+GET /fapi/v3/userTrades  (HMAC SHA256)
+``
+
+获取某交易对的成交历史
+
+**权重:**
+5
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |                     描述
+---------- | ------ | -------- | --------------------------------------------
+symbol     | STRING | YES      | 交易对
+startTime  | LONG   | NO       | 起始时间
+endTime    | LONG   | NO       | 结束时间
+fromId     | LONG   | NO       | 返回该fromId及之后的成交,缺省返回最近的成交
+limit      | INT    | NO       | 返回的结果集数量 默认值:500 最大值:1000.
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+* 如果`startTime` 和 `endTime` 均未发送, 只会返回最近7天的数据。
+* startTime 和 endTime 的最大间隔为7天
+
+
+## 获取账户损益资金流水(USER_DATA)
+
+> **响应:**
+
+```javascript
+[
+	{
+    	"symbol": "", // 交易对,仅针对涉及交易对的资金流
+    	"incomeType": "TRANSFER",	// 资金流类型
+    	"income": "-0.37500000", // 资金流数量,正数代表流入,负数代表流出
+    	"asset": "USDT", // 资产内容
+    	"info":"TRANSFER", // 备注信息,取决于流水类型
+    	"time": 1570608000000, // 时间
+    	"tranId":"9689322392",		// 划转ID
+    	"tradeId":""					// 引起流水产生的原始交易ID
+	},
+	{
+   		"symbol": "BTCUSDT",
+    	"incomeType": "COMMISSION", 
+    	"income": "-0.01000000",
+    	"asset": "USDT",
+    	"info":"COMMISSION",
+    	"time": 1570636800000,
+    	"tranId":"9689322392",		
+    	"tradeId":"2059192"					
+	}
+]
+```
+
+``
+GET /fapi/v3/income (HMAC SHA256)
+``
+
+**权重:**
+30
+
+**参数:**
+
+   名称    |  类型  | 是否必需 |                                              描述
+---------- | ------ | -------- | -----------------------------------------------------------------------------------------------
+symbol     | STRING | NO       | 交易对
+incomeType | STRING | NO       | 收益类型 "TRANSFER","WELCOME_BONUS", "REALIZED_PNL","FUNDING_FEE", "COMMISSION", "INSURANCE_CLEAR", and "MARKET_MERCHANT_RETURN_REWARD"
+startTime  | LONG   | NO       | 起始时间
+endTime    | LONG   | NO       | 结束时间
+limit      | INT    | NO       | 返回的结果集数量 默认值:100 最大值:1000
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+* 如果`startTime` 和 `endTime` 均未发送, 只会返回最近7天的数据。
+* 如果`incomeType`没有发送,返回所有类型账户损益资金流水。
+* "trandId" 在相同用户的同一种收益流水类型中是唯一的。
+
+
+## 杠杆分层标准 (USER_DATA)
+
+
+> **响应:**
+
+```javascript
+[
+    {
+        "symbol": "ETHUSDT",
+        "brackets": [
+            {
+                "bracket": 1,   // 层级
+                "initialLeverage": 75,  // 该层允许的最高初始杠杆倍数
+                "notionalCap": 10000,  // 该层对应的名义价值上限
+                "notionalFloor": 0,  // 该层对应的名义价值下限 
+                "maintMarginRatio": 0.0065, // 该层对应的维持保证金率
+                "cum":0 // 速算数
+            },
+        ]
+    }
+]
+```
+
+> **或** (若发送symbol)
+
+```javascript
+
+{
+    "symbol": "ETHUSDT",
+    "brackets": [
+        {
+            "bracket": 1,
+            "initialLeverage": 75,
+            "notionalCap": 10000,
+            "notionalFloor": 0,
+            "maintMarginRatio": 0.0065,
+            "cum":0
+        },
+    ]
+}
+```
+
+
+``
+GET /fapi/v3/leverageBracket
+``
+
+
+**权重:** 1
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |  描述
+------ | ------ | -------- | ------
+symbol	| STRING | NO
+recvWindow | LONG   | NO       |
+timestamp  | LONG   | YES      |
+
+
+
+## 持仓ADL队列估算 (USER_DATA)
+
+
+> **响应:**
+
+```javascript
+[
+	{
+		"symbol": "ETHUSDT", 
+		"adlQuantile": 
+			{
+				// 对于全仓状态下的双向持仓模式的交易对,会返回 "LONG", "SHORT" 和 "HEDGE", 其中"HEDGE"的存在仅作为标记;如果多空均有持仓的情况下,"LONG"和"SHORT"应返回共同计算后相同的队列分数。
+				"LONG": 3,  
+				"SHORT": 3, 
+				"HEDGE": 0   // HEDGE 仅作为指示出现,请忽略数值
+			}
+		},
+ 	{
+ 		"symbol": "BTCUSDT", 
+ 		"adlQuantile": 
+ 			{
+ 				// 对于单向持仓模式或者是逐仓状态下的双向持仓模式的交易对,会返回 "LONG", "SHORT" 和 "BOTH" 分别表示不同持仓方向上持仓的adl队列分数
+ 				"LONG": 1, 	// 双开模式下多头持仓的ADL队列估算分
+ 				"SHORT": 2, 	// 双开模式下空头持仓的ADL队列估算分
+ 				"BOTH": 0		// 单开模式下持仓的ADL队列估算分
+ 			}
+ 	}
+ ]
+```
+
+``
+GET /fapi/v3/adlQuantile
+``
+
+
+**权重:** 5
+
+**参数:**
+
+ 名称  |  类型  | 是否必需 |  描述
+------ | ------ | -------- | ------
+symbol	| STRING | NO
+recvWindow|LONG|NO| 
+timestamp|LONG|YES|
+
+* 每30秒更新数据
+
+* 队列分数0,1,2,3,4,分数越高说明在ADL队列中的位置越靠前
+
+* 对于单向持仓模式或者是逐仓状态下的双向持仓模式的交易对,会返回 "LONG", "SHORT" 和 "BOTH" 分别表示不同持仓方向上持仓的adl队列分数
+
+* 对于全仓状态下的双向持仓模式的交易对,会返回 "LONG", "SHORT" 和 "HEDGE", 其中"HEDGE"的存在仅作为标记;其中如果多空均有持仓的情况下,"LONG"和"SHORT"返回共同计算后相同的队列分数。
+
+
+## 用户强平单历史 (USER_DATA)
+
+
+> **响应:**
+
+```javascript
+[
+  {
+  	"orderId": 6071832819, 
+  	"symbol": "BTCUSDT", 
+  	"status": "FILLED", 
+  	"clientOrderId": "autoclose-1596107620040000020", 
+  	"price": "10871.09", 
+  	"avgPrice": "10913.21000", 
+  	"origQty": "0.001", 
+  	"executedQty": "0.001", 
+  	"cumQuote": "10.91321", 
+  	"timeInForce": "IOC", 
+  	"type": "LIMIT", 
+  	"reduceOnly": false, 
+  	"closePosition": false, 
+  	"side": "SELL", 
+  	"positionSide": "BOTH", 
+  	"stopPrice": "0", 
+  	"workingType": "CONTRACT_PRICE", 
+  	"origType": "LIMIT", 
+  	"time": 1596107620044, 
+  	"updateTime": 1596107620087
+  }
+  {
+   	"orderId": 6072734303, 
+   	"symbol": "BTCUSDT", 
+   	"status": "FILLED", 
+   	"clientOrderId": "adl_autoclose", 
+   	"price": "11023.14", 
+   	"avgPrice": "10979.82000", 
+   	"origQty": "0.001", 
+   	"executedQty": "0.001", 
+   	"cumQuote": "10.97982", 
+   	"timeInForce": "GTC", 
+   	"type": "LIMIT", 
+   	"reduceOnly": false, 
+   	"closePosition": false, 
+   	"side": "BUY", 
+   	"positionSide": "SHORT", 
+   	"stopPrice": "0", 
+   	"workingType": "CONTRACT_PRICE", 
+   	"origType": "LIMIT", 
+   	"time": 1596110725059, 
+   	"updateTime": 1596110725071
+  }
+]
+```
+
+
+``
+GET /fapi/v3/forceOrders
+``
+
+
+**权重:** 带symbol 20, 不带symbol 50
+
+**参数:**
+
+  名称      |  类型  | 是否必需 |                   描述
+------------- | ------ | -------- | ----------------------------------------
+symbol        | STRING | NO       |
+autoCloseType | ENUM   | NO       | "LIQUIDATION": 强平单, "ADL": ADL减仓单.
+startTime     | LONG   | NO       |
+endTime       | LONG   | NO       |
+limit         | INT    | NO       | Default 50; max 100.
+recvWindow    | LONG   | NO       |
+timestamp     | LONG   | YES      |
+
+* 如果没有传 "autoCloseType", 强平单和ADL减仓单都会被返回
+* 如果没有传"startTime", 只会返回"endTime"之前7天内的数据
+
+
+
+## 用户手续费率 (USER_DATA)
+
+> **响应:**
+
+```javascript
+{
+	"symbol": "BTCUSDT",
+  	"makerCommissionRate": "0.0002",  // 0.02%
+  	"takerCommissionRate": "0.0004"   // 0.04%
+}
+```
+
+``
+GET /fapi/v3/commissionRate (HMAC SHA256)
+``
+
+**权重:**
+20
+
+
+**参数:**
+
+名称  |  类型  | 是否必需 |  描述
+------------ | ------------ | ------------ | ------------
+symbol | STRING | YES	
+recvWindow | LONG | NO	
+timestamp | LONG | YES
+
+
+
+
+
+
+# Websocket 账户信息推送
+
+
+* 本篇所列出REST接口的baseurl **https://fapi.asterdex.com**
+* 用于订阅账户数据的 `listenKey` 从创建时刻起有效期为60分钟
+* 可以通过`PUT`一个`listenKey`延长60分钟有效期
+* 可以通过`DELETE`一个 `listenKey` 立即关闭当前数据流,并使该`listenKey` 无效
+* 在具有有效`listenKey`的帐户上执行`POST`将返回当前有效的`listenKey`并将其有效期延长60分钟
+* 本篇所列出的websocket接口baseurl: **wss://fstream.asterdex.com**
+* 订阅账户数据流的stream名称为 **/ws/\<listenKey\>**
+* 每个链接有效期不超过24小时,请妥善处理断线重连。
+* 账户数据流的消息**不保证**严格时间序; **请使用 E 字段进行排序**
+* 考虑到剧烈行情下, RESTful接口可能存在查询延迟,我们强烈建议您优先从Websocket user data stream推送的消息来获取订单,仓位等信息。
+
+
+## 生成listenKey (USER_STREAM)
+
+
+> **响应:**
+
+```javascript
+{
+  "listenKey": "pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1"
+}
+```
+
+``
+POST /fapi/v3/listenKey
+``
+
+创建一个新的user data stream,返回值为一个listenKey,即websocket订阅的stream名称。如果该帐户具有有效的`listenKey`,则将返回该`listenKey`并将其有效期延长60分钟。
+
+**权重:**
+1
+
+**参数:**
+
+None
+
+
+## 延长listenKey有效期 (USER_STREAM)
+
+
+> **响应:**
+
+```javascript
+{}
+```
+
+``
+PUT /fapi/v3/listenKey
+``
+
+有效期延长至本次调用后60分钟
+
+**权重:**
+1
+
+**参数:**
+
+None
+
+
+
+## 关闭listenKey (USER_STREAM)
+
+> **响应:**
+
+```javascript
+{}
+```
+
+``
+DELETE /fapi/v3/listenKey
+``
+
+关闭某账户数据流
+
+**权重:**
+1
+
+**参数:**
+
+None
+
+
+
+## listenKey 过期推送
+
+> **Payload:**
+
+```javascript
+{
+	'e': 'listenKeyExpired',      // 事件类型
+	'E': 1576653824250				// 事件时间
+}
+```
+
+当前连接使用的有效listenKey过期时,user data stream 将会推送此事件。
+
+**注意:**
+
+* 此事件与websocket连接中断没有必然联系
+* 只有正在连接中的有效`listenKey`过期时才会收到此消息
+* 收到此消息后user data stream将不再更新,直到用户使用新的有效的`listenKey`
+
+
+
+
+## 追加保证金通知
+
+> **Payload:**
+
+```javascript
+{
+    "e":"MARGIN_CALL",    	// 事件类型
+    "E":1587727187525,		// 事件时间
+    "cw":"3.16812045",		// 除去逐仓仓位保证金的钱包余额, 仅在全仓 margin call 情况下推送此字段
+    "p":[					// 涉及持仓
+      {
+        "s":"ETHUSDT",		// symbol
+        "ps":"LONG",		// 持仓方向
+        "pa":"1.327",		// 仓位
+        "mt":"CROSSED",		// 保证金模式
+        "iw":"0",			// 若为逐仓,仓位保证金
+        "mp":"187.17127",	// 标记价格
+        "up":"-1.166074",	// 未实现盈亏
+        "mm":"1.614445"		// 持仓需要的维持保证金
+      }
+    ]
+}  
+ 
+```
+
+
+* 当用户持仓风险过高,会推送此消息。
+* 此消息仅作为风险指导信息,不建议用于投资策略。
+* 在大波动市场行情下,不排除此消息发出的同时用户仓位已被强平的可能。
+
+
+
+
+## Balance和Position更新推送
+
+> **Payload:**
+
+```javascript
+{
+  "e": "ACCOUNT_UPDATE",				// 事件类型
+  "E": 1564745798939,            		// 事件时间
+  "T": 1564745798938 ,           		// 撮合时间
+  "a":                          		// 账户更新事件
+    {
+      "m":"ORDER",						// 事件推出原因 
+      "B":[                     		// 余额信息
+        {
+          "a":"USDT",           		// 资产名称
+          "wb":"122624.12345678",    	// 钱包余额
+          "cw":"100.12345678",			// 除去逐仓仓位保证金的钱包余额
+          "bc":"50.12345678"			// 除去盈亏与交易手续费以外的钱包余额改变量
+        },
+        {
+          "a":"BUSD",           
+          "wb":"1.00000000",
+          "cw":"0.00000000",         
+          "bc":"-49.12345678"
+        }
+      ],
+      "P":[
+       {
+          "s":"BTCUSDT",          	// 交易对
+          "pa":"0",               	// 仓位
+          "ep":"0.00000",            // 入仓价格
+          "cr":"200",             	// (费前)累计实现损益
+          "up":"0",						// 持仓未实现盈亏
+          "mt":"isolated",				// 保证金模式
+          "iw":"0.00000000",			// 若为逐仓,仓位保证金
+          "ps":"BOTH"					// 持仓方向
+       },
+       {
+        	"s":"BTCUSDT",
+        	"pa":"20",
+        	"ep":"6563.66500",
+        	"cr":"0",
+        	"up":"2850.21200",
+        	"mt":"isolated",
+        	"iw":"13200.70726908",
+        	"ps":"LONG"
+      	 },
+       {
+        	"s":"BTCUSDT",
+        	"pa":"-10",
+        	"ep":"6563.86000",
+        	"cr":"-45.04000000",
+        	"up":"-1423.15600",
+        	"mt":"isolated",
+        	"iw":"6570.42511771",
+        	"ps":"SHORT"
+       }
+      ]
+    }
+}
+```
+
+账户更新事件的 event type 固定为 `ACCOUNT_UPDATE`
+
+* 当账户信息有变动时,会推送此事件:
+	* 仅当账户信息有变动时(包括资金、仓位、保证金模式等发生变化),才会推送此事件;
+	* 订单状态变化没有引起账户和持仓变化的,不会推送此事件;
+	* 每次因持仓变动推送的position 信息,仅包含当前持仓不为0或逐仓仓位保证金不为0的symbol position。
+
+* "FUNDING FEE" 引起的资金余额变化,仅推送简略事件:
+	* 当用户某**全仓**持仓发生"FUNDING FEE"时,事件`ACCOUNT_UPDATE`将只会推送相关的用户资产余额信息`B`(仅推送FUNDING FEE 发生相关的资产余额信息),而不会推送任何持仓信息`P`。
+	* 当用户某**逐仓**仓持仓发生"FUNDING FEE"时,事件`ACCOUNT_UPDATE`将只会推送相关的用户资产余额信息`B`(仅推送"FUNDING FEE"所使用的资产余额信息),和相关的持仓信息`P`(仅推送这笔"FUNDING FEE"发生所在的持仓信息),其余持仓信息不会被推送。
+
+* 字段"m"代表了事件推出的原因,包含了以下可能类型:
+	* DEPOSIT
+	* WITHDRAW
+	* ORDER
+	* FUNDING_FEE
+	* WITHDRAW_REJECT
+	* ADJUSTMENT
+	* INSURANCE_CLEAR
+	* ADMIN_DEPOSIT
+	* ADMIN_WITHDRAW
+	* MARGIN_TRANSFER
+	* MARGIN_TYPE_CHANGE
+	* ASSET_TRANSFER
+	* OPTIONS_PREMIUM_FEE
+	* OPTIONS_SETTLE_PROFIT
+	* AUTO_EXCHANGE
+
+* 字段"bc"代表了钱包余额的改变量,即 balance change,但注意其不包含仓位盈亏及交易手续费。
+
+## 订单/交易 更新推送
+
+> **Payload:**
+
+```javascript
+{
+  
+  "e":"ORDER_TRADE_UPDATE",			// 事件类型
+  "E":1568879465651,				// 事件时间
+  "T":1568879465650,				// 撮合时间
+  "o":{								
+    "s":"BTCUSDT",					// 交易对
+    "c":"TEST",						// 客户端自定订单ID
+      // 特殊的自定义订单ID:
+      // "autoclose-"开头的字符串: 系统强平订单
+      // "adl_autoclose": ADL自动减仓订单
+    "S":"SELL",						// 订单方向
+    "o":"TRAILING_STOP_MARKET",	// 订单类型
+    "f":"GTC",						// 有效方式
+    "q":"0.001",					// 订单原始数量
+    "p":"0",						// 订单原始价格
+    "ap":"0",						// 订单平均价格
+    "sp":"7103.04",					// 条件订单触发价格,对追踪止损单无效
+    "x":"NEW",						// 本次事件的具体执行类型
+    "X":"NEW",						// 订单的当前状态
+    "i":8886774,					// 订单ID
+    "l":"0",						// 订单末次成交量
+    "z":"0",						// 订单累计已成交量
+    "L":"0",						// 订单末次成交价格
+    "N": "USDT",                 	// 手续费资产类型
+    "n": "0",                    	// 手续费数量
+    "T":1568879465651,				// 成交时间
+    "t":0,							// 成交ID
+    "b":"0",						// 买单净值
+    "a":"9.91",						// 卖单净值
+    "m": false,					    // 该成交是作为挂单成交吗?
+    "R":false	,				    // 是否是只减仓单
+    "wt": "CONTRACT_PRICE",	        // 触发价类型
+    "ot": "TRAILING_STOP_MARKET",	// 原始订单类型
+    "ps":"LONG"						// 持仓方向
+    "cp":false,						// 是否为触发平仓单; 仅在条件订单情况下会推送此字段
+    "AP":"7476.89",					// 追踪止损激活价格, 仅在追踪止损单时会推送此字段
+    "cr":"5.0",						// 追踪止损回调比例, 仅在追踪止损单时会推送此字段
+    "rp":"0"							// 该交易实现盈亏
+    
+  }
+  
+}
+```
+
+
+当有新订单创建、订单有新成交或者新的状态变化时会推送此类事件
+事件类型统一为 `ORDER_TRADE_UPDATE`
+
+**订单方向**
+
+* BUY 买入
+* SELL 卖出
+
+**订单类型**
+
+* MARKET  市价单
+* LIMIT	限价单
+* STOP		止损单
+* TAKE_PROFIT 止盈单
+* LIQUIDATION 强平单
+
+**本次事件的具体执行类型**
+
+* NEW
+* CANCELED		已撤
+* CALCULATED		
+* EXPIRED			订单失效
+* TRADE			交易
+	
+
+**订单状态**
+
+* NEW
+* PARTIALLY_FILLED    
+* FILLED
+* CANCELED
+* EXPIRED
+* NEW_INSURANCE		风险保障基金(强平)
+* NEW_ADL				自动减仓序列(强平)
+
+**有效方式:**
+
+* GTC 
+* IOC
+* FOK
+* GTX
+
+
+## 杠杆倍数等账户配置 更新推送
+
+> **Payload:**
+
+```javascript
+{
+    "e":"ACCOUNT_CONFIG_UPDATE",       // 事件类型
+    "E":1611646737479,		           // 事件时间
+    "T":1611646737476,		           // 撮合时间
+    "ac":{								
+    "s":"BTCUSDT",					   // 交易对
+    "l":25						       // 杠杆倍数
+     
+    }
+}  
+ 
+```
+
+> **Or**
+
+```javascript
+{
+    "e":"ACCOUNT_CONFIG_UPDATE",       // 事件类型
+    "E":1611646737479,		           // 事件时间
+    "T":1611646737476,		           // 撮合时间
+    "ai":{							   // 用户账户配置
+    "j":true						   // 联合保证金状态
+    }
+}  
+```
+
+当账户配置发生变化时会推送此类事件类型统一为`ACCOUNT_CONFIG_UPDATE `
+
+当交易对杠杆倍数发生变化时推送消息体会包含对象`ac`表示交易对账户配置,其中`s`代表具体的交易对,`l`代表杠杆倍数
+
+当用户联合保证金状态发生变化时推送消息体会包含对象`ai`表示用户账户配置,其中`j`代表用户联合保证金状态
+
+
+
+# 错误代码
+
+> error JSON payload:
+ 
+```javascript
+{
+  "code":-1121,
+  "msg":"Invalid symbol."
+}
+```
+
+错误由两部分组成:错误代码和消息。 代码是通用的,但是消息可能会有所不同。
+
+
+## 10xx - 常规服务器或网络问题
+> -1000 UNKNOWN
+ * An unknown error occured while processing the request.
+ * 处理请求时发生未知错误。
+
+> -1001 DISCONNECTED
+ * Internal error; unable to process your request. Please try again.
+ * 内部错误; 无法处理您的请求。 请再试一次.
+
+> -1002 UNAUTHORIZED
+ * You are not authorized to execute this request.
+ * 您无权执行此请求。
+
+> -1003 TOO_MANY_REQUESTS
+ * Too many requests queued.
+ * 排队的请求过多。
+ * Too many requests; please use the websocket for live updates.
+ * 请求权重过多; 请使用websocket获取最新更新。
+ * Too many requests; current limit is %s requests per minute. Please use the websocket for live updates to avoid polling the API.
+ * 请求权重过多; 当前限制为每分钟%s请求权重。 请使用websocket进行实时更新,以避免轮询API。
+ * Way too many requests; IP banned until %s. Please use the websocket for live updates to avoid bans.
+ * 请求权重过多; IP被禁止,直到%s。 请使用websocket进行实时更新,以免被禁。
+ 
+> -1004 DUPLICATE_IP
+ * This IP is already on the white list
+ * IP地址已经在白名单
+
+> -1005 NO_SUCH_IP
+ * No such IP has been white listed
+ * 白名单上没有此IP地址
+ 
+> -1006 UNEXPECTED_RESP
+ * An unexpected response was received from the message bus. Execution status unknown.
+ * 从消息总线收到意外的响应。执行状态未知。
+
+> -1007 TIMEOUT
+ * Timeout waiting for response from backend server. Send status unknown; execution status unknown.
+ * 等待后端服务器响应超时。 发送状态未知; 执行状态未知。
+
+> -1014 UNKNOWN_ORDER_COMPOSITION
+ * Unsupported order combination.
+ * 不支持当前的下单参数组合
+
+> -1015 TOO_MANY_ORDERS
+ * Too many new orders.
+ * 新订单太多。
+ * Too many new orders; current limit is %s orders per %s.
+ * 新订单太多; 当前限制为每%s %s个订单。
+
+> -1016 SERVICE_SHUTTING_DOWN
+ * This service is no longer available.
+ * 该服务不可用。
+
+> -1020 UNSUPPORTED_OPERATION
+ * This operation is not supported.
+ * 不支持此操作。
+
+> -1021 INVALID_TIMESTAMP
+ * Timestamp for this request is outside of the recvWindow.
+  * 此请求的时间戳在recvWindow之外。
+ * Timestamp for this request was 1000ms ahead of the server's time.
+ * 此请求的时间戳比服务器时间提前1000毫秒。
+
+> -1022 INVALID_SIGNATURE
+ * Signature for this request is not valid.
+ * 此请求的签名无效。
+
+> -1023 START_TIME_GREATER_THAN_END_TIME
+ * Start time is greater than end time.
+ * 参数里面的开始时间在结束时间之后
+
+
+## 11xx - Request issues
+> -1100 ILLEGAL_CHARS
+ * Illegal characters found in a parameter.
+ * 在参数中发现非法字符。
+ * Illegal characters found in parameter '%s'; legal range is '%s'.
+ * 在参数`%s`中发现非法字符; 合法范围是`%s`。
+
+> -1101 TOO_MANY_PARAMETERS
+ * Too many parameters sent for this endpoint.
+ * 为此端点发送的参数太多。
+ * Too many parameters; expected '%s' and received '%s'.
+ * 参数太多;预期为`%s`并收到了`%s`。
+ * Duplicate values for a parameter detected.
+ * 检测到的参数值重复。
+
+> -1102 MANDATORY_PARAM_EMPTY_OR_MALFORMED
+ * A mandatory parameter was not sent, was empty/null, or malformed.
+ * 未发送强制性参数,该参数为空/空或格式错误。
+ * Mandatory parameter '%s' was not sent, was empty/null, or malformed.
+ * 强制参数`%s`未发送,为空/空或格式错误。
+ * Param '%s' or '%s' must be sent, but both were empty/null!
+ * 必须发送参数`%s`或`%s`,但两者均为空!
+
+> -1103 UNKNOWN_PARAM
+ * An unknown parameter was sent.
+ * 发送了未知参数。
+
+> -1104 UNREAD_PARAMETERS
+ * Not all sent parameters were read.
+ * 并非所有发送的参数都被读取。
+ * Not all sent parameters were read; read '%s' parameter(s) but was sent '%s'.
+ * 并非所有发送的参数都被读取; 读取了`%s`参数,但被发送了`%s`。
+
+> -1105 PARAM_EMPTY
+ * A parameter was empty.
+ * 参数为空。
+ * Parameter '%s' was empty.
+ * 参数`%s`为空。
+
+> -1106 PARAM_NOT_REQUIRED
+ * A parameter was sent when not required.
+ * 发送了不需要的参数。
+ * Parameter '%s' sent when not required.
+ * 发送了不需要参数`%s`。
+
+> -1111 BAD_PRECISION
+ * Precision is over the maximum defined for this asset.
+ * 精度超过为此资产定义的最大值。
+
+> -1112 NO_DEPTH
+ * No orders on book for symbol.
+ * 交易对没有挂单。
+ 
+> -1114 TIF_NOT_REQUIRED
+ * TimeInForce parameter sent when not required.
+ * 发送的`TimeInForce`参数不需要。
+
+> -1115 INVALID_TIF
+ * Invalid timeInForce.
+ * 无效的`timeInForce`
+
+> -1116 INVALID_ORDER_TYPE
+ * Invalid orderType.
+ * 无效订单类型。
+
+> -1117 INVALID_SIDE
+ * Invalid side.
+ * 无效买卖方向。
+
+> -1118 EMPTY_NEW_CL_ORD_ID
+ * New client order ID was empty.
+ * 新的客户订单ID为空。
+
+> -1119 EMPTY_ORG_CL_ORD_ID
+ * Original client order ID was empty.
+ * 客户自定义的订单ID为空。
+
+> -1120 BAD_INTERVAL
+ * Invalid interval.
+ * 无效时间间隔。
+
+> -1121 BAD_SYMBOL
+ * Invalid symbol.
+ * 无效的交易对。
+
+> -1125 INVALID_LISTEN_KEY
+ * This listenKey does not exist.
+ * 此`listenKey`不存在。
+
+> -1127 MORE_THAN_XX_HOURS
+ * Lookup interval is too big.
+ * 查询间隔太大。
+ * More than %s hours between startTime and endTime.
+ * 从开始时间到结束时间之间超过`%s`小时。
+
+> -1128 OPTIONAL_PARAMS_BAD_COMBO
+ * Combination of optional parameters invalid.
+ * 可选参数组合无效。
+
+> -1130 INVALID_PARAMETER
+ * Invalid data sent for a parameter.
+ * 发送的参数为无效数据。
+ * Data sent for parameter '%s' is not valid.
+ * 发送参数`%s`的数据无效。
+
+> -1136 INVALID_NEW_ORDER_RESP_TYPE
+ * Invalid newOrderRespType.
+ * 无效的 newOrderRespType。
+
+
+## 20xx - Processing Issues
+> -2010 NEW_ORDER_REJECTED
+ * NEW_ORDER_REJECTED
+ * 新订单被拒绝
+
+> -2011 CANCEL_REJECTED
+ * CANCEL_REJECTED
+ * 取消订单被拒绝
+
+> -2013 NO_SUCH_ORDER
+ * Order does not exist.
+ * 订单不存在。
+
+> -2014 BAD_API_KEY_FMT
+ * API-key format invalid.
+ * API-key 格式无效。
+
+> -2015 REJECTED_MBX_KEY
+ * Invalid API-key, IP, or permissions for action.
+ * 无效的API密钥,IP或操作权限。
+
+> -2016 NO_TRADING_WINDOW
+ * No trading window could be found for the symbol. Try ticker/24hrs instead.
+ * 找不到该交易对的交易窗口。 尝试改为24小时自动报价。
+
+> -2018 BALANCE_NOT_SUFFICIENT
+ * Balance is insufficient.
+ * 余额不足
+
+> -2019 MARGIN_NOT_SUFFICIEN
+ * Margin is insufficient.
+ * 杠杆账户余额不足
+
+> -2020 UNABLE_TO_FILL
+ * Unable to fill.
+ * 无法成交
+
+> -2021 ORDER_WOULD_IMMEDIATELY_TRIGGER
+ * Order would immediately trigger.
+ * 订单可能被立刻触发
+
+> -2022 REDUCE_ONLY_REJECT
+ * ReduceOnly Order is rejected.
+ * `ReduceOnly`订单被拒绝
+
+> -2023 USER_IN_LIQUIDATION
+ * User in liquidation mode now.
+ * 用户正处于被强平模式
+
+> -2024 POSITION_NOT_SUFFICIENT
+ * Position is not sufficient.
+ * 持仓不足
+
+> -2025 MAX_OPEN_ORDER_EXCEEDED
+ * Reach max open order limit.
+ * 挂单量达到上限
+
+> -2026 REDUCE_ONLY_ORDER_TYPE_NOT_SUPPORTED
+ * This OrderType is not supported when reduceOnly.
+ * 当前订单类型不支持`reduceOnly`
+
+> -2027 MAX_LEVERAGE_RATIO
+ * Exceeded the maximum allowable position at current leverage.
+ * 挂单或持仓超出当前初始杠杆下的最大值
+
+> -2028 MIN_LEVERAGE_RATIO
+ * Leverage is smaller than permitted: insufficient margin balance.
+ * 调整初始杠杆过低,导致可用余额不足 
+
+## 40xx - Filters and other Issues
+> -4000 INVALID_ORDER_STATUS
+ * Invalid order status.
+ * 订单状态不正确
+
+> -4001 PRICE_LESS_THAN_ZERO
+ * Price less than 0.
+ * 价格小于0
+
+> -4002 PRICE_GREATER_THAN_MAX_PRICE
+ * Price greater than max price.
+ * 价格超过最大值
+ 
+> -4003 QTY_LESS_THAN_ZERO
+ * Quantity less than zero.
+ * 数量小于0
+
+> -4004 QTY_LESS_THAN_MIN_QTY
+ * Quantity less than min quantity.
+ * 数量小于最小值
+ 
+> -4005 QTY_GREATER_THAN_MAX_QTY
+ * Quantity greater than max quantity.
+ * 数量大于最大值
+
+> -4006 STOP_PRICE_LESS_THAN_ZERO
+ * Stop price less than zero. 
+ * 触发价小于最小值
+ 
+> -4007 STOP_PRICE_GREATER_THAN_MAX_PRICE
+ * Stop price greater than max price.
+ * 触发价大于最大值
+
+> -4008 TICK_SIZE_LESS_THAN_ZERO
+ * Tick size less than zero.
+ * 价格精度小于0
+
+> -4009 MAX_PRICE_LESS_THAN_MIN_PRICE
+ * Max price less than min price.
+ * 最大价格小于最小价格
+
+> -4010 MAX_QTY_LESS_THAN_MIN_QTY
+ * Max qty less than min qty.
+ * 最大数量小于最小数量
+
+> -4011 STEP_SIZE_LESS_THAN_ZERO
+ * Step size less than zero.
+ * 步进值小于0
+
+> -4012 MAX_NUM_ORDERS_LESS_THAN_ZERO
+ * Max num orders less than zero.
+ * 最大订单量小于0
+
+> -4013 PRICE_LESS_THAN_MIN_PRICE
+ * Price less than min price.
+ * 价格小于最小价格
+
+> -4014 PRICE_NOT_INCREASED_BY_TICK_SIZE
+ * Price not increased by tick size.
+ * 价格增量不是价格精度的倍数。
+ 
+> -4015 INVALID_CL_ORD_ID_LEN
+ * Client order id is not valid.
+ * 客户订单ID有误。
+ * Client order id length should not be more than 36 chars
+ * 客户订单ID长度应该不多于36字符
+
+> -4016 PRICE_HIGHTER_THAN_MULTIPLIER_UP
+ * Price is higher than mark price multiplier cap.
+
+> -4017 MULTIPLIER_UP_LESS_THAN_ZERO
+ * Multiplier up less than zero.
+ * 价格上限小于0
+
+> -4018 MULTIPLIER_DOWN_LESS_THAN_ZERO
+ * Multiplier down less than zero.
+ * 价格下限小于0
+
+> -4019 COMPOSITE_SCALE_OVERFLOW
+ * Composite scale too large.
+
+> -4020 TARGET_STRATEGY_INVALID
+ * Target strategy invalid for orderType '%s',reduceOnly '%b'.
+ * 目标策略值不适合`%s`订单状态, 只减仓`%b`。
+
+> -4021 INVALID_DEPTH_LIMIT
+ * Invalid depth limit.
+ * 深度信息的`limit`值不正确。
+ * '%s' is not valid depth limit.
+ * `%s`不是合理的深度信息的`limit`值。
+
+> -4022 WRONG_MARKET_STATUS
+ * market status sent is not valid.
+ * 发送的市场状态不正确。
+ 
+> -4023 QTY_NOT_INCREASED_BY_STEP_SIZE
+ * Qty not increased by step size.
+ * 数量的递增值不是步进值的倍数。
+
+> -4024 PRICE_LOWER_THAN_MULTIPLIER_DOWN
+ * Price is lower than mark price multiplier floor.
+
+> -4025 MULTIPLIER_DECIMAL_LESS_THAN_ZERO
+ * Multiplier decimal less than zero.
+
+> -4026 COMMISSION_INVALID
+ * Commission invalid.
+ * 收益值不正确
+ * `%s` less than zero.
+ * `%s`少于0
+ * `%s` absolute value greater than `%s`
+ * `%s`绝对值大于`%s`
+
+> -4027 INVALID_ACCOUNT_TYPE
+ * Invalid account type.
+ * 账户类型不正确。
+
+> -4028 INVALID_LEVERAGE
+ * Invalid leverage
+ * 杠杆倍数不正确
+ * Leverage `%s` is not valid
+ * 杠杆`%s`不正确
+ * Leverage `%s` already exist with `%s`
+ * 杠杆`%s`已经存在于`%s`
+
+> -4029 INVALID_TICK_SIZE_PRECISION
+ * Tick size precision is invalid.
+ * 价格精度小数点位数不正确。
+
+> -4030 INVALID_STEP_SIZE_PRECISION
+ * Step size precision is invalid.
+ * 步进值小数点位数不正确。
+
+> -4031 INVALID_WORKING_TYPE
+ * Invalid parameter working type
+ * 不正确的参数类型
+ * Invalid parameter working type: `%s`
+ * 不正确的参数类型: `%s`
+
+> -4032 EXCEED_MAX_CANCEL_ORDER_SIZE
+ * Exceed maximum cancel order size.
+ * 超过可以取消的最大订单量。
+ * Invalid parameter working type: `%s`
+ * 不正确的参数类型: `%s`
+
+> -4033 INSURANCE_ACCOUNT_NOT_FOUND
+ * Insurance account not found.
+ * 风险保障基金账号没找到。
+
+> -4044 INVALID_BALANCE_TYPE
+ * Balance Type is invalid.
+ * 余额类型不正确。
+
+> -4045 MAX_STOP_ORDER_EXCEEDED
+ * Reach max stop order limit.
+ * 达到止损单的上限。
+
+> -4046 NO_NEED_TO_CHANGE_MARGIN_TYPE
+ * No need to change margin type.
+ * 不需要切换仓位模式。
+
+> -4047 THERE_EXISTS_OPEN_ORDERS
+ * Margin type cannot be changed if there exists open orders.
+ * 如果有挂单,仓位模式不能切换。
+
+> -4048 THERE_EXISTS_QUANTITY
+ * Margin type cannot be changed if there exists position.
+ * 如果有仓位,仓位模式不能切换。
+
+> -4049 ADD_ISOLATED_MARGIN_REJECT
+ * Add margin only support for isolated position.
+
+> -4050 CROSS_BALANCE_INSUFFICIENT
+ * Cross balance insufficient.
+ * 全仓余额不足。
+
+> -4051 ISOLATED_BALANCE_INSUFFICIENT
+ * Isolated balance insufficient.
+ * 逐仓余额不足。
+
+> -4052 NO_NEED_TO_CHANGE_AUTO_ADD_MARGIN
+ * No need to change auto add margin.
+
+> -4053 AUTO_ADD_CROSSED_MARGIN_REJECT
+ * Auto add margin only support for isolated position.
+ * 自动增加保证金只适用于逐仓。
+
+> -4054 ADD_ISOLATED_MARGIN_NO_POSITION_REJECT
+ * Cannot add position margin: position is 0.
+ * 不能增加逐仓保证金: 持仓为0
+
+> -4055 AMOUNT_MUST_BE_POSITIVE
+ * Amount must be positive.
+ * 数量必须是正整数
+
+> -4056 INVALID_API_KEY_TYPE
+ * Invalid api key type.
+ * API key的类型不正确
+
+> -4057 INVALID_RSA_PUBLIC_KEY
+ * Invalid api public key
+ * API key不正确
+
+> -4058 MAX_PRICE_TOO_LARGE
+ * maxPrice and priceDecimal too large,please check.
+ * maxPrice和priceDecimal太大,请检查。
+
+> -4059 NO_NEED_TO_CHANGE_POSITION_SIDE
+ * No need to change position side.
+ * 无需变更仓位方向
+
+> -4060 INVALID_POSITION_SIDE
+ * Invalid position side.
+ * 仓位方向不正确。
+
+> -4061 POSITION_SIDE_NOT_MATCH
+ * Order's position side does not match user's setting.
+ * 订单的持仓方向和用户设置不一致。
+
+> -4062 REDUCE_ONLY_CONFLICT
+ * Invalid or improper reduceOnly value.
+ * 仅减仓的设置不正确。
+
+> -4063 INVALID_OPTIONS_REQUEST_TYPE
+ * Invalid options request type
+ * 无效的期权请求类型
+
+> -4064 INVALID_OPTIONS_TIME_FRAME
+ * Invalid options time frame
+ * 无效的期权时间窗口
+
+> -4065 INVALID_OPTIONS_AMOUNT
+ * Invalid options amount
+ * 无效的期权数量
+
+> -4066 INVALID_OPTIONS_EVENT_TYPE
+ * Invalid options event type
+ * 无效的期权事件类型
+
+> -4067 POSITION_SIDE_CHANGE_EXISTS_OPEN_ORDERS
+ * Position side cannot be changed if there exists open orders.
+ * 如果有挂单,无法修改仓位方向。
+
+> -4068 POSITION_SIDE_CHANGE_EXISTS_QUANTITY
+ * Position side cannot be changed if there exists position.
+ * 如果有仓位, 无法修改仓位方向。
+
+> -4069 INVALID_OPTIONS_PREMIUM_FEE
+ * Invalid options premium fee
+ * 无效的期权费
+
+> -4070 INVALID_CL_OPTIONS_ID_LEN
+ * Client options id is not valid.
+ * 客户的期权ID不合法
+ * Client options id length should be less than 32 chars
+ * 客户的期权ID长度应该小于32个字符
+
+> -4071 INVALID_OPTIONS_DIRECTION
+ * Invalid options direction
+ * 期权的方向无效
+
+> -4072 OPTIONS_PREMIUM_NOT_UPDATE
+ * premium fee is not updated, reject order
+ * 期权费没有更新
+
+> -4073 OPTIONS_PREMIUM_INPUT_LESS_THAN_ZERO
+ * input premium fee is less than 0, reject order
+ * 输入的期权费小于0
+
+> -4074 OPTIONS_AMOUNT_BIGGER_THAN_UPPER
+ * Order amount is bigger than upper boundary or less than 0, reject order
+
+> -4075 OPTIONS_PREMIUM_OUTPUT_ZERO
+ * output premium fee is less than 0, reject order
+
+> -4076 OPTIONS_PREMIUM_TOO_DIFF
+ * original fee is too much higher than last fee
+ * 期权的费用比之前的费用高 
+
+> -4077 OPTIONS_PREMIUM_REACH_LIMIT
+ * place order amount has reached to limit, reject order
+ * 下单的数量达到上限
+
+> -4078 OPTIONS_COMMON_ERROR
+ * options internal error
+ * 期权内部系统错误
+
+> -4079 INVALID_OPTIONS_ID
+ * invalid options id
+ * invalid options id: %s
+ * duplicate options id %d for user %d
+ * 期权ID无效
+
+> -4080 OPTIONS_USER_NOT_FOUND
+ * user not found
+ * user not found with id: %s
+ * 用户找不到
+
+> -4081 OPTIONS_NOT_FOUND
+ * options not found
+ * options not found with id: %s
+ * 期权找不到
+
+> -4082 INVALID_BATCH_PLACE_ORDER_SIZE
+ * Invalid number of batch place orders.
+ * Invalid number of batch place orders: %s
+ * 批量下单的数量不正确
+
+> -4083 PLACE_BATCH_ORDERS_FAIL
+ * Fail to place batch orders.
+ * 无法批量下单
+
+> -4084 UPCOMING_METHOD
+ * Method is not allowed currently. Upcoming soon.
+ * 方法不支持
+
+> -4085 INVALID_NOTIONAL_LIMIT_COEF
+ * Invalid notional limit coefficient
+ * 期权的有限系数不正确
+
+> -4086 INVALID_PRICE_SPREAD_THRESHOLD
+ * Invalid price spread threshold
+ * 无效的价差阀值
+ 
+> -4087 REDUCE_ONLY_ORDER_PERMISSION
+ * User can only place reduce only order
+ * 用户只能下仅减仓订单
+
+> -4088 NO_PLACE_ORDER_PERMISSION
+ * User can not place order currently
+ * 用户当前不能下单
+
+> -4104 INVALID_CONTRACT_TYPE
+ * Invalid contract type
+ * 无效的合约类型
+
+> -4114 INVALID_CLIENT_TRAN_ID_LEN
+ * clientTranId  is not valid
+ * clientTranId不正确
+ * Client tran id length should be less than 64 chars
+ * 客户的tranId长度应该小于64个字符
+
+> -4115 DUPLICATED_CLIENT_TRAN_ID
+ * clientTranId  is duplicated
+ *  clientTranId重复
+ * Client tran id should be unique within 7 days
+ * 客户的tranId应在7天内唯一
+
+> -4118 REDUCE_ONLY_MARGIN_CHECK_FAILED
+ * ReduceOnly Order Failed. Please check your existing position and open orders
+ * 仅减仓订单失败。请检查现有的持仓和挂单
+ 
+> -4131 MARKET_ORDER_REJECT
+ * The counterparty's best price does not meet the PERCENT_PRICE filter limit
+ * 交易对手的最高价格未达到PERCENT_PRICE过滤器限制
+
+> -4135 INVALID_ACTIVATION_PRICE
+ * Invalid activation price
+ * 无效的激活价格
+
+> -4137 QUANTITY_EXISTS_WITH_CLOSE_POSITION
+ * Quantity must be zero with closePosition equals true
+ * 数量必须为0,当closePosition为true时
+
+> -4138 REDUCE_ONLY_MUST_BE_TRUE
+ * Reduce only must be true with closePosition equals true
+ * Reduce only 必须为true,当closePosition为true时
+
+> -4139 ORDER_TYPE_CANNOT_BE_MKT
+ * Order type can not be market if it's unable to cancel
+ * 订单类型不能为市价单如果不能取消
+
+> -4140 INVALID_OPENING_POSITION_STATUS
+ * Invalid symbol status for opening position
+ * 无效的交易对状态
+
+> -4141 SYMBOL_ALREADY_CLOSED
+ * Symbol is closed
+ * 交易对已下架
+
+> -4142 STRATEGY_INVALID_TRIGGER_PRICE
+ * REJECT: take profit or stop order will be triggered immediately
+ * 拒绝:止盈止损单将立即被触发
+
+> -4144 INVALID_PAIR
+ * Invalid pair
+ * 无效的pair
+
+> -4161 ISOLATED_LEVERAGE_REJECT_WITH_POSITION
+ * Leverage reduction is not supported in Isolated Margin Mode with open positions
+ * 逐仓仓位模式下无法降低杠杆
+
+> -4164 MIN_NOTIONAL
+ * Order's notional must be no smaller than 5.0 (unless you choose reduce only)
+ *  订单的名义价值不可以小于5,除了使用reduce only
+ * Order's notional must be no smaller than %s (unless you choose reduce only)
+ *  订单的名义价值不可以小于`%s`,除了使用reduce only
+
+> -4165 INVALID_TIME_INTERVAL
+ * Invalid time interval
+ * 无效的间隔
+ * Maximum time interval is %s days
+ * 最大的时间间隔为 `%s` 天
+
+> -4183 PRICE_HIGHTER_THAN_STOP_MULTIPLIER_UP
+ * Price is higher than stop price multiplier cap.
+ * 止盈止损订单价格不应高于触发价与报价乘数上限的乘积
+ * Limit price can't be higher than %s.
+ * 止盈止损订单价格不应高于 `%s`
+
+> -4184 PRICE_LOWER_THAN_STOP_MULTIPLIER_DOWN
+ * Price is lower than stop price multiplier floor.
+ * 止盈止损订单价格不应低于触发价与报价乘数下限的乘积
+ * Limit price can't be lower than %s.
+ * 止盈止损订单价格不应低于 `%s`
+

+ 527 - 0
src/cex/binance/futuresConnector.ts

@@ -0,0 +1,527 @@
+import {
+  DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL,
+  DerivativesTradingUsdsFutures,
+  DerivativesTradingUsdsFuturesRestAPI,
+} from '@binance/derivatives-trading-usds-futures'
+
+export class FutureConnector {
+  client: DerivativesTradingUsdsFutures
+  constructor(apiKey: string, apiSecret: string) {
+    const configurationRestAPI = {
+      apiKey: apiKey,
+      apiSecret: apiSecret,
+      basePath: DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL,
+    }
+    this.client = new DerivativesTradingUsdsFutures({ configurationRestAPI })
+  }
+
+  /**
+   * 获取账户资产信息(只返回钱包余额大于0的资产)
+   * @returns 资产信息数组
+   */
+  async getAssetsInfo() {
+    try {
+      const response = await this.client.restAPI.accountInformationV3()
+      const data = await response.data()
+      return data.assets.filter(asset => Number(asset.walletBalance) > 0)
+      // 过滤持仓不为 0 的仓位
+    } catch (error) {
+      console.error('accountInformation() error:', error)
+      return []
+    }
+  }
+
+  /**
+   * 获取当前持有的所有正持仓(只返回持仓数量大于0的仓位)
+   * @returns 持仓信息数组
+   */
+  async getPositonInfo() {
+    try {
+      const response = await this.client.restAPI.accountInformationV3()
+      const data = await response.data()
+      return data.positions.filter(position => Number(position.positionAmt) > 0)
+    } catch (error) {
+      console.error('futuresPositionInformation() error:', error)
+      return []
+    }
+  }
+
+  /**
+   * 获取指定交易对的持仓信息
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @returns 持仓信息对象,未持仓则为 null
+   */
+  async getPositionBalanceBySymbol(symbol: string) {
+    try {
+      const response = await this.client.restAPI.accountInformationV3()
+      const data = await response.data()
+      const position = data.positions.find(pos => pos.symbol === symbol)
+      if (position) {
+        return position
+      } else {
+        return null
+      }
+    } catch (error) {
+      console.error('getPositionBalanceBySymbol() error:', error)
+      return null
+    }
+  }
+
+  /**
+   * 修改已有订单(如改单)
+   * @param symbol 交易对
+   * @param side 买卖方向
+   * @param quantity 数量
+   * @param price 价格
+   * @returns 修改结果
+   */
+  async openModifyPosition(
+    symbol: string,
+    side: DerivativesTradingUsdsFuturesRestAPI.ModifyOrderSideEnum,
+    quantity: number,
+    price: number,
+  ) {
+    try {
+      const response = await this.client.restAPI.modifyOrder({
+        symbol: symbol,
+        side: side,
+        quantity: quantity,
+        price: price,
+      })
+      return await response.data()
+    } catch (error) {
+      console.error('openPosition() error:', error)
+      return null
+    }
+  }
+
+  /**
+   * 获取当前所有未成交订单(挂单)
+   * @returns 当前所有挂单信息
+   */
+  async getCurrentAllOpenPosition() {
+    try {
+      const response = await this.client.restAPI.currentAllOpenOrders()
+      const data = await response.data()
+      console.log('currentAllOpenOrders() response:', data)
+    } catch (error) {
+      console.error('currentAllOpenOrders() error:', error)
+    }
+  }
+
+  /**
+   * 通用开单方法(建议优先使用更简洁的 openXXXOrder 方法)
+   * @param symbol 交易对
+   * @param side 买卖方向(API原始枚举类型)
+   * @param quantity 数量
+   * @param price 价格
+   * @param options 其他高级参数
+   * @returns 下单结果
+   */
+  async openPosition(
+    symbol: string,
+    side: DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum,
+    quantity: number,
+    price: number,
+    options?: any,
+  ) {
+    try {
+      // 构建订单参数
+      const orderParams: any = {
+        symbol: symbol,
+        side: side,
+        type: options?.type || 'LIMIT',
+        timeInForce: options?.timeInForce || DerivativesTradingUsdsFuturesRestAPI.NewOrderTimeInForceEnum.GTC,
+        quantity: quantity,
+        price: price,
+      }
+      // 添加可选参数
+      if (options?.positionSide) {
+        orderParams.positionSide = options.positionSide
+      }
+      if (options?.reduceOnly) {
+        orderParams.reduceOnly = options.reduceOnly
+      }
+      if (options?.closePosition) {
+        orderParams.closePosition = options.closePosition
+      }
+      if (options?.activationPrice) {
+        orderParams.activationPrice = options.activationPrice
+      }
+      if (options?.callbackRate) {
+        orderParams.callbackRate = options.callbackRate
+      }
+      if (options?.workingType) {
+        orderParams.workingType = options.workingType
+      }
+      if (options?.priceProtect) {
+        orderParams.priceProtect = options.priceProtect
+      }
+      if (options?.newOrderRespType) {
+        orderParams.newOrderRespType = options.newOrderRespType
+      }
+      if (options?.newClientOrderId) {
+        orderParams.newClientOrderId = options.newClientOrderId
+      }
+      if (options?.stopPrice) {
+        orderParams.stopPrice = options.stopPrice
+      }
+      if (options?.icebergQty) {
+        orderParams.icebergQty = options.icebergQty
+      }
+      if (options?.orderTag) {
+        orderParams.orderTag = options.orderTag
+      }
+      console.log('下单参数:', orderParams)
+      const response = await this.client.restAPI.newOrder(orderParams)
+      const result = await response.data()
+      console.log('下单成功:', result)
+      return result
+    } catch (error) {
+      console.error('openPosition() 错误:', error)
+      return null
+    }
+  }
+
+  /**
+   * 限价单(对冲模式,支持订单有效期参数)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param side 方向,'long' 表示多头,'short' 表示空头
+   * @param quantity 下单数量
+   * @param price 限价价格
+   * @param timeInForce 订单有效期参数,可选值:'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD',默认 'GTC'
+   * @returns 下单结果
+   */
+  async openLimitOrder(
+    symbol: string,
+    side: 'long' | 'short',
+    quantity: number,
+    price: number,
+    timeInForce: 'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD' = 'GTC',
+  ) {
+    return this.openPosition(
+      symbol,
+      side === 'long'
+        ? DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY
+        : DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL,
+      quantity,
+      price,
+      {
+        type: 'LIMIT',
+        timeInForce,
+        positionSide:
+          side === 'long'
+            ? DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.LONG
+            : DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.SHORT,
+      },
+    )
+  }
+
+  /**
+   * 市价单(对冲模式,简洁参数)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param side 方向,'long' 表示多头,'short' 表示空头
+   * @param quantity 下单数量
+   * @returns 下单结果
+   */
+  async openMarketOrder(symbol: string, side: 'long' | 'short', quantity: number) {
+    return this.openPosition(
+      symbol,
+      side === 'long'
+        ? DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY
+        : DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL,
+      quantity,
+      0,
+      {
+        type: 'MARKET',
+        positionSide:
+          side === 'long'
+            ? DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.LONG
+            : DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.SHORT,
+      },
+    )
+  }
+
+  /**
+   * 止损单(对冲模式,支持限价止损的订单有效期参数)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param side 方向,'long' 表示多头,'short' 表示空头
+   * @param quantity 下单数量
+   * @param stopPrice 触发价格(到达该价格触发止损)
+   * @param type 订单类型,默认为 'STOP_MARKET',如需限价止损可传 'STOP' 并指定 price
+   * @param price 止损限价单需指定价格(type='STOP' 时必填)
+   * @param timeInForce 订单有效期参数,仅 type='STOP' 时有效,可选值:'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD',默认 'GTC'
+   * @returns 下单结果
+   */
+  async openStopOrder(
+    symbol: string,
+    side: 'long' | 'short',
+    quantity: number,
+    stopPrice: number,
+    type: 'STOP' | 'STOP_MARKET' = 'STOP_MARKET',
+    price?: number,
+    timeInForce: 'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD' = 'GTC',
+  ) {
+    const orderParams: any = {
+      type,
+      stopPrice,
+      reduceOnly: 'true',
+      positionSide:
+        side === 'long'
+          ? DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.LONG
+          : DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.SHORT,
+    }
+    if (type === 'STOP') {
+      orderParams.timeInForce = timeInForce
+    }
+    return this.openPosition(
+      symbol,
+      side === 'long'
+        ? DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY
+        : DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL,
+      quantity,
+      price || 0,
+      orderParams,
+    )
+  }
+
+  /**
+   * 止盈单(对冲模式,支持限价止盈的订单有效期参数)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param side 方向,'long' 表示多头,'short' 表示空头
+   * @param quantity 下单数量
+   * @param stopPrice 触发价格(到达该价格触发止盈)
+   * @param type 订单类型,默认为 'TAKE_PROFIT_MARKET',如需限价止盈可传 'TAKE_PROFIT' 并指定 price
+   * @param price 止盈限价单需指定价格(type='TAKE_PROFIT' 时必填)
+   * @param timeInForce 订单有效期参数,仅 type='TAKE_PROFIT' 时有效,可选值:'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD',默认 'GTC'
+   * @returns 下单结果
+   */
+  async openTakeProfitOrder(
+    symbol: string,
+    side: 'long' | 'short',
+    quantity: number,
+    stopPrice: number,
+    type: 'TAKE_PROFIT' | 'TAKE_PROFIT_MARKET' = 'TAKE_PROFIT_MARKET',
+    price?: number,
+    timeInForce: 'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD' = 'GTC',
+  ) {
+    const orderParams: any = {
+      type,
+      stopPrice,
+      reduceOnly: 'true',
+      positionSide:
+        side === 'long'
+          ? DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.LONG
+          : DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.SHORT,
+    }
+    if (type === 'TAKE_PROFIT') {
+      orderParams.timeInForce = timeInForce
+    }
+    return this.openPosition(
+      symbol,
+      side === 'long'
+        ? DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY
+        : DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL,
+      quantity,
+      price || 0,
+      orderParams,
+    )
+  }
+
+  /**
+   * 追踪止损单(对冲模式,简洁参数)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param side 方向,'long' 表示多头,'short' 表示空头
+   * @param quantity 下单数量
+   * @param activationPrice 激活价格(到达该价格后开始追踪)
+   * @param callbackRate 回调比例(百分比,如 0.1 表示 10%)
+   * @returns 下单结果
+   */
+  async openTrailingStopOrder(
+    symbol: string,
+    side: 'long' | 'short',
+    quantity: number,
+    activationPrice: number,
+    callbackRate: number,
+  ) {
+    return this.openPosition(
+      symbol,
+      side === 'long'
+        ? DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY
+        : DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL,
+      quantity,
+      0,
+      {
+        type: 'TRAILING_STOP_MARKET',
+        activationPrice,
+        callbackRate,
+        reduceOnly: 'true',
+        positionSide:
+          side === 'long'
+            ? DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.LONG
+            : DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.SHORT,
+      },
+    )
+  }
+
+  /**
+   * 冰山订单(对冲模式,支持订单有效期参数)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param side 方向,'long' 表示多头,'short' 表示空头
+   * @param quantity 下单总数量
+   * @param price 限价价格
+   * @param icebergQty 冰山显示数量(每次挂单可见数量)
+   * @param timeInForce 订单有效期参数,可选值:'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD',默认 'GTC'
+   * @returns 下单结果
+   */
+  async openIcebergOrder(
+    symbol: string,
+    side: 'long' | 'short',
+    quantity: number,
+    price: number,
+    icebergQty: number,
+    timeInForce: 'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD' = 'GTC',
+  ) {
+    return this.openPosition(
+      symbol,
+      side === 'long'
+        ? DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY
+        : DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL,
+      quantity,
+      price,
+      {
+        type: 'LIMIT',
+        timeInForce,
+        icebergQty,
+        positionSide:
+          side === 'long'
+            ? DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.LONG
+            : DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.SHORT,
+      },
+    )
+  }
+
+  /**
+   * 平多仓(市价平多,reduceOnly)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param quantity 平仓数量
+   * @returns 下单结果
+   */
+  async closeLongPosition(symbol: string, quantity: number) {
+    return this.openPosition(symbol, DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL, quantity, 0, {
+      type: 'MARKET',
+      reduceOnly: 'true',
+      positionSide: DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.LONG,
+    })
+  }
+
+  /**
+   * 平空仓(市价平空,reduceOnly)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param quantity 平仓数量
+   * @returns 下单结果
+   */
+  async closeShortPosition(symbol: string, quantity: number) {
+    return this.openPosition(symbol, DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY, quantity, 0, {
+      type: 'MARKET',
+      reduceOnly: 'true',
+      positionSide: DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.SHORT,
+    })
+  }
+
+  /**
+   * 全部平仓(自动检测所有持仓,逐个平仓)
+   * @returns 平仓结果数组
+   */
+  async closeAllPositions() {
+    const positions = await this.getAllPositions()
+    const results = []
+    for (const pos of positions) {
+      const amt = Number(pos.positionAmt)
+      if (amt > 0) {
+        // 多头
+        results.push(await this.closeLongPosition(pos.symbol, Math.abs(amt)))
+      } else if (amt < 0) {
+        // 空头
+        results.push(await this.closeShortPosition(pos.symbol, Math.abs(amt)))
+      }
+    }
+    return results
+  }
+
+  /**
+   * 撤销单个订单
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param orderId 订单ID(可选)
+   * @param origClientOrderId 客户端订单ID(可选)
+   * @returns 撤单结果
+   */
+  async cancelOrder(symbol: string, orderId?: number, origClientOrderId?: string) {
+    return this.client.restAPI.cancelOrder({ symbol, orderId, origClientOrderId })
+  }
+
+  /**
+   * 撤销某交易对的所有挂单
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @returns 撤单结果
+   */
+  async cancelAllOrders(symbol: string) {
+    return this.client.restAPI.cancelAllOpenOrders({ symbol })
+  }
+
+  /**
+   * 查询历史订单(近7天)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param startTime 开始时间(毫秒时间戳,可选)
+   * @param endTime 结束时间(毫秒时间戳,可选)
+   * @param limit 返回条数,默认100,最大1000
+   * @returns 历史订单数组
+   */
+  async getOrderHistory(symbol: string, startTime?: number, endTime?: number, limit: number = 100) {
+    const response = await this.client.restAPI.allOrders({ symbol, startTime, endTime, limit })
+    return response.data()
+  }
+
+  /**
+   * 查询历史成交(近7天)
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param startTime 开始时间(毫秒时间戳,可选)
+   * @param endTime 结束时间(毫秒时间戳,可选)
+   * @param limit 返回条数,默认100,最大1000
+   * @returns 历史成交数组
+   */
+  async getTradeHistory(symbol: string, startTime?: number, endTime?: number, limit: number = 100) {
+    const response = await this.client.restAPI.accountTradeList({ symbol, startTime, endTime, limit })
+    return response.data()
+  }
+
+  /**
+   * 设置杠杆倍数
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param leverage 杠杆倍数(1-125)
+   * @returns 设置结果
+   */
+  async setLeverage(symbol: string, leverage: number) {
+    return this.client.restAPI.changeInitialLeverage({ symbol, leverage })
+  }
+
+  /**
+   * 切换保证金模式
+   * @param symbol 交易对,如 'BTCUSDT'
+   * @param marginType 保证金模式 'ISOLATED'(逐仓)或 'CROSSED'(全仓)
+   * @returns 设置结果
+   */
+  async setMarginType(symbol: string, marginType: DerivativesTradingUsdsFuturesRestAPI.ChangeMarginTypeMarginTypeEnum) {
+    return this.client.restAPI.changeMarginType({ symbol, marginType })
+  }
+
+  /**
+   * 获取所有仓位(包括空仓)
+   * @returns 所有仓位数组
+   */
+  async getAllPositions() {
+    const response = await this.client.restAPI.accountInformationV3()
+    const data = await response.data()
+    return data.positions || []
+  }
+}

+ 0 - 0
src/config.ts


+ 102 - 0
src/config/asterConfig.ts

@@ -0,0 +1,102 @@
+import { AsterWsConfig, AsterAuthConfig } from '../dex/aster/types';
+
+export interface AsterConfig {
+  // WebSocket 配置
+  ws: {
+    url: string;
+    pingInterval: number;
+    pongTimeout: number;
+    reconnectInterval: number;
+    maxReconnectAttempts: number;
+  };
+  
+  // HTTP API 配置
+  http: {
+    baseUrl: string;
+  };
+  
+  // 鉴权配置
+  auth: {
+    user: string;
+    signer: string;
+    privateKey: string;
+  };
+  
+  // 订阅配置
+  subscribe: {
+    symbols: string[];
+  };
+  
+  // 日志配置
+  log: {
+    level: string;
+  };
+}
+
+/**
+ * 从环境变量读取 Aster DEX 配置
+ */
+export function loadAsterConfig(): AsterConfig {
+  const required = [
+    'ASTER_WS_URL',
+    'ASTER_ORDER_USER',
+    'ASTER_ORDER_SIGNER',
+    'PRIVATE_KEY'
+  ];
+  
+  for (const key of required) {
+    if (!process.env[key]) {
+      throw new Error(`Missing required environment variable: ${key}`);
+    }
+  }
+  
+  return {
+    ws: {
+      url: process.env.ASTER_WS_URL!,
+      pingInterval: parseInt(process.env.ASTER_WS_PING_INTERVAL || '30000'),
+      pongTimeout: parseInt(process.env.ASTER_WS_PONG_TIMEOUT || '10000'),
+      reconnectInterval: parseInt(process.env.ASTER_WS_RECONNECT_INTERVAL || '5000'),
+      maxReconnectAttempts: parseInt(process.env.ASTER_WS_MAX_RECONNECT_ATTEMPTS || '10')
+    },
+    http: {
+      baseUrl: process.env.ASTER_HTTP_BASE || 'https://fapi.asterdex.com'
+    },
+    auth: {
+      user: process.env.ASTER_ORDER_USER!,
+      signer: process.env.ASTER_ORDER_SIGNER!,
+      privateKey: process.env.PRIVATE_KEY!
+    },
+    subscribe: {
+      symbols: (process.env.ASTER_SUBSCRIBE_SYMBOLS || 'BTCUSDT,ETHUSDT').split(',').map(s => s.trim())
+    },
+    log: {
+      level: process.env.LOG_LEVEL || 'info'
+    }
+  };
+}
+
+/**
+ * 转换为 AsterWsConfig
+ */
+export function toAsterWsConfig(config: AsterConfig): AsterWsConfig {
+  return {
+    wsUrl: config.ws.url,
+    pingIntervalMs: config.ws.pingInterval,
+    pongTimeoutMs: config.ws.pongTimeout,
+    autoReconnect: true,
+    reconnectIntervalMs: config.ws.reconnectInterval
+  };
+}
+
+/**
+ * 转换为 AsterAuthConfig
+ */
+export function toAsterAuthConfig(config: AsterConfig): AsterAuthConfig {
+  return {
+    type: 'signer',
+    user: config.auth.user,
+    signer: config.auth.signer,
+    privateKey: config.auth.privateKey,
+    loginMethod: 'login'
+  };
+}

+ 41 - 0
src/constants/index.ts

@@ -0,0 +1,41 @@
+// 常量与枚举
+
+export enum OrderSide {
+  BUY = 'buy',
+  SELL = 'sell'
+}
+
+export enum PositionSide {
+  LONG = 'long',
+  SHORT = 'short'
+}
+
+export enum OrderType {
+  MARKET = 'market',
+  LIMIT = 'limit',
+  STOP = 'stop',
+  STOP_MARKET = 'stop_market'
+}
+
+export enum Severity {
+  LOW = 'low',
+  MEDIUM = 'medium',
+  HIGH = 'high',
+  CRITICAL = 'critical'
+}
+
+export const DEFAULT_WS_URL = 'wss://fstream.binance.com/ws';
+
+export const DEFAULT_RECONNECT_MS = 5000;
+export const DEFAULT_MAX_RECONNECT = 10;
+
+export const SUPPORTED_INTERVALS = ['1m', '5m', '15m', '1h', '4h', '1d'];
+
+export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
+
+export const CHAIN_IDS = {
+  ETHEREUM: 1,
+  ARBITRUM: 42161,
+  POLYGON: 137,
+  BSC: 56
+};

+ 21 - 0
src/core/app.ts

@@ -0,0 +1,21 @@
+import { marketDataFetcher } from './market/marketDataFetcher';
+import { positionStrategy } from './strategy/positionStrategy';
+import { hedgingExecutor } from './hedging/hedgingExecutor';
+import { riskAssessor } from '../risk/riskAssessor';
+import { logger } from '../utils/logger';
+
+export async function startApp() {
+  await marketDataFetcher.init();
+  const symbols = ['BTCUSDT', 'ETHUSDT'];
+  marketDataFetcher.subscribe(symbols, ['1m']);
+
+  setInterval(async () => {
+    const signals = positionStrategy.generateSignals(symbols);
+    for (const s of signals) {
+      if (s.action === 'hedge' && s.quantity > 0) {
+        const tx = await hedgingExecutor.hedgeSpot(s.symbol, s.quantity);
+        logger.info(`Hedge executed: ${tx}`);
+      }
+    }
+  }, 5000);
+}

+ 48 - 0
src/core/hedging/hedgeCalculator.ts

@@ -0,0 +1,48 @@
+import { HedgeDecision, HedgeRequest } from './types';
+
+export class HedgeCalculator {
+  decide(req: HedgeRequest): HedgeDecision {
+    const { perpPosition, prices, funding, config } = req;
+
+    // 非开仓状态不对冲
+    if (!perpPosition.isOpen || perpPosition.size === 0) {
+      return { shouldHedge: false, method: config.preferredMethod, hedgeQuantity: 0, reason: 'position_closed' };
+    }
+
+    const positionNotionalUsd = Math.abs(perpPosition.size * prices.perpPrice);
+    if (positionNotionalUsd < config.thresholds.minNotionalUSD) {
+      return { shouldHedge: false, method: config.preferredMethod, hedgeQuantity: 0, reason: 'notional_below_min' };
+    }
+
+    // 计算当前 delta 偏差(简化:方向 * 数量)
+    const direction = perpPosition.side === 'long' ? 1 : -1;
+    const currentDelta = direction * perpPosition.size; // 以 base 数量计
+
+    // 目标 delta = 0,考虑相关系数与价格换算
+    const hedgeQtyRaw = -currentDelta * (prices.perpPrice / prices.hedgePrice) * Math.max(0, Math.min(1, prices.correlation || 1));
+
+    // 偏差阈值控制:若需要调整的数量占仓位比例过小则不执行
+    const deltaDeviationPct = Math.abs(hedgeQtyRaw) / Math.max(1e-9, Math.abs(perpPosition.size));
+    if (deltaDeviationPct < config.thresholds.deltaDeviationPct) {
+      return { shouldHedge: false, method: config.preferredMethod, hedgeQuantity: 0, reason: 'delta_within_threshold' };
+    }
+
+    // 资金费率驱动:若费率绝对值低于门槛则不进行“费率对冲”
+    if (Math.abs(funding.fundingRate) < config.thresholds.minFundingRate) {
+      return { shouldHedge: false, method: config.preferredMethod, hedgeQuantity: 0, reason: 'funding_rate_low' };
+    }
+
+    // 粗略估算单周期资金费收益(正费率多付,负费率可赚)
+    const expectedFundingPnlPerPeriod = -funding.fundingRate * positionNotionalUsd;
+
+    return {
+      shouldHedge: true,
+      method: config.preferredMethod,
+      hedgeQuantity: hedgeQtyRaw,
+      reason: 'delta_neutral_funding_driven',
+      expectedFundingPnlPerPeriod
+    };
+  }
+}
+
+export const hedgeCalculator = new HedgeCalculator();

+ 38 - 0
src/core/hedging/hedgingExecutor.ts

@@ -0,0 +1,38 @@
+import { hedgeCalculator } from './hedgeCalculator';
+import { tradeRouter } from './router';
+import { HedgeRequest, ExecutionPlan, ExecutionResult } from './types';
+
+export class HedgingExecutor {
+  async execute(req: HedgeRequest): Promise<ExecutionResult> {
+    // 1) 计算对冲决策
+    const decision = hedgeCalculator.decide(req);
+    if (!decision.shouldHedge) {
+      return { success: true, executedQuantity: 0, executedPrice: 0, orderId: undefined, txHash: undefined };
+    }
+
+    const side: 'buy' | 'sell' = decision.hedgeQuantity > 0 ? 'buy' : 'sell';
+    const absQty = Math.abs(decision.hedgeQuantity);
+
+    // 2) 路由报价(根据 method)
+    const quote = decision.method === 'spot'
+      ? await tradeRouter.quoteSpot(req.symbol, absQty)
+      : await tradeRouter.quotePerp(req.symbol, absQty);
+
+    // 3) 滑点检查(占位:总是通过)。实际应根据 req.config.thresholds.maxSlippage 与 quote 对比
+
+    // 4) 生成执行计划
+    const plan: ExecutionPlan = {
+      method: decision.method,
+      symbol: req.symbol,
+      quantity: absQty,
+      side,
+      quote
+    };
+
+    // 5) 执行
+    const result = await tradeRouter.execute(plan);
+    return result as ExecutionResult;
+  }
+}
+
+export const hedgingExecutor = new HedgingExecutor();

+ 59 - 0
src/core/hedging/router.ts

@@ -0,0 +1,59 @@
+import { RouterQuote, ExecutionPlan } from './types';
+import { walletManager } from '../../infrastructure/wallet/walletManager';
+import { AsterAdapter, AsterConfig } from '../../dex/aster';
+
+export class TradeRouter {
+  private aster?: AsterAdapter;
+
+  constructor() {
+    const asterRpc = process.env.ASTER_RPC_URL;
+    const asterChainId = process.env.ASTER_CHAIN_ID ? Number(process.env.ASTER_CHAIN_ID) : undefined;
+    const asterRouter = process.env.ASTER_ROUTER_ADDRESS;
+    if (asterRpc && asterChainId && asterRouter) {
+      const cfg: AsterConfig = { rpcUrl: asterRpc, chainId: asterChainId, routerAddress: asterRouter };
+      this.aster = new AsterAdapter(cfg, walletManager.wallet || undefined);
+    }
+  }
+
+  async quoteSpot(symbol: string, quantity: number): Promise<RouterQuote> {
+    const price = 0;
+    return {
+      method: 'spot',
+      expectedIn: Math.abs(quantity),
+      expectedOut: Math.abs(quantity) * price,
+      price,
+      slippage: 0.0,
+      gasEstimateUSD: 0,
+      route: 'spot:placeholder'
+    };
+  }
+
+  async quotePerp(symbol: string, quantity: number): Promise<RouterQuote> {
+    if (!this.aster) throw new Error('Aster adapter not configured');
+    const side = quantity > 0 ? 'long' : 'short';
+    const q = await this.aster.quote({ symbol, side, quantity: Math.abs(quantity), slippage: 0.005 });
+    return {
+      method: 'perp',
+      expectedIn: Math.abs(quantity),
+      expectedOut: Math.abs(quantity) * q.expectedPrice,
+      price: q.expectedPrice,
+      slippage: 0.0,
+      gasEstimateUSD: 0,
+      route: 'aster:router'
+    };
+  }
+
+  async execute(plan: ExecutionPlan) {
+    if (plan.method === 'spot') {
+      const addr = await walletManager.getAddress();
+      return { success: true, txHash: `0xspot_${plan.symbol}_${plan.quantity}_${addr}` };
+    } else {
+      if (!this.aster) throw new Error('Aster adapter not configured');
+      const side = plan.side === 'buy' ? 'long' : 'short';
+      const res = await this.aster.openPerp({ symbol: plan.symbol, side, quantity: plan.quantity, slippage: 0.005, deadlineSec: Math.floor(Date.now()/1000)+300 });
+      return res;
+    }
+  }
+}
+
+export const tradeRouter = new TradeRouter();

+ 69 - 0
src/core/hedging/types.ts

@@ -0,0 +1,69 @@
+import { Position } from '../../types/core';
+
+export type HedgeMethod = 'spot' | 'perp';
+
+export interface HedgeThresholds {
+  maxSlippage: number;        // 允许最大滑点比例,如 0.005 = 0.5%
+  deltaDeviationPct: number;  // 允许的 delta 偏差百分比阈值
+  minFundingRate: number;     // 触发费率对冲的最小资金费率(绝对值)
+  minNotionalUSD: number;     // 执行最小名义金额(美元)
+}
+
+export interface FundingContext {
+  fundingRate: number;        // 当前资金费率(每8小时或每小时,统一成年化/周期化在调用处处理)
+  nextFundingTime?: number;
+}
+
+export interface PriceContext {
+  perpPrice: number;          // Perp 市场价格(USD)
+  hedgePrice: number;         // 对冲工具价格(USD)
+  correlation: number;        // 相关系数(0-1)
+}
+
+export interface HedgeConfig {
+  preferredMethod: HedgeMethod;  // 首选对冲方式
+  thresholds: HedgeThresholds;   // 阈值控制
+}
+
+export interface HedgeDecision {
+  shouldHedge: boolean;
+  method: HedgeMethod;
+  hedgeQuantity: number;      // 对冲工具的数量(按 hedgePrice 标的)
+  reason: string;
+  expectedFundingPnlPerPeriod?: number; // 预计单周期资金费收益(USD)
+}
+
+export interface RouterQuote {
+  method: HedgeMethod;
+  expectedIn?: number;        // 预期输入数量
+  expectedOut?: number;       // 预期输出数量
+  price: number;              // 用于滑点检查的价格
+  gasEstimateUSD?: number;    // 预估 gas 成本(USD)
+  slippage: number;           // 实际滑点
+  route?: string;             // 路由信息(演示)
+}
+
+export interface ExecutionPlan {
+  method: HedgeMethod;
+  symbol: string;
+  quantity: number;           // 对冲标的数量
+  side: 'buy' | 'sell';       // 对冲方向
+  quote: RouterQuote;
+}
+
+export interface ExecutionResult {
+  success: boolean;
+  txHash?: string;
+  orderId?: string | number;
+  executedQuantity?: number;
+  executedPrice?: number;
+  error?: string;
+}
+
+export interface HedgeRequest {
+  symbol: string;             // 如 BTCUSDT(perp 对)
+  perpPosition: Position;     // 当前 perp 仓位
+  prices: PriceContext;       // 价格上下文
+  funding: FundingContext;    // 资金费率上下文
+  config: HedgeConfig;        // 对冲配置
+}

+ 58 - 0
src/core/market/aggregator.ts

@@ -0,0 +1,58 @@
+import { ethers } from 'ethers';
+import { ChainlinkSource } from './sources/chainlinkSource';
+import { CcxtSource } from './sources/ccxtSource';
+import { UniswapV3Source } from './sources/uniswapV3Source';
+import { defaultMarketConfig, MarketConfig } from './marketConfig';
+import { EventEmitter } from 'events';
+
+export interface AggregatedPrice {
+  symbol: string;
+  price: number;
+  timestamp: number;
+  sources: Record<string, number>;
+}
+
+export class MarketAggregator extends EventEmitter {
+  private cfg: MarketConfig;
+  private chainlink: ChainlinkSource;
+  private ccxt: CcxtSource;
+  private uni: UniswapV3Source;
+
+  constructor(provider: ethers.JsonRpcProvider, cfg: MarketConfig = defaultMarketConfig) {
+    super();
+    this.cfg = cfg;
+    this.chainlink = new ChainlinkSource({ provider });
+    this.ccxt = new CcxtSource();
+    this.uni = new UniswapV3Source({ provider });
+  }
+
+  async fetchOnce(symbol: string): Promise<AggregatedPrice | null> {
+    const map = this.cfg.symbols[symbol];
+    if (!map) return null;
+
+    const [cl, cx, uv3] = await Promise.all([
+      this.chainlink.getPrice(map.chainlinkFeed || ''),
+      map.cexSymbol ? this.ccxt.getPrice(map.cexSymbol) : Promise.resolve(null),
+      this.uni.getPrice(map.uniswapV3Pool || '', map.inverted)
+    ]);
+
+    const sources: Record<string, number> = {};
+    const prices: number[] = [];
+    const now = Date.now();
+
+    if (cl && cl.price > 0) { prices.push(cl.price); sources.chainlink = cl.price; }
+    if (cx && cx.price > 0) { prices.push(cx.price); sources.ccxt = cx.price; }
+    if (uv3 && uv3.price > 0) { prices.push(uv3.price); sources.uniswapV3 = uv3.price; }
+
+    if (prices.length === 0) return null;
+
+    // 简单中位数聚合
+    prices.sort((a, b) => a - b);
+    const mid = prices.length >> 1;
+    const agg = prices.length % 2 ? prices[mid] : (prices[mid - 1] + prices[mid]) / 2;
+
+    const result: AggregatedPrice = { symbol, price: agg, timestamp: now, sources };
+    this.emit('price', result);
+    return result;
+  }
+}

+ 6 - 0
src/core/market/index.ts

@@ -0,0 +1,6 @@
+export * from './marketConfig';
+export * from './sources/chainlinkSource';
+export * from './sources/ccxtSource';
+export * from './sources/uniswapV3Source';
+export * from './aggregator';
+export * from './marketDataService';

+ 46 - 0
src/core/market/marketConfig.ts

@@ -0,0 +1,46 @@
+export interface SymbolMapping {
+  symbol: string;            // 统一符号,如 BTCUSDT
+  cexSymbol?: string;        // CEX 符号,如 BTC/USDT
+  chainlinkFeed?: string;    // Chainlink 预言机地址
+  uniswapV3Pool?: string;    // Uniswap V3 池地址
+  baseDecimals?: number;     // base token decimals
+  quoteDecimals?: number;    // quote token decimals
+  inverted?: boolean;        // 是否取 1/price
+}
+
+export interface MarketConfig {
+  symbols: Record<string, SymbolMapping>;
+  pollIntervals: {
+    chainlinkMs: number;
+    cexMs: number;
+    uniswapMs: number;
+  };
+}
+
+export const defaultMarketConfig: MarketConfig = {
+  symbols: {
+    BTCUSDT: {
+      symbol: 'BTCUSDT',
+      cexSymbol: 'BTC/USDT',
+      chainlinkFeed: '0x0000000000000000000000000000000000000000',
+      uniswapV3Pool: '0x0000000000000000000000000000000000000000',
+      baseDecimals: 8,
+      quoteDecimals: 6,
+      inverted: false
+    },
+    ETHUSDT: {
+      symbol: 'ETHUSDT',
+      cexSymbol: 'ETH/USDT',
+      chainlinkFeed: '0x0000000000000000000000000000000000000000',
+      uniswapV3Pool: '0x0000000000000000000000000000000000000000',
+      baseDecimals: 18,
+      quoteDecimals: 6,
+      inverted: false
+    }
+  },
+  pollIntervals: {
+    chainlinkMs: 5000,
+    cexMs: 2000,
+    uniswapMs: 5000
+  }
+};

+ 25 - 0
src/core/market/marketDataFetcher.ts

@@ -0,0 +1,25 @@
+import { EnhancedMarketManager } from '../../market/enhancedMarketManager';
+import { eventBus } from '../../utils/events';
+
+export class MarketDataFetcher {
+  private manager = new EnhancedMarketManager();
+  private initialized = false;
+
+  async init(): Promise<void> {
+    if (this.initialized) return;
+    await this.manager.initialize();
+    this.initialized = true;
+
+    this.manager.on('marketData', data => eventBus.emit('marketData', data));
+    this.manager.on('ticker24hr', data => eventBus.emit('ticker24hr', data));
+    this.manager.on('kline', data => eventBus.emit('kline', data));
+    this.manager.on('depth', data => eventBus.emit('depth', data));
+  }
+
+  subscribe(symbols: string[], intervals: string[] = ['1m']): void {
+    if (!this.initialized) throw new Error('MarketDataFetcher not initialized');
+    this.manager.subscribeMarketData(symbols, intervals);
+  }
+}
+
+export const marketDataFetcher = new MarketDataFetcher();

+ 29 - 0
src/core/market/marketDataService.ts

@@ -0,0 +1,29 @@
+import { ethers } from 'ethers';
+import { MarketAggregator } from './aggregator';
+import { defaultMarketConfig } from './marketConfig';
+import { eventBus } from '../../utils/events';
+
+export class MarketDataService {
+  private aggregator: MarketAggregator;
+  private timers: NodeJS.Timeout[] = [];
+
+  constructor(provider: ethers.JsonRpcProvider) {
+    this.aggregator = new MarketAggregator(provider, defaultMarketConfig);
+    this.aggregator.on('price', p => eventBus.emit('aggPrice', p));
+  }
+
+  start(symbols: string[]): void {
+    this.stop();
+    for (const s of symbols) {
+      const t = setInterval(() => {
+        this.aggregator.fetchOnce(s).catch(() => undefined);
+      }, 1500);
+      this.timers.push(t);
+    }
+  }
+
+  stop(): void {
+    this.timers.forEach(t => clearInterval(t));
+    this.timers = [];
+  }
+}

+ 34 - 0
src/core/market/sources/ccxtSource.ts

@@ -0,0 +1,34 @@
+import ccxt from 'ccxt';
+
+export interface CcxtOptions {
+  exchangeId?: string; // e.g., binance
+  apiKey?: string;
+  secret?: string;
+}
+
+export class CcxtSource {
+  private exchange: ccxt.Exchange;
+
+  constructor(options: CcxtOptions = {}) {
+    const id = options.exchangeId || 'binance';
+    // @ts-ignore
+    const ExchangeClass = ccxt[id];
+    this.exchange = new ExchangeClass({
+      apiKey: options.apiKey,
+      secret: options.secret,
+      enableRateLimit: true,
+      timeout: 10000
+    });
+  }
+
+  async getPrice(symbol: string): Promise<{ price: number; timestamp: number } | null> {
+    try {
+      const ticker = await this.exchange.fetchTicker(symbol);
+      const price = ticker.last || ticker.close || ticker.bid || ticker.ask;
+      if (!price) return null;
+      return { price, timestamp: ticker.timestamp || Date.now() };
+    } catch (e) {
+      return null;
+    }
+  }
+}

+ 20 - 0
src/core/market/sources/chainlinkSource.ts

@@ -0,0 +1,20 @@
+import { ethers } from 'ethers';
+
+export interface ChainlinkOptions {
+  provider: ethers.JsonRpcProvider;
+}
+
+export class ChainlinkSource {
+  private provider: ethers.JsonRpcProvider;
+
+  constructor(options: ChainlinkOptions) {
+    this.provider = options.provider;
+  }
+
+  // 读取 aggregatorV3Interface latestRoundData(占位)
+  async getPrice(feedAddress: string): Promise<{ price: number; decimals: number; timestamp: number } | null> {
+    if (!feedAddress || feedAddress === '0x0000000000000000000000000000000000000000') return null;
+    // TODO: 集成真实 ABI 调用
+    return null;
+  }
+}

+ 20 - 0
src/core/market/sources/uniswapV3Source.ts

@@ -0,0 +1,20 @@
+import { ethers } from 'ethers';
+
+export interface UniswapV3Options {
+  provider: ethers.JsonRpcProvider;
+}
+
+export class UniswapV3Source {
+  private provider: ethers.JsonRpcProvider;
+
+  constructor(options: UniswapV3Options) {
+    this.provider = options.provider;
+  }
+
+  // 从池读取 sqrtPriceX96 并换算(占位)
+  async getPrice(poolAddress: string, inverted: boolean = false): Promise<{ price: number; timestamp: number } | null> {
+    if (!poolAddress || poolAddress === '0x0000000000000000000000000000000000000000') return null;
+    // TODO: 读取 slot0.sqrtPriceX96 并转换为价格
+    return null;
+  }
+}

+ 19 - 0
src/core/strategy/positionStrategy.ts

@@ -0,0 +1,19 @@
+import { TradingSignal } from '../../types/core';
+
+export class PositionStrategy {
+  generateSignals(symbols: string[]): TradingSignal[] {
+    const now = Date.now();
+    // TODO: 使用指标计算真实信号
+    return symbols.map(symbol => ({
+      symbol,
+      action: 'hedge',
+      confidence: 0.5,
+      quantity: 0,
+      reason: 'placeholder',
+      indicators: {},
+      timestamp: now
+    }));
+  }
+}
+
+export const positionStrategy = new PositionStrategy();

+ 184 - 0
src/dex/aster/asterAdapter.ts

@@ -0,0 +1,184 @@
+import { AbiCoder, Wallet, keccak256, getBytes } from 'ethers';
+
+export interface AsterConfig {
+  rpcUrl: string;
+  chainId: number;
+  routerAddress: string;     // Aster 合约/路由地址(根据文档填写)
+  readerAddress?: string;    // 读取器合约地址(可选)
+  httpBase?: string;         // REST API Base,例如 https://fapi.asterdex.com
+  defaultUser?: string;      // 下单 user 地址(owner)
+  defaultSigner?: string;    // 下单 signer 地址(与私钥对应)
+}
+
+export interface AsterQuoteParams {
+  symbol: string;            // 交易对(与 Aster 平台定义保持一致)
+  side: 'long' | 'short';
+  quantity: number;          // 张数或标的数量(依据 Aster 定义)
+  slippage: number;          // 最大滑点比例
+}
+
+export interface AsterQuoteResult {
+  expectedPrice: number;
+  feeUsd?: number;
+  fundingRate?: number;
+}
+
+export interface AsterOrderParams extends AsterQuoteParams {
+  deadlineSec: number;
+}
+
+export interface AsterOrderResult {
+  success: boolean;
+  txHash?: string;
+  error?: string;
+}
+
+export class AsterAdapter {
+  private provider: any; // 懒加载链上需求
+  private signer?: Wallet;
+  private cfg: AsterConfig;
+
+  constructor(cfg: AsterConfig, signer?: Wallet) {
+    this.cfg = cfg;
+    // 仅在需要链上时使用 provider
+    // @ts-ignore
+    this.provider = undefined;
+    this.signer = signer;
+  }
+
+  async getFundingRate(symbol: string): Promise<number | null> {
+    // TODO: 根据 Aster 文档读取资金费率(链上或 API)
+    return null;
+  }
+
+  async quote(params: AsterQuoteParams): Promise<AsterQuoteResult> {
+    // TODO: 调用 Aster 合约/接口进行报价估算
+    return { expectedPrice: 0 };
+  }
+
+  async openPerp(params: AsterOrderParams): Promise<AsterOrderResult> {
+    // TODO: 构造并签名交易,发送到 Aster 路由
+    return { success: false, error: 'not_implemented' };
+  }
+
+  async closePerp(params: AsterOrderParams): Promise<AsterOrderResult> {
+    // TODO
+    return { success: false, error: 'not_implemented' };
+  }
+
+  // === REST 下单封装(按照文档 /fapi/v3/order 签名流程) ===
+
+  /**
+   * 业务参数 -> 纯字符串字典(递归字符串化 list/dict),移除空值
+   */
+  private normalizeBusinessParams(input: Record<string, any>): Record<string, string> {
+    const cleaned: Record<string, any> = {};
+    for (const [k, v] of Object.entries(input)) {
+      if (v === undefined || v === null) continue;
+      cleaned[k] = v;
+    }
+
+    const toStringValue = (val: any): string => {
+      if (Array.isArray(val)) {
+        const arr = val.map(item => (typeof item === 'object' && item !== null) ? JSON.stringify(this.deepStringify(item)) : String(item));
+        return JSON.stringify(arr);
+      }
+      if (typeof val === 'object') {
+        return JSON.stringify(this.deepStringify(val));
+      }
+      return String(val);
+    };
+
+    const out: Record<string, string> = {};
+    for (const [k, v] of Object.entries(cleaned)) {
+      out[k] = toStringValue(v);
+    }
+    return out;
+  }
+
+  private deepStringify(obj: Record<string, any>): Record<string, string> {
+    const out: Record<string, string> = {};
+    for (const [k, v] of Object.entries(obj)) {
+      if (v === undefined || v === null) continue;
+      if (Array.isArray(v)) {
+        out[k] = JSON.stringify(v.map(item => (typeof item === 'object' && item !== null) ? this.deepStringify(item) : String(item)));
+      } else if (typeof v === 'object') {
+        out[k] = JSON.stringify(this.deepStringify(v as any));
+      } else {
+        out[k] = String(v);
+      }
+    }
+    return out;
+  }
+
+  /**
+   * 生成按 ASCII 排序的 JSON 字符串
+   */
+  private stableJSONString(obj: Record<string, string>): string {
+    const entries = Object.entries(obj).sort(([a], [b]) => a.localeCompare(b));
+    const sorted = Object.fromEntries(entries);
+    return JSON.stringify(sorted);
+  }
+
+  /**
+   * 计算签名(ABI 编码 ['string','address','address','uint256'] 后 keccak,再 personal_sign)
+   */
+  private async signOrderJSONString(jsonStr: string, user: string, signerAddr: string, nonce: bigint, privateKey: string): Promise<string> {
+    const coder = AbiCoder.defaultAbiCoder();
+    const encoded = coder.encode(['string','address','address','uint256'], [jsonStr, user, signerAddr, nonce]);
+    const hash = keccak256(encoded);
+    const wallet = new Wallet(privateKey);
+    const sig = await wallet.signMessage(getBytes(hash));
+    return sig;
+  }
+
+  /**
+   * 仅生成签名(不发起 HTTP 请求)。
+   * 返回 jsonStr(按 ASCII 排序的业务 JSON 字符串)、nonce(微秒)、signature、user、signer 供外部自行拼装表单提交。
+   */
+  async generateOrderSignature(order: {
+    symbol: string;
+    positionSide: 'BOTH' | 'LONG' | 'SHORT';
+    type: 'LIMIT' | 'MARKET' | 'STOP' | 'STOP_MARKET' | 'TAKE_PROFIT' | 'TAKE_PROFIT_MARKET';
+    side: 'BUY' | 'SELL';
+    timeInForce?: 'GTC' | 'IOC' | 'FOK' | 'GTX' | 'GTD';
+    quantity: string | number;
+    price?: string | number;
+    recvWindow?: number;
+    timestamp?: number;
+  }, creds?: { user?: string; signer?: string; privateKey?: string; nonceMicros?: bigint }): Promise<{
+    jsonStr: string;
+    nonce: bigint;
+    signature: string;
+    user: string;
+    signer: string;
+    formFields: Record<string, string>; // 直接用于 x-www-form-urlencoded 的字段
+  }> {
+    const business: Record<string, any> = {
+      ...order,
+      recvWindow: order.recvWindow ?? 50000,
+      timestamp: order.timestamp ?? Date.now()
+    };
+
+    const bizStrDict = this.normalizeBusinessParams(business);
+    const jsonStr = this.stableJSONString(bizStrDict);
+
+    const user = creds?.user || this.cfg.defaultUser || process.env.ASTER_ORDER_USER || '';
+    const signer = creds?.signer || this.cfg.defaultSigner || process.env.ASTER_ORDER_SIGNER || '';
+    const privateKey = creds?.privateKey || process.env.PRIVATE_KEY || '';
+    if (!user || !signer || !privateKey) throw new Error('missing_user_signer_privateKey');
+
+    const nonce = creds?.nonceMicros ?? BigInt(Math.trunc(Date.now() * 1000));
+    const signature = await this.signOrderJSONString(jsonStr, user, signer, nonce, privateKey);
+
+    const formFields: Record<string, string> = {
+      ...bizStrDict,
+      nonce: String(nonce),
+      user,
+      signer,
+      signature
+    };
+
+    return { jsonStr, nonce, signature, user, signer, formFields };
+  }
+}

+ 170 - 0
src/dex/aster/dualAccountHedgeExecutor.ts

@@ -0,0 +1,170 @@
+import { AsterTradingClient } from './tradingClient';
+import { OrderType, OrderSide, TimeInForce } from './tradingTypes';
+
+export interface DualAccountConfig {
+  symbol: string;
+  quantity: string;              // 名义数量(张或币量,按 Aster 定义)
+  entrySkewBps?: number;         // 入场滑点(基点),可选
+  takeProfitBps: number;         // 止盈幅度(基点)
+  stopLossBps: number;           // 止损幅度(基点)
+  timeInForce?: TimeInForce;     // 有效方式
+}
+
+export interface HedgeResult {
+  longOrderId: number;
+  shortOrderId: number;
+  longTpId?: number;
+  longSlId?: number;
+  shortTpId?: number;
+  shortSlId?: number;
+}
+
+/**
+ * 双账户对冲执行器
+ * 账户A开多,账户B开空;随后分别挂 reduce-only 止盈止损
+ */
+export class DualAccountHedgeExecutor {
+  private accountA: AsterTradingClient;
+  private accountB: AsterTradingClient;
+
+  constructor(accountA: AsterTradingClient, accountB: AsterTradingClient) {
+    this.accountA = accountA;
+    this.accountB = accountB;
+  }
+
+  /**
+   * 执行对冲(同步开仓 + 带TP/SL)
+   */
+  async openHedgeWithTPSL(cfg: DualAccountConfig, referencePrice?: number): Promise<HedgeResult> {
+    const symbol = cfg.symbol.toUpperCase();
+    const tif = cfg.timeInForce ?? TimeInForce.GTC;
+
+    // 计算价格
+    const mid = referencePrice ?? await this.fetchMarkPrice(symbol);
+    const toPrice = (bps: number, up: boolean) => {
+      const pct = bps / 10000;
+      return (up ? mid * (1 + pct) : mid * (1 - pct)).toFixed(6);
+    };
+
+    const tpLong = toPrice(cfg.takeProfitBps, true);
+    const slLong = toPrice(cfg.stopLossBps, false);
+    const tpShort = toPrice(cfg.takeProfitBps, false);
+    const slShort = toPrice(cfg.stopLossBps, true);
+
+    // 账户A: 开多 (可用限价挂较优价,或直接市价)
+    const orderLong = await this.accountA.placeOrder({
+      symbol,
+      side: OrderSide.BUY,
+      type: OrderType.MARKET,
+      quantity: cfg.quantity,
+      positionSide: 'LONG',
+      newOrderRespType: 'RESULT'
+    });
+
+    // 账户B: 开空
+    const orderShort = await this.accountB.placeOrder({
+      symbol,
+      side: OrderSide.SELL,
+      type: OrderType.MARKET,
+      quantity: cfg.quantity,
+      positionSide: 'SHORT',
+      newOrderRespType: 'RESULT'
+    });
+
+    // A: 止盈(只平仓),止损(只平仓)
+    const aTp = await this.accountA.placeOrder({
+      symbol,
+      side: OrderSide.SELL,
+      type: OrderType.TAKE_PROFIT,
+      quantity: cfg.quantity,
+      price: tpLong,
+      stopPrice: tpLong,
+      timeInForce: tif,
+      positionSide: 'LONG',
+      reduceOnly: true as unknown as boolean // 后端为字符串, 此处透传
+    });
+    const aSl = await this.accountA.placeOrder({
+      symbol,
+      side: OrderSide.SELL,
+      type: OrderType.STOP,
+      quantity: cfg.quantity,
+      price: slLong,
+      stopPrice: slLong,
+      timeInForce: tif,
+      positionSide: 'LONG',
+      reduceOnly: true as unknown as boolean
+    });
+
+    // B: 止盈/止损(只平仓)
+    const bTp = await this.accountB.placeOrder({
+      symbol,
+      side: OrderSide.BUY,
+      type: OrderType.TAKE_PROFIT,
+      quantity: cfg.quantity,
+      price: tpShort,
+      stopPrice: tpShort,
+      timeInForce: tif,
+      positionSide: 'SHORT',
+      reduceOnly: true as unknown as boolean
+    });
+    const bSl = await this.accountB.placeOrder({
+      symbol,
+      side: OrderSide.BUY,
+      type: OrderType.STOP,
+      quantity: cfg.quantity,
+      price: slShort,
+      stopPrice: slShort,
+      timeInForce: tif,
+      positionSide: 'SHORT',
+      reduceOnly: true as unknown as boolean
+    });
+
+    return {
+      longOrderId: orderLong.orderId,
+      shortOrderId: orderShort.orderId,
+      longTpId: aTp.orderId,
+      longSlId: aSl.orderId,
+      shortTpId: bTp.orderId,
+      shortSlId: bSl.orderId
+    };
+  }
+
+  /**
+   * 平仓两边
+   */
+  async closeBoth(symbol: string, quantity: string): Promise<void> {
+    const s = symbol.toUpperCase();
+    // A 平多
+    await this.accountA.placeOrder({
+      symbol: s,
+      side: OrderSide.SELL,
+      type: OrderType.MARKET,
+      quantity,
+      positionSide: 'LONG',
+      reduceOnly: true as unknown as boolean,
+      newOrderRespType: 'RESULT'
+    });
+    // B 平空
+    await this.accountB.placeOrder({
+      symbol: s,
+      side: OrderSide.BUY,
+      type: OrderType.MARKET,
+      quantity,
+      positionSide: 'SHORT',
+      reduceOnly: true as unknown as boolean,
+      newOrderRespType: 'RESULT'
+    });
+  }
+
+  private async fetchMarkPrice(symbol: string): Promise<number> {
+    // 简化:直接使用 ticker 价格作为参考
+    // 在项目中已有 REST 客户端时可替换为真实 markPrice 拉取
+    // 这里使用账户A的 httpClient 简易调用
+    // 为简洁暂不暴露 httpClient;生产可抽象统一 REST 客户端
+    const res = await fetch(`${this.accountA.getConfig().baseUrl}/fapi/v3/ticker/price?symbol=${symbol}`);
+    const data = await res.json() as { price: string };
+    return parseFloat(data.price);
+  }
+}
+
+

+ 322 - 0
src/dex/aster/gridConfig.ts

@@ -0,0 +1,322 @@
+import { DualGridConfig } from './gridDualExecutor';
+
+/**
+ * 超详细中文说明(把你当第一次用的朋友来写):
+ *
+ * 这个文件做的事:
+ * - 从环境变量(.env 或启动命令里的 GRID_XXX)读取参数
+ * - 设定“保守、安全”的默认值(就算你一个都不配,也能跑)
+ * - 把这些参数转换成网格机器人能理解的配置对象
+ *
+ * 一句话原则:宁可慢,别炸;先活下来,有单边也要能退。
+ *
+ * 如何使用:
+ * - 不懂就什么都不改,直接跑,默认值很保守。
+ * - 想改某个参数:在 .env 里加一行,比如:
+ *   GRID_BASE_NOTIONAL=3
+ *   这样就把“每笔名义金额”从默认 2 美元改为 3 美元。
+ * - 想恢复默认:把这一行删掉或注释掉即可。
+ *
+ * 参数大类(直白解释):
+ * - 基础参数:交易品种、杠杆、每笔下多少美金、网格步长(间隔)
+ * - 节奏控制:多久下一个单、每次下几个、是否激进、是否并发
+ * - 成本/点差:点差太小就不做、成本上限(避免越做越亏)
+ * - 高频控制:高频模式开不开、每侧最多并发几个、持有多久就平
+ * - 做市(限价)参数:优先用限价省手续费,超时就撤掉走市价
+ * - 再平衡:两个账户/双向仓位的净敞口太偏时,把它往中间拉
+ * - 去杠杆:资金吃紧(可用资金/钱包余额 比例低)就自动减仓
+ * - Taker 爆发:在很短时间窗口内快速打几单,但有并发和冷却限制
+ * - 保护单:持续维护止盈/止损(可开/可关),避免裸奔
+ * - 审计日志:把每次开/平、资金、仓位写日志,方便复盘
+ *
+ * 提醒:
+ * - 你看到的“默认值”都是偏保守,不追求收益最大化,先把风险压住。
+ * - 真想冲,就把“每笔名义金额”、“网格步长”、“并发”等逐步往上调,
+ *   但务必结合你的资金体量与风险偏好。
+ */
+
+export interface GridEnv {
+  GRID_SYMBOL?: string;
+  GRID_LEVERAGE?: string;
+  GRID_BASE_NOTIONAL?: string;
+  GRID_STEP_BPS?: string;
+  GRID_COUNT?: string;
+  GRID_TICK_MS?: string;
+  GRID_MAX_OPEN?: string;
+  GRID_TP_BPS?: string;
+  GRID_SL_BPS?: string;
+  GRID_WORKING_TYPE?: 'MARK_PRICE' | 'CONTRACT_PRICE' | string;
+  GRID_STATE_FILE?: string;
+  GRID_PERSIST_MS?: string;
+  GRID_MIN_NOTIONAL_USD?: string;
+  GRID_FORCE_OPEN_ON_START?: string;
+  GRID_MIN_OPEN_INTERVAL_MS?: string;
+  GRID_ORDERS_PER_TICK_PER_SIDE?: string;
+  GRID_AGGRESSIVE?: string;
+  GRID_PARALLEL_PLACEMENT?: string;
+  GRID_MAX_EXPOSURE_USD?: string;
+  GRID_REDUCE_WHEN_EXPOSURE_ABOVE_USD?: string;
+  GRID_REDUCE_CHUNK_USD?: string;
+  GRID_MIN_REDUCE_INTERVAL_MS?: string;
+  GRID_USE_MAX_LEVERAGE?: string;
+  GRID_CONTINUOUS_PROTECT?: string;
+  GRID_PROTECT_REFRESH_MS?: string;
+  GRID_HF_MODE?: string;
+  GRID_HF_HOLD_MS?: string;
+  GRID_HF_MAX_CONCURRENT_PER_SIDE?: string;
+  GRID_AUDIT_ENABLED?: string;
+  GRID_AUDIT_LOG_FILE?: string;
+  GRID_MAKER_MODE?: string;              // 入场使用限价GTX(post-only)
+  GRID_MAKER_OFFSET_BPS?: string;        // 入场挂价相对中价偏移bps
+  GRID_MAKER_TIMEOUT_MS?: string;        // 入场挂单超时取消
+  GRID_REBALANCE_ENABLED?: string;       // 是否启用净敞口再平衡
+  GRID_REBALANCE_THRESHOLD_QTY?: string; // 触发再平衡的净持仓阈值(基础币数量)
+  GRID_REBALANCE_CHUNK_QTY?: string;     // 每次再平衡数量(基础币数量)
+  GRID_REBALANCE_MIN_INTERVAL_MS?: string; // 再平衡最小间隔
+  GRID_DELEVERAGE_AFTER_2019?: string;     // -2019 后是否尝试减仓
+  GRID_DELEVERAGE_CHUNK_QTY?: string;      // 每次减仓数量(基础币)
+  GRID_DELEVERAGE_MIN_INTERVAL_MS?: string;// 减仓最小间隔
+  GRID_DELEVERAGE_STEPS_ON_2019?: string;  // -2019 时连击步数
+  GRID_DELEVERAGE_MULTIPLIER?: string;     // 连击步进系数
+  GRID_DELEVERAGE_MAX_CHUNK_QTY?: string;  // 单次最大减仓数量
+  GRID_TAKER_RATIO?: string;               // Taker 触发比例(0~1)
+  GRID_TAKER_BURST_MS?: string;            // Taker 爆发时窗(ms)
+  GRID_TAKER_MAX_CONCURRENT?: string;      // Taker 每侧最大并发
+  GRID_COST_CAP_BPS?: string;              // 成本上限(费率+点差+滑点)bps
+  GRID_MIN_SPREAD_BPS?: string;            // 最小点差 bps
+  GRID_TAKER_COOLDOWN_MS?: string;         // Taker 连续失败后的冷却(ms)
+  GRID_MAX_GROSS_QTY_PER_SIDE?: string;    // 每侧最大总持仓数量(基础币),超过则优先减仓
+  GRID_DERISK_ENABLED?: string;            // 启用利用率去杠杆器
+  GRID_DERISK_AVAIL_RATIO?: string;        // 可用/钱包 低于该比例时去杠杆(0~1)
+  GRID_DERISK_CHUNK_QTY?: string;          // 每次去杠杆数量(基础币)
+  GRID_DERISK_INTERVAL_MS?: string;        // 去杠杆最小间隔
+  GRID_MAX_NET_QTY?: string;               // 允许的最大净敞口(基础币)
+  GRID_ENTRY_ALIGN_ENABLED?: string;       // 启用开仓均价动态回归(成对刷新)
+  GRID_ENTRY_ALIGN_BPS?: string;           // 触发阈值:两侧开仓均价差(bps)
+  GRID_ENTRY_ALIGN_CHUNK_RATIO?: string;   // 每次回归的成对刷新比例(0~1)
+  GRID_ENTRY_ALIGN_MIN_INTERVAL_MS?: string; // 回归的最小间隔
+  GRID_ENTRY_ALIGN_MODE?: 'ADD' | 'REPLACE' | 'AUTO' | string; // 回归模式:ADD=同向加仓,REPLACE=先反向平仓再同向重开,AUTO=自适应
+  GRID_ENTRY_ALIGN_ADD_BPS?: string;        // AUTO模式:ADD触发阈值(bps)
+  GRID_ENTRY_ALIGN_REPLACE_BPS?: string;    // AUTO模式:REPLACE触发阈值(bps)
+  GRID_ENTRY_ALIGN_SUSTAIN_TICKS?: string;  // AUTO模式:REPLACE需连续满足阈值的tick数
+  // 薄盘口优化
+  GRID_SPREAD_AWARE?: string;               // 启用点差感知
+  GRID_TAKER_MAX_SPREAD_BPS?: string;       // 允许吃单的最大点差(bps)
+  GRID_MAKER_JOIN_BEST?: string;            // maker 价格跟随盘口最优
+  GRID_MAKER_INSIDE_TICKS?: string;         // maker 相对最优内移的 tick 数
+}
+
+export function loadGridEnv(env: NodeJS.ProcessEnv = process.env): GridEnv {
+  return {
+    GRID_SYMBOL: env.GRID_SYMBOL,
+    GRID_LEVERAGE: env.GRID_LEVERAGE,
+    GRID_BASE_NOTIONAL: env.GRID_BASE_NOTIONAL,
+    GRID_STEP_BPS: env.GRID_STEP_BPS,
+    GRID_COUNT: env.GRID_COUNT,
+    GRID_TICK_MS: env.GRID_TICK_MS,
+    GRID_MAX_OPEN: env.GRID_MAX_OPEN,
+    GRID_TP_BPS: env.GRID_TP_BPS,
+    GRID_SL_BPS: env.GRID_SL_BPS,
+    GRID_WORKING_TYPE: env.GRID_WORKING_TYPE as any,
+    GRID_STATE_FILE: env.GRID_STATE_FILE,
+    GRID_PERSIST_MS: env.GRID_PERSIST_MS,
+    GRID_MIN_NOTIONAL_USD: env.GRID_MIN_NOTIONAL_USD,
+    GRID_FORCE_OPEN_ON_START: env.GRID_FORCE_OPEN_ON_START,
+    GRID_MIN_OPEN_INTERVAL_MS: env.GRID_MIN_OPEN_INTERVAL_MS,
+    GRID_ORDERS_PER_TICK_PER_SIDE: env.GRID_ORDERS_PER_TICK_PER_SIDE,
+    GRID_AGGRESSIVE: env.GRID_AGGRESSIVE,
+    GRID_PARALLEL_PLACEMENT: env.GRID_PARALLEL_PLACEMENT,
+    GRID_MAX_EXPOSURE_USD: env.GRID_MAX_EXPOSURE_USD,
+    GRID_REDUCE_WHEN_EXPOSURE_ABOVE_USD: env.GRID_REDUCE_WHEN_EXPOSURE_ABOVE_USD,
+    GRID_REDUCE_CHUNK_USD: env.GRID_REDUCE_CHUNK_USD,
+    GRID_MIN_REDUCE_INTERVAL_MS: env.GRID_MIN_REDUCE_INTERVAL_MS,
+    GRID_USE_MAX_LEVERAGE: env.GRID_USE_MAX_LEVERAGE,
+    GRID_CONTINUOUS_PROTECT: env.GRID_CONTINUOUS_PROTECT,
+    GRID_PROTECT_REFRESH_MS: env.GRID_PROTECT_REFRESH_MS,
+    GRID_HF_MODE: env.GRID_HF_MODE,
+    GRID_HF_HOLD_MS: env.GRID_HF_HOLD_MS,
+    GRID_HF_MAX_CONCURRENT_PER_SIDE: env.GRID_HF_MAX_CONCURRENT_PER_SIDE,
+    GRID_AUDIT_ENABLED: env.GRID_AUDIT_ENABLED,
+    GRID_AUDIT_LOG_FILE: env.GRID_AUDIT_LOG_FILE,
+    GRID_TAKER_RATIO: env.GRID_TAKER_RATIO,
+    GRID_TAKER_BURST_MS: env.GRID_TAKER_BURST_MS,
+    GRID_TAKER_MAX_CONCURRENT: env.GRID_TAKER_MAX_CONCURRENT,
+    GRID_COST_CAP_BPS: env.GRID_COST_CAP_BPS,
+    GRID_MIN_SPREAD_BPS: env.GRID_MIN_SPREAD_BPS,
+    GRID_TAKER_COOLDOWN_MS: env.GRID_TAKER_COOLDOWN_MS
+    ,GRID_MAX_GROSS_QTY_PER_SIDE: env.GRID_MAX_GROSS_QTY_PER_SIDE
+    ,GRID_DERISK_ENABLED: env.GRID_DERISK_ENABLED
+    ,GRID_DERISK_AVAIL_RATIO: env.GRID_DERISK_AVAIL_RATIO
+    ,GRID_DERISK_CHUNK_QTY: env.GRID_DERISK_CHUNK_QTY
+    ,GRID_DERISK_INTERVAL_MS: env.GRID_DERISK_INTERVAL_MS
+    ,GRID_MAX_NET_QTY: env.GRID_MAX_NET_QTY
+    ,GRID_ENTRY_ALIGN_ENABLED: env.GRID_ENTRY_ALIGN_ENABLED
+    ,GRID_ENTRY_ALIGN_BPS: env.GRID_ENTRY_ALIGN_BPS
+    ,GRID_ENTRY_ALIGN_CHUNK_RATIO: env.GRID_ENTRY_ALIGN_CHUNK_RATIO
+    ,GRID_ENTRY_ALIGN_MIN_INTERVAL_MS: env.GRID_ENTRY_ALIGN_MIN_INTERVAL_MS
+    ,GRID_ENTRY_ALIGN_MODE: env.GRID_ENTRY_ALIGN_MODE as any
+    ,GRID_ENTRY_ALIGN_ADD_BPS: env.GRID_ENTRY_ALIGN_ADD_BPS
+    ,GRID_ENTRY_ALIGN_REPLACE_BPS: env.GRID_ENTRY_ALIGN_REPLACE_BPS
+    ,GRID_ENTRY_ALIGN_SUSTAIN_TICKS: env.GRID_ENTRY_ALIGN_SUSTAIN_TICKS
+    ,GRID_SPREAD_AWARE: env.GRID_SPREAD_AWARE
+    ,GRID_TAKER_MAX_SPREAD_BPS: env.GRID_TAKER_MAX_SPREAD_BPS
+    ,GRID_MAKER_JOIN_BEST: env.GRID_MAKER_JOIN_BEST
+    ,GRID_MAKER_INSIDE_TICKS: env.GRID_MAKER_INSIDE_TICKS
+  };
+}
+
+// 参数默认值(保守):
+// - baseNotionalUsd=2:单笔名义低,减少 -2019
+// - gridStepBps=60:较大步长,覆盖成本/点差
+// - hfMaxConcurrentPerSide=2、ordersPerTickPerSide=1:降低并发
+// - minSpreadBps=25:点差较小时不吃单
+// - takerCooldownMs=10000:-2019 后适度冷却
+// - maxGrossQtyPerSide=0.02、maxNetQty=0.003:限制总仓与净敞口
+// - 去杠杆阈值=25%,步长0.006,间隔6s:保证金吃紧时主动释压
+export function toDualGridConfig(env: GridEnv): DualGridConfig {
+  const symbol = (env.GRID_SYMBOL || 'BTCUSDT').toUpperCase();
+  const leverage = parseInt(env.GRID_LEVERAGE || '100');
+  const baseNotionalUsd = parseFloat(env.GRID_BASE_NOTIONAL || '2');
+  const gridStepBps = parseInt(env.GRID_STEP_BPS || '60');
+  const gridCount = parseInt(env.GRID_COUNT || '1');
+  const tickIntervalMs = parseInt(env.GRID_TICK_MS || '700');
+  const maxOpenPerSide = parseInt(env.GRID_MAX_OPEN || '1');
+  const tpBps = env.GRID_TP_BPS ? parseInt(env.GRID_TP_BPS) : undefined;
+  const slBps = env.GRID_SL_BPS ? parseInt(env.GRID_SL_BPS) : undefined;
+  const workingType = env.GRID_WORKING_TYPE as 'MARK_PRICE' | 'CONTRACT_PRICE' | undefined;
+  const stateFile = env.GRID_STATE_FILE || undefined;
+  const persistIntervalMs = env.GRID_PERSIST_MS ? parseInt(env.GRID_PERSIST_MS) : 8000;
+  const minNotionalUsd = env.GRID_MIN_NOTIONAL_USD ? parseFloat(env.GRID_MIN_NOTIONAL_USD) : undefined;
+  const forceOpenOnStart = env.GRID_FORCE_OPEN_ON_START ? env.GRID_FORCE_OPEN_ON_START === '1' : true;
+  const minOpenIntervalMs = env.GRID_MIN_OPEN_INTERVAL_MS ? parseInt(env.GRID_MIN_OPEN_INTERVAL_MS) : 1000;
+  const ordersPerTickPerSide = env.GRID_ORDERS_PER_TICK_PER_SIDE ? parseInt(env.GRID_ORDERS_PER_TICK_PER_SIDE) : 1;
+  const aggressive = env.GRID_AGGRESSIVE ? env.GRID_AGGRESSIVE === '1' : true;
+  const parallelPlacement = env.GRID_PARALLEL_PLACEMENT ? env.GRID_PARALLEL_PLACEMENT === '1' : true;
+  const maxExposureUsdPerSide = env.GRID_MAX_EXPOSURE_USD ? parseFloat(env.GRID_MAX_EXPOSURE_USD) : undefined;
+  const reduceWhenExposureAboveUsd = env.GRID_REDUCE_WHEN_EXPOSURE_ABOVE_USD ? parseFloat(env.GRID_REDUCE_WHEN_EXPOSURE_ABOVE_USD) : undefined;
+  const reduceChunkUsd = env.GRID_REDUCE_CHUNK_USD ? parseFloat(env.GRID_REDUCE_CHUNK_USD) : undefined;
+  const minReduceIntervalMs = env.GRID_MIN_REDUCE_INTERVAL_MS ? parseInt(env.GRID_MIN_REDUCE_INTERVAL_MS) : undefined;
+  const useMaxLeverage = env.GRID_USE_MAX_LEVERAGE ? env.GRID_USE_MAX_LEVERAGE === '1' : true;
+  const continuousProtect = env.GRID_CONTINUOUS_PROTECT ? env.GRID_CONTINUOUS_PROTECT === '1' : false;
+  const protectRefreshMs = env.GRID_PROTECT_REFRESH_MS ? parseInt(env.GRID_PROTECT_REFRESH_MS) : 15000;
+  const hfMode = env.GRID_HF_MODE ? env.GRID_HF_MODE === '1' : true;
+  const hfHoldMs = env.GRID_HF_HOLD_MS ? parseInt(env.GRID_HF_HOLD_MS) : 800;
+  const hfMaxConcurrentPerSide = env.GRID_HF_MAX_CONCURRENT_PER_SIDE ? parseInt(env.GRID_HF_MAX_CONCURRENT_PER_SIDE) : 2;
+  const hfSymmetric = true;
+  // 为进一步减少进场漂移,默认关闭 makerMode,成对下单强制用市价同步成交
+  const makerMode = env.GRID_MAKER_MODE ? env.GRID_MAKER_MODE === '1' : false;
+  // 为减少价差与滑点:加大挂单偏移、缩短挂单超时
+  const makerOffsetBps = env.GRID_MAKER_OFFSET_BPS ? parseInt(env.GRID_MAKER_OFFSET_BPS) : 8;
+  const makerTimeoutMs = env.GRID_MAKER_TIMEOUT_MS ? parseInt(env.GRID_MAKER_TIMEOUT_MS) : 800;
+  const rebalanceEnabled = env.GRID_REBALANCE_ENABLED ? env.GRID_REBALANCE_ENABLED === '1' : true;
+  const rebalanceThresholdQty = env.GRID_REBALANCE_THRESHOLD_QTY ? parseFloat(env.GRID_REBALANCE_THRESHOLD_QTY) : 0.005; // 0.005 BTC
+  const rebalanceChunkQty = env.GRID_REBALANCE_CHUNK_QTY ? parseFloat(env.GRID_REBALANCE_CHUNK_QTY) : 0.005;
+  const rebalanceMinIntervalMs = env.GRID_REBALANCE_MIN_INTERVAL_MS ? parseInt(env.GRID_REBALANCE_MIN_INTERVAL_MS) : 3000;
+  const deleverageAfter2019 = env.GRID_DELEVERAGE_AFTER_2019 ? env.GRID_DELEVERAGE_AFTER_2019 === '1' : true;
+  const deleverageChunkQty = env.GRID_DELEVERAGE_CHUNK_QTY ? parseFloat(env.GRID_DELEVERAGE_CHUNK_QTY) : 0.002;
+  const deleverageMinIntervalMs = env.GRID_DELEVERAGE_MIN_INTERVAL_MS ? parseInt(env.GRID_DELEVERAGE_MIN_INTERVAL_MS) : 8000;
+  const deleverageStepsOn2019 = env.GRID_DELEVERAGE_STEPS_ON_2019 ? parseInt(env.GRID_DELEVERAGE_STEPS_ON_2019) : 3;
+  const deleverageMultiplier = env.GRID_DELEVERAGE_MULTIPLIER ? parseFloat(env.GRID_DELEVERAGE_MULTIPLIER) : 2.0;
+  const deleverageMaxChunkQty = env.GRID_DELEVERAGE_MAX_CHUNK_QTY ? parseFloat(env.GRID_DELEVERAGE_MAX_CHUNK_QTY) : 0.02;
+  const takerRatio = env.GRID_TAKER_RATIO ? parseFloat(env.GRID_TAKER_RATIO) : 0.6;
+  const takerBurstMs = env.GRID_TAKER_BURST_MS ? parseInt(env.GRID_TAKER_BURST_MS) : 1000;
+  const takerMaxConcurrent = env.GRID_TAKER_MAX_CONCURRENT ? parseInt(env.GRID_TAKER_MAX_CONCURRENT) : 1;
+  const costCapBps = env.GRID_COST_CAP_BPS ? parseInt(env.GRID_COST_CAP_BPS) : 30;
+  const minSpreadBps = env.GRID_MIN_SPREAD_BPS ? parseInt(env.GRID_MIN_SPREAD_BPS) : 35;
+  const takerCooldownMs = env.GRID_TAKER_COOLDOWN_MS ? parseInt(env.GRID_TAKER_COOLDOWN_MS) : 10000;
+  const maxGrossQtyPerSide = env.GRID_MAX_GROSS_QTY_PER_SIDE ? parseFloat(env.GRID_MAX_GROSS_QTY_PER_SIDE) : 0.02; // 0.02 BTC
+  const deRiskEnabled = env.GRID_DERISK_ENABLED ? env.GRID_DERISK_ENABLED === '1' : true;
+  const deRiskAvailRatio = env.GRID_DERISK_AVAIL_RATIO ? parseFloat(env.GRID_DERISK_AVAIL_RATIO) : 0.30; // 可用/钱包 < 30%
+  const deRiskChunkQty = env.GRID_DERISK_CHUNK_QTY ? parseFloat(env.GRID_DERISK_CHUNK_QTY) : 0.008;   // 每次去杠杆更多
+  const deRiskIntervalMs = env.GRID_DERISK_INTERVAL_MS ? parseInt(env.GRID_DERISK_INTERVAL_MS) : 4000; // 更频繁检查
+  const maxNetQty = env.GRID_MAX_NET_QTY ? parseFloat(env.GRID_MAX_NET_QTY) : 0.002;                    // 更紧净敞口
+  const entryAlignEnabled = env.GRID_ENTRY_ALIGN_ENABLED ? env.GRID_ENTRY_ALIGN_ENABLED === '1' : true; // 默认开启
+  const entryAlignDeltaBps = env.GRID_ENTRY_ALIGN_BPS ? parseInt(env.GRID_ENTRY_ALIGN_BPS) : 15;        // 均价差>15bps回归
+  const entryAlignChunkRatio = env.GRID_ENTRY_ALIGN_CHUNK_RATIO ? parseFloat(env.GRID_ENTRY_ALIGN_CHUNK_RATIO) : 0.15; // 每次回滚15%
+  const entryAlignMinIntervalMs = env.GRID_ENTRY_ALIGN_MIN_INTERVAL_MS ? parseInt(env.GRID_ENTRY_ALIGN_MIN_INTERVAL_MS) : 8000;
+  // 默认 AUTO:小差用 ADD(更少实亏),大差且持续时用 REPLACE(更快回归)
+  const entryAlignMode = ((env.GRID_ENTRY_ALIGN_MODE as any) === 'ADD' || (env.GRID_ENTRY_ALIGN_MODE as any) === 'REPLACE' || (env.GRID_ENTRY_ALIGN_MODE as any) === 'AUTO') ? (env.GRID_ENTRY_ALIGN_MODE as any) : 'AUTO';
+  const entryAlignAddBps = env.GRID_ENTRY_ALIGN_ADD_BPS ? parseInt(env.GRID_ENTRY_ALIGN_ADD_BPS) : 12;
+  const entryAlignReplaceBps = env.GRID_ENTRY_ALIGN_REPLACE_BPS ? parseInt(env.GRID_ENTRY_ALIGN_REPLACE_BPS) : 30;
+  const entryAlignSustainTicks = env.GRID_ENTRY_ALIGN_SUSTAIN_TICKS ? parseInt(env.GRID_ENTRY_ALIGN_SUSTAIN_TICKS) : 2;
+  // 薄盘口优化默认:开启点差感知,限制大点差时的吃单;maker 贴最优
+  const spreadAware = env.GRID_SPREAD_AWARE ? env.GRID_SPREAD_AWARE === '1' : false;
+  const takerMaxSpreadBps = env.GRID_TAKER_MAX_SPREAD_BPS ? parseInt(env.GRID_TAKER_MAX_SPREAD_BPS) : 120;
+  const makerJoinBest = env.GRID_MAKER_JOIN_BEST ? env.GRID_MAKER_JOIN_BEST === '1' : false;
+  const makerInsideTicks = env.GRID_MAKER_INSIDE_TICKS ? parseInt(env.GRID_MAKER_INSIDE_TICKS) : 0;
+  const auditEnabled = env.GRID_AUDIT_ENABLED ? env.GRID_AUDIT_ENABLED === '1' : true;
+  const auditLogFile = env.GRID_AUDIT_LOG_FILE || undefined;
+
+  return {
+    symbol,
+    leverage,
+    baseNotionalUsd,
+    gridStepBps,
+    gridCount,
+    tickIntervalMs,
+    maxOpenPerSide,
+    tpBps,
+    slBps,
+    workingType,
+    stateFile,
+    persistIntervalMs,
+    minNotionalUsd,
+    forceOpenOnStart,
+    minOpenIntervalMs,
+    ordersPerTickPerSide,
+    aggressive,
+    parallelPlacement,
+    maxExposureUsdPerSide,
+    reduceWhenExposureAboveUsd,
+    reduceChunkUsd,
+    minReduceIntervalMs,
+    useMaxLeverage,
+    continuousProtect,
+    protectRefreshMs,
+    hfMode,
+    hfHoldMs,
+    hfMaxConcurrentPerSide,
+    hfSymmetric,
+    auditEnabled,
+    auditLogFile,
+    makerMode,
+    makerOffsetBps,
+    makerTimeoutMs,
+    rebalanceEnabled,
+    rebalanceThresholdQty,
+    rebalanceChunkQty,
+    rebalanceMinIntervalMs,
+    deleverageAfter2019,
+    deleverageChunkQty,
+    deleverageMinIntervalMs,
+    deleverageStepsOn2019,
+    deleverageMultiplier,
+    deleverageMaxChunkQty,
+    takerRatio,
+    takerBurstMs,
+    takerMaxConcurrent,
+    costCapBps,
+    minSpreadBps,
+    takerCooldownMs
+    ,maxGrossQtyPerSide
+    ,deRiskEnabled
+    ,deRiskAvailRatio
+    ,deRiskChunkQty
+    ,deRiskIntervalMs
+    ,maxNetQty
+    ,entryAlignEnabled
+    ,entryAlignDeltaBps
+    ,entryAlignChunkRatio
+    ,entryAlignMinIntervalMs
+    ,entryAlignMode
+    ,entryAlignAddBps
+    ,entryAlignReplaceBps
+    ,entryAlignSustainTicks
+    ,spreadAware
+    ,takerMaxSpreadBps
+    ,makerJoinBest
+    ,makerInsideTicks
+  };
+}
+
+

+ 1641 - 0
src/dex/aster/gridDualExecutor.ts

@@ -0,0 +1,1641 @@
+import { EventEmitter } from 'events';
+import axios from 'axios';
+import { promises as fs } from 'fs';
+import * as path from 'path';
+import { AsterTradingClient } from './tradingClient';
+import { getSymbolMeta } from './marketMeta';
+
+export interface DualGridConfig {
+  symbol: string;                 // 交易对,如 BTCUSDT / BUSDT
+  leverage: number;               // 目标杠杆(将自动按可用最大值降档)
+  baseNotionalUsd: number;        // 每格名义(USD)
+  gridStepBps: number;            // 网格步长(基点),如 30 = 0.30%
+  gridCount: number;              // 每侧网格数量(上、下各 gridCount)
+  tickIntervalMs: number;         // 执行周期(毫秒)
+  maxOpenPerSide: number;         // 每侧最大同时持仓笔数(控制保证金占用)
+  tpBps?: number;                 // 止盈触发(基点),如 30 = 0.30%
+  slBps?: number;                 // 止损触发(基点)
+  workingType?: 'MARK_PRICE' | 'CONTRACT_PRICE';
+  minNotionalUsd?: number;        // 交易所最小名义(默认 5)
+  stateFile?: string;             // 本地状态文件路径(JSON)
+  persistIntervalMs?: number;     // 状态持久化间隔
+  forceOpenOnStart?: boolean;     // 启动时各侧先开一单
+  minOpenIntervalMs?: number;     // 若未触发价穿越,最小时间间隔后也开一单
+  ordersPerTickPerSide?: number;  // 每tick每侧最多开几笔(受 maxOpenPerSide 限制)
+  aggressive?: boolean;           // 激进模式:在容量允许下尽量下单
+  parallelPlacement?: boolean;    // 是否并发下单
+  maxExposureUsdPerSide?: number; // 每侧最大名义USD,超过则暂停开仓
+  reduceWhenExposureAboveUsd?: number; // 超过此敞口则触发自动减仓
+  reduceChunkUsd?: number;       // 每次减仓名义USD
+  minReduceIntervalMs?: number;  // 同侧最小减仓间隔
+  useMaxLeverage?: boolean;       // 启动时自动设置最大可用杠杆
+  continuousProtect?: boolean;    // 持续维护 TP/SL 保护单
+  protectRefreshMs?: number;      // 保护单刷新间隔
+  hfMode?: boolean;               // 高频模式:开仓后按持有时长自动用市价平仓
+  hfHoldMs?: number;              // 高频模式持仓时长
+  hfMaxConcurrentPerSide?: number;// 高频模式每侧最多并发开平对数
+  auditEnabled?: boolean;         // 是否记录审计日志
+  auditLogFile?: string;          // 审计日志文件(JSONL)
+  hfSymmetric?: boolean;          // 高频模式对称:同tick两侧下单数相同
+  makerMode?: boolean;            // Maker 网格:限价GTX优先,超时兜底
+  makerOffsetBps?: number;        // 入场挂价偏移bps(相对中价)
+  makerTimeoutMs?: number;        // 入场挂单超时时间
+  rebalanceEnabled?: boolean;     // 启用净敞口再平衡
+  rebalanceThresholdQty?: number; // 触发再平衡的净持仓阈值(基础币数量)
+  rebalanceChunkQty?: number;     // 每次再平衡数量(基础币数量)
+  rebalanceMinIntervalMs?: number;// 再平衡最小间隔
+  deleverageAfter2019?: boolean;  // -2019 后尝试减仓
+  deleverageChunkQty?: number;    // 每次减仓数量(基础币)
+  deleverageMinIntervalMs?: number; // 减仓最小间隔
+  deleverageStepsOn2019?: number; // 连击步数
+  deleverageMultiplier?: number;  // 步进系数
+  deleverageMaxChunkQty?: number; // 单次最大减仓
+  takerRatio?: number;            // Taker 触发概率(0~1)
+  takerBurstMs?: number;          // Taker 爆发窗
+  takerMaxConcurrent?: number;    // Taker 每侧最大并发
+  costCapBps?: number;            // 成本上限(费率+点差+滑点)bps
+  minSpreadBps?: number;          // 最小点差 bps
+  takerCooldownMs?: number;       // Taker 连续失败后的冷却
+  maxGrossQtyPerSide?: number;    // 每侧最大总持仓数量(基础币)
+  deRiskEnabled?: boolean;        // 利用率去杠杆器
+  deRiskAvailRatio?: number;      // 可用/钱包 比例阈值
+  deRiskChunkQty?: number;        // 去杠杆数量
+  deRiskIntervalMs?: number;      // 去杠杆最小间隔
+  maxNetQty?: number;             // 最大净敞口(基础币)
+  entryAlignEnabled?: boolean;    // 启用开仓均价动态回归
+  entryAlignDeltaBps?: number;    // 触发阈值(bps)
+  entryAlignChunkRatio?: number;  // 每次回归比例(0~1)
+  entryAlignMinIntervalMs?: number; // 最小回归间隔
+  entryAlignMode?: 'ADD' | 'REPLACE' | 'AUTO'; // 回归模式:ADD=同向加仓,REPLACE=先反向平仓再重开,AUTO=自适应
+  entryAlignAddBps?: number;        // AUTO:ADD触发阈值
+  entryAlignReplaceBps?: number;    // AUTO:REPLACE触发阈值
+  entryAlignSustainTicks?: number;  // AUTO:REPLACE 连续满足 tick 数
+  // 薄盘口优化
+  spreadAware?: boolean;            // 启用点差感知(大点差时避免吃单)
+  takerMaxSpreadBps?: number;       // 允许吃单的最大点差(bps),超出则强制 maker
+  makerJoinBest?: boolean;          // maker 价格跟随盘口最优(bestBid/bestAsk)
+  makerInsideTicks?: number;        // maker 挂在最优价内第 N 个 tick(>=0)
+}
+
+export interface DualGridStats {
+  lastMidPrice?: number;
+  placedLong: number;
+  placedShort: number;
+  errors: number;
+}
+
+/**
+ * 双账户网格执行器:AccountA 跑 LONG 网格;AccountB 跑 SHORT 网格(Taker风格)。
+ * MVP:按时间片执行,围绕中价生成网格,按每格名义发起市价单,并带 positionSide。
+ */
+export class DualAccountGridExecutor extends EventEmitter {
+  private readonly accountA: AsterTradingClient; // LONG grid
+  private readonly accountB: AsterTradingClient; // SHORT grid
+  private readonly cfg: DualGridConfig;
+  private timer: NodeJS.Timeout | null = null;
+  private running = false;
+  private stats: DualGridStats = { placedLong: 0, placedShort: 0, errors: 0 };
+  private recentLongOpens: number[] = [];
+  private recentShortOpens: number[] = [];
+  private prevMid: number | null = null;
+  private persistTimer: NodeJS.Timeout | null = null;
+  private startedAt: number = Date.now();
+  private lastReduceAtLong = 0;
+  private lastReduceAtShort = 0;
+  private lastProtectRefresh = 0;
+  private hfConcurrentLong = 0;
+  private hfConcurrentShort = 0;
+  private lastRebalanceAt = 0;
+  private lastMarginErrA = 0;
+  private lastMarginErrB = 0;
+  private lastDeleverageAtA = 0;
+  private lastDeleverageAtB = 0;
+  private lastDeRiskAt = 0;
+  private lastEntryAlignAt = 0;
+  private alignSustainCounter = 0;
+
+  constructor(accountA: AsterTradingClient, accountB: AsterTradingClient, cfg: DualGridConfig) {
+    super();
+    this.accountA = accountA;
+    this.accountB = accountB;
+    this.cfg = cfg;
+  }
+
+  getStats(): DualGridStats {
+    return { ...this.stats };
+  }
+
+  async start(): Promise<void> {
+    if (this.running) return;
+    this.running = true;
+    await this.tryLoadState();
+    // 启动前归一化:取消两侧待成交限价单,尽量把已有净敞口打回阈值内
+    try {
+      const sym = this.cfg.symbol.toUpperCase();
+      await Promise.all([
+        this.cancelOpenLimitOrders(this.accountA, sym, 'LONG').catch(() => {}),
+        this.cancelOpenLimitOrders(this.accountA, sym, 'SHORT').catch(() => {}),
+        this.cancelOpenLimitOrders(this.accountB, sym, 'LONG').catch(() => {}),
+        this.cancelOpenLimitOrders(this.accountB, sym, 'SHORT').catch(() => {})
+      ]);
+      await this.forceNetZero().catch(() => {});
+    } catch {}
+    await Promise.all([
+      this.ensureLeverage(this.accountA, this.cfg.symbol, this.cfg.leverage, this.cfg.useMaxLeverage),
+      this.ensureLeverage(this.accountB, this.cfg.symbol, this.cfg.leverage, this.cfg.useMaxLeverage)
+    ]).catch(() => {/* 忽略,使用账户默认杠杆 */});
+    this.startPersist();
+    this.loop();
+  }
+
+  stop(): void {
+    this.running = false;
+    if (this.timer) {
+      clearTimeout(this.timer);
+      this.timer = null;
+    }
+    this.stopPersist();
+    // 尝试立即保存一次
+    this.trySaveState().catch(() => {/* ignore */});
+  }
+
+  private async loop(): Promise<void> {
+    if (!this.running) return;
+    try {
+      const mid = await this.fetchMidPrice(this.cfg.symbol);
+      this.stats.lastMidPrice = mid;
+      const now = Date.now();
+      const isHf = !!this.cfg.hfMode;
+      if (!isHf) {
+        this.sweepRecent(this.recentLongOpens, now);
+        this.sweepRecent(this.recentShortOpens, now);
+      }
+
+      const levels = this.buildGridLevels(mid, this.cfg.gridStepBps, this.cfg.gridCount);
+
+      // 持仓名义上限控制(HF模式跳过)
+      let allowLong = true;
+      let allowShort = true;
+      if (!isHf && this.cfg.maxExposureUsdPerSide && this.cfg.maxExposureUsdPerSide > 0) {
+        const [longExp, shortExp] = await Promise.all([
+          this.getExposureUsd(this.accountA, 'LONG'),
+          this.getExposureUsd(this.accountB, 'SHORT')
+        ]);
+        allowLong = longExp < this.cfg.maxExposureUsdPerSide;
+        allowShort = shortExp < this.cfg.maxExposureUsdPerSide;
+      }
+
+      const stepPct = this.cfg.gridStepBps / 10000;
+      const prev = this.prevMid;
+      let triggerLong = false;
+      let triggerShort = false;
+      if (isHf) {
+        // 高频:始终允许触发,由下方并发与 per-tick 限制控制节奏
+        triggerLong = true;
+        triggerShort = true;
+      } else {
+        if (prev != null && prev > 0) {
+          if (mid >= prev * (1 + stepPct)) triggerShort = true;
+          if (mid <= prev * (1 - stepPct)) triggerLong = true;
+        }
+      }
+
+      // 时间驱动兜底(HF模式跳过)
+      if (!isHf) {
+        const minInterval = this.cfg.minOpenIntervalMs ?? 20_000;
+        const lastLongAt = this.recentLongOpens[this.recentLongOpens.length - 1] || 0;
+        const lastShortAt = this.recentShortOpens[this.recentShortOpens.length - 1] || 0;
+        if (!triggerLong && now - lastLongAt >= minInterval) triggerLong = true;
+        if (!triggerShort && now - lastShortAt >= minInterval) triggerShort = true;
+      }
+
+      if (this.cfg.forceOpenOnStart && this.prevMid == null) {
+        triggerLong = true;
+        triggerShort = true;
+      }
+
+      // 自动减仓(HF模式跳过,以减少 REST 开销)
+      if (!isHf) await this.maybeAutoReduce(mid);
+
+      // 净敞口再平衡:A 的 LONG 与 B 的 SHORT 尽量对称
+      if (this.cfg.rebalanceEnabled) {
+        await this.maybeRebalance();
+      }
+
+      // 利用率去杠杆器:可用/钱包过低时主动小步减仓,给策略留生存空间
+      if (this.cfg.deRiskEnabled) {
+        await this.maybeDeRisk();
+      }
+
+      // 在开单前先做“均价回归”,避免新单把价差再次拉大
+      let alignedThisTick = false;
+      try {
+        const before = this.lastEntryAlignAt;
+        await this.maybeAlignEntry(mid);
+        alignedThisTick = this.lastEntryAlignAt !== before;
+      } catch (e) {
+        this.emit('error', e as any);
+      }
+
+      // 定期刷新保护单(TP/SL)
+      if (!isHf && this.cfg.continuousProtect) {
+        const prMs = this.cfg.protectRefreshMs ?? 15000;
+        const nowTs = Date.now();
+        if (nowTs - this.lastProtectRefresh >= prMs) {
+          await Promise.all([
+            this.refreshProtectiveOrders(this.accountA, 'LONG', mid),
+            this.refreshProtectiveOrders(this.accountB, 'SHORT', mid)
+          ]).catch((e) => this.emit('error', e));
+          this.lastProtectRefresh = nowTs;
+        }
+      }
+
+      // 激进模式:在容量允许下尽量下单
+      if (this.cfg.aggressive) {
+        if (!isHf) {
+          triggerLong = triggerLong || (this.recentLongOpens.length < this.cfg.maxOpenPerSide);
+          triggerShort = triggerShort || (this.recentShortOpens.length < this.cfg.maxOpenPerSide);
+        }
+      }
+
+      // Taker 触发器:在 burst 窗内 + 概率 + 点差过滤
+      let forceTakerLong = false;
+      let forceTakerShort = false;
+      try {
+        const takerRatio = this.cfg.takerRatio ?? 0.6;
+        const takerBurstMs = this.cfg.takerBurstMs ?? 1000;
+        const minSpreadBps = this.cfg.minSpreadBps ?? 20;
+        const burst = (now % (this.cfg.tickIntervalMs)) <= takerBurstMs;
+        if (burst) {
+          const { spreadBps, bestBid, bestAsk } = await this.fetchTopSpread(this.cfg.symbol);
+          // spread-aware:点差过小不做(maker 更难成交、taker 成本不值)
+          if (spreadBps >= minSpreadBps) {
+            // 若启用上限:点差太大时禁止 taker,避免吃巨额点差
+            const takerMax = this.cfg.takerMaxSpreadBps ?? 120; // 1.2%
+            const allowTaker = !this.cfg.spreadAware || spreadBps <= takerMax;
+            if (allowTaker) {
+              if (Math.random() < takerRatio) forceTakerLong = true;
+              if (Math.random() < takerRatio) forceTakerShort = true;
+            }
+            // maker join best:薄盘口时,maker 价格跟随 bestBid/bestAsk 的第N档tick
+            if (this.cfg.makerMode && this.cfg.makerJoinBest) {
+              (this as any)._lastBestBid = bestBid;
+              (this as any)._lastBestAsk = bestAsk;
+            }
+          }
+        }
+      } catch {}
+
+      const perTick = Math.max(1, this.cfg.ordersPerTickPerSide ?? 1);
+      const tasks: Array<Promise<void>> = [];
+
+      // 若近期某账户出现保证金不足,进入短冷却
+      const marginCooldownMs = 8000;
+      if (Date.now() - this.lastMarginErrA < marginCooldownMs) allowLong = false;
+      if (Date.now() - this.lastMarginErrB < marginCooldownMs) allowShort = false;
+
+      // 在 Taker 爆发前,取消本侧所有未成交 GTC 限价单,释放保证金
+      if (forceTakerLong) {
+        await this.cancelOpenLimitOrders(this.accountA, this.cfg.symbol.toUpperCase(), 'LONG').catch(() => {});
+      }
+      if (forceTakerShort) {
+        await this.cancelOpenLimitOrders(this.accountB, this.cfg.symbol.toUpperCase(), 'SHORT').catch(() => {});
+      }
+
+      // 净敞口硬约束:超过阈值则只允许向回收缩的方向触发
+      if (this.cfg.maxNetQty && this.cfg.maxNetQty > 0) {
+        try {
+          const [aRisk, bRisk] = await Promise.all([
+            this.accountA.getPositionRisk({ symbol: this.cfg.symbol.toUpperCase() } as any).catch(() => []),
+            this.accountB.getPositionRisk({ symbol: this.cfg.symbol.toUpperCase() } as any).catch(() => [])
+          ]);
+          const getAmt = (list: any[], side: 'LONG' | 'SHORT') => {
+            const p = Array.isArray(list) ? list.find((x: any) => x.symbol === this.cfg.symbol.toUpperCase() && x.positionSide === side) : null;
+            return p ? parseFloat(p.positionAmt || '0') : 0;
+          };
+          const net = getAmt(aRisk as any[], 'LONG') + getAmt(bRisk as any[], 'SHORT');
+          if (net > this.cfg.maxNetQty) allowLong = false; // 禁止继续加多
+          if (net < -this.cfg.maxNetQty) allowShort = false; // 禁止继续加空
+        } catch {}
+      }
+
+      if (!alignedThisTick && isHf && this.cfg.hfSymmetric) {
+        const hfCapL = Math.max(0, (this.cfg.hfMaxConcurrentPerSide ?? 2) - this.hfConcurrentLong);
+        const hfCapS = Math.max(0, (this.cfg.hfMaxConcurrentPerSide ?? 2) - this.hfConcurrentShort);
+        const canL = allowLong && triggerLong ? hfCapL : 0;
+        const canS = allowShort && triggerShort ? hfCapS : 0;
+        const toPlaceBoth = Math.min(perTick, canL, canS);
+        for (let i = 0; i < toPlaceBoth; i++) {
+          // 统一两侧数量:取两账户可承受的“最小可下”数量,强制一致,减少PnL漂移
+          const fixedQty = await this.estimateSymmetricQty(mid).catch(() => null);
+          // 对称开仓强制使用市价,尽量同步成交,减小进场价差
+          const useMarket = true;
+          const pair = await Promise.allSettled([
+            this.tryPlaceOne(this.accountA, 'LONG', mid, useMarket, fixedQty || undefined),
+            this.tryPlaceOne(this.accountB, 'SHORT', mid, useMarket, fixedQty || undefined)
+          ]);
+          const okL = pair[0].status === 'fulfilled';
+          const okS = pair[1].status === 'fulfilled';
+          if (okL) this.stats.placedLong += 1; else this.stats.errors += 1;
+          if (okS) this.stats.placedShort += 1; else this.stats.errors += 1;
+          // 原子配对:若仅一侧成功,立即用对冲单将其平回,避免暴露净仓
+          if (okL && !okS) {
+            await this.safeClose(this.accountA, this.cfg.symbol.toUpperCase(), 'LONG').catch((e) => this.emit('error', e));
+          } else if (!okL && okS) {
+            await this.safeClose(this.accountB, this.cfg.symbol.toUpperCase(), 'SHORT').catch((e) => this.emit('error', e));
+          }
+        }
+      } else if (!alignedThisTick) {
+        // 非HF路径也做“成对对称”下单,尽量同时开/对冲
+        const canL = allowLong && triggerLong ? Math.max(0, this.cfg.maxOpenPerSide - this.recentLongOpens.length) : 0;
+        const canS = allowShort && triggerShort ? Math.max(0, this.cfg.maxOpenPerSide - this.recentShortOpens.length) : 0;
+        const toPlaceBoth = Math.min(perTick, canL, canS);
+        for (let i = 0; i < toPlaceBoth; i++) {
+          const fixedQty = await this.estimateSymmetricQty(mid).catch(() => null);
+          // 对称开仓强制使用市价
+          const useMarket = true;
+          const pair = await Promise.allSettled([
+            this.tryPlaceOne(this.accountA, 'LONG', mid, useMarket, fixedQty || undefined),
+            this.tryPlaceOne(this.accountB, 'SHORT', mid, useMarket, fixedQty || undefined)
+          ]);
+          const okL = pair[0].status === 'fulfilled';
+          const okS = pair[1].status === 'fulfilled';
+          if (okL) { this.stats.placedLong += 1; this.recentLongOpens.push(now); } else { this.stats.errors += 1; }
+          if (okS) { this.stats.placedShort += 1; this.recentShortOpens.push(now); } else { this.stats.errors += 1; }
+          if (okL && !okS) {
+            await this.safeClose(this.accountA, this.cfg.symbol.toUpperCase(), 'LONG').catch((e) => this.emit('error', e));
+          } else if (!okL && okS) {
+            await this.safeClose(this.accountB, this.cfg.symbol.toUpperCase(), 'SHORT').catch((e) => this.emit('error', e));
+          }
+        }
+        // 若一侧允许、另一侧不允许,则按单侧配额去下剩余
+        const remainL = allowLong && triggerLong ? Math.max(0, this.cfg.maxOpenPerSide - this.recentLongOpens.length) : 0;
+        const remainS = allowShort && triggerShort ? Math.max(0, this.cfg.maxOpenPerSide - this.recentShortOpens.length) : 0;
+        const toL = Math.min(perTick, remainL);
+        const toS = Math.min(perTick, remainS);
+        for (let i = 0; i < toL; i++) {
+          const p = this.tryPlaceOne(this.accountA, 'LONG', mid, forceTakerLong)
+            .then(() => { this.stats.placedLong += 1; this.recentLongOpens.push(now); })
+            .catch((e) => { this.stats.errors += 1; this.emit('error', e); });
+          if (this.cfg.parallelPlacement !== false) tasks.push(p); else await p;
+        }
+        for (let i = 0; i < toS; i++) {
+          const p = this.tryPlaceOne(this.accountB, 'SHORT', mid, forceTakerShort)
+            .then(() => { this.stats.placedShort += 1; this.recentShortOpens.push(now); })
+            .catch((e) => { this.stats.errors += 1; this.emit('error', e); });
+          if (this.cfg.parallelPlacement !== false) tasks.push(p); else await p;
+        }
+      }
+
+      if (tasks.length) await Promise.allSettled(tasks);
+
+      // 周期强制归零:将净敞口拉回 0(小步平仓),尽量只留费损
+      await this.forceNetZero().catch((e) => this.emit('error', e));
+
+      // 动态回归开仓均价:当成对 entry 差偏大时,小步刷新成对持仓
+      await this.maybeAlignEntry(mid).catch((e) => this.emit('error', e));
+
+      this.emit('tick', { mid, levels, stats: this.getStats() });
+      // 打印两账户的持仓快照:size / entry / uPnL
+      await this.printPositionSnapshot(mid).catch(() => {/* ignore log errors */});
+      // 持久化账户净值/组合指标快照,便于离线分析
+      await this.writeTickSnapshot(mid).catch(() => {/* ignore */});
+      this.prevMid = mid;
+    } catch (e: any) {
+      this.stats.errors += 1;
+      this.emit('error', e);
+    } finally {
+      this.timer = setTimeout(() => this.loop(), this.cfg.tickIntervalMs);
+    }
+  }
+
+  private async maybeAlignEntry(mid: number): Promise<void> {
+    if (!this.cfg.entryAlignEnabled) return;
+    const now = Date.now();
+    const minIv = this.cfg.entryAlignMinIntervalMs ?? 8000;
+    if (now - this.lastEntryAlignAt < minIv) return;
+    const symbol = this.cfg.symbol.toUpperCase();
+    const [aRisk, bRisk] = await Promise.all([
+      this.accountA.getPositionRisk({ symbol } as any).catch(() => []),
+      this.accountB.getPositionRisk({ symbol } as any).catch(() => [])
+    ]);
+    const pick = (list: any[], side: 'LONG' | 'SHORT') => {
+      const p = Array.isArray(list) ? list.find((x: any) => x.symbol === symbol && x.positionSide === side) : null;
+      return p ? { qty: Math.abs(parseFloat(p.positionAmt || '0')), entry: parseFloat(p.entryPrice || '0') } : { qty: 0, entry: 0 };
+    };
+    const aL = pick(aRisk as any[], 'LONG');
+    const bS = pick(bRisk as any[], 'SHORT');
+    const aS = pick(aRisk as any[], 'SHORT');
+    const bL = pick(bRisk as any[], 'LONG');
+    let acted = false;
+    const tryAlignPair = async (
+      qtyA: number, entryA: number, sideA: 'LONG' | 'SHORT',
+      qtyB: number, entryB: number, sideB: 'LONG' | 'SHORT'
+    ) => {
+      if (qtyA <= 0 || qtyB <= 0 || entryA <= 0 || entryB <= 0) return;
+      const de = Math.abs(entryA - entryB) / ((entryA + entryB) / 2) * 10000; // bps
+      const th = this.cfg.entryAlignDeltaBps ?? 15;
+      if (de < th) return;
+      const ratio = Math.max(0, Math.min(1, this.cfg.entryAlignChunkRatio ?? 0.15));
+      const chunk = Math.max(0, Math.min(qtyA, qtyB)) * ratio;
+      if (chunk <= 0) return;
+      const qStr = chunk.toFixed(3);
+      // 两种回归模式:
+      // ADD:同向再加仓,均价向当前价靠拢,速度较慢但稳定
+      // REPLACE:先各自反向平掉 chunk,再按原方向各自重开 chunk(原子化配对),均价回归更快
+      let modeRaw = this.cfg.entryAlignMode ?? 'AUTO';
+      if (modeRaw === 'AUTO') {
+        const addTh = this.cfg.entryAlignAddBps ?? 12;
+        const repTh = this.cfg.entryAlignReplaceBps ?? 30;
+        if (de >= repTh) {
+          this.alignSustainCounter += 1;
+        } else {
+          this.alignSustainCounter = 0;
+        }
+        const needReplace = this.alignSustainCounter >= (this.cfg.entryAlignSustainTicks ?? 2);
+        const modeDecided = needReplace ? 'REPLACE' : (de >= addTh ? 'ADD' as const : null);
+        if (!modeDecided) return; // 未达任何阈值
+        const addMode = modeDecided === 'ADD';
+        if (addMode) {
+          const sideStrA = sideA === 'LONG' ? 'BUY' : 'SELL';
+          const sideStrB = sideB === 'LONG' ? 'BUY' : 'SELL';
+          await Promise.all([
+            this.accountA.placeOrder({ symbol, side: sideStrA as any, type: 'MARKET', quantity: qStr, positionSide: sideA, newOrderRespType: 'ACK' } as any).catch(() => {}),
+            this.accountB.placeOrder({ symbol, side: sideStrB as any, type: 'MARKET', quantity: qStr, positionSide: sideB, newOrderRespType: 'ACK' } as any).catch(() => {})
+          ]);
+          acted = true;
+        } else {
+          const closeSideA = sideA === 'LONG' ? 'SELL' : 'BUY';
+          const closeSideB = sideB === 'LONG' ? 'SELL' : 'BUY';
+          await Promise.all([
+            this.accountA.placeOrder({ symbol, side: closeSideA as any, type: 'MARKET', quantity: qStr, positionSide: sideA, newOrderRespType: 'ACK' } as any).catch(() => {}),
+            this.accountB.placeOrder({ symbol, side: closeSideB as any, type: 'MARKET', quantity: qStr, positionSide: sideB, newOrderRespType: 'ACK' } as any).catch(() => {})
+          ]);
+          const openSideA = sideA === 'LONG' ? 'BUY' : 'SELL';
+          const openSideB = sideB === 'LONG' ? 'BUY' : 'SELL';
+          await Promise.all([
+            this.accountA.placeOrder({ symbol, side: openSideA as any, type: 'MARKET', quantity: qStr, positionSide: sideA, newOrderRespType: 'ACK' } as any).catch(() => {}),
+            this.accountB.placeOrder({ symbol, side: openSideB as any, type: 'MARKET', quantity: qStr, positionSide: sideB, newOrderRespType: 'ACK' } as any).catch(() => {})
+          ]);
+          acted = true;
+        }
+        return;
+      }
+      // 非 AUTO:显式模式
+      const addMode = modeRaw === 'ADD';
+      if (addMode) {
+        const sideStrA = sideA === 'LONG' ? 'BUY' : 'SELL';
+        const sideStrB = sideB === 'LONG' ? 'BUY' : 'SELL';
+        await Promise.all([
+          this.accountA.placeOrder({ symbol, side: sideStrA as any, type: 'MARKET', quantity: qStr, positionSide: sideA, newOrderRespType: 'ACK' } as any).catch(() => {}),
+          this.accountB.placeOrder({ symbol, side: sideStrB as any, type: 'MARKET', quantity: qStr, positionSide: sideB, newOrderRespType: 'ACK' } as any).catch(() => {})
+        ]);
+        acted = true;
+      } else {
+        // REPLACE:反向平 -> 原向重开(尽量原子)
+        const closeSideA = sideA === 'LONG' ? 'SELL' : 'BUY';
+        const closeSideB = sideB === 'LONG' ? 'SELL' : 'BUY';
+        // 同步反向平
+        await Promise.all([
+          this.accountA.placeOrder({ symbol, side: closeSideA as any, type: 'MARKET', quantity: qStr, positionSide: sideA, newOrderRespType: 'ACK' } as any).catch(() => {}),
+          this.accountB.placeOrder({ symbol, side: closeSideB as any, type: 'MARKET', quantity: qStr, positionSide: sideB, newOrderRespType: 'ACK' } as any).catch(() => {})
+        ]);
+        // 同步原向重开
+        const openSideA = sideA === 'LONG' ? 'BUY' : 'SELL';
+        const openSideB = sideB === 'LONG' ? 'BUY' : 'SELL';
+        await Promise.all([
+          this.accountA.placeOrder({ symbol, side: openSideA as any, type: 'MARKET', quantity: qStr, positionSide: sideA, newOrderRespType: 'ACK' } as any).catch(() => {}),
+          this.accountB.placeOrder({ symbol, side: openSideB as any, type: 'MARKET', quantity: qStr, positionSide: sideB, newOrderRespType: 'ACK' } as any).catch(() => {})
+        ]);
+        acted = true;
+      }
+    };
+    await tryAlignPair(aL.qty, aL.entry, 'LONG', bS.qty, bS.entry, 'SHORT');
+    await tryAlignPair(aS.qty, aS.entry, 'SHORT', bL.qty, bL.entry, 'LONG');
+    if (acted) this.lastEntryAlignAt = now;
+  }
+
+  private async printPositionSnapshot(mid: number): Promise<void> {
+    const symbol = this.cfg.symbol.toUpperCase();
+    const [aRisk, bRisk] = await Promise.all([
+      this.accountA.getPositionRisk({ symbol } as any).catch(() => []),
+      this.accountB.getPositionRisk({ symbol } as any).catch(() => [])
+    ]);
+    const pick = (list: any[], side: 'LONG' | 'SHORT') => {
+      const p = Array.isArray(list) ? list.find((x: any) => x.symbol === symbol && x.positionSide === side) : null;
+      const amt = p ? parseFloat(p.positionAmt || '0') : 0;
+      const entry = p ? parseFloat(p.entryPrice || '0') : 0;
+      const qty = Math.abs(amt);
+      const upnl = qty > 0 && entry > 0
+        ? (side === 'LONG' ? (mid - entry) * qty : (entry - mid) * qty)
+        : 0;
+      return { qty, entry, upnl };
+    };
+    const aL = pick(aRisk as any[], 'LONG');
+    const aS = pick(aRisk as any[], 'SHORT');
+    const bL = pick(bRisk as any[], 'LONG');
+    const bS = pick(bRisk as any[], 'SHORT');
+    const f4 = (n: number) => (Number.isFinite(n) ? n.toFixed(4) : '0');
+    const f2 = (n: number) => (Number.isFinite(n) ? n.toFixed(2) : '0');
+    // 成对对比:A_LONG vs B_SHORT;A_SHORT vs B_LONG
+    const pair1 = {
+      qtyA: aL.qty, qtyB: bS.qty, dq: aL.qty - bS.qty,
+      entryA: aL.entry, entryB: bS.entry, de: aL.entry - bS.entry,
+      upnlA: aL.upnl, upnlB: bS.upnl, net: aL.upnl + bS.upnl
+    };
+    const pair2 = {
+      qtyA: aS.qty, qtyB: bL.qty, dq: aS.qty - bL.qty,
+      entryA: aS.entry, entryB: bL.entry, de: aS.entry - bL.entry,
+      upnlA: aS.upnl, upnlB: bL.upnl, net: aS.upnl + bL.upnl
+    };
+    const totalNet = pair1.net + pair2.net;
+    console.log(`\n[持仓] ${symbol} 中价=${f2(mid)}\n` +
+      `  组1 多(A) 对 空(B):\n` +
+      `    数量    A=${f4(pair1.qtyA)} | B=${f4(pair1.qtyB)} | 差=${f4(pair1.dq)}\n` +
+      `    开仓价  A=${f2(pair1.entryA)} | B=${f2(pair1.entryB)} | 差=${f2(pair1.de)}\n` +
+      `    浮盈亏  A=$${f2(pair1.upnlA)} | B=$${f2(pair1.upnlB)} | 合计=$${f2(pair1.net)}\n` +
+      `  组2 空(A) 对 多(B):\n` +
+      `    数量    A=${f4(pair2.qtyA)} | B=${f4(pair2.qtyB)} | 差=${f4(pair2.dq)}\n` +
+      `    开仓价  A=${f2(pair2.entryA)} | B=${f2(pair2.entryB)} | 差=${f2(pair2.de)}\n` +
+      `    浮盈亏  A=$${f2(pair2.upnlA)} | B=$${f2(pair2.upnlB)} | 合计=$${f2(pair2.net)}\n` +
+      `  全部组合 浮盈亏合计=$${f2(totalNet)}\n`);
+  }
+
+  private async writeTickSnapshot(mid: number): Promise<void> {
+    if (!this.cfg.auditEnabled) return;
+    const symbol = this.cfg.symbol.toUpperCase();
+    const ts = Date.now();
+    const [aInfo, bInfo, aRisk, bRisk] = await Promise.all([
+      this.accountA.getAccountInfo({ timestamp: ts } as any).catch(() => null),
+      this.accountB.getAccountInfo({ timestamp: ts } as any).catch(() => null),
+      this.accountA.getPositionRisk({ symbol } as any).catch(() => []),
+      this.accountB.getPositionRisk({ symbol } as any).catch(() => [])
+    ]);
+    const sumInfo = (info: any) => ({
+      wallet: parseFloat(info?.totalWalletBalance ?? '0'),
+      available: parseFloat(info?.availableBalance ?? '0')
+    });
+    const aSum = aInfo ? sumInfo(aInfo) : { wallet: 0, available: 0 };
+    const bSum = bInfo ? sumInfo(bInfo) : { wallet: 0, available: 0 };
+    const pick = (list: any[], side: 'LONG' | 'SHORT') => {
+      const p = Array.isArray(list) ? list.find((x: any) => x.symbol === symbol && x.positionSide === side) : null;
+      return p ? { qty: Math.abs(parseFloat(p.positionAmt || '0')), entry: parseFloat(p.entryPrice || '0'), notional: Math.abs(parseFloat(p.notional || '0')) } : { qty: 0, entry: 0, notional: 0 };
+    };
+    const aL = pick(aRisk as any[], 'LONG');
+    const aS = pick(aRisk as any[], 'SHORT');
+    const bL = pick(bRisk as any[], 'LONG');
+    const bS = pick(bRisk as any[], 'SHORT');
+    const netAmt = (aL.qty - aS.qty) + (bL.qty - bS.qty);
+    const netUsd = (aL.notional - aS.notional) + (bL.notional - bS.notional);
+    const record = {
+      time: ts,
+      symbol,
+      mid,
+      accountA: { wallet: aSum.wallet, available: aSum.available, long: aL, short: aS },
+      accountB: { wallet: bSum.wallet, available: bSum.available, long: bL, short: bS },
+      totals: {
+        walletSum: aSum.wallet + bSum.wallet,
+        availableSum: aSum.available + bSum.available,
+        netAmt,
+        netUsd
+      },
+      stats: this.getStats()
+    };
+    const file = path.resolve(process.cwd(), 'logs/dual_grid_tick.jsonl');
+    await fs.mkdir(path.dirname(file), { recursive: true }).catch(() => {});
+    await fs.appendFile(file, JSON.stringify(record) + '\n');
+  }
+
+  private async maybeRebalance(): Promise<void> {
+    const now = Date.now();
+    const minIv = this.cfg.rebalanceMinIntervalMs ?? 5000;
+    if (now - this.lastRebalanceAt < minIv) return;
+    const symbol = this.cfg.symbol.toUpperCase();
+    const [aRisk, bRisk] = await Promise.all([
+      this.accountA.getPositionRisk({ symbol } as any).catch(() => []),
+      this.accountB.getPositionRisk({ symbol } as any).catch(() => [])
+    ]);
+    const getAmt = (list: any[], side: 'LONG' | 'SHORT') => {
+      const p = Array.isArray(list) ? list.find((x: any) => x.symbol === symbol && x.positionSide === side) : null;
+      return p ? parseFloat(p.positionAmt || '0') : 0;
+    };
+    const aL = getAmt(aRisk as any[], 'LONG');
+    const bS = getAmt(bRisk as any[], 'SHORT');
+    const net = aL + bS; // 期望接近 0
+    const th = this.cfg.rebalanceThresholdQty ?? 0.005;
+    if (Math.abs(net) < th) return;
+    const chunk = this.cfg.rebalanceChunkQty ?? th;
+    try {
+      if (net > 0) {
+        // A多超出,B补一个 SHORT 市价
+        const qty = net >= chunk ? chunk : Math.abs(net);
+        const qStr = await this.formatQtyForSymbol(this.accountB, qty);
+        await this.accountB.placeOrder({ symbol, side: 'SELL' as any, type: 'MARKET', quantity: qStr, positionSide: 'SHORT', newOrderRespType: 'ACK' } as any);
+      } else {
+        // B空超出,A补一个 LONG 市价
+        const qty = Math.abs(net) >= chunk ? chunk : Math.abs(net);
+        const qStr = await this.formatQtyForSymbol(this.accountA, qty);
+        await this.accountA.placeOrder({ symbol, side: 'BUY' as any, type: 'MARKET', quantity: qStr, positionSide: 'LONG', newOrderRespType: 'ACK' } as any);
+      }
+      this.lastRebalanceAt = now;
+    } catch (e) {
+      this.emit('error', e as any);
+    }
+  }
+
+  private async maybeDeRisk(): Promise<void> {
+    const now = Date.now();
+    if (now - this.lastDeRiskAt < (this.cfg.deRiskIntervalMs ?? 10000)) return;
+    try {
+      const ts = Date.now();
+      const [aInfo, bInfo] = await Promise.all([
+        this.accountA.getAccountInfo({ timestamp: ts } as any).catch(() => null),
+        this.accountB.getAccountInfo({ timestamp: ts } as any).catch(() => null)
+      ]);
+      const ratio = (info: any) => {
+        const w = Math.max(0, parseFloat(info?.totalWalletBalance ?? '0'));
+        const av = Math.max(0, parseFloat(info?.availableBalance ?? '0'));
+        return w > 0 ? av / w : 1;
+      };
+      const th = this.cfg.deRiskAvailRatio ?? 0.30; // 提高触发阈值,单边更早去杠杆
+      const chunk = this.cfg.deRiskChunkQty ?? 0.008; // 增大单次减仓
+      const sym = this.cfg.symbol.toUpperCase();
+      // 如果任一账户利用率过低,双边各减一次(HF/非HF统一)
+      if ((aInfo && ratio(aInfo) < th) || (bInfo && ratio(bInfo) < th)) {
+        const [qa, qb] = await Promise.all([
+          this.formatQtyForSymbol(this.accountA, chunk),
+          this.formatQtyForSymbol(this.accountB, chunk)
+        ]);
+        await Promise.all([
+          this.accountA.placeOrder({ symbol: sym, side: 'SELL' as any, type: 'MARKET', quantity: qa, positionSide: 'LONG', newOrderRespType: 'ACK' } as any).catch(() => {}),
+          this.accountB.placeOrder({ symbol: sym, side: 'BUY'  as any, type: 'MARKET', quantity: qb, positionSide: 'SHORT', newOrderRespType: 'ACK' } as any).catch(() => {})
+        ]);
+        this.lastDeRiskAt = now;
+      }
+    } catch {}
+  }
+
+  private async maybeAutoReduce(midPrice: number): Promise<void> {
+    const threshold = this.cfg.reduceWhenExposureAboveUsd || 0;
+    const chunk = this.cfg.reduceChunkUsd || 0;
+    if (threshold <= 0 || chunk <= 0) return;
+
+    const now = Date.now();
+    const minIv = this.cfg.minReduceIntervalMs ?? 10_000;
+
+    // LONG 侧
+    const longExp = await this.getExposureUsd(this.accountA, 'LONG');
+    if (longExp > threshold && now - this.lastReduceAtLong >= minIv) {
+      const qty = await this.usdToQtyRounded(this.accountA, chunk, midPrice);
+      if (parseFloat(qty) > 0) {
+        try { await this.accountA.placeOrder({ symbol: this.cfg.symbol.toUpperCase(), side: 'SELL' as any, type: 'MARKET', quantity: qty, positionSide: 'LONG', newOrderRespType: 'ACK' }); } catch (e) { this.emit('error', e); }
+      }
+      this.lastReduceAtLong = now;
+    }
+
+    // SHORT 侧
+    const shortExp = await this.getExposureUsd(this.accountB, 'SHORT');
+    if (shortExp > threshold && now - this.lastReduceAtShort >= minIv) {
+      const qty = await this.usdToQtyRounded(this.accountB, chunk, midPrice);
+      if (parseFloat(qty) > 0) {
+        try { await this.accountB.placeOrder({ symbol: this.cfg.symbol.toUpperCase(), side: 'BUY' as any, type: 'MARKET', quantity: qty, positionSide: 'SHORT', newOrderRespType: 'ACK' }); } catch (e) { this.emit('error', e); }
+      }
+      this.lastReduceAtShort = now;
+    }
+  }
+
+  private async usdToQtyRounded(client: AsterTradingClient, usd: number, priceRef: number): Promise<string> {
+    const { baseUrl } = client.getConfig();
+    const symbol = this.cfg.symbol.toUpperCase();
+    const meta = await getSymbolMeta(baseUrl, symbol);
+    const price = priceRef || (await this.fetchMidPrice(symbol));
+    let qtyNum = usd / price;
+    if (symbol === 'BUSDT') {
+      qtyNum = Math.max(Math.floor(qtyNum), Math.ceil(meta.minQty));
+      return qtyNum.toFixed(0);
+    }
+    // 对齐步进并按精度截断
+    qtyNum = Math.floor(qtyNum / meta.stepSize) * meta.stepSize;
+    if (qtyNum < meta.minQty) qtyNum = meta.minQty;
+    const qpFactor = Math.pow(10, meta.quantityPrecision);
+    qtyNum = Math.floor(qtyNum * qpFactor) / qpFactor;
+    const decimals = Math.min(meta.quantityPrecision, (meta.stepSize.toString().split('.')[1] || '').length);
+    return qtyNum.toFixed(decimals);
+  }
+
+  // 将数量按交易对的 stepSize/quantityPrecision 对齐,避免 -1111 精度错误
+  private async formatQtyForSymbol(client: AsterTradingClient, qty: number): Promise<string> {
+    const { baseUrl } = client.getConfig();
+    const symbol = this.cfg.symbol.toUpperCase();
+    const meta = await getSymbolMeta(baseUrl, symbol);
+    if (symbol === 'BUSDT') {
+      let q = Math.max(Math.floor(qty), Math.ceil(meta.minQty));
+      return q.toFixed(0);
+    }
+    const stepDp = (meta.stepSize.toString().split('.')[1] || '').length;
+    const qp = Math.min(meta.quantityPrecision, stepDp);
+    let q = Math.max(qty, meta.minQty);
+    // 对齐步进并按精度向下截断
+    q = Math.floor(q / meta.stepSize) * meta.stepSize;
+    const factor = Math.pow(10, qp);
+    q = Math.max(meta.minQty, Math.floor(q * factor) / factor);
+    return q.toFixed(qp);
+  }
+
+  private async getExposureUsd(client: AsterTradingClient, side: 'LONG' | 'SHORT'): Promise<number> {
+    try {
+      const list = await client.getPositionRisk({ symbol: this.cfg.symbol.toUpperCase(), timestamp: Date.now() } as any);
+      const pos = Array.isArray(list) ? list.find((p: any) => p.symbol === this.cfg.symbol.toUpperCase() && p.positionSide === side) : null;
+      if (!pos) return 0;
+      const notional = parseFloat(pos.notional || '0');
+      return Math.abs(notional);
+    } catch {
+      return 0;
+    }
+  }
+
+  private sweepRecent(arr: number[], now: number): void {
+    // 以 60 秒窗口清理计数(避免无限增长);窗口大小可调。
+    const windowMs = 60_000;
+    while (arr.length && now - arr[0] > windowMs) arr.shift();
+  }
+
+  private buildGridLevels(mid: number, stepBps: number, count: number): number[] {
+    const out: number[] = [mid];
+    for (let i = 1; i <= count; i++) {
+      const up = mid * (1 + (stepBps * i) / 10000);
+      const dn = mid * (1 - (stepBps * i) / 10000);
+      out.push(up, dn);
+    }
+    return out.sort((a, b) => a - b);
+  }
+
+  private async tryPlaceOne(
+    client: AsterTradingClient,
+    posSide: 'LONG' | 'SHORT',
+    refPrice?: number,
+    forceTaker?: boolean,
+    fixedQtyStr?: string
+  ): Promise<void> {
+    const { baseUrl } = client.getConfig();
+    const symbol = this.cfg.symbol.toUpperCase();
+    const meta = await getSymbolMeta(baseUrl, symbol);
+    const price = refPrice ?? (await this.fetchMidPrice(symbol));
+    // 计算数量:若提供 fixedQtyStr(对称下单),直接使用;否则按账户可用资金动态估算
+    let quantity: string;
+    if (fixedQtyStr) {
+      quantity = fixedQtyStr;
+    } else {
+      const qtyRaw = await this.estimateQty(client, price, meta);
+      let qtyNum: number;
+      if (symbol === 'BUSDT') {
+        qtyNum = Math.floor(qtyRaw);
+        if (qtyNum < meta.minQty) qtyNum = Math.ceil(meta.minQty);
+      } else {
+        qtyNum = Math.floor(qtyRaw / meta.stepSize) * meta.stepSize;
+        if (qtyNum < meta.minQty) qtyNum = meta.minQty;
+        const qpFactor = Math.pow(10, meta.quantityPrecision);
+        qtyNum = Math.floor(qtyNum * qpFactor) / qpFactor;
+      }
+      const minNotional = this.cfg.minNotionalUsd ?? 5;
+      if (qtyNum * price < minNotional) {
+        const neededQty = minNotional / price;
+        if (symbol === 'BUSDT') {
+          qtyNum = Math.max(Math.ceil(neededQty), Math.ceil(meta.minQty));
+        } else {
+          const steps = Math.ceil(neededQty / meta.stepSize);
+          qtyNum = Math.max(steps * meta.stepSize, meta.minQty);
+          const qpFactor = Math.pow(10, meta.quantityPrecision);
+          qtyNum = Math.ceil(qtyNum * qpFactor) / qpFactor;
+        }
+      }
+      const stepDp = (meta.stepSize.toString().split('.')[1] || '').length;
+      const decimals = symbol === 'BUSDT' ? 0 : Math.min(meta.quantityPrecision ?? stepDp, stepDp);
+      quantity = qtyNum.toFixed(decimals);
+    }
+
+    const side = posSide === 'LONG' ? 'BUY' : 'SELL';
+    // 高频模式并发限制
+    if (this.cfg.hfMode) {
+      const limit = this.cfg.hfMaxConcurrentPerSide ?? 2;
+      if (posSide === 'LONG' && this.hfConcurrentLong >= limit) return;
+      if (posSide === 'SHORT' && this.hfConcurrentShort >= limit) return;
+    }
+
+    const openResp = await this.placeOpen(client, { symbol, side: side as any, quantity, positionSide: posSide, newOrderRespType: 'RESULT' }, meta, price, forceTaker === true);
+
+    const entry = parseFloat(openResp.avgPrice || `${price}`);
+
+    // 审计:开仓后聚合两账户数据并记录
+    await this.writeAudit({
+      event: 'OPEN',
+      account: client === this.accountA ? 'A' : 'B',
+      symbol,
+      posSide,
+      quantity,
+      price: entry,
+      orderId: openResp?.orderId
+    }).catch(() => {/* ignore */});
+    // 额外:单笔下单记录,便于离线统计“每笔开单数量/价格/侧”
+    try {
+      const rec = {
+        time: Date.now(),
+        symbol,
+        side: side,
+        posSide,
+        quantity,
+        price: entry,
+        account: client === this.accountA ? 'A' : 'B',
+        orderId: openResp?.orderId
+      };
+      const file = path.resolve(process.cwd(), 'logs/dual_grid_orders.jsonl');
+      await fs.mkdir(path.dirname(file), { recursive: true }).catch(() => {});
+      await fs.appendFile(file, JSON.stringify(rec) + '\n');
+    } catch {}
+
+    if (this.cfg.hfMode) {
+      // 高频:延时用市价平同侧仓位,避免价格精度问题
+      const hold = this.cfg.hfHoldMs ?? 2000;
+      if (posSide === 'LONG') this.hfConcurrentLong++;
+      else this.hfConcurrentShort++;
+      setTimeout(async () => {
+        try {
+          const closeSide = posSide === 'LONG' ? 'SELL' : 'BUY';
+          const closeResp = await this.safePlace(client, { symbol, side: closeSide, type: 'MARKET', quantity, positionSide: posSide, newOrderRespType: 'ACK' }, meta);
+          await this.writeAudit({
+            event: 'CLOSE',
+            account: client === this.accountA ? 'A' : 'B',
+            symbol,
+            posSide,
+            quantity,
+            price: refPrice ?? (await this.fetchMidPrice(symbol)),
+            orderId: closeResp?.orderId
+          }).catch(() => {/* ignore */});
+        } catch (e) {
+          this.emit('error', e);
+        } finally {
+          if (posSide === 'LONG') this.hfConcurrentLong = Math.max(0, this.hfConcurrentLong - 1);
+          else this.hfConcurrentShort = Math.max(0, this.hfConcurrentShort - 1);
+        }
+      }, hold);
+      return;
+    }
+
+    try {
+      await this.placeTpSl(client, symbol, posSide, quantity, entry, meta);
+    } catch (e) {
+      // 不影响已开仓,记录错误即可
+      this.emit('error', e);
+    }
+  }
+
+  // 计算对称数量:分别按两账户可用资金估算可下的数量,取较小值;
+  // 返回已按步进与精度格式化好的字符串,确保两侧完全一致。
+  private async estimateSymmetricQty(refPrice: number): Promise<string> {
+    const symbol = this.cfg.symbol.toUpperCase();
+    const baseUrl = this.accountA.getConfig().baseUrl;
+    const meta = await getSymbolMeta(baseUrl, symbol);
+    const price = refPrice || (await this.fetchMidPrice(symbol));
+    const [qA, qB] = await Promise.all([
+      this.estimateQty(this.accountA, price, meta),
+      this.estimateQty(this.accountB, price, meta)
+    ]);
+    let q = Math.min(qA, qB);
+    if (symbol === 'BUSDT') {
+      q = Math.max(Math.floor(q), Math.ceil(meta.minQty));
+      return q.toFixed(0);
+    }
+    q = Math.floor(q / meta.stepSize) * meta.stepSize;
+    if (q < meta.minQty) q = meta.minQty;
+    const stepDp = (meta.stepSize.toString().split('.')[1] || '').length;
+    const qp = Math.min(meta.quantityPrecision, stepDp);
+    const factor = Math.pow(10, qp);
+    q = Math.floor(q * factor) / factor;
+    return q.toFixed(qp);
+  }
+
+  private async placeOpen(
+    client: AsterTradingClient,
+    base: any,
+    meta: { stepSize: number; minQty: number; quantityPrecision: number; tickSize?: number; pricePrecision?: number },
+    mid: number,
+    forceMarket: boolean = false
+  ): Promise<any> {
+    if (!forceMarket && this.cfg.makerMode) {
+      const side = base.side as 'BUY' | 'SELL';
+      const offset = (this.cfg.makerOffsetBps ?? 3) / 10000;
+      const tick = meta.tickSize || 0.01;
+      const tickDp = Math.max(0, (tick.toString().split('.')[1] || '').length);
+      let dp = Math.max(0, Math.min((meta as any).pricePrecision ?? tickDp, tickDp));
+      const sym = (base.symbol || this.cfg.symbol || '').toUpperCase();
+      if (sym === 'BTCUSDT') dp = 1;
+      const factor = Math.pow(10, dp);
+      const intTick = Math.round(tick * factor);
+      const alignDown = (p: number) => {
+        let pi = Math.round(p * factor);
+        pi = Math.floor(pi / intTick) * intTick;
+        return (pi / factor).toFixed(dp);
+      };
+      const alignUp = (p: number) => {
+        let pi = Math.round(p * factor);
+        pi = Math.ceil(pi / intTick) * intTick;
+        return (pi / factor).toFixed(dp);
+      };
+      let priceStr: string;
+      if (this.cfg.makerJoinBest && (this as any)._lastBestBid && (this as any)._lastBestAsk) {
+        // 薄盘口:跟随盘口最优,向内第N个tick,减少被动成交风险
+        const inside = Math.max(0, this.cfg.makerInsideTicks ?? 0);
+        const bestBid = (this as any)._lastBestBid as number;
+        const bestAsk = (this as any)._lastBestAsk as number;
+        if (side === 'BUY') {
+          const p = bestBid - inside * tick;
+          priceStr = alignDown(p);
+        } else {
+          const p = bestAsk + inside * tick;
+          priceStr = alignUp(p);
+        }
+      } else {
+        priceStr = side === 'BUY' ? alignDown(mid * (1 - offset)) : alignUp(mid * (1 + offset));
+      }
+      // 使用 GTX(Post-Only):避免立刻吃单,减少点差损耗
+      const order = { ...base, type: 'LIMIT', price: priceStr, timeInForce: 'GTX' };
+      // 短超时机制:若在 makerTimeoutMs 内未成交,则撤单并改走市价
+      const timeout = this.cfg.makerTimeoutMs ?? 800;
+      const start = Date.now();
+      try {
+        const resp = await this.safePlace(client, order, meta);
+        // 若服务器立即返回已接单但未成交,这里仍按“已下单”处理;调用方会在下一tick刷新净敞口
+        // 为降低原子性破坏,若同tick另一侧失败,外层逻辑会回滚
+        // 如需更强一致性,可在此对 openResp 检查成交量(若接口支持)
+        // 超时判断:如果过了timeout仍未配对另一侧,将在外层回滚
+        if (Date.now() - start > timeout) {
+          // 超时:撤单并市价
+          // 注意:若无法拿到 orderId,可直接尝试下市价,由交易所处理冲突
+          return await this.safePlace(client, { ...base, type: 'MARKET', price: undefined, timeInForce: undefined }, meta);
+        }
+        return resp;
+      } catch (e: any) {
+        // GTX 失败:直接降级走市价
+        return this.safePlace(client, { ...base, type: 'MARKET', price: undefined, timeInForce: undefined }, meta);
+      }
+    }
+    return this.safePlace(client, { ...base, type: 'MARKET' }, meta);
+  }
+
+  private async fetchTopSpread(symbol: string): Promise<{ spreadBps: number; bestBid: number; bestAsk: number }> {
+    const url = `${this.accountA.getConfig().baseUrl}/fapi/v3/depth?symbol=${symbol.toUpperCase()}&limit=5`;
+    const resp = await axios.get(url);
+    const bids = resp.data.bids || [];
+    const asks = resp.data.asks || [];
+    const bestBid = bids.length ? parseFloat(bids[0][0]) : 0;
+    const bestAsk = asks.length ? parseFloat(asks[0][0]) : 0;
+    if (bestBid <= 0 || bestAsk <= 0 || bestAsk <= bestBid) return { spreadBps: 0, bestBid: 0, bestAsk: 0 };
+    const mid = (bestAsk + bestBid) / 2;
+    const spreadBps = ((bestAsk - bestBid) / mid) * 10000;
+    return { spreadBps, bestBid, bestAsk };
+  }
+
+  private async cancelOpenLimitOrders(client: AsterTradingClient, symbol: string, posSide: 'LONG' | 'SHORT'): Promise<void> {
+    try {
+      const opens = await client.getOpenOrders({ symbol } as any);
+      for (const o of (opens as any[])) {
+        const side = (o as any).positionSide || (o as any).ps || 'BOTH';
+        if (side === posSide && (o as any).type === 'LIMIT') {
+          try { await client.cancelOrder({ symbol, orderId: (o as any).orderId } as any); } catch {}
+        }
+      }
+    } catch {}
+  }
+
+  private async safeClose(client: AsterTradingClient, symbol: string, posSide: 'LONG' | 'SHORT'): Promise<void> {
+    try {
+      const risks = await client.getPositionRisk({ symbol } as any);
+      const p = Array.isArray(risks) ? risks.find((x: any) => x.symbol === symbol && x.positionSide === posSide) : null;
+      const amt = p ? Math.abs(parseFloat(p.positionAmt || '0')) : 0;
+      if (amt <= 0) return;
+      const side = posSide === 'LONG' ? 'SELL' : 'BUY';
+      const qtyStr = await this.formatQtyForSymbol(client, amt);
+      await this.safePlace(client, { symbol, side, type: 'MARKET', quantity: qtyStr, positionSide: posSide, newOrderRespType: 'ACK' }, await getSymbolMeta(client.getConfig().baseUrl, symbol));
+    } catch {}
+  }
+
+  private async forceNetZero(): Promise<void> {
+    if (!this.cfg.maxNetQty || this.cfg.maxNetQty <= 0) return;
+    try {
+      const symbol = this.cfg.symbol.toUpperCase();
+      const [aRisk, bRisk] = await Promise.all([
+        this.accountA.getPositionRisk({ symbol } as any).catch(() => []),
+        this.accountB.getPositionRisk({ symbol } as any).catch(() => [])
+      ]);
+      const getAmt = (list: any[], side: 'LONG' | 'SHORT') => {
+        const p = Array.isArray(list) ? list.find((x: any) => x.symbol === symbol && x.positionSide === side) : null;
+        return p ? parseFloat(p.positionAmt || '0') : 0;
+      };
+      let aLongAmt = getAmt(aRisk as any[], 'LONG');
+      let bShortAmt = getAmt(bRisk as any[], 'SHORT');
+      let net = aLongAmt + bShortAmt;
+      const th = this.cfg.maxNetQty;
+      // 循环小步归零,直到净敞口回到阈值以内或无仓位可操作
+      let guard = 0;
+      while (Math.abs(net) > th && guard++ < 5) {
+        const over = Math.max(0, Math.abs(net) - th);
+        if (over <= 0) break;
+        const step = Math.max(0.001, th);
+        let chunk = Math.min(over, step);
+        if (net > 0) {
+          chunk = Math.min(chunk, Math.max(0, aLongAmt));
+          if (chunk <= 0) break;
+          const meta = await getSymbolMeta(this.accountA.getConfig().baseUrl, symbol);
+          const qStr = await this.formatQtyForSymbol(this.accountA, chunk);
+          await this.safePlace(this.accountA, { symbol, side: 'SELL', type: 'MARKET', quantity: qStr, positionSide: 'LONG', newOrderRespType: 'ACK' }, meta).catch(() => {});
+        } else {
+          chunk = Math.min(chunk, Math.max(0, bShortAmt));
+          if (chunk <= 0) break;
+          const meta = await getSymbolMeta(this.accountB.getConfig().baseUrl, symbol);
+          const qStr = await this.formatQtyForSymbol(this.accountB, chunk);
+          await this.safePlace(this.accountB, { symbol, side: 'BUY', type: 'MARKET', quantity: qStr, positionSide: 'SHORT', newOrderRespType: 'ACK' }, meta).catch(() => {});
+        }
+        // 重新读取净敞口
+        const [a2, b2] = await Promise.all([
+          this.accountA.getPositionRisk({ symbol } as any).catch(() => []),
+          this.accountB.getPositionRisk({ symbol } as any).catch(() => [])
+        ]);
+        aLongAmt = getAmt(a2 as any[], 'LONG');
+        bShortAmt = getAmt(b2 as any[], 'SHORT');
+        net = aLongAmt + bShortAmt;
+      }
+    } catch {}
+  }
+
+  private async maybeDeleverageBothOn2019(
+    who: AsterTradingClient,
+    symbol: string,
+    side: 'LONG' | 'SHORT',
+    chunk: number,
+    minIv: number,
+    now: number
+  ): Promise<void> {
+    // 触发方账户先减仓
+    const steps = Math.max(1, this.cfg.deleverageStepsOn2019 ?? 2);
+    const mul = Math.max(1, this.cfg.deleverageMultiplier ?? 1.5);
+    const maxChunk = Math.max(chunk, this.cfg.deleverageMaxChunkQty ?? 0.01);
+    const doA = async (q: number) => this.accountA.placeOrder({ symbol, side: 'SELL' as any, type: 'MARKET', quantity: q.toFixed(3), positionSide: 'LONG', newOrderRespType: 'ACK' } as any).catch(() => {});
+    const doB = async (q: number) => this.accountB.placeOrder({ symbol, side: 'BUY'  as any, type: 'MARKET', quantity: q.toFixed(3), positionSide: 'SHORT', newOrderRespType: 'ACK' } as any).catch(() => {});
+    let q = chunk;
+    for (let i = 0; i < steps; i++) {
+      const qi = Math.min(maxChunk, q);
+      if (who === this.accountA && side === 'LONG' && now - this.lastDeleverageAtA >= minIv) {
+        await doA(qi); this.lastDeleverageAtA = now;
+      }
+      if (who === this.accountB && side === 'SHORT' && now - this.lastDeleverageAtB >= minIv) {
+        await doB(qi); this.lastDeleverageAtB = now;
+      }
+      // 对侧同步
+      if (side === 'LONG' && now - this.lastDeleverageAtB >= minIv) { await doB(qi); this.lastDeleverageAtB = now; }
+      if (side === 'SHORT' && now - this.lastDeleverageAtA >= minIv) { await doA(qi); this.lastDeleverageAtA = now; }
+      q *= mul;
+    }
+  }
+
+  private async estimateQty(
+    client: AsterTradingClient,
+    midPrice: number,
+    meta: { stepSize: number; minQty: number; quantityPrecision: number }
+  ): Promise<number> {
+    const minNotional = this.cfg.minNotionalUsd ?? 5;
+    const baseNotional = this.cfg.baseNotionalUsd;
+    try {
+      const ts = Date.now();
+      const acct = await client.getAccountInfo({ timestamp: ts } as any);
+      const avail = Math.max(0, parseFloat((acct as any)?.availableBalance ?? '0'));
+      let lev = this.cfg.leverage;
+      try {
+        const risks = await client.getPositionRisk({ symbol: this.cfg.symbol.toUpperCase(), timestamp: ts } as any);
+        const pr = Array.isArray(risks) ? risks.find((p: any) => p.symbol === this.cfg.symbol.toUpperCase()) : null;
+        if (pr && pr.leverage) lev = Math.max(1, parseInt(pr.leverage));
+      } catch {}
+      const feeBuffer = 1.02; // 手续费与滑点冗余
+      const safeNotional = Math.max(0, avail * lev / feeBuffer);
+      const targetNotional = Math.min(baseNotional, safeNotional);
+      if (targetNotional < minNotional) return 0; // 不足最小名义,跳过
+      // 转换为数量
+      let q = targetNotional / midPrice;
+      if (this.cfg.symbol.toUpperCase() === 'BUSDT') {
+        q = Math.max(Math.floor(q), Math.ceil(meta.minQty));
+      } else {
+        q = Math.floor(q / meta.stepSize) * meta.stepSize;
+        if (q < meta.minQty) q = meta.minQty;
+        const qp = Math.min(meta.quantityPrecision, (meta.stepSize.toString().split('.')[1] || '').length);
+        const factor = Math.pow(10, qp);
+        q = Math.floor(q * factor) / factor;
+      }
+      return q;
+    } catch {
+      // 回退固定名义
+      return this.cfg.baseNotionalUsd / midPrice;
+    }
+  }
+
+  private async writeAudit(payload: {
+    event: 'OPEN' | 'CLOSE';
+    account: 'A' | 'B';
+    symbol: string;
+    posSide: 'LONG' | 'SHORT';
+    quantity: string;
+    price?: number;
+    orderId?: number;
+  }): Promise<void> {
+    if (!this.cfg.auditEnabled) return;
+    const file = this.cfg.auditLogFile || path.resolve(process.cwd(), 'logs/dual_grid_audit.jsonl');
+    const ts = Date.now();
+    // 聚合两账户账目
+    const [aInfo, bInfo, aRisk, bRisk] = await Promise.all([
+      this.accountA.getAccountInfo({ timestamp: ts } as any).catch(() => null),
+      this.accountB.getAccountInfo({ timestamp: ts } as any).catch(() => null),
+      this.accountA.getPositionRisk({ symbol: this.cfg.symbol.toUpperCase(), timestamp: ts } as any).catch(() => null),
+      this.accountB.getPositionRisk({ symbol: this.cfg.symbol.toUpperCase(), timestamp: ts } as any).catch(() => null)
+    ]);
+    const summarize = (info: any, risk: any) => {
+      const wallet = parseFloat(info?.totalWalletBalance ?? '0');
+      const available = parseFloat(info?.availableBalance ?? '0');
+      const positions = Array.isArray(risk) ? risk.filter((p: any) => p.symbol === this.cfg.symbol.toUpperCase()) : [];
+      const pos = {
+        long: positions.find((p: any) => p.positionSide === 'LONG') || null,
+        short: positions.find((p: any) => p.positionSide === 'SHORT') || null
+      } as any;
+      const project = (p: any) => p ? { amt: parseFloat(p.positionAmt || '0'), notional: Math.abs(parseFloat(p.notional || '0')), entry: parseFloat(p.entryPrice || '0') } : null;
+      return {
+        wallet,
+        available,
+        long: project(pos.long),
+        short: project(pos.short)
+      };
+    };
+    const rec = {
+      time: ts,
+      event: payload.event,
+      symbol: payload.symbol,
+      side: payload.posSide,
+      account: payload.account,
+      qty: payload.quantity,
+      price: payload.price,
+      orderId: payload.orderId,
+      accountA: summarize(aInfo, aRisk),
+      accountB: summarize(bInfo, bRisk)
+    };
+    // 计算合并净值指标
+    const aSum = rec.accountA as any;
+    const bSum = rec.accountB as any;
+    const netAmt =
+      (aSum.long?.amt || 0) + (aSum.short?.amt || 0) +
+      (bSum.long?.amt || 0) + (bSum.short?.amt || 0);
+    const netNotional =
+      (aSum.long?.notional || 0) - (aSum.short?.notional || 0) +
+      (bSum.long?.notional || 0) - (bSum.short?.notional || 0);
+    const walletSum = (aSum.wallet || 0) + (bSum.wallet || 0);
+    const availableSum = (aSum.available || 0) + (bSum.available || 0);
+    (rec as any).net = {
+      amt: netAmt,
+      notional: netNotional,
+      walletSum,
+      availableSum
+    };
+    const dir = path.dirname(file);
+    await fs.mkdir(dir, { recursive: true }).catch(() => {});
+    await fs.appendFile(file, JSON.stringify(rec) + '\n');
+    // 控制台精简单行输出
+    const pretty = (n: number, d = 2) => Number.isFinite(n) ? n.toFixed(d) : '0';
+    const pretty4 = (n: number) => pretty(n, 4);
+    const nowStr = new Date(ts).toISOString();
+    console.log(
+      `[AUDIT] ${nowStr} ${rec.event} ${rec.account} ${rec.symbol} ${rec.side} q=${payload.quantity}` +
+      `${payload.price ? ` p=${pretty(payload.price)}` : ''} | netAmt=${pretty4(netAmt)} netUsd=$${pretty(netNotional)}` +
+      ` | wallet=$${pretty(walletSum)} avail=$${pretty(availableSum)}`
+    );
+  }
+
+  private async placeTpSl(
+    client: AsterTradingClient,
+    symbol: string,
+    posSide: 'LONG' | 'SHORT',
+    quantity: string,
+    entryPrice: number,
+    meta: { tickSize: number; pricePrecision: number }
+  ): Promise<void> {
+    const tpBps = this.cfg.tpBps ?? 30;
+    const slBps = this.cfg.slBps ?? 60;
+    const workingType = this.cfg.workingType ?? 'MARK_PRICE';
+    const tpPct = tpBps / 10000;
+    const slPct = slBps / 10000;
+    const tick = meta.tickSize || 0.01;
+    const tickDp = Math.max(0, (tick.toString().split('.')[1] || '').length);
+    const dp = tickDp; // 使用 tickSize 小数位,通常 <= pricePrecision
+    const alignDown = (p: number): string => {
+      const n = Math.floor((p + 1e-12) / tick);
+      const v = n * tick;
+      return v.toFixed(dp);
+    };
+    const alignUp = (p: number): string => {
+      const n = Math.ceil((p - 1e-12) / tick);
+      const v = n * tick;
+      return v.toFixed(dp);
+    };
+
+    if (posSide === 'LONG') {
+      const tp = alignUp(entryPrice * (1 + tpPct));
+      const sl = alignDown(entryPrice * (1 - slPct));
+      await this.tryPlaceReduceOnly(client, {
+        symbol,
+        side: 'SELL' as any,
+        type: 'TAKE_PROFIT_MARKET',
+        positionSide: 'LONG',
+        quantity,
+        stopPrice: tp,
+        workingType: workingType as any,
+        newOrderRespType: 'ACK'
+      });
+      await this.tryPlaceReduceOnly(client, {
+        symbol,
+        side: 'SELL' as any,
+        type: 'STOP_MARKET',
+        positionSide: 'LONG',
+        quantity,
+        stopPrice: sl,
+        workingType: workingType as any,
+        newOrderRespType: 'ACK'
+      });
+    } else {
+      const tp = alignDown(entryPrice * (1 - tpPct));
+      const sl = alignUp(entryPrice * (1 + slPct));
+      await this.tryPlaceReduceOnly(client, {
+        symbol,
+        side: 'BUY' as any,
+        type: 'TAKE_PROFIT_MARKET',
+        positionSide: 'SHORT',
+        quantity,
+        stopPrice: tp,
+        workingType: workingType as any,
+        newOrderRespType: 'ACK'
+      });
+      await this.tryPlaceReduceOnly(client, {
+        symbol,
+        side: 'BUY' as any,
+        type: 'STOP_MARKET',
+        positionSide: 'SHORT',
+        quantity,
+        stopPrice: sl,
+        workingType: workingType as any,
+        newOrderRespType: 'ACK'
+      });
+    }
+  }
+
+  private async tryPlaceReduceOnly(client: AsterTradingClient, baseOrder: any): Promise<void> {
+    // 默认不带 reduceOnly,Dual-Position 模式下带 positionSide 即可
+    const { baseUrl } = client.getConfig();
+    const meta = await getSymbolMeta(baseUrl, this.cfg.symbol.toUpperCase());
+    await this.safePlace(client, baseOrder, meta);
+  }
+
+  private async safePlace(client: AsterTradingClient, order: any, meta: { stepSize: number; minQty: number; quantityPrecision: number; tickSize?: number }): Promise<any> {
+    const symbol = this.cfg.symbol.toUpperCase();
+    const isBUSDT = symbol === 'BUSDT';
+    let attempt = 0;
+    let current = { ...order };
+    // 基础字段清洗,避免 NaN/undefined 传入
+    const sanitize = () => {
+      // 处理数量
+      if (current.quantity != null) {
+        let q = parseFloat(current.quantity);
+        if (!Number.isFinite(q) || q <= 0) {
+          // 回退为最小下单量
+          q = Math.max(meta.minQty, isBUSDT ? Math.ceil(meta.minQty) : meta.minQty);
+        }
+        const stepDp = (meta.stepSize.toString().split('.')[1] || '').length;
+        const qp = isBUSDT ? 0 : Math.min(meta.quantityPrecision, stepDp);
+        if (!isBUSDT) {
+          // 对齐步进
+          q = Math.floor(q / meta.stepSize) * meta.stepSize;
+          const factor = Math.pow(10, qp);
+          q = Math.max(meta.minQty, Math.floor(q * factor) / factor);
+        } else {
+          q = Math.max(Math.floor(q), Math.ceil(meta.minQty));
+        }
+        current.quantity = q.toFixed(qp);
+      }
+      // 处理价格
+      if (current.type === 'LIMIT') {
+        if (current.price == null) {
+          // 降级为市价
+          delete current.price; delete current.timeInForce; current.type = 'MARKET';
+        } else {
+          const p = parseFloat(current.price);
+          if (!Number.isFinite(p) || p <= 0) {
+            delete current.price; delete current.timeInForce; current.type = 'MARKET';
+          } else {
+            const tick = meta.tickSize || 0.01;
+            const tickDp = Math.max(0, (tick.toString().split('.')[1] || '').length);
+            let dp = tickDp;
+            const sym = (current.symbol || this.cfg.symbol || '').toUpperCase();
+            if (sym === 'BTCUSDT') dp = 1;
+            const factor = Math.pow(10, dp);
+            const intTick = Math.round(tick * factor);
+            let pi = Math.round(p * factor);
+            // 对齐到最近 tick
+            pi = Math.round(pi / intTick) * intTick;
+            current.price = (pi / factor).toFixed(dp);
+          }
+        }
+      } else {
+        // 市价不需要 price
+        if (current.price != null) delete current.price;
+      }
+    };
+    sanitize();
+    const adjustQtyDown = () => {
+      if (!current.quantity) return false;
+      let q = parseFloat(current.quantity);
+      if (isNaN(q) || q <= 0) return false;
+      if (isBUSDT) {
+        q = Math.max(Math.floor(q - 1), Math.ceil(meta.minQty));
+        if (q < meta.minQty) return false;
+        current.quantity = q.toFixed(0);
+        return true;
+      }
+      // 减一档步进
+      q = q - meta.stepSize;
+      // 对齐步进
+      q = Math.floor(q / meta.stepSize) * meta.stepSize;
+      if (q < meta.minQty) return false;
+      const qp = Math.min(meta.quantityPrecision, (meta.stepSize.toString().split('.')[1] || '').length);
+      current.quantity = q.toFixed(qp);
+      return true;
+    };
+    const ensurePositiveQty = () => {
+      if (current.quantity == null) return false;
+      let q = parseFloat(current.quantity);
+      if (!Number.isFinite(q) || q <= 0) return false;
+      // 最小对齐
+      if (isBUSDT) {
+        q = Math.max(Math.floor(q), Math.ceil(meta.minQty));
+        current.quantity = q.toFixed(0);
+        return q > 0;
+      }
+      const stepDp = (meta.stepSize.toString().split('.')[1] || '').length;
+      const qp = Math.min(meta.quantityPrecision, stepDp);
+      q = Math.max(q, meta.minQty);
+      const factor = Math.pow(10, qp);
+      q = Math.max(meta.minQty, Math.floor(q * factor) / factor);
+      current.quantity = q.toFixed(qp);
+      return q > 0;
+    };
+    while (attempt < 3) {
+      try {
+        // 防御:在最终发单前确保数量>0
+        if (!ensurePositiveQty()) throw new Error('quantity_not_positive');
+        return await client.placeOrder(current as any);
+      } catch (e: any) {
+        const code = e?.response?.data?.code;
+        if (process.env.ASTER_TRACE_HTTP === '1') {
+          console.error('❌ placeOrder failed code:', code, 'payload=', JSON.stringify(current));
+        }
+        // -4003: Quantity less than zero(或被精度压成0)→ 尝试最小量重试一次
+        if (code === -4003) {
+          if (isBUSDT) {
+            current.quantity = Math.ceil(meta.minQty).toFixed(0);
+          } else {
+            const stepDp = (meta.stepSize.toString().split('.')[1] || '').length;
+            const qp = Math.min(meta.quantityPrecision, stepDp);
+            current.quantity = (meta.minQty).toFixed(qp);
+          }
+          attempt++; continue;
+        }
+        if (code === -1111) {
+          // 精度问题:下调数量重试
+          let fixed = false;
+          // 价格精度(LIMIT)
+          if ((current.type === 'LIMIT' || current.type === 'STOP' || current.type === 'TAKE_PROFIT') && current.price) {
+            const tick = meta.tickSize || 0.01;
+            const tickDp = Math.max(0, (tick.toString().split('.')[1] || '').length);
+            let dp = Math.max(0, Math.min((meta as any).pricePrecision ?? tickDp, tickDp));
+            const sym = (current.symbol || this.cfg.symbol || '').toUpperCase();
+            if (sym === 'BTCUSDT') dp = 1;
+            const factor = Math.pow(10, dp);
+            const intTick = Math.round(tick * factor);
+            const pNum = parseFloat(current.price);
+            if (Number.isFinite(pNum)) {
+              let pi = Math.round(pNum * factor);
+              if (current.side === 'BUY') {
+                pi = Math.floor((pi - intTick) / intTick) * intTick;
+              } else {
+                pi = Math.ceil((pi + intTick) / intTick) * intTick;
+              }
+              current.price = (pi / factor).toFixed(dp);
+              fixed = true;
+            }
+          }
+          // 数量精度
+          if (!fixed && adjustQtyDown()) { attempt++; continue; }
+          if (fixed) { attempt++; continue; }
+        } else if (code === -2019) {
+          // 保证金不足:将数量降至 70%
+          if (current.quantity) {
+            let q = parseFloat(current.quantity);
+            if (isBUSDT) {
+              q = Math.max(Math.floor(q * 0.7), Math.ceil(meta.minQty));
+              if (q >= meta.minQty) { current.quantity = q.toFixed(0); attempt++; continue; }
+            } else {
+              q = q * 0.7;
+              q = Math.floor(q / meta.stepSize) * meta.stepSize;
+              if (q >= meta.minQty) {
+                const qp = Math.min(meta.quantityPrecision, (meta.stepSize.toString().split('.')[1] || '').length);
+                current.quantity = q.toFixed(qp);
+                attempt++; continue;
+              }
+            }
+          }
+          // 记录冷却
+          if ((current as any).positionSide === 'LONG') this.lastMarginErrA = Date.now();
+          if ((current as any).positionSide === 'SHORT') this.lastMarginErrB = Date.now();
+          // 动态减仓:释放保证金
+          if (this.cfg.deleverageAfter2019) {
+            const now = Date.now();
+            const minIv = this.cfg.deleverageMinIntervalMs ?? 8000;
+            const chunk = this.cfg.deleverageChunkQty ?? 0.002;
+            try {
+              const sym = this.cfg.symbol.toUpperCase();
+              await this.maybeDeleverageBothOn2019(client, sym, (current as any).positionSide, chunk, minIv, now);
+            } catch (err) {
+              this.emit('error', err as any);
+            }
+          }
+        } else if (code === -1102) {
+          // 参数 NaN/空:重新清洗后重试
+          sanitize();
+          attempt++; continue;
+        }
+        throw e;
+      }
+    }
+    // 最终失败
+    if (!ensurePositiveQty()) throw new Error('quantity_not_positive_final');
+    return await client.placeOrder(current as any);
+  }
+
+  private async refreshProtectiveOrders(client: AsterTradingClient, posSide: 'LONG' | 'SHORT', refMid?: number): Promise<void> {
+    const symbol = this.cfg.symbol.toUpperCase();
+    const { baseUrl } = client.getConfig();
+    const meta = await getSymbolMeta(baseUrl, symbol);
+    const mid = refMid ?? (await this.fetchMidPrice(symbol));
+
+    // 读取当前侧持仓数量
+    const risk = await client.getPositionRisk({ symbol } as any);
+    const pos = Array.isArray(risk) ? risk.find((p: any) => p.symbol === symbol && p.positionSide === posSide) : null;
+    const amt = pos ? parseFloat(pos.positionAmt || '0') : 0;
+    const qtyAbs = Math.abs(amt);
+    if (qtyAbs <= 0) {
+      await this.cancelProtectiveOrders(client, symbol, posSide);
+      return;
+    }
+
+    // 先取消旧保护单
+    await this.cancelProtectiveOrders(client, symbol, posSide);
+
+    // 计算数量字符串
+    const qp = Math.min(meta.quantityPrecision, (meta.stepSize.toString().split('.')[1] || '').length);
+    const qtyStr = symbol === 'BUSDT' ? Math.floor(qtyAbs).toFixed(0) : qtyAbs.toFixed(qp);
+
+    // 计算触发价
+    const tpBps = this.cfg.tpBps ?? 30;
+    const slBps = this.cfg.slBps ?? 60;
+    const tpPct = tpBps / 10000;
+    const slPct = slBps / 10000;
+    const tick2 = meta.tickSize || 0.01;
+    const dp2 = Math.max(0, (tick2.toString().split('.')[1] || '').length);
+    const alignDown2 = (p: number): string => {
+      const n = Math.floor((p + 1e-12) / tick2);
+      const v = n * tick2;
+      return v.toFixed(dp2);
+    };
+    const alignUp2 = (p: number): string => {
+      const n = Math.ceil((p - 1e-12) / tick2);
+      const v = n * tick2;
+      return v.toFixed(dp2);
+    };
+    const workingType = this.cfg.workingType ?? 'MARK_PRICE';
+
+    if (posSide === 'LONG') {
+      const tp = alignUp2(mid * (1 + tpPct));
+      const sl = alignDown2(mid * (1 - slPct));
+      await this.safePlace(client, { symbol, side: 'SELL', type: 'TAKE_PROFIT_MARKET', positionSide: 'LONG', quantity: qtyStr, stopPrice: tp, workingType, newOrderRespType: 'ACK', newClientOrderId: `GRID_TP_LONG_${Date.now()}` }, meta);
+      await this.safePlace(client, { symbol, side: 'SELL', type: 'STOP_MARKET',         positionSide: 'LONG', quantity: qtyStr, stopPrice: sl, workingType, newOrderRespType: 'ACK', newClientOrderId: `GRID_SL_LONG_${Date.now()}` }, meta);
+    } else {
+      const tp = alignDown2(mid * (1 - tpPct));
+      const sl = alignUp2(mid * (1 + slPct));
+      await this.safePlace(client, { symbol, side: 'BUY', type: 'TAKE_PROFIT_MARKET', positionSide: 'SHORT', quantity: qtyStr, stopPrice: tp, workingType, newOrderRespType: 'ACK', newClientOrderId: `GRID_TP_SHORT_${Date.now()}` }, meta);
+      await this.safePlace(client, { symbol, side: 'BUY', type: 'STOP_MARKET',         positionSide: 'SHORT', quantity: qtyStr, stopPrice: sl, workingType, newOrderRespType: 'ACK', newClientOrderId: `GRID_SL_SHORT_${Date.now()}` }, meta);
+    }
+  }
+
+  private async cancelProtectiveOrders(client: AsterTradingClient, symbol: string, posSide: 'LONG' | 'SHORT'): Promise<void> {
+    try {
+      const opens = await client.getOpenOrders({ symbol } as any);
+      const prefixes = posSide === 'LONG' ? ['GRID_TP_LONG_', 'GRID_SL_LONG_'] : ['GRID_TP_SHORT_', 'GRID_SL_SHORT_'];
+      for (const o of opens as any[]) {
+        const cid = (o as any).clientOrderId || (o as any).c || '';
+        if (cid && prefixes.some(p => (cid as string).startsWith(p))) {
+          try { await client.cancelOrder({ symbol, origClientOrderId: cid } as any); } catch { /* ignore */ }
+        }
+      }
+    } catch { /* ignore */ }
+  }
+
+  // ===== 持久化 =====
+  private getStateFile(): string | null {
+    const sf = this.cfg.stateFile || '';
+    if (sf) return sf;
+    // 默认保存到当前工作目录下的隐藏文件
+    const name = `.grid_state_${this.cfg.symbol.toUpperCase()}.json`;
+    return path.resolve(process.cwd(), name);
+  }
+
+  private async tryLoadState(): Promise<void> {
+    try {
+      const file = this.getStateFile();
+      if (!file) return;
+      const buf = await fs.readFile(file, 'utf8');
+      const json = JSON.parse(buf);
+      if (typeof json.prevMid === 'number') this.prevMid = json.prevMid;
+      if (json.stats) {
+        this.stats = {
+          placedLong: Number(json.stats.placedLong) || 0,
+          placedShort: Number(json.stats.placedShort) || 0,
+          errors: Number(json.stats.errors) || 0,
+          lastMidPrice: typeof json.stats.lastMidPrice === 'number' ? json.stats.lastMidPrice : undefined
+        };
+      }
+      const now = Date.now();
+      const windowMs = 60_000;
+      this.recentLongOpens = Array.isArray(json.recentLongOpens) ? json.recentLongOpens.filter((t: number) => now - t <= windowMs) : [];
+      this.recentShortOpens = Array.isArray(json.recentShortOpens) ? json.recentShortOpens.filter((t: number) => now - t <= windowMs) : [];
+    } catch {
+      // ignore
+    }
+  }
+
+  private async trySaveState(): Promise<void> {
+    try {
+      const file = this.getStateFile();
+      if (!file) return;
+      const dir = path.dirname(file);
+      await fs.mkdir(dir, { recursive: true });
+      const state = {
+        prevMid: this.prevMid,
+        stats: this.stats,
+        recentLongOpens: this.recentLongOpens,
+        recentShortOpens: this.recentShortOpens,
+        updatedAt: Date.now()
+      };
+      await fs.writeFile(file, JSON.stringify(state));
+    } catch {
+      // ignore
+    }
+  }
+
+  private startPersist(): void {
+    this.stopPersist();
+    const interval = this.cfg.persistIntervalMs ?? 15_000;
+    this.persistTimer = setInterval(() => {
+      this.trySaveState().catch(() => {/* ignore */});
+    }, interval);
+  }
+
+  private stopPersist(): void {
+    if (this.persistTimer) {
+      clearInterval(this.persistTimer);
+      this.persistTimer = null;
+    }
+  }
+
+  private async fetchMidPrice(symbol: string): Promise<number> {
+    // 直接使用 ticker 价格作为参考中价
+    const url = `${this.accountA.getConfig().baseUrl}/fapi/v3/ticker/price?symbol=${symbol.toUpperCase()}`;
+    const resp = await axios.get(url);
+    return parseFloat(resp.data.price);
+  }
+
+  private async ensureLeverage(client: AsterTradingClient, symbol: string, targetLev: number, useMax?: boolean): Promise<void> {
+    try {
+      const data = await client.getLeverageBracket(symbol);
+      const brackets = data?.brackets || data?.[0]?.brackets || [];
+      const maxLev = brackets.reduce((m: number, b: any) => Math.max(m, b.initialLeverage || 0), 0) || targetLev;
+      const finalLev = useMax ? maxLev : Math.min(targetLev, maxLev);
+      if (finalLev > 0) await client.setLeverage(symbol, finalLev);
+    } catch {
+      // ignore
+    }
+  }
+}
+
+
+

+ 31 - 0
src/dex/aster/index.ts

@@ -0,0 +1,31 @@
+// Aster DEX 模块导出
+
+// WebSocket 相关
+export * from './wsClient';
+export * from './types';
+
+// OrderBook 相关
+export * from './orderBook';
+export * from './orderBookManager';
+
+// 交易相关
+export * from './tradingClient';
+export * from './tradingTypes';
+export * from './tradingConfig';
+export * from './signatureGenerator';
+
+// 用户数据流相关
+export * from './userStreamClient';
+export * from './userStreamTypes';
+export * from './listenKeyManager';
+
+// Rh 积分系统相关
+export * from './pointSystem';
+export * from './rhPointStrategyManager';
+
+// 对冲执行器相关
+export * from './dualAccountHedgeExecutor';
+export * from './gridDualExecutor';
+
+// 配置相关
+export * from '../config/asterConfig';

+ 203 - 0
src/dex/aster/listenKeyManager.ts

@@ -0,0 +1,203 @@
+import axios, { AxiosInstance } from 'axios';
+import { AsterSignatureGenerator } from './signatureGenerator';
+import { AsterTradingConfig } from './tradingTypes';
+import { AsterListenKeyResponse } from './userStreamTypes';
+
+export class AsterListenKeyManager {
+  private config: AsterTradingConfig;
+  private httpClient: AxiosInstance;
+  private listenKey: string | null = null;
+  private refreshTimer: NodeJS.Timeout | null = null;
+  private refreshInterval: number = 30 * 60 * 1000; // 30分钟刷新一次
+
+  constructor(config: AsterTradingConfig) {
+    this.config = config;
+    this.httpClient = axios.create({
+      baseURL: this.config.baseUrl,
+      timeout: 10000,
+      headers: {
+        'Content-Type': 'application/x-www-form-urlencoded',
+        'User-Agent': 'AsterListenKeyManager/1.0'
+      }
+    });
+  }
+
+  /**
+   * 创建 ListenKey
+   */
+  async createListenKey(): Promise<string> {
+    try {
+      const signedParams = await this.createSignedParams({});
+      
+      const response = await this.httpClient.post('/fapi/v3/listenKey', signedParams);
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`创建 ListenKey 失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      this.listenKey = response.data.listenKey;
+      console.log('✅ ListenKey 创建成功');
+      
+      // 启动自动刷新
+      this.startAutoRefresh();
+      
+      return this.listenKey;
+    } catch (error: any) {
+      console.error('❌ 创建 ListenKey 失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 延长 ListenKey 有效期
+   */
+  async extendListenKey(): Promise<void> {
+    if (!this.listenKey) {
+      throw new Error('ListenKey 不存在,请先创建');
+    }
+
+    try {
+      const signedParams = await this.createSignedParams({});
+      
+      const response = await this.httpClient.put('/fapi/v3/listenKey', signedParams);
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`延长 ListenKey 失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      console.log('✅ ListenKey 有效期延长成功');
+    } catch (error: any) {
+      console.error('❌ 延长 ListenKey 失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 关闭 ListenKey
+   */
+  async closeListenKey(): Promise<void> {
+    if (!this.listenKey) {
+      console.log('⚠️ ListenKey 不存在,无需关闭');
+      return;
+    }
+
+    try {
+      const signedParams = await this.createSignedParams({});
+      
+      const response = await this.httpClient.delete('/fapi/v3/listenKey', { data: signedParams });
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`关闭 ListenKey 失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      console.log('✅ ListenKey 关闭成功');
+      this.listenKey = null;
+      this.stopAutoRefresh();
+    } catch (error: any) {
+      console.error('❌ 关闭 ListenKey 失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 获取当前 ListenKey
+   */
+  getListenKey(): string | null {
+    return this.listenKey;
+  }
+
+  /**
+   * 检查 ListenKey 是否存在
+   */
+  hasListenKey(): boolean {
+    return this.listenKey !== null;
+  }
+
+  /**
+   * 创建带签名的请求参数
+   */
+  private async createSignedParams(businessParams: Record<string, any>): Promise<Record<string, any>> {
+    const nonce = AsterSignatureGenerator.generateNonce();
+    const timestamp = AsterSignatureGenerator.generateTimestamp();
+
+    // 生成签名
+    const signature = await AsterSignatureGenerator.generateSignature({
+      user: this.config.user,
+      signer: this.config.signer,
+      privateKey: this.config.privateKey,
+      nonce,
+      timestamp,
+      businessParams
+    });
+
+    return {
+      ...businessParams,
+      nonce,
+      user: this.config.user,
+      signer: this.config.signer,
+      signature,
+      recvWindow: this.config.recvWindow,
+      timestamp
+    };
+  }
+
+  /**
+   * 启动自动刷新
+   */
+  private startAutoRefresh(): void {
+    this.stopAutoRefresh(); // 先停止现有的定时器
+    
+    this.refreshTimer = setInterval(async () => {
+      try {
+        await this.extendListenKey();
+      } catch (error) {
+        console.error('❌ 自动延长 ListenKey 失败:', error);
+        // 如果延长失败,尝试重新创建
+        try {
+          await this.createListenKey();
+        } catch (createError) {
+          console.error('❌ 重新创建 ListenKey 失败:', createError);
+        }
+      }
+    }, this.refreshInterval);
+    
+    console.log(`🔄 ListenKey 自动刷新已启动,间隔: ${this.refreshInterval / 1000 / 60} 分钟`);
+  }
+
+  /**
+   * 停止自动刷新
+   */
+  private stopAutoRefresh(): void {
+    if (this.refreshTimer) {
+      clearInterval(this.refreshTimer);
+      this.refreshTimer = null;
+      console.log('⏹️ ListenKey 自动刷新已停止');
+    }
+  }
+
+  /**
+   * 设置刷新间隔
+   */
+  setRefreshInterval(intervalMs: number): void {
+    this.refreshInterval = intervalMs;
+    if (this.refreshTimer) {
+      this.startAutoRefresh(); // 重新启动定时器
+    }
+  }
+
+  /**
+   * 销毁管理器
+   */
+  async destroy(): Promise<void> {
+    this.stopAutoRefresh();
+    if (this.listenKey) {
+      try {
+        await this.closeListenKey();
+      } catch (error) {
+        console.error('❌ 销毁时关闭 ListenKey 失败:', error);
+      }
+    }
+  }
+}
+
+

+ 62 - 0
src/dex/aster/marketMeta.ts

@@ -0,0 +1,62 @@
+import axios from 'axios';
+
+export interface SymbolMeta {
+  symbol: string;
+  quantityPrecision: number;
+  pricePrecision: number;
+  minQty: number;
+  stepSize: number;
+  minPrice: number;
+  tickSize: number;
+  minNotional?: number;
+}
+
+interface CacheEntry<T> {
+  value: T;
+  expireAt: number;
+}
+
+const metaCache: Map<string, CacheEntry<SymbolMeta>> = new Map();
+const DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 分钟
+
+export async function getSymbolMeta(baseUrl: string, symbol: string, ttlMs: number = DEFAULT_TTL_MS): Promise<SymbolMeta> {
+  const key = `${baseUrl}::${symbol.toUpperCase()}`;
+  const now = Date.now();
+  const cached = metaCache.get(key);
+  if (cached && cached.expireAt > now) {
+    return cached.value;
+  }
+
+  const url = `${baseUrl}/fapi/v3/exchangeInfo?symbol=${symbol.toUpperCase()}`;
+  const resp = await axios.get(url);
+  const info = resp.data.symbols?.[0];
+  if (!info) throw new Error(`exchangeInfo 缺少 symbol: ${symbol}`);
+
+  const priceFilter = info.filters?.find((f: any) => f.filterType === 'PRICE_FILTER');
+  const marketLot = info.filters?.find((f: any) => f.filterType === 'MARKET_LOT_SIZE');
+  const lot = info.filters?.find((f: any) => f.filterType === 'LOT_SIZE');
+  const minNotional = info.filters?.find((f: any) => f.filterType === 'MIN_NOTIONAL');
+
+  const chosenLot = marketLot || lot;
+
+  const meta: SymbolMeta = {
+    symbol: info.symbol,
+    quantityPrecision: typeof info.quantityPrecision === 'number' ? info.quantityPrecision : (lot ? (lot.stepSize.toString().split('.')[1] || '0').length : 6),
+    pricePrecision: typeof info.pricePrecision === 'number' ? info.pricePrecision : (priceFilter ? (priceFilter.tickSize.toString().split('.')[1] || '0').length : 6),
+    minQty: chosenLot ? parseFloat(chosenLot.minQty) : 1,
+    stepSize: chosenLot ? parseFloat(chosenLot.stepSize) : 1,
+    minPrice: priceFilter ? parseFloat(priceFilter.minPrice) : 0.000001,
+    tickSize: priceFilter ? parseFloat(priceFilter.tickSize) : 0.000001,
+    minNotional: minNotional ? parseFloat(minNotional.notional) : undefined
+  };
+
+  metaCache.set(key, { value: meta, expireAt: now + ttlMs });
+  return meta;
+}
+
+export function clearSymbolMetaCache(): void {
+  metaCache.clear();
+}
+
+
+

+ 335 - 0
src/dex/aster/orderBook.ts

@@ -0,0 +1,335 @@
+import { EventEmitter } from 'events';
+import axios from 'axios';
+
+export interface OrderBookLevel {
+  price: number;
+  quantity: number;
+}
+
+export interface OrderBookSnapshot {
+  symbol: string;
+  bids: OrderBookLevel[];
+  asks: OrderBookLevel[];
+  lastUpdateId: number;
+  timestamp: number;
+}
+
+export interface DepthUpdate {
+  symbol: string;
+  firstUpdateId: number;  // U
+  finalUpdateId: number;  // u
+  previousFinalUpdateId: number; // pu
+  bids: [string, string][]; // [price, quantity]
+  asks: [string, string][]; // [price, quantity]
+  timestamp: number;
+}
+
+export interface OrderBookEvents {
+  update: (orderBook: OrderBookSnapshot) => void;
+  error: (error: Error) => void;
+  reconnect: () => void;
+}
+
+export class AsterOrderBook extends EventEmitter {
+  private symbol: string;
+  private bids: Map<number, number> = new Map(); // price -> quantity
+  private asks: Map<number, number> = new Map(); // price -> quantity
+  private lastUpdateId: number = 0;
+  private lastFinalUpdateId: number = 0;
+  private isInitialized: boolean = false;
+  private pendingUpdates: DepthUpdate[] = [];
+  private httpBase: string;
+
+  constructor(symbol: string, httpBase: string = 'https://fapi.asterdex.com') {
+    super();
+    this.symbol = symbol.toUpperCase();
+    this.httpBase = httpBase;
+  }
+
+  /**
+   * 初始化 OrderBook - 获取快照并处理待处理的更新
+   */
+  async initialize(): Promise<void> {
+    try {
+      console.log(`📊 初始化 ${this.symbol} OrderBook...`);
+      
+      // 1. 获取深度快照
+      const snapshot = await this.fetchSnapshot();
+      console.log(`📸 获取到快照: lastUpdateId=${snapshot.lastUpdateId}`);
+      
+      // 2. 清空本地缓存
+      this.bids.clear();
+      this.asks.clear();
+      
+      // 3. 丢弃过期的待处理更新
+      this.pendingUpdates = this.pendingUpdates.filter(update => 
+        update.finalUpdateId >= snapshot.lastUpdateId
+      );
+      
+      // 4. 应用快照到本地副本
+      this.applySnapshot(snapshot);
+      
+      // 5. 处理待处理的更新
+      this.processPendingUpdates();
+      
+      this.isInitialized = true;
+      console.log(`✅ ${this.symbol} OrderBook 初始化完成`);
+      
+    } catch (error) {
+      console.error(`❌ ${this.symbol} OrderBook 初始化失败:`, error);
+      this.emit('error', error as Error);
+      throw error;
+    }
+  }
+
+  /**
+   * 获取深度快照
+   */
+  private async fetchSnapshot(): Promise<OrderBookSnapshot> {
+    const url = `${this.httpBase}/fapi/v3/depth`;
+    const response = await axios.get(url, {
+      params: {
+        symbol: this.symbol,
+        limit: 1000
+      }
+    });
+
+    const data = response.data;
+    return {
+      symbol: this.symbol,
+      bids: data.bids.map(([price, quantity]: [string, string]) => ({
+        price: parseFloat(price),
+        quantity: parseFloat(quantity)
+      })),
+      asks: data.asks.map(([price, quantity]: [string, string]) => ({
+        price: parseFloat(price),
+        quantity: parseFloat(quantity)
+      })),
+      lastUpdateId: data.lastUpdateId,
+      timestamp: Date.now()
+    };
+  }
+
+  /**
+   * 应用快照到本地副本
+   */
+  private applySnapshot(snapshot: OrderBookSnapshot): void {
+    // 清空现有数据
+    this.bids.clear();
+    this.asks.clear();
+    
+    // 应用买单
+    for (const level of snapshot.bids) {
+      if (level.quantity > 0) {
+        this.bids.set(level.price, level.quantity);
+      }
+    }
+    
+    // 应用卖单
+    for (const level of snapshot.asks) {
+      if (level.quantity > 0) {
+        this.asks.set(level.price, level.quantity);
+      }
+    }
+    
+    this.lastUpdateId = snapshot.lastUpdateId;
+    this.lastFinalUpdateId = snapshot.lastUpdateId;
+  }
+
+  /**
+   * 处理深度更新
+   */
+  updateDepth(update: DepthUpdate): void {
+    if (!this.isInitialized) {
+      // 如果未初始化,缓存更新
+      this.pendingUpdates.push(update);
+      return;
+    }
+
+    // 检查是否丢包
+    if (this.lastFinalUpdateId !== 0 && update.previousFinalUpdateId !== this.lastFinalUpdateId) {
+      console.warn(`⚠️ ${this.symbol} 检测到丢包: expected pu=${this.lastFinalUpdateId}, got pu=${update.previousFinalUpdateId}`);
+      this.handlePacketLoss();
+      return;
+    }
+
+    // 检查更新ID是否有效
+    if (update.finalUpdateId <= this.lastUpdateId) {
+      // 过期更新,忽略
+      return;
+    }
+
+    // 应用更新
+    this.applyDepthUpdate(update);
+    
+    // 更新状态
+    this.lastFinalUpdateId = update.finalUpdateId;
+    
+    // 发射更新事件
+    this.emitUpdate();
+  }
+
+  /**
+   * 应用深度更新
+   */
+  private applyDepthUpdate(update: DepthUpdate): void {
+    // 处理买单更新
+    for (const [priceStr, quantityStr] of update.bids) {
+      const price = parseFloat(priceStr);
+      const quantity = parseFloat(quantityStr);
+      
+      if (quantity === 0) {
+        // 数量为0,移除该价位
+        this.bids.delete(price);
+      } else {
+        // 更新数量(绝对值)
+        this.bids.set(price, quantity);
+      }
+    }
+    
+    // 处理卖单更新
+    for (const [priceStr, quantityStr] of update.asks) {
+      const price = parseFloat(priceStr);
+      const quantity = parseFloat(quantityStr);
+      
+      if (quantity === 0) {
+        // 数量为0,移除该价位
+        this.asks.delete(price);
+      } else {
+        // 更新数量(绝对值)
+        this.asks.set(price, quantity);
+      }
+    }
+  }
+
+  /**
+   * 处理丢包情况
+   */
+  private async handlePacketLoss(): Promise<void> {
+    console.log(`🔄 ${this.symbol} 重新初始化 OrderBook...`);
+    this.isInitialized = false;
+    this.pendingUpdates = [];
+    
+    try {
+      await this.initialize();
+      this.emit('reconnect');
+    } catch (error) {
+      console.error(`❌ ${this.symbol} 重新初始化失败:`, error);
+      this.emit('error', error as Error);
+    }
+  }
+
+  /**
+   * 处理待处理的更新
+   */
+  private processPendingUpdates(): void {
+    // 按时间顺序处理待处理更新
+    this.pendingUpdates.sort((a, b) => a.timestamp - b.timestamp);
+    
+    for (const update of this.pendingUpdates) {
+      if (update.finalUpdateId > this.lastUpdateId) {
+        this.applyDepthUpdate(update);
+        this.lastFinalUpdateId = update.finalUpdateId;
+      }
+    }
+    
+    this.pendingUpdates = [];
+  }
+
+  /**
+   * 发射更新事件
+   */
+  private emitUpdate(): void {
+    const orderBook: OrderBookSnapshot = {
+      symbol: this.symbol,
+      bids: Array.from(this.bids.entries())
+        .map(([price, quantity]) => ({ price, quantity }))
+        .sort((a, b) => b.price - a.price), // 降序排列
+      asks: Array.from(this.asks.entries())
+        .map(([price, quantity]) => ({ price, quantity }))
+        .sort((a, b) => a.price - b.price), // 升序排列
+      lastUpdateId: this.lastFinalUpdateId,
+      timestamp: Date.now()
+    };
+    
+    this.emit('update', orderBook);
+  }
+
+  /**
+   * 获取当前 OrderBook 快照
+   */
+  getSnapshot(): OrderBookSnapshot {
+    return {
+      symbol: this.symbol,
+      bids: Array.from(this.bids.entries())
+        .map(([price, quantity]) => ({ price, quantity }))
+        .sort((a, b) => b.price - a.price),
+      asks: Array.from(this.asks.entries())
+        .map(([price, quantity]) => ({ price, quantity }))
+        .sort((a, b) => a.price - b.price),
+      lastUpdateId: this.lastFinalUpdateId,
+      timestamp: Date.now()
+    };
+  }
+
+  /**
+   * 获取最佳买价
+   */
+  getBestBid(): number | null {
+    if (this.bids.size === 0) return null;
+    return Math.max(...this.bids.keys());
+  }
+
+  /**
+   * 获取最佳卖价
+   */
+  getBestAsk(): number | null {
+    if (this.asks.size === 0) return null;
+    return Math.min(...this.asks.keys());
+  }
+
+  /**
+   * 获取买卖价差
+   */
+  getSpread(): number | null {
+    const bestBid = this.getBestBid();
+    const bestAsk = this.getBestAsk();
+    
+    if (bestBid === null || bestAsk === null) return null;
+    return bestAsk - bestBid;
+  }
+
+  /**
+   * 获取中间价
+   */
+  getMidPrice(): number | null {
+    const bestBid = this.getBestBid();
+    const bestAsk = this.getBestAsk();
+    
+    if (bestBid === null || bestAsk === null) return null;
+    return (bestBid + bestAsk) / 2;
+  }
+
+  /**
+   * 获取指定价位的数量
+   */
+  getQuantity(price: number, side: 'bid' | 'ask'): number {
+    const map = side === 'bid' ? this.bids : this.asks;
+    return map.get(price) || 0;
+  }
+
+  /**
+   * 获取指定深度内的总数量
+   */
+  getTotalQuantity(depth: number, side: 'bid' | 'ask'): number {
+    const map = side === 'bid' ? this.bids : this.asks;
+    const prices = Array.from(map.keys()).sort(side === 'bid' ? (a, b) => b - a : (a, b) => a - b);
+    
+    let total = 0;
+    for (let i = 0; i < Math.min(depth, prices.length); i++) {
+      total += map.get(prices[i]) || 0;
+    }
+    
+    return total;
+  }
+}

+ 282 - 0
src/dex/aster/orderBookManager.ts

@@ -0,0 +1,282 @@
+import { EventEmitter } from 'events';
+import { AsterOrderBook, OrderBookSnapshot, DepthUpdate } from './orderBook';
+import { AsterWsClient, AsterWsConfig, AsterStreamArg } from './wsClient';
+
+export interface OrderBookManagerEvents {
+  update: (symbol: string, orderBook: OrderBookSnapshot) => void;
+  error: (symbol: string, error: Error) => void;
+  reconnect: (symbol: string) => void;
+}
+
+export class AsterOrderBookManager extends EventEmitter {
+  private orderBooks: Map<string, AsterOrderBook> = new Map();
+  private wsClient: AsterWsClient;
+  private httpBase: string;
+  private isConnected: boolean = false;
+
+  constructor(wsConfig: AsterWsConfig, httpBase: string = 'https://fapi.asterdex.com') {
+    super();
+    this.httpBase = httpBase;
+    this.wsClient = new AsterWsClient(wsConfig);
+    this.wsClient.useCombinedStream(); // 使用组合流格式
+    this.setupWebSocketHandlers();
+  }
+
+  /**
+   * 设置 WebSocket 事件处理器
+   */
+  private setupWebSocketHandlers(): void {
+    this.wsClient.on('open', () => {
+      console.log('🔌 OrderBook Manager WebSocket 连接成功');
+      this.isConnected = true;
+    });
+
+    this.wsClient.on('close', () => {
+      console.log('🔌 OrderBook Manager WebSocket 连接断开');
+      this.isConnected = false;
+    });
+
+    this.wsClient.on('error', (error) => {
+      console.error('❌ OrderBook Manager WebSocket 错误:', error);
+    });
+
+    this.wsClient.on('raw', (message) => {
+      this.handleWebSocketMessage(message);
+    });
+  }
+
+  /**
+   * 处理 WebSocket 消息
+   */
+  private handleWebSocketMessage(message: any): void {
+    // 处理组合流格式:{"stream":"<streamName>","data":<rawPayload>}
+    if (message.stream && message.data) {
+      const streamName = message.stream;
+      const data = message.data;
+      
+      if (streamName.includes('@depth')) {
+        const update: DepthUpdate = {
+          symbol: data.s,
+          firstUpdateId: data.U,
+          finalUpdateId: data.u,
+          previousFinalUpdateId: data.pu,
+          bids: data.b || [],
+          asks: data.a || [],
+          timestamp: data.E || Date.now()
+        };
+
+        this.updateOrderBook(update);
+      }
+    }
+    // 处理原始流格式
+    else if (message.e === 'depthUpdate') {
+      const update: DepthUpdate = {
+        symbol: message.s,
+        firstUpdateId: message.U,
+        finalUpdateId: message.u,
+        previousFinalUpdateId: message.pu,
+        bids: message.b || [],
+        asks: message.a || [],
+        timestamp: message.E || Date.now()
+      };
+
+      this.updateOrderBook(update);
+    }
+  }
+
+  /**
+   * 添加交易对到 OrderBook 管理
+   */
+  async addSymbol(symbol: string): Promise<void> {
+    const normalizedSymbol = symbol.toUpperCase();
+    
+    if (this.orderBooks.has(normalizedSymbol)) {
+      console.log(`📊 ${normalizedSymbol} OrderBook 已存在`);
+      return;
+    }
+
+    console.log(`📊 添加 ${normalizedSymbol} OrderBook...`);
+
+    // 创建 OrderBook 实例
+    const orderBook = new AsterOrderBook(normalizedSymbol, this.httpBase);
+    
+    // 设置事件监听
+    orderBook.on('update', (snapshot) => {
+      this.emit('update', normalizedSymbol, snapshot);
+    });
+    
+    orderBook.on('error', (error) => {
+      this.emit('error', normalizedSymbol, error);
+    });
+    
+    orderBook.on('reconnect', () => {
+      this.emit('reconnect', normalizedSymbol);
+    });
+
+    // 初始化 OrderBook
+    try {
+      await orderBook.initialize();
+      this.orderBooks.set(normalizedSymbol, orderBook);
+      
+      // 订阅 WebSocket 深度数据
+      if (this.isConnected) {
+        this.subscribeDepth(normalizedSymbol);
+      }
+      
+      console.log(`✅ ${normalizedSymbol} OrderBook 添加成功`);
+    } catch (error) {
+      console.error(`❌ ${normalizedSymbol} OrderBook 初始化失败:`, error);
+      throw error;
+    }
+  }
+
+  /**
+   * 移除交易对
+   */
+  removeSymbol(symbol: string): void {
+    const normalizedSymbol = symbol.toUpperCase();
+    
+    if (!this.orderBooks.has(normalizedSymbol)) {
+      console.log(`📊 ${normalizedSymbol} OrderBook 不存在`);
+      return;
+    }
+
+    // 取消订阅
+    if (this.isConnected) {
+      this.unsubscribeDepth(normalizedSymbol);
+    }
+
+    // 移除 OrderBook
+    this.orderBooks.delete(normalizedSymbol);
+    console.log(`🗑️ ${normalizedSymbol} OrderBook 已移除`);
+  }
+
+  /**
+   * 订阅深度数据
+   */
+  private subscribeDepth(symbol: string): void {
+    const normalizedSymbol = symbol.toLowerCase();
+    const subscription: AsterStreamArg = {
+      channel: 'depth',
+      symbol: normalizedSymbol
+    };
+
+    this.wsClient.subscribe(subscription);
+    console.log(`📡 订阅 ${symbol} 深度数据`);
+  }
+
+  /**
+   * 取消订阅深度数据
+   */
+  private unsubscribeDepth(symbol: string): void {
+    const normalizedSymbol = symbol.toLowerCase();
+    const subscription: AsterStreamArg = {
+      channel: 'depth',
+      symbol: normalizedSymbol
+    };
+
+    this.wsClient.unsubscribe(subscription);
+    console.log(`📡 取消订阅 ${symbol} 深度数据`);
+  }
+
+  /**
+   * 更新 OrderBook
+   */
+  private updateOrderBook(update: DepthUpdate): void {
+    const orderBook = this.orderBooks.get(update.symbol);
+    if (orderBook) {
+      orderBook.updateDepth(update);
+    }
+  }
+
+  /**
+   * 连接 WebSocket
+   */
+  connect(): void {
+    this.wsClient.connect();
+  }
+
+  /**
+   * 断开 WebSocket 连接
+   */
+  disconnect(): void {
+    this.wsClient.disconnect();
+    this.isConnected = false;
+  }
+
+  /**
+   * 获取 OrderBook 快照
+   */
+  getOrderBook(symbol: string): OrderBookSnapshot | null {
+    const orderBook = this.orderBooks.get(symbol.toUpperCase());
+    return orderBook ? orderBook.getSnapshot() : null;
+  }
+
+  /**
+   * 获取最佳买价
+   */
+  getBestBid(symbol: string): number | null {
+    const orderBook = this.orderBooks.get(symbol.toUpperCase());
+    return orderBook ? orderBook.getBestBid() : null;
+  }
+
+  /**
+   * 获取最佳卖价
+   */
+  getBestAsk(symbol: string): number | null {
+    const orderBook = this.orderBooks.get(symbol.toUpperCase());
+    return orderBook ? orderBook.getBestAsk() : null;
+  }
+
+  /**
+   * 获取买卖价差
+   */
+  getSpread(symbol: string): number | null {
+    const orderBook = this.orderBooks.get(symbol.toUpperCase());
+    return orderBook ? orderBook.getSpread() : null;
+  }
+
+  /**
+   * 获取中间价
+   */
+  getMidPrice(symbol: string): number | null {
+    const orderBook = this.orderBooks.get(symbol.toUpperCase());
+    return orderBook ? orderBook.getMidPrice() : null;
+  }
+
+  /**
+   * 获取所有交易对
+   */
+  getSymbols(): string[] {
+    return Array.from(this.orderBooks.keys());
+  }
+
+  /**
+   * 获取 OrderBook 统计信息
+   */
+  getStats(symbol: string): {
+    bidLevels: number;
+    askLevels: number;
+    totalBidQuantity: number;
+    totalAskQuantity: number;
+    bestBid: number | null;
+    bestAsk: number | null;
+    spread: number | null;
+    midPrice: number | null;
+  } | null {
+    const orderBook = this.orderBooks.get(symbol.toUpperCase());
+    if (!orderBook) return null;
+
+    const snapshot = orderBook.getSnapshot();
+    return {
+      bidLevels: snapshot.bids.length,
+      askLevels: snapshot.asks.length,
+      totalBidQuantity: snapshot.bids.reduce((sum, level) => sum + level.quantity, 0),
+      totalAskQuantity: snapshot.asks.reduce((sum, level) => sum + level.quantity, 0),
+      bestBid: orderBook.getBestBid(),
+      bestAsk: orderBook.getBestAsk(),
+      spread: orderBook.getSpread(),
+      midPrice: orderBook.getMidPrice()
+    };
+  }
+}

+ 372 - 0
src/dex/aster/pointSystem.ts

@@ -0,0 +1,372 @@
+// Aster Genesis Stage 2 积分系统类型定义
+
+export interface AsterRhPointConfig {
+  // 交易量积分配置
+  tradingVolume: {
+    enabled: boolean;
+    minVolumeUSD: number;    // 最小交易量(USD)
+    maxVolumeUSD: number;    // 最大交易量(USD)
+    takerMultiplier: number; // Taker 交易倍数(2x)
+    makerMultiplier: number; // Maker 交易倍数(1x)
+    symbols: string[];       // 交易对列表
+  };
+  
+  // 持仓时间积分配置
+  holdingTime: {
+    enabled: boolean;
+    minHoldingMinutes: number; // 最小持仓时间(分钟)
+    maxHoldingMinutes: number; // 最大持仓时间(分钟)
+    pointsCapMultiplier: number; // 积分上限倍数(2x 周交易量)
+    symbols: string[];         // 交易对列表
+  };
+  
+  // Aster 资产持有积分配置
+  asterAssetHolding: {
+    enabled: boolean;
+    minAssetValueUSD: number; // 最小资产价值(USD)
+    maxAssetValueUSD: number; // 最大资产价值(USD)
+    pointsCapMultiplier: number; // 积分上限倍数(2x 周交易量)
+    supportedAssets: string[]; // 支持的 Aster 资产(asBNB, USDF 等)
+  };
+  
+  // 实现盈亏积分配置
+  realizedPnL: {
+    enabled: boolean;
+    targetDailyProfit: number; // 目标日盈利
+    maxDailyLoss: number;      // 最大日亏损
+    profitThreshold: number;   // 盈利阈值
+    lossThreshold: number;     // 亏损阈值
+  };
+  
+  // 推荐和团队积分配置
+  referralTeam: {
+    enabled: boolean;
+    level1Commission: number;  // 一级推荐佣金(10%)
+    level2Commission: number;  // 二级推荐佣金(5%)
+    maxReferralLevels: number; // 最大推荐层级(2)
+  };
+  
+  // 风险控制
+  riskControl: {
+    maxDailyLoss: number;      // 每日最大亏损
+    maxPositionValue: number;  // 最大持仓价值
+    stopLossPercentage: number; // 止损百分比
+    takeProfitPercentage: number; // 止盈百分比
+    maxWeeklyVolume: number;   // 最大周交易量
+  };
+  
+  // 执行配置
+  execution: {
+    enabled: boolean;
+    startTime: string;         // 开始时间 (HH:MM)
+    endTime: string;           // 结束时间 (HH:MM)
+    maxTradesPerDay: number;   // 每日最大交易次数
+    cooldownPeriod: number;    // 冷却期(毫秒)
+    epochStartDay: number;     // 周期开始日(1=周一)
+  };
+}
+
+export interface AsterRhPointStats {
+  // 交易量统计
+  tradingVolumeStats: {
+    weeklyVolumeUSD: number;
+    dailyVolumeUSD: number;
+    takerVolumeUSD: number;
+    makerVolumeUSD: number;
+    totalTrades: number;
+    avgTradeSize: number;
+  };
+  
+  // 持仓时间统计
+  holdingTimeStats: {
+    totalHoldingMinutes: number;
+    avgHoldingMinutes: number;
+    maxHoldingMinutes: number;
+    totalPositions: number;
+    activePositions: number;
+  };
+  
+  // Aster 资产持有统计
+  asterAssetStats: {
+    totalAssetValueUSD: number;
+    asBNBValue: number;
+    USDFValue: number;
+    otherAssetsValue: number;
+    assetDiversification: number;
+  };
+  
+  // 实现盈亏统计
+  realizedPnLStats: {
+    dailyProfit: number;
+    dailyLoss: number;
+    weeklyPnL: number;
+    totalRealizedPnL: number;
+    profitDays: number;
+    lossDays: number;
+  };
+  
+  // 推荐团队统计
+  referralTeamStats: {
+    level1Referrals: number;
+    level2Referrals: number;
+    totalReferralCommission: number;
+    teamBoostMultiplier: number;
+  };
+  
+  // Rh 积分统计
+  rhPointStats: {
+    tradingVolumeScore: number;
+    holdingTimeScore: number;
+    asterAssetScore: number;
+    realizedPnLScore: number;
+    referralCommissionPoints: number;
+    teamBoostPoints: number;
+    totalRhPoints: number;
+    weeklyRank: number;
+  };
+}
+
+export interface AsterRhPointStrategy {
+  name: string;
+  description: string;
+  config: AsterRhPointConfig;
+  stats: AsterRhPointStats;
+  isActive: boolean;
+  startTime?: Date;
+  endTime?: Date;
+  currentEpoch: number;
+  epochStartDate: Date;
+  epochEndDate: Date;
+}
+
+// 积分计算规则
+export interface AsterPointRules {
+  // 交易积分规则
+  tradePointRules: {
+    basePoints: number;      // 基础积分
+    volumeMultiplier: number; // 交易量倍数
+    frequencyBonus: number;  // 频率奖励
+    symbolBonus: { [symbol: string]: number }; // 交易对奖励
+  };
+  
+  // 持仓积分规则
+  positionPointRules: {
+    basePoints: number;      // 基础积分
+    sizeMultiplier: number;  // 持仓大小倍数
+    durationMultiplier: number; // 持仓时间倍数
+    leverageBonus: number;   // 杠杆奖励
+  };
+  
+  // 盈亏积分规则
+  profitLossPointRules: {
+    profitMultiplier: number; // 盈利倍数
+    lossPenalty: number;      // 亏损惩罚
+    riskRewardRatio: number;  // 风险回报比
+  };
+}
+
+// 交易信号
+export interface AsterTradeSignal {
+  symbol: string;
+  side: 'BUY' | 'SELL';
+  type: 'MARKET' | 'LIMIT';
+  quantity: number;
+  price?: number;
+  reason: string;
+  priority: 'HIGH' | 'MEDIUM' | 'LOW';
+  timestamp: Date;
+}
+
+// 持仓信号
+export interface AsterPositionSignal {
+  symbol: string;
+  side: 'LONG' | 'SHORT';
+  size: number;
+  leverage: number;
+  duration: number;
+  reason: string;
+  timestamp: Date;
+}
+
+// 积分事件
+export interface AsterPointEvent {
+  type: 'TRADE' | 'POSITION' | 'PROFIT' | 'LOSS';
+  points: number;
+  description: string;
+  timestamp: Date;
+  metadata?: any;
+}
+
+// 积分策略类型
+export enum PointStrategyType {
+  CONSERVATIVE = 'CONSERVATIVE',  // 保守策略
+  MODERATE = 'MODERATE',          // 中等策略
+  AGGRESSIVE = 'AGGRESSIVE',      // 激进策略
+  CUSTOM = 'CUSTOM'               // 自定义策略
+}
+
+// 预定义 Rh 积分策略配置
+export const PREDEFINED_RH_STRATEGIES: { [key in PointStrategyType]: Partial<AsterRhPointConfig> } = {
+  [PointStrategyType.CONSERVATIVE]: {
+    tradingVolume: {
+      enabled: true,
+      minVolumeUSD: 1000,
+      maxVolumeUSD: 10000,
+      takerMultiplier: 2.0,
+      makerMultiplier: 1.0,
+      symbols: ['BTCUSDT', 'ETHUSDT']
+    },
+    holdingTime: {
+      enabled: true,
+      minHoldingMinutes: 60,
+      maxHoldingMinutes: 1440, // 24小时
+      pointsCapMultiplier: 2.0,
+      symbols: ['BTCUSDT', 'ETHUSDT']
+    },
+    asterAssetHolding: {
+      enabled: true,
+      minAssetValueUSD: 1000,
+      maxAssetValueUSD: 10000,
+      pointsCapMultiplier: 2.0,
+      supportedAssets: ['asBNB', 'USDF']
+    },
+    realizedPnL: {
+      enabled: true,
+      targetDailyProfit: 50,
+      maxDailyLoss: 100,
+      profitThreshold: 20,
+      lossThreshold: 50
+    },
+    referralTeam: {
+      enabled: false,
+      level1Commission: 0.10,
+      level2Commission: 0.05,
+      maxReferralLevels: 2
+    },
+    riskControl: {
+      maxDailyLoss: 200,
+      maxPositionValue: 5000,
+      stopLossPercentage: 0.02,
+      takeProfitPercentage: 0.03,
+      maxWeeklyVolume: 50000
+    },
+    execution: {
+      enabled: true,
+      startTime: '09:00',
+      endTime: '21:00',
+      maxTradesPerDay: 20,
+      cooldownPeriod: 300000, // 5分钟
+      epochStartDay: 1 // 周一
+    }
+  },
+  
+  [PointStrategyType.MODERATE]: {
+    tradingVolume: {
+      enabled: true,
+      minVolumeUSD: 5000,
+      maxVolumeUSD: 50000,
+      takerMultiplier: 2.0,
+      makerMultiplier: 1.0,
+      symbols: ['BTCUSDT', 'ETHUSDT', 'ADAUSDT', 'DOTUSDT']
+    },
+    holdingTime: {
+      enabled: true,
+      minHoldingMinutes: 30,
+      maxHoldingMinutes: 720, // 12小时
+      pointsCapMultiplier: 2.0,
+      symbols: ['BTCUSDT', 'ETHUSDT', 'ADAUSDT', 'DOTUSDT']
+    },
+    asterAssetHolding: {
+      enabled: true,
+      minAssetValueUSD: 5000,
+      maxAssetValueUSD: 50000,
+      pointsCapMultiplier: 2.0,
+      supportedAssets: ['asBNB', 'USDF']
+    },
+    realizedPnL: {
+      enabled: true,
+      targetDailyProfit: 100,
+      maxDailyLoss: 200,
+      profitThreshold: 50,
+      lossThreshold: 100
+    },
+    referralTeam: {
+      enabled: true,
+      level1Commission: 0.10,
+      level2Commission: 0.05,
+      maxReferralLevels: 2
+    },
+    riskControl: {
+      maxDailyLoss: 500,
+      maxPositionValue: 10000,
+      stopLossPercentage: 0.025,
+      takeProfitPercentage: 0.04,
+      maxWeeklyVolume: 200000
+    },
+    execution: {
+      enabled: true,
+      startTime: '08:00',
+      endTime: '22:00',
+      maxTradesPerDay: 50,
+      cooldownPeriod: 180000, // 3分钟
+      epochStartDay: 1 // 周一
+    }
+  },
+  
+  [PointStrategyType.AGGRESSIVE]: {
+    tradingVolume: {
+      enabled: true,
+      minVolumeUSD: 10000,
+      maxVolumeUSD: 100000,
+      takerMultiplier: 2.0,
+      makerMultiplier: 1.0,
+      symbols: ['BTCUSDT', 'ETHUSDT', 'ADAUSDT', 'DOTUSDT', 'LINKUSDT', 'UNIUSDT']
+    },
+    holdingTime: {
+      enabled: true,
+      minHoldingMinutes: 15,
+      maxHoldingMinutes: 360, // 6小时
+      pointsCapMultiplier: 2.0,
+      symbols: ['BTCUSDT', 'ETHUSDT', 'ADAUSDT', 'DOTUSDT', 'LINKUSDT', 'UNIUSDT']
+    },
+    asterAssetHolding: {
+      enabled: true,
+      minAssetValueUSD: 10000,
+      maxAssetValueUSD: 100000,
+      pointsCapMultiplier: 2.0,
+      supportedAssets: ['asBNB', 'USDF']
+    },
+    realizedPnL: {
+      enabled: true,
+      targetDailyProfit: 200,
+      maxDailyLoss: 500,
+      profitThreshold: 100,
+      lossThreshold: 200
+    },
+    referralTeam: {
+      enabled: true,
+      level1Commission: 0.10,
+      level2Commission: 0.05,
+      maxReferralLevels: 2
+    },
+    riskControl: {
+      maxDailyLoss: 1000,
+      maxPositionValue: 20000,
+      stopLossPercentage: 0.03,
+      takeProfitPercentage: 0.05,
+      maxWeeklyVolume: 500000
+    },
+    execution: {
+      enabled: true,
+      startTime: '00:00',
+      endTime: '23:59',
+      maxTradesPerDay: 100,
+      cooldownPeriod: 60000, // 1分钟
+      epochStartDay: 1 // 周一
+    }
+  },
+  
+  [PointStrategyType.CUSTOM]: {
+    // 自定义策略需要用户配置
+  }
+};

+ 733 - 0
src/dex/aster/rhPointStrategyManager.ts

@@ -0,0 +1,733 @@
+import { EventEmitter } from 'events';
+import { AsterTradingClient } from './tradingClient';
+import { AsterUserStreamClient } from './userStreamClient';
+import { AsterOrderBookManager } from './orderBookManager';
+import {
+  AsterRhPointConfig,
+  AsterRhPointStats,
+  AsterRhPointStrategy,
+  PointStrategyType,
+  PREDEFINED_RH_STRATEGIES,
+  AsterTradeSignal,
+  AsterPositionSignal,
+  AsterPointEvent
+} from './pointSystem';
+
+export interface RhPointStrategyManagerEvents {
+  tradeSignal: (signal: AsterTradeSignal) => void;
+  positionSignal: (signal: AsterPositionSignal) => void;
+  pointEvent: (event: AsterPointEvent) => void;
+  statsUpdate: (stats: AsterRhPointStats) => void;
+  error: (error: Error) => void;
+}
+
+export class RhPointStrategyManager extends EventEmitter {
+  private strategy: AsterRhPointStrategy;
+  private tradingClient: AsterTradingClient;
+  private userStreamClient: AsterUserStreamClient;
+  private orderBookManager: AsterOrderBookManager;
+  private isRunning: boolean = false;
+  private executionTimer: NodeJS.Timeout | null = null;
+  private statsTimer: NodeJS.Timeout | null = null;
+  private currentPositions: Map<string, any> = new Map();
+  private dailyStats: any = {};
+
+  constructor(
+    strategyType: PointStrategyType,
+    tradingClient: AsterTradingClient,
+    userStreamClient: AsterUserStreamClient,
+    orderBookManager: AsterOrderBookManager
+  ) {
+    super();
+    
+    this.tradingClient = tradingClient;
+    this.userStreamClient = userStreamClient;
+    this.orderBookManager = orderBookManager;
+    
+    // 初始化策略
+    this.strategy = this.createStrategy(strategyType);
+    
+    // 设置事件监听
+    this.setupEventListeners();
+  }
+
+  /**
+   * 创建策略实例
+   */
+  private createStrategy(strategyType: PointStrategyType): AsterRhPointStrategy {
+    const config = PREDEFINED_RH_STRATEGIES[strategyType];
+    const currentEpoch = this.getCurrentEpoch();
+    const epochDates = this.getEpochDates(currentEpoch);
+    
+    return {
+      name: `${strategyType} Rh Point Strategy`,
+      description: `Aster Genesis Stage 2 ${strategyType} strategy for maximizing Rh points`,
+      config: config as AsterRhPointConfig,
+      stats: this.createEmptyStats(),
+      isActive: false,
+      currentEpoch,
+      epochStartDate: epochDates.start,
+      epochEndDate: epochDates.end
+    };
+  }
+
+  /**
+   * 创建空统计
+   */
+  private createEmptyStats(): AsterRhPointStats {
+    return {
+      tradingVolumeStats: {
+        weeklyVolumeUSD: 0,
+        dailyVolumeUSD: 0,
+        takerVolumeUSD: 0,
+        makerVolumeUSD: 0,
+        totalTrades: 0,
+        avgTradeSize: 0
+      },
+      holdingTimeStats: {
+        totalHoldingMinutes: 0,
+        avgHoldingMinutes: 0,
+        maxHoldingMinutes: 0,
+        totalPositions: 0,
+        activePositions: 0
+      },
+      asterAssetStats: {
+        totalAssetValueUSD: 0,
+        asBNBValue: 0,
+        USDFValue: 0,
+        otherAssetsValue: 0,
+        assetDiversification: 0
+      },
+      realizedPnLStats: {
+        dailyProfit: 0,
+        dailyLoss: 0,
+        weeklyPnL: 0,
+        totalRealizedPnL: 0,
+        profitDays: 0,
+        lossDays: 0
+      },
+      referralTeamStats: {
+        level1Referrals: 0,
+        level2Referrals: 0,
+        totalReferralCommission: 0,
+        teamBoostMultiplier: 1.0
+      },
+      rhPointStats: {
+        tradingVolumeScore: 0,
+        holdingTimeScore: 0,
+        asterAssetScore: 0,
+        realizedPnLScore: 0,
+        referralCommissionPoints: 0,
+        teamBoostPoints: 0,
+        totalRhPoints: 0,
+        weeklyRank: 0
+      }
+    };
+  }
+
+  /**
+   * 设置事件监听
+   */
+  private setupEventListeners(): void {
+    // 监听用户数据流事件
+    this.userStreamClient.on('accountUpdate', (event) => {
+      this.handleAccountUpdate(event);
+    });
+
+    this.userStreamClient.on('orderTradeUpdate', (event) => {
+      this.handleOrderTradeUpdate(event);
+    });
+
+    // 监听 OrderBook 更新
+    this.orderBookManager.on('update', (symbol, orderBook) => {
+      this.handleOrderBookUpdate(symbol, orderBook);
+    });
+  }
+
+  /**
+   * 启动策略
+   */
+  async start(): Promise<void> {
+    if (this.isRunning) {
+      console.log('⚠️ 策略已在运行中');
+      return;
+    }
+
+    console.log(`🚀 启动 ${this.strategy.name}...`);
+    
+    try {
+      // 初始化统计
+      await this.updateStats();
+      
+      // 启动执行定时器
+      this.startExecutionTimer();
+      
+      // 启动统计更新定时器
+      this.startStatsTimer();
+      
+      this.strategy.isActive = true;
+      this.isRunning = true;
+      
+      console.log('✅ 策略启动成功');
+    } catch (error: any) {
+      console.error('❌ 策略启动失败:', error.message);
+      this.emit('error', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 停止策略
+   */
+  stop(): void {
+    if (!this.isRunning) {
+      console.log('⚠️ 策略未在运行');
+      return;
+    }
+
+    console.log('⏹️ 停止策略...');
+    
+    this.stopExecutionTimer();
+    this.stopStatsTimer();
+    
+    this.strategy.isActive = false;
+    this.isRunning = false;
+    
+    console.log('✅ 策略已停止');
+  }
+
+  /**
+   * 启动执行定时器
+   */
+  private startExecutionTimer(): void {
+    this.stopExecutionTimer();
+    
+    const interval = this.strategy.config.execution.cooldownPeriod;
+    this.executionTimer = setInterval(() => {
+      this.executeStrategy();
+    }, interval);
+    
+    console.log(`⏰ 执行定时器已启动,间隔: ${interval / 1000} 秒`);
+  }
+
+  /**
+   * 停止执行定时器
+   */
+  private stopExecutionTimer(): void {
+    if (this.executionTimer) {
+      clearInterval(this.executionTimer);
+      this.executionTimer = null;
+    }
+  }
+
+  /**
+   * 启动统计定时器
+   */
+  private startStatsTimer(): void {
+    this.stopStatsTimer();
+    
+    // 每5分钟更新一次统计
+    this.statsTimer = setInterval(async () => {
+      await this.updateStats();
+    }, 5 * 60 * 1000);
+    
+    console.log('📊 统计定时器已启动,间隔: 5 分钟');
+  }
+
+  /**
+   * 停止统计定时器
+   */
+  private stopStatsTimer(): void {
+    if (this.statsTimer) {
+      clearInterval(this.statsTimer);
+      this.statsTimer = null;
+    }
+  }
+
+  /**
+   * 执行策略
+   */
+  private async executeStrategy(): Promise<void> {
+    if (!this.isRunning || !this.strategy.config.execution.enabled) {
+      return;
+    }
+
+    try {
+      // 检查执行时间
+      if (!this.isExecutionTime()) {
+        return;
+      }
+
+      // 生成交易信号
+      const tradeSignals = await this.generateTradeSignals();
+      
+      // 生成持仓信号
+      const positionSignals = await this.generatePositionSignals();
+      
+      // 执行交易信号
+      for (const signal of tradeSignals) {
+        await this.executeTradeSignal(signal);
+      }
+      
+      // 执行持仓信号
+      for (const signal of positionSignals) {
+        await this.executePositionSignal(signal);
+      }
+      
+    } catch (error: any) {
+      console.error('❌ 策略执行失败:', error.message);
+      this.emit('error', error);
+    }
+  }
+
+  /**
+   * 生成交易信号
+   */
+  private async generateTradeSignals(): Promise<AsterTradeSignal[]> {
+    const signals: AsterTradeSignal[] = [];
+    
+    if (!this.strategy.config.tradingVolume.enabled) {
+      return signals;
+    }
+
+    // 检查是否达到每日交易限制
+    if (this.dailyStats.trades >= this.strategy.config.execution.maxTradesPerDay) {
+      return signals;
+    }
+
+    // 为每个交易对生成信号
+    for (const symbol of this.strategy.config.tradingVolume.symbols) {
+      const orderBook = this.orderBookManager.getOrderBook(symbol);
+      if (!orderBook) continue;
+
+      const spread = this.orderBookManager.getSpread(symbol);
+      const midPrice = this.orderBookManager.getMidPrice(symbol);
+      
+      if (!spread || !midPrice) continue;
+
+      // 生成 Taker 交易信号(优先,因为积分倍数更高)
+      if (this.shouldGenerateTakerSignal(symbol, spread, midPrice)) {
+        signals.push({
+          symbol,
+          side: Math.random() > 0.5 ? 'BUY' : 'SELL',
+          type: 'MARKET',
+          quantity: this.calculateTradeQuantity(symbol, midPrice),
+          reason: 'Taker trade for volume points',
+          priority: 'HIGH',
+          timestamp: new Date()
+        });
+      }
+    }
+
+    return signals;
+  }
+
+  /**
+   * 生成持仓信号
+   */
+  private async generatePositionSignals(): Promise<AsterPositionSignal[]> {
+    const signals: AsterPositionSignal[] = [];
+    
+    if (!this.strategy.config.holdingTime.enabled) {
+      return signals;
+    }
+
+    // 检查当前持仓数量
+    if (this.currentPositions.size >= this.strategy.config.tradingVolume.symbols.length) {
+      return signals;
+    }
+
+    // 为每个交易对生成持仓信号
+    for (const symbol of this.strategy.config.holdingTime.symbols) {
+      if (this.currentPositions.has(symbol)) continue;
+
+      const orderBook = this.orderBookManager.getOrderBook(symbol);
+      if (!orderBook) continue;
+
+      const midPrice = this.orderBookManager.getMidPrice(symbol);
+      if (!midPrice) continue;
+
+      signals.push({
+        symbol,
+        side: Math.random() > 0.5 ? 'LONG' : 'SHORT',
+        size: this.calculatePositionSize(symbol, midPrice),
+        leverage: 10, // 默认杠杆
+        duration: this.strategy.config.holdingTime.minHoldingMinutes * 60 * 1000,
+        reason: 'Position holding for time points',
+        timestamp: new Date()
+      });
+    }
+
+    return signals;
+  }
+
+  /**
+   * 执行交易信号
+   */
+  private async executeTradeSignal(signal: AsterTradeSignal): Promise<void> {
+    try {
+      console.log(`📋 执行交易信号: ${signal.symbol} ${signal.side} ${signal.type}`);
+      
+      let order;
+      if (signal.type === 'MARKET') {
+        if (signal.side === 'BUY') {
+          order = await this.tradingClient.marketBuy(signal.symbol, signal.quantity.toString());
+        } else {
+          order = await this.tradingClient.marketSell(signal.symbol, signal.quantity.toString());
+        }
+      } else {
+        if (signal.side === 'BUY') {
+          order = await this.tradingClient.limitBuy(signal.symbol, signal.quantity.toString(), signal.price!.toString());
+        } else {
+          order = await this.tradingClient.limitSell(signal.symbol, signal.quantity.toString(), signal.price!.toString());
+        }
+      }
+
+      // 记录交易
+      this.recordTrade(signal, order);
+      
+      // 发射事件
+      this.emit('tradeSignal', signal);
+      
+      console.log(`✅ 交易执行成功: 订单ID ${order.orderId}`);
+      
+    } catch (error: any) {
+      console.error(`❌ 交易执行失败: ${signal.symbol}`, error.message);
+      this.emit('error', error);
+    }
+  }
+
+  /**
+   * 执行持仓信号
+   */
+  private async executePositionSignal(signal: AsterPositionSignal): Promise<void> {
+    try {
+      console.log(`📊 执行持仓信号: ${signal.symbol} ${signal.side} ${signal.size}`);
+      
+      // 记录持仓
+      this.currentPositions.set(signal.symbol, {
+        ...signal,
+        startTime: new Date(),
+        status: 'ACTIVE'
+      });
+      
+      // 发射事件
+      this.emit('positionSignal', signal);
+      
+      console.log(`✅ 持仓创建成功: ${signal.symbol}`);
+      
+    } catch (error: any) {
+      console.error(`❌ 持仓创建失败: ${signal.symbol}`, error.message);
+      this.emit('error', error);
+    }
+  }
+
+  /**
+   * 处理账户更新
+   */
+  private handleAccountUpdate(event: any): void {
+    // 更新持仓信息
+    if (event.a.P) {
+      for (const position of event.a.P) {
+        if (parseFloat(position.pa) !== 0) {
+          this.updatePosition(position);
+        }
+      }
+    }
+    
+    // 更新余额信息
+    if (event.a.B) {
+      for (const balance of event.a.B) {
+        this.updateBalance(balance);
+      }
+    }
+  }
+
+  /**
+   * 处理订单交易更新
+   */
+  private handleOrderTradeUpdate(event: any): void {
+    const order = event.o;
+    
+    // 记录交易统计
+    if (order.x === 'TRADE') {
+      this.recordTradeExecution(order);
+    }
+    
+    // 更新持仓
+    if (order.x === 'TRADE' && parseFloat(order.z) > 0) {
+      this.updatePositionFromTrade(order);
+    }
+  }
+
+  /**
+   * 处理 OrderBook 更新
+   */
+  private handleOrderBookUpdate(symbol: string, orderBook: any): void {
+    // 可以基于 OrderBook 变化生成交易信号
+    // 这里可以根据具体策略逻辑实现
+  }
+
+  /**
+   * 更新统计
+   */
+  private async updateStats(): Promise<void> {
+    try {
+      // 获取账户信息
+      const accountInfo = await this.tradingClient.getAccountInfo({});
+      
+      // 获取持仓风险
+      const positionRisk = await this.tradingClient.getPositionRisk({});
+      
+      // 更新统计
+      this.updateTradingVolumeStats(accountInfo);
+      this.updateHoldingTimeStats(positionRisk);
+      this.updateAsterAssetStats(accountInfo);
+      this.updateRealizedPnLStats(accountInfo);
+      
+      // 计算 Rh 积分
+      this.calculateRhPoints();
+      
+      // 发射统计更新事件
+      this.emit('statsUpdate', this.strategy.stats);
+      
+    } catch (error: any) {
+      console.error('❌ 更新统计失败:', error.message);
+      this.emit('error', error);
+    }
+  }
+
+  /**
+   * 计算 Rh 积分
+   */
+  private calculateRhPoints(): void {
+    const stats = this.strategy.stats;
+    const config = this.strategy.config;
+    
+    // 交易量积分
+    if (config.tradingVolume.enabled) {
+      stats.rhPointStats.tradingVolumeScore = 
+        stats.tradingVolumeStats.takerVolumeUSD * config.tradingVolume.takerMultiplier +
+        stats.tradingVolumeStats.makerVolumeUSD * config.tradingVolume.makerMultiplier;
+    }
+    
+    // 持仓时间积分
+    if (config.holdingTime.enabled) {
+      stats.rhPointStats.holdingTimeScore = 
+        Math.min(
+          stats.holdingTimeStats.totalHoldingMinutes * 0.1,
+          stats.tradingVolumeStats.weeklyVolumeUSD * config.holdingTime.pointsCapMultiplier
+        );
+    }
+    
+    // Aster 资产积分
+    if (config.asterAssetHolding.enabled) {
+      stats.rhPointStats.asterAssetScore = 
+        Math.min(
+          stats.asterAssetStats.totalAssetValueUSD * 0.01,
+          stats.tradingVolumeStats.weeklyVolumeUSD * config.asterAssetHolding.pointsCapMultiplier
+        );
+    }
+    
+    // 实现盈亏积分
+    if (config.realizedPnL.enabled) {
+      stats.rhPointStats.realizedPnLScore = stats.realizedPnLStats.totalRealizedPnL;
+    }
+    
+    // 推荐佣金积分
+    if (config.referralTeam.enabled) {
+      stats.rhPointStats.referralCommissionPoints = stats.referralTeamStats.totalReferralCommission;
+    }
+    
+    // 计算总积分
+    stats.rhPointStats.totalRhPoints = 
+      stats.rhPointStats.tradingVolumeScore +
+      stats.rhPointStats.holdingTimeScore +
+      stats.rhPointStats.asterAssetScore +
+      stats.rhPointStats.realizedPnLScore +
+      stats.rhPointStats.referralCommissionPoints +
+      stats.rhPointStats.teamBoostPoints;
+  }
+
+  /**
+   * 获取当前周期
+   */
+  private getCurrentEpoch(): number {
+    const startDate = new Date('2024-09-22'); // Stage 2 开始日期
+    const now = new Date();
+    const diffTime = now.getTime() - startDate.getTime();
+    const diffWeeks = Math.floor(diffTime / (7 * 24 * 60 * 60 * 1000));
+    return diffWeeks + 1;
+  }
+
+  /**
+   * 获取周期日期
+   */
+  private getEpochDates(epoch: number): { start: Date; end: Date } {
+    const startDate = new Date('2024-09-22');
+    const start = new Date(startDate.getTime() + (epoch - 1) * 7 * 24 * 60 * 60 * 1000);
+    const end = new Date(start.getTime() + 7 * 24 * 60 * 60 * 1000 - 1);
+    return { start, end };
+  }
+
+  /**
+   * 检查是否在执行时间
+   */
+  private isExecutionTime(): boolean {
+    const now = new Date();
+    const currentTime = now.getHours() * 60 + now.getMinutes();
+    const startTime = this.parseTime(this.strategy.config.execution.startTime);
+    const endTime = this.parseTime(this.strategy.config.execution.endTime);
+    
+    return currentTime >= startTime && currentTime <= endTime;
+  }
+
+  /**
+   * 解析时间字符串
+   */
+  private parseTime(timeStr: string): number {
+    const [hours, minutes] = timeStr.split(':').map(Number);
+    return hours * 60 + minutes;
+  }
+
+  /**
+   * 判断是否应该生成 Taker 信号
+   */
+  private shouldGenerateTakerSignal(symbol: string, spread: number, midPrice: number): boolean {
+    const spreadPercentage = spread / midPrice;
+    return spreadPercentage < 0.001; // 价差小于 0.1% 时生成信号
+  }
+
+  /**
+   * 计算交易数量
+   */
+  private calculateTradeQuantity(symbol: string, price: number): number {
+    const minVolume = this.strategy.config.tradingVolume.minVolumeUSD;
+    const maxVolume = this.strategy.config.tradingVolume.maxVolumeUSD;
+    const volume = minVolume + Math.random() * (maxVolume - minVolume);
+    return volume / price;
+  }
+
+  /**
+   * 计算持仓大小
+   */
+  private calculatePositionSize(symbol: string, price: number): number {
+    const minValue = this.strategy.config.riskControl.maxPositionValue * 0.1;
+    const maxValue = this.strategy.config.riskControl.maxPositionValue * 0.5;
+    const value = minValue + Math.random() * (maxValue - minValue);
+    return value / price;
+  }
+
+  /**
+   * 记录交易
+   */
+  private recordTrade(signal: AsterTradeSignal, order: any): void {
+    this.dailyStats.trades = (this.dailyStats.trades || 0) + 1;
+    this.dailyStats.volume = (this.dailyStats.volume || 0) + parseFloat(order.cumQuote);
+  }
+
+  /**
+   * 记录交易执行
+   */
+  private recordTradeExecution(order: any): void {
+    // 更新交易量统计
+    this.strategy.stats.tradingVolumeStats.totalTrades++;
+    this.strategy.stats.tradingVolumeStats.dailyVolumeUSD += parseFloat(order.cumQuote);
+    
+    // 判断是 Taker 还是 Maker
+    if (order.m) { // 是挂单方
+      this.strategy.stats.tradingVolumeStats.makerVolumeUSD += parseFloat(order.cumQuote);
+    } else { // 是吃单方
+      this.strategy.stats.tradingVolumeStats.takerVolumeUSD += parseFloat(order.cumQuote);
+    }
+  }
+
+  /**
+   * 更新持仓
+   */
+  private updatePosition(position: any): void {
+    // 更新持仓统计
+    if (parseFloat(position.pa) !== 0) {
+      this.strategy.stats.holdingTimeStats.activePositions++;
+    }
+  }
+
+  /**
+   * 从交易更新持仓
+   */
+  private updatePositionFromTrade(order: any): void {
+    // 根据交易更新持仓信息
+    // 这里可以根据具体需求实现
+  }
+
+  /**
+   * 更新余额
+   */
+  private updateBalance(balance: any): void {
+    // 更新 Aster 资产统计
+    if (balance.a === 'asBNB') {
+      this.strategy.stats.asterAssetStats.asBNBValue = parseFloat(balance.wb);
+    } else if (balance.a === 'USDF') {
+      this.strategy.stats.asterAssetStats.USDFValue = parseFloat(balance.wb);
+    }
+    
+    this.strategy.stats.asterAssetStats.totalAssetValueUSD = 
+      this.strategy.stats.asterAssetStats.asBNBValue + 
+      this.strategy.stats.asterAssetStats.USDFValue;
+  }
+
+  /**
+   * 更新交易量统计
+   */
+  private updateTradingVolumeStats(accountInfo: any): void {
+    // 从账户信息更新交易量统计
+    // 这里可以根据具体需求实现
+  }
+
+  /**
+   * 更新持仓时间统计
+   */
+  private updateHoldingTimeStats(positionRisk: any): void {
+    // 从持仓风险更新持仓时间统计
+    // 这里可以根据具体需求实现
+  }
+
+  /**
+   * 更新 Aster 资产统计
+   */
+  private updateAsterAssetStats(accountInfo: any): void {
+    // 从账户信息更新 Aster 资产统计
+    // 这里可以根据具体需求实现
+  }
+
+  /**
+   * 更新实现盈亏统计
+   */
+  private updateRealizedPnLStats(accountInfo: any): void {
+    // 从账户信息更新实现盈亏统计
+    // 这里可以根据具体需求实现
+  }
+
+  /**
+   * 获取策略状态
+   */
+  getStrategy(): AsterRhPointStrategy {
+    return { ...this.strategy };
+  }
+
+  /**
+   * 获取统计信息
+   */
+  getStats(): AsterRhPointStats {
+    return { ...this.strategy.stats };
+  }
+
+  /**
+   * 更新配置
+   */
+  updateConfig(newConfig: Partial<AsterRhPointConfig>): void {
+    this.strategy.config = { ...this.strategy.config, ...newConfig };
+  }
+}
+
+

+ 144 - 0
src/dex/aster/signatureGenerator.ts

@@ -0,0 +1,144 @@
+import { ethers } from 'ethers';
+
+export interface SignatureParams {
+  user: string;
+  signer: string;
+  privateKey: string;
+  nonce: number;
+  timestamp: number;
+  businessParams: Record<string, any>;
+}
+
+export class AsterSignatureGenerator {
+  /**
+   * 生成 Aster API 签名
+   */
+  static async generateSignature(params: SignatureParams): Promise<string> {
+    const { user, signer, privateKey, nonce, timestamp, businessParams } = params;
+
+    // 1. 处理业务参数
+    const normalizedParams = this.normalizeBusinessParams(businessParams);
+    
+    // 2. 添加 recvWindow 和 timestamp
+    normalizedParams.recvWindow = '50000';
+    normalizedParams.timestamp = timestamp.toString();
+
+    // 3. 生成稳定的 JSON 字符串
+    const jsonString = this.stableJSONString(normalizedParams);
+
+    // 4. ABI 编码
+    const encoded = this.abiEncode(jsonString, user, signer, nonce);
+
+    // 5. Keccak 哈希
+    const hash = ethers.keccak256(encoded);
+
+    // 6. EIP-191 签名
+    const signature = await this.signMessage(hash, privateKey);
+
+    return signature;
+  }
+
+  /**
+   * 标准化业务参数
+   */
+  private static normalizeBusinessParams(params: Record<string, any>): Record<string, string> {
+    const normalized: Record<string, string> = {};
+
+    for (const [key, value] of Object.entries(params)) {
+      if (value === null || value === undefined) {
+        continue; // 跳过空值
+      }
+
+      if (Array.isArray(value)) {
+        // 处理数组
+        const newValue = [];
+        for (const item of value) {
+          if (typeof item === 'object' && item !== null) {
+            newValue.push(JSON.stringify(this.normalizeBusinessParams(item)));
+          } else {
+            newValue.push(String(item));
+          }
+        }
+        normalized[key] = JSON.stringify(newValue);
+      } else if (typeof value === 'object' && value !== null) {
+        // 处理对象
+        normalized[key] = JSON.stringify(this.normalizeBusinessParams(value));
+      } else {
+        // 处理基本类型
+        normalized[key] = String(value);
+      }
+    }
+
+    return normalized;
+  }
+
+  /**
+   * 生成稳定的 JSON 字符串
+   */
+  private static stableJSONString(obj: Record<string, string>): string {
+    // 按 ASCII 排序并移除空格和单引号
+    return JSON.stringify(obj, Object.keys(obj).sort())
+      .replace(/\s/g, '')
+      .replace(/'/g, '"');
+  }
+
+  /**
+   * ABI 编码
+   */
+  private static abiEncode(jsonString: string, user: string, signer: string, nonce: number): string {
+    const abiCoder = ethers.AbiCoder.defaultAbiCoder();
+    return abiCoder.encode(
+      ['string', 'address', 'address', 'uint256'],
+      [jsonString, user, signer, nonce]
+    );
+  }
+
+  /**
+   * EIP-191 签名
+   */
+  private static async signMessage(messageHash: string, privateKey: string): Promise<string> {
+    // 创建钱包实例
+    const wallet = new ethers.Wallet(privateKey);
+    
+    // 使用 EIP-191 签名
+    const signature = await wallet.signMessage(ethers.getBytes(messageHash));
+    
+    return signature;
+  }
+
+  /**
+   * 生成 nonce(微秒时间戳)
+   */
+  static generateNonce(): number {
+    return Math.floor(Date.now() * 1000) + Math.floor(Math.random() * 1000);
+  }
+
+  /**
+   * 生成 timestamp(毫秒时间戳)
+   */
+  static generateTimestamp(): number {
+    return Date.now();
+  }
+
+  /**
+   * 验证签名参数
+   */
+  static validateSignatureParams(params: SignatureParams): boolean {
+    if (!params.user || !params.signer || !params.privateKey) {
+      return false;
+    }
+
+    if (!ethers.isAddress(params.user) || !ethers.isAddress(params.signer)) {
+      return false;
+    }
+
+    try {
+      new ethers.Wallet(params.privateKey);
+      return true;
+    } catch {
+      return false;
+    }
+  }
+}
+
+

+ 439 - 0
src/dex/aster/tradingClient.ts

@@ -0,0 +1,439 @@
+import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
+import { HttpsProxyAgent } from 'https-proxy-agent';
+import { AsterSignatureGenerator } from './signatureGenerator';
+import {
+  AsterTradingConfig,
+  AsterOrderRequest,
+  AsterOrderResponse,
+  AsterBatchOrderRequest,
+  AsterBatchOrderResponse,
+  AsterCancelOrderRequest,
+  AsterCancelOrderResponse,
+  AsterQueryOrderRequest,
+  AsterQueryOrderResponse,
+  AsterOpenOrdersRequest,
+  AsterOpenOrdersResponse,
+  AsterAllOrdersRequest,
+  AsterAllOrdersResponse,
+  AsterAccountInfoRequest,
+  AsterAccountInfoResponse,
+  AsterPositionRiskRequest,
+  AsterPositionRiskResponse,
+  AsterApiError,
+  OrderType,
+  OrderSide,
+  PositionSide,
+  TimeInForce,
+  WorkingType
+} from './tradingTypes';
+
+export class AsterTradingClient {
+  private config: AsterTradingConfig;
+  private httpClient: AxiosInstance;
+
+  constructor(config: AsterTradingConfig) {
+    this.config = {
+      recvWindow: 50000,
+      ...config
+    };
+
+    const axiosConfig: any = {
+      baseURL: this.config.baseUrl,
+      timeout: 30000,
+      headers: {
+        'Content-Type': 'application/x-www-form-urlencoded',
+        'User-Agent': 'AsterTradingClient/1.0'
+      }
+    };
+
+    // 代理支持:确保不同账户走不同出口IP
+    if (this.config.proxy) {
+      // 通过 HTTPS 代理隧道
+      const userInfo = this.config.proxy.auth
+        ? `${this.config.proxy.auth.username}:${this.config.proxy.auth.password}@`
+        : '';
+      const proxyUrl = `${this.config.proxy.protocol || 'http'}://${userInfo}${this.config.proxy.host}:${this.config.proxy.port}`;
+      const agent = new HttpsProxyAgent(proxyUrl);
+      axiosConfig.httpAgent = agent;
+      axiosConfig.httpsAgent = agent;
+      // 关闭 axios 内置 proxy 配置,避免与自定义 agent 冲突
+      axiosConfig.proxy = false;
+      if (process.env.ASTER_TRACE_HTTP === '1') {
+        const masked = `${this.config.proxy.protocol || 'http'}://${this.config.proxy.host}:${this.config.proxy.port}`;
+        console.log(`🔌 Proxy enabled: ${masked}`);
+      }
+    }
+
+    this.httpClient = axios.create(axiosConfig);
+
+    // 添加请求拦截器
+    this.httpClient.interceptors.request.use(
+      (config) => {
+        // 降低日志噪声:仅在显式开启时打印详细请求
+        if (process.env.ASTER_TRACE_HTTP === '1') {
+          console.log(`📤 ${config.method?.toUpperCase()} ${config.url}`);
+        }
+        return config;
+      },
+      (error) => {
+        console.error('❌ 请求拦截器错误:', error);
+        return Promise.reject(error);
+      }
+    );
+
+    // 添加响应拦截器
+    this.httpClient.interceptors.response.use(
+      (response) => {
+        if (process.env.ASTER_TRACE_HTTP === '1') {
+          console.log(`📥 ${response.status} ${response.config.url}`);
+        }
+        return response;
+      },
+      (error) => {
+        const data = error?.response?.data;
+        if (process.env.ASTER_TRACE_HTTP === '1') {
+          console.error('❌ 响应拦截器错误:', data || error.message);
+        }
+        return Promise.reject(error);
+      }
+    );
+  }
+
+  /**
+   * 创建带签名的请求参数
+   */
+  private async createSignedParams(businessParams: Record<string, any>): Promise<Record<string, any>> {
+    const nonce = AsterSignatureGenerator.generateNonce();
+    const timestamp = AsterSignatureGenerator.generateTimestamp();
+
+    // 生成签名
+    const signature = await AsterSignatureGenerator.generateSignature({
+      user: this.config.user,
+      signer: this.config.signer,
+      privateKey: this.config.privateKey,
+      nonce,
+      timestamp,
+      businessParams
+    });
+
+    return {
+      ...businessParams,
+      nonce,
+      user: this.config.user,
+      signer: this.config.signer,
+      signature,
+      recvWindow: this.config.recvWindow,
+      timestamp
+    };
+  }
+
+  /**
+   * 下单
+   */
+  async placeOrder(orderRequest: Omit<AsterOrderRequest, 'timestamp'>): Promise<AsterOrderResponse> {
+    try {
+      const signedParams = await this.createSignedParams(orderRequest);
+      
+      const response = await this.httpClient.post('/fapi/v3/order', signedParams);
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`下单失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 下单失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 批量下单
+   */
+  async placeBatchOrders(batchRequest: Omit<AsterBatchOrderRequest, 'timestamp'>): Promise<AsterBatchOrderResponse> {
+    try {
+      const signedParams = await this.createSignedParams(batchRequest);
+      
+      const response = await this.httpClient.post('/fapi/v3/batchOrders', signedParams);
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`批量下单失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      // 处理批量下单响应
+      const results = response.data;
+      const success: AsterOrderResponse[] = [];
+      const failed: Array<{ code: number; msg: string; order: AsterOrderRequest }> = [];
+
+      for (const result of results) {
+        if (result.code) {
+          failed.push({
+            code: result.code,
+            msg: result.msg,
+            order: result.order || {}
+          });
+        } else {
+          success.push(result);
+        }
+      }
+
+      return { success, failed };
+    } catch (error: any) {
+      console.error('❌ 批量下单失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 撤销订单
+   */
+  async cancelOrder(cancelRequest: Omit<AsterCancelOrderRequest, 'timestamp'>): Promise<AsterCancelOrderResponse> {
+    try {
+      const signedParams = await this.createSignedParams(cancelRequest);
+      
+      const response = await this.httpClient.delete('/fapi/v3/order', { data: signedParams });
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`撤单失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 撤单失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 查询订单
+   */
+  async queryOrder(queryRequest: Omit<AsterQueryOrderRequest, 'timestamp'>): Promise<AsterQueryOrderResponse> {
+    try {
+      const signedParams = await this.createSignedParams(queryRequest);
+      
+      const response = await this.httpClient.get('/fapi/v3/order', { params: signedParams });
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`查询订单失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 查询订单失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 查询当前挂单
+   */
+  async getOpenOrders(openOrdersRequest: Omit<AsterOpenOrdersRequest, 'timestamp'>): Promise<AsterOpenOrdersResponse> {
+    try {
+      const signedParams = await this.createSignedParams(openOrdersRequest);
+      
+      const response = await this.httpClient.get('/fapi/v3/openOrders', { params: signedParams });
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`查询挂单失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 查询挂单失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 查询所有订单
+   */
+  async getAllOrders(allOrdersRequest: Omit<AsterAllOrdersRequest, 'timestamp'>): Promise<AsterAllOrdersResponse> {
+    try {
+      const signedParams = await this.createSignedParams(allOrdersRequest);
+      
+      const response = await this.httpClient.get('/fapi/v3/allOrders', { params: signedParams });
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`查询所有订单失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 查询所有订单失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 查询账户信息
+   */
+  async getAccountInfo(accountRequest: Omit<AsterAccountInfoRequest, 'timestamp'>): Promise<AsterAccountInfoResponse> {
+    try {
+      const signedParams = await this.createSignedParams(accountRequest);
+      
+      const response = await this.httpClient.get('/fapi/v3/account', { params: signedParams });
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`查询账户信息失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 查询账户信息失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 查询持仓风险
+   */
+  async getPositionRisk(positionRiskRequest: Omit<AsterPositionRiskRequest, 'timestamp'>): Promise<AsterPositionRiskResponse> {
+    try {
+      const signedParams = await this.createSignedParams(positionRiskRequest);
+      
+      const response = await this.httpClient.get('/fapi/v3/positionRisk', { params: signedParams });
+      
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`查询持仓风险失败: ${response.data.msg} (${response.data.code})`);
+      }
+
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 查询持仓风险失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 设置杠杆(尝试使用签名方案调用)
+   */
+  async setLeverage(symbol: string, leverage: number): Promise<{ symbol: string; leverage: number; maxNotionalValue?: string }> {
+    try {
+      const signedParams = await this.createSignedParams({ symbol, leverage });
+      const response = await this.httpClient.post('/fapi/v3/leverage', signedParams);
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`设置杠杆失败: ${response.data.msg} (${response.data.code})`);
+      }
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 设置杠杆失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 获取杠杆分层(含各层 notionalCap 与 initialLeverage)
+   */
+  async getLeverageBracket(symbol?: string): Promise<any> {
+    try {
+      const params: any = symbol ? { symbol } : {};
+      const signedParams = await this.createSignedParams(params);
+      const response = await this.httpClient.get('/fapi/v3/leverageBracket', { params: signedParams });
+      if (response.data.code && response.data.code !== 200) {
+        throw new Error(`查询杠杆分层失败: ${response.data.msg} (${response.data.code})`);
+      }
+      return response.data;
+    } catch (error: any) {
+      console.error('❌ 查询杠杆分层失败:', error.response?.data || error.message);
+      throw error;
+    }
+  }
+
+  /**
+   * 便捷方法:市价买入
+   */
+  async marketBuy(symbol: string, quantity: string, clientOrderId?: string): Promise<AsterOrderResponse> {
+    return this.placeOrder({
+      symbol,
+      side: OrderSide.BUY,
+      type: OrderType.MARKET,
+      quantity,
+      newClientOrderId: clientOrderId
+    });
+  }
+
+  /**
+   * 便捷方法:市价卖出
+   */
+  async marketSell(symbol: string, quantity: string, clientOrderId?: string): Promise<AsterOrderResponse> {
+    return this.placeOrder({
+      symbol,
+      side: OrderSide.SELL,
+      type: OrderType.MARKET,
+      quantity,
+      newClientOrderId: clientOrderId
+    });
+  }
+
+  /**
+   * 便捷方法:限价买入
+   */
+  async limitBuy(symbol: string, quantity: string, price: string, clientOrderId?: string): Promise<AsterOrderResponse> {
+    return this.placeOrder({
+      symbol,
+      side: OrderSide.BUY,
+      type: OrderType.LIMIT,
+      quantity,
+      price,
+      timeInForce: TimeInForce.GTC,
+      newClientOrderId: clientOrderId
+    });
+  }
+
+  /**
+   * 便捷方法:限价卖出
+   */
+  async limitSell(symbol: string, quantity: string, price: string, clientOrderId?: string): Promise<AsterOrderResponse> {
+    return this.placeOrder({
+      symbol,
+      side: OrderSide.SELL,
+      type: OrderType.LIMIT,
+      quantity,
+      price,
+      timeInForce: TimeInForce.GTC,
+      newClientOrderId: clientOrderId
+    });
+  }
+
+  /**
+   * 便捷方法:止损单
+   */
+  async stopLoss(symbol: string, quantity: string, stopPrice: string, clientOrderId?: string): Promise<AsterOrderResponse> {
+    return this.placeOrder({
+      symbol,
+      side: OrderSide.SELL,
+      type: OrderType.STOP_MARKET,
+      quantity,
+      stopPrice,
+      newClientOrderId: clientOrderId
+    });
+  }
+
+  /**
+   * 便捷方法:止盈单
+   */
+  async takeProfit(symbol: string, quantity: string, stopPrice: string, clientOrderId?: string): Promise<AsterOrderResponse> {
+    return this.placeOrder({
+      symbol,
+      side: OrderSide.SELL,
+      type: OrderType.TAKE_PROFIT_MARKET,
+      quantity,
+      stopPrice,
+      newClientOrderId: clientOrderId
+    });
+  }
+
+  /**
+   * 更新配置
+   */
+  updateConfig(newConfig: Partial<AsterTradingConfig>): void {
+    this.config = { ...this.config, ...newConfig };
+  }
+
+  /**
+   * 获取当前配置
+   */
+  getConfig(): AsterTradingConfig {
+    return { ...this.config };
+  }
+}

+ 134 - 0
src/dex/aster/tradingConfig.ts

@@ -0,0 +1,134 @@
+import { AsterTradingConfig } from './tradingTypes';
+
+export interface AsterTradingConfigEnv {
+  // 用户信息
+  ASTER_ORDER_USER: string;
+  ASTER_ORDER_SIGNER: string;
+  PRIVATE_KEY: string;
+  
+  // API 配置
+  ASTER_HTTP_BASE: string;
+  ASTER_RECV_WINDOW?: string;
+  // 代理(可选)
+  ASTER_PROXY_PROTOCOL?: string; // http | https
+  ASTER_PROXY_HOST?: string;
+  ASTER_PROXY_PORT?: string;
+  ASTER_PROXY_USER?: string;
+  ASTER_PROXY_PASS?: string;
+  // 代理会话(可选,若提供则动态生成密码)
+  ASTER_PROXY_SESSION_PREFIX?: string; // 例如: test123...country-jp,sg_session-
+  ASTER_PROXY_SESSION_SUFFIX?: string; // 例如: _lifetime-59m
+  ASTER_PROXY_SESSION_STATIC?: string; // 固定会话(8位),若提供则使用此值
+}
+
+export function loadAsterTradingConfig(): AsterTradingConfigEnv {
+  const config: AsterTradingConfigEnv = {
+    ASTER_ORDER_USER: process.env.ASTER_ORDER_USER || '',
+    ASTER_ORDER_SIGNER: process.env.ASTER_ORDER_SIGNER || '',
+    PRIVATE_KEY: process.env.PRIVATE_KEY || '',
+    ASTER_HTTP_BASE: process.env.ASTER_HTTP_BASE || 'https://fapi.asterdex.com',
+    ASTER_RECV_WINDOW: process.env.ASTER_RECV_WINDOW || '50000',
+    ASTER_PROXY_PROTOCOL: process.env.ASTER_PROXY_PROTOCOL,
+    ASTER_PROXY_HOST: process.env.ASTER_PROXY_HOST,
+    ASTER_PROXY_PORT: process.env.ASTER_PROXY_PORT,
+    ASTER_PROXY_USER: process.env.ASTER_PROXY_USER,
+    ASTER_PROXY_PASS: process.env.ASTER_PROXY_PASS,
+    ASTER_PROXY_SESSION_PREFIX: process.env.ASTER_PROXY_SESSION_PREFIX,
+    ASTER_PROXY_SESSION_SUFFIX: process.env.ASTER_PROXY_SESSION_SUFFIX,
+    ASTER_PROXY_SESSION_STATIC: process.env.ASTER_PROXY_SESSION_STATIC
+  };
+
+  // 验证必需配置
+  const requiredFields = ['ASTER_ORDER_USER', 'ASTER_ORDER_SIGNER', 'PRIVATE_KEY'];
+  const missingFields = requiredFields.filter(field => !config[field as keyof AsterTradingConfigEnv]);
+  
+  if (missingFields.length > 0) {
+    throw new Error(`缺少必需的配置项: ${missingFields.join(', ')}`);
+  }
+
+  return config;
+}
+
+export function toAsterTradingConfig(envConfig: AsterTradingConfigEnv): AsterTradingConfig {
+  const cfg: AsterTradingConfig = {
+    user: envConfig.ASTER_ORDER_USER,
+    signer: envConfig.ASTER_ORDER_SIGNER,
+    privateKey: envConfig.PRIVATE_KEY,
+    baseUrl: envConfig.ASTER_HTTP_BASE,
+    recvWindow: envConfig.ASTER_RECV_WINDOW ? parseInt(envConfig.ASTER_RECV_WINDOW) : 50000
+  };
+  if (envConfig.ASTER_PROXY_HOST && envConfig.ASTER_PROXY_PORT) {
+    cfg.proxy = {
+      protocol: (envConfig.ASTER_PROXY_PROTOCOL as 'http' | 'https') || 'http',
+      host: envConfig.ASTER_PROXY_HOST,
+      port: parseInt(envConfig.ASTER_PROXY_PORT),
+      auth: envConfig.ASTER_PROXY_USER
+        ? {
+            username: envConfig.ASTER_PROXY_USER,
+            password: resolveProxyPassword(envConfig)
+          }
+        : undefined
+    };
+  }
+  return cfg;
+}
+
+// 读取第二账户(带 ASTER2_ 前缀)
+export interface AsterTradingConfigEnv2 extends AsterTradingConfigEnv {
+  ASTER2_ORDER_USER: string;
+  ASTER2_ORDER_SIGNER: string;
+  PRIVATE_KEY2: string;
+}
+
+export function loadAsterTradingConfig2(): AsterTradingConfigEnv {
+  const config: AsterTradingConfigEnv = {
+    ASTER_ORDER_USER: process.env.ASTER2_ORDER_USER || '',
+    ASTER_ORDER_SIGNER: process.env.ASTER2_ORDER_SIGNER || '',
+    PRIVATE_KEY: process.env.PRIVATE_KEY2 || '',
+    ASTER_HTTP_BASE: process.env.ASTER_HTTP_BASE || 'https://fapi.asterdex.com',
+    ASTER_RECV_WINDOW: process.env.ASTER_RECV_WINDOW || '50000',
+    ASTER_PROXY_PROTOCOL: process.env.ASTER2_PROXY_PROTOCOL,
+    ASTER_PROXY_HOST: process.env.ASTER2_PROXY_HOST,
+    ASTER_PROXY_PORT: process.env.ASTER2_PROXY_PORT,
+    ASTER_PROXY_USER: process.env.ASTER2_PROXY_USER,
+    ASTER_PROXY_PASS: process.env.ASTER2_PROXY_PASS,
+    ASTER_PROXY_SESSION_PREFIX: process.env.ASTER2_PROXY_SESSION_PREFIX,
+    ASTER_PROXY_SESSION_SUFFIX: process.env.ASTER2_PROXY_SESSION_SUFFIX,
+    ASTER_PROXY_SESSION_STATIC: process.env.ASTER2_PROXY_SESSION_STATIC
+  };
+
+  const requiredFields = ['ASTER_ORDER_USER', 'ASTER_ORDER_SIGNER', 'PRIVATE_KEY'];
+  const missingFields = requiredFields.filter(field => !config[field as keyof AsterTradingConfigEnv]);
+  if (missingFields.length > 0) {
+    throw new Error(`缺少第二账户配置项: ${missingFields.join(', ')}`);
+  }
+
+  return config;
+}
+
+// 生成随机会话(8位,字母数字混合)
+function generateSessionToken(length = 8): string {
+  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+  let out = '';
+  for (let i = 0; i < length; i++) {
+    out += chars.charAt(Math.floor(Math.random() * chars.length));
+  }
+  return out;
+}
+
+// 解析/组装代理密码
+function resolveProxyPassword(env: AsterTradingConfigEnv): string | undefined {
+  // 若显式提供完整密码则直接使用
+  if (env.ASTER_PROXY_PASS && env.ASTER_PROXY_PASS.trim().length > 0) {
+    return env.ASTER_PROXY_PASS;
+  }
+  // 否则尝试使用前后缀自动拼装(需要用户名存在才会启用代理)
+  if (env.ASTER_PROXY_SESSION_PREFIX && env.ASTER_PROXY_SESSION_SUFFIX) {
+    const session = (env.ASTER_PROXY_SESSION_STATIC && env.ASTER_PROXY_SESSION_STATIC.length > 0)
+      ? env.ASTER_PROXY_SESSION_STATIC
+      : generateSessionToken(8);
+    return `${env.ASTER_PROXY_SESSION_PREFIX}${session}${env.ASTER_PROXY_SESSION_SUFFIX}`;
+  }
+  // 未配置
+  return undefined;
+}

+ 274 - 0
src/dex/aster/tradingTypes.ts

@@ -0,0 +1,274 @@
+// Aster 交易 API 类型定义
+
+export interface AsterOrderRequest {
+  symbol: string;
+  side: 'BUY' | 'SELL';
+  type: 'LIMIT' | 'MARKET' | 'STOP' | 'TAKE_PROFIT' | 'STOP_MARKET' | 'TAKE_PROFIT_MARKET' | 'TRAILING_STOP_MARKET';
+  positionSide?: 'LONG' | 'SHORT' | 'BOTH';
+  reduceOnly?: boolean;
+  quantity?: string;
+  price?: string;
+  newClientOrderId?: string;
+  stopPrice?: string;
+  closePosition?: boolean;
+  activationPrice?: string;
+  callbackRate?: string;
+  timeInForce?: 'GTC' | 'IOC' | 'FOK' | 'GTX';
+  workingType?: 'MARK_PRICE' | 'CONTRACT_PRICE';
+  priceProtect?: boolean;
+  newOrderRespType?: 'ACK' | 'RESULT';
+  recvWindow?: number;
+  timestamp: number;
+}
+
+export interface AsterOrderResponse {
+  clientOrderId: string;
+  cumQty: string;
+  cumQuote: string;
+  executedQty: string;
+  orderId: number;
+  avgPrice: string;
+  origQty: string;
+  price: string;
+  reduceOnly: boolean;
+  side: string;
+  positionSide: string;
+  status: 'NEW' | 'PARTIALLY_FILLED' | 'FILLED' | 'CANCELED' | 'PENDING_CANCEL' | 'REJECTED' | 'EXPIRED';
+  stopPrice: string;
+  closePosition: boolean;
+  symbol: string;
+  timeInForce: string;
+  type: string;
+  origType: string;
+  activatePrice?: string;
+  priceRate?: string;
+  updateTime: number;
+  workingType: string;
+  priceProtect: boolean;
+}
+
+export interface AsterBatchOrderRequest {
+  batchOrders: AsterOrderRequest[];
+  recvWindow?: number;
+  timestamp: number;
+}
+
+export interface AsterBatchOrderResponse {
+  success: AsterOrderResponse[];
+  failed: Array<{
+    code: number;
+    msg: string;
+    order: AsterOrderRequest;
+  }>;
+}
+
+export interface AsterCancelOrderRequest {
+  symbol: string;
+  orderId?: number;
+  origClientOrderId?: string;
+  newClientOrderId?: string;
+  recvWindow?: number;
+  timestamp: number;
+}
+
+export interface AsterCancelOrderResponse {
+  clientOrderId: string;
+  cumQty: string;
+  cumQuote: string;
+  executedQty: string;
+  orderId: number;
+  origQty: string;
+  price: string;
+  reduceOnly: boolean;
+  side: string;
+  positionSide: string;
+  status: string;
+  symbol: string;
+  timeInForce: string;
+  type: string;
+  origType: string;
+  updateTime: number;
+  workingType: string;
+  priceProtect: boolean;
+}
+
+export interface AsterQueryOrderRequest {
+  symbol: string;
+  orderId?: number;
+  origClientOrderId?: string;
+  recvWindow?: number;
+  timestamp: number;
+}
+
+export interface AsterQueryOrderResponse extends AsterOrderResponse {
+  // 查询订单响应包含所有订单信息
+}
+
+export interface AsterOpenOrdersRequest {
+  symbol?: string;
+  recvWindow?: number;
+  timestamp: number;
+}
+
+export interface AsterOpenOrdersResponse extends Array<AsterOrderResponse> {}
+
+export interface AsterAllOrdersRequest {
+  symbol: string;
+  orderId?: number;
+  startTime?: number;
+  endTime?: number;
+  limit?: number;
+  recvWindow?: number;
+  timestamp: number;
+}
+
+export interface AsterAllOrdersResponse extends Array<AsterOrderResponse> {}
+
+export interface AsterAccountInfoRequest {
+  recvWindow?: number;
+  timestamp: number;
+}
+
+export interface AsterAccountInfoResponse {
+  feeTier: number;
+  canTrade: boolean;
+  canDeposit: boolean;
+  canWithdraw: boolean;
+  updateTime: number;
+  totalInitialMargin: string;
+  totalMaintMargin: string;
+  totalWalletBalance: string;
+  totalUnrealizedProfit: string;
+  totalMarginBalance: string;
+  totalPositionInitialMargin: string;
+  totalOpenOrderInitialMargin: string;
+  totalCrossWalletBalance: string;
+  totalCrossUnPnl: string;
+  availableBalance: string;
+  maxWithdrawAmount: string;
+  assets: Array<{
+    asset: string;
+    walletBalance: string;
+    unrealizedProfit: string;
+    marginBalance: string;
+    maintMargin: string;
+    initialMargin: string;
+    positionInitialMargin: string;
+    openOrderInitialMargin: string;
+    crossWalletBalance: string;
+    crossUnPnl: string;
+    availableBalance: string;
+    maxWithdrawAmount: string;
+    marginAvailable: boolean;
+    updateTime: number;
+  }>;
+  positions: Array<{
+    symbol: string;
+    initialMargin: string;
+    maintMargin: string;
+    unrealizedProfit: string;
+    positionInitialMargin: string;
+    openOrderInitialMargin: string;
+    leverage: string;
+    isolated: boolean;
+    entryPrice: string;
+    maxNotional: string;
+    bidNotional: string;
+    askNotional: string;
+    positionSide: string;
+    positionAmt: string;
+    updateTime: number;
+  }>;
+}
+
+export interface AsterPositionRiskRequest {
+  symbol?: string;
+  recvWindow?: number;
+  timestamp: number;
+}
+
+export interface AsterPositionRiskResponse extends Array<{
+  symbol: string;
+  positionAmt: string;
+  entryPrice: string;
+  markPrice: string;
+  unRealizedProfit: string;
+  liquidationPrice: string;
+  leverage: string;
+  maxNotional: string;
+  marginType: string;
+  isolatedMargin: string;
+  isAutoAddMargin: string;
+  positionSide: string;
+  notional: string;
+  isolatedWallet: string;
+  updateTime: number;
+}> {}
+
+export interface AsterTradingConfig {
+  user: string;
+  signer: string;
+  privateKey: string;
+  baseUrl: string;
+  recvWindow?: number;
+  proxy?: {
+    protocol?: 'http' | 'https';
+    host: string;
+    port: number;
+    auth?: { username: string; password: string };
+  };
+}
+
+export interface AsterApiError {
+  code: number;
+  msg: string;
+}
+
+// 订单状态枚举
+export enum OrderStatus {
+  NEW = 'NEW',
+  PARTIALLY_FILLED = 'PARTIALLY_FILLED',
+  FILLED = 'FILLED',
+  CANCELED = 'CANCELED',
+  PENDING_CANCEL = 'PENDING_CANCEL',
+  REJECTED = 'REJECTED',
+  EXPIRED = 'EXPIRED'
+}
+
+// 订单类型枚举
+export enum OrderType {
+  LIMIT = 'LIMIT',
+  MARKET = 'MARKET',
+  STOP = 'STOP',
+  TAKE_PROFIT = 'TAKE_PROFIT',
+  STOP_MARKET = 'STOP_MARKET',
+  TAKE_PROFIT_MARKET = 'TAKE_PROFIT_MARKET',
+  TRAILING_STOP_MARKET = 'TRAILING_STOP_MARKET'
+}
+
+// 买卖方向枚举
+export enum OrderSide {
+  BUY = 'BUY',
+  SELL = 'SELL'
+}
+
+// 持仓方向枚举
+export enum PositionSide {
+  LONG = 'LONG',
+  SHORT = 'SHORT',
+  BOTH = 'BOTH'
+}
+
+// 时间有效性枚举
+export enum TimeInForce {
+  GTC = 'GTC', // Good Till Cancel
+  IOC = 'IOC', // Immediate Or Cancel
+  FOK = 'FOK', // Fill Or Kill
+  GTX = 'GTX'  // Good Till Crossing
+}
+
+// 工作类型枚举
+export enum WorkingType {
+  MARK_PRICE = 'MARK_PRICE',
+  CONTRACT_PRICE = 'CONTRACT_PRICE'
+}

+ 90 - 0
src/dex/aster/types.ts

@@ -0,0 +1,90 @@
+import { EventEmitter } from 'events';
+
+export type AsterWsChannel = 'tickers' | 'trades' | 'books' | 'klines';
+
+export interface AsterStreamArg {
+  channel: AsterWsChannel | string;
+  symbol?: string;          // e.g., BTCUSDT
+  interval?: string;        // e.g., 1m, 5m
+  depth?: number;           // e.g., 5, 50, 200
+  [key: string]: any;
+}
+
+export interface AsterWsConfig {
+  wsUrl: string;            // e.g., wss://ws.aster.finance/futures
+  pingIntervalMs?: number;  // default 30s
+  pongTimeoutMs?: number;   // default 10s
+  autoReconnect?: boolean;  // default true
+  reconnectIntervalMs?: number; // default 5s
+}
+
+export type AsterAuthType = 'none' | 'signer';
+
+export interface AsterAuthConfig {
+  type: AsterAuthType;          // 'signer' for signer-based auth
+  user?: string;                // User address
+  signer?: string;              // Signer address  
+  privateKey?: string;          // Private key for signing
+  loginMethod?: 'login';        // server expected method name, default 'login'
+}
+
+export interface AsterTicker {
+  symbol: string;
+  price: number;
+  change24h?: number;
+  changePercent24h?: number;
+  high24h?: number;
+  low24h?: number;
+  volume24h?: number;
+  ts: number;
+}
+
+export interface AsterTrade {
+  symbol: string;
+  price: number;
+  size: number;
+  side: 'buy' | 'sell';
+  ts: number;
+}
+
+export interface AsterDepth {
+  symbol: string;
+  bids: Array<[number, number]>; // price, size
+  asks: Array<[number, number]>; // price, size
+  ts: number;
+}
+
+export interface AsterKline {
+  symbol: string;
+  interval: string;
+  open: number;
+  high: number;
+  low: number;
+  close: number;
+  volume: number;
+  openTime: number;
+  closeTime: number;
+}
+
+export interface AsterWsMessage {
+  event?: string;           // e.g., 'tickers', 'trades', 'books', 'klines'
+  arg?: any;                // original arg
+  data?: any;               // payload
+  [key: string]: any;       // passthrough
+}
+
+export interface AsterWsClientEvents {
+  open: () => void;
+  close: (code: number, reason: string) => void;
+  error: (err: Error) => void;
+  ticker: (t: AsterTicker) => void;
+  trade: (t: AsterTrade) => void;
+  depth: (d: AsterDepth) => void;
+  kline: (k: AsterKline) => void;
+  raw: (msg: any) => void;
+}
+
+export type AsterWsClientEmitter = EventEmitter & {
+  on<U extends keyof AsterWsClientEvents>(event: U, listener: AsterWsClientEvents[U]): AsterWsClientEmitter;
+  emit<U extends keyof AsterWsClientEvents>(event: U, ...args: Parameters<AsterWsClientEvents[U]>): boolean;
+};

+ 362 - 0
src/dex/aster/userStreamClient.ts

@@ -0,0 +1,362 @@
+import { EventEmitter } from 'events';
+import WebSocket from 'ws';
+import { AsterListenKeyManager } from './listenKeyManager';
+import { AsterTradingConfig } from './tradingTypes';
+import {
+  AsterUserStreamEvent,
+  AsterUserStreamEvents,
+  AsterListenKeyExpiredEvent,
+  AsterAccountUpdateEvent,
+  AsterOrderTradeUpdateEvent,
+  AsterAccountConfigUpdateEvent
+} from './userStreamTypes';
+
+export interface AsterUserStreamConfig {
+  wsUrl: string;
+  autoReconnect?: boolean;
+  reconnectIntervalMs?: number;
+  maxReconnectAttempts?: number;
+  pingIntervalMs?: number;
+  pongTimeoutMs?: number;
+}
+
+export class AsterUserStreamClient extends EventEmitter {
+  private config: AsterUserStreamConfig;
+  private tradingConfig: AsterTradingConfig;
+  private listenKeyManager: AsterListenKeyManager;
+  private ws: WebSocket | null = null;
+  private isConnected: boolean = false;
+  private reconnectAttempts: number = 0;
+  private pingTimer: NodeJS.Timeout | null = null;
+  private pongTimer: NodeJS.Timeout | null = null;
+  private reconnectTimer: NodeJS.Timeout | null = null;
+
+  constructor(
+    userStreamConfig: AsterUserStreamConfig,
+    tradingConfig: AsterTradingConfig
+  ) {
+    super();
+    this.config = {
+      autoReconnect: true,
+      reconnectIntervalMs: 5000,
+      maxReconnectAttempts: 10,
+      pingIntervalMs: 30000,
+      pongTimeoutMs: 10000,
+      ...userStreamConfig
+    };
+    this.tradingConfig = tradingConfig;
+    this.listenKeyManager = new AsterListenKeyManager(tradingConfig);
+  }
+
+  /**
+   * 连接用户数据流
+   */
+  async connect(): Promise<void> {
+    if (this.ws) {
+      console.log('⚠️ 用户数据流已连接');
+      return;
+    }
+
+    try {
+      // 获取或创建 ListenKey
+      let listenKey = this.listenKeyManager.getListenKey();
+      if (!listenKey) {
+        listenKey = await this.listenKeyManager.createListenKey();
+      }
+
+      // 构建 WebSocket URL
+      const wsUrl = `${this.config.wsUrl}/ws/${listenKey}`;
+      console.log(`🔌 连接用户数据流: ${wsUrl}`);
+
+      // 创建 WebSocket 连接
+      this.ws = new WebSocket(wsUrl);
+
+      this.ws.on('open', () => {
+        console.log('✅ 用户数据流连接成功');
+        this.isConnected = true;
+        this.reconnectAttempts = 0;
+        this.emit('connect');
+        this.startPing();
+      });
+
+      this.ws.on('message', (data: WebSocket.Data) => {
+        this.handleMessage(data);
+      });
+
+      this.ws.on('close', (code: number, reason: Buffer) => {
+        console.log(`🔌 用户数据流连接断开: ${code} ${reason.toString()}`);
+        this.isConnected = false;
+        this.stopPing();
+        this.emit('disconnect');
+        
+        if (this.config.autoReconnect && this.reconnectAttempts < this.config.maxReconnectAttempts!) {
+          this.scheduleReconnect();
+        }
+      });
+
+      this.ws.on('error', (error: Error) => {
+        console.error('❌ 用户数据流错误:', error);
+        this.emit('error', error);
+      });
+
+      this.ws.on('pong', () => {
+        this.onPong();
+      });
+
+    } catch (error: any) {
+      console.error('❌ 连接用户数据流失败:', error.message);
+      this.emit('error', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 断开用户数据流连接
+   */
+  disconnect(): void {
+    if (this.ws) {
+      this.ws.close();
+      this.ws = null;
+    }
+    this.isConnected = false;
+    this.stopPing();
+    this.stopReconnect();
+  }
+
+  /**
+   * 销毁客户端
+   */
+  async destroy(): Promise<void> {
+    this.disconnect();
+    await this.listenKeyManager.destroy();
+  }
+
+  /**
+   * 处理 WebSocket 消息
+   */
+  private handleMessage(data: WebSocket.Data): void {
+    try {
+      const message = JSON.parse(data.toString()) as AsterUserStreamEvent;
+      
+      switch (message.e) {
+        case 'listenKeyExpired':
+          this.handleListenKeyExpired(message as AsterListenKeyExpiredEvent);
+          break;
+        case 'ACCOUNT_UPDATE':
+          this.handleAccountUpdate(message as AsterAccountUpdateEvent);
+          break;
+        case 'ORDER_TRADE_UPDATE':
+          this.handleOrderTradeUpdate(message as AsterOrderTradeUpdateEvent);
+          break;
+        case 'ACCOUNT_CONFIG_UPDATE':
+          this.handleAccountConfigUpdate(message as AsterAccountConfigUpdateEvent);
+          break;
+        default:
+          console.log('📨 未知用户数据流事件:', message);
+      }
+    } catch (error: any) {
+      console.error('❌ 解析用户数据流消息失败:', error.message);
+      this.emit('error', error);
+    }
+  }
+
+  /**
+   * 处理 ListenKey 过期事件
+   */
+  private async handleListenKeyExpired(event: AsterListenKeyExpiredEvent): Promise<void> {
+    console.log('⚠️ ListenKey 已过期,重新创建...');
+    this.emit('listenKeyExpired', event);
+    
+    try {
+      // 重新创建 ListenKey
+      const newListenKey = await this.listenKeyManager.createListenKey();
+      
+      // 重新连接
+      this.disconnect();
+      await this.connect();
+      
+      this.emit('reconnect');
+    } catch (error: any) {
+      console.error('❌ 重新创建 ListenKey 失败:', error.message);
+      this.emit('error', error);
+    }
+  }
+
+  /**
+   * 处理账户更新事件
+   */
+  private handleAccountUpdate(event: AsterAccountUpdateEvent): void {
+    console.log('📊 账户更新:', {
+      原因: event.a.m,
+      余额变化: event.a.B.length,
+      持仓变化: event.a.P.length,
+      时间: new Date(event.E).toISOString()
+    });
+    
+    this.emit('accountUpdate', event);
+  }
+
+  /**
+   * 处理订单/交易更新事件
+   */
+  private handleOrderTradeUpdate(event: AsterOrderTradeUpdateEvent): void {
+    const order = event.o;
+    console.log('📋 订单更新:', {
+      交易对: order.s,
+      订单ID: order.i,
+      客户端订单ID: order.c,
+      方向: order.S,
+      类型: order.o,
+      状态: order.X,
+      执行类型: order.x,
+      数量: order.q,
+      价格: order.p,
+      时间: new Date(event.E).toISOString()
+    });
+    
+    this.emit('orderTradeUpdate', event);
+  }
+
+  /**
+   * 处理账户配置更新事件
+   */
+  private handleAccountConfigUpdate(event: AsterAccountConfigUpdateEvent): void {
+    if (event.ac) {
+      console.log('⚙️ 交易对配置更新:', {
+        交易对: event.ac.s,
+        杠杆倍数: event.ac.l,
+        时间: new Date(event.E).toISOString()
+      });
+    }
+    
+    if (event.ai) {
+      console.log('⚙️ 账户配置更新:', {
+        联合保证金: event.ai.j,
+        时间: new Date(event.E).toISOString()
+      });
+    }
+    
+    this.emit('accountConfigUpdate', event);
+  }
+
+  /**
+   * 启动 Ping
+   */
+  private startPing(): void {
+    this.stopPing();
+    
+    this.pingTimer = setInterval(() => {
+      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+        this.ws.ping();
+        this.startPongTimeout();
+      }
+    }, this.config.pingIntervalMs!);
+  }
+
+  /**
+   * 停止 Ping
+   */
+  private stopPing(): void {
+    if (this.pingTimer) {
+      clearInterval(this.pingTimer);
+      this.pingTimer = null;
+    }
+    this.stopPongTimeout();
+  }
+
+  /**
+   * 启动 Pong 超时
+   */
+  private startPongTimeout(): void {
+    this.stopPongTimeout();
+    
+    this.pongTimer = setTimeout(() => {
+      console.log('⚠️ Pong 超时,断开连接');
+      if (this.ws) {
+        this.ws.terminate();
+      }
+    }, this.config.pongTimeoutMs!);
+  }
+
+  /**
+   * 停止 Pong 超时
+   */
+  private stopPongTimeout(): void {
+    if (this.pongTimer) {
+      clearTimeout(this.pongTimer);
+      this.pongTimer = null;
+    }
+  }
+
+  /**
+   * 处理 Pong
+   */
+  private onPong(): void {
+    this.stopPongTimeout();
+  }
+
+  /**
+   * 安排重连
+   */
+  private scheduleReconnect(): void {
+    this.stopReconnect();
+    
+    this.reconnectAttempts++;
+    const delay = this.config.reconnectIntervalMs! * this.reconnectAttempts;
+    
+    console.log(`🔄 ${delay / 1000} 秒后尝试重连 (${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`);
+    
+    this.reconnectTimer = setTimeout(async () => {
+      try {
+        await this.connect();
+      } catch (error: any) {
+        console.error('❌ 重连失败:', error.message);
+        if (this.reconnectAttempts < this.config.maxReconnectAttempts!) {
+          this.scheduleReconnect();
+        } else {
+          console.error('❌ 达到最大重连次数,停止重连');
+        }
+      }
+    }, delay);
+  }
+
+  /**
+   * 停止重连
+   */
+  private stopReconnect(): void {
+    if (this.reconnectTimer) {
+      clearTimeout(this.reconnectTimer);
+      this.reconnectTimer = null;
+    }
+  }
+
+  /**
+   * 获取连接状态
+   */
+  isStreamConnected(): boolean {
+    return this.isConnected && this.ws?.readyState === WebSocket.OPEN;
+  }
+
+  /**
+   * 获取 ListenKey
+   */
+  getListenKey(): string | null {
+    return this.listenKeyManager.getListenKey();
+  }
+
+  /**
+   * 手动延长 ListenKey
+   */
+  async extendListenKey(): Promise<void> {
+    return this.listenKeyManager.extendListenKey();
+  }
+
+  /**
+   * 更新配置
+   */
+  updateConfig(newConfig: Partial<AsterUserStreamConfig>): void {
+    this.config = { ...this.config, ...newConfig };
+  }
+}
+
+

+ 161 - 0
src/dex/aster/userStreamTypes.ts

@@ -0,0 +1,161 @@
+// Aster 用户数据流类型定义
+
+export interface AsterListenKeyResponse {
+  listenKey: string;
+}
+
+export interface AsterListenKeyExpiredEvent {
+  e: 'listenKeyExpired';
+  E: number; // 事件时间
+}
+
+export interface AsterBalanceInfo {
+  a: string;  // 资产名称
+  wb: string; // 钱包余额
+  cw: string; // 除去逐仓仓位保证金的钱包余额
+  bc: string; // 除去盈亏与交易手续费以外的钱包余额改变量
+}
+
+export interface AsterPositionInfo {
+  s: string;  // 交易对
+  pa: string; // 仓位
+  ep: string; // 入仓价格
+  cr: string; // (费前)累计实现损益
+  up: string; // 持仓未实现盈亏
+  mt: string; // 保证金模式
+  iw: string; // 若为逐仓,仓位保证金
+  ps: string; // 持仓方向
+}
+
+export interface AsterAccountUpdateEvent {
+  e: 'ACCOUNT_UPDATE'; // 事件类型
+  E: number;           // 事件时间
+  T: number;           // 撮合时间
+  a: {
+    m: string;         // 事件推出原因
+    B: AsterBalanceInfo[]; // 余额信息
+    P: AsterPositionInfo[]; // 持仓信息
+  };
+}
+
+export interface AsterOrderTradeUpdateEvent {
+  e: 'ORDER_TRADE_UPDATE'; // 事件类型
+  E: number;               // 事件时间
+  T: number;               // 撮合时间
+  o: {
+    s: string;             // 交易对
+    c: string;             // 客户端自定订单ID
+    S: string;             // 订单方向
+    o: string;             // 订单类型
+    f: string;             // 有效方式
+    q: string;             // 订单原始数量
+    p: string;             // 订单原始价格
+    ap: string;            // 订单平均价格
+    sp: string;            // 条件订单触发价格
+    x: string;             // 本次事件的具体执行类型
+    X: string;             // 订单的当前状态
+    i: number;             // 订单ID
+    l: string;             // 订单末次成交量
+    z: string;             // 订单累计已成交量
+    L: string;             // 订单末次成交价格
+    N: string;             // 手续费资产类型
+    n: string;             // 手续费数量
+    T: number;             // 成交时间
+    t: number;             // 成交ID
+    b: string;             // 买单净值
+    a: string;             // 卖单净值
+    m: boolean;            // 该成交是作为挂单成交吗?
+    R: boolean;            // 是否是只减仓单
+    wt: string;            // 触发价类型
+    ot: string;            // 原始订单类型
+    ps: string;            // 持仓方向
+    cp?: boolean;          // 是否为触发平仓单
+    AP?: string;           // 追踪止损激活价格
+    cr?: string;           // 追踪止损回调比例
+    rp?: string;           // 该交易实现盈亏
+  };
+}
+
+export interface AsterAccountConfigUpdateEvent {
+  e: 'ACCOUNT_CONFIG_UPDATE'; // 事件类型
+  E: number;                  // 事件时间
+  T: number;                  // 撮合时间
+  ac?: {                      // 交易对账户配置
+    s: string;                // 交易对
+    l: number;                // 杠杆倍数
+  };
+  ai?: {                      // 用户账户配置
+    j: boolean;               // 联合保证金状态
+  };
+}
+
+export type AsterUserStreamEvent = 
+  | AsterListenKeyExpiredEvent
+  | AsterAccountUpdateEvent
+  | AsterOrderTradeUpdateEvent
+  | AsterAccountConfigUpdateEvent;
+
+export interface AsterUserStreamEvents {
+  listenKeyExpired: (event: AsterListenKeyExpiredEvent) => void;
+  accountUpdate: (event: AsterAccountUpdateEvent) => void;
+  orderTradeUpdate: (event: AsterOrderTradeUpdateEvent) => void;
+  accountConfigUpdate: (event: AsterAccountConfigUpdateEvent) => void;
+  error: (error: Error) => void;
+  connect: () => void;
+  disconnect: () => void;
+  reconnect: () => void;
+}
+
+// 事件推出原因枚举
+export enum AccountUpdateReason {
+  DEPOSIT = 'DEPOSIT',
+  WITHDRAW = 'WITHDRAW',
+  ORDER = 'ORDER',
+  FUNDING_FEE = 'FUNDING_FEE',
+  WITHDRAW_REJECT = 'WITHDRAW_REJECT',
+  ADJUSTMENT = 'ADJUSTMENT',
+  INSURANCE_CLEAR = 'INSURANCE_CLEAR',
+  ADMIN_DEPOSIT = 'ADMIN_DEPOSIT',
+  ADMIN_WITHDRAW = 'ADMIN_WITHDRAW',
+  MARGIN_TRANSFER = 'MARGIN_TRANSFER',
+  MARGIN_TYPE_CHANGE = 'MARGIN_TYPE_CHANGE',
+  ASSET_TRANSFER = 'ASSET_TRANSFER',
+  OPTIONS_PREMIUM_FEE = 'OPTIONS_PREMIUM_FEE',
+  OPTIONS_SETTLE_PROFIT = 'OPTIONS_SETTLE_PROFIT',
+  AUTO_EXCHANGE = 'AUTO_EXCHANGE'
+}
+
+// 订单执行类型枚举
+export enum OrderExecutionType {
+  NEW = 'NEW',
+  CANCELED = 'CANCELED',
+  CALCULATED = 'CALCULATED',
+  EXPIRED = 'EXPIRED',
+  TRADE = 'TRADE'
+}
+
+// 订单状态枚举
+export enum OrderStatus {
+  NEW = 'NEW',
+  PARTIALLY_FILLED = 'PARTIALLY_FILLED',
+  FILLED = 'FILLED',
+  CANCELED = 'CANCELED',
+  EXPIRED = 'EXPIRED',
+  NEW_INSURANCE = 'NEW_INSURANCE',
+  NEW_ADL = 'NEW_ADL'
+}
+
+// 保证金模式枚举
+export enum MarginType {
+  ISOLATED = 'isolated',
+  CROSS = 'cross'
+}
+
+// 持仓方向枚举
+export enum PositionSide {
+  LONG = 'LONG',
+  SHORT = 'SHORT',
+  BOTH = 'BOTH'
+}
+
+

+ 365 - 0
src/dex/aster/wsClient.ts

@@ -0,0 +1,365 @@
+import WebSocket from 'ws';
+import { EventEmitter } from 'events';
+import crypto from 'crypto';
+import { AbiCoder, Wallet, keccak256, getBytes } from 'ethers';
+import { AsterWsClientEmitter, AsterWsConfig, AsterStreamArg, AsterWsMessage, AsterTicker, AsterTrade, AsterDepth, AsterKline, AsterAuthConfig } from './types';
+
+export class AsterWsClient extends (EventEmitter as { new(): AsterWsClientEmitter }) {
+  private ws: WebSocket | null = null;
+  private cfg: Required<AsterWsConfig>;
+  private pingTimer: NodeJS.Timeout | null = null;
+  private pongTimer: NodeJS.Timeout | null = null;
+  private reconnectTimer: NodeJS.Timeout | null = null;
+  private subs: AsterStreamArg[] = [];
+  private auth?: AsterAuthConfig;
+
+  constructor(cfg: AsterWsConfig, auth?: AsterAuthConfig) {
+    super();
+    this.cfg = {
+      wsUrl: cfg.wsUrl,
+      pingIntervalMs: cfg.pingIntervalMs ?? 30000,
+      pongTimeoutMs: cfg.pongTimeoutMs ?? 10000,
+      autoReconnect: cfg.autoReconnect ?? true,
+      reconnectIntervalMs: cfg.reconnectIntervalMs ?? 5000
+    };
+    this.auth = auth;
+    
+    // 如果 URL 不包含路径,添加默认路径
+    if (!this.cfg.wsUrl.includes('/ws/') && !this.cfg.wsUrl.includes('/stream')) {
+      this.cfg.wsUrl = this.cfg.wsUrl.replace(/\/$/, '') + '/ws/';
+    }
+  }
+
+  /**
+   * 使用组合流格式连接
+   */
+  useCombinedStream(): void {
+    this.cfg.wsUrl = this.cfg.wsUrl.replace('/ws/', '/stream?streams=');
+  }
+
+  connect(): void {
+    if (this.ws) return;
+    this.ws = new WebSocket(this.cfg.wsUrl);
+
+    this.ws.on('open', () => {
+      this.emit('open');
+      this.startPing();
+      // 暂时注释掉鉴权,先测试能否收到数据
+      // this.tryAuth();
+      if (this.subs.length > 0) this.subscribe(this.subs);
+    });
+
+    this.ws.on('message', (buf) => {
+      const text = buf.toString();
+      this.handleMessage(text);
+    });
+
+    this.ws.on('pong', () => this.onPong());
+
+    this.ws.on('close', (code, reasonBuf) => {
+      const reason = reasonBuf.toString();
+      this.emit('close', code, reason);
+      this.stopPing();
+      this.ws = null;
+      if (this.cfg.autoReconnect) this.scheduleReconnect();
+    });
+
+    this.ws.on('error', (err) => {
+      this.emit('error', err as any);
+    });
+  }
+
+  disconnect(): void {
+    this.stopPing();
+    if (this.ws) {
+      this.ws.close();
+      this.ws = null;
+    }
+  }
+
+  subscribe(args: AsterStreamArg[] | AsterStreamArg): void {
+    const list = Array.isArray(args) ? args : [args];
+    // 缓存订阅参数,用于重连恢复
+    for (const a of list) {
+      const key = JSON.stringify(a);
+      if (!this.subs.find(x => JSON.stringify(x) === key)) this.subs.push(a);
+    }
+    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
+    
+    // 按照 Aster 文档格式:<symbol>@<streamType>
+    const streamNames = list.map(arg => {
+      const symbol = arg.symbol?.toLowerCase() || 'btcusdt';
+      const channel = arg.channel || 'aggTrade';
+      return `${symbol}@${channel}`;
+    });
+    
+    const payload = {
+      method: 'SUBSCRIBE',
+      params: streamNames,
+      id: Date.now()
+    };
+    
+    this.ws.send(JSON.stringify(payload));
+    console.log('📡 发送订阅请求:', JSON.stringify(payload, null, 2));
+  }
+
+  unsubscribe(args: AsterStreamArg[] | AsterStreamArg): void {
+    const list = Array.isArray(args) ? args : [args];
+    this.subs = this.subs.filter(s => !list.find(x => JSON.stringify(x) === JSON.stringify(s)));
+    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
+    
+    // 按照 Aster 文档格式
+    const streamNames = list.map(arg => {
+      const symbol = arg.symbol?.toLowerCase() || 'btcusdt';
+      const channel = arg.channel || 'aggTrade';
+      return `${symbol}@${channel}`;
+    });
+    
+    const payload = {
+      method: 'UNSUBSCRIBE',
+      params: streamNames,
+      id: Date.now()
+    };
+    
+    this.ws.send(JSON.stringify(payload));
+    console.log('📡 发送取消订阅请求:', JSON.stringify(payload, null, 2));
+  }
+
+  private handleMessage(text: string) {
+    let msg: any;
+    try { msg = JSON.parse(text); } catch {}
+    if (!msg) return;
+    
+    console.log('📨 收到消息:', JSON.stringify(msg, null, 2));
+    this.emit('raw', msg);
+
+    // 处理订阅响应
+    if (msg.id && (msg.result !== undefined)) {
+      console.log('✅ 订阅响应:', msg);
+      return;
+    }
+
+    // 处理错误响应
+    if (msg.code !== undefined) {
+      console.error('❌ 错误响应:', msg);
+      return;
+    }
+
+    // 处理组合流格式:{"stream":"<streamName>","data":<rawPayload>}
+    if (msg.stream && msg.data) {
+      const streamName = msg.stream;
+      const data = msg.data;
+      
+      if (streamName.includes('@aggTrade')) {
+        const trade: AsterTrade = {
+          symbol: data.s || data.symbol,
+          price: Number(data.p || data.price),
+          size: Number(data.q || data.quantity),
+          side: data.m ? 'sell' : 'buy', // m=true 表示主动卖出
+          ts: Number(data.T || data.timestamp || Date.now())
+        };
+        if (trade.symbol && Number.isFinite(trade.price)) {
+          this.emit('trade', trade);
+        }
+        return;
+      }
+
+      if (streamName.includes('@markPrice')) {
+        const ticker: AsterTicker = {
+          symbol: data.s || data.symbol,
+          price: Number(data.p || data.price),
+          ts: Number(data.E || data.timestamp || Date.now())
+        };
+        if (ticker.symbol && Number.isFinite(ticker.price)) {
+          this.emit('ticker', ticker);
+        }
+        return;
+      }
+    }
+
+    // 处理原始流格式(直接数据)
+    if (msg.e) {
+      const eventType = msg.e;
+      
+      if (eventType === 'aggTrade') {
+        const trade: AsterTrade = {
+          symbol: msg.s,
+          price: Number(msg.p),
+          size: Number(msg.q),
+          side: msg.m ? 'sell' : 'buy',
+          ts: Number(msg.T)
+        };
+        if (trade.symbol && Number.isFinite(trade.price)) {
+          this.emit('trade', trade);
+        }
+        return;
+      }
+
+      if (eventType === 'markPriceUpdate') {
+        const ticker: AsterTicker = {
+          symbol: msg.s,
+          price: Number(msg.p),
+          ts: Number(msg.E)
+        };
+        if (ticker.symbol && Number.isFinite(ticker.price)) {
+          this.emit('ticker', ticker);
+        }
+        return;
+      }
+    }
+  }
+
+  private startPing() {
+    this.stopPing();
+    // 按照文档:服务端每5分钟发送ping,客户端15分钟内回复pong
+    // 这里我们主动发送pong保持连接
+    this.pingTimer = setInterval(() => {
+      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+        try {
+          // 发送pong帧保持连接
+          this.ws.pong();
+          console.log('🏓 发送 pong 帧');
+        } catch {}
+      }
+    }, 60000); // 每分钟发送一次pong
+  }
+
+  private stopPing() {
+    if (this.pingTimer) clearInterval(this.pingTimer);
+    this.pingTimer = null;
+    if (this.pongTimer) clearTimeout(this.pongTimer);
+    this.pongTimer = null;
+  }
+
+  private startPongTimeout() {
+    if (this.pongTimer) clearTimeout(this.pongTimer);
+    this.pongTimer = setTimeout(() => {
+      this.disconnect();
+      if (this.cfg.autoReconnect) this.scheduleReconnect();
+    }, this.cfg.pongTimeoutMs);
+  }
+
+  private onPong() {
+    if (this.pongTimer) clearTimeout(this.pongTimer);
+    this.pongTimer = null;
+  }
+
+  private scheduleReconnect() {
+    if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
+    this.reconnectTimer = setTimeout(() => this.connect(), this.cfg.reconnectIntervalMs);
+  }
+
+  private async tryAuth() {
+    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
+    if (!this.auth || this.auth.type === 'none') return;
+
+    // Aster WebSocket 鉴权:使用 signer 信息进行签名
+    const user = this.auth.user || process.env.ASTER_ORDER_USER || '';
+    const signer = this.auth.signer || process.env.ASTER_ORDER_SIGNER || '';
+    const privateKey = this.auth.privateKey || process.env.PRIVATE_KEY || '';
+    
+    if (!user || !signer || !privateKey) return;
+
+    try {
+      // 1. 构建登录参数(按照 tx.py demo 的格式)
+      const timestamp = Math.round(Date.now() * 1000); // 毫秒时间戳
+      const nonce = BigInt(Math.trunc(Date.now() * 1000000)); // 微秒时间戳
+      
+      const loginParams = {
+        user,
+        signer,
+        timestamp,
+        nonce: nonce.toString()
+      };
+
+      // 2. 生成签名(按照 tx.py demo 的流程)
+      const signature = await this.generateSignature(loginParams, privateKey, user, signer, nonce);
+
+      // 3. 发送鉴权消息(尝试多种格式)
+      // 格式1:简单格式
+      const payload1 = {
+        method: 'login',
+        params: {
+          user,
+          signer,
+          timestamp,
+          nonce: nonce.toString(),
+          signature
+        }
+      };
+
+      // 格式2:数组格式
+      const payload2 = {
+        method: 'AUTH',
+        params: [user, signature, timestamp.toString()],
+        id: 1
+      };
+
+      // 先尝试格式1
+      this.ws.send(JSON.stringify(payload1));
+      console.log('🔐 发送 Aster WebSocket 鉴权请求 (格式1):', JSON.stringify(payload1, null, 2));
+      
+      // 等待1秒后尝试格式2
+      setTimeout(() => {
+        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+          this.ws.send(JSON.stringify(payload2));
+          console.log('🔐 发送 Aster WebSocket 鉴权请求 (格式2):', JSON.stringify(payload2, null, 2));
+        }
+      }, 1000);
+
+    } catch (error) {
+      console.error('❌ Aster WebSocket 鉴权失败:', error);
+    }
+  }
+
+  private async generateSignature(params: any, privateKey: string, user: string, signer: string, nonce: bigint): Promise<string> {
+    // 按照 tx.py demo 的流程
+    
+    // 1. 规范化参数并转换为字符串
+    const normalizedParams = this.normalizeBusinessParams(params);
+    
+    // 2. 生成紧凑 JSON 字符串(按照 tx.py 的格式)
+    const jsonString = this.stableJSONString(normalizedParams);
+    console.log('📝 JSON String:', jsonString);
+    
+    // 3. ABI 编码(使用 ethers.js 的 AbiCoder)
+    const coder = AbiCoder.defaultAbiCoder();
+    const encoded = coder.encode(['string', 'address', 'address', 'uint256'], [jsonString, user, signer, nonce]);
+    console.log('🔢 Encoded:', encoded);
+    
+    // 4. Keccak 哈希
+    const hash = keccak256(encoded);
+    console.log('🔐 Keccak Hash:', hash);
+    
+    // 5. EIP-191 签名(按照 tx.py 使用 encode_defunct)
+    const wallet = new Wallet(privateKey);
+    const signature = await wallet.signMessage(getBytes(hash));
+    console.log('✍️ Signature:', signature);
+    
+    return signature;
+  }
+
+  private normalizeBusinessParams(params: Record<string, any>): Record<string, string> {
+    const trimmed: Record<string, string> = {};
+    for (const key in params) {
+      if (params[key] !== undefined && params[key] !== null) {
+        let value = params[key];
+        if (typeof value === 'object' && value !== null) {
+          value = JSON.stringify(value);
+        }
+        trimmed[key] = String(value);
+      }
+    }
+    return trimmed;
+  }
+
+  private stableJSONString(params: Record<string, string>): string {
+    const sortedKeys = Object.keys(params).sort();
+    const sortedParams: Record<string, string> = {};
+    for (const key of sortedKeys) {
+      sortedParams[key] = params[key];
+    }
+    return JSON.stringify(sortedParams).replace(/ /g, '');
+  }
+
+}

+ 37 - 0
src/infrastructure/config/configManager.ts

@@ -0,0 +1,37 @@
+import 'dotenv/config';
+
+export interface AppConfig {
+  NODE_ENV: string;
+  RPC_URL?: string;
+  PRIVATE_KEY?: string;
+  REDIS_URL?: string;
+  PG_URL?: string;
+  TELEGRAM_BOT_TOKEN?: string;
+  TELEGRAM_CHAT_ID?: string;
+}
+
+class ConfigManager {
+  private config: AppConfig;
+
+  constructor() {
+    this.config = {
+      NODE_ENV: process.env.NODE_ENV || 'development',
+      RPC_URL: process.env.RPC_URL,
+      PRIVATE_KEY: process.env.PRIVATE_KEY,
+      REDIS_URL: process.env.REDIS_URL,
+      PG_URL: process.env.PG_URL,
+      TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
+      TELEGRAM_CHAT_ID: process.env.TELEGRAM_CHAT_ID
+    };
+  }
+
+  get<T extends keyof AppConfig>(key: T): AppConfig[T] {
+    return this.config[key];
+  }
+
+  getAll(): AppConfig {
+    return { ...this.config };
+  }
+}
+
+export const configManager = new ConfigManager();

+ 31 - 0
src/infrastructure/config/default.example.yaml

@@ -0,0 +1,31 @@
+networks:
+  arbitrum:
+    chainId: 42161
+    rpcUrl: ${RPC_URL}
+    explorerUrl: https://arbiscan.io
+    gasPrice: 0
+
+apis:
+  chainlink:
+    feeds:
+      BTCUSDT: '0x0000000000000000000000000000000000000000'
+
+risk:
+  maxLeverage: 5
+  maxPositionSize: 100000
+  maxDrawdown: 0.05
+  minMarginRatio: 0.1
+  maxSlippage: 0.003
+
+trading:
+  defaultSlippage: 0.001
+  minOrderValue: 10
+  maxOrderValue: 100000
+  rebalanceInterval: 60
+  hedgeThreshold: 0.01
+
+monitoring:
+  alertCooldown: 30
+  metricsInterval: 10
+  healthCheckInterval: 15
+  logLevel: info

+ 14 - 0
src/infrastructure/database/index.ts

@@ -0,0 +1,14 @@
+export interface Db {
+  connected: boolean;
+}
+
+class Database implements Db {
+  connected = false;
+
+  async connect(): Promise<void> {
+    // TODO: 集成 Sequelize/Prisma
+    this.connected = true;
+  }
+}
+
+export const db = new Database();

+ 30 - 0
src/infrastructure/wallet/walletManager.ts

@@ -0,0 +1,30 @@
+import { ethers } from 'ethers';
+import { configManager } from '../config/configManager';
+
+export class WalletManager {
+  provider: ethers.JsonRpcProvider;
+  wallet: ethers.Wallet | null = null;
+
+  constructor() {
+    const rpc = configManager.get('RPC_URL');
+    if (!rpc) throw new Error('RPC_URL not configured');
+    this.provider = new ethers.JsonRpcProvider(rpc);
+
+    const pk = configManager.get('PRIVATE_KEY');
+    if (pk) {
+      this.wallet = new ethers.Wallet(pk, this.provider);
+    }
+  }
+
+  getAddress(): Promise<string> {
+    if (!this.wallet) throw new Error('Wallet not initialized');
+    return this.wallet.getAddress();
+  }
+
+  getNonce(address?: string): Promise<number> {
+    const addrPromise = address ? Promise.resolve(address) : this.getAddress();
+    return addrPromise.then(addr => this.provider.getTransactionCount(addr));
+  }
+}
+
+export const walletManager = new WalletManager();

+ 342 - 0
src/market/README.md

@@ -0,0 +1,342 @@
+# 行情数据模块
+
+这个模块提供了完整的行情数据管理功能,通过 WebSocket 实时获取 Binance 期货市场的行情数据,并在内存中缓存和管理。
+
+## 功能特性
+
+- 🔄 **实时数据**: 通过 WebSocket 获取实时行情数据
+- 💾 **内存缓存**: 高效的内存缓存机制,减少重复请求
+- 🔄 **自动重连**: 网络断开时自动重连
+- 📊 **多种数据类型**: 支持行情、K线、深度等多种数据
+- 🎯 **事件驱动**: 基于事件的数据更新机制
+- 🧹 **自动清理**: 自动清理过期数据,优化内存使用
+
+## 模块组成
+
+### 1. MarketDataManager
+基础的 WebSocket 行情数据管理器,负责:
+- WebSocket 连接管理
+- 数据订阅和取消订阅
+- 原始数据处理
+
+### 2. MarketDataCache
+缓存管理器,负责:
+- 内存数据缓存
+- 数据过期管理
+- 内存使用优化
+
+### 3. EnhancedMarketManager
+增强的行情管理器,整合了:
+- WebSocket 连接
+- 缓存管理
+- 便捷的数据访问方法
+
+## 快速开始
+
+### 基本使用
+
+```typescript
+import { EnhancedMarketManager } from './market';
+
+// 创建行情管理器
+const marketManager = new EnhancedMarketManager({
+  enableCache: true,
+  cacheConfig: {
+    maxKlineRecords: 500,
+    maxDepthLevels: 10,
+    cleanupInterval: 30000,
+    maxAge: 60000
+  }
+});
+
+// 初始化连接
+await marketManager.initialize();
+
+// 订阅行情数据
+marketManager.subscribeMarketData(['BTCUSDT', 'ETHUSDT'], ['1m', '5m']);
+
+// 监听数据更新
+marketManager.on('marketData', (data) => {
+  console.log(`${data.symbol}: $${data.price}`);
+});
+
+// 获取当前价格
+const btcPrice = marketManager.getCurrentPrice('BTCUSDT');
+console.log(`BTC 当前价格: $${btcPrice}`);
+```
+
+### 高级使用
+
+```typescript
+// 监听所有事件
+marketManager.on('connected', () => {
+  console.log('WebSocket 连接成功');
+});
+
+marketManager.on('disconnected', (code, reason) => {
+  console.log(`连接断开: ${code} - ${reason}`);
+});
+
+marketManager.on('ticker24hr', (data) => {
+  console.log(`${data.symbol} 24h涨跌幅: ${data.priceChangePercent}%`);
+});
+
+marketManager.on('kline', (data) => {
+  console.log(`${data.symbol} ${data.interval} K线: ${data.close}`);
+});
+
+marketManager.on('depth', (data) => {
+  console.log(`${data.symbol} 深度数据更新`);
+});
+
+// 获取各种数据
+const marketData = marketManager.getMarketData('BTCUSDT');
+const ticker24hr = marketManager.getTicker24hr('BTCUSDT');
+const klines = marketManager.getKlineData('BTCUSDT', '1m', 10);
+const depth = marketManager.getDepthData('BTCUSDT');
+
+// 获取分析数据
+const spread = marketManager.getSpread('BTCUSDT');
+const spreadPercent = marketManager.getSpreadPercent('BTCUSDT');
+const isUp = marketManager.isPriceUp('BTCUSDT');
+const isDown = marketManager.isPriceDown('BTCUSDT');
+
+// 获取排序数据
+const symbolsByPrice = marketManager.getSymbolsByPrice('desc');
+const symbolsByChange = marketManager.getSymbolsByChange('desc');
+```
+
+## 数据类型
+
+### MarketData
+```typescript
+interface MarketData {
+  symbol: string;
+  price: number;
+  volume: number;
+  change: number;
+  changePercent: number;
+  high: number;
+  low: number;
+  open: number;
+  close: number;
+  timestamp: number;
+  bid: number;
+  ask: number;
+  bidSize: number;
+  askSize: number;
+}
+```
+
+### Ticker24hr
+```typescript
+interface Ticker24hr {
+  symbol: string;
+  priceChange: string;
+  priceChangePercent: string;
+  weightedAvgPrice: string;
+  prevClosePrice: string;
+  lastPrice: string;
+  lastQty: string;
+  bidPrice: string;
+  bidQty: string;
+  askPrice: string;
+  askQty: string;
+  openPrice: string;
+  highPrice: string;
+  lowPrice: string;
+  volume: string;
+  quoteVolume: string;
+  openTime: number;
+  closeTime: number;
+  firstId: number;
+  lastId: number;
+  count: number;
+}
+```
+
+### KlineData
+```typescript
+interface KlineData {
+  symbol: string;
+  interval: string;
+  openTime: number;
+  open: number;
+  high: number;
+  low: number;
+  close: number;
+  volume: number;
+  closeTime: number;
+  quoteVolume: number;
+  trades: number;
+  takerBuyBaseVolume: number;
+  takerBuyQuoteVolume: number;
+}
+```
+
+### DepthData
+```typescript
+interface DepthData {
+  symbol: string;
+  bids: [string, string][]; // [price, quantity]
+  asks: [string, string][]; // [price, quantity]
+  lastUpdateId: number;
+}
+```
+
+## 配置选项
+
+### EnhancedMarketManagerConfig
+```typescript
+interface EnhancedMarketManagerConfig {
+  wsUrl?: string;                    // WebSocket URL
+  reconnectInterval?: number;        // 重连间隔(毫秒)
+  maxReconnectAttempts?: number;     // 最大重连次数
+  cacheConfig?: Partial<CacheConfig>; // 缓存配置
+  enableCache?: boolean;             // 是否启用缓存
+  enableAutoReconnect?: boolean;     // 是否启用自动重连
+  enablePingPong?: boolean;          // 是否启用 Ping/Pong
+}
+```
+
+### CacheConfig
+```typescript
+interface CacheConfig {
+  maxKlineRecords: number;      // 每个交易对每个时间间隔最大K线记录数
+  maxDepthLevels: number;       // 深度数据最大档位数
+  cleanupInterval: number;      // 清理间隔(毫秒)
+  maxAge: number;              // 数据最大保存时间(毫秒)
+}
+```
+
+## 事件列表
+
+| 事件名 | 描述 | 参数 |
+|--------|------|------|
+| `connected` | WebSocket 连接成功 | - |
+| `disconnected` | WebSocket 连接断开 | `code: number, reason: string` |
+| `error` | 发生错误 | `error: Error` |
+| `marketData` | 行情数据更新 | `data: MarketData` |
+| `ticker24hr` | 24小时数据更新 | `data: Ticker24hr` |
+| `kline` | K线数据更新 | `data: KlineData` |
+| `depth` | 深度数据更新 | `data: DepthData` |
+| `maxReconnectAttemptsReached` | 达到最大重连次数 | - |
+
+## 方法列表
+
+### 连接管理
+- `initialize()`: 初始化连接
+- `disconnect()`: 断开连接
+- `isConnected()`: 检查连接状态
+- `destroy()`: 销毁管理器
+
+### 数据订阅
+- `subscribeMarketData(symbols, intervals)`: 订阅行情数据
+- `unsubscribeMarketData(symbols)`: 取消订阅
+- `getSubscribedSymbols()`: 获取订阅的符号列表
+
+### 数据获取
+- `getMarketData(symbol)`: 获取行情数据
+- `getAllMarketData()`: 获取所有行情数据
+- `getTicker24hr(symbol)`: 获取24小时数据
+- `getAllTicker24hr()`: 获取所有24小时数据
+- `getKlineData(symbol, interval, limit)`: 获取K线数据
+- `getDepthData(symbol)`: 获取深度数据
+- `getSymbolData(symbol)`: 获取指定交易对的所有数据
+
+### 便捷方法
+- `getCurrentPrice(symbol)`: 获取当前价格
+- `getPriceChangePercent(symbol)`: 获取价格变化百分比
+- `get24hrHigh(symbol)`: 获取24小时最高价
+- `get24hrLow(symbol)`: 获取24小时最低价
+- `get24hrVolume(symbol)`: 获取24小时成交量
+- `getSpread(symbol)`: 获取买卖价差
+- `getSpreadPercent(symbol)`: 获取买卖价差百分比
+- `isPriceUp(symbol)`: 检查价格是否上涨
+- `isPriceDown(symbol)`: 检查价格是否下跌
+
+### 排序方法
+- `getAllPrices()`: 获取所有价格
+- `getSymbolsByPrice(sortOrder)`: 按价格排序
+- `getSymbolsByChange(sortOrder)`: 按涨跌幅排序
+
+### 缓存管理
+- `getCacheStats()`: 获取缓存统计
+- `cleanupCache()`: 清理过期缓存
+- `clearCache()`: 清空所有缓存
+- `isDataExpired(symbol, type, interval)`: 检查数据是否过期
+- `getDataTimestamp(symbol, type, interval)`: 获取数据更新时间
+
+## 使用示例
+
+### 实时价格监控
+```typescript
+const marketManager = new EnhancedMarketManager();
+await marketManager.initialize();
+
+marketManager.subscribeMarketData(['BTCUSDT', 'ETHUSDT']);
+
+// 实时监控价格
+setInterval(() => {
+  const btcPrice = marketManager.getCurrentPrice('BTCUSDT');
+  const ethPrice = marketManager.getCurrentPrice('ETHUSDT');
+  
+  console.log(`BTC: $${btcPrice?.toFixed(2)}, ETH: $${ethPrice?.toFixed(2)}`);
+}, 1000);
+```
+
+### 价格变化监控
+```typescript
+marketManager.on('marketData', (data) => {
+  const changePercent = marketManager.getPriceChangePercent(data.symbol);
+  const isUp = marketManager.isPriceUp(data.symbol);
+  
+  if (isUp) {
+    console.log(`📈 ${data.symbol} 上涨 ${changePercent}%`);
+  } else {
+    console.log(`📉 ${data.symbol} 下跌 ${Math.abs(changePercent)}%`);
+  }
+});
+```
+
+### K线数据分析
+```typescript
+marketManager.on('kline', (data) => {
+  const klines = marketManager.getKlineData(data.symbol, data.interval, 20);
+  
+  if (klines.length >= 20) {
+    // 计算移动平均线
+    const ma5 = klines.slice(-5).reduce((sum, k) => sum + k.close, 0) / 5;
+    const ma20 = klines.slice(-20).reduce((sum, k) => sum + k.close, 0) / 20;
+    
+    console.log(`${data.symbol} MA5: ${ma5.toFixed(2)}, MA20: ${ma20.toFixed(2)}`);
+  }
+});
+```
+
+## 注意事项
+
+1. **内存使用**: 大量订阅会占用较多内存,建议合理配置缓存参数
+2. **网络稳定性**: 确保网络稳定,模块会自动重连但有限制
+3. **数据准确性**: 缓存数据可能有延迟,关键操作建议使用实时数据
+4. **资源清理**: 使用完毕后调用 `destroy()` 方法清理资源
+
+## 错误处理
+
+```typescript
+marketManager.on('error', (error) => {
+  console.error('行情数据错误:', error);
+});
+
+marketManager.on('maxReconnectAttemptsReached', () => {
+  console.error('达到最大重连次数,请检查网络连接');
+});
+
+// 处理连接断开
+marketManager.on('disconnected', (code, reason) => {
+  console.log(`连接断开: ${code} - ${reason}`);
+  // 可以在这里实现自定义的重连逻辑
+});
+```
+
+

+ 428 - 0
src/market/enhancedMarketManager.ts

@@ -0,0 +1,428 @@
+import { MarketDataManager, MarketData, Ticker24hr, KlineData, DepthData } from './marketDataManager';
+import { MarketDataCache, CacheConfig } from './marketDataCache';
+import { EventEmitter } from 'events';
+
+/**
+ * 增强行情管理器配置
+ */
+export interface EnhancedMarketManagerConfig {
+  wsUrl?: string;
+  reconnectInterval?: number;
+  maxReconnectAttempts?: number;
+  cacheConfig?: Partial<CacheConfig>;
+  enableCache?: boolean;
+  enableAutoReconnect?: boolean;
+  enablePingPong?: boolean;
+}
+
+/**
+ * 增强的行情管理器
+ * 整合 WebSocket 连接和缓存管理功能
+ */
+export class EnhancedMarketManager extends EventEmitter {
+  private marketDataManager: MarketDataManager;
+  private marketDataCache: MarketDataCache;
+  private config: EnhancedMarketManagerConfig;
+  private isInitialized: boolean = false;
+
+  constructor(config: EnhancedMarketManagerConfig = {}) {
+    super();
+    
+    this.config = {
+      wsUrl: 'wss://fstream.binance.com/ws',
+      reconnectInterval: 5000,
+      maxReconnectAttempts: 10,
+      enableCache: true,
+      enableAutoReconnect: true,
+      enablePingPong: true,
+      ...config
+    };
+
+    // 初始化行情数据管理器
+    this.marketDataManager = new MarketDataManager(
+      this.config.wsUrl,
+      this.config.reconnectInterval,
+      this.config.maxReconnectAttempts
+    );
+
+    // 初始化缓存管理器
+    this.marketDataCache = new MarketDataCache(this.config.cacheConfig);
+
+    // 绑定事件
+    this.bindEvents();
+  }
+
+  /**
+   * 初始化连接
+   */
+  public async initialize(): Promise<void> {
+    if (this.isInitialized) {
+      return;
+    }
+
+    try {
+      await this.marketDataManager.connect();
+      this.isInitialized = true;
+      console.log('✅ 增强行情管理器初始化成功');
+    } catch (error) {
+      console.error('❌ 增强行情管理器初始化失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 订阅行情数据
+   * @param symbols 交易对列表
+   * @param intervals K线间隔列表
+   */
+  public subscribeMarketData(symbols: string[], intervals: string[] = []): void {
+    if (!this.isInitialized) {
+      throw new Error('请先调用 initialize() 方法初始化连接');
+    }
+
+    this.marketDataManager.subscribeMarketData(symbols, intervals);
+  }
+
+  /**
+   * 取消订阅
+   * @param symbols 交易对列表
+   */
+  public unsubscribeMarketData(symbols: string[]): void {
+    this.marketDataManager.unsubscribeMarketData(symbols);
+  }
+
+  /**
+   * 获取行情数据(优先从缓存获取)
+   */
+  public getMarketData(symbol: string): MarketData | null {
+    if (this.config.enableCache) {
+      return this.marketDataCache.getMarketData(symbol);
+    }
+    return this.marketDataManager.getMarketData(symbol);
+  }
+
+  /**
+   * 获取所有行情数据
+   */
+  public getAllMarketData(): Map<string, MarketData> {
+    if (this.config.enableCache) {
+      return this.marketDataCache.getAllMarketData();
+    }
+    return this.marketDataManager.getAllMarketData();
+  }
+
+  /**
+   * 获取24小时行情数据
+   */
+  public getTicker24hr(symbol: string): Ticker24hr | null {
+    if (this.config.enableCache) {
+      return this.marketDataCache.getTicker24hr(symbol);
+    }
+    return this.marketDataManager.getTicker24hr(symbol);
+  }
+
+  /**
+   * 获取所有24小时行情数据
+   */
+  public getAllTicker24hr(): Map<string, Ticker24hr> {
+    if (this.config.enableCache) {
+      return this.marketDataCache.getAllTicker24hr();
+    }
+    return this.marketDataManager.getAllTicker24hr();
+  }
+
+  /**
+   * 获取K线数据
+   */
+  public getKlineData(symbol: string, interval: string, limit: number = 100): KlineData[] {
+    if (this.config.enableCache) {
+      return this.marketDataCache.getKlineData(symbol, interval, limit);
+    }
+    return this.marketDataManager.getKlineData(symbol, interval, limit);
+  }
+
+  /**
+   * 获取深度数据
+   */
+  public getDepthData(symbol: string): DepthData | null {
+    if (this.config.enableCache) {
+      return this.marketDataCache.getDepthData(symbol);
+    }
+    return this.marketDataManager.getDepthData(symbol);
+  }
+
+  /**
+   * 获取订阅的符号列表
+   */
+  public getSubscribedSymbols(): string[] {
+    return this.marketDataManager.getSubscribedSymbols();
+  }
+
+  /**
+   * 检查是否已连接
+   */
+  public isConnected(): boolean {
+    return this.marketDataManager.isConnectedToWebSocket();
+  }
+
+  /**
+   * 获取缓存统计信息
+   */
+  public getCacheStats() {
+    if (!this.config.enableCache) {
+      return null;
+    }
+    return this.marketDataCache.getCacheStats();
+  }
+
+  /**
+   * 获取指定交易对的所有数据
+   */
+  public getSymbolData(symbol: string) {
+    if (this.config.enableCache) {
+      return this.marketDataCache.getSymbolData(symbol);
+    }
+
+    return {
+      marketData: this.marketDataManager.getMarketData(symbol),
+      ticker24hr: this.marketDataManager.getTicker24hr(symbol),
+      klineData: new Map(), // 需要从 manager 获取
+      depthData: this.marketDataManager.getDepthData(symbol)
+    };
+  }
+
+  /**
+   * 检查数据是否过期
+   */
+  public isDataExpired(symbol: string, type: 'market' | 'ticker' | 'kline' | 'depth', interval?: string): boolean {
+    if (!this.config.enableCache) {
+      return true; // 没有缓存时认为数据已过期
+    }
+    return this.marketDataCache.isDataExpired(symbol, type, interval);
+  }
+
+  /**
+   * 获取数据更新时间
+   */
+  public getDataTimestamp(symbol: string, type: 'market' | 'ticker' | 'kline' | 'depth', interval?: string): number | null {
+    if (!this.config.enableCache) {
+      return null;
+    }
+    return this.marketDataCache.getDataTimestamp(symbol, type, interval);
+  }
+
+  /**
+   * 清理缓存
+   */
+  public cleanupCache(): void {
+    if (this.config.enableCache) {
+      this.marketDataCache.cleanup();
+    }
+  }
+
+  /**
+   * 清空缓存
+   */
+  public clearCache(): void {
+    if (this.config.enableCache) {
+      this.marketDataCache.clear();
+    }
+  }
+
+  /**
+   * 断开连接
+   */
+  public disconnect(): void {
+    this.marketDataManager.disconnect();
+    this.isInitialized = false;
+  }
+
+  /**
+   * 销毁管理器
+   */
+  public destroy(): void {
+    this.disconnect();
+    if (this.config.enableCache) {
+      this.marketDataCache.destroy();
+    }
+    this.removeAllListeners();
+  }
+
+  /**
+   * 绑定事件
+   */
+  private bindEvents(): void {
+    // 连接事件
+    this.marketDataManager.on('connected', () => {
+      this.emit('connected');
+    });
+
+    this.marketDataManager.on('disconnected', (code: number, reason: string) => {
+      this.emit('disconnected', code, reason);
+    });
+
+    this.marketDataManager.on('error', (error: Error) => {
+      this.emit('error', error);
+    });
+
+    this.marketDataManager.on('maxReconnectAttemptsReached', () => {
+      this.emit('maxReconnectAttemptsReached');
+    });
+
+    // 数据事件
+    this.marketDataManager.on('marketData', (data: MarketData) => {
+      if (this.config.enableCache) {
+        this.marketDataCache.updateMarketData(data);
+      }
+      this.emit('marketData', data);
+    });
+
+    this.marketDataManager.on('ticker24hr', (data: Ticker24hr) => {
+      if (this.config.enableCache) {
+        this.marketDataCache.updateTicker24hr(data);
+      }
+      this.emit('ticker24hr', data);
+    });
+
+    this.marketDataManager.on('kline', (data: KlineData) => {
+      if (this.config.enableCache) {
+        this.marketDataCache.updateKlineData(data);
+      }
+      this.emit('kline', data);
+    });
+
+    this.marketDataManager.on('depth', (data: DepthData) => {
+      if (this.config.enableCache) {
+        this.marketDataCache.updateDepthData(data);
+      }
+      this.emit('depth', data);
+    });
+  }
+
+  /**
+   * 获取实时价格
+   */
+  public getCurrentPrice(symbol: string): number | null {
+    const marketData = this.getMarketData(symbol);
+    return marketData ? marketData.price : null;
+  }
+
+  /**
+   * 获取价格变化百分比
+   */
+  public getPriceChangePercent(symbol: string): number | null {
+    const ticker = this.getTicker24hr(symbol);
+    return ticker ? parseFloat(ticker.priceChangePercent) : null;
+  }
+
+  /**
+   * 获取24小时最高价
+   */
+  public get24hrHigh(symbol: string): number | null {
+    const ticker = this.getTicker24hr(symbol);
+    return ticker ? parseFloat(ticker.highPrice) : null;
+  }
+
+  /**
+   * 获取24小时最低价
+   */
+  public get24hrLow(symbol: string): number | null {
+    const ticker = this.getTicker24hr(symbol);
+    return ticker ? parseFloat(ticker.lowPrice) : null;
+  }
+
+  /**
+   * 获取24小时成交量
+   */
+  public get24hrVolume(symbol: string): number | null {
+    const ticker = this.getTicker24hr(symbol);
+    return ticker ? parseFloat(ticker.volume) : null;
+  }
+
+  /**
+   * 获取买卖价差
+   */
+  public getSpread(symbol: string): number | null {
+    const marketData = this.getMarketData(symbol);
+    if (!marketData) return null;
+    
+    return marketData.ask - marketData.bid;
+  }
+
+  /**
+   * 获取买卖价差百分比
+   */
+  public getSpreadPercent(symbol: string): number | null {
+    const marketData = this.getMarketData(symbol);
+    if (!marketData) return null;
+    
+    const spread = marketData.ask - marketData.bid;
+    return (spread / marketData.price) * 100;
+  }
+
+  /**
+   * 检查价格是否上涨
+   */
+  public isPriceUp(symbol: string): boolean | null {
+    const changePercent = this.getPriceChangePercent(symbol);
+    return changePercent !== null ? changePercent > 0 : null;
+  }
+
+  /**
+   * 检查价格是否下跌
+   */
+  public isPriceDown(symbol: string): boolean | null {
+    const changePercent = this.getPriceChangePercent(symbol);
+    return changePercent !== null ? changePercent < 0 : null;
+  }
+
+  /**
+   * 获取所有活跃交易对的价格
+   */
+  public getAllPrices(): Map<string, number> {
+    const prices = new Map<string, number>();
+    const allMarketData = this.getAllMarketData();
+    
+    for (const [symbol, data] of allMarketData) {
+      prices.set(symbol, data.price);
+    }
+    
+    return prices;
+  }
+
+  /**
+   * 获取价格排序的交易对列表
+   */
+  public getSymbolsByPrice(sortOrder: 'asc' | 'desc' = 'desc'): string[] {
+    const allMarketData = this.getAllMarketData();
+    const symbols = Array.from(allMarketData.entries())
+      .sort(([, a], [, b]) => {
+        return sortOrder === 'asc' ? a.price - b.price : b.price - a.price;
+      })
+      .map(([symbol]) => symbol);
+    
+    return symbols;
+  }
+
+  /**
+   * 获取价格变化排序的交易对列表
+   */
+  public getSymbolsByChange(sortOrder: 'asc' | 'desc' = 'desc'): string[] {
+    const symbols: Array<{ symbol: string; change: number }> = [];
+    
+    for (const symbol of this.getSubscribedSymbols()) {
+      const changePercent = this.getPriceChangePercent(symbol);
+      if (changePercent !== null) {
+        symbols.push({ symbol, change: changePercent });
+      }
+    }
+    
+    return symbols
+      .sort((a, b) => {
+        return sortOrder === 'asc' ? a.change - b.change : b.change - a.change;
+      })
+      .map(item => item.symbol);
+  }
+}
+
+

+ 13 - 0
src/market/index.ts

@@ -0,0 +1,13 @@
+// 导出行情数据管理器
+export { MarketDataManager } from './marketDataManager';
+export type { MarketData, Ticker24hr, KlineData, DepthData } from './marketDataManager';
+
+// 导出缓存管理器
+export { MarketDataCache } from './marketDataCache';
+export type { CacheConfig } from './marketDataCache';
+
+// 导出增强行情管理器
+export { EnhancedMarketManager } from './enhancedMarketManager';
+export type { EnhancedMarketManagerConfig } from './enhancedMarketManager';
+
+

+ 332 - 0
src/market/marketDataCache.ts

@@ -0,0 +1,332 @@
+import { MarketData, Ticker24hr, KlineData, DepthData } from './marketDataManager';
+
+/**
+ * 缓存配置
+ */
+export interface CacheConfig {
+  maxKlineRecords: number;      // 每个交易对每个时间间隔最大K线记录数
+  maxDepthLevels: number;       // 深度数据最大档位数
+  cleanupInterval: number;      // 清理间隔(毫秒)
+  maxAge: number;              // 数据最大保存时间(毫秒)
+}
+
+/**
+ * 行情数据缓存管理器
+ * 用于优化内存使用和数据管理
+ */
+export class MarketDataCache {
+  private config: CacheConfig;
+  private cleanupTimer: NodeJS.Timeout | null = null;
+
+  // 缓存数据
+  private marketDataCache: Map<string, MarketData> = new Map();
+  private ticker24hrCache: Map<string, Ticker24hr> = new Map();
+  private klineDataCache: Map<string, Map<string, KlineData[]>> = new Map();
+  private depthDataCache: Map<string, DepthData> = new Map();
+
+  // 数据时间戳
+  private dataTimestamps: Map<string, number> = new Map();
+
+  constructor(config: Partial<CacheConfig> = {}) {
+    this.config = {
+      maxKlineRecords: 1000,
+      maxDepthLevels: 20,
+      cleanupInterval: 60000, // 1分钟
+      maxAge: 300000, // 5分钟
+      ...config
+    };
+
+    this.startCleanupTimer();
+  }
+
+  /**
+   * 更新行情数据
+   */
+  public updateMarketData(data: MarketData): void {
+    this.marketDataCache.set(data.symbol, data);
+    this.dataTimestamps.set(`market_${data.symbol}`, Date.now());
+  }
+
+  /**
+   * 更新24小时行情数据
+   */
+  public updateTicker24hr(data: Ticker24hr): void {
+    this.ticker24hrCache.set(data.symbol, data);
+    this.dataTimestamps.set(`ticker_${data.symbol}`, Date.now());
+  }
+
+  /**
+   * 更新K线数据
+   */
+  public updateKlineData(data: KlineData): void {
+    const symbol = data.symbol;
+    const interval = data.interval;
+
+    if (!this.klineDataCache.has(symbol)) {
+      this.klineDataCache.set(symbol, new Map());
+    }
+
+    const symbolKlines = this.klineDataCache.get(symbol)!;
+    if (!symbolKlines.has(interval)) {
+      symbolKlines.set(interval, []);
+    }
+
+    const klines = symbolKlines.get(interval)!;
+    
+    // 查找是否已存在相同时间戳的K线
+    const existingIndex = klines.findIndex(k => k.openTime === data.openTime);
+    if (existingIndex >= 0) {
+      klines[existingIndex] = data;
+    } else {
+      klines.push(data);
+      // 保持最大记录数限制
+      if (klines.length > this.config.maxKlineRecords) {
+        klines.splice(0, klines.length - this.config.maxKlineRecords);
+      }
+    }
+
+    this.dataTimestamps.set(`kline_${symbol}_${interval}`, Date.now());
+  }
+
+  /**
+   * 更新深度数据
+   */
+  public updateDepthData(data: DepthData): void {
+    // 限制深度档位数
+    const limitedData: DepthData = {
+      symbol: data.symbol,
+      bids: data.bids.slice(0, this.config.maxDepthLevels),
+      asks: data.asks.slice(0, this.config.maxDepthLevels),
+      lastUpdateId: data.lastUpdateId
+    };
+
+    this.depthDataCache.set(data.symbol, limitedData);
+    this.dataTimestamps.set(`depth_${data.symbol}`, Date.now());
+  }
+
+  /**
+   * 获取行情数据
+   */
+  public getMarketData(symbol: string): MarketData | null {
+    return this.marketDataCache.get(symbol) || null;
+  }
+
+  /**
+   * 获取所有行情数据
+   */
+  public getAllMarketData(): Map<string, MarketData> {
+    return new Map(this.marketDataCache);
+  }
+
+  /**
+   * 获取24小时行情数据
+   */
+  public getTicker24hr(symbol: string): Ticker24hr | null {
+    return this.ticker24hrCache.get(symbol) || null;
+  }
+
+  /**
+   * 获取所有24小时行情数据
+   */
+  public getAllTicker24hr(): Map<string, Ticker24hr> {
+    return new Map(this.ticker24hrCache);
+  }
+
+  /**
+   * 获取K线数据
+   */
+  public getKlineData(symbol: string, interval: string, limit?: number): KlineData[] {
+    const symbolKlines = this.klineDataCache.get(symbol);
+    if (!symbolKlines) return [];
+
+    const klines = symbolKlines.get(interval);
+    if (!klines) return [];
+
+    if (limit) {
+      return klines.slice(-limit);
+    }
+    return [...klines];
+  }
+
+  /**
+   * 获取深度数据
+   */
+  public getDepthData(symbol: string): DepthData | null {
+    return this.depthDataCache.get(symbol) || null;
+  }
+
+  /**
+   * 获取缓存统计信息
+   */
+  public getCacheStats(): {
+    marketDataCount: number;
+    ticker24hrCount: number;
+    klineDataCount: number;
+    depthDataCount: number;
+    totalMemoryUsage: number;
+  } {
+    let klineDataCount = 0;
+    for (const symbolKlines of this.klineDataCache.values()) {
+      for (const klines of symbolKlines.values()) {
+        klineDataCount += klines.length;
+      }
+    }
+
+    return {
+      marketDataCount: this.marketDataCache.size,
+      ticker24hrCount: this.ticker24hrCache.size,
+      klineDataCount,
+      depthDataCount: this.depthDataCache.size,
+      totalMemoryUsage: this.estimateMemoryUsage()
+    };
+  }
+
+  /**
+   * 清理过期数据
+   */
+  public cleanup(): void {
+    const now = Date.now();
+    const expiredKeys: string[] = [];
+
+    // 检查过期数据
+    for (const [key, timestamp] of this.dataTimestamps.entries()) {
+      if (now - timestamp > this.config.maxAge) {
+        expiredKeys.push(key);
+      }
+    }
+
+    // 删除过期数据
+    expiredKeys.forEach(key => {
+      const [type, symbol, interval] = key.split('_');
+      
+      switch (type) {
+        case 'market':
+          this.marketDataCache.delete(symbol);
+          break;
+        case 'ticker':
+          this.ticker24hrCache.delete(symbol);
+          break;
+        case 'kline':
+          const symbolKlines = this.klineDataCache.get(symbol);
+          if (symbolKlines) {
+            symbolKlines.delete(interval);
+            if (symbolKlines.size === 0) {
+              this.klineDataCache.delete(symbol);
+            }
+          }
+          break;
+        case 'depth':
+          this.depthDataCache.delete(symbol);
+          break;
+      }
+      
+      this.dataTimestamps.delete(key);
+    });
+
+    if (expiredKeys.length > 0) {
+      console.log(`🧹 清理了 ${expiredKeys.length} 条过期数据`);
+    }
+  }
+
+  /**
+   * 清空所有缓存
+   */
+  public clear(): void {
+    this.marketDataCache.clear();
+    this.ticker24hrCache.clear();
+    this.klineDataCache.clear();
+    this.depthDataCache.clear();
+    this.dataTimestamps.clear();
+    console.log('🗑️  已清空所有缓存数据');
+  }
+
+  /**
+   * 获取指定交易对的所有数据
+   */
+  public getSymbolData(symbol: string): {
+    marketData: MarketData | null;
+    ticker24hr: Ticker24hr | null;
+    klineData: Map<string, KlineData[]>;
+    depthData: DepthData | null;
+  } {
+    return {
+      marketData: this.getMarketData(symbol),
+      ticker24hr: this.getTicker24hr(symbol),
+      klineData: this.klineDataCache.get(symbol) || new Map(),
+      depthData: this.getDepthData(symbol)
+    };
+  }
+
+  /**
+   * 检查数据是否过期
+   */
+  public isDataExpired(symbol: string, type: 'market' | 'ticker' | 'kline' | 'depth', interval?: string): boolean {
+    const key = interval ? `${type}_${symbol}_${interval}` : `${type}_${symbol}`;
+    const timestamp = this.dataTimestamps.get(key);
+    
+    if (!timestamp) return true;
+    
+    return Date.now() - timestamp > this.config.maxAge;
+  }
+
+  /**
+   * 获取数据更新时间
+   */
+  public getDataTimestamp(symbol: string, type: 'market' | 'ticker' | 'kline' | 'depth', interval?: string): number | null {
+    const key = interval ? `${type}_${symbol}_${interval}` : `${type}_${symbol}`;
+    return this.dataTimestamps.get(key) || null;
+  }
+
+  /**
+   * 启动清理定时器
+   */
+  private startCleanupTimer(): void {
+    this.cleanupTimer = setInterval(() => {
+      this.cleanup();
+    }, this.config.cleanupInterval);
+  }
+
+  /**
+   * 停止清理定时器
+   */
+  public stopCleanupTimer(): void {
+    if (this.cleanupTimer) {
+      clearInterval(this.cleanupTimer);
+      this.cleanupTimer = null;
+    }
+  }
+
+  /**
+   * 估算内存使用量
+   */
+  private estimateMemoryUsage(): number {
+    let totalSize = 0;
+
+    // 估算 Map 开销
+    totalSize += this.marketDataCache.size * 200; // 每个 MarketData 约 200 字节
+    totalSize += this.ticker24hrCache.size * 300; // 每个 Ticker24hr 约 300 字节
+    totalSize += this.depthDataCache.size * 400;  // 每个 DepthData 约 400 字节
+
+    // 估算 K线数据
+    for (const symbolKlines of this.klineDataCache.values()) {
+      for (const klines of symbolKlines.values()) {
+        totalSize += klines.length * 150; // 每个 KlineData 约 150 字节
+      }
+    }
+
+    // 估算时间戳 Map
+    totalSize += this.dataTimestamps.size * 50; // 每个时间戳约 50 字节
+
+    return totalSize;
+  }
+
+  /**
+   * 销毁缓存管理器
+   */
+  public destroy(): void {
+    this.stopCleanupTimer();
+    this.clear();
+  }
+}
+
+

+ 583 - 0
src/market/marketDataManager.ts

@@ -0,0 +1,583 @@
+import WebSocket from 'ws';
+import { EventEmitter } from 'events';
+
+/**
+ * 行情数据类型
+ */
+export interface MarketData {
+  symbol: string;
+  price: number;
+  volume: number;
+  change: number;
+  changePercent: number;
+  high: number;
+  low: number;
+  open: number;
+  close: number;
+  timestamp: number;
+  bid: number;
+  ask: number;
+  bidSize: number;
+  askSize: number;
+}
+
+/**
+ * 24小时行情数据
+ */
+export interface Ticker24hr {
+  symbol: string;
+  priceChange: string;
+  priceChangePercent: string;
+  weightedAvgPrice: string;
+  prevClosePrice: string;
+  lastPrice: string;
+  lastQty: string;
+  bidPrice: string;
+  bidQty: string;
+  askPrice: string;
+  askQty: string;
+  openPrice: string;
+  highPrice: string;
+  lowPrice: string;
+  volume: string;
+  quoteVolume: string;
+  openTime: number;
+  closeTime: number;
+  firstId: number;
+  lastId: number;
+  count: number;
+}
+
+/**
+ * K线数据
+ */
+export interface KlineData {
+  symbol: string;
+  interval: string;
+  openTime: number;
+  open: number;
+  high: number;
+  low: number;
+  close: number;
+  volume: number;
+  closeTime: number;
+  quoteVolume: number;
+  trades: number;
+  takerBuyBaseVolume: number;
+  takerBuyQuoteVolume: number;
+}
+
+/**
+ * 深度数据
+ */
+export interface DepthData {
+  symbol: string;
+  bids: [string, string][]; // [price, quantity]
+  asks: [string, string][]; // [price, quantity]
+  lastUpdateId: number;
+}
+
+/**
+ * 行情数据管理器
+ * 通过 WebSocket 实时更新内存中的行情信息
+ */
+export class MarketDataManager extends EventEmitter {
+  private ws: WebSocket | null = null;
+  private reconnectTimer: NodeJS.Timeout | null = null;
+  private pingTimer: NodeJS.Timeout | null = null;
+  private pongTimer: NodeJS.Timeout | null = null;
+  
+  // 内存中的行情数据
+  private marketDataMap: Map<string, MarketData> = new Map();
+  private ticker24hrMap: Map<string, Ticker24hr> = new Map();
+  private klineDataMap: Map<string, Map<string, KlineData[]>> = new Map(); // symbol -> interval -> klines
+  private depthDataMap: Map<string, DepthData> = new Map();
+  
+  // 订阅的符号列表
+  private subscribedSymbols: Set<string> = new Set();
+  private subscribedIntervals: Set<string> = new Set();
+  
+  // 配置
+  private wsUrl: string;
+  private reconnectInterval: number;
+  private maxReconnectAttempts: number;
+  private reconnectAttempts: number = 0;
+  private isConnected: boolean = false;
+  private isReconnecting: boolean = false;
+
+  constructor(
+    wsUrl: string = 'wss://fstream.binance.com/ws',
+    reconnectInterval: number = 5000,
+    maxReconnectAttempts: number = 10
+  ) {
+    super();
+    this.wsUrl = wsUrl;
+    this.reconnectInterval = reconnectInterval;
+    this.maxReconnectAttempts = maxReconnectAttempts;
+  }
+
+  /**
+   * 连接 WebSocket
+   */
+  public connect(): Promise<void> {
+    return new Promise((resolve, reject) => {
+      if (this.isConnected || this.isReconnecting) {
+        resolve();
+        return;
+      }
+
+      this.isReconnecting = true;
+      
+      try {
+        this.ws = new WebSocket(this.wsUrl);
+        
+        this.ws.on('open', () => {
+          console.log('📡 WebSocket 连接成功');
+          this.isConnected = true;
+          this.isReconnecting = false;
+          this.reconnectAttempts = 0;
+          this.startPingTimer();
+          this.resubscribeAll();
+          this.emit('connected');
+          resolve();
+        });
+
+        this.ws.on('message', (data: WebSocket.Data) => {
+          this.handleMessage(data);
+        });
+
+        this.ws.on('close', (code: number, reason: Buffer) => {
+          console.log(`📡 WebSocket 连接关闭: ${code} - ${reason.toString()}`);
+          this.isConnected = false;
+          this.stopPingTimer();
+          this.emit('disconnected', code, reason.toString());
+          this.scheduleReconnect();
+        });
+
+        this.ws.on('error', (error: Error) => {
+          console.error('📡 WebSocket 错误:', error);
+          this.emit('error', error);
+          reject(error);
+        });
+
+        this.ws.on('pong', () => {
+          this.handlePong();
+        });
+
+      } catch (error) {
+        this.isReconnecting = false;
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 断开连接
+   */
+  public disconnect(): void {
+    if (this.ws) {
+      this.ws.close();
+      this.ws = null;
+    }
+    this.isConnected = false;
+    this.isReconnecting = false;
+    this.stopPingTimer();
+    this.clearReconnectTimer();
+  }
+
+  /**
+   * 订阅行情数据
+   * @param symbols 交易对列表
+   * @param intervals K线间隔列表(可选)
+   */
+  public subscribeMarketData(symbols: string[], intervals: string[] = []): void {
+    const streams: string[] = [];
+    
+    // 添加 24小时行情订阅
+    symbols.forEach(symbol => {
+      const streamName = `${symbol.toLowerCase()}@ticker`;
+      streams.push(streamName);
+      this.subscribedSymbols.add(symbol);
+    });
+
+    // 添加 K线数据订阅
+    intervals.forEach(interval => {
+      symbols.forEach(symbol => {
+        const streamName = `${symbol.toLowerCase()}@kline_${interval}`;
+        streams.push(streamName);
+        this.subscribedIntervals.add(interval);
+      });
+    });
+
+    // 添加深度数据订阅
+    symbols.forEach(symbol => {
+      const streamName = `${symbol.toLowerCase()}@depth20@100ms`;
+      streams.push(streamName);
+    });
+
+    if (streams.length > 0) {
+      this.subscribe(streams);
+    }
+  }
+
+  /**
+   * 取消订阅
+   * @param symbols 交易对列表
+   */
+  public unsubscribeMarketData(symbols: string[]): void {
+    const streams: string[] = [];
+    
+    symbols.forEach(symbol => {
+      const tickerStream = `${symbol.toLowerCase()}@ticker`;
+      const depthStream = `${symbol.toLowerCase()}@depth20@100ms`;
+      streams.push(tickerStream, depthStream);
+      
+      this.subscribedSymbols.delete(symbol);
+      this.marketDataMap.delete(symbol);
+      this.ticker24hrMap.delete(symbol);
+      this.depthDataMap.delete(symbol);
+    });
+
+    if (streams.length > 0) {
+      this.unsubscribe(streams);
+    }
+  }
+
+  /**
+   * 获取行情数据
+   * @param symbol 交易对
+   */
+  public getMarketData(symbol: string): MarketData | null {
+    return this.marketDataMap.get(symbol) || null;
+  }
+
+  /**
+   * 获取所有行情数据
+   */
+  public getAllMarketData(): Map<string, MarketData> {
+    return new Map(this.marketDataMap);
+  }
+
+  /**
+   * 获取24小时行情数据
+   * @param symbol 交易对
+   */
+  public getTicker24hr(symbol: string): Ticker24hr | null {
+    return this.ticker24hrMap.get(symbol) || null;
+  }
+
+  /**
+   * 获取所有24小时行情数据
+   */
+  public getAllTicker24hr(): Map<string, Ticker24hr> {
+    return new Map(this.ticker24hrMap);
+  }
+
+  /**
+   * 获取K线数据
+   * @param symbol 交易对
+   * @param interval 时间间隔
+   * @param limit 数量限制
+   */
+  public getKlineData(symbol: string, interval: string, limit: number = 100): KlineData[] {
+    const symbolKlines = this.klineDataMap.get(symbol);
+    if (!symbolKlines) return [];
+    
+    const klines = symbolKlines.get(interval);
+    if (!klines) return [];
+    
+    return klines.slice(-limit);
+  }
+
+  /**
+   * 获取深度数据
+   * @param symbol 交易对
+   */
+  public getDepthData(symbol: string): DepthData | null {
+    return this.depthDataMap.get(symbol) || null;
+  }
+
+  /**
+   * 获取订阅的符号列表
+   */
+  public getSubscribedSymbols(): string[] {
+    return Array.from(this.subscribedSymbols);
+  }
+
+  /**
+   * 检查是否已连接
+   */
+  public isConnectedToWebSocket(): boolean {
+    return this.isConnected;
+  }
+
+  /**
+   * 处理 WebSocket 消息
+   */
+  private handleMessage(data: WebSocket.Data): void {
+    try {
+      const message = JSON.parse(data.toString());
+      
+      if (message.e === '24hrTicker') {
+        this.handleTicker24hr(message);
+      } else if (message.e === 'kline') {
+        this.handleKlineData(message);
+      } else if (message.e === 'depthUpdate') {
+        this.handleDepthData(message);
+      } else if (message.result === null && message.id) {
+        // 订阅确认消息
+        console.log('✅ 订阅成功:', message);
+      } else if (message.pong) {
+        // Pong 响应
+        this.handlePong();
+      }
+    } catch (error) {
+      console.error('❌ 解析 WebSocket 消息失败:', error);
+    }
+  }
+
+  /**
+   * 处理24小时行情数据
+   */
+  private handleTicker24hr(data: any): void {
+    const symbol = data.s;
+    const tickerData: Ticker24hr = {
+      symbol: data.s,
+      priceChange: data.P,
+      priceChangePercent: data.P,
+      weightedAvgPrice: data.w,
+      prevClosePrice: data.x,
+      lastPrice: data.c,
+      lastQty: data.Q,
+      bidPrice: data.b,
+      bidQty: data.B,
+      askPrice: data.a,
+      askQty: data.A,
+      openPrice: data.o,
+      highPrice: data.h,
+      lowPrice: data.l,
+      volume: data.v,
+      quoteVolume: data.q,
+      openTime: data.O,
+      closeTime: data.C,
+      firstId: data.F,
+      lastId: data.L,
+      count: data.n
+    };
+
+    this.ticker24hrMap.set(symbol, tickerData);
+
+    // 转换为 MarketData 格式
+    const marketData: MarketData = {
+      symbol: symbol,
+      price: parseFloat(data.c),
+      volume: parseFloat(data.v),
+      change: parseFloat(data.P),
+      changePercent: parseFloat(data.P),
+      high: parseFloat(data.h),
+      low: parseFloat(data.l),
+      open: parseFloat(data.o),
+      close: parseFloat(data.c),
+      timestamp: data.E,
+      bid: parseFloat(data.b),
+      ask: parseFloat(data.a),
+      bidSize: parseFloat(data.B),
+      askSize: parseFloat(data.A)
+    };
+
+    this.marketDataMap.set(symbol, marketData);
+    this.emit('ticker24hr', tickerData);
+    this.emit('marketData', marketData);
+  }
+
+  /**
+   * 处理K线数据
+   */
+  private handleKlineData(data: any): void {
+    const symbol = data.s;
+    const interval = data.k.i;
+    const kline = data.k;
+
+    const klineData: KlineData = {
+      symbol: symbol,
+      interval: interval,
+      openTime: kline.t,
+      open: parseFloat(kline.o),
+      high: parseFloat(kline.h),
+      low: parseFloat(kline.l),
+      close: parseFloat(kline.c),
+      volume: parseFloat(kline.v),
+      closeTime: kline.T,
+      quoteVolume: parseFloat(kline.q),
+      trades: kline.n,
+      takerBuyBaseVolume: parseFloat(kline.V),
+      takerBuyQuoteVolume: parseFloat(kline.Q)
+    };
+
+    // 初始化数据结构
+    if (!this.klineDataMap.has(symbol)) {
+      this.klineDataMap.set(symbol, new Map());
+    }
+    
+    const symbolKlines = this.klineDataMap.get(symbol)!;
+    if (!symbolKlines.has(interval)) {
+      symbolKlines.set(interval, []);
+    }
+
+    const klines = symbolKlines.get(interval)!;
+    
+    // 更新或添加K线数据
+    const existingIndex = klines.findIndex(k => k.openTime === klineData.openTime);
+    if (existingIndex >= 0) {
+      klines[existingIndex] = klineData;
+    } else {
+      klines.push(klineData);
+      // 保持最多1000条记录
+      if (klines.length > 1000) {
+        klines.splice(0, klines.length - 1000);
+      }
+    }
+
+    this.emit('kline', klineData);
+  }
+
+  /**
+   * 处理深度数据
+   */
+  private handleDepthData(data: any): void {
+    const symbol = data.s;
+    
+    const depthData: DepthData = {
+      symbol: symbol,
+      bids: data.b,
+      asks: data.a,
+      lastUpdateId: data.u
+    };
+
+    this.depthDataMap.set(symbol, depthData);
+    this.emit('depth', depthData);
+  }
+
+  /**
+   * 发送订阅请求
+   */
+  private subscribe(streams: string[]): void {
+    if (!this.ws || !this.isConnected) {
+      console.warn('⚠️  WebSocket 未连接,无法订阅');
+      return;
+    }
+
+    const message = {
+      method: 'SUBSCRIBE',
+      params: streams,
+      id: Date.now()
+    };
+
+    this.ws.send(JSON.stringify(message));
+    console.log('📡 订阅行情数据:', streams);
+  }
+
+  /**
+   * 发送取消订阅请求
+   */
+  private unsubscribe(streams: string[]): void {
+    if (!this.ws || !this.isConnected) {
+      return;
+    }
+
+    const message = {
+      method: 'UNSUBSCRIBE',
+      params: streams,
+      id: Date.now()
+    };
+
+    this.ws.send(JSON.stringify(message));
+    console.log('📡 取消订阅:', streams);
+  }
+
+  /**
+   * 重新订阅所有数据
+   */
+  private resubscribeAll(): void {
+    if (this.subscribedSymbols.size > 0) {
+      const symbols = Array.from(this.subscribedSymbols);
+      const intervals = Array.from(this.subscribedIntervals);
+      this.subscribeMarketData(symbols, intervals);
+    }
+  }
+
+  /**
+   * 启动 Ping 定时器
+   */
+  private startPingTimer(): void {
+    this.pingTimer = setInterval(() => {
+      if (this.ws && this.isConnected) {
+        this.ws.ping();
+        this.startPongTimer();
+      }
+    }, 30000); // 每30秒发送一次 ping
+  }
+
+  /**
+   * 停止 Ping 定时器
+   */
+  private stopPingTimer(): void {
+    if (this.pingTimer) {
+      clearInterval(this.pingTimer);
+      this.pingTimer = null;
+    }
+  }
+
+  /**
+   * 启动 Pong 超时定时器
+   */
+  private startPongTimer(): void {
+    this.pongTimer = setTimeout(() => {
+      console.warn('⚠️  Pong 超时,重新连接 WebSocket');
+      this.disconnect();
+      this.scheduleReconnect();
+    }, 10000); // 10秒超时
+  }
+
+  /**
+   * 处理 Pong 响应
+   */
+  private handlePong(): void {
+    if (this.pongTimer) {
+      clearTimeout(this.pongTimer);
+      this.pongTimer = null;
+    }
+  }
+
+  /**
+   * 安排重连
+   */
+  private scheduleReconnect(): void {
+    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
+      console.error('❌ 达到最大重连次数,停止重连');
+      this.emit('maxReconnectAttemptsReached');
+      return;
+    }
+
+    this.clearReconnectTimer();
+    this.reconnectTimer = setTimeout(() => {
+      this.reconnectAttempts++;
+      console.log(`🔄 尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
+      this.connect().catch(error => {
+        console.error('❌ 重连失败:', error);
+      });
+    }, this.reconnectInterval);
+  }
+
+  /**
+   * 清除重连定时器
+   */
+  private clearReconnectTimer(): void {
+    if (this.reconnectTimer) {
+      clearTimeout(this.reconnectTimer);
+      this.reconnectTimer = null;
+    }
+  }
+}
+
+

+ 19 - 0
src/risk/riskAssessor.ts

@@ -0,0 +1,19 @@
+import { Position } from '../types/core';
+
+export interface RiskSummary {
+  leverageRatio: number;
+  var: number;
+  maxDrawdown: number;
+}
+
+export class RiskAssessor {
+  evaluatePosition(position: Position): RiskSummary {
+    // TODO: 实现真实风险评估逻辑
+    const leverageRatio = position.leverage;
+    const varValue = Math.abs(position.unrealizedPnL) * 0.1;
+    const maxDrawdown = Math.abs(position.unrealizedPnL) * 0.2;
+    return { leverageRatio, var: varValue, maxDrawdown };
+  }
+}
+
+export const riskAssessor = new RiskAssessor();

+ 45 - 0
src/strategies/dualGridBot.ts

@@ -0,0 +1,45 @@
+import 'dotenv/config';
+import { AsterTradingClient } from '../dex/aster/tradingClient';
+import { loadAsterTradingConfig, toAsterTradingConfig, loadAsterTradingConfig2 } from '../dex/aster/tradingConfig';
+import { loadGridEnv, toDualGridConfig } from '../dex/aster/gridConfig';
+import { DualAccountGridExecutor } from '../dex/aster/gridDualExecutor';
+
+async function startDualGrid(): Promise<void> {
+  const gridEnv = loadGridEnv();
+  const gridCfg = toDualGridConfig(gridEnv);
+
+  const env1 = loadAsterTradingConfig();
+  const cfg1 = toAsterTradingConfig(env1);
+  const env2 = loadAsterTradingConfig2();
+  const cfg2 = toAsterTradingConfig(env2);
+  const a = new AsterTradingClient(cfg1);
+  const b = new AsterTradingClient(cfg2);
+
+  const exec = new DualAccountGridExecutor(a, b, gridCfg);
+
+  exec.on('tick', ({ mid, stats }) => {
+    console.log(`⏱️ mid=${mid?.toFixed(6)} placedLong=${stats.placedLong} placedShort=${stats.placedShort} errors=${stats.errors}`);
+  });
+  exec.on('error', (e) => {
+    console.error('❌ grid error:', (e as any)?.response?.data || (e as any)?.message || e);
+  });
+
+  console.log(`🚀 启动双账户网格策略:symbol=${gridCfg.symbol}, lev=${gridCfg.leverage}x, base=$${gridCfg.baseNotionalUsd}, step=${gridCfg.gridStepBps}bps, count=${gridCfg.gridCount}`);
+  await exec.start();
+
+  const shutdown = async () => {
+    console.log('🛑 停止策略...');
+    exec.stop();
+    process.exit(0);
+  };
+  process.on('SIGINT', shutdown);
+  process.on('SIGTERM', shutdown);
+}
+
+startDualGrid().catch((e) => {
+  console.error('❌ 启动失败:', (e as any)?.response?.data || (e as any)?.message || e);
+  process.exit(1);
+});
+
+
+

+ 85 - 0
src/tester.ts

@@ -0,0 +1,85 @@
+import 'dotenv/config'
+import { FutureConnector } from './cex/binance/futuresConnector'
+import { DerivativesTradingUsdsFuturesRestAPI } from '@binance/derivatives-trading-usds-futures'
+
+export async function greeter() {
+  const futureConnector = new FutureConnector(process.env.API_KEY, process.env.API_SECRET)
+
+  // 测试获取当前所有开仓订单
+  console.log('=== 测试获取当前所有开仓订单 ===')
+  await futureConnector.getCurrentAllOpenPosition()
+
+  // 测试开仓方法 - 基础用法
+  console.log('\n=== 测试基础开仓 ===')
+  try {
+    const result = await futureConnector.openPosition(
+      'BTCUSDT', // 交易对
+      DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.BUY, // 买入
+      0.001, // 数量
+      50000, // 价格
+    )
+    console.log('基础开仓结果:', result)
+  } catch (error) {
+    console.log('基础开仓测试失败:', error)
+  }
+
+  // 测试开仓方法 - 带可选参数
+  console.log('\n=== 测试带可选参数的开仓 ===')
+  try {
+    const result = await futureConnector.openPosition(
+      'ETHUSDT', // 交易对
+      DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL, // 卖出
+      0.01, // 数量
+      3000, // 价格
+      {
+        // 订单类型
+        type: 'LIMIT',
+        // 订单有效期
+        timeInForce: DerivativesTradingUsdsFuturesRestAPI.NewOrderTimeInForceEnum.GTC,
+        // 仓位方向
+        positionSide: DerivativesTradingUsdsFuturesRestAPI.NewOrderPositionSideEnum.BOTH,
+        // 是否仅减仓
+        reduceOnly: 'false',
+        // 订单响应类型
+        newOrderRespType: DerivativesTradingUsdsFuturesRestAPI.NewOrderNewOrderRespTypeEnum.RESULT,
+        // 客户端订单ID
+        newClientOrderId: `test_order_${Date.now()}`,
+        // 订单标签
+        orderTag: 'test_order',
+      },
+    )
+    console.log('带可选参数开仓结果:', result)
+  } catch (error) {
+    console.log('带可选参数开仓测试失败:', error)
+  }
+
+  // 测试止损单
+  console.log('\n=== 测试止损单 ===')
+  try {
+    const result = await futureConnector.openPosition(
+      'BTCUSDT',
+      DerivativesTradingUsdsFuturesRestAPI.NewOrderSideEnum.SELL,
+      0.001,
+      48000, // 止损价格
+      {
+        type: 'STOP_MARKET',
+        stopPrice: 48000,
+        reduceOnly: 'true',
+        newOrderRespType: DerivativesTradingUsdsFuturesRestAPI.NewOrderNewOrderRespTypeEnum.RESULT,
+      },
+    )
+    console.log('止损单结果:', result)
+  } catch (error) {
+    console.log('止损单测试失败:', error)
+  }
+}
+
+// await greeter().then()
+
+async function testData() {
+  const futureConnector = new FutureConnector(process.env.API_KEY, process.env.API_SECRET)
+  console.log(await futureConnector.getTradeHistory('NEIROETHUSDT'))
+  console.log(await futureConnector.getOrderHistory('NEIROETHUSDT'))
+}
+
+await testData()

+ 188 - 0
src/types/core/index.ts

@@ -0,0 +1,188 @@
+// 核心交易模块的类型定义
+
+/**
+ * 交易对信息
+ */
+export interface TradingPair {
+  symbol: string;           // 交易对符号,如 "BTC-USDT"
+  baseAsset: string;        // 基础资产
+  quoteAsset: string;       // 计价资产
+  contractAddress?: string; // 合约地址(Perp DEX)
+  decimals: number;         // 小数位数
+}
+
+/**
+ * 市场数据接口
+ */
+export interface MarketData {
+  symbol: string;
+  price: number;
+  timestamp: number;
+  volume24h: number;
+  change24h: number;
+  high24h: number;
+  low24h: number;
+  bidPrice: number;
+  askPrice: number;
+  bidQty: number;
+  askQty: number;
+  fundingRate?: number;     // 资金费率(Perp DEX)
+}
+
+/**
+ * 订单簿数据
+ */
+export interface OrderBook {
+  symbol: string;
+  timestamp: number;
+  bids: Array<[number, number]>; // [price, quantity]
+  asks: Array<[number, number]>; // [price, quantity]
+  lastUpdateId: number;
+}
+
+/**
+ * K线数据
+ */
+export interface KlineData {
+  symbol: string;
+  interval: string;
+  openTime: number;
+  open: number;
+  high: number;
+  low: number;
+  close: number;
+  volume: number;
+  closeTime: number;
+  quoteVolume: number;
+  trades: number;
+}
+
+/**
+ * 持仓信息
+ */
+export interface Position {
+  id: string;
+  symbol: string;
+  side: 'long' | 'short';
+  size: number;              // 仓位大小
+  entryPrice: number;        // 入场价格
+  markPrice: number;         // 标记价格
+  leverage: number;          // 杠杆倍数
+  unrealizedPnL: number;     // 未实现盈亏
+  realizedPnL: number;       // 已实现盈亏
+  fundingFees: number;       // 资金费用
+  liquidationPrice: number;  // 强平价格
+  margin: number;            // 保证金
+  timestamp: number;
+  isOpen: boolean;           // 是否为开仓
+}
+
+/**
+ * 交易订单
+ */
+export interface Order {
+  id: string;
+  symbol: string;
+  side: 'buy' | 'sell';
+  type: 'market' | 'limit' | 'stop' | 'stop_market';
+  quantity: number;
+  price?: number;
+  stopPrice?: number;
+  status: 'pending' | 'filled' | 'cancelled' | 'rejected';
+  filledQuantity: number;
+  remainingQuantity: number;
+  timestamp: number;
+  fee?: number;
+  feeAsset?: string;
+}
+
+/**
+ * 交易信号
+ */
+export interface TradingSignal {
+  symbol: string;
+  action: 'open_long' | 'open_short' | 'close' | 'hedge';
+  confidence: number;        // 置信度 0-1
+  quantity: number;          // 建议数量
+  price?: number;            // 建议价格
+  leverage?: number;         // 建议杠杆
+  reason: string;            // 信号原因
+  indicators: Record<string, number>; // 技术指标
+  timestamp: number;
+}
+
+/**
+ * 对冲配置
+ */
+export interface HedgeConfig {
+  symbol: string;
+  hedgeRatio: number;        // 对冲比例
+  hedgeMethod: 'spot' | 'perp' | 'options'; // 对冲方式
+  maxSlippage: number;       // 最大滑点
+  rebalanceThreshold: number; // 重新平衡阈值
+  minHedgeInterval: number;  // 最小对冲间隔
+}
+
+/**
+ * 交易执行结果
+ */
+export interface ExecutionResult {
+  success: boolean;
+  orderId?: string;
+  executedQuantity: number;
+  executedPrice: number;
+  fee: number;
+  error?: string;
+  txHash?: string;           // 链上交易哈希
+  gasUsed?: number;          // gas 使用量
+}
+
+/**
+ * 市场数据源
+ */
+export interface MarketDataSource {
+  name: string;
+  type: 'cex' | 'dex' | 'oracle';
+  baseUrl: string;
+  wsUrl?: string;
+  apiKey?: string;
+  secret?: string;
+  chainId?: number;          // 链ID
+  rpcUrl?: string;
+}
+
+/**
+ * 价格数据
+ */
+export interface PriceData {
+  symbol: string;
+  price: number;
+  source: string;
+  timestamp: number;
+  confidence: number;        // 数据置信度
+}
+
+/**
+ * 资金费率数据
+ */
+export interface FundingRateData {
+  symbol: string;
+  fundingRate: number;
+  markPrice: number;
+  indexPrice: number;
+  estimatedSettlePrice: number;
+  nextFundingTime: number;
+  timestamp: number;
+}
+
+/**
+ * 流动性数据
+ */
+export interface LiquidityData {
+  symbol: string;
+  poolAddress: string;
+  reserve0: number;
+  reserve1: number;
+  fee: number;
+  timestamp: number;
+}

+ 364 - 0
src/types/infrastructure/index.ts

@@ -0,0 +1,364 @@
+// 基础设施模块的类型定义
+
+/**
+ * 钱包配置
+ */
+export interface WalletConfig {
+  type: 'hd' | 'private_key' | 'mnemonic' | 'hardware';
+  chainId: number;
+  rpcUrl: string;
+  gasPrice?: number;
+  gasLimit?: number;
+
+  // HD 钱包配置
+  mnemonic?: string;
+  derivationPath?: string;
+  accountIndex?: number;
+
+  // 私钥配置
+  privateKey?: string;
+
+  // 硬件钱包配置
+  hardwareType?: 'ledger' | 'trezor';
+  hardwarePath?: string;
+}
+
+/**
+ * 账户信息
+ */
+export interface AccountInfo {
+  address: string;
+  chainId: number;
+  balance: Record<string, number>; // token -> balance
+  nonce: number;
+  gasPrice: number;
+
+  // 合约地址
+  contracts: {
+    perpContract?: string;
+    spotRouter?: string;
+    oracle?: string;
+  };
+
+  // 权限
+  permissions: {
+    canTrade: boolean;
+    canWithdraw: boolean;
+    canManage: boolean;
+  };
+}
+
+/**
+ * 交易配置
+ */
+export interface TransactionConfig {
+  gasPrice: number;
+  gasLimit: number;
+  maxSlippage: number;
+  deadline: number;
+  approveAmount?: number;
+  useFlashbots?: boolean;
+}
+
+/**
+ * 系统配置
+ */
+export interface SystemConfig {
+  // 网络配置
+  networks: Record<string, {
+    chainId: number;
+    rpcUrl: string;
+    wsUrl?: string;
+    explorerUrl: string;
+    gasPrice: number;
+  }>;
+
+  // API 配置
+  apis: {
+    binance?: {
+      apiKey: string;
+      secret: string;
+      testnet: boolean;
+    };
+    coingecko?: {
+      apiKey: string;
+    };
+    chainlink?: {
+      feeds: Record<string, string>; // symbol -> feed address
+    };
+  };
+
+  // 数据库配置
+  database: {
+    host: string;
+    port: number;
+    database: string;
+    username: string;
+    password: string;
+    dialect: 'postgres' | 'mysql' | 'sqlite';
+    logging: boolean;
+    pool: {
+      max: number;
+      min: number;
+      acquire: number;
+      idle: number;
+    };
+  };
+
+  // Redis 配置
+  redis: {
+    host: string;
+    port: number;
+    password?: string;
+    db: number;
+    keyPrefix: string;
+  };
+
+  // 风险配置
+  risk: {
+    maxLeverage: number;
+    maxPositionSize: number;
+    maxDrawdown: number;
+    minMarginRatio: number;
+    maxSlippage: number;
+  };
+
+  // 交易配置
+  trading: {
+    defaultSlippage: number;
+    minOrderValue: number;
+    maxOrderValue: number;
+    rebalanceInterval: number;
+    hedgeThreshold: number;
+  };
+
+  // 监控配置
+  monitoring: {
+    alertCooldown: number;
+    metricsInterval: number;
+    healthCheckInterval: number;
+    logLevel: 'debug' | 'info' | 'warn' | 'error';
+  };
+}
+
+/**
+ * 数据库模型定义
+ */
+export interface DatabaseSchema {
+  // 交易表
+  trades: {
+    id: string;
+    symbol: string;
+    side: 'buy' | 'sell';
+    type: string;
+    quantity: number;
+    price: number;
+    fee: number;
+    feeAsset: string;
+    status: string;
+    txHash?: string;
+    timestamp: Date;
+    metadata?: Record<string, any>;
+  };
+
+  // 持仓表
+  positions: {
+    id: string;
+    symbol: string;
+    side: 'long' | 'short';
+    size: number;
+    entryPrice: number;
+    leverage: number;
+    margin: number;
+    unrealizedPnL: number;
+    realizedPnL: number;
+    liquidationPrice: number;
+    status: 'open' | 'closed' | 'liquidated';
+    openedAt: Date;
+    closedAt?: Date;
+    metadata?: Record<string, any>;
+  };
+
+  // 订单表
+  orders: {
+    id: string;
+    symbol: string;
+    side: 'buy' | 'sell';
+    type: string;
+    quantity: number;
+    price?: number;
+    status: string;
+    filledQuantity: number;
+    remainingQuantity: number;
+    fee?: number;
+    txHash?: string;
+    createdAt: Date;
+    updatedAt: Date;
+  };
+
+  // 警报表
+  alerts: {
+    id: string;
+    type: string;
+    severity: string;
+    title: string;
+    message: string;
+    value: number;
+    threshold: number;
+    resolved: boolean;
+    resolvedAt?: Date;
+    createdAt: Date;
+  };
+
+  // 性能指标表
+  metrics: {
+    id: string;
+    name: string;
+    value: number;
+    labels?: Record<string, string>;
+    timestamp: Date;
+  };
+}
+
+/**
+ * 任务调度配置
+ */
+export interface JobConfig {
+  name: string;
+  schedule: string;          // cron 表达式
+  enabled: boolean;
+  retryCount: number;
+  retryDelay: number;
+  timeout: number;
+  params?: Record<string, any>;
+}
+
+/**
+ * 日志配置
+ */
+export interface LogConfig {
+  level: 'debug' | 'info' | 'warn' | 'error';
+  format: 'json' | 'simple' | 'detailed';
+  transports: {
+    console?: {
+      enabled: boolean;
+      level: string;
+      format: string;
+    };
+    file?: {
+      enabled: boolean;
+      level: string;
+      filename: string;
+      maxsize: number;
+      maxFiles: number;
+    };
+    telegram?: {
+      enabled: boolean;
+      botToken: string;
+      chatId: string;
+      level: string;
+    };
+  };
+}
+
+/**
+ * 通知配置
+ */
+export interface NotificationConfig {
+  telegram: {
+    botToken: string;
+    chatId: string;
+    enabled: boolean;
+    templates: Record<string, string>;
+  };
+
+  email: {
+    host: string;
+    port: number;
+    secure: boolean;
+    auth: {
+      user: string;
+      pass: string;
+    };
+    from: string;
+    to: string[];
+    enabled: boolean;
+    templates: Record<string, string>;
+  };
+
+  slack: {
+    webhook: string;
+    channel: string;
+    enabled: boolean;
+    templates: Record<string, string>;
+  };
+}
+
+/**
+ * 健康检查配置
+ */
+export interface HealthCheckConfig {
+  enabled: boolean;
+  interval: number;          // 检查间隔(秒)
+  timeout: number;           // 超时时间(秒)
+
+  checks: {
+    database?: {
+      enabled: boolean;
+      timeout: number;
+    };
+    redis?: {
+      enabled: boolean;
+      timeout: number;
+    };
+    web3?: {
+      enabled: boolean;
+      timeout: number;
+      networks: number[];
+    };
+    exchange?: {
+      enabled: boolean;
+      timeout: number;
+      apis: string[];
+    };
+  };
+
+  alerts: {
+    enabled: boolean;
+    thresholds: {
+      consecutiveFailures: number;
+      uptime: number;
+    };
+  };
+}
+
+/**
+ * 备份配置
+ */
+export interface BackupConfig {
+  enabled: boolean;
+  schedule: string;          // cron 表达式
+  retention: number;         // 保留天数
+
+  targets: {
+    database?: {
+      enabled: boolean;
+      type: 'full' | 'incremental';
+      compression: boolean;
+    };
+    logs?: {
+      enabled: boolean;
+      compression: boolean;
+    };
+    config?: {
+      enabled: boolean;
+    };
+  };
+
+  storage: {
+    type: 'local' | 's3' | 'ftp';
+    path?: string;
+    bucket?: string;
+    credentials?: Record<string, string>;
+  };
+}

+ 268 - 0
src/types/risk/index.ts

@@ -0,0 +1,268 @@
+// 风险与监控模块的类型定义
+
+/**
+ * 风险指标
+ */
+export interface RiskMetrics {
+  symbol: string;
+  timestamp: number;
+
+  // 基础风险指标
+  var: number;               // 价值-at-风险
+  cvar: number;              // 条件价值-at-风险
+  maxDrawdown: number;       // 最大回撤
+  sharpeRatio: number;       // 夏普比率
+  volatility: number;        // 波动率
+
+  // 持仓风险
+  leverageRatio: number;     // 杠杆比率
+  liquidationRisk: number;   // 强平风险
+  concentrationRisk: number; // 集中度风险
+
+  // 流动性风险
+  slippageRisk: number;      // 滑点风险
+  liquidityRisk: number;     // 流动性风险
+  fundingRateRisk: number;   // 资金费率风险
+
+  // 市场风险
+  correlationRisk: number;   // 相关性风险
+  marketImpact: number;      // 市场冲击
+}
+
+/**
+ * 持仓风险评估
+ */
+export interface PositionRisk {
+  positionId: string;
+  symbol: string;
+  side: 'long' | 'short';
+
+  // 风险值
+  delta: number;             // Delta
+  gamma: number;             // Gamma
+  theta: number;             // Theta
+  vega: number;              // Vega
+  rho: number;               // Rho
+
+  // 风险指标
+  var: number;               // 价值-at-风险
+  cvar: number;              // 条件价值-at-风险
+  liquidationPrice: number;  // 强平价格
+  marginRatio: number;       // 保证金比率
+  unrealizedPnL: number;     // 未实现盈亏
+
+  // 压力测试
+  stressTestResults: {
+    scenario: string;
+    pnl: number;
+    liquidationPrice: number;
+  }[];
+}
+
+/**
+ * 投资组合风险
+ */
+export interface PortfolioRisk {
+  totalValue: number;
+  totalUnrealizedPnL: number;
+  totalRealizedPnL: number;
+
+  // 整体风险指标
+  portfolioVaR: number;
+  portfolioCVaR: number;
+  maxDrawdown: number;
+  sharpeRatio: number;
+  volatility: number;
+
+  // 风险分解
+  marketRisk: number;
+  creditRisk: number;
+  liquidityRisk: number;
+  operationalRisk: number;
+
+  // 相关性矩阵
+  correlationMatrix: Record<string, Record<string, number>>;
+
+  // 风险贡献度
+  riskContributions: Record<string, number>; // symbol -> risk contribution
+
+  timestamp: number;
+}
+
+/**
+ * 警报配置
+ */
+export interface AlertConfig {
+  id: string;
+  name: string;
+  symbol?: string;           // 特定交易对(可选)
+  type: 'price' | 'volume' | 'risk' | 'position' | 'system';
+
+  // 触发条件
+  conditions: {
+    metric: string;          // 监控指标
+    operator: '>' | '<' | '>=' | '<=' | '==' | '!=';
+    threshold: number;       // 阈值
+    window?: number;         // 时间窗口(秒)
+  }[];
+
+  // 警报设置
+  severity: 'low' | 'medium' | 'high' | 'critical';
+  cooldown: number;          // 冷却时间(秒)
+  enabled: boolean;
+
+  // 通知渠道
+  channels: {
+    telegram?: boolean;
+    email?: boolean;
+    slack?: boolean;
+    webhook?: string;
+  };
+
+  // 自动响应
+  autoActions?: {
+    type: 'stop_loss' | 'take_profit' | 'reduce_position' | 'close_position';
+    params: Record<string, any>;
+  }[];
+}
+
+/**
+ * 警报事件
+ */
+export interface AlertEvent {
+  id: string;
+  configId: string;
+  symbol: string;
+  type: string;
+  severity: 'low' | 'medium' | 'high' | 'critical';
+  title: string;
+  message: string;
+  value: number;             // 当前值
+  threshold: number;         // 阈值
+  timestamp: number;
+  resolved?: boolean;
+  resolvedAt?: number;
+
+  // 相关数据
+  context?: Record<string, any>;
+}
+
+/**
+ * 风险阈值配置
+ */
+export interface RiskThresholds {
+  // 持仓风险阈值
+  maxLeverage: number;       // 最大杠杆
+  maxPositionSize: number;   // 最大仓位大小
+  maxDrawdown: number;       // 最大回撤
+  minMarginRatio: number;    // 最小保证金比率
+
+  // 流动性风险阈值
+  maxSlippage: number;       // 最大滑点
+  minLiquidity: number;      // 最小流动性
+  maxGasPrice: number;       // 最大gas价格
+
+  // 市场风险阈值
+  maxVolatility: number;     // 最大波动率
+  maxConcentration: number;  // 最大集中度
+
+  // 系统风险阈值
+  maxLatency: number;        // 最大延迟
+  minUptime: number;         // 最小运行时间
+}
+
+/**
+ * 压力测试场景
+ */
+export interface StressTestScenario {
+  id: string;
+  name: string;
+  description: string;
+
+  // 市场冲击
+  priceShocks: Record<string, number>; // symbol -> price change %
+  volatilityShocks: Record<string, number>; // symbol -> volatility multiplier
+
+  // 流动性冲击
+  liquidityShocks: Record<string, number>; // symbol -> liquidity reduction %
+
+  // 资金费率冲击
+  fundingRateShocks: Record<string, number>; // symbol -> funding rate change
+
+  // 持续时间
+  duration: number;          // 场景持续时间(秒)
+  probability: number;       // 发生概率
+}
+
+/**
+ * 压力测试结果
+ */
+export interface StressTestResult {
+  scenarioId: string;
+  symbol: string;
+  timestamp: number;
+
+  // 结果指标
+  portfolioValue: number;
+  unrealizedPnL: number;
+  realizedPnL: number;
+  marginRatio: number;
+  liquidationRisk: number;
+
+  // 风险指标
+  var: number;
+  cvar: number;
+  maxDrawdown: number;
+
+  // 持仓变化
+  positionChanges: Record<string, {
+    size: number;
+    unrealizedPnL: number;
+    liquidationPrice: number;
+  }>;
+
+  // 是否触发强平
+  liquidations: string[];    // 被强平的持仓ID列表
+}
+
+/**
+ * 风险报告
+ */
+export interface RiskReport {
+  id: string;
+  timestamp: number;
+  period: {
+    start: number;
+    end: number;
+  };
+
+  // 总体风险概览
+  overview: {
+    totalValue: number;
+    totalPnL: number;
+    sharpeRatio: number;
+    maxDrawdown: number;
+    winRate: number;
+  };
+
+  // 风险分解
+  riskBreakdown: {
+    marketRisk: number;
+    creditRisk: number;
+    liquidityRisk: number;
+    operationalRisk: number;
+  };
+
+  // 持仓风险详情
+  positionRisks: Record<string, PositionRisk>;
+
+  // 警报统计
+  alertStats: {
+    total: number;
+    bySeverity: Record<string, number>;
+    byType: Record<string, number>;
+  };
+
+  // 建议
+  recommendations: string[];
+}

+ 5 - 0
src/utils/events.ts

@@ -0,0 +1,5 @@
+import { EventEmitter } from 'events';
+
+class EventBus extends EventEmitter {}
+
+export const eventBus = new EventBus();

+ 17 - 0
src/utils/logger.ts

@@ -0,0 +1,17 @@
+import winston from 'winston';
+
+const { combine, timestamp, printf, colorize } = winston.format;
+
+const logFormat = printf(({ level, message, timestamp }) => {
+  return `${timestamp} [${level}] ${message}`;
+});
+
+export const logger = winston.createLogger({
+  level: process.env.LOG_LEVEL || 'info',
+  format: combine(timestamp(), logFormat),
+  transports: [
+    new winston.transports.Console({
+      format: combine(colorize(), timestamp(), logFormat)
+    })
+  ]
+});

+ 20 - 0
src/utils/math.ts

@@ -0,0 +1,20 @@
+export function toFixed(num: number, decimals: number = 8): number {
+  const factor = Math.pow(10, decimals);
+  return Math.round(num * factor) / factor;
+}
+
+export function percentChange(from: number, to: number): number {
+  if (from === 0) return 0;
+  return ((to - from) / from) * 100;
+}
+
+// 简单移动平均
+export function sma(values: number[], period: number): number[] {
+  const result: number[] = [];
+  for (let i = 0; i <= values.length - period; i++) {
+    const window = values.slice(i, i + period);
+    const avg = window.reduce((a, b) => a + b, 0) / period;
+    result.push(avg);
+  }
+  return result;
+}

+ 9 - 0
src/utils/web3.ts

@@ -0,0 +1,9 @@
+import { ethers } from 'ethers';
+
+export function getProvider(rpcUrl: string): ethers.JsonRpcProvider {
+  return new ethers.JsonRpcProvider(rpcUrl);
+}
+
+export function getWallet(privateKey: string, provider: ethers.JsonRpcProvider): ethers.Wallet {
+  return new ethers.Wallet(privateKey, provider);
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 788 - 60
yarn.lock


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott