Administrator
2026-06-02 d663147264d0fc97e9e7ddbd4aeee69e833ad025
fix(okxNewPrice): 修复合约面值配置错误并更新文档

- 将 OkxConfig.java 中的 ctVal 从 0.1 修正为 0.01
- 同步更新 OkxWebSocketClientManager.java 中的 ctVal 配置
- 重构 OKX网格策略架构文档,补充完整的Spring容器入口说明
- 添加详细的初始化和销毁流程说明
- 更新WS连接架构图和频道订阅参数说明
- 补充策略生命周期的状态机定义和完整流程
- 完善事件驱动循环的各个回调处理逻辑
- 更新网格ID体系和8个全局O(1)索引说明
- 修正关键公式计算和REST API映射对照表
- 添加网格ID示例和线程模型详细说明
3 files modified
473 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX网格策略架构文档.md 469 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxConfig.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX网格策略架构文档.md
@@ -4,105 +4,246 @@
```
okxNewPrice/
├── OkxConfig.java              # 全局配置(Builder模式)
├── OkxGridTradeService.java    # 核心策略引擎
├── OkxTradeExecutor.java       # REST API 异步执行器
├── OkxGridElement.java         # 网格价格层级 + 8个全局O(1)索引
├── OkxTraderParam.java         # 单笔挂单参数
├── OkxGridWsClient.java        # WebSocket 客户端(双连接)
├── OkxWebSocketClientManager.java  # Spring容器入口 + WS组装
├── OkxConfig.java              # 全局配置(Builder模式,策略唯一参数入口)
├── OkxGridTradeService.java    # 核心策略引擎(状态机 + 事件驱动循环)
├── OkxTradeExecutor.java       # REST API 异步执行器(单线程池 + 背压)
├── OkxGridElement.java         # 网格价格层级 + 8个全局O(1)静态索引
├── OkxTraderParam.java         # 单笔挂单参数(方向/触发价/数量/订单ID)
├── OkxGridWsClient.java        # WebSocket 客户端(双连接:business + private)
├── OkxWebSocketClientManager.java  # Spring容器入口 + Bean组装 + 生命周期
├── ExchangeInfoEnum.java       # 账户枚举
├── gridWs/
│   ├── OkxGridChannelHandler.java       # 频道处理器接口
│   ├── OkxKlineChannelHandler.java      # candle1m → onKline()
│   ├── OkxPositionsChannelHandler.java  # positions → onPositionUpdate()
│   ├── OkxOrdersChannelHandler.java     # orders → onOrderUpdate()(替代orders-algo)
│   └── OkxAlgoOrdersChannelHandler.java # orders-algo(测试网不可用,保留备用)
│   ├── OkxGridChannelHandler.java        # 频道处理器接口
│   ├── OkxKlineChannelHandler.java       # candle1m → onKline(closePrice)
│   ├── OkxPositionsChannelHandler.java   # positions → onPositionUpdate()
│   ├── OkxOrdersChannelHandler.java      # orders → onOrderUpdate() (替代orders-algo)
│   └── OkxAlgoOrdersChannelHandler.java  # orders-algo(测试网60018不可用,备用)
└── okxpi/config/              # OKX API 底层HTTP/签名工具
└── okxpi/config/              # OKX V5 API 底层HTTP签名/请求工具链
    ├── Account.java / OKXAccount.java
    ├── RequestHandler.java / ResponseHandler.java
    └── utils/ (SignUtils, UrlBuilder, OkHttpUtils...)
```
## 二、策略生命周期
## 二、Spring 容器入口
```
┌─────────────────────────────────────────────────────────────────┐
│  init() → startGrid() → WAITING_KLINE                          │
│    ↓                                                            │
│  onKline(首根K线) → OPENING → 市价双开基底                      │
│    ├── openLong(baseQuantity张)                                 │
│    └── openShort(baseQuantity张)                                │
│    ↓                                                            │
│  onPositionUpdate() → 双基底成交 → tryGenerateQueues()          │
│    ├── generateShortQueue()   空仓队列(基准价向下步进)         │
│    ├── generateLongQueue()    多仓队列(基准价向上步进)         │
│    ├── updateGridElements()   构建OkxGridElement双向链表         │
│    ├── 多仓止损 -2~-11: algo(sell+long, slTriggerPx, sz=qty)   │
│    └── 空仓止损  2~11: algo(buy+short, slTriggerPx, sz=qty)    │
│    ↓                                                            │
│  state = ACTIVE                                                 │
└─────────────────────────────────────────────────────────────────┘
`OkxWebSocketClientManager` 是 `@Component + @PostConstruct` 驱动的组装点。
### 2.1 激活条件
```yaml
# application-test.yml
app:
  quant: true   # @ConditionalOnProperty 控制是否启动
