| | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | |
| | | /** |
| | | * Gate 网格交易服务类。 |
| | | * |
| | | * <h3>策略概述</h3> |
| | | * 多空双开 → 各放止盈条件单 → 仓位推送驱动补仓 → history_pnl 判断停止 |
| | | * |
| | | * <h3>触发逻辑</h3> |
| | | * <pre> |
| | | * K线首次价格就绪 → dualOpenPositions() // 多空双开 + 止盈条件单 |
| | | * 仓位推送 size=0 → reopenXxxPosition() // 该方向被止盈平掉 → 补开 |
| | | * 仓位推送 history_pnl ≥ overallTp → 停止 |
| | | * 仓位推送 history_pnl ≤ -maxLoss → 停止 |
| | | * </pre> |
| | | * |
| | | * <h3>止盈计算</h3> |
| | | * 多头止盈价 = entryPrice × (1 + gridRate)<br> |
| | | * 空头止盈价 = entryPrice × (1 - gridRate) |
| | | * |
| | | * <h3>依赖</h3> |
| | | * 使用 {@code io.gate:gate-api (7.2.71)} SDK 通过 REST API 下单, |
| | | * 市场数据由 {@link GateKlineWebSocketClient} 通过 WebSocket 提供。 |
| | | * |
| | | * @author Administrator |
| | | */ |
| | | @Slf4j |
| | | public class GateGridTradeService { |
| | | |
| | |
| | | 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; |
| | | |
| | | /** WebSocket 推送的最新 K 线收盘价 */ |
| | | private volatile BigDecimal lastKlinePrice; |
| | | |
| | | /** 服务器返回的累计已实现盈亏 */ |
| | | private BigDecimal totalHistoryPnl = BigDecimal.ZERO; |
| | | |
| | | /** |
| | | * 构造函数,初始化 Gate 期货 API 客户端。 |
| | | * |
| | | * @param contract 合约名称(如 XAU_USDT) |
| | | * @param leverage 杠杆倍数 |
| | | * @param marginMode 保证金模式(cross/isolated) |
| | | * @param positionMode 持仓模式(single/dual/dual_plus) |
| | | * @param gridRate 网格间距比例(如 0.0035) |
| | | * @param overallTp 整体止盈阈值(USDT) |
| | | * @param maxLoss 最大亏损阈值(USDT) |
| | | * @param quantity 下单数量(合约张数) |
| | | */ |
| | | public GateGridTradeService(String apiKey, String apiSecret, |
| | | String contract, String leverage, |
| | | String marginMode, String positionMode, |
| | |
| | | this.futuresApi = new FuturesApi(apiClient); |
| | | } |
| | | |
| | | /** |
| | | * 初始化账户。设置杠杆、查询余额、切换持仓模式。 |
| | | */ |
| | | public void init() { |
| | | try { |
| | | futuresApi.updateContractPositionLeverageCall( |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 启动网格策略。策略激活后等待 K 线价格就绪,然后自动首次双开。 |
| | | */ |
| | | public void startGrid() { |
| | | if (strategyActive) { |
| | | log.warn("[GateGrid] 策略已在运行中"); |
| | |
| | | log.info("[GateGrid] 网格策略启动,等待K线价格..."); |
| | | } |
| | | |
| | | /** |
| | | * 停止网格策略。 |
| | | */ |
| | | public void stopGrid() { |
| | | strategyActive = false; |
| | | log.info("[GateGrid] 网格策略已停止, history_pnl: {}", totalHistoryPnl); |
| | | } |
| | | |
| | | /** |
| | | * K线回调:存储最新价格,首次价格就绪时双开 |
| | | * K 线回调入口。由 调用。 |
| | | * 首次收到价格时触发多空双开,后续仅缓存最新价格供补仓使用。 |
| | | * |
| | | * @param closePrice K 线收盘价 |
| | | */ |
| | | public void onKline(BigDecimal closePrice) { |
| | | lastKlinePrice = closePrice; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 仓位推送回调:检测止盈平仓 → 补仓,累加 history_pnl → 判断停止 |
| | | * 仓位推送回调入口。由 {@link GateKlineWebSocketClient} 调用。 |
| | | * 根据仓位模式(dual_long/dual_short)和 size 判断: |
| | | * <ul> |
| | | * <li>size=0 且之前活跃 → 该方向被平仓 → 补开</li> |
| | | * <li>size>0 → 确认仓位活跃,更新入场价</li> |
| | | * </ul> |
| | | * 每次推送更新 history_pnl 并检查停止条件。 |
| | | * |
| | | * @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, |
| | |
| | | checkStopConditions(); |
| | | } |
| | | |
| | | /** |
| | | * 检查策略停止条件。满足任一即置 strategyActive=false: |
| | | * <ul> |
| | | * <li>累计盈利 ≥ overallTp</li> |
| | | * <li>累计亏损 ≤ -maxLoss</li> |
| | | * </ul> |
| | | */ |
| | | private void checkStopConditions() { |
| | | if (totalHistoryPnl.compareTo(overallTp) >= 0) { |
| | | log.info("[GateGrid] 累计止盈 {} 达到 {} USDT,停止策略", totalHistoryPnl, overallTp); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 首次多空双开。使用当前 K 线价格以市价单同时开多和开空, |
| | | * 开仓成功后立即为每个方向创建止盈条件单。 |
| | | */ |
| | | private void dualOpenPositions() { |
| | | if (lastKlinePrice == null) { |
| | | log.warn("[GateGrid] K线价格未就绪,跳过双开"); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 补开多头仓位。多头被止盈平掉后调用,市价重新开多并创建止盈单。 |
| | | */ |
| | | private void reopenLongPosition() { |
| | | if (lastKlinePrice == null || !strategyActive) { |
| | | return; |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 补开空头仓位。空头被止盈平掉后调用,市价重新开空并创建止盈单。 |
| | | */ |
| | | private void reopenShortPosition() { |
| | | if (lastKlinePrice == null || !strategyActive) { |
| | | return; |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 创建多头止盈条件单。 |
| | | * 触发价 = entryPrice × (1 + gridRate),价格 ≥ 触发价时平多。 |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | | * 创建空头止盈条件单。 |
| | | * 触发价 = entryPrice × (1 - gridRate),价格 ≤ 触发价时平空。 |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | | * 通过 Gate REST API 创建止盈条件单。 |
| | | * |
| | | * @param triggerPrice 触发价格 |
| | | * @param rule 触发规则(1: ≥, 2: ≤) |
| | | * @param orderType 止盈止损类型(close-long-position / close-short-position) |
| | | * @param autoSize 双仓平仓方向(close_long / close_short) |
| | | */ |
| | | private void placePriceTriggeredOrder(BigDecimal triggerPrice, |
| | | FuturesPriceTrigger.RuleEnum rule, |
| | | String orderType, |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 打印当前网格配置和入场信息。 |
| | | */ |
| | | private void printGridInfo() { |
| | | BigDecimal longTp = BigDecimal.ZERO; |
| | | BigDecimal shortTp = BigDecimal.ZERO; |
| | |
| | | log.info("====================================="); |
| | | } |
| | | |
| | | /** 对数量取反(开多用正数,开空用负数) */ |
| | | private String negateQuantity(String qty) { |
| | | if (qty.startsWith("-")) { |
| | | return qty.substring(1); |
| | |
| | | return "-" + qty; |
| | | } |
| | | |
| | | /** 安全转换字符串为 BigDecimal,null 返回 0 */ |
| | | private BigDecimal safeDecimal(String val) { |
| | | if (val == null || val.isEmpty()) { |
| | | return BigDecimal.ZERO; |