From 5516e36f3ec2fa09803c427051e3887701032843 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Wed, 13 May 2026 16:46:14 +0800
Subject: [PATCH] refactor(okxNewPrice): 账户配置
---
src/main/java/com/xcong/excoin/modules/okxApi/okxApi-logic.md | 935 ++++++++++++++++------------------------------------------
1 files changed, 262 insertions(+), 673 deletions(-)
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/okxApi-logic.md b/src/main/java/com/xcong/excoin/modules/okxApi/okxApi-logic.md
index 9e67176..2a0a49c 100644
--- a/src/main/java/com/xcong/excoin/modules/okxApi/okxApi-logic.md
+++ b/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()` 中基底开仓用限价单(取盘口卖一/买一价),减少滑点
--
Gitblit v1.9.1