| | |
| | | * Gate 网格交易服务类。 |
| | | * |
| | | * <h3>策略概述</h3> |
| | | * 多空双开 → 各放止盈条件单 → 仓位推送驱动补仓 → history_pnl 判断停止 |
| | | * 多空双开 → 各放止盈条件单 → 仓位推送检测平仓补仓 → 平仓推送累加 pnl → 判断停止 |
| | | * |
| | | * <h3>触发逻辑</h3> |
| | | * <pre> |
| | | * K线首次价格就绪 → dualOpenPositions() // 多空双开 + 止盈条件单 |
| | | * 仓位推送 size=0 → reopenXxxPosition() // 该方向被止盈平掉 → 补开 |
| | | * 仓位推送 history_pnl ≥ overallTp → 停止 |
| | | * 仓位推送 history_pnl ≤ -maxLoss → 停止 |
| | | * 仓位推送 size=0 → reopenXxxPosition() // 该方向被平仓 → 只补该方向 |
| | | * 平仓推送 pnl → cumulativePnl += pnl // 累计盈亏 |
| | | * cumulativePnl ≥ overallTp → 停止 |
| | | * cumulativePnl ≤ -maxLoss → 停止 |
| | | * </pre> |
| | | * |
| | | * <h3>止盈计算</h3> |
| | |
| | | * |
| | | * <h3>依赖</h3> |
| | | * 使用 {@code io.gate:gate-api (7.2.71)} SDK 通过 REST API 下单, |
| | | * 市场数据由 {@link GateKlineWebSocketClient} 通过 WebSocket 提供。 |
| | | * 市场数据由 WebSocket Handler 类提供。 |
| | | * |
| | | * @author Administrator |
| | | */ |
| | |
| | | private BigDecimal shortEntryPrice; |
| | | /** WebSocket 推送的最新 K 线收盘价 */ |
| | | private volatile BigDecimal lastKlinePrice; |
| | | /** 服务器返回的累计已实现盈亏 */ |
| | | private BigDecimal totalHistoryPnl = BigDecimal.ZERO; |
| | | /** 平仓推送累计的盈亏 */ |
| | | private volatile BigDecimal cumulativePnl = BigDecimal.ZERO; |
| | | /** 用户 ID,用于 WebSocket 私有频道订阅 */ |
| | | private Long userId; |
| | | |
| | |
| | | String contract, String leverage, |
| | | String marginMode, String positionMode, |
| | | BigDecimal gridRate, BigDecimal overallTp, |
| | | int maxCycles, BigDecimal maxLoss, |
| | | BigDecimal maxLoss, |
| | | String quantity) { |
| | | this.contract = contract; |
| | | this.leverage = leverage; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 初始化账户。设置杠杆、查询余额、切换持仓模式。 |
| | | * 初始化账户。顺序:切持仓模式 → 清旧条件单 → 设杠杆 → 查余额。 |
| | | */ |
| | | public void init() { |
| | | try { |
| | |
| | | this.userId = accountDetail.getUserId(); |
| | | log.info("[GateGrid] 用户ID: {}", userId); |
| | | |
| | | FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE); |
| | | String positionModeSet = account.getPositionMode(); |
| | | if (!positionMode.equals(positionModeSet)) { |
| | | futuresApi.setPositionMode(SETTLE, positionMode); |
| | | } |
| | | log.info("[GateGrid] 已设置持仓模式: {}", positionMode); |
| | | |
| | | futuresApi.cancelPriceTriggeredOrderList(SETTLE, contract); |
| | | log.info("[GateGrid] 已取消所有既有止盈止损条件单"); |
| | | |
| | |
| | | SETTLE, contract, leverage, marginMode, positionMode, null); |
| | | log.info("[GateGrid] 已设置杠杆: {}x, 保证金模式: {}", leverage, marginMode); |
| | | |
| | | FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE); |
| | | log.info("[GateGrid] 账户可用余额: {}, 总资产: {}", |
| | | account.getAvailable(), account.getTotal()); |
| | | String positionModeSet = account.getPositionMode(); |
| | | if (!positionMode.equals(positionModeSet)) { |
| | | futuresApi.setPositionMode(SETTLE, positionMode); |
| | | } |
| | | log.info("[GateGrid] 已设置双向持仓模式"); |
| | | } catch (GateApiException e) { |
| | | log.error("[GateGrid] 初始化失败, label: {}, msg: {}", e.getErrorLabel(), e.getMessage()); |
| | | } catch (ApiException e) { |
| | |
| | | return; |
| | | } |
| | | strategyActive = true; |
| | | totalHistoryPnl = BigDecimal.ZERO; |
| | | cumulativePnl = BigDecimal.ZERO; |
| | | log.info("[GateGrid] 网格策略启动,等待K线价格..."); |
| | | } |
| | | |
| | |
| | | */ |
| | | public void stopGrid() { |
| | | strategyActive = false; |
| | | log.info("[GateGrid] 网格策略已停止, history_pnl: {}", totalHistoryPnl); |
| | | log.info("[GateGrid] 网格策略已停止, cumulativePnl: {}", cumulativePnl); |
| | | } |
| | | |
| | | /** |
| | |
| | | * <li>size=0 且之前活跃 → 该方向被平仓 → 补开</li> |
| | | * <li>size>0 → 确认仓位活跃,更新入场价</li> |
| | | * </ul> |
| | | * 每次推送更新 history_pnl 并检查停止条件。 |
| | | * 注意:累计盈亏由 onPositionClose 独立计算。 |
| | | * |
| | | * @param contract 合约名 |
| | | * @param mode 仓位模式(dual_long / dual_short) |
| | | * @param size 仓位数量(0 表示无仓位) |
| | | * @param entryPrice 入场价格 |
| | | * @param historyPnl 已实现累计盈亏 |
| | | * @param realisedPnl 已实现盈亏 |
| | | */ |
| | | public void onPositionUpdate(String contract, String mode, BigDecimal size, |
| | | BigDecimal entryPrice, BigDecimal historyPnl, |
| | | BigDecimal realisedPnl) { |
| | | BigDecimal entryPrice) { |
| | | if (!strategyActive) { |
| | | return; |
| | | } |
| | |
| | | shortEntryPrice = entryPrice; |
| | | } |
| | | } |
| | | } |
| | | |
| | | totalHistoryPnl = historyPnl; |
| | | /** |
| | | * 平仓推送回调入口。由 {@link GateKlineWebSocketClient} 调用。 |
| | | * 累加平仓盈亏,每次平仓推送后检查停止条件。 |
| | | * |
| | | * @param contract 合约名 |
| | | * @param side 平仓方向(long / short) |
| | | * @param pnl 该次平仓的盈亏 |
| | | */ |
| | | public void onPositionClose(String contract, String side, BigDecimal pnl) { |
| | | if (!strategyActive) { |
| | | return; |
| | | } |
| | | cumulativePnl = cumulativePnl.add(pnl); |
| | | log.info("[GateGrid] 平仓盈亏累计, side:{}, pnl:{}, cumulativePnl:{}", side, pnl, cumulativePnl); |
| | | checkStopConditions(); |
| | | } |
| | | |
| | |
| | | * </ul> |
| | | */ |
| | | private void checkStopConditions() { |
| | | if (totalHistoryPnl.compareTo(overallTp) >= 0) { |
| | | log.info("[GateGrid] 累计止盈 {} 达到 {} USDT,停止策略", totalHistoryPnl, overallTp); |
| | | if (cumulativePnl.compareTo(overallTp) >= 0) { |
| | | log.info("[GateGrid] 累计止盈 {} 达到 {} USDT,停止策略", cumulativePnl, overallTp); |
| | | strategyActive = false; |
| | | return; |
| | | } |
| | | if (totalHistoryPnl.compareTo(maxLoss.negate()) <= 0) { |
| | | log.info("[GateGrid] 累计亏损 {} 达到上限 {} USDT,停止策略", totalHistoryPnl, maxLoss); |
| | | if (cumulativePnl.compareTo(maxLoss.negate()) <= 0) { |
| | | log.info("[GateGrid] 累计亏损 {} 达到上限 {} USDT,停止策略", cumulativePnl, maxLoss); |
| | | strategyActive = false; |
| | | } |
| | | } |
| | |
| | | FuturesPriceTrigger.RuleEnum rule, |
| | | String orderType, |
| | | String autoSize) { |
| | | FuturesPriceTriggeredOrder order = buildTriggeredOrder(triggerPrice, rule, orderType, autoSize); |
| | | 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(0L); |
| | | initial.setPrice("0"); |
| | | initial.setTif(FuturesInitialOrder.TifEnum.IOC); |
| | | initial.setReduceOnly(true); |
| | | initial.setAutoSize(autoSize); |
| | | |
| | | FuturesPriceTriggeredOrder order = new FuturesPriceTriggeredOrder(); |
| | | order.setTrigger(trigger); |
| | | order.setInitial(initial); |
| | | order.setOrderType(orderType); |
| | | |
| | | TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order); |
| | | log.info("[GateGrid] 止盈条件单已创建, triggerPrice:{}, orderType:{}, autoSize:{}, id:{}", |
| | | triggerPrice, orderType, autoSize, response.getId()); |
| | | } catch (GateApiException e) { |
| | | if ("AUTO_USER_EXIST_POSITION_ORDER".equals(e.getErrorLabel())) { |
| | | log.warn("[GateGrid] 止盈条件单已存在,取消旧单后重试, label:{}", e.getErrorLabel()); |
| | | log.warn("[GateGrid] 止盈条件单已存在,取消旧单后重试"); |
| | | try { |
| | | futuresApi.cancelPriceTriggeredOrderList(SETTLE, contract); |
| | | TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, createPriceTriggeredOrderBean(triggerPrice, rule, orderType, autoSize)); |
| | | TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order); |
| | | log.info("[GateGrid] 止盈条件单重试成功, triggerPrice:{}, orderType:{}, autoSize:{}, id:{}", |
| | | triggerPrice, orderType, autoSize, response.getId()); |
| | | } catch (Exception retryEx) { |
| | |
| | | } |
| | | } |
| | | |
| | | private FuturesPriceTriggeredOrder createPriceTriggeredOrderBean(BigDecimal triggerPrice, |
| | | private FuturesPriceTriggeredOrder buildTriggeredOrder(BigDecimal triggerPrice, |
| | | FuturesPriceTrigger.RuleEnum rule, |
| | | String orderType, |
| | | String autoSize) { |
| | |
| | | return strategyActive; |
| | | } |
| | | |
| | | public BigDecimal getTotalHistoryPnl() { |
| | | return totalHistoryPnl; |
| | | public BigDecimal getCumulativePnl() { |
| | | return cumulativePnl; |
| | | } |
| | | |
| | | public Long getUserId() { |