```
### 状态机
| 状态 | 含义 | 进入条件 |
|------|------|---------|
| `WAITING_KLINE` | 等待首根K线 | startGrid() |
| `OPENING` | 基底开仓中 | 首根K线到达 |
| `ACTIVE` | 策略运行中 | 网格初始化完成 |
| `STOPPED` | 已停止(等待重启) | 盈亏达标/持仓归零 |
## 三、WS连接架构
### 2.2 初始化顺序 (@PostConstruct)
```
┌──────────────────────────────────────────────────────┐
│  OkxWebSocketClientManager (@PostConstruct)           │
│                                                       │
│  ┌─ gridWsClientPublic ───────────────────────────┐  │
│  │  URL: /ws/v5/business  (无需登录,直连订阅)     │  │
│  │  ├── OkxKlineChannelHandler (candle1m)          │  │
│  └────────────────────────────────────────────────┘  │
│                                                       │
│  ┌─ gridWsClientPrivate ──────────────────────────┐  │
│  │  URL: /ws/v5/private  (先登录再订阅)           │  │
│  │  ├── OkxPositionsChannelHandler (positions)     │  │
│  │  └── OkxOrdersChannelHandler (orders)           │  │
│  └────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────┘
1. ExchangeInfoEnum 取首个账户
2. 构建 OkxAccount(baseUrl + apiKey/secretKey/passphrase)
3. OkxConfig.builder() 构建配置(BTC-USDT-SWAP, 100x, cross, gridRate=0.001)
4. OkxGridTradeService.init()
   ├── GET /api/v5/account/balance → 取 USDT details[].eq 作为初始本金
   ├── POST /api/v5/trade/cancel-algos → 清旧条件单
   ├── GET /api/v5/account/positions → 遍历平已有仓位
   └── POST /api/v5/account/set-leverage → 设置杠杆
5. 创建双 WS 客户端并注册频道处理器:
   ├── gridWsClientPublic  → /v5/business  → candle1m (无需登录)
   └── gridWsClientPrivate → /v5/private   → positions + orders (先登录)
6. gridTradeService.startGrid() → WAITING_KLINE
```
| 连接 | URL | 频道 | 是否登录 | 回调 |
|------|-----|------|---------|------|
| 业务WS | `/ws/v5/business` | `candle1m` | 否 | `onKline(closePrice)` |
| 私有WS | `/ws/v5/private` | `positions` | 是 | `onPositionUpdate(instId, posSide, pos, avgPx)` |
| 私有WS | `/ws/v5/private` | `orders` | 是 | `onOrderUpdate(algoId, state, ordType)` |
> **注意**:`orders-algo` 频道在测试网不可用(60018),已替换为 `orders` 频道。algo触发后生成普通订单,fill数据中带 `algoId` 字段可匹配回原始条件单。
## 四、网格ID体系
### 2.3 销毁顺序 (@PreDestroy)
```
价格方向:  低 ←────────────────── 基准价 ──────────────────→ 高
ID:        -N ... -3  -2  -1    0    1   2   3 ... N
用途:      多仓止损区           基底     空仓止损区
           (初始化: -2~-11)           (初始化: 2~11)
