package com.xcong.excoin.modules.gateApi; import io.gate.gateapi.ApiClient; import io.gate.gateapi.ApiException; import io.gate.gateapi.GateApiException; import io.gate.gateapi.api.AccountApi; import io.gate.gateapi.api.FuturesApi; import io.gate.gateapi.models.AccountDetail; import io.gate.gateapi.models.FuturesAccount; import io.gate.gateapi.models.FuturesOrder; import io.gate.gateapi.models.FuturesPriceTrigger; import io.gate.gateapi.models.Position; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.math.RoundingMode; /** * Gate 网格交易服务类。使用 Gate SDK 通过 REST API 下单。 * *

状态机

*
 *   WAITING_KLINE → (首次 K 线) → OPENING → ACTIVE
 *                             双开失败 → STOPPED
 *
 *   ACTIVE:
 *     ├─ 仓位 size=0 且方向活跃 → REOPENING_L/S → ACTIVE
 *     │   补仓失败 → 重试 → 仍失败 → STOPPED
 *     └─ cumulativePnl ≥ overallTp 或 ≤ -maxLoss → STOPPED
 * 
* *

架构

* REST 下单委派给 {@link GateTradeExecutor}(独立线程池,避免阻塞 WS 回调线程)。 * * @author Administrator */ @Slf4j public class GateGridTradeService { public enum StrategyState { WAITING_KLINE, OPENING, ACTIVE, REOPENING_LONG, REOPENING_SHORT, STOPPED } private static final String AUTO_SIZE_LONG = "close_long"; private static final String AUTO_SIZE_SHORT = "close_short"; private static final String ORDER_TYPE_CLOSE_LONG = "close-long-position"; private static final String ORDER_TYPE_CLOSE_SHORT = "close-short-position"; private final GateConfig config; private final GateTradeExecutor executor; private final FuturesApi futuresApi; private static final String SETTLE = "usdt"; private volatile StrategyState state = StrategyState.WAITING_KLINE; /** 多头是否活跃(有仓位) */ private volatile boolean longActive = false; /** 空头是否活跃(有仓位) */ private volatile boolean shortActive = false; private BigDecimal longEntryPrice; private BigDecimal shortEntryPrice; private volatile BigDecimal lastKlinePrice; private volatile BigDecimal cumulativePnl = BigDecimal.ZERO; private Long userId; /** 多头补仓连续失败次数 */ private int longReopenFails = 0; /** 空头补仓连续失败次数 */ private int shortReopenFails = 0; public GateGridTradeService(GateConfig config) { this.config = config; ApiClient apiClient = new ApiClient(); apiClient.setBasePath(config.getRestBasePath()); apiClient.setApiKeySecret(config.getApiKey(), config.getApiSecret()); this.futuresApi = new FuturesApi(apiClient); this.executor = new GateTradeExecutor(apiClient, config.getContract()); } public void init() { try { ApiClient detailClient = new ApiClient(); detailClient.setBasePath(config.getRestBasePath()); detailClient.setApiKeySecret(config.getApiKey(), config.getApiSecret()); AccountDetail detail = new AccountApi(detailClient).getAccountDetail(); this.userId = detail.getUserId(); log.info("[Gate] 用户ID: {}", userId); FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE); if (!config.getPositionMode().equals(account.getPositionMode())) { futuresApi.setPositionMode(SETTLE, config.getPositionMode()); } log.info("[Gate] 持仓模式: {} 余额: {}", config.getPositionMode(), account.getAvailable()); futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract()); log.info("[Gate] 旧条件单已清除"); closeExistingPositions(); futuresApi.updateContractPositionLeverageCall( SETTLE, config.getContract(), config.getLeverage(), config.getMarginMode(), config.getPositionMode(), null); log.info("[Gate] 杠杆: {}x {}", config.getLeverage(), config.getMarginMode()); } catch (GateApiException e) { log.error("[Gate] 初始化失败, label:{}, msg:{}", e.getErrorLabel(), e.getMessage()); } catch (ApiException e) { log.error("[Gate] 初始化失败, code:{}", e.getCode()); } } /** * 平掉当前合约所有已有仓位。 * 策略启动前的准备工作,确保从零持仓状态开始运行。 */ private void closeExistingPositions() { try { java.util.List positions = futuresApi.listPositions(SETTLE).execute(); if (positions == null || positions.isEmpty()) { log.info("[Gate] 无已有仓位,无需平仓"); return; } for (Position pos : positions) { if (!config.getContract().equals(pos.getContract())) { continue; } String sizeStr = pos.getSize(); long size = Long.parseLong(sizeStr); if (size == 0) { continue; } String closeSize = size > 0 ? String.valueOf(-size) : String.valueOf(Math.abs(size)); boolean isLong = size > 0; Position.ModeEnum mode = pos.getMode(); FuturesOrder closeOrder = new FuturesOrder(); closeOrder.setContract(config.getContract()); closeOrder.setPrice("0"); closeOrder.setTif(FuturesOrder.TifEnum.IOC); closeOrder.setReduceOnly(true); if (mode != null && mode.getValue() != null && mode.getValue().contains("dual")) { closeOrder.setSize("0"); closeOrder.setClose(false); closeOrder.setAutoSize(isLong ? FuturesOrder.AutoSizeEnum.LONG : FuturesOrder.AutoSizeEnum.SHORT); } else { closeOrder.setSize(closeSize); } closeOrder.setText("t-grid-init-close"); futuresApi.createFuturesOrder(SETTLE, closeOrder, null); log.info("[Gate] 已平掉已有仓位, 方向:{}, sizes:{}, mode:{}", isLong ? "多头" : "空头", sizeStr, mode); } } catch (GateApiException e) { log.warn("[Gate] 平已有仓位失败, label:{}, msg:{}, 可能无仓位", e.getErrorLabel(), e.getMessage()); } catch (Exception e) { log.warn("[Gate] 平已有仓位异常, 可能无仓位", e); } } public void startGrid() { if (state != StrategyState.WAITING_KLINE && state != StrategyState.STOPPED) { log.warn("[Gate] 策略已在运行中, state:{}", state); return; } state = StrategyState.WAITING_KLINE; cumulativePnl = BigDecimal.ZERO; longActive = false; shortActive = false; longReopenFails = 0; shortReopenFails = 0; log.info("[Gate] 网格策略已启动"); } public void stopGrid() { state = StrategyState.STOPPED; executor.cancelAllPriceTriggeredOrders(); executor.shutdown(); log.info("[Gate] 策略已停止, 累计盈亏: {}", cumulativePnl); } /** * K 线回调。首次价格就绪 → 异步双开。 */ public void onKline(BigDecimal closePrice) { lastKlinePrice = closePrice; if (state != StrategyState.WAITING_KLINE) { return; } state = StrategyState.OPENING; log.info("[Gate] 首根K线到达,开始双开..."); executor.openLong(config.getQuantity(), () -> { synchronized (this) { longEntryPrice = lastKlinePrice; longActive = true; } executor.placeTakeProfit(longTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1, ORDER_TYPE_CLOSE_LONG, AUTO_SIZE_LONG); }, null); executor.openShort(negate(config.getQuantity()), () -> { synchronized (this) { shortEntryPrice = lastKlinePrice; shortActive = true; } executor.placeTakeProfit(shortTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2, ORDER_TYPE_CLOSE_SHORT, AUTO_SIZE_SHORT); if (longActive && shortActive && state != StrategyState.STOPPED) { state = StrategyState.ACTIVE; log.info("[Gate] 已激活, 多头入场:{}, 空头入场:{}, 多头止盈:{}, 空头止盈:{}", longEntryPrice, shortEntryPrice, longTpPrice(), shortTpPrice()); } }, null); } /** * 仓位推送回调。检测 size=0 触发补仓。 */ public void onPositionUpdate(String contract, Position.ModeEnum mode, BigDecimal size, BigDecimal entryPrice) { if (state == StrategyState.STOPPED || state == StrategyState.WAITING_KLINE) { return; } boolean hasPosition = size.abs().compareTo(BigDecimal.ZERO) > 0; if (Position.ModeEnum.DUAL_LONG == mode) { if (longActive && !hasPosition) { log.info("[Gate] 多头已平仓"); longActive = false; tryReopenLong(); } else if (hasPosition) { longActive = true; longEntryPrice = entryPrice; } } else if (Position.ModeEnum.DUAL_SHORT == mode) { if (shortActive && !hasPosition) { log.info("[Gate] 空头已平仓"); shortActive = false; tryReopenShort(); } else if (hasPosition) { shortActive = true; shortEntryPrice = entryPrice; } } } /** * 平仓推送回调。累加 pnl 并检查停止条件。 */ public void onPositionClose(String contract, String side, BigDecimal pnl) { if (state == StrategyState.STOPPED) { return; } cumulativePnl = cumulativePnl.add(pnl); log.info("[Gate] 盈亏累加:{}, 方向:{}, 累计:{}", pnl, side, cumulativePnl); if (cumulativePnl.compareTo(config.getOverallTp()) >= 0) { log.info("[Gate] 已达止盈目标 {}→已停止", cumulativePnl); state = StrategyState.STOPPED; } else if (cumulativePnl.compareTo(config.getMaxLoss().negate()) <= 0) { log.info("[Gate] 已达亏损上限 {}→已停止", cumulativePnl); state = StrategyState.STOPPED; } } // ---- 补仓(含失败重试) ---- private void tryReopenLong() { if (state == StrategyState.STOPPED) { return; } if (longActive) { return; } longReopenFails++; if (longReopenFails > config.getReopenMaxRetries()) { log.warn("[Gate] 多头补仓连续失败{}次,停止策略", longReopenFails); state = StrategyState.STOPPED; return; } state = StrategyState.REOPENING_LONG; executor.openLong(config.getQuantity(), () -> { synchronized (this) { longEntryPrice = lastKlinePrice; longActive = true; } executor.placeTakeProfit(longTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_1, ORDER_TYPE_CLOSE_LONG, AUTO_SIZE_LONG); longReopenFails = 0; if (state != StrategyState.STOPPED) { state = StrategyState.ACTIVE; } log.info("[Gate] 多头已补开, 价格:{}", longEntryPrice); }, this::tryReopenLong); } private void tryReopenShort() { if (state == StrategyState.STOPPED) { return; } if (shortActive) { return; } shortReopenFails++; if (shortReopenFails > config.getReopenMaxRetries()) { log.warn("[Gate] 空头补仓连续失败{}次,停止策略", shortReopenFails); state = StrategyState.STOPPED; return; } state = StrategyState.REOPENING_SHORT; executor.openShort(negate(config.getQuantity()), () -> { synchronized (this) { shortEntryPrice = lastKlinePrice; shortActive = true; } executor.placeTakeProfit(shortTpPrice(), FuturesPriceTrigger.RuleEnum.NUMBER_2, ORDER_TYPE_CLOSE_SHORT, AUTO_SIZE_SHORT); shortReopenFails = 0; if (state != StrategyState.STOPPED) { state = StrategyState.ACTIVE; } log.info("[Gate] 空头已补开, 价格:{}", shortEntryPrice); }, this::tryReopenShort); } // ---- 止盈价格计算 ---- private BigDecimal longTpPrice() { return lastKlinePrice.multiply(BigDecimal.ONE.add(config.getGridRate())) .setScale(1, RoundingMode.HALF_UP); } private BigDecimal shortTpPrice() { return lastKlinePrice.multiply(BigDecimal.ONE.subtract(config.getGridRate())) .setScale(1, RoundingMode.HALF_UP); } /** 对数量取反(开多用正数,开空用负数) */ private String negate(String qty) { return qty.startsWith("-") ? qty.substring(1) : "-" + qty; } public BigDecimal getLastKlinePrice() { return lastKlinePrice; } public boolean isStrategyActive() { return state != StrategyState.STOPPED && state != StrategyState.WAITING_KLINE; } public BigDecimal getCumulativePnl() { return cumulativePnl; } public Long getUserId() { return userId; } public StrategyState getState() { return state; } }