feat(gateApi): 更新网格交易逻辑文档和实现
- 修改回调设计,为placeGridLimitOrder添加Consumer<String>接收orderId参数
- 新增placeConditionalEntryOrder和cancelConditionalOrder方法支持条件单操作
- 添加多仓和空仓止盈队列以及当前订单ID状态管理
- 重构网格核心逻辑,增加止盈队列管理和条件单挂单机制
- 更新初始化流程,改用条件单替代限价单进行网格开仓
- 完善异常处理和保证金安全检查机制
| | |
| | | log.info("[Gate] 多止盈队列:{}", longTakeProfitQueue); |
| | | log.info("[Gate] 空止盈队列:{}", shortTakeProfitQueue); |
| | | |
| | | executor.placeGridLimitOrder(longPriceQueue.get(0), config.getQuantity(), |
| | | orderId -> { currentLongOrderId = orderId; log.info("[Gate] 初始限价多单已挂, id:{}", orderId); }, |
| | | executor.placeConditionalEntryOrder(longPriceQueue.get(0), |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), |
| | | orderId -> { currentLongOrderId = orderId; log.info("[Gate] 初始条件多单已挂, id:{}, trigger:{}", orderId, longPriceQueue.get(0)); }, |
| | | null); |
| | | executor.placeGridLimitOrder(shortPriceQueue.get(0), negate(config.getQuantity()), |
| | | orderId -> { currentShortOrderId = orderId; log.info("[Gate] 初始限价空单已挂, id:{}", orderId); }, |
| | | executor.placeConditionalEntryOrder(shortPriceQueue.get(0), |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()), |
| | | orderId -> { currentShortOrderId = orderId; log.info("[Gate] 初始条件空单已挂, id:{}, trigger:{}", orderId, shortPriceQueue.get(0)); }, |
| | | null); |
| | | |
| | | state = StrategyState.ACTIVE; |
| | |
| | | log.info("[Gate] 空止盈队列增加:{}, 现止盈队列:{}", stpElem, shortTakeProfitQueue); |
| | | |
| | | if (!isMarginSafe()) { |
| | | log.warn("[Gate] 保证金超限,跳过挂限价单"); |
| | | log.warn("[Gate] 保证金超限,跳过挂条件单"); |
| | | } else { |
| | | String oldLongId = currentLongOrderId; |
| | | executor.cancelOrder(oldLongId); |
| | | executor.placeGridLimitOrder(newShortFirst, negate(config.getQuantity()), |
| | | orderId -> { currentShortOrderId = orderId; log.info("[Gate] 新限价空单, id:{}, price:{}", orderId, newShortFirst); }, |
| | | executor.cancelConditionalOrder(oldLongId); |
| | | executor.placeConditionalEntryOrder(newShortFirst, |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()), |
| | | orderId -> { currentShortOrderId = orderId; log.info("[Gate] 新条件空单, id:{}, trigger:{}", orderId, newShortFirst); }, |
| | | null); |
| | | executor.placeGridLimitOrder(newLongFirst, config.getQuantity(), |
| | | orderId -> { currentLongOrderId = orderId; log.info("[Gate] 新限价多单, id:{}, price:{}", orderId, newLongFirst); }, |
| | | executor.placeConditionalEntryOrder(newLongFirst, |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), |
| | | orderId -> { currentLongOrderId = orderId; log.info("[Gate] 新条件多单, id:{}, trigger:{}", orderId, newLongFirst); }, |
| | | null); |
| | | } |
| | | |
| | |
| | | log.info("[Gate] 多止盈队列增加:{}, 现止盈队列:{}", ltpElem, longTakeProfitQueue); |
| | | |
| | | if (!isMarginSafe()) { |
| | | log.warn("[Gate] 保证金超限,跳过挂限价单"); |
| | | log.warn("[Gate] 保证金超限,跳过挂条件单"); |
| | | } else { |
| | | String oldShortId = currentShortOrderId; |
| | | executor.cancelOrder(oldShortId); |
| | | executor.placeGridLimitOrder(newLongFirst, config.getQuantity(), |
| | | orderId -> { currentLongOrderId = orderId; log.info("[Gate] 新限价多单, id:{}, price:{}", orderId, newLongFirst); }, |
| | | executor.cancelConditionalOrder(oldShortId); |
| | | executor.placeConditionalEntryOrder(newLongFirst, |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_1, config.getQuantity(), |
| | | orderId -> { currentLongOrderId = orderId; log.info("[Gate] 新条件多单, id:{}, trigger:{}", orderId, newLongFirst); }, |
| | | null); |
| | | executor.placeGridLimitOrder(newShortFirst, negate(config.getQuantity()), |
| | | orderId -> { currentShortOrderId = orderId; log.info("[Gate] 新限价空单, id:{}, price:{}", orderId, newShortFirst); }, |
| | | executor.placeConditionalEntryOrder(newShortFirst, |
| | | FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(config.getQuantity()), |
| | | orderId -> { currentShortOrderId = orderId; log.info("[Gate] 新条件空单, id:{}, trigger:{}", orderId, newShortFirst); }, |
| | | null); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 异步创建条件开仓单(价格触发开仓)。 |
| | | * |
| | | * <p>服务器监控价格,达到触发价后以市价 IOC 开仓。与止盈单不同,不设 order_type(默认开仓), |
| | | * reduce_only=false。适用于"价格到达 X 才买入 / 跌到 Y 才卖出"的场景。 |
| | | * |
| | | * @param triggerPrice 触发价格 |
| | | * @param rule 触发规则(NUMBER_1: 最新价≥触发价时执行;NUMBER_2: 最新价≤触发价时执行) |
| | | * @param size 开仓张数(正=开多,负=开空) |
| | | * @param onSuccess 成功回调,接收 conditionOrderId |
| | | * @param onFailure 失败回调 |
| | | */ |
| | | public void placeConditionalEntryOrder(BigDecimal triggerPrice, |
| | | FuturesPriceTrigger.RuleEnum rule, |
| | | String size, |
| | | Consumer<String> onSuccess, |
| | | Runnable onFailure) { |
| | | executor.execute(() -> { |
| | | try { |
| | | FuturesPriceTrigger trigger = new FuturesPriceTrigger(); |
| | | trigger.setStrategyType(FuturesPriceTrigger.StrategyTypeEnum.NUMBER_0); |
| | | trigger.setPriceType(FuturesPriceTrigger.PriceTypeEnum.NUMBER_0); |
| | | trigger.setPrice(triggerPrice.toString()); |
| | | trigger.setRule(rule); |
| | | trigger.setExpiration(0); |
| | | |
| | | FuturesInitialOrder initial = new FuturesInitialOrder(); |
| | | initial.setContract(contract); |
| | | initial.setSize(Long.parseLong(size)); |
| | | initial.setPrice("0"); |
| | | initial.setTif(FuturesInitialOrder.TifEnum.IOC); |
| | | initial.setReduceOnly(false); |
| | | |
| | | FuturesPriceTriggeredOrder order = new FuturesPriceTriggeredOrder(); |
| | | order.setTrigger(trigger); |
| | | order.setInitial(initial); |
| | | |
| | | TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order); |
| | | String orderId = String.valueOf(response.getId()); |
| | | log.info("[TradeExec] 条件开仓单已创建, trigger:{}, rule:{}, size:{}, id:{}", |
| | | triggerPrice, rule, size, orderId); |
| | | if (onSuccess != null) { |
| | | onSuccess.accept(orderId); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[TradeExec] 条件开仓单创建失败, trigger:{}, size:{}", triggerPrice, size, e); |
| | | if (onFailure != null) { |
| | | onFailure.run(); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 异步取消单个条件单。 |
| | | * |
| | | * @param orderId 条件单 ID,为 null 时跳过 |
| | | */ |
| | | public void cancelConditionalOrder(String orderId) { |
| | | if (orderId == null) { |
| | | return; |
| | | } |
| | | executor.execute(() -> { |
| | | try { |
| | | futuresApi.cancelPriceTriggeredOrder(SETTLE, Long.parseLong(orderId)); |
| | | log.info("[TradeExec] 条件单已取消, id:{}", orderId); |
| | | } catch (Exception e) { |
| | | log.warn("[TradeExec] 取消条件单失败(可能已触发), id:{}", orderId); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 构建 FuturesPriceTriggeredOrder 对象。 |
| | | * |
| | | * <p>策略=0(价格触发),price_type=0(最新价),expiration=0(永不过期), |
| | |
| | | - allowCoreThreadTimeOut: 60s 空闲后线程回收 |
| | | |
| | | **回调设计**: |
| | | - 每个下单方法接受 `onSuccess` 和 `onFailure` 两个 `Runnable` |
| | | - REST 调用成功 → 执行 `onSuccess`(标记基底已开等) |
| | | - `openLong`/`openShort`/`placeTakeProfit` 接受 `onSuccess` 和 `onFailure` 两个 `Runnable` |
| | | - `placeGridLimitOrder` 的 `onSuccess` 为 `Consumer<String>`(接收 orderId) |
| | | - REST 调用成功 → 执行 `onSuccess`(标记基底已开、记录 orderId 等) |
| | | - REST 调用失败 → 执行 `onFailure`(当前版本多为 null,依赖 position 推送修正) |
| | | |
| | | | 方法 | 说明 | |
| | | |------|------| |
| | | | `openLong(qty, onSuccess, onFailure)` | 异步 IOC 市价开多,双回调 | |
| | | | `openShort(qty, onSuccess, onFailure)` | 异步 IOC 市价开空,双回调 | |
| | | | `placeGridLimitOrder(price, size, onSuccess, onFailure)` | 异步 GTC 限价单(网格开仓用),onSuccess 接收 orderId | |
| | | | `cancelOrder(orderId)` | 异步取消指定挂单(orderId 为 null 时跳过) | |
| | | | `placeTakeProfit(trigger, rule, type, size)` | 异步止盈条件单(plan-close-*-position)。size 为显式平仓张数(正=平空,负=平多),多次调用互不影响 | |
| | | | `cancelAllPriceTriggeredOrders()` | 清除所有条件单 | |
| | | | `shutdown()` | 等待10秒,超时强制关闭 | |
| | |
| | | |------|------|------| |
| | | | shortPriceQueue | List\<BigDecimal\> | 空仓价格队列,降序(大→小),容量 gridQueueSize | |
| | | | longPriceQueue | List\<BigDecimal\> | 多仓价格队列,升序(小→大),容量 gridQueueSize | |
| | | | longTakeProfitQueue | List\<BigDecimal\> | 多仓止盈队列,升序(小→大),仓位推送时消费 | |
| | | | shortTakeProfitQueue | List\<BigDecimal\> | 空仓止盈队列,降序(大→小),仓位推送时消费 | |
| | | | currentLongOrderId | String | 当前多仓限价单 ID(用于取消旧单) | |
| | | | currentShortOrderId | String | 当前空仓限价单 ID(用于取消旧单) | |
| | | | shortBaseEntryPrice | BigDecimal | 基底空头入场价(仅首次记录,用于生成队列) | |
| | | | longBaseEntryPrice | BigDecimal | 基底多头入场价(仅首次记录,用于生成队列) | |
| | | | shortEntryPrice | BigDecimal | 当前空仓入场价(推送实时更新,加权均价) | |
| | |
| | | | initialPrincipal | BigDecimal | 初始本金(启动时账户总资产) | |
| | | |
| | | **回调方法**: |
| | | - `onKline(closePrice)`: 更新 lastKlinePrice → 计算 unrealizedPnl → WAITING_KLINE 时触发基底双开,ACTIVE 时驱动 processShortGrid+processLongGrid |
| | | - `onPositionUpdate(contract, mode, size, entryPrice)`: 记录当前入场价和持仓量 → 基底:首次成交记录入场价、生成队列;非基底:按 quantity 张数创建独立止盈条件单(plan-close-*-position)。无仓位时清空持仓量并标记不活跃 |
| | | - `onKline(closePrice)`: 更新 lastKlinePrice → 计算 unrealizedPnl → WAITING_KLINE 时触发基底双开,ACTIVE 时方向区分(closePrice>longPriceQueue[0]→processLongGrid,closePrice<shortPriceQueue[0]→processShortGrid,其余跳过) |
| | | - `onPositionUpdate(contract, mode, size, entryPrice)`: 记录当前入场价和持仓量 → 基底:首次成交记录入场价、生成队列+止盈队列+挂限价单;非基底(仓位增加):按 quantity 为单位计算增量,逐个从止盈队列消费止盈价(队列不足时 entryPrice ± step 兜底),每批挂独立止盈条件单。无仓位时清空持仓量并标记不活跃 |
| | | - `onPositionClose(contract, side, pnl)`: 累加已实现盈亏,检查停止条件 |
| | | |
| | | **processShortGrid / processLongGrid 核心逻辑**: |
| | |
| | | | 步骤 | processShortGrid | processLongGrid | |
| | | |------|-----------------|-----------------| |
| | | | 匹配 | 收集 shortPriceQueue 中 > currentPrice 的元素 | 收集 longPriceQueue 中 < currentPrice 的元素 | |
| | | | 本队补充 | 尾价 − step 循环递减 | 尾价 + step 循环递增 | |
| | | | 对方转移 | 以多仓首元素为种子,递减 step | 以空仓首元素为种子,递增 step | |
| | | | 贴近过滤 | 新增与 longEntryPrice 太近 → 跳过 | 新增与 shortEntryPrice 太近 → 跳过 | |
| | | | 本队补充 | 尾价 − step 循环递减 × matched.size() 次 | 尾价 + step 循环递增 × matched.size() 次 | |
| | | | 对方转移 | 以多仓首元素为种子,递减 step × i | 以空仓首元素为种子,递增 step × i | |
| | | | 止盈队列 | shortTakeProfitQueue.add(新 short[0] − step),降序 | longTakeProfitQueue.add(新 long[0] + step),升序 | |
| | | | 下单 | 取消旧多仓限价单 → 挂新空单(新 short[0]) + 新多单(新 long[0]) | 取消旧空仓限价单 → 挂新多单(新 long[0]) + 新空单(新 short[0]) | |
| | | | 额外反向 | closePrice在[空均价,多均价]间 且 多>空 → openLong | closePrice在[空均价,多均价]间 且 多>空 → openShort | |
| | | |
| | | **未实现盈亏计算** (`updateUnrealizedPnl()`): |
| | | |
| | |
| | | |
| | | | 方向 | 公式 | order_type | size(平仓张数) | rule | |
| | | |------|------|------------|-----------|------| |
| | | | 多头 TP | longPriceQueue[0](多仓队列首元素) | `plan-close-long-position` | `-quantity`(负=平多) | NUMBER_1(≥触发价) | |
| | | | 空头 TP | shortPriceQueue[0](空仓队列首元素) | `plan-close-short-position` | `+quantity`(正=平空) | NUMBER_2(≤触发价) | |
| | | | 多头 TP | longTakeProfitQueue.remove(0),空时兜底 longEntryPrice + step | `plan-close-long-position` | `-quantity`(负=平多) | NUMBER_1(≥触发价) | |
| | | | 空头 TP | shortTakeProfitQueue.remove(0),空时兜底 shortEntryPrice − step | `plan-close-short-position` | `+quantity`(正=平空) | NUMBER_2(≤触发价) | |
| | | |
| | | > 止盈价取自对应方向队列的首元素(多仓队列升序首=最小价,空仓队列降序首=最高价)。止盈单使用显式张数而非 autoSize。每次网格触发开仓 quantity 张,只为该批张数创建独立的条件单,多个止盈单之间互不覆盖。 |
| | | > 止盈价从止盈队列消费。止盈队列由 K线触发时新增元素(新 queue[0] ± step),仓位推送时按每批 quantity 张数逐个消费。 |
| | | > 止盈队列空时兜底用当前入场价 ± step。每批挂独立止盈条件单,互不覆盖。 |
| | | |
| | | **REST API 调用**: |
| | | |
| | |
| | | | 查账户 | `GET /futures/usdt/accounts` | `FuturesApi.listFuturesAccounts()` | 获取初始本金和保证金 | |
| | | | 清除条件单 | `DELETE /futures/usdt/price_orders` | `FuturesApi.cancelPriceTriggeredOrderList()` | | |
| | | | 市价单 | `POST /futures/usdt/orders` | `FuturesApi.createFuturesOrder()` | price=0, tif=IOC | |
| | | | 限价单 | `POST /futures/usdt/orders` | `FuturesApi.createFuturesOrder()` | price=具体价, tif=GTC(网格开仓用) | |
| | | | 取消订单 | `DELETE /futures/usdt/orders/{order_id}` | `FuturesApi.cancelFuturesOrder()` | 取消指定挂单 | |
| | | | 条件单 | `POST /futures/usdt/price_orders` | `FuturesApi.createPriceTriggeredOrder()` | strategy=0, price_type=0, expiration=0, order_type=plan-close-*-position, size=显式张数,多次调用互不冲突 | |
| | | |
| | | **初始化顺序** (`init()`): |
| | |
| | | 1. 获取用户 ID |
| | | 2. 查账户 → 记录初始本金 → 如需要切持仓模式 |
| | | 3. 清除旧的止盈止损条件单 |
| | | 4. 查当前合约所有仓位 → 逐个市价平仓 |
| | | - 单向持仓(single): size=相反数, reduce_only=true |
| | | - 双向持仓(dual): size=0, close=false, autoSize=LONG/SHORT, reduce_only=true |
| | | 5. 设杠杆 |
| | | 6. 打印账户余额 |
| | | 4. 设多/空方向杠杆 |
| | | 5. 重置策略状态 + 清空队列 + 清空止盈队列 + 清除 orderId |
| | | 6. 等待 K 线回调 → 双开基底(市价开多+市价开空,IOC) |
| | | 7. 双基底成交 → 生成网格队列 + 止盈队列初始化 + 挂限价多单+空单(GTC) → state=ACTIVE |
| | | 8. 异常回退: 10s 内任一基底未成交 → state=STOPPED |
| | | ``` |
| | | |
| | | --- |