stopGrid() → cancelAllAlgoOrders + shutdown → gridWsClientPublic.destroy() → gridWsClientPrivate.destroy()
```
### 2.4 当前活跃配置
| 参数 | 值 | 说明 |
|------|-----|------|
| `instId` | `BTC-USDT-SWAP` | 合约 |
| `leverage` | `100` | 杠杆 |
| `tdMode` | `cross` | 全仓 |
| `gridRate` | `0.001` | 网格间距 0.1% |
| `expectedProfit` | `20` USDT | 盈亏达标重置线 |
| `maxLoss` | `30` USDT | 亏损风控告警线 |
| `quantity` | `1` | 每格下单张数 |
| `baseQuantity` | `10` | 基底开仓张数 |
| `priceScale` | `2` | 价格精度 |
| `ctVal` | `0.1` | 合约面值 |
| `gridQueueSize` | `300` | 价格队列容量 |
## 三、WS 连接架构
```
┌──────────────────────────────────────────────────────────┐
│                 OkxWebSocketClientManager                │
│                                                          │
│  gridWsClientPublic (isPublic=true)                     │
│  ├── URL: wss://wspap.okx.com:8443/ws/v5/business       │
│  ├── 连接后立即 subscribeAllHandlers()(无需登录)       │
│  └── OkxKlineChannelHandler(candle1m, instId)            │
│                                                          │
│  gridWsClientPrivate (isPublic=false)                    │
│  ├── URL: wss://wspap.okx.com:8443/ws/v5/private        │
│  ├── 连接后 wsLogin() → 登录成功 → subscribeAllHandlers()│
│  ├── OkxPositionsChannelHandler(positions, instType:SWAP)│
│  └── OkxOrdersChannelHandler(orders, instType:SWAP)     │
└──────────────────────────────────────────────────────────┘
```
| 连接 | URL | 频道 | 订阅参数 | 回调方法 |
|------|-----|------|---------|---------|
| business | `/v5/business` | `candle1m` | `instId` | `onKline(closePrice)` |
| private | `/v5/private` | `positions` | `instType:SWAP` | `onPositionUpdate(instId, posSide, pos, avgPx)` |
| private | `/v5/private` | `orders` | `instType:SWAP` | `onOrderUpdate(algoId, state, ordType)` |
> **orders 频道替代 orders-algo**:`orders-algo` 在测试网不可用(60018),改订阅 `orders` 频道。
> algo 触发后生成普通市价单,fill 数据中 `algoId` 字段非空时可匹配回原始条件单。
> `OkxOrdersChannelHandler` 过滤逻辑:`state=filled` AND `algoId` 非空。
### 3.1 心跳机制
```
10s 无消息 → send("ping") → server reply "pong" → 重置计时器
25s 定时检查 → 超时则 send("ping")
LostConnectionChecker: setConnectionLostTimeout(0) 已关闭(协议级ping OKX不响应)
```
## 四、策略生命周期
### 4.1 状态机
| 状态 | 含义 | 进入条件 | 退出动作 |
|------|------|---------|---------|
| `WAITING_KLINE` | 等待首根K线 | startGrid() | 首根K线→OPENING |
| `OPENING` | 基底开仓中 | 首根K线 | 双基底成交→ACTIVE |
| `ACTIVE` | 策略运行 | 网格初始化完成 | 盈亏达标/持仓归零→STOPPED |
| `STOPPED` | 已停止 | 重置/达标 | 下根K线自动 startGrid() |
### 4.2 完整流程
```
┌──────────────────────────────────────────────────────────────┐
│  init() → startGrid() → WAITING_KLINE                       │
│    ↓                                                         │
│  onKline(首根) → OPENING → executor.openLong/Short(10张)     │
│    ↓                                                         │
│  onPositionUpdate → 基底成交 → tryGenerateQueues()           │
│    ├── generateShortQueue(): shortBasePrice - step 向下步进  │
│    ├── generateLongQueue():  shortBasePrice + step 向上步进  │
│    ├── updateGridElements(): 构建 601个 OkxGridElement       │
│    │   ├── ID≤-1: 空仓区(降序)  ID=0:基底  ID≥1:多仓区(升序)│
│    │   └── ID索引 + 价格索引 + 6个订单ID索引 (O(1))          │
│    ├── 多仓止损 -2~-11: POST order-algo                      │
│    │   ordType=conditional, side=sell, posSide=long,         │
│    │   slTriggerPx=网格价, slOrdPx=-1, sz=quantity           │
│    └── 空仓止损 +2~+11: POST order-algo                      │
│        ordType=conditional, side=buy, posSide=short,         │
│        slTriggerPx=网格价, slOrdPx=-1, sz=quantity           │
│    ↓                                                         │
│  state = ACTIVE ─────────────────────────────────────────────│
└──────────────────────────────────────────────────────────────┘
```
## 五、事件驱动循环 (ACTIVE 状态)
### 5.1 K线回调 `onKline(closePrice)`
```
lastKlinePrice = closePrice
updateUnrealizedPnl()
if STOPPED:
    cancelAllAlgoOrders() + closeExistingPositions() + startGrid() → WAITING_KLINE
