Administrator
5 days ago 3015f832329028cbabef2c45fc0d4586bf60510c
fix(okxWs): 修复OKX生产环境配置的调试模式设置

- 将OKX_PRD_xiao配置项的调试模式从true更改为false
- 确保生产环境不会启用调试模式导致的安全风险
2 files modified
1 files added
502 ■■■■ changed files
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java 343 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java 88 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java 71 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
New file
@@ -0,0 +1,343 @@
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.FuturesOrder;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
/**
 * Gate 网格交易服务类,使用 gate-api SDK 进行合约下单。
 * 策略:多空双开 → 设置止盈止损点位 → 网格循环交易
 *
 * 测试参数:
 *   品种: XAU_USDT(黄金)
 *   杠杆: 100x(全仓)
 *   数量: 0.01 XAU
 *   网格: 0.0035(千分之三点五)
 *   整体止盈: 0.5 USDT
 *   循环次数: 3
 *   报警: 本金亏损 15%(初始本金 50 USDT)
 *
 * @author Administrator
 */
@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 int maxCycles;
    private final BigDecimal maxLoss;
    private final String quantity;
    private volatile boolean strategyActive = false;
    private int currentCycle = 0;
    private BigDecimal totalProfit = BigDecimal.ZERO;
    private BigDecimal longEntryPrice;
    private BigDecimal shortEntryPrice;
    private Long longOrderId;
    private Long shortOrderId;
    private volatile BigDecimal lastClosePrice;
    public GateGridTradeService(String apiKey, String apiSecret,
                                 String contract, String leverage, String marginMode,
                                 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.maxCycles = maxCycles;
        this.maxLoss = maxLoss;
        this.quantity = quantity;
        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.setPositionMode(SETTLE, "dual");
            log.info("[GateGrid] 已设置双向持仓模式");
            futuresApi.updateContractPositionLeverageCall(
                    SETTLE, contract, leverage, marginMode, "dual", null);
            log.info("[GateGrid] 已设置杠杆: {}x, 保证金模式: {}", leverage, marginMode);
            FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
            log.info("[GateGrid] 账户可用余额: {}, 总资产: {}",
                    account.getAvailable(), account.getTotal());
        } 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;
        currentCycle = 0;
        totalProfit = BigDecimal.ZERO;
        log.info("[GateGrid] 网格策略启动, cycle: {}", currentCycle + 1);
        dualOpenPositions();
    }
    /**
     * 停止网格策略
     */
    public void stopGrid() {
        strategyActive = false;
        closeAllPositions();
        log.info("[GateGrid] 网格策略已停止, 总盈亏: {}, 循环: {}", totalProfit, currentCycle);
    }
    /**
     * K线回调:收到新的收盘价
     */
    public void onKline(BigDecimal closePrice, boolean isKlineClosed) {
        lastClosePrice = closePrice;
        if (!strategyActive || !isKlineClosed) {
            return;
        }
        checkPositions(closePrice);
    }
    /**
     * 多空双开
     */
    private void dualOpenPositions() {
        try {
            FuturesOrder longOrder = new FuturesOrder();
            longOrder.setContract(contract);
            longOrder.setSize(quantity);
            longOrder.setPrice("0");
            longOrder.setTif(FuturesOrder.TifEnum.IOC);
            longOrder.setText("t-grid-long-" + (currentCycle + 1));
            FuturesOrder longResult = futuresApi.createFuturesOrder(SETTLE, longOrder, null);
            longOrderId = longResult.getId();
            longEntryPrice = safeDecimal(longResult.getFillPrice());
            log.info("[GateGrid] 开多成功, price: {}, id: {}", longEntryPrice, longOrderId);
            FuturesOrder shortOrder = new FuturesOrder();
            shortOrder.setContract(contract);
            shortOrder.setSize(negateQuantity(quantity));
            shortOrder.setPrice("0");
            shortOrder.setTif(FuturesOrder.TifEnum.IOC);
            shortOrder.setText("t-grid-short-" + (currentCycle + 1));
            FuturesOrder shortResult = futuresApi.createFuturesOrder(SETTLE, shortOrder, null);
            shortOrderId = shortResult.getId();
            shortEntryPrice = safeDecimal(shortResult.getFillPrice());
            log.info("[GateGrid] 开空成功, price: {}, id: {}", shortEntryPrice, shortOrderId);
            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 checkPositions(BigDecimal currentPrice) {
        if (longEntryPrice == null || shortEntryPrice == null) {
            return;
        }
        BigDecimal longTp = longEntryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
        BigDecimal longSl = longEntryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
        BigDecimal shortTp = shortEntryPrice.multiply(BigDecimal.ONE.subtract(gridRate)).setScale(1, RoundingMode.HALF_UP);
        BigDecimal shortSl = shortEntryPrice.multiply(BigDecimal.ONE.add(gridRate)).setScale(1, RoundingMode.HALF_UP);
        System.out.println("========== Gate 网格状态 ==========");
        System.out.println("当前价格: " + currentPrice);
        System.out.println("多头入场: " + longEntryPrice + " TP: " + longTp + " SL: " + longSl);
        System.out.println("空头入场: " + shortEntryPrice + " TP: " + shortTp + " SL: " + shortSl);
        System.out.println("累计盈亏: " + totalProfit + " 循环: " + currentCycle + "/" + maxCycles);
        System.out.println("===================================");
        // 多头止盈
        if (currentPrice.compareTo(longTp) >= 0) {
            log.info("[GateGrid] 多头止盈触发! entry:{}, current:{}", longEntryPrice, currentPrice);
            BigDecimal cnt = new BigDecimal(quantity);
            BigDecimal profit = currentPrice.subtract(longEntryPrice).multiply(cnt);
            totalProfit = totalProfit.add(profit);
            log.info("[GateGrid] 多头止盈 profit:{}, totalProfit:{}", profit, totalProfit);
            closeLongPosition();
            closeShortPosition();
            currentCycle++;
            checkStopConditions();
            return;
        }
        // 多头止损
        if (currentPrice.compareTo(longSl) <= 0) {
            log.info("[GateGrid] 多头止损触发! entry:{}, current:{}", longEntryPrice, currentPrice);
            BigDecimal cnt = new BigDecimal(quantity);
            BigDecimal loss = longEntryPrice.subtract(currentPrice).multiply(cnt);
            totalProfit = totalProfit.subtract(loss);
            log.info("[GateGrid] 多头止损 loss:{}, totalProfit:{}", loss, totalProfit);
            closeLongPosition();
            currentCycle++;
            checkStopConditions();
            return;
        }
        // 空头止盈
        if (currentPrice.compareTo(shortTp) <= 0) {
            log.info("[GateGrid] 空头止盈触发! entry:{}, current:{}", shortEntryPrice, currentPrice);
            BigDecimal cnt = new BigDecimal(quantity);
            BigDecimal profit = shortEntryPrice.subtract(currentPrice).multiply(cnt);
            totalProfit = totalProfit.add(profit);
            log.info("[GateGrid] 空头止盈 profit:{}, totalProfit:{}", profit, totalProfit);
            closeShortPosition();
            closeLongPosition();
            currentCycle++;
            checkStopConditions();
            return;
        }
        // 空头止损
        if (currentPrice.compareTo(shortSl) >= 0) {
            log.info("[GateGrid] 空头止损触发! entry:{}, current:{}", shortEntryPrice, currentPrice);
            BigDecimal cnt = new BigDecimal(quantity);
            BigDecimal loss = currentPrice.subtract(shortEntryPrice).multiply(cnt);
            totalProfit = totalProfit.subtract(loss);
            log.info("[GateGrid] 空头止损 loss:{}, totalProfit:{}", loss, totalProfit);
            closeShortPosition();
            currentCycle++;
            checkStopConditions();
        }
    }
    private void checkStopConditions() {
        if (totalProfit.compareTo(overallTp) >= 0) {
            log.info("[GateGrid] 达到整体止盈 {} USDT,停止策略", overallTp);
            strategyActive = false;
            return;
        }
        if (BigDecimal.ZERO.subtract(totalProfit).compareTo(maxLoss) >= 0) {
            log.info("[GateGrid] 亏损 {} 达到上限 {} USDT,停止策略", totalProfit.negate(), maxLoss);
            strategyActive = false;
            return;
        }
        if (currentCycle >= maxCycles) {
            log.info("[GateGrid] 达到最大循环次数 {},停止策略", maxCycles);
            strategyActive = false;
            return;
        }
        log.info("[GateGrid] 进入下一轮循环: {}", currentCycle + 1);
        dualOpenPositions();
    }
    private void closeLongPosition() {
        if (longEntryPrice == null) return;
        try {
            FuturesOrder closeOrder = new FuturesOrder();
            closeOrder.setContract(contract);
            closeOrder.setSize(negateQuantity(quantity));
            closeOrder.setPrice("0");
            closeOrder.setTif(FuturesOrder.TifEnum.IOC);
            closeOrder.setReduceOnly(true);
            closeOrder.setText("t-grid-close-long");
            FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, closeOrder, null);
            log.info("[GateGrid] 平多成功, id: {}, fillPrice: {}", result.getId(), result.getFillPrice());
        } catch (Exception e) {
            log.error("[GateGrid] 平多失败", e);
        }
        longEntryPrice = null;
        longOrderId = null;
    }
    private void closeShortPosition() {
        if (shortEntryPrice == null) return;
        try {
            FuturesOrder closeOrder = new FuturesOrder();
            closeOrder.setContract(contract);
            closeOrder.setSize(quantity);
            closeOrder.setPrice("0");
            closeOrder.setTif(FuturesOrder.TifEnum.IOC);
            closeOrder.setReduceOnly(true);
            closeOrder.setText("t-grid-close-short");
            FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, closeOrder, null);
            log.info("[GateGrid] 平空成功, id: {}, fillPrice: {}", result.getId(), result.getFillPrice());
        } catch (Exception e) {
            log.error("[GateGrid] 平空失败", e);
        }
        shortEntryPrice = null;
        shortOrderId = null;
    }
    private void closeAllPositions() {
        closeLongPosition();
        closeShortPosition();
    }
    private void printGridInfo() {
        System.out.println("========== Gate 网格开仓 ==========");
        System.out.println("合约: " + contract + " 杠杆: " + leverage + "x " + marginMode);
        System.out.println("多头入场: " + longEntryPrice);
        System.out.println("空头入场: " + shortEntryPrice);
        System.out.println("数量: " + quantity + " 网格间距: " + gridRate.multiply(new BigDecimal("100")) + "%");
        System.out.println("整体止盈: " + overallTp + " USDT 最大循环: " + maxCycles);
        System.out.println("最大亏损: " + maxLoss + " USDT");
        System.out.println("=====================================");
    }
    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 getLastClosePrice() {
        return lastClosePrice;
    }
    public boolean isStrategyActive() {
        return strategyActive;
    }
    public BigDecimal getTotalProfit() {
        return totalProfit;
    }
    public int getCurrentCycle() {
        return currentCycle;
    }
}
src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java
@@ -58,7 +58,9 @@
    private static final String FUTURES_PING = "futures.ping";
    private static final String FUTURES_PONG = "futures.pong";
    private static final String GATE_INTERVAL = "1m";
    private static final String GATE_CONTRACT = "BTC_USDT";
    private static final String GATE_CONTRACT = "XAU_USDT";
    private GateGridTradeService gridTradeService;
    // 心跳超时时间(秒),小于30秒
    private static final int HEARTBEAT_TIMEOUT = 10;
