| | |
| | | # 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()` 中基底开仓用限价单(取盘口卖一/买一价),减少滑点 |