if WAITING_KLINE:
    市价双开 baseQuantity 张 (openLong + openShort) → OPENING
if ACTIVE:
    checkProfitAndReset()  // 每根K线检查盈亏达标
```
### 5.2 仓位推送 `onPositionUpdate`
```
long 有仓位:
  首次(基底) → 记录 baseLongOpened + 均价 → tryGenerateQueues()
  后续       → 更新 positionSize / entryPrice
  归零       → handlePositionZeroAndReset("多仓")
short 有仓位:
  首次(基底) → 记录 baseShortOpened + 均价 → tryGenerateQueues()
  后续       → 更新 positionSize / entryPrice
  归零       → handlePositionZeroAndReset("空仓")
```
### 5.3 订单成交 `onOrderUpdate(algoId, state, ordType)` — 核心事件
触发条件:`state == "filled"` (来自 `orders` 频道的成交推送)
```
                    ┌─ findByLongStopLossOrderId(algoId)
                    │  → handleLongStopLossTriggered()
                    │    止损-ID=-N → 清空止损ID
                    │    → 在 -(N-1) 挂 count×qty 张多单
                    │       (ordType=trigger, triggerPx, orderPx=-1)
                    │    → N>2 时取消 -(N-2) 旧多单
                    │
                    ├─ findByShortStopLossOrderId(algoId)
                    │  → handleShortStopLossTriggered()
                    │    止损ID=N → 清空止损ID
                    │    → 在 N-1 挂 count×qty 张空单
                    │       (ordType=trigger, triggerPx, orderPx=-1)
                    │    → N>2 时取消 N-2 旧空单
algoId 匹配 ────────┤
                    ├─ findByShortOrderId(algoId) && hasShortOrder
                    │  → shortEntryTraderIdParam(null, false)
                    │  → extendShortStopLoss(filledQty)
                    │    到最远空仓止损外扩 stopLossCount 个网格
                    │    每格挂 quantity 张止损
                    │
                    └─ findByLongOrderId(algoId) && hasLongOrder
                       → longEntryTraderIdParam(null, false)
                       → extendLongStopLoss(filledQty)
                         到最远多仓止损外扩 stopLossCount 个网格
                         每格挂 quantity 张止损
```
### 5.4 平仓推送 `onPositionClose`
```
cumulativePnl += pnl
总盈亏 ≤ -maxLoss → 钉钉告警(仅通知,不停止)
```
## 六、网格ID体系
```
价格方向:  低 ←────────────── 基底价 ──────────────→ 高
ID:        ... -3  -2  -1    0    1   2   3 ...
用途:      多仓止损/追单区    基底    空仓止损/追单区
           初始化止损:-2~-11        初始化止损:2~11
链表: ... ← -3 ← -2 ← -1 ← 0 → 1 → 2 → 3 → ...
         (通过 upId/downId + INDEX 实现 O(1) 遍历)
      (upId/downId + INDEX → O(1) 遍历)