@@ -77,6 +79,10 @@
        this.caoZuoService = caoZuoService;
        this.clientManager = clientManager;
        this.wangGeListService = wangGeListService;
    }
    public void setGridTradeService(GateGridTradeService gridTradeService) {
        this.gridTradeService = gridTradeService;
    }
    /**
@@ -366,85 +372,13 @@
            System.out.println("成交额(a): " + baseVol);
            System.out.println("K线完结(w): " + windowClosed);
            System.out.println("==================================");
            if (gridTradeService != null) {
                gridTradeService.onKline(closePx, windowClosed);
            }
        } catch (Exception e) {
            log.error("处理 K线频道推送数据失败", e);
        }
    }
    private void doOpen(WebSocketClient webSocketClient, String accountName, MacdEmaStrategy.TradingOrder tradingOrderOpenOpen, BigDecimal closePx) {
        // 根据信号执行交易操作
        TradeRequestParam tradeRequestParam = new TradeRequestParam();
        String posSide = tradingOrderOpenOpen.getPosSide();
        tradeRequestParam.setPosSide(posSide);
        String currentPrice = String.valueOf(closePx);
        tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, currentPrice, posSide);
        String side = tradingOrderOpenOpen.getSide();
        tradeRequestParam.setSide(side);
        String clOrdId = WsParamBuild.getOrderNum(side);
        tradeRequestParam.setClOrdId(clOrdId);
        String sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
        tradeRequestParam.setSz(sz);
        TradeOrderWs.orderEvent(webSocketClient, tradeRequestParam);
    }
    private List<Kline> getKlineDataByInstIdAndBar(String instId, String bar) {
        List<Kline> klineList = new ArrayList<>();
        try {
            LinkedHashMap<String, Object> requestParam = new LinkedHashMap<>();
            requestParam.put("instId", instId);
            requestParam.put("bar", bar);
            requestParam.put("limit", "200");
            String result = ExchangeLoginService.getInstance(ExchangeInfoEnum.OKX_UAT.name()).lineHistory(requestParam);
            JSONObject json = JSON.parseObject(result);
            String data = json.getString("data");
            if (data != null) {
                List<String[]> klinesList = JSON.parseArray(data, String[].class);
                if (!CollUtil.isEmpty(klinesList)) {
                    for (String[] s : klinesList) {
                        // 确保数组有足够的元素
                        if (s != null && s.length >= 9) {
                            String s1 = s[8];
                            try {
                                if ("1".equals(s1)){
                                    Kline kline = new Kline();
                                    kline.setTs(s[0]);
                                    kline.setO(new BigDecimal(s[1]));
                                    kline.setH(new BigDecimal(s[2]));
                                    kline.setL(new BigDecimal(s[3]));
                                    kline.setC(new BigDecimal(s[4]));
                                    kline.setVol(new BigDecimal(s[5]));
                                    kline.setConfirm(s[8]);
                                    klineList.add(kline);
                                }
                            } catch (NumberFormatException e) {
                                log.error("K线数据转换为BigDecimal失败: {}", Arrays.toString(s), e);
                            }
                        } else {
                            log.warn("K线数据数组长度不足: {}", Arrays.toString(s));
                        }
                    }
                }
            } else {
                log.warn("K线数据为空");
            }
        } catch (JSONException e) {
            log.error("K线数据解析失败", e);
        } catch (Exception e) {
            log.error("获取K线数据异常", e);
        }
        return klineList;
    }
    /**
     * 构建 Redis Key
     */
    private String buildRedisKey(String instId) {
        return "PRICE_" + instId.replace("-", "");
    }
    /**
src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -1,24 +1,18 @@
package com.xcong.excoin.modules.gateApi;
import com.xcong.excoin.modules.okxNewPrice.OkxKlineWebSocketClient;
import com.xcong.excoin.modules.okxNewPrice.OkxQuantWebSocketClient;
import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.ExchangeInfoEnum;
import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListService;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.math.BigDecimal;
/**
 * 管理多个OKX WebSocket客户端实例,每个实例对应一个账号
 * 管理 Gate WebSocket 客户端和网格交易服务实例
 */
