refactor(gateApi): 重构 Gate API 模块代码结构
- 移除未使用的 GateChannelHandler 导入
- 为 AbstractPrivateChannelHandler 添加详细的 Javadoc 文档
- 优化 subscribe 和 unsubscribe 方法的日志输出格式
- 为 CandlestickChannelHandler 添加完整的类文档注释
- 更新 gateApi-logic.md 文档,添加 GateConfig 和 GateTradeExecutor 组件说明
- 优化 handleMessage 方法的日志输出和错误处理
- 简化方法实现,移除不必要的换行和注释
- 添加 GateConfig 类,提供统一的配置管理和环境切换功能
| | |
| | | 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; |
| | |
| | | detailClient.setApiKeySecret(config.getApiKey(), config.getApiSecret()); |
| | | AccountDetail detail = new AccountApi(detailClient).getAccountDetail(); |
| | | this.userId = detail.getUserId(); |
| | | log.info("[Gate] uid:{}", userId); |
| | | log.info("[Gate] 用户ID: {}", userId); |
| | | |
| | | FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE); |
| | | if (!config.getPositionMode().equals(account.getPositionMode())) { |
| | | futuresApi.setPositionMode(SETTLE, config.getPositionMode()); |
| | | } |
| | | log.info("[Gate] mode:{} balance:{}", config.getPositionMode(), account.getAvailable()); |
| | | log.info("[Gate] 持仓模式: {} 余额: {}", config.getPositionMode(), account.getAvailable()); |
| | | |
| | | futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract()); |
| | | log.info("[Gate] old orders cleared"); |
| | | log.info("[Gate] 旧条件单已清除"); |
| | | |
| | | closeExistingPositions(); |
| | | |
| | | futuresApi.updateContractPositionLeverageCall( |
| | | SETTLE, config.getContract(), config.getLeverage(), |
| | | config.getMarginMode(), config.getPositionMode(), null); |
| | | log.info("[Gate] {}x {}", config.getLeverage(), config.getMarginMode()); |
| | | log.info("[Gate] 杠杆: {}x {}", config.getLeverage(), config.getMarginMode()); |
| | | } catch (GateApiException e) { |
| | | log.error("[Gate] init fail, label:{}, msg:{}", e.getErrorLabel(), e.getMessage()); |
| | | log.error("[Gate] 初始化失败, label:{}, msg:{}", e.getErrorLabel(), e.getMessage()); |
| | | } catch (ApiException e) { |
| | | log.error("[Gate] init fail, code:{}", e.getCode()); |
| | | log.error("[Gate] 初始化失败, code:{}", e.getCode()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 平掉当前合约所有已有仓位。 |
| | | * 策略启动前的准备工作,确保从零持仓状态开始运行。 |
| | | */ |
| | | private void closeExistingPositions() { |
| | | try { |
| | | java.util.List<Position> 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.setSize(closeSize); |
| | | closeOrder.setPrice("0"); |
| | | closeOrder.setTif(FuturesOrder.TifEnum.IOC); |
| | | closeOrder.setReduceOnly(true); |
| | | if (mode != null && mode.getValue() != null && mode.getValue().contains("dual")) { |
| | | closeOrder.setAutoSize(isLong ? FuturesOrder.AutoSizeEnum.LONG : FuturesOrder.AutoSizeEnum.SHORT); |
| | | } |
| | | 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] already running, state:{}", state); |
| | | log.warn("[Gate] 策略已在运行中, state:{}", state); |
| | | return; |
| | | } |
| | | state = StrategyState.WAITING_KLINE; |
| | |
| | | shortActive = false; |
| | | longReopenFails = 0; |
| | | shortReopenFails = 0; |
| | | log.info("[Gate] grid started"); |
| | | log.info("[Gate] 网格策略已启动"); |
| | | } |
| | | |
| | | public void stopGrid() { |
| | | state = StrategyState.STOPPED; |
| | | executor.cancelAllPriceTriggeredOrders(); |
| | | executor.shutdown(); |
| | | log.info("[Gate] stopped, pnl:{}", cumulativePnl); |
| | | log.info("[Gate] 策略已停止, 累计盈亏: {}", cumulativePnl); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | state = StrategyState.OPENING; |
| | | log.info("[Gate] first kline, opening..."); |
| | | log.info("[Gate] 首根K线到达,开始双开..."); |
| | | |
| | | executor.openLong(config.getQuantity(), () -> { |
| | | synchronized (this) { |
| | |
| | | "close-short-position", "close_short"); |
| | | if (longActive && shortActive && state != StrategyState.STOPPED) { |
| | | state = StrategyState.ACTIVE; |
| | | log.info("[Gate] active, long:{}, short:{}, tpL:{}, tpS:{}", |
| | | log.info("[Gate] 已激活, 多头入场:{}, 空头入场:{}, 多头止盈:{}, 空头止盈:{}", |
| | | longEntryPrice, shortEntryPrice, longTpPrice(), shortTpPrice()); |
| | | } |
| | | }); |
| | |
| | | |
| | | if ("dual_long".equals(mode)) { |
| | | if (longActive && !hasPosition) { |
| | | log.info("[Gate] long closed"); |
| | | log.info("[Gate] 多头已平仓"); |
| | | longActive = false; |
| | | tryReopenLong(0); |
| | | } else if (hasPosition) { |
| | |
| | | } |
| | | } else if ("dual_short".equals(mode)) { |
| | | if (shortActive && !hasPosition) { |
| | | log.info("[Gate] short closed"); |
| | | log.info("[Gate] 空头已平仓"); |
| | | shortActive = false; |
| | | tryReopenShort(0); |
| | | } else if (hasPosition) { |
| | |
| | | return; |
| | | } |
| | | cumulativePnl = cumulativePnl.add(pnl); |
| | | log.info("[Gate] pnl+{}, side:{}, total:{}", pnl, side, cumulativePnl); |
| | | log.info("[Gate] 盈亏累加:{}, 方向:{}, 累计:{}", pnl, side, cumulativePnl); |
| | | |
| | | if (cumulativePnl.compareTo(config.getOverallTp()) >= 0) { |
| | | log.info("[Gate] TP reached {}→STOPPED", cumulativePnl); |
| | | log.info("[Gate] 已达止盈目标 {}→已停止", cumulativePnl); |
| | | state = StrategyState.STOPPED; |
| | | } else if (cumulativePnl.compareTo(config.getMaxLoss().negate()) <= 0) { |
| | | log.info("[Gate] loss {}→STOPPED", cumulativePnl); |
| | | log.info("[Gate] 已达亏损上限 {}→已停止", cumulativePnl); |
| | | state = StrategyState.STOPPED; |
| | | } |
| | | } |
| | | |
| | | // ---- reopen with retry ---- |
| | | // ---- 补仓(含重试) ---- |
| | | |
| | | private void tryReopenLong(int retry) { |
| | | if (state == StrategyState.STOPPED) { |
| | |
| | | if (state != StrategyState.STOPPED) { |
| | | state = StrategyState.ACTIVE; |
| | | } |
| | | log.info("[Gate] long reopened, price:{}", longEntryPrice); |
| | | log.info("[Gate] 多头已补开, 价格:{}", longEntryPrice); |
| | | }); |
| | | } |
| | | |
| | |
| | | if (state != StrategyState.STOPPED) { |
| | | state = StrategyState.ACTIVE; |
| | | } |
| | | log.info("[Gate] short reopened, price:{}", shortEntryPrice); |
| | | log.info("[Gate] 空头已补开, 价格:{}", shortEntryPrice); |
| | | }); |
| | | } |
| | | |
| | | // ---- util ---- |
| | | // ---- 止盈价格计算 ---- |
| | | |
| | | private BigDecimal longTpPrice() { |
| | | return lastKlinePrice.multiply(BigDecimal.ONE.add(config.getGridRate())) |
| | |
| | | .setScale(1, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** 对数量取反(开多用正数,开空用负数) */ |
| | | private String negate(String qty) { |
| | | return qty.startsWith("-") ? qty.substring(1) : "-" + qty; |
| | | } |
| | |
| | | * |
| | | * @author Administrator |
| | | */ |
| | | @SuppressWarnings("ALL") |
| | | @Slf4j |
| | | public class GateKlineWebSocketClient { |
| | | |
| | |
| | | */ |
| | | public void init() { |
| | | if (!isInitialized.compareAndSet(false, true)) { |
| | | log.warn("[WS] already init, skip"); |
| | | log.warn("[WS] 已初始化过,跳过重复初始化"); |
| | | return; |
| | | } |
| | | connect(); |
| | |
| | | * 避免 onClose 回调中的 reconnectWithBackoff 访问已关闭的线程池。 |
| | | */ |
| | | public void destroy() { |
| | | log.info("[WS] destroy..."); |
| | | log.info("[WS] 开始销毁..."); |
| | | |
| | | if (webSocketClient != null && webSocketClient.isOpen()) { |
| | | for (GateChannelHandler handler : channelHandlers) { |
| | |
| | | Thread.sleep(500); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.warn("[WS] unsubscribe wait interrupted"); |
| | | log.warn("[WS] 取消订阅等待被中断"); |
| | | } |
| | | } |
| | | |
| | |
| | | webSocketClient.closeBlocking(); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.warn("[WS] close interrupted"); |
| | | log.warn("[WS] 关闭连接时被中断"); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | shutdownExecutorGracefully(sharedExecutor); |
| | | |
| | | log.info("[WS] destroyed"); |
| | | log.info("[WS] 销毁完成"); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | private void connect() { |
| | | if (isConnecting.get() || !isConnecting.compareAndSet(false, true)) { |
| | | log.info("[WS] already connecting"); |
| | | log.info("[WS] 连接进行中,跳过重复请求"); |
| | | return; |
| | | } |
| | | try { |
| | |
| | | webSocketClient = new WebSocketClient(uri) { |
| | | @Override |
| | | public void onOpen(ServerHandshake handshake) { |
| | | log.info("[WS] connected"); |
| | | log.info("[WS] 连接成功"); |
| | | isConnected.set(true); |
| | | isConnecting.set(false); |
| | | if (sharedExecutor != null && !sharedExecutor.isShutdown()) { |
| | |
| | | } |
| | | sendPing(); |
| | | } else { |
| | | log.warn("[WS] shutting down, ignore onOpen"); |
| | | log.warn("[WS] 应用正在关闭,忽略连接成功回调"); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | @Override |
| | | public void onClose(int code, String reason, boolean remote) { |
| | | log.warn("[WS] closed, code:{}, reason:{}", code, reason); |
| | | log.warn("[WS] 连接关闭, code:{}, reason:{}", code, reason); |
| | | isConnected.set(false); |
| | | isConnecting.set(false); |
| | | cancelPongTimeout(); |
| | | if (sharedExecutor != null && !sharedExecutor.isShutdown() && !sharedExecutor.isTerminated()) { |
| | | sharedExecutor.execute(() -> { |
| | | try { reconnectWithBackoff(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { log.error("[WS] reconnect fail", e); } |
| | | try { reconnectWithBackoff(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { log.error("[WS] 重连失败", e); } |
| | | }); |
| | | } else { |
| | | log.warn("[WS] executor closed, no reconnect"); |
| | | log.warn("[WS] 线程池已关闭,不执行重连"); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Exception ex) { |
| | | log.error("[WS] error", ex); |
| | | log.error("[WS] 发生错误", ex); |
| | | isConnected.set(false); |
| | | } |
| | | }; |
| | | webSocketClient.connect(); |
| | | } catch (URISyntaxException e) { |
| | | log.error("[WS] bad uri", e); |
| | | log.error("[WS] URI格式错误", e); |
| | | isConnecting.set(false); |
| | | } |
| | | } |
| | |
| | | String event = response.getString("event"); |
| | | |
| | | if (FUTURES_PONG.equals(channel)) { |
| | | log.debug("[WS] pong received"); |
| | | log.debug("[WS] 收到 pong 响应"); |
| | | cancelPongTimeout(); |
| | | return; |
| | | } |
| | | if ("subscribe".equals(event)) { |
| | | log.info("[WS] {} subscribed: {}", channel, response.getJSONObject("result")); |
| | | log.info("[WS] {} 订阅成功: {}", channel, response.getJSONObject("result")); |
| | | return; |
| | | } |
| | | if ("unsubscribe".equals(event)) { |
| | | log.info("[WS] {} unsubscribed", channel); |
| | | log.info("[WS] {} 取消订阅成功", channel); |
| | | return; |
| | | } |
| | | if ("error".equals(event)) { |
| | | JSONObject error = response.getJSONObject("error"); |
| | | log.error("[WS] {} error, code:{}, msg:{}", |
| | | log.error("[WS] {} 错误, code:{}, msg:{}", |
| | | channel, |
| | | error != null ? error.getInteger("code") : "N/A", |
| | | error != null ? error.getString("message") : response.getString("msg")); |
| | |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[WS] handle msg fail: {}", message, e); |
| | | log.error("[WS] 处理消息失败: {}", message, e); |
| | | } |
| | | } |
| | | |
| | |
| | | pingMsg.put("time", System.currentTimeMillis() / 1000); |
| | | pingMsg.put("channel", FUTURES_PING); |
| | | webSocketClient.send(pingMsg.toJSONString()); |
| | | log.debug("[WS] ping sent"); |
| | | log.debug("[WS] 发送 ping 请求"); |
| | | } |
| | | } catch (Exception e) { log.warn("[WS] ping fail", e); } |
| | | } catch (Exception e) { log.warn("[WS] 发送 ping 失败", e); } |
| | | } |
| | | |
| | | private synchronized void cancelPongTimeout() { |
| | |
| | | int attempt = 0, maxAttempts = 3; |
| | | long delayMs = 5000; |
| | | while (attempt < maxAttempts) { |
| | | try { Thread.sleep(delayMs); connect(); return; } catch (Exception e) { log.warn("[WS] reconnect attempt {} fail", attempt + 1, e); delayMs *= 2; attempt++; } |
| | | try { Thread.sleep(delayMs); connect(); return; } catch (Exception e) { log.warn("[WS] 第{}次重连失败", attempt + 1, e); delayMs *= 2; attempt++; } |
| | | } |
| | | log.error("[WS] reconnect exhausted after {} attempts", maxAttempts); |
| | | log.error("[WS] 超过最大重试次数({}),放弃重连", maxAttempts); |
| | | } |
| | | |
| | | private void shutdownExecutorGracefully(ExecutorService executor) { |
| | |
| | | order.setTif(FuturesOrder.TifEnum.IOC); |
| | | order.setText("t-grid-long"); |
| | | FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, order, null); |
| | | log.info("[TradeExec] 开多成功, price:{}, id:{}", result.getFillPrice(), result.getId()); |
| | | if (onSuccess != null) onSuccess.run(); |
| | | log.info("[TradeExec] 开多成功, 价格:{}, id:{}", result.getFillPrice(), result.getId()); |
| | | if (onSuccess != null) { |
| | | onSuccess.run(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[TradeExec] 开多失败", e); |
| | | } |
| | |
| | | order.setTif(FuturesOrder.TifEnum.IOC); |
| | | order.setText("t-grid-short"); |
| | | FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, order, null); |
| | | log.info("[TradeExec] 开空成功, price:{}, id:{}", result.getFillPrice(), result.getId()); |
| | | if (onSuccess != null) onSuccess.run(); |
| | | log.info("[TradeExec] 开空成功, 价格:{}, id:{}", result.getFillPrice(), result.getId()); |
| | | if (onSuccess != null) { |
| | | onSuccess.run(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[TradeExec] 开空失败", e); |
| | | } |
| | |
| | | FuturesPriceTriggeredOrder order = buildTriggeredOrder(triggerPrice, rule, orderType, autoSize); |
| | | try { |
| | | TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order); |
| | | log.info("[TradeExec] 止盈单已创建, tp:{}, orderType:{}, id:{}", |
| | | log.info("[TradeExec] 止盈单已创建, 触发价:{}, 类型:{}, id:{}", |
| | | triggerPrice, orderType, response.getId()); |
| | | } catch (GateApiException e) { |
| | | if ("AUTO_USER_EXIST_POSITION_ORDER".equals(e.getErrorLabel())) { |
| | | log.warn("[TradeExec] 止盈单已存在,清除后重试"); |
| | | log.warn("[TradeExec] 止盈单已存在,清除旧单后重试"); |
| | | try { |
| | | futuresApi.cancelPriceTriggeredOrderList(SETTLE, contract); |
| | | TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order); |
| | | log.info("[TradeExec] 止盈单重试成功, tp:{}, id:{}", triggerPrice, response.getId()); |
| | | log.info("[TradeExec] 止盈单重试成功, 触发价:{}, id:{}", triggerPrice, response.getId()); |
| | | } catch (Exception retryEx) { |
| | | log.error("[TradeExec] 止盈单重试失败", retryEx); |
| | | } |
| | | } else { |
| | | log.error("[TradeExec] 止盈单创建失败, tp:{}", triggerPrice, e); |
| | | log.error("[TradeExec] 止盈单创建失败, 触发价:{}", triggerPrice, e); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("[TradeExec] 止盈单创建失败, tp:{}", triggerPrice, e); |
| | | log.error("[TradeExec] 止盈单创建失败, 触发价:{}", triggerPrice, e); |
| | | } |
| | | }); |
| | | } |
| | |
| | | executor.execute(() -> { |
| | | try { |
| | | futuresApi.cancelPriceTriggeredOrderList(SETTLE, contract); |
| | | log.info("[TradeExec] 已清除所有止盈止损单"); |
| | | log.info("[TradeExec] 已清除所有止盈止损条件单"); |
| | | } catch (Exception e) { |
| | | log.error("[TradeExec] 清除止盈止损单失败", e); |
| | | log.error("[TradeExec] 清除止盈止损条件单失败", e); |
| | | } |
| | | }); |
| | | } |
| | |
| | | |
| | | @PostConstruct |
| | | public void init() { |
| | | log.info("[GateMgr] init..."); |
| | | log.info("[管理器] 开始初始化..."); |
| | | |
| | | try { |
| | | config = GateConfig.builder() |
| | |
| | | wsClient.addChannelHandler(new PositionClosesChannelHandler( |
| | | config.getApiKey(), config.getApiSecret(), config.getContract(), gridTradeService)); |
| | | wsClient.init(); |
| | | log.info("[GateMgr] ws connected, 3 handlers registered"); |
| | | log.info("[管理器] WS已连接, 已注册 3 个频道处理器"); |
| | | |
| | | gridTradeService.startGrid(); |
| | | } catch (Exception e) { |
| | | log.error("[GateMgr] init fail", e); |
| | | log.error("[管理器] 初始化失败", e); |
| | | } |
| | | } |
| | | |
| | | @PreDestroy |
| | | public void destroy() { |
| | | log.info("[GateMgr] destroy..."); |
| | | log.info("[管理器] 开始销毁..."); |
| | | if (gridTradeService != null) { |
| | | gridTradeService.stopGrid(); |
| | | } |
| | | if (wsClient != null) { |
| | | wsClient.destroy(); |
| | | } |
| | | log.info("[GateMgr] destroyed"); |
| | | log.info("[管理器] 销毁完成"); |
| | | } |
| | | |
| | | public GateKlineWebSocketClient getKlineWebSocketClient() { return wsClient; } |
| | |
| | | Spring @PostConstruct |
| | | → GateConfig.builder()...build() |
| | | → GateGridTradeService(config) |
| | | → init(): 查ID → 查账户切持仓 → 清旧条件单 → 设杠杆 |
| | | → init(): 查ID → 查账户切持仓 → 清旧条件单 → 平已有仓位 → 设杠杆 |
| | | → GateKlineWebSocketClient(config.getWsUrl()) |
| | | → addChannelHandler x3 → init() → connect() |
| | | → onOpen: handlers依次subscribe → sendPing |
| | |
| | | |------|-----|------| |
| | | | 获取用户ID | `GET /account/detail` | `AccountApi.getAccountDetail()` | |
| | | | 切持仓模式 | `POST /futures/usdt/set_position_mode` | `FuturesApi.setPositionMode()` | |
| | | | 查仓位 | `GET /futures/usdt/positions` | `FuturesApi.listPositions()` | |
| | | | 市价平仓 | `POST /futures/usdt/orders` (reduce_only, IOC) | `FuturesApi.createFuturesOrder()` | |
| | | | 设杠杆 | `POST /futures/usdt/positions/{contract}/leverage` | `FuturesApi.updateContractPositionLeverageCall()` | |
| | | | 查账户 | `GET /futures/usdt/accounts` | `FuturesApi.listFuturesAccounts()` | |
| | | | 清除条件单 | `DELETE /futures/usdt/price_orders` | `FuturesApi.cancelPriceTriggeredOrderList()` | |
| | | | 市价单 | `POST /futures/usdt/orders` (price=0, IOC) | `FuturesApi.createFuturesOrder()` | |
| | | | 条件单 | `POST /futures/usdt/price_orders` | `FuturesApi.createPriceTriggeredOrder()` | |
| | | |
| | | --- |
| | | **初始化顺序** (`init()`): |
| | | ``` |
| | | 1. 获取用户 ID |
| | | 2. 查账户 → 如需要切持仓模式 |
| | | 3. 清除旧的止盈止损条件单 |
| | | 4. 市价平掉当前合约所有已有仓位(确保从零持仓开始) |
| | | 5. 设杠杆 |
| | | 6. 打印账户余额 |
| | | ``` |
| | | |
| | | ## GateWebSocketClientMain |
| | | --- |
| | | |
| | | 独立 `main()` 方法入口,通过 Spring XML 上下文启动,运行后手动关闭。 |
| | | |
| | |
| | | long timeSec = System.currentTimeMillis() / 1000; |
| | | JSONObject msg = buildAuthRequest("subscribe", buildUid(), timeSec); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] subscribed, contract:{}", channelName, contract); |
| | | log.info("[{}] 订阅成功, 合约:{}", channelName, contract); |
| | | } |
| | | |
| | | /** |
| | |
| | | auth.put("SIGN", hs512Sign("unsubscribe", timeSec)); |
| | | msg.put("auth", auth); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] unsubscribed, contract:{}", channelName, contract); |
| | | log.info("[{}] 取消订阅成功, 合约:{}", channelName, contract); |
| | | } |
| | | |
| | | protected GateGridTradeService getGridTradeService() { return gridTradeService; } |
| | |
| | | } |
| | | return hex.toString(); |
| | | } catch (Exception e) { |
| | | log.error("[{}] sign fail", channelName, e); |
| | | log.error("[{}] 签名计算失败", channelName, e); |
| | | return ""; |
| | | } |
| | | } |
| | |
| | | payload.add(contract); |
| | | msg.put("payload", payload); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] subscribed, contract:{}, interval:{}", CHANNEL_NAME, contract, INTERVAL); |
| | | log.info("[{}] 订阅成功, 合约:{}, 周期:{}", CHANNEL_NAME, contract, INTERVAL); |
| | | } |
| | | |
| | | @Override |
| | |
| | | payload.add(contract); |
| | | msg.put("payload", payload); |
| | | ws.send(msg.toJSONString()); |
| | | log.info("[{}] unsubscribed", CHANNEL_NAME); |
| | | log.info("[{}] 取消订阅成功", CHANNEL_NAME); |
| | | } |
| | | |
| | | @Override |
| | |
| | | if (!CHANNEL_NAME.equals(response.getString("channel"))) return false; |
| | | try { |
| | | JSONArray resultArray = response.getJSONArray("result"); |
| | | if (resultArray == null || resultArray.isEmpty()) { log.warn("[{}] empty", CHANNEL_NAME); return true; } |
| | | if (resultArray == null || resultArray.isEmpty()) { log.warn("[{}] 数据为空", CHANNEL_NAME); return true; } |
| | | JSONObject data = resultArray.getJSONObject(0); |
| | | BigDecimal closePx = new BigDecimal(data.getString("c")); |
| | | |
| | | log.info("========== Gate K线 =========="); |
| | | log.info("n:{} t:{} o:{} h:{} l:{} c:{} v:{} a:{} w:{}", |
| | | data.getString("n"), DateUtil.TimeStampToDateTime(data.getLong("t")), |
| | | log.info("========== Gate K线数据 =========="); |
| | | log.info("名称: {} 时间: {}", data.getString("n"), DateUtil.TimeStampToDateTime(data.getLong("t"))); |
| | | log.info("开盘: {} 最高: {} 最低: {} 收盘: {} 成交量: {} 成交额: {} 已完结: {}", |
| | | data.getString("o"), data.getString("h"), data.getString("l"), |
| | | data.getString("c"), data.getString("v"), data.getString("a"), |
| | | data.getBooleanValue("w")); |
| | | log.info("=============================="); |
| | | log.info("=================================="); |
| | | |
| | | if (gridTradeService != null) gridTradeService.onKline(closePx); |
| | | } catch (Exception e) { log.error("[{}] handle fail", CHANNEL_NAME, e); } |
| | | } catch (Exception e) { log.error("[{}] 处理数据失败", CHANNEL_NAME, e); } |
| | | return true; |
| | | } |
| | | } |
| | |
| | | if (!getContract().equals(item.getString("contract"))) continue; |
| | | BigDecimal pnl = new BigDecimal(item.getString("pnl")); |
| | | String side = item.getString("side"); |
| | | log.info("[{}] side:{}, pnl:{}", CHANNEL_NAME, side, pnl); |
| | | log.info("[{}] 平仓更新, 方向:{}, 盈亏:{}", CHANNEL_NAME, side, pnl); |
| | | if (getGridTradeService() != null) { |
| | | getGridTradeService().onPositionClose(getContract(), side, pnl); |
| | | } |
| | | } |
| | | } catch (Exception e) { log.error("[{}] handle fail", CHANNEL_NAME, e); } |
| | | } catch (Exception e) { log.error("[{}] 处理数据失败", CHANNEL_NAME, e); } |
| | | return true; |
| | | } |
| | | } |
| | |
| | | String mode = pos.getString("mode"); |
| | | BigDecimal size = new BigDecimal(pos.getString("size")); |
| | | BigDecimal entryPrice = new BigDecimal(pos.getString("entry_price")); |
| | | log.info("[{}] mode:{}, size:{}, entry:{}", CHANNEL_NAME, mode, size, entryPrice); |
| | | log.info("[{}] 持仓更新, 模式:{}, 数量:{}, 入场价:{}", CHANNEL_NAME, mode, size, entryPrice); |
| | | if (getGridTradeService() != null) { |
| | | getGridTradeService().onPositionUpdate(getContract(), mode, size, entryPrice); |
| | | } |
| | | } |
| | | } catch (Exception e) { log.error("[{}] handle fail", CHANNEL_NAME, e); } |
| | | } catch (Exception e) { log.error("[{}] 处理数据失败", CHANNEL_NAME, e); } |
| | | return true; |
| | | } |
| | | } |