OKX 网格交易策略 — 架构文档
一、模块总览
okxNewPrice/
├── 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(closePrice)
│ ├── OkxPositionsChannelHandler.java # positions → onPositionUpdate()
│ ├── OkxOrdersChannelHandler.java # orders → onOrderUpdate() (替代orders-algo)
│ └── OkxAlgoOrdersChannelHandler.java # orders-algo(测试网60018不可用,备用)
│
└── okxpi/config/ # OKX V5 API 底层HTTP签名/请求工具链
├── Account.java / OKXAccount.java
├── RequestHandler.java / ResponseHandler.java
└── utils/ (SignUtils, UrlBuilder, OkHttpUtils...)
二、Spring 容器入口
OkxWebSocketClientManager 是 @Component + @PostConstruct 驱动的组装点。
2.1 激活条件
# application-test.yml
app:
quant: true # @ConditionalOnProperty 控制是否启动
2.2 初始化顺序 (@PostConstruct)
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
2.3 销毁顺序 (@PreDestroy)
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) 遍历)
6.1 GridElement 字段
| 类别 |
字段 |
说明 |
| 标识 |
id, gridPrice, upId, downId |
编号、价格、双向链表指针 |
| 多仓 |
hasLongOrder, longOrderId, longTraderParam |
多仓挂单状态 |
| 空仓 |
hasShortOrder, shortOrderId, shortTraderParam |
空仓挂单状态 |
| 止盈 |
longTakeProfitOrderId, shortTakeProfitOrderId |
止盈algoId |
| 止损 |
longStopLossOrderId, shortStopLossOrderId |
止损algoId |
6.2 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)
每次订单状态变更后调用 OkxGridElement.refreshIndices() 增量重建,同时 logAll() 打印全量网格数据。
七、关键公式
7.1 网格步长
step = shortBaseEntryPrice × gridRate (priceScale 精度对齐)
7.2 止损触发 → 追单
priceDiff = |avgEntryPrice - newEntryGridPrice|.abs()
count = priceDiff / step (RoundingMode.DOWN, 最小1)
entryQty = count × quantity → 挂 ordType=trigger 条件单
7.3 挂单成交 → 追挂止损
stopLossCount = filledQty / quantity
从最远止损ID向外扩展 stopLossCount 个网格
每格挂 1 个 quantity 张止损 (ordType=conditional, slTriggerPx)
7.4 未实现盈亏
longPnl = longPositionSize × ctVal × (lastKlinePrice - longEntryPrice)
shortPnl = shortPositionSize × ctVal × (shortEntryPrice - lastKlinePrice)
unrealizedPnl = longPnl + shortPnl
7.5 盈亏达标检查
GET balance → upl (未实现盈亏) + availEq (可用保证金)
if upl + availEq > initialPrincipal + expectedProfit → STOPPED → 平仓+清条件单+startGrid()
八、REST API 映射
| 方法 |
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 |
设置杠杆 |
8.1 ordType 对照表
| ordType |
OKX含义 |
我们用途 |
触发价参数 |
trigger |
计划委托 |
开仓挂单(等价格到位开仓) |
triggerPx |
conditional |
单向止盈止损 |
止损单(等价格到位平仓) |
slTriggerPx |
九、网格ID示例(BTC-USDT, step≈70)
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()
│
└─ onOrderUpdate(algoId, state, ordType)
├─ handleLongStopLossTriggered → placeConditionalEntryOrder
├─ handleShortStopLossTriggered → placeConditionalEntryOrder
├─ extendLongStopLoss → placeTakeProfit
└─ extendShortStopLoss → placeTakeProfit
- WS 回调线程串行执行,天然线程安全
- REST 提交到
okx-trade-worker 单线程异步执行,避免阻塞 WS
closeExistingPositions() / handlePositionZeroAndReset() 在 WS 线程中同步调用 REST(IOC 市价单,秒级完成)
LostConnectionChecker(0) 已禁用,由应用层 send("ping")/handle "pong" 接管