edit | blame | history | raw

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-algoorders-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" 接管