```
### GridElement 字段
### 6.1 GridElement 字段
| 类别 | 字段 | 说明 |
|------|------|------|
| 标识 | `id`, `gridPrice`, `upId`, `downId` | 编号、价格、双向链表指针 |
| 多仓订单 | `hasLongOrder`, `longOrderId`, `longTraderParam` | 多仓条件单状态 |
| 空仓订单 | `hasShortOrder`, `shortOrderId`, `shortTraderParam` | 空仓条件单状态 |
| 多仓 | `hasLongOrder`, `longOrderId`, `longTraderParam` | 多仓挂单状态 |
| 空仓 | `hasShortOrder`, `shortOrderId`, `shortTraderParam` | 空仓挂单状态 |
| 止盈 | `longTakeProfitOrderId`, `shortTakeProfitOrderId` | 止盈algoId |
| 止损 | `longStopLossOrderId`, `shortStopLossOrderId` | 止损algoId |
### 8个全局O(1)索引
### 6.2 8个全局O(1)索引
```
INDEX                  → findById(int)
@@ -115,134 +256,118 @@
SHORT_SL_ORDER_ID_INDEX→ findByShortStopLossOrderId(String)
```
## 五、事件驱动循环(ACTIVE状态)
每次订单状态变更后调用 `OkxGridElement.refreshIndices()` 增量重建,同时 `logAll()` 打印全量网格数据。
### 5.1 K线回调 `onKline(closePrice)`
## 七、关键公式
### 7.1 网格步长
```
更新 lastKlinePrice → updateUnrealizedPnl()
→ STOPPED: 清条件单+平仓+startGrid()
→ WAITING_KLINE: 市价双开基底
→ ACTIVE: checkProfitAndReset()(每根K线检查盈亏达标)
step = shortBaseEntryPrice × gridRate    (priceScale 精度对齐)
```
### 5.2 仓位推送 `onPositionUpdate(instId, posSide, posSize, avgPx)`
```
基底首次成交 → tryGenerateQueues()
后续仓位变化 → 更新 positionSize/entryPrice
仓位归零     → handlePositionZeroAndReset()
```
### 5.3 订单成交 `onOrderUpdate(algoId, state, ordType)`
```
state == "filled" 时:
  ┌─ findByLongStopLossOrderId(algoId) → handleLongStopLossTriggered()
  │   清空止损ID → grid -(N-1) 挂 count×qty 张多单
  │              → 若 N>2, 取消 grid -(N-2) 旧多单
  │
  ├─ findByShortStopLossOrderId(algoId) → handleShortStopLossTriggered()
  │   清空止损ID → grid N-1 挂 count×qty 张空单
  │              → 若 N>2, 取消 grid N-2 旧空单
  │
  ├─ findByShortOrderId(algoId) → extendShortStopLoss(filledQty)
  │   从最远空仓止损向外扩展 stopLossCount 个网格止损
  │
  └─ findByLongOrderId(algoId) → extendLongStopLoss(filledQty)
      从最远多仓止损向外扩展 stopLossCount 个网格止损