@Slf4j
@Component
@@ -31,53 +25,64 @@
    private WangGeListService wangGeListService;
    private GateKlineWebSocketClient klinePriceClient;
    private GateGridTradeService gridTradeService;
    private static final String API_KEY = "d90ca272391992b8e74f8f92cedb21ec";
    private static final String API_SECRET = "1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274";
    /**
     * 初始化方法,在Spring Bean构造完成后执行
     * 创建并初始化所有账号的WebSocket客户端实例
     */
    @PostConstruct
    public void init() {
        log.info("开始初始化OkxWebSocketClientManager");
        // 初始化价格WebSocket客户端
        log.info("开始初始化GateWebSocketClientManager");
        try {
            gridTradeService = new GateGridTradeService(
                    API_KEY, API_SECRET,
                    "XAUT_USDT",
                    "100",
                    "cross",
                    new BigDecimal("0.0035"),
                    new BigDecimal("0.5"),
                    3,
                    new BigDecimal("7.5"),
                    "1"
            );
            gridTradeService.init();
            klinePriceClient = new GateKlineWebSocketClient(caoZuoService, this, wangGeListService);
            klinePriceClient.setGridTradeService(gridTradeService);
            klinePriceClient.init();
            log.info("已初始化OkxNewPriceWebSocketClient");
            log.info("已初始化GateKlineWebSocketClient");
            gridTradeService.startGrid();
        } catch (Exception e) {
            log.error("初始化OkxNewPriceWebSocketClient失败", e);
            log.error("初始化GateWebSocketClientManager失败", e);
        }
    }
    /**
     * 销毁方法,在Spring Bean销毁前执行
     * 关闭所有WebSocket客户端连接和相关资源
     */
    @PreDestroy
    public void destroy() {
        log.info("开始销毁OkxWebSocketClientManager");
        // 关闭价格WebSocket客户端
        log.info("开始销毁GateWebSocketClientManager");
        if (gridTradeService != null) {
            gridTradeService.stopGrid();
        }
        if (klinePriceClient != null) {
            try {
                klinePriceClient.destroy();
                log.info("已销毁OkxNewPriceWebSocketClient");
                log.info("已销毁GateKlineWebSocketClient");
            } catch (Exception e) {
                log.error("销毁OkxNewPriceWebSocketClient失败", e);
                log.error("销毁GateKlineWebSocketClient失败", e);
            }
        }
        log.info("OkxWebSocketClientManager销毁完成");
        log.info("GateWebSocketClientManager销毁完成");
    }
    /**
     * 获取OkxNewPriceWebSocketClient实例
     * @return 价格WebSocket客户端实例
     */
    public GateKlineWebSocketClient getKlineWebSocketClient() {
        return klinePriceClient;
    }
    public GateGridTradeService getGridTradeService() {
        return gridTradeService;
    }
}