Administrator
3 hours ago 6cfe51f29210e1bf5223059402aa15a54278409e
src/main/java/com/xcong/excoin/modules/okxApi/okxApi-logic.md
@@ -1,724 +1,313 @@
# OKX API 网格交易策略 — 逻辑文档
# OKX 永续合约双边网格策略 — 逻辑文档
> 最后更新: 2026-05-13
---
## 目录
## 一、模块架构
1. [整体架构](#1-整体架构)
2. [配置层:OkxConfig](#2-配置层okxconfig)
3. [基础设施层](#3-基础设施层)
   - [3.1 OkxEnums — 常量定义](#31-okxenums--常量定义)
   - [3.2 OkxWsUtil — WebSocket 工具类](#32-okxwsutil--websocket-工具类)
   - [3.3 TradeRequestParam — 下单参数](#33-traderequestparam--下单参数)
4. [WebSocket 通信层](#4-websocket-通信层)
   - [4.1 OkxWebSocketClientManager — 入口管理器](#41-okxwebsocketclientmanager--入口管理器)
   - [4.2 OkxKlineWebSocketClient — WS 连接客户端](#42-okxklinewebsocketclient--ws-连接客户端)
5. [频道处理器层](#5-频道处理器层)
   - [5.1 OkxChannelHandler — 处理器接口](#51-okxchannelhandler--处理器接口)
   - [5.2 OkxCandlestickChannelHandler — K线频道](#52-okxcandlestickchannelhandler--k线频道)
   - [5.3 OkxPositionsChannelHandler — 持仓频道](#53-okxpositionschannelhandler--持仓频道)
   - [5.4 OkxAccountChannelHandler — 账户频道](#54-okxaccountchannelhandler--账户频道)
   - [5.5 OkxOrderInfoChannelHandler — 订单成交频道](#55-okxorderinfochannelhandler--订单成交频道)
6. [策略执行层](#6-策略执行层)
   - [6.1 OkxTradeExecutor — 异步下单执行器](#61-okxtradeexecutor--异步下单执行器)
   - [6.2 OkxGridTradeService — 网格策略核心](#62-okxgridtradeservice--网格策略核心)
7. [独立启动类:OkxWebSocketClientMain](#7-独立启动类okxwebsocketclientmain)
8. [完整调用链](#8-完整调用链)
9. [与 Gate API 的差异对比](#9-与-gate-api-的差异对比)
```
OkxWebSocketClientMain                     ← 入口,持有主运行线程
  └── OkxWebSocketClientManager            ← 管理 WS 连接(公共 + 私有)
        ├── OkxKlineWebSocketClient (公共)  ← K线推送
        └── OkxKlineWebSocketClient (私有)  ← 私有频道(login → 订阅)
              ├── OkxCandlestickChannelHandler  → onKline()
              ├── OkxOrderInfoChannelHandler    → onOrderFilled()
              ├── OkxPositionsChannelHandler    → onPositionUpdate()
              └── OkxAccountChannelHandler      → 仅日志,供后续扩展
  └── OkxGridTradeService                  ← 核心策略引擎(状态机)
        ├── OkxConfig                       ← 策略配置(Builder 模式)
        └── OkxTradeExecutor               ← REST 下单执行器
```
### WS 连接层职责
| 类 | 职责 |
|----|------|
| `OkxKlineWebSocketClient` | TCP 连接管理、心跳(10s ping)、指数退避重连(最多3次)、消息路由(login/subscribe/pong → handler.handleMessage) |
| `OkxWebSocketClientManager` | 双重连接(公共/私有)的初始化、销毁、共享线程池 |
| `OkxWsUtil` | SSL 配置、HMAC-SHA256 签名、订单 ID 生成、ISO8601 时间戳 |
---
## 1. 整体架构
## 二、核心配置 `OkxConfig`
```
┌──────────────────────────────────────────────────────────────┐
│  OkxWebSocketClientManager (Spring @Component)               │
│    · 读取配置 · 组装所有组件 · 启动 WS 连接 · 生命周期管理    │
└──────────────────────┬───────────────────────────────────────┘
                       │
         ┌─────────────┼─────────────┐
         ▼             ▼             ▼
   OkxConfig      OkxGridTradeService   OkxKlineWebSocketClient
   (Builder配置)    (策略核心)            (WS连接客户端)
                       │                      │
                       │              ┌────────┴────────┐
                       │              │  4 个频道处理器   │
                       │              ├─────────────────┤
                       │              │ K线 | 持仓       │
                       │              │ 账户 | 订单成交   │
                       │              └────────┬────────┘
                       │                       │
                       ▼                       │
              OkxTradeExecutor                 │
              (异步下单线程池) ◄────────────────┘
                       │
                       ▼
              通过 WS 发送下单 JSON
```
**核心数据流**:
```
K线推送 → OkxGridTradeService.onKline() → 匹配网格队列 → OkxTradeExecutor 异步下单
持仓推送 → OkxGridTradeService.onPositionUpdate() → 识别基底成交 → 设置止盈单 → 队列就绪 → 激活策略
订单推送 → OkxGridTradeService.onOrderFilled() → 累计盈亏跟踪 → 达标/超限自动停止
```
**设计原则**:
- **包自包含**:`okxApi` 包不依赖任何其他业务包(`okxNewPrice`、`gateApi`、`newPrice`、`blackchain` 等)
- **WS 回调不阻塞**:所有下单操作通过 `OkxTradeExecutor` 单线程池异步执行
- **状态机驱动**:策略状态(`WAITING_KLINE → OPENING → ACTIVE → STOPPED`)严格控制执行流程
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `contract` | `BTC-USDT-SWAP` | 合约 ID |
| `gridRate` | `0.003` (0.3%) | 网格步长比率 |
| `overallTp` | `100 USDT` | 整体止盈线 |
| `maxLoss` | `100 USDT` | 最大亏损线 |
| `quantity` | `"1"` | 单次开仓张数(字符串,1 张 = 100 合约) |
| `gridQueueSize` | `20` | 多/空队列各 20 个元素 |
| `marginRatioLimit` | `0.05` (5%) | 保证金安全阀 |
| `contractMultiplier` | `0.01` | 合约乘数(BTC 0.01,ETH 0.1) |
| `unrealizedPnlPriceMode` | `LAST_PRICE` | 未实现盈亏计算基准价(LAST_PRICE / MARK_PRICE) |
| `maxPosSize` | `10` 张 | **新** 单方向最大持仓张数 |
| `step` | 运行时计算 | **新** 网格步长 = `shortBaseEntryPrice × gridRate`,队列生成时写入 |
---
## 2. 配置层:OkxConfig
## 三、策略状态机
```
文件:OkxConfig.java
INIT ──startGrid()──▶ WAITING_KLINE ──首根K线稳定──▶ OPENING ──双基底成交──▶ ACTIVE ──止盈/止损──▶ STOPPED
                         ▲                                                          │
                         │                       K线重连 / 重订阅                    │
                         └──────────────────────────────────────────────────────────┘
```
使用 **Builder 模式**构造配置对象,不可变设计,所有字段 `private final`。
| 状态 | 说明 | 触发条件 |
|------|------|----------|
| `INIT` | 初始态 | `startGrid()` |
| `WAITING_KLINE` | 等待 K 线推送稳定(标记价在基底持仓价 ± 步长范围内) | 基底开仓成功 |
| `OPENING` | 等待双基底订单成交 | K线稳定后开仓 |
| `ACTIVE` | 网格运行中 | 双基底成交,队列生成完成 |
| `STOPPED` | 策略停止 | `cumulativePnl >= overallTp` 或 `<= -maxLoss` |
### 配置字段
---
| 分组 | 字段 | 说明 |
|------|------|------|
| **API 密钥** | `apiKey` | OKX API Key |
| | `secretKey` | OKX Secret Key |
| | `passphrase` | OKX Passphrase |
| **合约参数** | `contract` | 合约品种(如 `BTC-USDT-SWAP`) |
| | `marginMode` | 保证金模式(`cross` 全仓 / `isolated` 逐仓) |
| | `tickSz` | 价格精度 |
| | `contractMultiplier` | 合约乘数(用于盈亏计算) |
| | `leverage` | 杠杆倍数 |
| **策略参数** | `quantity` | 每次开仓张数 |
| | `gridRate` | 网格间距比例(如 0.01 = 1%) |
| | `gridQueueSize` | 网格队列长度 |
| | `marginRatioLimit` | 保证金占用比例上限 |
| | `overallTp` | 全局止盈目标(累计盈亏 ≥ 此值停止) |
| | `maxLoss` | 最大亏损限制(累计盈亏 ≤ 此值停止) |
| **环境** | `isProduction` | 是否生产环境(决定 WS URL 域名) |
## 四、完整策略时序图
### Builder 方法链
```mermaid
sequenceDiagram
    participant M as Main
    participant S as OkxGridTradeService
    participant C as OkxConfig
    participant K as K线Handler
    participant O as 订单Handler
    participant P as 仓位Handler
    participant E as OkxTradeExecutor
    participant OKX as OKX交易所
```java
OkxConfig config = OkxConfig.builder()
    .apiKey("xxx")
    .secretKey("xxx")
    .passphrase("xxx")
    .contract("BTC-USDT-SWAP")
    .marginMode("cross")
    .leverage(1)
    .quantity("1")
    .gridRate(new BigDecimal("0.01"))
    .gridQueueSize(10)
    .overallTp(new BigDecimal("100"))
    .isProduction(false)
    .build();
    Note over M,OKX: ═══ 阶段①: 初始化 ═══
    M->>S: startGrid()
    S->>S: state = INIT
    S->>E: setPositionMode("long_short_mode")
    E->>OKX: POST /set-position-mode
    S->>E: setLeverage(instId, lever, "isolated")
    E->>OKX: POST /set-leverage
    S->>E: openShort(quantity, null, null)  基底空单
    E->>OKX: 市价做空
    S->>E: openLong(quantity, null, null)   基底多单
    E->>OKX: 市价做多
    S->>S: state = WAITING_KLINE
    S->>S: 重置所有字段(step=null, baseOpened=false, ...)
    Note over M,OKX: ═══ 阶段②: 等待K线稳定 ═══
    K-->>S: onKline(price)
    Note over S: WAITING_KLINE 状态 <br/> 检查标记价是否在基底价±步长内
    S->>S: step = shortBaseEntryPrice × gridRate
    S->>S: state = OPENING
    S->>E: openLong(quantity, baseStepLongPrice, null)
    E->>OKX: 限价做多
    S->>E: openShort(quantity, baseStepShortPrice, null)
    E->>OKX: 限价做空
    Note over M,OKX: ═══ 阶段③: 订单成交 → 基底识别 + 队列生成 ═══
    O-->>S: onOrderFilled(posSide=long, avgPx, fillSz, pnl)
    Note over S: !baseLongOpened → 首次多基底
    S->>S: longBaseEntryPrice = avgPx
    S->>S: baseLongOpened = true
    S->>S: tryGenerateQueues()
    O-->>S: onOrderFilled(posSide=short, avgPx, fillSz, pnl)
    Note over S: !baseShortOpened → 首次空基底
    S->>S: shortBaseEntryPrice = avgPx
    S->>S: baseShortOpened = true
    S->>S: tryGenerateQueues()
    Note over S: 双基底就绪 ✓
    S->>C: setStep(shortBaseEntryPrice × gridRate)
    S->>S: generateShortQueue() → [s0-s, s0-2s, ..., s0-Ns]
    S->>S: generateLongQueue() → [l0+s, l0+2s, ..., l0+Ns]
    S->>S: state = ACTIVE
    Note over M,OKX: ═══ 阶段④: 网格运行 (ACTIVE) ═══
    loop 每根K线
        K-->>S: onKline(price)
        Note over S: 止盈止损检查<br/>队列匹配
        alt 空仓队列触发 (price <= queue[0])
            S->>S: processShortGrid()
            Note over S: 收集所有 price<=queue[i] 的元素<br/>check maxPosSize<br/>开空 + 填充队列 + 转移到多队列
        else 多仓队列触发 (price >= queue[0])
            S->>S: processLongGrid()
            Note over S: 收集所有 price>=queue[i] 的元素<br/>check maxPosSize<br/>开多 + 填充队列 + 转移到空队列
        end
    end
    Note over M,OKX: ═══ 阶段⑤: 加仓成交 → 挂止盈 ═══
    O-->>S: onOrderFilled(posSide, avgPx, fillSz, pnl)
    Note over S: baseOpened && fillSz > 0 → 加仓
    S->>S: tpPrice = queue.get(0)
    S->>E: placeTakeProfit(tpPrice, CLOSE_LONG, quantity)
    E->>OKX: 挂止盈限价单
    Note over M,OKX: ═══ 阶段⑥: 仓位更新 → 累计盈亏 → 停止判断 ═══
    loop 每次仓位变化
        P-->>S: onPositionUpdate(posSide, size, avgPx, realizedPnl)
        S->>S: 更新 longRealizedPnl / shortRealizedPnl
        S->>S: cumulativePnl = longRealizedPnl + shortRealizedPnl
        alt cumulativePnl >= overallTp
            S->>S: state = STOPPED
            Note over S: 🏁 盈利目标达成
        else cumulativePnl <= -maxLoss
            S->>S: state = STOPPED
            Note over S: 🏁 亏损上限触发
        end
    end
```
---
## 3. 基础设施层
## 五、详细逻辑说明
### 3.1 OkxEnums — 常量定义
### 5.1 K线处理 `onKline()` — 策略核心驱动
```
文件:enums/OkxEnums.java
WAITING_KLINE:
  └── 标记价在 [shortBasePrice - step, shortBasePrice + step] 范围 → OPENING
       └── 开基多限价单(baseStepLongPrice) + 基空限价单(baseStepShortPrice)
ACTIVE:
  ├── 止盈止损检查(累计已实现盈亏 vs overallTp / maxLoss)
  ├── 保证金安全阀检查(isMarginSafe)
  ├── processShortGrid(): 当前价 <= 空队列首 → 触发空仓网格
  └── processLongGrid(): 当前价 >= 多队列首 → 触发多仓网格
```
集中管理所有 OKX API 相关字符串常量,替代外部 `CoinEnums` 依赖。
### 5.2 订单成交处理 `onOrderFilled()` — 基底识别 + 挂止盈
| 常量 | 值 | 用途 |
|------|-----|------|
| `INSTTYPE_SPOT` | `SPOT` | 现货 |
| `INSTTYPE_SWAP` | `SWAP` | 永续合约 |
| `POSSIDE_LONG` | `long` | 多仓方向 |
| `POSSIDE_SHORT` | `short` | 空仓方向 |
| `SIDE_BUY` | `buy` | 买入 |
| `SIDE_SELL` | `sell` | 卖出 |
| `ORDTYPE_MARKET` | `market` | 市价单 |
| `ORDTYPE_LIMIT` | `limit` | 限价单 |
| `CHANNEL_POSITIONS` | `positions` | 持仓频道 |
| `CHANNEL_CANDLE` | `candle` + 周期 | K线频道 |
| `CHANNEL_ACCOUNT` | `account` | 账户频道 |
| `CHANNEL_ORDERS` | `orders` | 订单频道 |
| `CHANNEL_ORDERS_ALGO` | `orders-algo` | 策略委托频道 |
**数据来源**:orders 频道 `state=filled`,取 `avgPx`(成交量加权均价)。
```
!baseLongOpened → 基底多成交 → longBaseEntryPrice = avgPx → tryGenerateQueues()
!baseShortOpened → 基底空成交 → shortBaseEntryPrice = avgPx → tryGenerateQueues()
baseOpened → 加仓成交 → queue[0] = 止盈价 → placeTakeProfit()
```
> `tryGenerateQueues()`:双基底都成交后,计算 step,生成多空队列,状态 → ACTIVE。
### 5.3 队列生成逻辑
```
step = shortBaseEntryPrice × config.gridRate  (统一用空基价)
空队列 (shortPriceQueue):  [shortBase - s, shortBase - 2s, ...]  → 降序(最近的在前)
多队列 (longPriceQueue):   [longBase + s, longBase + 2s, ...]   → 升序(最近的在前)
```
**步长统一来源**:`config.step`,由 `tryGenerateQueues()` 计算并写入。
### 5.4 网格触发 `processShortGrid()` / `processLongGrid()`
```
processShortGrid():
  1. 收集所有 price <= queue[i] 的元素
  2. 开空(shortPositionSize < maxPosSize 限制)
  3. 填补自身队列(从队尾追加 step 递减)
  4. 转移到多仓队列(在 longBaseEntryPrice - step 下方插入)
  5. 额外开多条件: 价格在多/空持仓价之间 && 多>空 && 远离多均价 && longPositionSize < maxPosSize
processLongGrid():
  1. 收集所有 price >= queue[i] 的元素
  2. 开多(longPositionSize < maxPosSize 限制)
  3. 填补自身队列(从队尾追加 step 递增)
  4. 转移到空仓队列(在 shortBaseEntryPrice + step 上方插入)
  5. 额外开空条件: 价格在多/空持仓价之间 && 多>空 && 远离空均价 && shortPositionSize < maxPosSize
```
### 5.5 止盈止损 `onPositionUpdate()` — 累计已实现盈亏
**数据来源**:positions 频道 `realizedPnl` 字段(含手续费、资金费的综合已实现盈亏)。
```
cumulativePnl = longRealizedPnl + shortRealizedPnl
≥ overallTp → STOPPED (盈利)
≤ -maxLoss → STOPPED (亏损)
```
> 仓位 size=0 时该方向的 `realizedPnl` 置为 0,避免重复累加。
---
### 3.2 OkxWsUtil — WebSocket 工具类
## 六、策略评估
```
文件:OkxWsUtil.java
```
### ✅ 好的方面
替代外部 `SSLConfig`、`SignUtils`、`WsParamBuild`、`DateUtil` 等依赖,提供以下静态方法:
| # | 方面 | 说明 |
|---|------|------|
| 1 | **双边网格 + 队列转换** | 多空队列互为补仓,价格在区间内震荡时持续收割网格利润。止盈触发后队列元素转移到对方队列,形成"无限网格"循环 |
| 2 | **WS 推送驱动,非轮询** | orders + positions + kline 三频道覆盖,低延迟,不浪费 API 额度 |
| 3 | **用 `avgPx` 而非 `fillPx`** | 均价比单笔成交价更能反映真实持仓成本,避免分笔成交造成的偏差 |
| 4 | **用 `realizedPnl` 而非手动累加** | positions 频道的 realizedPnl 包含手续费 + 资金费,比 orders 频道的逐笔 pnl 累加更完整 |
| 5 | **步长统一存储** | `config.step` 全局唯一来源,避免多空各自计算不一致 |
| 6 | **maxPosSize 持仓上限** | 防止单方向无限加仓,控制风险敞口 |
| 7 | **保证金安全阀** | `isMarginSafe()` 检查,防止爆仓 |
| 8 | **状态机清晰** | INIT → WAITING_KLINE → OPENING → ACTIVE → STOPPED,每个状态职责明确 |
| 9 | **连接层健壮** | 心跳保活、指数退避重连、订阅恢复 |
| 方法 | 用途 |
|------|------|
| `configureSSL(wsClient)` | 为 `WebSocketClient` 配置 SSL(跳过证书验证,仅测试环境) |
| `generateSignature(timestamp, method, requestPath, body, secretKey)` | OKX 签名算法:HMAC-SHA256 + Base64 |
| `getOrderNum(side)` | 生成唯一订单 ID(时间戳 + 随机数 + side) |
| `timestampToDateTime(timestamp)` | 毫秒时间戳 → `yyyy/MM/dd HH:mm:ss` 格式 |
| `timestampToDateToString(timestamp)` | 毫秒时间戳 → `yyyy/MM/dd` 格式 |
| `buildJsonObject(connId, channel, args)` | 构建 WS 请求 JSON 对象 |
| `buildLoginParam(okxConfig)` | 构建登录认证参数(sign + timestamp) |
### ⚠️ 不好的方面 / 潜在风险
**签名算法**:
```
sign = Base64(HMAC-SHA256(timestamp + "GET" + requestPath + body, secretKey))
```
| # | 问题 | 风险等级 | 说明 |
|---|------|----------|------|
| 1 | **无订单状态追踪** | 🔴 高 | `onOrderFilled` 触发了止盈挂单,但没有记录止盈单的 `ordId`。如果止盈单一直未成交但策略已标记 STOPPED,可能出现"策略已停止但仍有挂单"的幽灵状态 |
| 2 | **止盈挂单无超时/撤销机制** | 🔴 高 | `placeTakeProfit` 挂出的限价单没有定时检查和撤销逻辑。市场反向走时止盈单长期挂单,占用保证金且可能过期(OKX 限价单默认 30 天) |
| 3 | **基底开仓用市价单** | 🟡 中 | `startGrid()` 中 `openShort/openLong` 传 null 即市价,滑点风险 |
| 4 | **队列队列无限增长风险** | 🟡 中 | `replenishOwnQueue` 从队尾追加新元素,没有队列长度上限检查。如果价格长时间单边运动,队列可能增长到远超 `gridQueueSize` 的规模 |
| 5 | **基底价更新与队列不同步** | 🟡 中 | `onPositionUpdate` 更新 `longEntryPrice/shortEntryPrice`(加权均价),但队列仍用初始 `longBaseEntryPrice/shortBaseEntryPrice` 计算。加仓后均价变化,队列位置可能不再合理 |
| 6 | **`realizedPnl` 归零逻辑有漏洞** | 🟡 中 | 仓位 size=0 时 `realizedPnl` 置为 0,但如果仓位先平仓再重新开仓,累计盈亏会丢失。正确的做法是记录到另一个持久累加字段 |
| 7 | **步长只依赖空基价** | 🟡 中 | 多空各自用 `空基价 × gridRate`,如果多空基底价相差较大,多的队列步长偏大/偏小 |
| 8 | **无断路器(熔断)** | 🟡 中 | 极端行情下没有暂停交易的机制,可能连续触发网格大量开仓 |
| 9 | **多线程安全依赖 volatile** | 🟢 低 | 状态字段用 `volatile`,但对于复合操作(判断-修改)没有锁保护。实际风险较低,因为 WS 回调是单线程 |
| 10 | **无订单幂等保护** | 🟢 低 | 如果 OKX 重复推送同一订单(小概率),会重复挂止盈单 |
| 11 | **K线处理无防重** | 🟢 低 | 同一根 K 线可能被 onKline 和 processGrid 多次触发(WS 推送更新),不过 markprice 变更才会进入处理,影响有限 |
| 12 | **账户频道未使用** | 🟢 低 | `OkxAccountChannelHandler` 只输出日志,`isMarginSafe()` 用的是交易量/可用余额估算,不如直接用 account 频道的 `availBal` |
---
### 3.3 TradeRequestParam — 下单参数
## 七、优化方向(按优先级排序)
```
文件:param/TradeRequestParam.java
```
### 🔴 P0 — 紧急
纯 POJO,替代外部 `TradeRequestParam` 依赖。
1. **止盈挂单生命周期管理**
   - 挂单后记录 `tpOrdId`,用 orders 频道监控成交
   - 止盈单未成交超过 N 根 K 线 → 撤销重挂
   - 策略 STOPPED 时撤销所有未成交挂单
| 字段 | 说明 |
|------|------|
| `accountName` | 账户标识 |
| `instId` | 合约 ID |
| `tdMode` | 保证金模式(cross/isolated) |
| `posSide` | 持仓方向(long/short) |
| `ordType` | 订单类型(market/limit) |
| `side` | 买卖方向(buy/sell) |
| `clOrdId` | 客户端订单 ID(唯一) |
| `sz` | 下单数量 |
| `markPx` | 标记价格(限价单用) |
| `tradeType` | 交易类型(1=开仓,3=平仓) |
2. **队列长度上限**
   - `replenishOwnQueue` 中限制 `queue.size() <= gridQueueSize * 2`
   - 超过上限时清理队尾(远离当前价的元素)
---
### 🟡 P1 — 重要
## 4. WebSocket 通信层
3. **`realizedPnl` 累加到独立字段**
   - 当前用 volatile `longRealizedPnl/shortRealizedPnl` 直接赋值会被归零覆盖
   - 新增 `totalRealizedPnl` 累加字段,`onPositionUpdate` 中 delta 累加而非覆盖
### 4.1 OkxWebSocketClientManager — 入口管理器
4. **基底价与队列同步更新**
   - 当 `longEntryPrice` 偏离 `longBaseEntryPrice` 超过 N 个 step 时,重新生成队列
```
文件:OkxWebSocketClientManager.java
```
5. **爆仓熔断**
   - 每分钟开仓次数超过阈值 → 暂停策略 + 告警
   - 未实现亏损超过保证金 80% → 暂停
**Spring `@Component`**,管理完整的 WS 生命周期。
6. **账户频道接入保证金计算**
   - `isMarginSafe()` 改用 account 频道的 `availBal`,更精确
#### 职责
### 🟢 P2 — 优化
1. **组件组装**:创建 `OkxConfig` → `OkxGridTradeService` → `OkxTradeExecutor`(注入 WS Client)→ 4 个频道处理器 → `OkxKlineWebSocketClient`
2. **生命周期管理**:
   - `@PostConstruct init()`:初始化和连接(生产环境)或注册 MBean(测试环境)
   - `@PreDestroy close()`:优雅关闭(停止策略 → 取消条件单 → 关闭 WS)
7. **订单幂等**
   - 用 `Set<clOrdId>` 缓存已处理订单 ID,防止重复挂单
#### 初始化流程
8. **步长优化**
   - 考虑用 `(longBase + shortBase) / 2 × gridRate` 作为统一基准,而非只用空基价
```
init()
  ├── configMap — 从本地缓存读取账户配置
  ├── OkxGridTradeService.startGrid()
  ├── OkxTradeExecutor.setWebSocketClient(wsClient)
  ├── 创建 4 个频道处理器
  ├── OkxKlineWebSocketClient.connect()
  │     ├── 连接 OSKL 公开 WS(K线频道不需要登录)
  │     ├── 登录私有 WS → 订阅 持仓/账户/订单/策略委托频道
  │     └── 启动心跳定时器(30s ping/pong)
  └── isProduction ? 直接启动 : MBean 注册(JMX 手动控制)
```
---
### 4.2 OkxKlineWebSocketClient — WS 连接客户端
```
文件:OkxKlineWebSocketClient.java
```
封装 `java-websocket` 客户端,管理物理连接。
#### 连接架构
- **公开频道 WS**(`wss://ws.okx.com:8443/ws/v5/public` 或模拟盘域名):K线推送不需要登录
- **私有频道 WS**(`wss://ws.okx.com:8443/ws/v5/private` 或模拟盘域名):需要登录认证,订阅持仓/账户/订单/策略委托频道
#### 连接流程
```
connect()
  ├── 1. 创建公开 WS Client → connect()
  │      └── onOpen → subscribePublicChannels() → 订阅 K线频道
  ├── 2. 创建私有 WS Client → connect()
  │      └── onOpen → login() → onLoginSuccess → subscribePrivateChannels()
  │             ├── 订阅 positions 频道
  │             ├── 订阅 account 频道
  │             ├── 订阅 orders 频道
  │             └── 订阅 orders-algo 频道
  └── 3. 启动心跳定时器(30s 间隔 ping/pong,60s 超时检测)
```
#### 断线重连机制
- 最多重连 `MAX_RECONNECT_ATTEMPTS` 次(默认 30)
- 重连延迟 `RECONNECT_DELAY_MS`(默认 5000ms)
- 重连成功后重新执行登录 + 订阅流程
- 兜底机制:重连失败后尝试通过 MBean 重启整个 WS 客户端
#### 消息路由
`onMessage` → 遍历所有 `OkxChannelHandler` → 调用 `handleMessage(response)` 分发到具体处理器
---
## 5. 频道处理器层
### 5.1 OkxChannelHandler — 处理器接口
```
文件:wsHandler/OkxChannelHandler.java
```
统一接口,所有频道处理器实现此接口:
```java
public interface OkxChannelHandler {
    String getChannelName();                      // 频道名称
    void subscribe(WebSocketClient ws);           // 订阅
    void unsubscribe(WebSocketClient ws);         // 取消订阅
    boolean handleMessage(JSONObject response);   // 处理推送消息
}
```
### 5.2 OkxCandlestickChannelHandler — K线频道
```
文件:wsHandler/handler/OkxCandlestickChannelHandler.java
```
#### 订阅参数
| 参数 | 值 |
|------|-----|
| `channel` | `candle{period}`(如 `candle1H`) |
| `instId` | 合约 ID(如 `BTC-USDT-SWAP`) |
#### 数据处理
- 解析 `data[0]` → `[ts, o, h, l, c, vol, volCcy, ...]`
- 提取**收盘价** `c` → 调用 `gridTradeService.onKline(closePrice)`
- K线为 `candle1H` 时打印整点日志
#### 调用链
```
onKline(closePrice)
  ├── WAITING_KLINE → 进入 OPENING 状态,开基底多+空
  ├── ACTIVE → processShortGrid(closePrice) + processLongGrid(closePrice)
  └── STOPPED → 仅更新未实现盈亏
```
### 5.3 OkxPositionsChannelHandler — 持仓频道
```
文件:wsHandler/handler/OkxPositionsChannelHandler.java
```
#### 订阅参数
| 参数 | 值 |
|------|-----|
| `channel` | `positions` |
| `instType` | `SWAP` |
| `instId` | 合约 ID |
#### 数据处理
解析 `data[]` 数组 → 提取 `posSide`、`pos`(数量)、`avgPx`(均价)→ 调用 `gridTradeService.onPositionUpdate(posSide, size, avgPx)`
#### 在策略中的作用
```
onPositionUpdate() → 区分 3 种场景:
  1. 仓位从无到有(基底开仓成交)→ 标记 baseOpened → 双基底都成后生成网格队列
  2. 仓位量增加(网格触发开仓成交)→ 取队列首元素做止盈价 → 设止盈条件单
  3. 仓位归零(止盈平仓完成)→ 标记 active=false
```
### 5.4 OkxAccountChannelHandler — 账户频道
```
文件:wsHandler/handler/OkxAccountChannelHandler.java
```
#### 订阅参数
| 参数 | 值 |
|------|-----|
| `channel` | `account` |
#### 数据处理
解析 `data[]` → 提取 `availBal`(可用余额)、`cashBal`(现金余额)、`eq`(权益)、`upl`(未实现盈亏)、`imr`(保证金占用)
**当前版本**:仅做日志输出,不做业务判断。后续可扩展保证金安全阀功能。
### 5.5 OkxOrderInfoChannelHandler — 订单成交频道
```
文件:wsHandler/handler/OkxOrderInfoChannelHandler.java
```
#### 订阅参数
| 参数 | 值 |
|------|-----|
| `channel` | `orders` |
| `instType` | `SWAP` |
| `instId` | 合约 ID |
#### 数据处理
- 过滤 `state=filled` 且 `accFillSz>0` 的订单
- 提取 `posSide`、`accFillSz`(成交数量)、`fillPnl`(已实现盈亏)
- 调用 `gridTradeService.onOrderFilled(posSide, accFillSz, fillPnl)`
#### 在策略中的作用
```
onOrderFilled()
  ├── 累计盈亏 += fillPnl
  ├── 累计盈亏 ≥ overallTp → 策略停止(止盈达标)
  └── 累计盈亏 ≤ -maxLoss → 策略停止(亏损超限)
```
---
## 6. 策略执行层
### 6.1 OkxTradeExecutor — 异步下单执行器
```
文件:OkxTradeExecutor.java
```
#### 设计目的
WS 消息在回调线程处理,下单操作提交到**独立线程池异步执行**,避免阻塞 WS 回调线程。
#### 线程模型
| 参数 | 值 |
|------|-----|
| 核心线程 | 1 |
| 最大线程 | 1 |
| 空闲超时 | 60s(`allowCoreThreadTimeOut`) |
| 队列类型 | `LinkedBlockingQueue` |
| 队列容量 | 64 |
| 拒绝策略 | `CallerRunsPolicy`(队列满时由提交线程直接同步执行,形成自然背压) |
| 守护线程 | 是 |
**单线程的作用**:保证下单顺序(开多 → 开空 → 止盈单),避免并发竞争。
#### 公开方法
| 方法 | 说明 |
|------|------|
| `openLong(quantity, onSuccess, onFailure)` | 异步市价开多 |
| `openShort(quantity, onSuccess, onFailure)` | 异步市价开空 |
| `placeTakeProfit(triggerPrice, orderType, size)` | 异步创建止盈条件单(通过 `batch-orders` 发送 algo 委托) |
| `cancelAllPriceTriggeredOrders()` | 撤销所有条件单(`cancel-algos`) |
| `setWebSocketClient(wsClient)` | 注入 WS 客户端引用 |
| `shutdown()` | 优雅关闭(等待 10s,超时强制中断) |
#### 止盈兜底机制
```
placeTakeProfit()
  ├── 成功 → 发送 batch-orders(algo 条件单)
  └── 失败 → marketClose() 立即市价平仓兜底
```
#### 下单方式
所有下单通过 **WebSocket** 发送 JSON(非 REST API),`sendOrder` 构建如下消息:
```json
{
  "id": "order_1712345678901_a1b2c3",
  "op": "order",
  "args": [{
    "instId": "BTC-USDT-SWAP",
    "tdMode": "cross",
    "clOrdId": "buy_1712345678901_d4e5f6",
    "side": "buy",
    "posSide": "long",
    "ordType": "market",
    "sz": "1"
  }]
}
```
#### 调用链
```
OkxGridTradeService.onKline
  └── executor.openLong() / openShort()     ← 基底双开 + 网格触发
OkxGridTradeService.onPositionUpdate
  └── executor.placeTakeProfit()             ← 仓位成交后设止盈
OkxGridTradeService.stopGrid
  └── executor.cancelAllPriceTriggeredOrders()  + shutdown()
```
---
### 6.2 OkxGridTradeService — 网格策略核心
```
文件:OkxGridTradeService.java
```
这是整个包的核心,实现了**多空双开网格交易策略**的所有状态机和网格队列逻辑。
#### 策略状态机
```
WAITING_KLINE ──(首根K线到达)──▶ OPENING ──(双基底成交)──▶ ACTIVE ──(止盈/止损达标)──▶ STOPPED
                        ▲                                                         │
                        └──────────────────(startGrid() 重新启动)──────────────────┘
```
| 状态 | 含义 |
|------|------|
| `WAITING_KLINE` | 等待首根 K 线到达 |
| `OPENING` | 已收到 K 线,正在开基底仓位(多+空各一单) |
| `ACTIVE` | 双基底已成交,网格队列已生成,正常交易中 |
| `STOPPED` | 已停止(止盈达标 / 亏损超限 / 手动停止) |
#### 数据结构
| 字段 | 类型 | 说明 |
|------|------|------|
| `shortPriceQueue` | `List<BigDecimal>` | 空仓价格队列(**降序**) |
| `longPriceQueue` | `List<BigDecimal>` | 多仓价格队列(**升序**) |
| `shortBaseEntryPrice` | `BigDecimal` | 空仓基底成交价 |
| `longBaseEntryPrice` | `BigDecimal` | 多仓基底成交价 |
| `baseLongOpened` | `boolean` | 多仓基底是否已开 |
| `baseShortOpened` | `boolean` | 空仓基底是否已开 |
| `cumulativePnl` | `BigDecimal` | 累计已实现盈亏 |
#### 策略完整生命周期
```
1. startGrid()
   └── 重置所有状态 → WAITING_KLINE
2. onKline(closePrice) → WAITING_KLINE
   └── 转为 OPENING → 发送基底多单 + 基底空单
3. onPositionUpdate(posSide, size, entryPrice)
   ├── 仓位从无到有
   │   ├── 标记 baseLongOpened / baseShortOpened
   │   ├── 记录 entryPrice
   │   ├── 双基底都成交 → tryGenerateQueues() → ACTIVE
   │   └── 生成网格队列:
   │       · 空仓队列:entryPrice × (1 - gridRate×1), (1 - gridRate×2), ... 降序排列
   │       · 多仓队列:entryPrice × (1 + gridRate×1), (1 + gridRate×2), ... 升序排列
   ├── 仓位量增加(网格触发开仓成交)
   │   └── 检查队列非空 → 取队列首元素做止盈价 → executor.placeTakeProfit()
   └── 仓位归零
       └── 标记 active=false
4. onKline(closePrice) → ACTIVE
   ├── processShortGrid(closePrice)  ← 空仓网格处理
   │   ├── 匹配队列中 > closePrice 的元素
   │   ├── 移除已匹配 → 尾部补充新元素(递减)
   │   ├── 多仓队列转移(以对方队列首元素为种子生成递减元素)
   │   ├── 贴近持仓均价过滤(skip)
   │   ├── 保证金安全检查
   │   ├── 开空一次
   │   └── 额外反向开多(价格夹在多/空均价之间且多>空倒挂时)
   └── processLongGrid(closePrice)   ← 多仓网格处理
       └── (对称逻辑,方向反转)
5. onOrderFilled(posSide, fillSz, pnl)
   ├── cumulativePnl += pnl
   ├── cumulativePnl ≥ overallTp → STOPPED
   └── cumulativePnl ≤ -maxLoss → STOPPED
6. stopGrid()
   ├── 状态 → STOPPED
   ├── cancelAllPriceTriggeredOrders()
   └── executor.shutdown()
```
#### 空仓网格处理 `processShortGrid(currentPrice)` 详解
```
1. 匹配队列元素(空仓队列降序遍历,收集 > currentPrice 的元素)
   └── 为空 → 直接返回
2. 空仓队列更新
   ├── 移除 matched 元素
   └── 尾部补充新元素(尾价 × (1 - gridRate) 循环递减)→ 降序排序
3. 多仓队列转移
   ├── 以多仓队列首元素(最小价)为种子
   ├── 生成 matched.size() 个递减元素加入多仓队列
   ├── 贴近持仓均价过滤:元素与多仓均价差距 < gridRate → skip
   └── 升序排序,超长截断
4. 保证金安全检查
   └── 超限 → warn 跳过开仓
5. 开空一次 → executor.openShort()
6. 额外反向开多条件(同时满足):
   ├── longEntryPrice > shortEntryPrice(多>空倒挂)
   ├── currentPrice > shortEntryPrice(当前价在空仓均价上方)
   └── currentPrice < longEntryPrice × (1 - gridRate)(远离多仓均价)
   └── 满足 → executor.openLong() 额外开多一次
```
#### 多仓网格处理 `processLongGrid(currentPrice)` 详解
对称逻辑,方向反转。
---
## 7. 独立启动类:OkxWebSocketClientMain
```
文件:OkxWebSocketClientMain.java
```
**纯 main 方法启动**,不依赖 Spring 容器。
### 用途
用于**本地测试和调试**,无需启动整个 Spring 应用。
### 启动流程
```java
public static void main(String[] args) {
    OkxConfig config = OkxConfig.builder()
        .apiKey("xxx")
        .secretKey("xxx")
        .passphrase("xxx")
        .contract("BTC-USDT-SWAP")
        .marginMode("cross")
        .leverage(1)
        .quantity("1")
        .gridRate(new BigDecimal("0.01"))
        .gridQueueSize(10)
        .overallTp(new BigDecimal("100"))
        .isProduction(false)
        .build();
    OkxGridTradeService service = new OkxGridTradeService(config, "test-account");
    service.startGrid();
    OkxKlineWebSocketClient wsClient = new OkxKlineWebSocketClient(config, service, ...);
    wsClient.connect();
    // 注册 JVM 关闭钩子
    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        service.stopGrid();
        wsClient.close();
    }));
}
```
---
## 8. 完整调用链
```
┌─────────────────────────────────────────────────────────────┐
│ 1. 启动阶段                                                   │
├─────────────────────────────────────────────────────────────┤
│ OkxWebSocketClientManager.init()                             │
│   ├── new OkxConfig.Builder()...build()                      │
│   ├── new OkxGridTradeService(config, accountName)           │
│   │     └── new OkxTradeExecutor(contract, marginMode, name) │
│   ├── gridTradeService.startGrid() → WAITING_KLINE           │
│   ├── new OkxCandlestickChannelHandler(instId, candlePeriod, │
│   │        gridTradeService, config)                          │
│   ├── new OkxPositionsChannelHandler(instId, gridTradeService)│
│   ├── new OkxAccountChannelHandler()                         │
│   ├── new OkxOrderInfoChannelHandler(instId, gridTradeService,│
│   │        config)                                            │
│   ├── new OkxKlineWebSocketClient(config, handlers, ...)     │
│   ├── wsClient.connect()                                     │
│   │     ├── 连接公开 WS → 订阅 K线频道                        │
│   │     ├── 连接私有 WS → 登录 → 订阅 持仓/账户/订单/策略委托  │
│   │     └── 启动心跳定时器                                    │
│   └── executor.setWebSocketClient(privateWsClient)           │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 2. 运行时数据流                                               │
├─────────────────────────────────────────────────────────────┤
│ [K线推送]                                                    │
│ OkxCandlestickChannelHandler.handleMessage()                 │
│   └── gridTradeService.onKline(closePrice)                   │
│         ├── WAITING_KLINE → OPENING                          │
│         │     ├── executor.openLong(quantity, onSuccess,     │
│         │     │        onFailure)                             │
│         │     └── executor.openShort(quantity, onSuccess,    │
│         │              onFailure)                             │
│         └── ACTIVE                                           │
│               ├── processShortGrid(closePrice)               │
│               │     ├── 匹配队列 → 更新队列 → 转移对方队列    │
│               │     └── executor.openShort()                  │
│               └── processLongGrid(closePrice)                │
│                     └──(对称逻辑)                            │
│                                                              │
│ [持仓推送]                                                    │
│ OkxPositionsChannelHandler.handleMessage()                   │
│   └── gridTradeService.onPositionUpdate(posSide, size, avgPx)│
│         ├── 基底成交 → 标记 baseOpened → tryGenerateQueues() │
│         └── 增量成交 → executor.placeTakeProfit(tp, type, sz)│
│                                                              │
│ [订单成交推送]                                                │
│ OkxOrderInfoChannelHandler.handleMessage()                   │
│   └── gridTradeService.onOrderFilled(posSide, fillSz, pnl)   │
│         └── cumulativePnl 累加 → 达标则停止                   │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 3. 停止阶段                                                   │
├─────────────────────────────────────────────────────────────┤
│ OkxWebSocketClientManager.close()                            │
│   ├── gridTradeService.stopGrid()                            │
│   │     ├── state = STOPPED                                  │
│   │     ├── executor.cancelAllPriceTriggeredOrders()         │
│   │     │     └── wsClient.send(cancel-algos)               │
│   │     └── executor.shutdown()                              │
│   └── wsClient.close()                                       │
│         └── 关闭公开WS + 私有WS                               │
└─────────────────────────────────────────────────────────────┘
```
---
## 9. 与 Gate API 的差异对比
| 方面 | Gate API (gateApi) | OKX API (okxApi) |
|------|-------------------|-----------------|
| **下单方式** | REST API (`FuturesApi.createFuturesOrder`) | WebSocket JSON 消息 (`op: "order"`) |
| **止盈单** | REST API (`createPriceTriggeredOrder`),`plan-close-*-position` | WS 消息 (`op: "batch-orders"`),`ordType: limit` + `px` 触发价 |
| **仓位方向** | 正数=开多、负数=开空(size 带符号) | `posSide: long/short` 显式区分,`sz` 始终正数 |
| **保证金模式** | 无(Gate API 隐含) | `tdMode: cross/isolated` 显式指定 |
| **客户端订单 ID** | 自动生成(Gate API 隐式处理) | `clOrdId` 显式生成和传入 |
| **取消条件单** | REST API (`cancelPriceTriggeredOrderList`) | WS 消息 (`op: "cancel-algos"`) |
| **止损失败兜底** | REST `createFuturesOrder` IOC 市价平仓 | WS 消息 `marketClose()`(`tradeType: "3"`) |
| **成交识别** | 通过 WS `FuturesOrderBookTicker` 或 REST 查询 | WS `orders` 频道推送 `state=filled` |
| **API 认证** | HMAC-SHA256 请求头签名(Gate SDK 封装) | HMAC-SHA256 + Base64 WS 登录消息 |
| **WS 连接** | 单一连接,频道订阅混合 | 双连接:公开 WS(K线)+ 私有 WS(持仓/账户/订单) |
| **`placeTakeProfit` 签名** | `(triggerPrice, rule, orderType, size)` 多一个 `rule` 参数 | `(triggerPrice, orderType, size)` (OKX 无 rule 概念) |
| **止盈单下单** | 单条 `FuturesPriceTriggeredOrder` | `batch-orders` 包装(List 格式,但实际传 1 条) |
| **心跳机制** | 应用层 ping/pong JSON 消息 | `java-websocket` 自带 ping/pong + 60s 超时重连 |
| **包依赖** | 依赖 Gate SDK (`io.gate.gateapi`) | **完全自包含**,仅依赖 `java-websocket` + `fastjson` + `lombok` |
9. **基底开仓改用限价单**
   - `startGrid()` 中基底开仓用限价单(取盘口卖一/买一价),减少滑点