```
### 5.4 平仓推送 `onPositionClose(side, pnl)`
```
cumulativePnl += pnl
→ 总盈亏 ≤ -maxLoss → 钉钉告警(不停止,仅通知)
```
## 六、关键公式
### 6.1 网格步长
```
step = shortBaseEntryPrice × gridRate    (对齐到 priceScale 精度)
```
### 6.2 止损触发 → 追单数量
### 7.2 止损触发 → 追单
```
priceDiff = |avgEntryPrice - newEntryGridPrice|.abs()
count     = priceDiff / step  (DOWN取整, 最小1)
entryQty  = count × quantity
count     = priceDiff / step           (RoundingMode.DOWN, 最小1)
entryQty  = count × quantity           → 挂 ordType=trigger 条件单
```
### 6.3 挂单成交 → 追挂止损
### 7.3 挂单成交 → 追挂止损
```
stopLossCount = filledQty / quantity
从最远止损ID向外扩展 stopLossCount 个网格,每格挂 1个 quantity张的止损单
从最远止损ID向外扩展 stopLossCount 个网格
每格挂 1 个 quantity 张止损 (ordType=conditional, slTriggerPx)
```
### 6.4 未实现盈亏
### 7.4 未实现盈亏
```
多仓Pnl = longPositionSize × ctVal × (lastKlinePrice - longEntryPrice)
空仓Pnl = shortPositionSize × ctVal × (shortEntryPrice - lastKlinePrice)
unrealizedPnl = 多仓Pnl + 空仓Pnl
longPnl  = longPositionSize × ctVal × (lastKlinePrice - longEntryPrice)
shortPnl = shortPositionSize × ctVal × (shortEntryPrice - lastKlinePrice)
unrealizedPnl = longPnl + shortPnl
```
### 6.5 盈亏达标检查
### 7.5 盈亏达标检查
```
upl + availEq > initialPrincipal + expectedProfit  →  重置策略
GET balance → upl (未实现盈亏) + availEq (可用保证金)
if upl + availEq > initialPrincipal + expectedProfit → STOPPED → 平仓+清条件单+startGrid()
```
## 七、REST API 映射
## 八、REST API 映射
| OkxTradeExecutor方法 | OKX API | 用途 |
|------|---------|------|
| `openLong(size)` / `openShort(size)` | `POST /api/v5/trade/order` (market) | 市价基底开仓 |
| `marketClose(side, posSide, sz)` | `POST /api/v5/trade/order` (market, reduceOnly) | 市价平仓 |
| `placeConditionalEntryOrder(tp, side, posSide, sz)` | `POST /api/v5/trade/order-algo` (triggerPx) | 条件开仓单 |
| `placeTakeProfit(tp, side, posSide, sz)` | `POST /api/v5/trade/order-algo` (slTriggerPx) | 止损/止盈条件单 |
| `cancelAlgoOrder(algoId)` | `POST /api/v5/trade/cancel-algos` | 取消单个条件单 |
| `getBalance()` | `GET /api/v5/account/balance` | 查询余额 |
| `getPositions()` | `GET /api/v5/account/positions` | 查询持仓 |
| `setLeverage(lever)` | `POST /api/v5/account/set-leverage` | 设置杠杆 |
| 方法 | OKX API | ordType | 触发参数 | 用途 |
|------|---------|---------|---------|------|
| `openLong(size)` | `POST /api/v5/trade/order` | `market` | — | 市价开多 |
| `openShort(size)` | `POST /api/v5/trade/order` | `market` | — | 市价开空 |
| `marketClose(s,p,sz)` | `POST /api/v5/trade/order` | `market` | reduceOnly | 市价平仓 |
| `placeConditionalEntryOrder` | `POST /api/v5/trade/order-algo` | **`trigger`** | `triggerPx` | 计划委托开仓 |
| `placeTakeProfit` | `POST /api/v5/trade/order-algo` | **`conditional`** | `slTriggerPx` | 止损/止盈平仓 |
| `cancelAlgoOrder(id)` | `POST /api/v5/trade/cancel-algos` | — | array body | 取消单个条件单 |
| `cancelAllAlgoOrders()` | `POST /api/v5/trade/cancel-algos` | — | array body | 清除全部条件单 |
| `getBalance()` | `GET /api/v5/account/balance` | — | ccy=USDT | 查询余额 |
| `getPositions()` | `GET /api/v5/account/positions` | — | instId | 查询持仓 |
| `setLeverage(l)` | `POST /api/v5/account/set-leverage` | — | lever+mgnMode | 设置杠杆 |
## 八、配置参数(OkxConfig.Builder默认值)
### 8.1 ordType 对照表
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `instId` | `ETH-USDT-SWAP` | 合约名称 |
| `leverage` | `100` | 杠杆倍数 |
| `tdMode` | `cross` | 全仓保证金 |
| `gridRate` | `0.0025` | 网格间距 0.25% |
| `expectedProfit` | `2` | 预期收益 2 USDT |
| `maxLoss` | `15` | 最大亏损 15 USDT |
| `quantity` | `1` | 每格下单张数 |
| `baseQuantity` | `10` | 基底开仓张数 |
| `priceScale` | `2` | 价格精度 |
| `ctVal` | `0.1` | 合约面值 |
| `gridQueueSize` | `300` | 队列容量 |
| `marginRatioLimit` | `0.2` | 保证金占比上限 20% |
| ordType | OKX含义 | 我们用途 | 触发价参数 |
|---------|--------|---------|-----------|
| `trigger` | 计划委托 | **开仓挂单**(等价格到位开仓) | `triggerPx` |
| `conditional` | 单向止盈止损 | **止损单**(等价格到位平仓) | `slTriggerPx` |
## 九、线程模型
## 九、网格ID示例(BTC-USDT, step≈70)
```
WS回调线程(串行)             Executor线程(单线程池)
  │                              │
  ├─ onKline()                   ├─ openLong/openShort
  ├─ onPositionUpdate()          ├─ marketClose
  ├─ onOrderUpdate() ──下单──→   ├─ placeConditionalEntryOrder
  │                              ├─ placeTakeProfit
  │                              └─ cancelAlgoOrder
