# OKX 网格交易策略 — 架构文档 ## 一、模块总览 ``` okxNewPrice/ ├── OkxConfig.java # 全局配置(Builder模式) ├── OkxGridTradeService.java # 核心策略引擎 ├── OkxTradeExecutor.java # REST API 异步执行器 ├── OkxGridElement.java # 网格价格层级 + 8个全局O(1)索引 ├── OkxTraderParam.java # 单笔挂单参数 ├── OkxGridWsClient.java # WebSocket 客户端(双连接) ├── OkxWebSocketClientManager.java # Spring容器入口 + WS组装 ├── ExchangeInfoEnum.java # 账户枚举 │ ├── gridWs/ │ ├── OkxGridChannelHandler.java # 频道处理器接口 │ ├── OkxKlineChannelHandler.java # candle1m → onKline() │ ├── OkxPositionsChannelHandler.java # positions → onPositionUpdate() │ ├── OkxOrdersChannelHandler.java # orders → onOrderUpdate()(替代orders-algo) │ └── OkxAlgoOrdersChannelHandler.java # orders-algo(测试网不可用,保留备用) │ └── okxpi/config/ # OKX API 底层HTTP/签名工具 ``` ## 二、策略生命周期 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 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 │ └─────────────────────────────────────────────────────────────────┘ ``` ### 状态机 | 状态 | 含义 | 进入条件 | |------|------|---------| | `WAITING_KLINE` | 等待首根K线 | startGrid() | | `OPENING` | 基底开仓中 | 首根K线到达 | | `ACTIVE` | 策略运行中 | 网格初始化完成 | | `STOPPED` | 已停止(等待重启) | 盈亏达标/持仓归零 | ## 三、WS连接架构 ``` ┌──────────────────────────────────────────────────────┐ │ OkxWebSocketClientManager (@PostConstruct) │ │ │ │ ┌─ gridWsClientPublic ───────────────────────────┐ │ │ │ URL: /ws/v5/business (无需登录,直连订阅) │ │ │ │ ├── OkxKlineChannelHandler (candle1m) │ │ │ └────────────────────────────────────────────────┘ │ │ │ │ ┌─ gridWsClientPrivate ──────────────────────────┐ │ │ │ URL: /ws/v5/private (先登录再订阅) │ │ │ │ ├── OkxPositionsChannelHandler (positions) │ │ │ │ └── OkxOrdersChannelHandler (orders) │ │ │ └────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘ ``` | 连接 | 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体系 ``` 价格方向: 低 ←────────────────── 基准价 ──────────────────→ 高 ID: -N ... -3 -2 -1 0 1 2 3 ... N 用途: 多仓止损区 基底 空仓止损区 (初始化: -2~-11) (初始化: 2~11) 链表: ... ← -3 ← -2 ← -1 ← 0 → 1 → 2 → 3 → ... (通过 upId/downId + INDEX 实现 O(1) 遍历) ``` ### GridElement 字段 | 类别 | 字段 | 说明 | |------|------|------| | 标识 | `id`, `gridPrice`, `upId`, `downId` | 编号、价格、双向链表指针 | | 多仓订单 | `hasLongOrder`, `longOrderId`, `longTraderParam` | 多仓条件单状态 | | 空仓订单 | `hasShortOrder`, `shortOrderId`, `shortTraderParam` | 空仓条件单状态 | | 止盈 | `longTakeProfitOrderId`, `shortTakeProfitOrderId` | 止盈algoId | | 止损 | `longStopLossOrderId`, `shortStopLossOrderId` | 止损algoId | ### 8个全局O(1)索引 ``` INDEX → findById(int) PRICE_INDEX → findByPrice(BigDecimal) LONG_ORDER_ID_INDEX → findByLongOrderId(String) SHORT_ORDER_ID_INDEX → findByShortOrderId(String) LONG_TP_ORDER_ID_INDEX → findByLongTakeProfitOrderId(String) SHORT_TP_ORDER_ID_INDEX→ findByShortTakeProfitOrderId(String) LONG_SL_ORDER_ID_INDEX → findByLongStopLossOrderId(String) SHORT_SL_ORDER_ID_INDEX→ findByShortStopLossOrderId(String) ``` ## 五、事件驱动循环(ACTIVE状态) ### 5.1 K线回调 `onKline(closePrice)` ``` 更新 lastKlinePrice → updateUnrealizedPnl() → STOPPED: 清条件单+平仓+startGrid() → WAITING_KLINE: 市价双开基底 → ACTIVE: checkProfitAndReset()(每根K线检查盈亏达标) ``` ### 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 止损触发 → 追单数量 ``` priceDiff = |avgEntryPrice - newEntryGridPrice|.abs() count = priceDiff / step (DOWN取整, 最小1) entryQty = count × quantity ``` ### 6.3 挂单成交 → 追挂止损 ``` stopLossCount = filledQty / quantity 从最远止损ID向外扩展 stopLossCount 个网格,每格挂 1个 quantity张的止损单 ``` ### 6.4 未实现盈亏 ``` 多仓Pnl = longPositionSize × ctVal × (lastKlinePrice - longEntryPrice) 空仓Pnl = shortPositionSize × ctVal × (shortEntryPrice - lastKlinePrice) unrealizedPnl = 多仓Pnl + 空仓Pnl ``` ### 6.5 盈亏达标检查 ``` upl + availEq > initialPrincipal + expectedProfit → 重置策略 ``` ## 七、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` | 设置杠杆 | ## 八、配置参数(OkxConfig.Builder默认值) | 参数 | 默认值 | 说明 | |------|--------|------| | `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% | ## 九、线程模型 ``` WS回调线程(串行) Executor线程(单线程池) │ │ ├─ onKline() ├─ openLong/openShort ├─ onPositionUpdate() ├─ marketClose ├─ onOrderUpdate() ──下单──→ ├─ placeConditionalEntryOrder │ ├─ placeTakeProfit │ └─ cancelAlgoOrder │ └─ checkProfitAndReset() (REST同步查余额) ``` - WS回调在 `[ctReadThread-XX]` 中**串行**执行,避免并发问题 - REST API 提交到 `okx-trade-worker` 单线程池异步执行,避免阻塞WS线程 - `closeExistingPositions()` 和 `handlePositionZeroAndReset()` 在WS线程中同步调用REST,需注意耗时 - `LostConnectionChecker` 已关闭(`setConnectionLostTimeout(0)`),由应用层 `ping`/`pong` 心跳接管