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.FuturesApi; import io.gate.gateapi.models.FuturesAccount; import io.gate.gateapi.models.FuturesInitialOrder; import io.gate.gateapi.models.FuturesOrder; import io.gate.gateapi.models.FuturesPriceTrigger; import io.gate.gateapi.models.FuturesPriceTriggeredOrder; import io.gate.gateapi.models.TriggerOrderResponse; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.math.RoundingMode; @Slf4j public class GateGridTradeService { private final ApiClient apiClient; private final FuturesApi futuresApi; private static final String SETTLE = "usdt"; private final String contract; private final String leverage; private final String marginMode; private final BigDecimal gridRate; private final BigDecimal overallTp; private final BigDecimal maxLoss; private final String quantity; private final String positionMode; private volatile boolean strategyActive = false; private volatile boolean dualOpened = false; private volatile boolean longActive = false; private volatile boolean shortActive = false; private BigDecimal longEntryPrice; private BigDecimal shortEntryPrice; private volatile BigDecimal lastKlinePrice; private BigDecimal totalHistoryPnl = BigDecimal.ZERO; public GateGridTradeService(String apiKey, String apiSecret, String contract, String leverage, String marginMode, String positionMode, BigDecimal gridRate, BigDecimal overallTp, int maxCycles, BigDecimal maxLoss, String quantity) { this.contract = contract; this.leverage = leverage; this.marginMode = marginMode; this.gridRate = gridRate; this.overallTp = overallTp; this.maxLoss = maxLoss; this.quantity = quantity; this.positionMode = positionMode; this.apiClient = new ApiClient(); this.apiClient.setBasePath("https://api-testnet.gateapi.io/api/v4"); this.apiClient.setApiKeySecret(apiKey, apiSecret); this.futuresApi = new FuturesApi(apiClient); } public void init() { try { futuresApi.updateContractPositionLeverageCall( 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) { log.error("[GateGrid] 初始化API调用失败, code: {}", e.getCode()); } } public void startGrid() { if (strategyActive) { log.warn("[GateGrid] 策略已在运行中"); return; } strategyActive = true; totalHistoryPnl = BigDecimal.ZERO; log.info("[GateGrid] 网格策略启动,等待K线价格..."); } public void stopGrid() { strategyActive = false; log.info("[GateGrid] 网格策略已停止, history_pnl: {}", totalHistoryPnl); } /** * K线回调:存储最新价格,首次价格就绪时双开 */ public void onKline(BigDecimal closePrice) { lastKlinePrice = closePrice; if (!strategyActive) { return; } if (!dualOpened) { dualOpened = true; dualOpenPositions(); } } /** * 仓位推送回调:检测止盈平仓 → 补仓,累加 history_pnl → 判断停止 */ public void onPositionUpdate(String contract, String mode, BigDecimal size, BigDecimal entryPrice, BigDecimal historyPnl, BigDecimal realisedPnl) { if (!strategyActive) { return; } boolean hasPosition = size.compareTo(BigDecimal.ZERO) > 0; if ("dual_long".equals(mode)) { if (longActive && !hasPosition) { log.info("[GateGrid] 多头被平仓(止盈触发), 重新开多"); longActive = false; reopenLongPosition(); } else if (hasPosition) { longActive = true; longEntryPrice = entryPrice; } } else if ("dual_short".equals(mode)) { if (shortActive && !hasPosition) { log.info("[GateGrid] 空头被平仓(止盈触发), 重新开空"); shortActive = false; reopenShortPosition(); } else if (hasPosition) { shortActive = true; shortEntryPrice = entryPrice; } } totalHistoryPnl = historyPnl; checkStopConditions(); } private void checkStopConditions() { if (totalHistoryPnl.compareTo(overallTp) >= 0) { log.info("[GateGrid] 累计止盈 {} 达到 {} USDT,停止策略", totalHistoryPnl, overallTp); strategyActive = false; return; } if (totalHistoryPnl.compareTo(maxLoss.negate()) <= 0) { log.info("[GateGrid] 累计亏损 {} 达到上限 {} USDT,停止策略", totalHistoryPnl, maxLoss); strategyActive = false; } } private void dualOpenPositions() { if (lastKlinePrice == null) { log.warn("[GateGrid] K线价格未就绪,跳过双开"); dualOpened = false; return; } try { FuturesOrder longOrder = new FuturesOrder(); longOrder.setContract(contract); longOrder.setSize(quantity); longOrder.setPrice("0"); longOrder.setTif(FuturesOrder.TifEnum.IOC); longOrder.setText("t-grid-long-init"); FuturesOrder longResult = futuresApi.createFuturesOrder(SETTLE, longOrder, null); longEntryPrice = safeDecimal(longResult.getFillPrice()); longActive = true; log.info("[GateGrid] 开多成功, price: {}, id: {}", longEntryPrice, longResult.getId()); placeLongTp(longEntryPrice); FuturesOrder shortOrder = new FuturesOrder(); shortOrder.setContract(contract); shortOrder.setSize(negateQuantity(quantity)); shortOrder.setPrice("0"); shortOrder.setTif(FuturesOrder.TifEnum.IOC); shortOrder.setText("t-grid-short-init"); FuturesOrder shortResult = futuresApi.createFuturesOrder(SETTLE, shortOrder, null); shortEntryPrice = safeDecimal(shortResult.getFillPrice()); shortActive = true; log.info("[GateGrid] 开空成功, price: {}, id: {}", shortEntryPrice, shortResult.getId()); placeShortTp(shortEntryPrice); printGridInfo(); } catch (GateApiException e) { log.error("[GateGrid] 双开失败, label: {}, msg: {}", e.getErrorLabel(), e.getMessage()); strategyActive = false; } catch (Exception e) { log.error("[GateGrid] 双开异常", e); strategyActive = false; } } private void reopenLongPosition() { if (lastKlinePrice == null || !strategyActive) { return; } if (longActive) { log.warn("[GateGrid] 多头已存在,跳过补开"); return; } try { FuturesOrder longOrder = new FuturesOrder(); longOrder.setContract(contract); longOrder.setSize(quantity); longOrder.setPrice("0"); longOrder.setTif(FuturesOrder.TifEnum.IOC); longOrder.setText("t-grid-long-reopen"); FuturesOrder longResult = futuresApi.createFuturesOrder(SETTLE, longOrder, null); longEntryPrice = safeDecimal(longResult.getFillPrice()); longActive = true; log.info("[GateGrid] 补开多成功, price: {}, id: {}", longEntryPrice, longResult.getId()); placeLongTp(longEntryPrice); } catch (Exception e) { log.error("[GateGrid] 补开多失败", e); } } private void reopenShortPosition() { if (lastKlinePrice == null || !strategyActive) { return; } if (shortActive) { log.warn("[GateGrid] 空头已存在,跳过补开"); return; } try { FuturesOrder shortOrder = new FuturesOrder(); shortOrder.setContract(contract); shortOrder.setSize(negateQuantity(quantity)); shortOrder.setPrice("0"); shortOrder.setTif(FuturesOrder.TifEnum.IOC); shortOrder.setText("t-grid-short-reopen"); FuturesOrder shortResult = futuresApi.createFuturesOrder(SETTLE, shortOrder, null); shortEntryPrice = safeDecimal(shortResult.getFillPrice()); shortActive = true; log.info("[GateGrid] 补开空成功, price: {}, id: {}", shortEntryPrice, shortResult.getId()); placeShortTp(shortEntryPrice); } catch (Exception e) { log.error("[GateGrid] 补开空失败", e); } } private void placeLongTp(BigDecimal entryPrice) { BigDecimal tpPrice = entryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP); placePriceTriggeredOrder(tpPrice, FuturesPriceTrigger.RuleEnum.NUMBER_1, "close-long-position", "close_long"); log.info("[GateGrid] 多头止盈已设置, TP:{}", tpPrice); } private void placeShortTp(BigDecimal entryPrice) { BigDecimal tpPrice = entryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP); placePriceTriggeredOrder(tpPrice, FuturesPriceTrigger.RuleEnum.NUMBER_2, "close-short-position", "close_short"); log.info("[GateGrid] 空头止盈已设置, TP:{}", tpPrice); } private void placePriceTriggeredOrder(BigDecimal triggerPrice, FuturesPriceTrigger.RuleEnum rule, String orderType, String 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 (Exception e) { log.error("[GateGrid] 止盈条件单创建失败, triggerPrice:{}, orderType:{}, autoSize:{}", triggerPrice, orderType, autoSize, e); } } private void printGridInfo() { BigDecimal longTp = BigDecimal.ZERO; BigDecimal shortTp = BigDecimal.ZERO; if (longEntryPrice != null) { longTp = longEntryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP); } if (shortEntryPrice != null) { shortTp = shortEntryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP); } log.info("========== Gate 网格开仓 =========="); log.info("合约: {} 杠杆: {}x {}", contract, leverage, marginMode); log.info("多头入场: {} TP: {}", longEntryPrice, longTp); log.info("空头入场: {} TP: {}", shortEntryPrice, shortTp); log.info("数量: {} 网格间距: {}%", quantity, gridRate.multiply(new BigDecimal("100"))); log.info("整体止盈: {} USDT 最大亏损: {} USDT", overallTp, maxLoss); log.info("====================================="); } private String negateQuantity(String qty) { if (qty.startsWith("-")) { return qty.substring(1); } return "-" + qty; } private BigDecimal safeDecimal(String val) { if (val == null || val.isEmpty()) { return BigDecimal.ZERO; } return new BigDecimal(val); } public BigDecimal getLastKlinePrice() { return lastKlinePrice; } public boolean isStrategyActive() { return strategyActive; } public BigDecimal getTotalHistoryPnl() { return totalHistoryPnl; } }