ID=-11  price=69309.67 ← 最远多仓止损
ID=-10  price=69379.97
ID= -9  price=69450.27
ID= -8  price=69520.57
ID= -7  price=69590.87
ID= -6  price=69661.17
ID= -5  price=69731.47
ID= -4  price=69801.77
ID= -3  price=69872.07
ID= -2  price=69942.37  ← 多仓止损起点
ID= -1  price=70012.67
ID=  0  price=70082.97  ← 基底 (shortBaseEntryPrice)
ID=  1  price=70153.27
ID=  2  price=70223.57  ← 空仓止损起点
ID=  3  price=70293.87
ID=  4  price=70364.17
ID=  5  price=70434.47
ID=  6  price=70504.77
ID=  7  price=70575.07
ID=  8  price=70645.37
ID=  9  price=70715.67
ID= 10  price=70785.97
ID= 11  price=70856.27  ← 最远空仓止损
```
## 十、线程模型
```
[ctReadThread-XX] WS回调线程(串行)     [okx-trade-worker] Executor(单线程池)
  │                                        │
  ├─ onKline(closePrice)                   ├─ openLong / openShort
  │   └─ updateUnrealizedPnl()             ├─ marketClose
  │   └─ checkProfitAndReset()(同步REST)   ├─ placeConditionalEntryOrder (trigger)
  │                                        ├─ placeTakeProfit (conditional)
  ├─ onPositionUpdate(...)                 └─ cancelAlgoOrder / cancelAllAlgoOrders
  │   └─ tryGenerateQueues() → 批量挂止损
  │   └─ handlePositionZeroAndReset()
  │
  └─ checkProfitAndReset() (REST同步查余额)
  └─ onOrderUpdate(algoId, state, ordType)
      ├─ handleLongStopLossTriggered → placeConditionalEntryOrder
      ├─ handleShortStopLossTriggered → placeConditionalEntryOrder
      ├─ extendLongStopLoss → placeTakeProfit
      └─ extendShortStopLoss → placeTakeProfit
```
- WS回调在 `[ctReadThread-XX]` 中**串行**执行,避免并发问题
- REST API 提交到 `okx-trade-worker` 单线程池异步执行,避免阻塞WS线程
- `closeExistingPositions()` 和 `handlePositionZeroAndReset()` 在WS线程中同步调用REST,需注意耗时
- `LostConnectionChecker` 已关闭(`setConnectionLostTimeout(0)`),由应用层 `ping`/`pong` 心跳接管
- WS 回调线程串行执行,天然线程安全
- REST 提交到 `okx-trade-worker` 单线程异步执行,避免阻塞 WS
- `closeExistingPositions()` / `handlePositionZeroAndReset()` 在 WS 线程中同步调用 REST(IOC 市价单,秒级完成)
- `LostConnectionChecker(0)` 已禁用,由应用层 `send("ping")`/`handle "pong"` 接管
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxConfig.java
@@ -154,7 +154,7 @@
        private boolean isSimulate = false;
        private int gridQueueSize = 300;
        private BigDecimal marginRatioLimit = new BigDecimal("0.2");
        private BigDecimal ctVal = new BigDecimal("0.1");
        private BigDecimal ctVal = new BigDecimal("0.01");
        private int priceScale = 2;
        public Builder apiKey(String apiKey) { this.apiKey = apiKey; return this; }
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
@@ -95,7 +95,7 @@
                    .quantity("1")
                    .baseQuantity("10")
                    .priceScale(2)
                    .ctVal(new BigDecimal("0.1"))
                    .ctVal(new BigDecimal("0.01"))
                    .isSimulate(!primaryAccount.isAccountType())
                    .gridQueueSize(300)
                    .marginRatioLimit(new BigDecimal("0.2"))