src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
@@ -5,41 +5,42 @@ import java.util.List; /** * Gate 模块统一配置。 * Gate 交易模块全局配置,策略的唯一参数入口。 * * <p>通过 Builder 模式集中管理所有运行参数,避免参数散落在多个文件中。 * 提供 REST API 和 WebSocket 地址的自动环境切换(测试网/生产网)。 * <h3>定位</h3> * 通过 Builder 模式将所有运行参数集中管理,避免策略参数散落在多个类中。 * 运行时动态参数(step、gridElements、baseLongTraderParam、baseShortTraderParam) * 由 {@link GateGridTradeService} 在策略执行过程中写入。 * * <h3>关键参数</h3> * <h3>参数分类</h3> * <table> * <tr><th>类别</th><th>参数</th><th>用途</th></tr> * <tr><td>认证</td><td>apiKey, apiSecret</td><td>REST/WS 签名认证</td></tr> * <tr><td>交易标的</td><td>contract, leverage, quantity, contractMultiplier</td><td>合约、杠杆、张数、乘数</td></tr> * <tr><td>持仓</td><td>marginMode, positionMode</td><td>全仓/逐仓、单向/双向</td></tr> * <tr><td>网格策略</td><td>gridRate, gridQueueSize, priceScale</td><td>间距比例、队列容量、价格精度(交易所tick)</td></tr> * <tr><td>止盈止损</td><td>overallTp, maxLoss</td><td>整体止盈/亏损阈值(USDT),触发后策略停止</td></tr> * <tr><td>风控</td><td>marginRatioLimit, reopenMaxRetries</td><td>保证金占比上限、补仓重试次数</td></tr> * <tr><td>盈亏计算</td><td>contractMultiplier, unrealizedPnlPriceMode</td><td>合约乘数、未实现盈亏计价模式</td></tr> * <tr><td>运行时</td><td>step, gridElements, baseLongTraderParam, baseShortTraderParam</td><td>由策略动态填充</td></tr> * </table> * * <h3>priceScale 说明</h3> * 价格精度表示交易所允许的最小价格单位的小数位数: * <ul> * <li><b>网格参数</b>:gridRate(比例间距)、step(绝对步长 = shortBaseEntryPrice × gridRate,运行时计算)、 * gridQueueSize(队列容量)</li> * <li><b>止盈止损</b>:overallTp(整体止盈 USDT)、maxLoss(最大亏损 USDT)</li> * <li><b>风险控制</b>:marginRatioLimit(保证金占比上限,超限跳过开仓)、leverage(杠杆)、 * marginMode(全仓/逐仓)、positionMode(单向/双向)</li> * <li><b>盈亏计算</b>:contractMultiplier(合约乘数)、unrealizedPnlPriceMode(计价模式:最新价/标记价)</li> * <li>XAU_USDT: tick=0.1 → priceScale=1</li> * <li>ETH_USDT: tick=0.01 → priceScale=2</li> * </ul> * 所有价格计算必须对齐到 tick 整数倍,否则 Gate API 返回 * {@code invalid argument: trigger.price price is not an integer multiple of a price unit}。 * * <h3>使用示例</h3> * <pre> * GateConfig config = GateConfig.builder() * .apiKey("...") * .apiSecret("...") * .contract("ETH_USDT") * .leverage("100") * .gridRate(new BigDecimal("0.0035")) * .contractMultiplier("0.01") * .isProduction(false) * .build(); * </pre> * * <h3>默认值</h3> * <ul> * <li>合约: BTC_USDT, 杠杆: 10x, 全仓, 双向持仓</li> * <li>网格间距: 0.35%, 队列容量: 50, 保证金比例上限: 20%</li> * <li>止盈: 0.5 USDT, 亏损上限: 7.5 USDT</li> * <li>数量: 1 张, 合约乘数: 0.001, 环境: 测试网</li> * </ul> * <h3>gridElements 生命周期</h3> * <ol> * <li>项目启动时初始化为空 ArrayList</li> * <li>{@code tryGenerateQueues()} 中通过 {@code updateGridElements()} 填充,同时触发 * {@link GridElement#rebuildIndex(List)} 建立全局 O(1) 索引</li> * <li>每次挂单/止盈操作后通过 {@link GridElement#refreshIndices()} 更新索引</li> * </ol> * * @author Administrator */ src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
@@ -24,79 +24,63 @@ import com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionsChannelHandler; /** * Gate 网格交易服务 — 策略核心。 * 网格交易策略引擎 — 多空对冲网格。 * * <h3>策略概述</h3> * 多空双开基底 → 生成价格网格队列 → 条件单监控 → 触发成交后队列动态转移。 * 每根 K 线更新未实现盈亏(unrealizedPnl),平仓后累加已实现盈亏(cumulativePnl)。 * <h3>策略原理</h3> * 以空仓基底入场价(shortBaseEntryPrice)为价格基准,向上/向下各生成一个价格网格队列。 * 价格触发网格层级时挂条件单,成交后自动挂止盈单。每笔止盈盈利 = step - minTick。 * * <h3>核心机制</h3> * <ul> * <li><b>条件开仓单</b>:使用 Gate API {@code FuturesPriceTriggeredOrder},服务器监控价格, * 达到触发价后以市价 IOC 开仓。相比限价单,条件单仅在触发价到达时才执行,避免提前成交。</li> * <li><b>条件单 ID 映射</b>(currentLongOrderIds / currentShortOrderIds): * 用同步 Map 管理所有活跃的条件单(订单ID → 止盈价格)。挂条件单时通过回调存入, * 订单成交后通过 {@code futures.orders} 推送匹配止盈价并挂止盈单。</li> * <li><b>订单订阅(futures.orders)</b>:订单成交(status=finished, finish_as=filled)时, * 通过 {@link #onOrderUpdate(String, String, String)} 从 Map 中取出止盈价, * 调用 {@code executor.placeTakeProfit} 创建止盈条件单。</li> * <li><b>反向条件单</b>:当新网格首元素价格夹在多/空持仓均价之间, * 且反向持仓张数不超过 3 张时,额外挂一张反向市价单,通过订单订阅自动挂止盈。</li> * </ul> * * <h3>状态机</h3> * <h3>完整生命周期</h3> * <pre> * WAITING_KLINE → (首K线) → 异步双开基底 * * 仓位推送(dual_long/dual_short) → 基底成交 → 记录入场价 * → 双基底都成交 → 生成队列 + 初始条件单 + 止盈队列 → ACTIVE * * ACTIVE: * ├─ 每根K线 → 更新 unrealizedPnl → 方向判断 * │ ├─ closePrice > longPriceQueue[0] → processLongGrid * │ └─ closePrice < shortPriceQueue[0] → processShortGrid * ├─ processShortGrid: 匹配空仓队列 → 本队补充 → 挂空仓+多仓条件单(止盈价存入Map) * ├─ processLongGrid: 匹配多仓队列 → 本队补充 → 挂多仓+空仓条件单(止盈价存入Map) * ├─ 订单推送(futures.orders) → onOrderUpdate → Map 匹配止盈价 → 挂止盈条件单 * ├─ 仓位推送 → 更新均价/持仓量、仓位减少时处理反向单 * ├─ 平仓推送 → 累加 cumulativePnl * ├─ 保证金安全阀 → 超限跳过挂单,队列照常更新 * └─ cumulativePnl ≥ overallTp 或 ≤ -maxLoss → STOPPED * init() → startGrid() → WAITING_KLINE * ↓ * onKline(首根K线) → OPENING → 异步市价双开基底(开多+开空) * ↓ * onPositionUpdate() → 基底成交 → baseLongOpened && baseShortOpened * ↓ * tryGenerateQueues() * ├── generateShortQueue() ← 空仓价格队列(降序,从 shortBaseEntryPrice-step 向下) * ├── generateLongQueue() ← 多仓价格队列(升序,从 shortBaseEntryPrice+step 向上) * ├── updateGridElements() ← 构建 GridElement 列表 + TraderParam + 全局索引 * ├── 挂基座止盈单(ID=0 的 long/short takeProfit) * └── 挂初始条件单(up=-1 多单, down=1 空单) * ↓ * state = ACTIVE(每根K线反复执行以下循环) * ↓ * onKline() → processLongGrid() + processShortGrid() * ├── 匹配队列元素 → 队列补偿 → 保证金检查 * ├── 首元素方向:挂条件开仓单 → 订单ID + GridElement状态同步 * └── 反向守卫:在 downGrid 位置挂对向单(价格区间+trigger方向校验) * ↓ * onOrderUpdate() ← futures.orders / futures.autoorders 推送 * ├── 匹配止盈单ID → 清空止盈状态(已成交) * └── 匹配挂单ID → 挂止盈条件单 → 止盈ID + GridElement状态同步 * ↓ * onPositionClose() → cumulativePnl 累加 * ├── ≥ overallTp → STOPPED * └── ≤ -maxLoss → STOPPED * </pre> * * <h3>队列转移规则</h3> * <ul> * <li><b>空仓队列触发</b>(processShortGrid):matched 元素从空仓队列移除, * 尾部递减 step 补充新元素;多仓队列以首元素(最小价)递减 step 生成新元素加入。</li> * <li><b>多仓队列触发</b>(processLongGrid):matched 元素从多仓队列移除, * 尾部递增 step 补充新元素;空仓队列以首元素(最高价)递增 step 生成新元素加入。</li> * <li>队列容量超限时截断尾部,保持固定容量。</li> * </ul> * * <h3>止盈机制</h3> * <ul> * <li>网格触发时,挂条件单的回调中将订单 ID 和止盈价存入 currentLongOrderIds / currentShortOrderIds Map。</li> * <li>条件单成交后,{@code futures.orders} 推送触发 {@link #onOrderUpdate}, * 通过订单 ID 取出止盈价,创建止盈条件单(plan-close-*-position)。</li> * <li>止盈条件单:以触发价监控(price_type=最新价,strategy_type=价格触发), * 到达后以市价 IOC 平仓(reduce_only=true,price="0")。</li> * </ul> * * <h3>反向条件单条件</h3> * <h3>仓位线动态调整</h3> * <pre> * newFirstPrice > shortEntryPrice AND newFirstPrice < longEntryPrice * AND 反向持仓张数 < 3 * onPositionUpdate() 中仓位均价变化后: * longEntryPrice ↑ → 取消 高于 longEntryPrice 的空仓挂单(避免逆势空单) * shortEntryPrice ↓ → 取消 低于 shortEntryPrice 的多仓挂单(避免逆势多单) * </pre> * 满足条件时以 newFirstPrice ± step 为止盈价直接挂市价单,通过订单订阅自动挂止盈。 * * <h3>未实现盈亏公式(正向合约)</h3> * <h3>关键公式</h3> * <pre> * 多仓: 持仓量 × 合约乘数 × (计价价格 − 开仓均价) * 空仓: 持仓量 × 合约乘数 × (开仓均价 − 计价价格) * step = shortBaseEntryPrice × gridRate ← 网格绝对步长 * minTick = 10^(-priceScale) ← 交易所最小价格单位 * 多止盈 = gridPrice + (step - minTick) ← 多仓止盈价 * 空止盈 = gridPrice - (step - minTick) ← 空仓止盈价 * 单笔盈利 = (step - minTick) × contractMultiplier × quantity ← USDT * </pre> * 计价价格支持切换:{@link GateConfig.PnLPriceMode#LAST_PRICE 最新成交价} 或 * {@link GateConfig.PnLPriceMode#MARK_PRICE 标记价格}(通过 {@link #setMarkPrice(BigDecimal)} 注入)。 * 入场价和持仓量由 {@link #onPositionUpdate(String, Position.ModeEnum, BigDecimal, BigDecimal)} 实时更新。 * * <h3>线程模型</h3> * 所有 WS 回调(onKline/onPositionUpdate/onOrderUpdate 等)在 WS 回调线程中串行执行。 * 下单/撤单操作提交到 GateTradeExecutor 的单线程池异步执行,避免阻塞 WS 线程。 * stopGrid() 会将 state 设为 STOPPED,后续所有 WS 回调直接返回不再处理。 * * @author Administrator */ @@ -461,7 +445,7 @@ List<GridElement> allLongOrders = GridElement.findAllLongOrders(shortEntryPrice); if (CollUtil.isNotEmpty(allLongOrders)){ for (GridElement e : allLongOrders) { executor.cancelOrder(e.getShortOrderId()); executor.cancelOrder(e.getLongOrderId()); } } } src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
@@ -17,45 +17,37 @@ import java.util.function.Consumer; /** * Gate REST API 执行器。 * Gate REST API 异步执行器,所有下单/撤单操作经此类提交。 * * <h3>设计目的</h3> * WebSocket 消息在回调线程中处理(如 {@code WebSocketClient} 的 {@code onMessage} 线程)。 * 下单 REST API 调用可能耗时数百毫秒,若同步执行会阻塞 WS 回调线程,导致心跳超时误判。 * 本类将所有 REST 调用提交到独立线程池异步执行。 * * <h3>回调设计</h3> * 每个下单方法接受 onSuccess/onFailure 两个 Runnable。 * 基底开仓时 onSuccess 用于标记基底已开,网格触发时通常为 null(成交状态由仓位推送驱动)。 * REST API 调用可能耗时数百毫秒,若在 WebSocket 回调线程中同步执行会阻塞消息处理, * 导致心跳超时误判。本类将所有网络 I/O 提交到独立单线程池异步执行。 * * <h3>线程模型</h3> * <ul> * <li><b>单线程</b>:保证下单顺序(开多→开空→止盈单),避免并发竞争</li> * <li><b>有界队列 64</b>:防止堆积。极端行情下最多累积 64 个任务</li> * <li><b>CallerRunsPolicy</b>:队列满时由提交线程直接同步执行,形成自然背压</li> * <li><b>allowCoreThreadTimeOut</b>:60s 空闲后线程回收,不浪费资源</li> * <li><b>单线程 + 有界队列(64)</b> — 保证下单顺序,避免并发竞争</li> * <li><b>CallerRunsPolicy</b> — 队列满时由提交线程直接执行,形成自然背压</li> * <li><b>Daemon 线程</b> — 60s 空闲自动回收</li> * </ul> * * <h3>调用链</h3> * <pre> * // 基底双开 * GateGridTradeService.init → executor.openLong / executor.openShort * <h3>对外接口</h3> * <table> * <tr><th>方法</th><th>用途</th><th>调用方</th></tr> * <tr><td>openLong / openShort</td><td>市价 IOC 基底开仓</td><td>onKline(WAITING_KLINE)</td></tr> * <tr><td>placeConditionalEntryOrder</td><td>挂条件开仓单(价格触发后市价开仓)</td><td>tryGenerateQueues / processShortGrid / processLongGrid</td></tr> * <tr><td>placeTakeProfit</td><td>挂止盈条件单(plan-close-*-position)</td><td>tryGenerateQueues / onOrderUpdate / onAutoOrder</td></tr> * <tr><td>cancelOrder</td><td>取消限价单(仓位线调整用)</td><td>onPositionUpdate</td></tr> * <tr><td>cancelConditionalOrder</td><td>取消单个条件单</td><td>遗留保留</td></tr> * <tr><td>cancelAllPriceTriggeredOrders</td><td>取消所有条件单(策略停止时)</td><td>stopGrid</td></tr> * </table> * * // 网格条件开仓 * GateGridTradeService.tryGenerateQueues / processShortGrid / processLongGrid * → executor.placeConditionalEntryOrder(价格触发后市价 IOC 开仓) * → executor.cancelConditionalOrder(取消对方方向旧条件单) * <h3>容错</h3> * <ul> * <li>止盈单创建失败 → 立即 marketClose() 市价平仓</li> * <li>取消订单失败 → 仅 warn 日志(可能已成交/已取消)</li> * </ul> * * // 止盈 * GateGridTradeService.onPositionUpdate → executor.placeTakeProfit(开仓后设止盈条件单) * * // 停止 * GateGridTradeService.stopGrid → executor.cancelAllPriceTriggeredOrders * </pre> * * <h3>遗留方法(当前策略未使用)</h3> * {@link #placeGridLimitOrder(BigDecimal, String, Consumer, Runnable)} 和 * {@link #cancelOrder(String)} 为旧版限价单策略的遗留方法,保留以备后续使用。 * @author Administrator */ @Slf4j public class GateTradeExecutor { src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
@@ -14,24 +14,31 @@ import java.math.BigDecimal; /** * Gate 模块 Spring 入口,组装所有组件并管理生命周期。 * Gate 模块 Spring 容器入口 — 组件组装 + 生命周期管理。 * * <h3>启动流程 ({@code @PostConstruct})</h3> * <h3>组装顺序({@code @PostConstruct})</h3> * <ol> * <li>构建 {@link GateConfig}(Builder 模式,含 API 密钥、合约、策略参数)</li> * <li>创建 {@link GateGridTradeService} → init():切持仓模式、清旧条件单、设杠杆</li> * <li>创建 {@link GateKlineWebSocketClient} → 注册 3 个 Handler → init():建立 WS 连接</li> * <li>gridTradeService.startGrid():激活策略,等待 K 线触发首次双开</li> * <li>{@link GateConfig} — 构建配置(API 密钥、合约、策略参数)</li> * <li>{@link GateGridTradeService} — init():获取用户 ID → 切双向持仓 → 清旧条件单 → 平仓 → 设杠杆</li> * <li>{@link GateKlineWebSocketClient} — 注册 6 个频道处理器 → init():建立 WS 连接并订阅</li> * <li>{@code gridTradeService.startGrid()} — 状态重置,等待首根 K 线</li> * </ol> * * <h3>销毁流程 ({@code @PreDestroy})</h3> * <h3>6 个频道处理器</h3> * <ol> * <li>gridTradeService.stopGrid():取消条件单 → 关闭交易线程池</li> * <li>CandlestickChannelHandler — 公开频道,K线 → onKline()</li> * <li>PositionsChannelHandler — 私有频道,仓位 → onPositionUpdate()</li> * <li>PositionClosesChannelHandler — 私有频道,平仓 → onPositionClose()</li> * <li>OrdersChannelHandler — 私有频道,订单成交 → onOrderUpdate()</li> * <li>UserTradesChannelHandler — 私有频道,用户成交 → onUserTrade()</li> * <li>AutoOrdersChannelHandler — 私有频道,条件单状态 → onAutoOrder()</li> * </ol> * * <h3>销毁顺序({@code @PreDestroy})</h3> * <ol> * <li>gridTradeService.stopGrid():取消所有条件单 → 关闭交易线程池</li> * <li>wsClient.destroy():取消订阅 → 断开 WS → 关闭线程池</li> * </ol> * * <h3>配置</h3> * 当前在代码中硬编码测试网参数。切换到生产网只需改为 {@code .isProduction(true)}。 * * @author Administrator */ src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
@@ -7,34 +7,56 @@ import java.util.concurrent.ConcurrentHashMap; /** * 网格元素,表示网格交易策略中的一个价格层级。 * 网格价格层级,策略的最小操作单元。 * * <p>每个网格层级包含该价格位置上的多仓和空仓挂单信息, * 通过编号和网格价格唯一标识一个网格层级。 * <h3>定位</h3> * 每个 GridElement 对应网格中的一个价格点,同时持有该点的多仓和空仓挂单状态。 * 与传统的"价格队列+Map"模式不同,GridElement 将价格、方向、状态、订单ID 聚合到一个对象中, * 并维护全局静态 HashMap 索引实现 O(1) 双向查询。 * * <h3>关键字段</h3> * <ul> * <li><b>编号</b>:网格层级的唯一标识</li> * <li><b>网格价格</b>:该层级对应的触发价格</li> * <li><b>多仓挂单</b>:是否已有多头挂单、多头挂单参数</li> * <li><b>空仓挂单</b>:是否有空头挂单、空头挂单参数</li> * </ul> * <h3>ID 体系与链表</h3> * <pre> * ID ≦ -1: 空仓队列区域(降序),ID 自减,gridPrice 递减 * ID = 0: 基座位置,gridPrice = shortBaseEntryPrice * ID ≧ 1: 多仓队列区域(升序),ID 自增,gridPrice 递增 * * 链表: ... ← -3 ← -2 ← -1 ← 0 → 1 → 2 → 3 → ... * (通过 upId/downId + INDEX 实现 O(1) 遍历) * </pre> * * <h3>字段分组</h3> * <table> * <tr><th>类别</th><th>字段</th><th>说明</th></tr> * <tr><td>标识</td><td>id, gridPrice, upId, downId</td><td>编号、价格、双向链表指针</td></tr> * <tr><td>多仓订单</td><td>hasLongOrder, longOrderId, longTraderParam</td><td>是否有挂单、订单ID、完整参数</td></tr> * <tr><td>空仓订单</td><td>hasShortOrder, shortOrderId, shortTraderParam</td><td>是否有挂单、订单ID、完整参数</td></tr> * <tr><td>止盈</td><td>longTakeProfitOrderId, shortTakeProfitOrderId</td><td>止盈条件单ID</td></tr> * </table> * * <h3>6 个全局 O(1) 索引</h3> * <pre> * INDEX → findById(int) ID → 元素 * PRICE_INDEX → findByPrice(BigDecimal) 价格 → 元素 * LONG_ORDER_ID_INDEX → findByLongOrderId(String) 多仓挂单ID → 元素 * SHORT_ORDER_ID_INDEX → findByShortOrderId(String) 空仓挂单ID → 元素 * LONG_TP_ORDER_ID_INDEX→ findByLongTakeProfitOrderId(String) 多止盈ID → 元素 * SHORT_TP_ORDER_ID_INDEX→ findByShortTakeProfitOrderId(String) 空止盈ID → 元素 * </pre> * 索引通过 {@link #rebuildIndex(List)}(全量重建)或 {@link #refreshIndices()}(增量刷新) * 维护,每次操作后自动打印全量网格状态到控制台。 * * <h3>何时填充 TraderParam</h3> * 初始化时 {@code updateGridElements()} 为每个元素预填充 longTraderParam 和 shortTraderParam * (含 direction/entryPrice/takeProfitPrice/quantity),订单ID字段在挂单成功后由 * {@link GateGridTradeService} 的 4 个辅助方法写入。 * * <h3>使用示例</h3> * <pre> * GridElement element = GridElement.builder() * .id(1) * .gridPrice(new BigDecimal("2300.0")) * .build(); * * // 挂多仓单 * TraderParam longParam = TraderParam.builder() * .direction(TraderParam.Direction.LONG) * .entryPrice(new BigDecimal("2293.7")) * .takeProfitPrice(new BigDecimal("2301.6")) * .build(); * element.setHasLongOrder(true); * element.setLongTraderParam(longParam); * GridElement elem = GridElement.findById(1); * boolean hasLong = elem.isHasLongOrder(); // 是否挂了多单 * BigDecimal tpPrice = elem.getLongTraderParam().getTakeProfitPrice(); // 止盈价 * elem.getUp(); // 上一个网格(O(1)) * elem.getDown(); // 下一个网格(O(1)) * </pre> * * @author Administrator src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java
@@ -3,38 +3,27 @@ import java.math.BigDecimal; /** * 网格交易单参数,封装单笔条件开仓单及其止盈单的完整状态。 * 单笔挂单的完整参数,封装条件开仓单及止盈单的状态。 * * <p>每笔挂单对应的参数独立存储为一个 TraderParam 实例, * 用于追踪条件开仓单和止盈条件单的挂单状态、价格位置及订单 ID。 * <h3>定位</h3> * 每个 GridElement 内嵌两个 TraderParam(longTraderParam / shortTraderParam), * 分别记录该价格层级上多仓和空仓的挂单参数。所有字段通过 getter/setter * 在挂单/成交/止盈各阶段逐步填充。 * * <h3>关键字段</h3> * <ul> * <li><b>方向</b>:多 / 空,决定挂单和止盈的触发方向</li> * <li><b>价格</b>:挂单价(条件开仓触发价)、止盈价(止盈触发价)</li> * <li><b>挂单状态</b>:挂单价是否挂成功、止盈价是否挂成功</li> * <li><b>网格位置</b>:挂单价网格位置、止盈价网格位置(用于定位在价格队列中的索引)</li> * <li><b>订单ID</b>:挂单订单 ID、止盈订单 ID(用于取消和匹配)</li> * </ul> * <h3>字段分组</h3> * <table> * <tr><th>类别</th><th>字段</th><th>生命周期</th></tr> * <tr><td>开仓准备</td><td>direction, entryPrice, quantity</td><td>updateGridElements() 预填充</td></tr> * <tr><td>止盈预定</td><td>takeProfitPrice</td><td>updateGridElements() 预填充(entryPrice ± (step - minTick))</td></tr> * <tr><td>挂单确认</td><td>entryOrderPlaced, entryOrderId</td><td>条件单挂成功后由 longEntryTraderIdParam 等写入</td></tr> * <tr><td>止盈确认</td><td>takeProfitPlaced, takeProfitOrderId</td><td>止盈单挂成功后由 longTakeProfitTraderIdParam 等写入</td></tr> * <tr><td>定位</td><td>entryGridPosition, takeProfitGridPosition</td><td>当前策略中由 upId/downId 替代</td></tr> * </table> * * <h3>使用示例</h3> * <h3>盈利公式(正向合约)</h3> * <pre> * TraderParam param = TraderParam.builder() * .direction(Direction.LONG) * .entryPrice(new BigDecimal("2293.7")) * .takeProfitPrice(new BigDecimal("2301.6")) * .quantity("1") * .entryGridPosition(3) * .takeProfitGridPosition(4) * .build(); * * // 挂单成功后更新 * param.setEntryOrderPlaced(true); * param.setEntryOrderId("12345"); * * // 止盈单挂成功后更新 * param.setTakeProfitPlaced(true); * param.setTakeProfitOrderId("12346"); * 多仓止盈盈利 = takeProfitPrice - entryPrice = (entryPrice + step - minTick) - entryPrice = step - minTick * 空仓止盈盈利 = entryPrice - takeProfitPrice = entryPrice - (entryPrice - step + minTick) = step - minTick * </pre> * * @author Administrator src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/AbstractPrivateChannelHandler.java
@@ -11,22 +11,7 @@ import java.nio.charset.StandardCharsets; /** * 私有频道处理器的抽象基类。 * * <h3>封装内容</h3> * <ul> * <li>HMAC-SHA512 签名计算(UTF-8 编码)</li> * <li>认证请求 JSON 构建(id/time/channel/payload/auth)</li> * <li>subscribe / unsubscribe 的默认实现(含签名)</li> * <li>用户 ID 获取(从 {@link GateGridTradeService#getUserId()})</li> * </ul> * * <h3>签名算法</h3> * {@code SIGN = Hex(HmacSHA512(secret_utf8, "channel={channel}&event={event}&time={timeSec}"_utf8))} * * <h3>子类</h3> * {@link com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionsChannelHandler}、 * {@link com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionClosesChannelHandler} * 私有频道 WS 处理器的抽象基类,封装 HMAC-SHA512 签名认证与订阅/取消订阅逻辑。 * * @author Administrator */ src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java
@@ -11,21 +11,14 @@ import java.math.BigDecimal; /** * K 线(Candlestick)频道处理器。 * K 线频道处理器(futures.candlesticks)— 策略的唯一价格时间驱动源。 * * <h3>特点</h3> * 公开频道,无需 HMAC-SHA512 认证签名。订阅 1m 周期的 K 线数据。 * * <h3>数据流</h3> * <pre> * WebSocket 推送 update event * → handleMessage() → 解析 OHLCV → log 打印 → gridTradeService.onKline(closePx) * → WAITING_KLINE: 首次 K 线触发基底双开 * → ACTIVE: 驱动 processShortGrid + processLongGrid 网格触发 * </pre> * <h3>定位</h3> * 订阅 1 分钟 K 线实时推送,每收到一根 K 线(不等待完结)即触发 * {@link GateGridTradeService#onKline(BigDecimal)},由策略引擎决定是否开仓/止盈。 * * <h3>订阅格式</h3> * payload: {@code ["1m", contract]} * 公开频道,无需认证签名。payload: {@code ["1m", contract]}。 * * @author Administrator */ src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/OrdersChannelHandler.java
@@ -7,24 +7,8 @@ import lombok.extern.slf4j.Slf4j; /** * 订单频道处理器(futures.orders)。 * * <h3>数据用途</h3> * 订阅订单更新推送。当订单状态变为 finished 且成交方式为 filled 时, * 通过订单 ID 匹配 {@code currentShortOrderIds} 中存储的止盈价格,自动挂止盈单。 * * <h3>关键推送字段</h3> * <ul> * <li>id:订单 ID</li> * <li>contract:合约名称</li> * <li>status:订单状态(open / finished)</li> * <li>finish_as:订单结束方式(filled / cancelled / ioc 等)</li> * <li>price:订单价格</li> * <li>size:订单大小</li> * </ul> * * <h3>回调数据(传给 GateGridTradeService)</h3> * orderId (String), status (String), finishAs (String) * 订单频道处理器(futures.orders),接收订单更新推送并回调 {@link GateGridTradeService#onOrderUpdate}。 * 回调内部通过 {@code GridElement.findByLongOrderId / findByShortOrderId} 匹配网格订单。 * * @author Administrator */ src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java
@@ -9,18 +9,7 @@ import java.math.BigDecimal; /** * 平仓频道处理器。 * * <h3>数据用途</h3> * 每笔平仓发生时推送 pnl(盈亏金额),累加到 {@code cumulativePnl} 用于判断策略停止条件: * cumulativePnl ≥ overallTp(达到止盈目标)或 ≤ -maxLoss(超过亏损上限)。 * 止盈由 Gate 服务端条件单自动触发,平仓后仓位变为 0,盈亏通过本频道推送。 * * <h3>推送字段</h3> * contract, side(long / short), pnl(该次平仓的盈亏,如 "+0.2" 或 "-0.1") * * <h3>注意</h3> * 平仓盈亏来自服务器端,不受本地计算误差影响。这是策略停止的唯一盈亏判断来源。 * 平仓频道处理器(futures.position_closes),接收平仓盈亏推送并回调 {@link GateGridTradeService#onPositionClose}。 * * @author Administrator */ src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java
@@ -10,45 +10,7 @@ import java.math.BigDecimal; /** * 仓位频道处理器(futures.positions)。 * * <h3>数据用途</h3> * 监控仓位数量(size)和入场价(entry_price)。 * 有仓位时(size.abs > 0):标记方向活跃,记录入场价 → 基底首次成交记录基底入场价并等待生成网格队列, * 非基底成交立即设止盈条件单。无仓位时(size=0):标记方向不活跃。 * * <h3>完整推送字段(共20个,日志全部输出)</h3> * <table> * <tr><th>字段</th><th>类型</th><th>描述</th></tr> * <tr><td>contract</td><td>String</td><td>合约名称</td></tr> * <tr><td>mode</td><td>String</td><td>持仓模式:dual_long / dual_short</td></tr> * <tr><td>size</td><td>String/Integer</td><td>合约张数(正=多头,负=空头)</td></tr> * <tr><td>entry_price</td><td>Float</td><td>开仓均价</td></tr> * <tr><td>cross_leverage_limit</td><td>Float</td><td>全仓模式下的杠杆倍数上限</td></tr> * <tr><td>history_pnl</td><td>Float</td><td>已平仓的仓位总盈亏</td></tr> * <tr><td>history_point</td><td>Float</td><td>已平仓的点卡总盈亏</td></tr> * <tr><td>last_close_pnl</td><td>Float</td><td>最近一次平仓的盈亏</td></tr> * <tr><td>leverage</td><td>Integer</td><td>杠杆倍数(0=全仓,正数=逐仓)</td></tr> * <tr><td>leverage_max</td><td>Integer</td><td>当前风险限额下允许的最大杠杆倍数</td></tr> * <tr><td>liq_price</td><td>Float</td><td>爆仓价格</td></tr> * <tr><td>maintenance_rate</td><td>Float</td><td>当前风险限额下维持保证金比例</td></tr> * <tr><td>margin</td><td>Float</td><td>保证金</td></tr> * <tr><td>realised_pnl</td><td>Float</td><td>已实现盈亏</td></tr> * <tr><td>realised_point</td><td>Float</td><td>点卡已实现盈亏</td></tr> * <tr><td>risk_limit</td><td>Integer</td><td>风险限额</td></tr> * <tr><td>time</td><td>Integer</td><td>更新 unix 时间戳(秒)</td></tr> * <tr><td>time_ms</td><td>Integer</td><td>更新 unix 时间戳(毫秒)</td></tr> * <tr><td>user</td><td>String</td><td>用户 ID</td></tr> * <tr><td>update_id</td><td>Integer</td><td>消息序列号,每次推送 order 后自增1</td></tr> * </table> * * <h3>回调数据(传给 GateGridTradeService)</h3> * mode (Position.ModeEnum), size (BigDecimal), entry_price (BigDecimal) * * <h3>注意</h3> * 双向持仓模式下空头 size 为负数,使用 {@code size.abs()} 判断是否有仓位。 * 累计盈亏不由本频道计算,而是由 {@link PositionClosesChannelHandler} 独立处理。 * 止盈条件单由服务端自动触发平仓,本频道不负责开仓操作。 * 仓位频道处理器(futures.positions),接收仓位更新推送并回调 {@link GateGridTradeService#onPositionUpdate}。 * * @author Administrator */ src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/UserTradesChannelHandler.java
@@ -9,27 +9,7 @@ import java.math.BigDecimal; /** * 用户私有成交频道处理器(futures.usertrades)。 * * <h3>数据用途</h3> * 订阅用户私有成交更新推送。当订单成交时,获得真实的成交价格、手续费等信息, * 可用于计算实际盈亏。 * * <h3>关键推送字段</h3> * <ul> * <li>id:交易 ID</li> * <li>contract:合约名称</li> * <li>order_id:订单 ID</li> * <li>size:成交数量</li> * <li>price:成交价格</li> * <li>role:用户角色(maker/taker)</li> * <li>fee:手续费</li> * <li>point_fee:点卡手续费</li> * <li>text:订单自定义信息</li> * </ul> * * <h3>回调数据(传给 GateGridTradeService)</h3> * contract (String), orderId (String), price (BigDecimal), size (String), role (String), fee (BigDecimal) * 用户私有成交频道处理器(futures.usertrades),接收成交推送并回调 {@link GateGridTradeService#onUserTrade}。 * * @author Administrator */ @@ -69,9 +49,9 @@ String text = trade.getString("text"); log.info("[{}] 成交更新, id:{}, order_id:{}, price:{}, size:{}, role:{}, fee:{}, text:{}", CHANNEL_NAME, id, orderId, price, size, role, fee, text); if (getGridTradeService() != null) { getGridTradeService().onUserTrade(contract, orderId, price, size, role, fee); } // if (getGridTradeService() != null) { // getGridTradeService().onUserTrade(contract, orderId, price, size, role, fee); // } } } catch (Exception e) { log.error("[{}] 处理数据失败", CHANNEL_NAME, e);