From 5ae24effd360f2a6335045ccb40957474949ca40 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Wed, 24 Jun 2026 14:39:52 +0800
Subject: [PATCH] feat(okx): 添加OKX私有频道WebSocket处理器和API文档
---
src/main/java/com/xcong/excoin/modules/okxApi/OkxConfig.java | 162 +
src/main/java/com/xcong/excoin/modules/okxApi/OkxGridTradeService.java | 1224 ++++++++++++++++
src/main/java/com/xcong/excoin/modules/okxApi/OkxWebSocketClientMain.java | 63
src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/OkxChannelHandler.java | 102 +
.codebuddy/skills/okx-api/references/api_details.md | 276 +++
src/main/java/com/xcong/excoin/modules/okxApi/GridElement.java | 17
src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/MarkPriceOkxChannelHandler.java | 188 ++
src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/AbstractOkxPrivateChannelHandler.java | 160 ++
.codebuddy/skills/okx-api/SKILL.md | 87 +
src/main/java/com/xcong/excoin/modules/okxApi/TraderParam.java | 2
.codebuddy/settings.local.json | 5
src/main/java/com/xcong/excoin/modules/okxApi/OkxWebSocketClientManager.java | 117 +
/dev/null | 38
src/main/java/com/xcong/excoin/modules/okxApi/OkxKlineWebSocketClient.java | 753 ++++++++++
src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/OrderAlgoOkxChannelHandler.java | 230 +++
src/main/java/com/xcong/excoin/modules/okxApi/OkxTradeExecutor.java | 597 ++++++++
src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/CandlestickOkxChannelHandler.java | 195 ++
src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/PositionsOkxChannelHandler.java | 140 +
18 files changed, 4,245 insertions(+), 111 deletions(-)
diff --git a/.codebuddy/settings.local.json b/.codebuddy/settings.local.json
new file mode 100644
index 0000000..1c6d383
--- /dev/null
+++ b/.codebuddy/settings.local.json
@@ -0,0 +1,5 @@
+{
+ "enabledPlugins": {
+ "skill-creator@cb_teams_marketplace": true
+ }
+}
\ No newline at end of file
diff --git a/.codebuddy/skills/okx-api/SKILL.md b/.codebuddy/skills/okx-api/SKILL.md
new file mode 100644
index 0000000..5d2fd0b
--- /dev/null
+++ b/.codebuddy/skills/okx-api/SKILL.md
@@ -0,0 +1,87 @@
+---
+name: okx-api
+description: >
+ This skill provides comprehensive reference for developing applications with the OKX V5 API.
+ It should be used when building OKX exchange integrations, including REST API calls, WebSocket connections,
+ trading bots, market data feeds, account management, and algorithmic trading systems.
+ Trigger when the user mentions OKX, okx API, okx trading, okx websocket, cryptocurrency exchange integration,
+ or requests to build trading/quant applications for OKX.
+---
+
+# OKX V5 API
+
+## Overview
+
+Act as an OKX V5 API expert. Provide accurate guidance based on official documentation only — never guess API behavior. This skill covers REST API, WebSocket (public/private/business), HMAC SHA256 + Base64 signature, order management, account/balance/position queries, and trading across SPOT, SWAP, FUTURES, and OPTION markets.
+
+## When to Use This Skill
+
+Use this skill whenever the user requests:
+- OKX V5 API integration or development
+- Building trading bots, market data feeds, or quant strategies for OKX
+- WebSocket subscription (public/private/business channels)
+- API signature generation (HMAC SHA256 + Base64)
+- Order placement, modification, or cancellation
+- Account, balance, or position queries
+- Demo trading vs. live trading configuration
+- Rate limiting, connection management, or error code analysis
+
+## Core Principles
+
+1. Always reference official documentation; never guess endpoints or parameters.
+2. Provide code examples in Java, Python, or Go based on user preference.
+3. Clearly annotate request path, HTTP method, headers, and body.
+4. Distinguish between REST and WebSocket approaches.
+5. Distinguish between demo trading (x-simulated-trading: 1) and live trading.
+6. All signatures follow: `timestamp + method + requestPath + body` → HMAC SHA256 → Base64.
+
+## Quick Reference
+
+### REST API Headers
+
+```
+OK-ACCESS-KEY: <api_key>
+OK-ACCESS-SIGN: <base64_signature>
+OK-ACCESS-TIMESTAMP: <iso_timestamp>
+OK-ACCESS-PASSPHRASE: <passphrase>
+```
+
+For demo/simulated trading, add:
+```
+x-simulated-trading: 1
+```
+
+### WebSocket Endpoints
+
+| Channel | URL |
+|-----------|----------------------------------------------|
+| Public | wss://ws.okx.com:8443/ws/v5/public |
+| Private | wss://ws.okx.com:8443/ws/v5/private |
+| Business | wss://ws.okx.com:8443/ws/v5/business |
+
+### Connection Health
+
+Send a ping if no message is sent within 30 seconds. Wait for pong; otherwise reconnect.
+
+### Common Endpoints
+
+| Method | Path | Description |
+|--------|----------------------------------|----------------------|
+| GET | /api/v5/account/balance | Account balance |
+| GET | /api/v5/account/positions | Account positions |
+| GET | /api/v5/account/instruments | Account instruments |
+
+## Output Template
+
+When answering API-related questions, structure the response as:
+1. **接口说明** - endpoint description and purpose
+2. **请求示例** - full request example with headers and body
+3. **返回字段** - response field documentation
+4. **最佳实践** - best practices
+5. **异常处理** - error handling guidance
+6. **性能优化** - performance optimization tips
+
+## Resources
+
+### references/api_details.md
+Comprehensive API reference covering authentication with code examples (Java/Python/Go), full REST endpoint catalog (Account/Trade/Market Data), WebSocket channels (Public/Private/Business), error codes, rate limits, and best practices. Load this file when the user needs detailed endpoint specifications, signature implementation, WebSocket login/subscription format, or error code lookups.
diff --git a/.codebuddy/skills/okx-api/references/api_details.md b/.codebuddy/skills/okx-api/references/api_details.md
new file mode 100644
index 0000000..14f1be9
--- /dev/null
+++ b/.codebuddy/skills/okx-api/references/api_details.md
@@ -0,0 +1,276 @@
+# OKX V5 API Reference Documentation
+
+## Authentication
+
+### Signature Generation (HMAC SHA256 + Base64)
+
+The signature algorithm:
+
+```
+signature = Base64(HMAC_SHA256(secret_key, timestamp + method + request_path + body))
+```
+
+**Java Example:**
+```java
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.Base64;
+
+public static String sign(String timestamp, String method,
+ String requestPath, String body, String secretKey) {
+ try {
+ String preHash = timestamp + method + requestPath + body;
+ Mac mac = Mac.getInstance("HmacSHA256");
+ SecretKeySpec spec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
+ mac.init(spec);
+ return Base64.getEncoder().encodeToString(mac.doFinal(preHash.getBytes()));
+ } catch (Exception e) {
+ throw new RuntimeException("Signature failed", e);
+ }
+}
+```
+
+**Python Example:**
+```python
+import hmac, base64, hashlib
+
+def sign(timestamp, method, request_path, body, secret_key):
+ pre_hash = timestamp + method + request_path + body
+ signature = hmac.new(
+ secret_key.encode(), pre_hash.encode(), hashlib.sha256
+ ).digest()
+ return base64.b64encode(signature).decode()
+```
+
+**Go Example:**
+```go
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+)
+
+func sign(timestamp, method, requestPath, body, secretKey string) string {
+ preHash := timestamp + method + requestPath + body
+ mac := hmac.New(sha256.New, []byte(secretKey))
+ mac.Write([]byte(preHash))
+ return base64.StdEncoding.EncodeToString(mac.Sum(nil))
+}
+```
+
+### Required Headers
+
+All authenticated REST requests MUST include:
+
+| Header | Description |
+|-------------------------|-----------------------------------------------|
+| OK-ACCESS-KEY | API key |
+| OK-ACCESS-SIGN | Base64-encoded HMAC SHA256 signature |
+| OK-ACCESS-TIMESTAMP | ISO 8601 timestamp (e.g., 2023-01-01T00:00:00.000Z) |
+| OK-ACCESS-PASSPHRASE | API passphrase |
+| x-simulated-trading | Set to "1" for demo trading (optional) |
+
+## Market Types
+
+| Type | Description |
+|-----------|----------------------|
+| SPOT | Spot trading |
+| SWAP | Perpetual swap |
+| FUTURES | Futures contracts |
+| OPTION | Options trading |
+
+## WebSocket
+
+### Public Channel
+
+```
+wss://ws.okx.com:8443/ws/v5/public
+```
+
+Access market data without authentication: tickers, order books, trades, mark prices, funding rates, etc.
+
+### Private Channel
+
+```
+wss://ws.okx.com:8443/ws/v5/private
+```
+
+Requires authentication (login). Access account data, order updates, position changes, balance updates.
+
+### Business Channel
+
+```
+wss://ws.okx.com:8443/ws/v5/business
+```
+
+Requires authentication. Access business-specific channels (requires application).
+
+### Ping/Pong
+
+- Client must send a ping message if no message has been sent in the last 30 seconds
+- Server responds with pong
+- If no pong received, client should reconnect
+
+```json
+{"op": "ping"}
+```
+
+```json
+{"op": "pong"}
+```
+
+### Subscription Example
+
+```json
+{
+ "op": "subscribe",
+ "args": [
+ {
+ "channel": "tickers",
+ "instId": "BTC-USDT"
+ }
+ ]
+}
+```
+
+### Login (Private Channel)
+
+```json
+{
+ "op": "login",
+ "args": [
+ {
+ "apiKey": "YOUR_API_KEY",
+ "passphrase": "YOUR_PASSPHRASE",
+ "timestamp": "2023-01-01T00:00:00.000Z",
+ "sign": "BASE64_SIGNATURE"
+ }
+ ]
+}
+```
+
+WebSocket login signature: `timestamp + "GET" + "/users/self/verify"` (no body).
+
+## Common REST Endpoints
+
+### Account
+
+| Method | Path | Description |
+|--------|------------------------------------|--------------------------|
+| GET | /api/v5/account/balance | Get account balance |
+| GET | /api/v5/account/positions | Get positions |
+| GET | /api/v5/account/positions-history | Get positions history |
+| GET | /api/v5/account/bills | Get bills (transaction history) |
+| GET | /api/v5/account/config | Get account configuration |
+| GET | /api/v5/account/instruments | Get instruments |
+| POST | /api/v5/account/set-position-mode | Set position mode |
+| GET | /api/v5/account/leverage-info | Get leverage |
+| POST | /api/v5/account/set-leverage | Set leverage |
+
+### Trade
+
+| Method | Path | Description |
+|--------|------------------------------|---------------------|
+| POST | /api/v5/trade/order | Place order |
+| POST | /api/v5/trade/amend-order | Amend order |
+| POST | /api/v5/trade/cancel-order | Cancel order |
+| POST | /api/v5/trade/batch-orders | Place batch orders |
+| POST | /api/v5/trade/batch-amend-orders | Amend batch orders |
+| POST | /api/v5/trade/batch-cancel-orders | Cancel batch orders |
+| POST | /api/v5/trade/close-position | Close position |
+| GET | /api/v5/trade/order | Get order details |
+| GET | /api/v5/trade/orders-pending | Get pending orders |
+| GET | /api/v5/trade/orders-history | Get order history |
+| GET | /api/v5/trade/fills | Get transaction details |
+| GET | /api/v5/trade/fills-history | Get transaction history |
+
+### Market Data
+
+| Method | Path | Description |
+|--------|------------------------------------|--------------------------|
+| GET | /api/v5/market/tickers | Get all tickers |
+| GET | /api/v5/market/ticker | Get single ticker |
+| GET | /api/v5/market/books | Get order book |
+| GET | /api/v5/market/candles | Get candlestick (K-line) |
+| GET | /api/v5/market/history-candles | Get historical candles |
+| GET | /api/v5/market/trades | Get recent trades |
+| GET | /api/v5/market/history-trades | Get historical trades |
+| GET | /api/v5/market/mark-price | Get mark price |
+| GET | /api/v5/market/funding-rate | Get funding rate |
+
+## Order Placement Example
+
+### REST - Place Limit Order
+
+```
+POST /api/v5/trade/order
+```
+
+**Request Body:**
+```json
+{
+ "instId": "BTC-USDT-SWAP",
+ "tdMode": "cross",
+ "side": "buy",
+ "ordType": "limit",
+ "sz": "1",
+ "px": "20000"
+}
+```
+
+**Response:**
+```json
+{
+ "code": "0",
+ "msg": "",
+ "data": [
+ {
+ "clOrdId": "",
+ "ordId": "123456",
+ "tag": "",
+ "sCode": "0",
+ "sMsg": ""
+ }
+ ]
+}
+```
+
+## Rate Limits
+
+- REST: 20 requests / 2 seconds per endpoint (varies by endpoint)
+- WebSocket: 1 subscription request / second
+- Public WebSocket data: throttled per channel
+- Always implement exponential backoff on 429 responses
+
+## Error Codes
+
+| Code | Meaning |
+|---------|--------------------------------|
+| 0 | Success |
+| 50001 | Request timeout |
+| 50002 | Service unavailable |
+| 50004 | Rate limit exceeded |
+| 50011 | API key does not exist |
+| 50012 | API key has expired |
+| 50013 | Invalid sign |
+| 50014 | Invalid passphrase |
+| 50015 | Invalid IP |
+| 50016 | Permission denied |
+| 50017 | Withdrawal address not whitelisted |
+| 50100 | Unsupported operation |
+| 51000 | Parameter error |
+| 51001 | Instrument ID does not exist |
+| 51006 | Order does not exist |
+| 51400 | Order cancellation failed |
+| 51500 | Insufficient balance |
+
+## Best Practices
+
+1. **Timestamp synchronization**: Ensure server time is synced via NTP; timestamp must be within 30 seconds of OKX server time
+2. **Exponential backoff**: On rate limit (429) or 5xx errors, implement exponential backoff starting at 1s
+3. **WebSocket reconnection**: Automatically reconnect on disconnect with backoff; resubscribe all channels
+4. **Demo first**: Always test on demo trading (x-simulated-trading: 1) before live
+5. **Idempotency**: Use `clOrdId` to prevent duplicate orders
+6. **Error logging**: Log full error responses including code, msg, and data fields
+7. **Connection pooling**: Reuse HTTP connections for REST calls
+8. **WebSocket heartbeat**: Maintain 30-second ping/pong cycle
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/Example.java b/src/main/java/com/xcong/excoin/modules/gateApi/Example.java
deleted file mode 100644
index c8f8ff3..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/Example.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.xcong.excoin.modules.gateApi;
-
-// Import classes:
-import io.gate.gateapi.ApiClient;
-import io.gate.gateapi.ApiException;
-import io.gate.gateapi.Configuration;
-import io.gate.gateapi.GateApiException;
-import io.gate.gateapi.api.FuturesApi;
-import io.gate.gateapi.auth.*;
-import io.gate.gateapi.models.*;
-import io.gate.gateapi.api.AccountApi;
-import lombok.extern.slf4j.Slf4j;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-
-
-/**
- * Gate SDK API 使用示例。
- *
- * <h3>演示内容</h3>
- * <ol>
- * <li>通过 API Key/Secret 初始化客户端</li>
- * <li>查询期货账户余额</li>
- * <li>设置双向持仓模式</li>
- * <li>设置杠杆倍数</li>
- * <li>切换保证金模式(全仓/逐仓)</li>
- * </ol>
- *
- * <h3>注意</h3>
- * 此文件仅作为 SDK 用法参考,不影响实际策略运行。
- * 当前策略中相同功能的实现在 {@code GateGridTradeService.init()} 中。
- *
- * @author Administrator
- */
-@Slf4j
-public class Example {
- /**
- * 示例入口:依次执行查询账户 → 设置双向持仓 → 设杠杆 → 切换保证金模式。
- */
- public static void main(String[] args) {
- ApiClient defaultClient = Configuration.getDefaultApiClient();
- defaultClient.setBasePath("https://api-testnet.gateapi.io/api/v4");
-// defaultClient.setBasePath("https://api.gateio.ws/api/v4");
-
- // Configure APIv4 authorization: apiv4
- defaultClient.setApiKeySecret("d90ca272391992b8e74f8f92cedb21ec", "1861e4f52de4bb53369ea3208d9ede38ece4777368030f96c77d27934c46c274");
-
- try {
-
- String CONTRACT = "ETH_USDT";
- String SETTLE = "usdt";
- //保证金模式 isolated/cross
- String MARGINMODE = "CROSS";
- String LEVERAGE = "100";
- FuturesApi futuresApi = new FuturesApi(defaultClient);
- FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
- log.info("[Gate] 初始本金: {} USDT", account.getTotal());
-
- //设置持仓模式为双向持仓
- Boolean inDualMode = account.getInDualMode();
- if (!inDualMode) {
- try {
- futuresApi.setDualModeCall(SETTLE,true,null).execute();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- try {
- futuresApi.updateDualModePositionLeverageCall(
- SETTLE, CONTRACT, LEVERAGE,
- null, null).execute();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- if (!MARGINMODE.equals(account.getMarginMode())) {
-
- UpdateDualCompPositionCrossModeRequest updateDualCompPositionCrossModeRequest = new UpdateDualCompPositionCrossModeRequest();
- updateDualCompPositionCrossModeRequest.setMode(MARGINMODE);
- updateDualCompPositionCrossModeRequest.setContract(CONTRACT);
- try {
- futuresApi.updateDualCompPositionCrossModeCall(SETTLE, updateDualCompPositionCrossModeRequest, null).execute();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- } catch (GateApiException e) {
- System.err.println(String.format("Gate api exception, label: %s, message: %s", e.getErrorLabel(), e.getMessage()));
- e.printStackTrace();
- } catch (ApiException e) {
- System.err.println("Exception when calling AccountApi#getAccountDetail");
- e.printStackTrace();
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
deleted file mode 100644
index 1ea2c85..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateGridTradeService.java
+++ /dev/null
@@ -1,1690 +0,0 @@
-package com.xcong.excoin.modules.gateApi;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.StrUtil;
-import com.xcong.excoin.utils.dingtalk.DingTalkUtils;
-import io.gate.gateapi.ApiClient;
-import io.gate.gateapi.ApiException;
-import io.gate.gateapi.GateApiException;
-import io.gate.gateapi.api.AccountApi;
-import io.gate.gateapi.api.FuturesApi;
-import io.gate.gateapi.models.*;
-import lombok.extern.slf4j.Slf4j;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.*;
-
-import com.xcong.excoin.modules.gateApi.wsHandler.handler.CandlestickChannelHandler;
-import com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionClosesChannelHandler;
-import com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionsChannelHandler;
-
-/**
- * 网格交易策略引擎 — 多空对冲网格。
- *
- * <h3>策略原理</h3>
- * 以空仓基底入场价(shortBaseEntryPrice)为价格基准,向上/向下各生成一个价格网格队列。
- * 价格触发网格层级时挂条件单,成交后自动挂止盈单。每笔止盈盈利 = step - minTick。
- *
- * <h3>完整生命周期</h3>
- * <pre>
- * 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>
- * <pre>
- * onPositionUpdate() 中仓位均价变化后:
- * longEntryPrice ↑ → 取消 高于 longEntryPrice 的空仓挂单(避免逆势空单)
- * shortEntryPrice ↓ → 取消 低于 shortEntryPrice 的多仓挂单(避免逆势多单)
- * </pre>
- *
- * <h3>关键公式</h3>
- * <pre>
- * step = shortBaseEntryPrice × gridRate ← 网格绝对步长
- * minTick = 10^(-priceScale) ← 交易所最小价格单位
- * 多止盈 = gridPrice + (step - minTick) ← 多仓止盈价
- * 空止盈 = gridPrice - (step - minTick) ← 空仓止盈价
- * 单笔盈利 = (step - minTick) × contractMultiplier × quantity ← USDT
- * </pre>
- *
- * <h3>线程模型</h3>
- * 所有 WS 回调(onKline/onPositionUpdate/onOrderUpdate 等)在 WS 回调线程中串行执行。
- * 下单/撤单操作提交到 GateTradeExecutor 的单线程池异步执行,避免阻塞 WS 线程。
- * stopGrid() 会将 state 设为 STOPPED,后续所有 WS 回调直接返回不再处理。
- *
- * @author Administrator
- */
-@Slf4j
-public class GateGridTradeService {
-
- public enum StrategyState {
- WAITING_KLINE, OPENING, ACTIVE, STOPPED
- }
-
- /**
- * 止盈条件单 order_type:仓位计划止盈止损 — 平多仓(支持部分平仓,size<0)。
- * 注意:不能用 close-long-position(仅支持全平且双仓需 auto_size),
- * 必须用 plan-close-long-position 以支持指定张数部分平仓。
- */
- private static final String ORDER_TYPE_CLOSE_LONG = "plan-close-long-position";
- /**
- * 止盈条件单 order_type:仓位计划止盈止损 — 平空仓(支持部分平仓,size>0)。
- * 注意:不能用 close-short-position(仅支持全平且双仓需 auto_size),
- * 必须用 plan-close-short-position 以支持指定张数部分平仓。
- */
- private static final String ORDER_TYPE_CLOSE_SHORT = "plan-close-short-position";
-
- private final GateConfig config;
- private final GateTradeExecutor executor;
- private final FuturesApi futuresApi;
- private static final String SETTLE = "usdt";
-
- private volatile StrategyState state = StrategyState.WAITING_KLINE;
-
- /** 空仓价格队列,降序排列(大→小),容量 gridQueueSize */
- private final List<BigDecimal> shortPriceQueue = Collections.synchronizedList(new ArrayList<>());
- /** 多仓价格队列,升序排列(小→大),容量 gridQueueSize */
- private final List<BigDecimal> longPriceQueue = Collections.synchronizedList(new ArrayList<>());
- private final List<BigDecimal> totalLongPriceQueue = Collections.synchronizedList(new ArrayList<>());
- private final List<BigDecimal> totalShortPriceQueue = Collections.synchronizedList(new ArrayList<>());
-
- /** 当前多仓条件单映射:订单ID → 止盈价格,订单成交后通过订单订阅推送匹配止盈 */
- private final Map<String, BigDecimal> currentLongOrderIds = Collections.synchronizedMap(new LinkedHashMap<>());
- /** 当前空仓条件单映射:订单ID → 止盈价格,订单成交后通过订单订阅推送匹配止盈 */
- private final Map<String, BigDecimal> currentShortOrderIds = Collections.synchronizedMap(new LinkedHashMap<>());
-
- /** 基底空头入场价 */
- private BigDecimal shortBaseEntryPrice;
- /** 基底多头入场价(仅记录,当前未被业务逻辑消费,保留以备后续使用) */
- private BigDecimal longBaseEntryPrice;
- /** 基底多头是否已开 */
- private volatile boolean baseLongOpened = false;
- /** 基底空头是否已开 */
- private volatile boolean baseShortOpened = false;
-
- /** 空头是否活跃(有仓位) */
- private volatile boolean shortActive = false;
- /** 多头是否活跃(有仓位) */
- private volatile boolean longActive = false;
-
- /** 多头累计止损张数(加仓订单成交后归零) */
- private volatile int accumulatedLongLossCount = 0;
- /** 空头累计止损张数(加仓订单成交后归零) */
- private volatile int accumulatedShortLossCount = 0;
-
- private volatile BigDecimal lastKlinePrice;
- private volatile BigDecimal markPrice = BigDecimal.ZERO;
- private volatile BigDecimal cumulativePnl = BigDecimal.ZERO;
- private volatile BigDecimal unrealizedPnl = BigDecimal.ZERO;
- private volatile BigDecimal longEntryPrice = BigDecimal.ZERO;
- private volatile BigDecimal shortEntryPrice = BigDecimal.ZERO;
- private volatile BigDecimal longPositionSize = BigDecimal.ZERO;
- private volatile BigDecimal shortPositionSize = BigDecimal.ZERO;
- private Long userId;
- private volatile BigDecimal initialPrincipal = BigDecimal.ZERO;
- private volatile GateKlineWebSocketClient wsClient;
-
- public GateGridTradeService(GateConfig config) {
- this.config = config;
- ApiClient apiClient = new ApiClient();
- apiClient.setBasePath(config.getRestBasePath());
- apiClient.setApiKeySecret(config.getApiKey(), config.getApiSecret());
- this.futuresApi = new FuturesApi(apiClient);
- this.executor = new GateTradeExecutor(apiClient, config.getContract());
- }
-
- // ---- 初始化 ----
-
- /**
- * 初始化策略环境。
- *
- * <h3>执行顺序</h3>
- * <ol>
- * <li>获取用户 ID(用于私有频道订阅 payload)</li>
- * <li>获取账户信息 → 记录初始本金</li>
- * <li>如需要,切换为双向持仓模式</li>
- * <li>如需要,调整持仓模式(single/dual)</li>
- * <li>清除旧的止盈止损条件单</li>
- * <li>平掉所有已有仓位</li>
- * <li>设置杠杆倍数</li>
- * </ol>
- */
- public void init() {
- try {
- ApiClient detailClient = new ApiClient();
- detailClient.setBasePath(config.getRestBasePath());
- detailClient.setApiKeySecret(config.getApiKey(), config.getApiSecret());
- AccountDetail detail = new AccountApi(detailClient).getAccountDetail();
- this.userId = detail.getUserId();
- log.info("[Gate] 用户ID: {}", userId);
-
- FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
- this.initialPrincipal = new BigDecimal(account.getTotal());
- log.info("[Gate] 初始本金: {} USDT", initialPrincipal);
-
- futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
- log.info("[Gate] 旧条件单已清除");
- closeExistingPositions();
-
- //设置持仓模式为双向持仓
- Boolean inDualMode = account.getInDualMode();
- if (!inDualMode) {
- try {
- futuresApi.setDualModeCall(SETTLE,true,null).execute();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- try {
- futuresApi.updateDualModePositionLeverageCall(
- SETTLE, config.getContract(), config.getLeverage(),
- null, null).execute();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- if (!config.getMarginMode().equals(account.getMarginMode())) {
-
- UpdateDualCompPositionCrossModeRequest updateDualCompPositionCrossModeRequest = new UpdateDualCompPositionCrossModeRequest();
- updateDualCompPositionCrossModeRequest.setMode(config.getMarginMode());
- updateDualCompPositionCrossModeRequest.setContract(config.getContract());
- try {
- futuresApi.updateDualCompPositionCrossModeCall(SETTLE, updateDualCompPositionCrossModeRequest, null).execute();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- log.info("[Gate] 持仓模式: {} 余额: {}", config.getPositionMode(), account.getAvailable());
- log.info("[Gate] 杠杆: {}x {}", config.getLeverage(), config.getMarginMode());
- } catch (GateApiException e) {
- log.error("[Gate] 初始化失败, label:{}, msg:{}", e.getErrorLabel(), e.getMessage());
- } catch (ApiException e) {
- log.error("[Gate] 初始化失败, code:{}", e.getCode());
- }
- }
-
- /**
- * 平掉当前合约的所有已有仓位。
- *
- * <h3>平仓策略</h3>
- * <ul>
- * <li>单向持仓:size=相反数,reduceOnly=true,市价 IOC 平仓</li>
- * <li>双向持仓:size=0,close=false,autoSize=LONG/SHORT,reduceOnly=true,市价 IOC 全平</li>
- * </ul>
- *
- * <h3>注意事项</h3>
- * 双向持仓模式下必须使用 autoSize 参数,不能直接传负数 size,
- * 否则 Gate API 会拒绝(双向模式下空头 size 为负是正常的持仓方向)。
- */
- private void closeExistingPositions() {
- try {
- 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));
- Position.ModeEnum mode = pos.getMode();
- FuturesOrder closeOrder = new FuturesOrder();
- closeOrder.setContract(config.getContract());
- closeOrder.setPrice("0");
- closeOrder.setTif(FuturesOrder.TifEnum.IOC);
- closeOrder.setReduceOnly(true);
- if (mode != null && mode.getValue() != null && mode.getValue().contains("dual")) {
- closeOrder.setSize("0");
- closeOrder.setClose(false);
- closeOrder.setAutoSize(size > 0 ? FuturesOrder.AutoSizeEnum.LONG : FuturesOrder.AutoSizeEnum.SHORT);
- } else {
- closeOrder.setSize(closeSize);
- }
- closeOrder.setText("t-grid-init-close");
- futuresApi.createFuturesOrder(SETTLE, closeOrder, null);
- log.info("[Gate] 平已有仓位, 方向:{}, size:{}, mode:{}", size > 0 ? "多" : "空", sizeStr, mode);
- }
- } catch (GateApiException e) {
- log.warn("[Gate] 平仓位失败, label:{}, msg:{}", e.getErrorLabel(), e.getMessage());
- } catch (Exception e) {
- log.warn("[Gate] 平仓位异常", e);
- }
- }
-
- // ---- 启动/停止 ----
-
- /**
- * 启动网格策略。重置所有状态变量和队列,进入 WAITING_KLINE 等待首根 K 线。
- * 仅当当前状态为 WAITING_KLINE 或 STOPPED 时才允许启动。
- */
- public void startGrid() {
- if (state != StrategyState.WAITING_KLINE && state != StrategyState.STOPPED) {
- log.warn("[Gate] 策略已在运行中, state:{}", state);
- return;
- }
- state = StrategyState.WAITING_KLINE;
- cumulativePnl = BigDecimal.ZERO;
- unrealizedPnl = BigDecimal.ZERO;
- markPrice = BigDecimal.ZERO;
- longEntryPrice = BigDecimal.ZERO;
- shortEntryPrice = BigDecimal.ZERO;
- longPositionSize = BigDecimal.ZERO;
- shortPositionSize = BigDecimal.ZERO;
- baseLongOpened = false;
- baseShortOpened = false;
- longActive = false;
- shortActive = false;
- accumulatedLongLossCount = 0;
- accumulatedShortLossCount = 0;
- shortPriceQueue.clear();
- longPriceQueue.clear();
- totalShortPriceQueue.clear();
- totalLongPriceQueue.clear();
- currentLongOrderIds.clear();
- currentShortOrderIds.clear();
- // 每次重启重新获取当前本金
- refreshInitialPrincipal();
-
- log.info("[Gate] 网格策略已启动, 当前本金: {} USDT", initialPrincipal);
- }
-
- /**
- * 重新获取当前账户权益作为初始本金。
- */
- private void refreshInitialPrincipal() {
- try {
- FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
- this.initialPrincipal = new BigDecimal(account.getTotal());
- } catch (Exception e) {
- log.warn("[Gate] 获取初始化本金失败,使用旧值: {}", initialPrincipal);
- }
- }
-
- /**
- * 停止网格策略。取消所有条件单 → 关闭交易线程池。
- * 状态设为 STOPPED,K 线回调将直接返回不再处理。
- */
- public void stopGrid() {
- state = StrategyState.STOPPED;
- executor.cancelAllPriceTriggeredOrders();
- closeExistingPositions();
- executor.shutdown();
- log.info("[Gate] 策略已停止, 累计盈亏: {}", cumulativePnl);
- }
-
- // ---- K线回调 ----
-
- /**
- * K 线回调入口。由 {@link CandlestickChannelHandler} 在收到 WebSocket K 线推送时调用。
- *
- * <h3>处理流程</h3>
- * <ol>
- * <li>更新 lastKlinePrice → 计算 unrealizedPnl(浮动盈亏)</li>
- * <li>STOPPED → 直接返回(仅保留盈亏更新)</li>
- * <li>WAITING_KLINE → 切换为 OPENING → 异步提交基底双开(开多+开空)</li>
- * <li>OPENING → 等待仓位推送回调生成队列,此处返回</li>
- * <li>ACTIVE → 执行 processShortGrid + processLongGrid</li>
- * </ol>
- *
- * <h3>注意</h3>
- * 基底双开下单提交到 GateTradeExecutor 的独立线程池中异步执行,
- * 成交状态由 onPositionUpdate 回调驱动,不阻塞 WS 回调线程。
- *
- * @param closePrice K 线收盘价(即当前最新成交价)
- */
- public void onKline(BigDecimal closePrice) {
- lastKlinePrice = closePrice;
-
- //初始化0位置的开仓,并且用空的开仓价格,作为价格基准来划分网格
- if (state == StrategyState.WAITING_KLINE) {
- if (wsClient == null || !wsClient.areAllSubscribed()) {
- return;
- }
-
- state = StrategyState.OPENING;
-
- String size = config.getBaseQuantity();
- log.info("[Gate] 首根K线到达,开基底仓位 多空各{}张...", size);
- executor.openLong(size, (orderId) -> {
- TraderParam baseLongTp = TraderParam.builder()
- .entryOrderId(orderId)
- .build();
- config.setBaseLongTraderParam(baseLongTp);
- }, null);
- executor.openShort(negate(size), (orderId) -> {
- TraderParam baseShortTp = TraderParam.builder()
- .entryOrderId(orderId)
- .build();
- config.setBaseShortTraderParam(baseShortTp);
- }, null);
- return;
- }
-
-
-// checkProfitAndReset();
-
-
- if (state == StrategyState.ACTIVE &&
- longActive == false &&
- longPositionSize.compareTo(BigDecimal.ZERO) == 0){
- processShortGrid(closePrice);
- }
-
-
- if (state == StrategyState.ACTIVE &&
- shortActive == false &&
- shortPositionSize.compareTo(BigDecimal.ZERO) == 0){
- processLongGrid(closePrice);
- }
- }
-
- /** Gate 永续合约 taker 费率 0.05% */
- private static final BigDecimal TAKER_FEE_RATE = new BigDecimal("0.0005");
- private void checkProfitAndReset() {
- try {
-
- BigDecimal target = initialPrincipal.add(config.getExpectedProfit());
-
- FuturesAccount account = futuresApi.listFuturesAccounts(SETTLE);
- BigDecimal unrealisedPnl = new BigDecimal(account.getCrossUnrealisedPnl());
- BigDecimal available = new BigDecimal(account.getCrossAvailable());
- BigDecimal totalEquity = unrealisedPnl.add(available);
-
- // 估算平仓手续费:(多仓张数+空仓张数) × 合约面值 × 当前价 × taker费率
- BigDecimal totalSize = longPositionSize.abs().add(shortPositionSize.abs());
- BigDecimal closeContractValue =
- totalSize.multiply(config.getContractMultiplier()).multiply(lastKlinePrice != null ? lastKlinePrice : BigDecimal.ZERO);
- BigDecimal estimatedFee = closeContractValue.multiply(TAKER_FEE_RATE);
- BigDecimal netEquity = totalEquity.subtract(estimatedFee);
- log.info("[Gate] 盈亏检查,总张数:{}, upl:{}, avail:{}, 合计:{}, 估手续费:{}, 净权益:{}, 目标:{}",
- totalSize,unrealisedPnl, available, totalEquity, estimatedFee, netEquity, target);
- if (netEquity.compareTo(target) > 0) {
- log.info("[Gate] 盈亏达标(净权益{}>目标{}),重置策略", netEquity, target);
- state = StrategyState.STOPPED;
- try {
- futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
- } catch (ApiException e) {
- e.printStackTrace();
- }
- closeExistingPositions();
- // 提交到 executor 末尾:单线程FIFO保证前面所有平仓/取消任务完成后才重置
- executor.submitTask(() -> {
- try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
- startGrid();
- });
- }
- } catch (Exception e) {
- log.warn("[Gate] 盈亏检查失败", e);
- }
- }
-
- // ---- 仓位推送回调 ----
-
- /**
- * 仓位推送回调。由 {@link PositionsChannelHandler} 在收到 WebSocket 仓位更新时调用。
- *
- * <h3>处理逻辑</h3>
- * <ul>
- * <li><b>有仓位 (size ≠ 0)</b>:
- * <ul>
- * <li>首次开仓(基底):标记 baseOpened=true,记录基底入场价,双基底都成交后生成网格队列</li>
- * <li>仓位净减少(size.abs() < 之前记录值):止盈平仓后 → 检查反向条件单条件 →
- * 满足时以 entryPrice ± step 为止盈价挂反向市价单(订单ID + 止盈价存入 Map)</li>
- * </ul>
- * </li>
- * <li><b>无仓位 (size = 0)</b>:清空活跃标记和持仓量</li>
- * <li><b>Map 截断</b>:currentLongOrderIds / currentShortOrderIds 超过 5 个时,
- * 从 LinkedHashMap 头部删除最旧条目,保留最新 5 个</li>
- * </ul>
- *
- * @param contract 合约名称
- * @param mode 持仓模式(DUAL_LONG / DUAL_SHORT)
- * @param size 持仓张数(多头为正、空头为负)
- * @param entryPrice 当前持仓加权均价(交易所计算)
- */
- public void onPositionUpdate(String contract, Position.ModeEnum mode, BigDecimal size,
- BigDecimal entryPrice) {
- if (state == StrategyState.STOPPED || state == StrategyState.WAITING_KLINE) {
- return;
- }
-
- boolean hasPosition = size.abs().compareTo(BigDecimal.ZERO) > 0;
- if (state == StrategyState.OPENING){
- if (Position.ModeEnum.DUAL_LONG == mode && hasPosition && !baseLongOpened) {
- longActive = true;
- longPositionSize = size;
- longEntryPrice = entryPrice;
- longBaseEntryPrice = entryPrice;
- baseLongOpened = true;
- log.info("[Gate] 基底多成交价: {}", longBaseEntryPrice);
- tryGenerateQueues();
- } else if (Position.ModeEnum.DUAL_SHORT == mode && hasPosition && !baseShortOpened) {
- shortActive = true;
- shortPositionSize = size.abs();
- shortEntryPrice = entryPrice;
- shortBaseEntryPrice = entryPrice;
- baseShortOpened = true;
- log.info("[Gate] 基底空成交价: {}", shortBaseEntryPrice);
- tryGenerateQueues();
- }
- }
-
- if (state == StrategyState.ACTIVE){
- if (Position.ModeEnum.DUAL_LONG == mode) {
- if (hasPosition) {
- longActive = true;
- longPositionSize = size;
- longEntryPrice = entryPrice;
- } else {
-
- log.info("[Gate-0]多仓: {}", shortBaseEntryPrice);
- longActive = false;
- longPositionSize = BigDecimal.ZERO;
- longEntryPrice = BigDecimal.ZERO;
- }
- } else if (Position.ModeEnum.DUAL_SHORT == mode) {
- if (hasPosition) {
- shortActive = true;
- shortPositionSize = size.abs();
- shortEntryPrice = entryPrice;
- } else {
-
- log.info("[Gate-0]空仓: {}", shortBaseEntryPrice);
- shortActive = false;
- shortPositionSize = BigDecimal.ZERO;
- shortEntryPrice = BigDecimal.ZERO;
- }
- }
- }
-
- if (state == StrategyState.ACTIVE && shortActive == false && longActive == false) {
- try {
- futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
- } catch (ApiException e) {
- e.printStackTrace();
- }
- closeExistingPositions();
-
- state = StrategyState.STOPPED;
- // 提交到 executor 末尾:单线程FIFO保证前面所有平仓/取消任务完成后才重置
- executor.submitTask(() -> {
- try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
- startGrid();
- });
- log.info("[Gate] 重置策略");
- return;
- }
- }
-
- // ---- 平仓推送回调 ----
-
- /**
- * 平仓推送回调。由 {@link PositionClosesChannelHandler} 在收到平仓推送时调用。
- *
- * <h3>累加规则</h3>
- * cumulativePnl += pnl。止盈平仓时 pnl > 0,止损平仓时 pnl < 0。
- * 累加后检查停止条件:≥ overallTp 或 ≤ -maxLoss。
- *
- * @param contract 合约名称
- * @param side 平仓方向("long" / "short")
- * @param pnl 本次平仓的盈亏金额
- */
- public void onPositionClose(String contract, String side, BigDecimal pnl) {
- if (state == StrategyState.STOPPED) {
- return;
- }
- cumulativePnl = cumulativePnl.add(pnl);
- updateUnrealizedPnl();
- BigDecimal totalPnl = cumulativePnl.add(unrealizedPnl);
- log.info("[Gate] 已实现:{}, 未实现:{}, 合计:{}",
- cumulativePnl, unrealizedPnl, totalPnl);
- if(totalPnl.compareTo(config.getMaxLoss().negate()) <= 0) {
- String logMessage = StrUtil.format("[Gate] 已达亏损风险值(合计{}), 已实现:{}, 未实现:{}",
- totalPnl, cumulativePnl, unrealizedPnl);
- log.info(logMessage);
-
- DingTalkUtils.getDefault().sendActionCard("风险提醒", logMessage, config.getApiKey(), "");
- }
- }
-
-
- /**
- * 自动订单(条件单)状态变更回调。
- * 由 {@link com.xcong.excoin.modules.gateApi.wsHandler.handler.AutoOrdersChannelHandler}
- * 在收到 {@code futures.autoorders} 推送时调用。
- *
- * @param orderId 条件单 ID
- * @param status 订单状态(open / finished / cancelled)
- * @param reason 变更原因
- * @param orderType 订单类型(plan-close-long-position 等)
- */
- public void onAutoOrder(String orderId, String status, String reason, String orderType, String tradeId) {
- if (state == StrategyState.STOPPED) {
- return;
- }
- log.info("[Gate] 条件单状态变更, id:{}, status:{}, reason:{}, order_type:{}",
- orderId, status, reason, orderType);
- if (!"finished".equals(status)) {
- return;
- }
-
- // [Gate-需求1] 多仓止盈触发:清空止盈状态 + 取消最远多仓止损 + 检查是否最后一个止盈
- GridElement longTpElem = GridElement.findByLongTakeProfitOrderId(orderId);
- if (longTpElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
- longTakeProfitTraderIdParam(longTpElem, null, false);
- log.info("[Gate] 多仓止盈触发 gridId:{}, orderId:{}", longTpElem.getId(), orderId);
- cancelFarthestLongStopLoss();
- checkLastTakeProfitAndRestart();
- return;
- }
- // [Gate-需求1] 空仓止盈触发:清空止盈状态 + 取消最远空仓止损 + 检查是否最后一个止盈
- GridElement shortTpElem = GridElement.findByShortTakeProfitOrderId(orderId);
- if (shortTpElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
- shortTakeProfitTraderIdParam(shortTpElem, null, false);
- log.info("[Gate] 空仓止盈触发 gridId:{}, orderId:{}", shortTpElem.getId(), orderId);
- cancelFarthestShortStopLoss();
- checkLastTakeProfitAndRestart();
- return;
- }
-
- GridElement longStopLossElem = GridElement.findByLongStopLossOrderId(orderId);
-// if (longStopLossElem != null && longPositionSize.compareTo(BigDecimal.ZERO) > 0 && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
- if (longStopLossElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
- handleLongStopLossTriggered(longStopLossElem);
- return;
- }
- GridElement shortStopLossElem = GridElement.findByShortStopLossOrderId(orderId);
-// if (shortStopLossElem != null && shortPositionSize.compareTo(BigDecimal.ZERO) > 0 && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
- if (shortStopLossElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
- handleShortStopLossTriggered(shortStopLossElem);
- return;
- }
-
- GridElement shortGridElement = GridElement.findByShortOrderId(orderId);
- if (shortGridElement != null) {
- if (shortGridElement.isHasShortOrder() && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0") ){
- int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity());
- shortEntryTraderIdParam(shortGridElement, null, false);
- // [Gate-需求2] 加仓后先撤空仓所有止盈+止损,再查交易所持仓后重挂
- cancelAllShortTakeProfitsAndStopLosses();
- int posSize = queryPositionSize(Position.ModeEnum.DUAL_SHORT);
- extendShortStopLoss(posSize, shortGridElement.getId());
- accumulatedShortLossCount = 0; // 加仓订单成交,重置止损累计
- log.info("[Gate] 空单成交 gridId:{}, 当前持仓:{}张", filledQty, posSize);
-
- // 空仓持仓超过baseQuantity时,从gridId-2开始向外追挂止盈
- BigDecimal shortBaseQty = new BigDecimal(config.getBaseQuantity());
- BigDecimal shortGridQty = new BigDecimal(config.getQuantity());
- if (BigDecimal.valueOf(posSize).compareTo(shortBaseQty) > 0) {
- BigDecimal shortExcess = BigDecimal.valueOf(posSize).subtract(shortBaseQty);
- int shortExcessCount = shortExcess.divide(shortGridQty, 0, RoundingMode.DOWN).intValue();
- for (int i = 0; i < shortExcessCount; i++) {
- int tpGridId = shortGridElement.getId() - 2 - i;
- if (i > 0){
- tpGridId = tpGridId - 1;
- }
- GridElement tpElem = GridElement.findById(tpGridId);
- if (tpElem == null || tpElem.getShortTakeProfitOrderId() != null) {
- continue;
- }
- BigDecimal tpPrice = tpElem.getGridPrice();
- int finalTpGridId = tpGridId;
- executor.placeTakeProfit(
- tpPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_2,
- ORDER_TYPE_CLOSE_SHORT,
- config.getQuantity(),
- profitId -> {
- shortTakeProfitTraderIdParam(tpElem, profitId, true);
- log.info("[Gate] 空仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
- finalTpGridId, tpPrice, profitId);
- }
- );
- }
- }
- }
- }
- GridElement longGridElement = GridElement.findByLongOrderId(orderId);
- if (longGridElement != null) {
- if (longGridElement.isHasLongOrder() && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")){
-
- int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity());
- longEntryTraderIdParam(longGridElement, null, false);
- // [Gate-需求2] 加仓后先撤多仓所有止盈+止损,再查交易所持仓后重挂
- cancelAllLongTakeProfitsAndStopLosses();
- int posSize = queryPositionSize(Position.ModeEnum.DUAL_LONG);
- extendLongStopLoss(posSize, longGridElement.getId());
- accumulatedLongLossCount = 0; // 加仓订单成交,重置止损累计
- log.info("[Gate] 多单成交 gridId:{}, 当前持仓:{}张", filledQty, posSize);
-
- // 多仓持仓超过baseQuantity时,从gridId+2开始向外追挂止盈
- BigDecimal longBaseQty = new BigDecimal(config.getBaseQuantity());
- BigDecimal longGridQty = new BigDecimal(config.getQuantity());
- if (BigDecimal.valueOf(posSize).compareTo(longBaseQty) > 0) {
- BigDecimal longExcess = BigDecimal.valueOf(posSize).subtract(longBaseQty);
- int longExcessCount = longExcess.divide(longGridQty, 0, RoundingMode.DOWN).intValue();
- for (int i = 0; i < longExcessCount; i++) {
- int tpGridId = longGridElement.getId() + 2 + i;
- if (i > 0){
- tpGridId = tpGridId + 1;
- }
-
- GridElement tpElem = GridElement.findById(tpGridId);
- if (tpElem == null || tpElem.getLongTakeProfitOrderId() != null) {
- continue;
- }
- BigDecimal tpPrice = tpElem.getGridPrice();
- int finalTpGridId = tpGridId;
- executor.placeTakeProfit(
- tpPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_1,
- ORDER_TYPE_CLOSE_LONG,
- negate(config.getQuantity()),
- profitId -> {
- longTakeProfitTraderIdParam(tpElem, profitId, true);
- log.info("[Gate] 多仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
- finalTpGridId, tpPrice, profitId);
- }
- );
- }
- }
- }
- }
- }
-
-
- /**
- * 查询交易所当前持仓张数(绝对值)。加仓/开仓后重挂止盈止损时调用,
- * 绕过本地 WS 推送缓存避免时序竞态,直接拿到交易所权威数据。
- *
- * @param mode 持仓模式(DUAL_LONG / DUAL_SHORT)
- * @return 持仓张数(绝对值),查询失败返回 0
- */
- private int queryPositionSize(Position.ModeEnum mode) {
- Position p = queryPosition(mode);
- if (p != null) {
- return new BigDecimal(p.getSize()).abs().intValue();
- }
- return 0;
- }
-
- /**
- * 查询交易所当前持仓均价,绕过本地 WS 推送缓存避免时序竞态。
- *
- * @param mode 持仓模式(DUAL_LONG / DUAL_SHORT)
- * @return 持仓均价,无持仓或查询失败返回 BigDecimal.ZERO
- */
- private BigDecimal queryEntryPrice(Position.ModeEnum mode) {
- Position p = queryPosition(mode);
- if (p != null && p.getEntryPrice() != null) {
- return new BigDecimal(p.getEntryPrice());
- }
- return BigDecimal.ZERO;
- }
-
- /**
- * 查询指定模式的持仓对象。
- */
- private Position queryPosition(Position.ModeEnum mode) {
- try {
- List<Position> positions = futuresApi.listPositions(SETTLE).execute();
- if (positions != null) {
- for (Position p : positions) {
- if (mode == p.getMode() && config.getContract().equals(p.getContract())) {
- return p;
- }
- }
- }
- } catch (Exception e) {
- log.warn("[Gate] 查询{}持仓失败", mode, e);
- }
- return null;
- }
-
- // ---- 网格队列处理 ----
-
- /**
- * 尝试生成网格队列。双基底(多+空)都成交后才触发:
- * <ol>
- * <li>生成空仓价格队列(降序)和多仓价格队列(升序)</li>
- * <li>挂初始多仓条件单(触发价 = 多仓队列首元素,rule=NUMBER_1 ≥触发价时开多),
- * 止盈价 = 触发价 + step,通过 onSuccess 回调将 orderId → 止盈价存入 currentLongOrderIds</li>
- * <li>挂初始空仓条件单(触发价 = 空仓队列首元素,rule=NUMBER_2 ≤触发价时开空),
- * 止盈价 = 触发价 − step,通过 onSuccess 回调将 orderId → 止盈价存入 currentShortOrderIds</li>
- * <li>状态切换为 ACTIVE</li>
- * </ol>
- */
- private void tryGenerateQueues() {
- if (baseLongOpened && baseShortOpened) {
- generateShortQueue();
- generateLongQueue();
- updateGridElements();
-
- GridElement baseGridElement = GridElement.findById(0);
- TraderParam baseLongTraderParam = config.getBaseLongTraderParam();
- baseGridElement.setLongOrderId(baseLongTraderParam.getEntryOrderId());
- baseGridElement.setHasLongOrder(true);
- TraderParam baseShortTraderParam = config.getBaseShortTraderParam();
- baseGridElement.setShortOrderId(baseShortTraderParam.getEntryOrderId());
- baseGridElement.setHasShortOrder(true);
-
-// int shortTime = 2;
-// GridElement elemShort = GridElement.findById(shortTime);
-// if (elemShort != null) {
-// BigDecimal triggerPrice = elemShort.getGridPrice();
-// String size = config.getBaseQuantity();
-// executor.placeTakeProfit(
-// triggerPrice,
-// FuturesPriceTrigger.RuleEnum.NUMBER_1,
-// ORDER_TYPE_CLOSE_SHORT,
-// size,
-// profitId -> {
-// elemShort.setShortStopLossOrderId(profitId);
-// GridElement.refreshIndices();
-// log.info("[Gate] 空仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", shortTime, triggerPrice, profitId);
-// }
-// );
-// }
-//
-//
-// int longTime = -2;
-// GridElement elemLong = GridElement.findById(longTime);
-// if (elemLong != null) {
-// BigDecimal triggerPrice = elemLong.getGridPrice();
-// String size = config.getBaseQuantity();
-// executor.placeTakeProfit(
-// triggerPrice,
-// FuturesPriceTrigger.RuleEnum.NUMBER_2,
-// ORDER_TYPE_CLOSE_LONG,
-// negate(size),
-// profitId -> {
-// elemLong.setLongStopLossOrderId(profitId);
-// GridElement.refreshIndices();
-// log.info("[Gate] 多仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", longTime, triggerPrice, profitId);
-// }
-// );
-// }
-
- int shortTime = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
- for (int id = 2; id <= shortTime; id++) {
- GridElement elem = GridElement.findById(id);
- if (elem == null) {
- continue;
- }
- BigDecimal triggerPrice = elem.getGridPrice();
- String size = config.getQuantity();
- int finalId = id;
- executor.placeTakeProfit(
- triggerPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_1,
- ORDER_TYPE_CLOSE_SHORT,
- size,
- profitId -> {
- elem.setShortStopLossOrderId(profitId);
- GridElement.refreshIndices();
- log.info("[Gate] 空仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId);
- }
- );
- }
-
-
- int longTime = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
- for (int id = -2; id >= -longTime; id--) {
- GridElement elem = GridElement.findById(id);
- if (elem == null) {
- continue;
- }
- BigDecimal triggerPrice = elem.getGridPrice();
- String size = config.getQuantity();
- int finalId = id;
- executor.placeTakeProfit(
- triggerPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_2,
- ORDER_TYPE_CLOSE_LONG,
- negate(size),
- profitId -> {
- elem.setLongStopLossOrderId(profitId);
- GridElement.refreshIndices();
- log.info("[Gate] 多仓止损已挂, gridId:{}, 触发价:{}, stopLossId:{}", finalId, triggerPrice, profitId);
- }
- );
- }
-
- log.info("[Gate] 止损单已全部挂完, 空仓止损: 2~{}, 多仓止损: -2~-{}", shortTime, longTime);
-
- state = StrategyState.ACTIVE;
- }
- }
-
- /**
- * 更新基座止盈信息,将止盈价、订单ID等写入 TraderParam 并回填到 ID=0 的网格元素中。
- */
- private void longTakeProfitTraderIdParam(
- GridElement baseElement,String profitId, boolean flag
- ) {
- TraderParam tp = baseElement.getLongTraderParam();
- tp.setTakeProfitOrderId(profitId);
- tp.setTakeProfitPlaced(flag);
- baseElement.setLongTakeProfitOrderId(profitId);
- GridElement.refreshIndices();
- }
- private void shortTakeProfitTraderIdParam(
- GridElement baseElement,String profitId, boolean flag
- ) {
- TraderParam tp = baseElement.getShortTraderParam();
- tp.setTakeProfitOrderId(profitId);
- tp.setTakeProfitPlaced(flag);
- baseElement.setShortTakeProfitOrderId(profitId);
- GridElement.refreshIndices();
- }
-
- private void longEntryTraderIdParam(
- GridElement baseElement,String entryId,boolean flag
- ) {
- TraderParam tp = baseElement.getLongTraderParam();
- tp.setEntryOrderId(entryId);
- tp.setEntryOrderPlaced(flag);
- baseElement.setHasLongOrder(flag);
- baseElement.setLongOrderId(entryId);
- GridElement.refreshIndices();
- }
-
- private void shortEntryTraderIdParam(
- GridElement baseElement, String entryId, boolean flag
- ) {
- TraderParam tp = baseElement.getShortTraderParam();
- tp.setEntryOrderId(entryId);
- tp.setEntryOrderPlaced(flag);
- baseElement.setHasShortOrder(flag);
- baseElement.setShortOrderId(entryId);
- GridElement.refreshIndices();
- }
-
- /**
- * 生成空仓价格队列。
- * 以 shortBaseEntryPrice × gridRate 作为绝对步长 step,存到 config。
- * 第1个元素 = shortBaseEntryPrice − step,后续依次递减 step,共 gridQueueSize 个。
- * 队列降序排列(大→小),方便 processShortGrid 中从头遍历。
- */
- private void generateShortQueue() {
- shortPriceQueue.clear();
- totalShortPriceQueue.clear();
- totalLongPriceQueue.clear();
- int prec = config.getPriceScale();
- BigDecimal step = shortBaseEntryPrice.multiply(config.getGridRate()).setScale(prec, RoundingMode.HALF_UP);
- config.setStep(step);
- BigDecimal elem = shortBaseEntryPrice.subtract(step).setScale(prec, RoundingMode.HALF_UP);
- for (int i = 0; i < config.getGridQueueSize(); i++) {
- shortPriceQueue.add(elem);
- totalLongPriceQueue.add( elem);
- totalShortPriceQueue.add( elem);
-
- elem = elem.subtract(step).setScale(prec, RoundingMode.HALF_UP);
- if (elem.compareTo(BigDecimal.ZERO) <= 0) {
- break;
- }
- }
- shortPriceQueue.sort((a, b) -> b.compareTo(a));
- log.info("[Gate] 空队列:{}", shortPriceQueue);
- }
-
- /**
- * 生成多仓价格队列。
- * 以 shortBaseEntryPrice + step 为首元素,后续依次递增 step,共 gridQueueSize 个。
- * 队列升序排列(小→大),方便 processLongGrid 中从头遍历。
- */
- private void generateLongQueue() {
- longPriceQueue.clear();
- int prec = config.getPriceScale();
- BigDecimal step = config.getStep();
- BigDecimal elem = shortBaseEntryPrice.add(step).setScale(prec, RoundingMode.HALF_UP);
- for (int i = 0; i < config.getGridQueueSize(); i++) {
- longPriceQueue.add(elem);
- totalLongPriceQueue.add( elem);
- totalShortPriceQueue.add( elem);
- elem = elem.add(step).setScale(prec, RoundingMode.HALF_UP);
- }
- longPriceQueue.sort(BigDecimal::compareTo);
- log.info("[Gate] 多队列:{}", longPriceQueue);
- totalShortPriceQueue.sort((a, b) -> b.compareTo(a));
- log.info("[Gate] 队列从大到小:{}", totalShortPriceQueue);
- totalLongPriceQueue.sort(BigDecimal::compareTo);
- log.info("[Gate] 队列从小到大:{}", totalLongPriceQueue);
- }
-
- /**
- * 根据当前多空价格队列同步构建网格元素列表,写入 config。
- *
- * <h3>ID 分配规则</h3>
- * <ul>
- * <li>空仓队列:id 从 -1 自减(-1, -2, -3...),第一个元素 upId=0,最后一个 downId=null</li>
- * <li>位置 0:gridPrice=shortBaseEntryPrice,upId=-1,downId=1,其数据在基座开仓时更新</li>
- * <li>多仓队列:id 从 1 自增(1, 2, 3...),第一个元素 upId=0,最后一个 downId=null</li>
- * </ul>
- *
- * <h3>链表关系</h3>
- * 所有元素通过 upId/downId 串成一条双向链表:
- * ... → -3 → -2 → -1 → 0 → 1 → 2 → 3 → ...
- */
- private void updateGridElements() {
- List<GridElement> elements = new ArrayList<>();
- int shortSize = shortPriceQueue.size();
- int longSize = longPriceQueue.size();
- //根据精度转换成小数
- int prec = config.getPriceScale();
- BigDecimal step = config.getStep();
-// String qty = config.getBaseQuantity();
- String qty = config.getQuantity();
-
- // 空仓队列:id 从 -1 自减, shortPriceQueue[i] → id=-(i+1)
- for (int i = 0; i < shortSize; i++) {
- int id = -(i + 1);
- Integer upId = (i == 0) ? 0 : id + 1;
- Integer downId = (i == shortSize - 1) ? null : id - 1;
- BigDecimal price = shortPriceQueue.get(i);
- TraderParam longParam = TraderParam.builder()
- .direction(TraderParam.Direction.LONG)
- .entryPrice(price)
- .takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP))
- .quantity(qty)
- .build();
- TraderParam shortParam = TraderParam.builder()
- .direction(TraderParam.Direction.SHORT)
- .entryPrice(price)
- .takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP))
- .quantity(qty)
- .build();
- elements.add(GridElement.builder()
- .id(id)
- .gridPrice(price)
- .upId(upId)
- .downId(downId)
- .longTraderParam(longParam)
- .shortTraderParam(shortParam)
- .build());
- }
-
- // 位置 0:基底价格,数据在基座开仓时更新
- {
- BigDecimal price = shortBaseEntryPrice;
- TraderParam longParam = TraderParam.builder()
- .direction(TraderParam.Direction.LONG)
- .entryPrice(price)
- .takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP))
- .quantity(qty)
- .build();
- TraderParam shortParam = TraderParam.builder()
- .direction(TraderParam.Direction.SHORT)
- .entryPrice(price)
- .takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP))
- .quantity(qty)
- .build();
- elements.add(GridElement.builder()
- .id(0)
- .gridPrice(price)
- .upId(longSize > 0 ? 1 : null)
- .downId(shortSize > 0 ? -1 : null)
- .longTraderParam(longParam)
- .shortTraderParam(shortParam)
- .build());
- }
-
- // 多仓队列:id 从 1 自增, longPriceQueue[i] → id=i+1
- for (int i = 0; i < longSize; i++) {
- int id = i + 1;
- Integer downId = (i == 0) ? 0 : id - 1;
- Integer upId = (i == longSize - 1) ? null : id + 1;
- BigDecimal price = longPriceQueue.get(i);
- TraderParam longParam = TraderParam.builder()
- .direction(TraderParam.Direction.LONG)
- .entryPrice(price)
- .takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP))
- .quantity(qty)
- .build();
- TraderParam shortParam = TraderParam.builder()
- .direction(TraderParam.Direction.SHORT)
- .entryPrice(price)
- .takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP))
- .quantity(qty)
- .build();
- elements.add(GridElement.builder()
- .id(id)
- .gridPrice(price)
- .upId(upId)
- .downId(downId)
- .longTraderParam(longParam)
- .shortTraderParam(shortParam)
- .build());
- }
-
- config.setGridElements(elements);
- log.info("[Gate] 网格元素列表已构建, 共{}个元素 (空仓:{} 位置:0 多仓:{})", elements.size(), shortSize, longSize);
- }
-
-
- private void processShortGrid(BigDecimal currentPrice) {
- BigDecimal matched = BigDecimal.ZERO;
- synchronized (totalLongPriceQueue) {
- for (BigDecimal p : totalLongPriceQueue) {
- if (p.compareTo(currentPrice) >= 0) {
- matched = p;
- break;
- }
- }
-// log.info("[Gate-1] 多仓仓位归零 空仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
- if (BigDecimal.ZERO.compareTo( matched) == 0) {
- return;
- }
-
- GridElement matchedUpGridElement = GridElement.findByPrice(matched);
- if (matchedUpGridElement != null){
- if (!matchedUpGridElement.isHasLongOrder()){
- Integer upId = matchedUpGridElement.getUpId();
- GridElement newEntryGrid = GridElement.findById(upId);
-
- if (newEntryGrid != null) {
-
- GridElement cancelGridElement = GridElement.findById(newEntryGrid.getUpId());
- String quantity = cancelGridElement != null
- ? cancelGridElement.getLongTraderParam().getQuantity()
- : config.getBaseQuantity();
- if (cancelGridElement != null && cancelGridElement.isHasLongOrder()) {
- String longOrderId = cancelGridElement.getLongOrderId();
- executor.cancelConditionalOrder(longOrderId, oid -> {
- longEntryTraderIdParam(cancelGridElement, null, false);
- log.info("[Gate] 多仓仓位归零, 取消gridId:{}的多单,{}", cancelGridElement.getId(),longOrderId);
- });
- }
-// log.info("[Gate-2] 多仓仓位归零 空仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
- if (!newEntryGrid.isHasLongOrder()) {
- BigDecimal triggerPrice = newEntryGrid.getGridPrice();
- String size = quantity;
- log.info("[Gate] 多仓仓位归零 gridId:{}, 挂{}基础张多单",
- newEntryGrid.getId(), size);
- newEntryGrid.getLongTraderParam().setQuantity(size);
- placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_1, size);
- }
- }
- }
- }
- }
-
- }
-
- private void processLongGrid(BigDecimal currentPrice) {
- BigDecimal matched = BigDecimal.ZERO;
- synchronized (totalShortPriceQueue) {
- for (BigDecimal p : totalShortPriceQueue) {
- if (p.compareTo(currentPrice) <= 0) {
- matched = p;
- break;
- }
- }
-// log.info("[Gate-3] 空仓仓位归零 多仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
- if (BigDecimal.ZERO.compareTo( matched) == 0) {
- return;
- }
-
- GridElement matchedUpGridElement = GridElement.findByPrice(matched);
- if (matchedUpGridElement != null){
- if(!matchedUpGridElement.isHasShortOrder()){
- Integer downId = matchedUpGridElement.getDownId();
- GridElement newEntryGrid = GridElement.findById(downId);
-
- if (newEntryGrid != null) {
-
- GridElement cancelGridElement = GridElement.findById(newEntryGrid.getDownId());
-
- String quantity = cancelGridElement != null
- ? cancelGridElement.getShortTraderParam().getQuantity()
- : config.getBaseQuantity();
- /**
- * 看是否有空仓挂单,有就取消
- */
- if (cancelGridElement != null && cancelGridElement.isHasShortOrder()) {
- String shortOrderId = cancelGridElement.getShortOrderId();
- executor.cancelConditionalOrder(shortOrderId, oid -> {
- shortEntryTraderIdParam(cancelGridElement, null, false);
- log.info("[Gate] 空仓仓位归零, 取消gridId:{}的多单{}", cancelGridElement.getId(),shortOrderId);
- });
- }
-// log.info("[Gate-4] 空仓仓位归零 多仓队列触发, 匹配:{},当前价:{}", matched, currentPrice);
- if (!newEntryGrid.isHasShortOrder()){
- BigDecimal triggerPrice = newEntryGrid.getGridPrice();
- String size = quantity;
- log.info("[Gate] 空仓仓位归零 gridId:{}, 挂{}基础张多单",
- newEntryGrid.getId(), size);
- newEntryGrid.getShortTraderParam().setQuantity(size);
- placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(size));
- }
-
- }
- }
- }
- }
- }
-
- private void handleLongStopLossTriggered(GridElement gridElement) {
- gridElement.setLongStopLossOrderId(null);
-
- int gridId = gridElement.getId();
- log.info("[Gate] 多仓止损触发 gridId:{}, 开始追单", gridId);
- int newEntryGridId = gridId + 1;
-
- GridElement newEntryGrid = GridElement.findById(newEntryGridId);
- if (newEntryGrid == null) {
- log.warn("[Gate] 多仓止损触发 but gridId:{} 不存在", newEntryGridId);
- GridElement.refreshIndices();
- return;
- }
-
- // [Gate-BugFix] 防止与"仓位归零"重复下单:若该网格已有挂单则跳过
- if (!newEntryGrid.isHasLongOrder()) {
- BigDecimal triggerPrice = newEntryGrid.getGridPrice();
-
- // 累计止损张数 + 当前止损量作为追单size,不再依赖positionSize(避免WS竞态)
- accumulatedLongLossCount += Integer.parseInt(config.getQuantity());
- String size = String.valueOf(accumulatedLongLossCount + Integer.parseInt(config.getQuantity()));
- log.info("[Gate] 多仓止损触发 gridId:{}, 在gridId:{}挂{}基础张多单",
- gridId, newEntryGridId, size);
-
- newEntryGrid.getLongTraderParam().setQuantity(size);
- placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_1, size);
- }else{
- log.warn("[Gate] 多仓止损触发 gridId:{}, 目标gridId:{}已有挂单,跳过重复下单", gridId, newEntryGridId);
- }
-
- int cancelGridId = gridId + 2;
- GridElement cancelGrid = GridElement.findById(cancelGridId);
- if (cancelGrid != null && cancelGrid.isHasLongOrder()) {
- executor.cancelConditionalOrder(cancelGrid.getLongOrderId(), oid -> {
- longEntryTraderIdParam(cancelGrid, null, false);
- log.info("[Gate] 多仓止损触发, 取消gridId:{}的多单", cancelGridId);
- });
- }
-
- // 止损触发时,取消最远的多仓止盈订单
- GridElement farthestLongTp = null;
- for (GridElement e : config.getGridElements()) {
- if (e.getLongTakeProfitOrderId() != null) {
- if (farthestLongTp == null || e.getGridPrice().compareTo(farthestLongTp.getGridPrice()) > 0) {
- farthestLongTp = e;
- }
- }
- }
- if (farthestLongTp != null) {
- String tpOrderId = farthestLongTp.getLongTakeProfitOrderId();
- GridElement finalFarthestLongTp = farthestLongTp;
- executor.cancelConditionalOrder(tpOrderId, oid -> {
- longTakeProfitTraderIdParam(finalFarthestLongTp, null, false);
- log.info("[Gate] 多仓止损触发, 取消最远止盈 gridId:{}, orderId:{}", finalFarthestLongTp.getId(), tpOrderId);
- });
- }
- }
-
- private void handleShortStopLossTriggered(GridElement gridElement) {
- gridElement.setShortStopLossOrderId(null);
-
- int gridId = gridElement.getId();
- log.info("[Gate] 空仓止损触发 gridId:{}, 开始追单", gridId);
- int newEntryGridId = gridId - 1;
-
- GridElement newEntryGrid = GridElement.findById(newEntryGridId);
- if (newEntryGrid == null) {
- log.warn("[Gate] 空仓止损触发 but gridId:{} 不存在", newEntryGridId);
- GridElement.refreshIndices();
- return;
- }
-
- // [Gate-BugFix] 防止与"仓位归零"重复下单:若该网格已有挂单则跳过
- if (!newEntryGrid.isHasShortOrder()) {
- BigDecimal triggerPrice = newEntryGrid.getGridPrice();
-
- // 累计止损张数 + 当前止损量作为追单size,不再依赖positionSize(避免WS竞态)
- accumulatedShortLossCount += Integer.parseInt(config.getQuantity());
- String size = String.valueOf(accumulatedShortLossCount + Integer.parseInt(config.getQuantity()));
- log.info("[Gate] 空仓止损触发 gridId:{}, 在gridId:{}挂{}基础张空单",
- gridId, newEntryGridId, size);
- newEntryGrid.getShortTraderParam().setQuantity(size);
- placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_2, negate(size));
- }else{
- log.warn("[Gate] 空仓止损触发 gridId:{}, 目标gridId:{}已有挂单,跳过重复下单", gridId, newEntryGridId);
- }
-
-
-
-
- int cancelGridId = gridId - 2;
- GridElement cancelGrid = GridElement.findById(cancelGridId);
- if (cancelGrid != null && cancelGrid.isHasShortOrder()) {
- executor.cancelConditionalOrder(cancelGrid.getShortOrderId(), oid -> {
- shortEntryTraderIdParam(cancelGrid, null, false);
- log.info("[Gate] 空仓止损触发, 取消gridId:{}的空单", cancelGridId);
- });
- }
-
- // 止损触发时,取消最远的空仓止盈订单
- GridElement farthestShortTp = null;
- for (GridElement e : config.getGridElements()) {
- if (e.getShortTakeProfitOrderId() != null) {
- if (farthestShortTp == null || e.getGridPrice().compareTo(farthestShortTp.getGridPrice()) < 0) {
- farthestShortTp = e;
- }
- }
- }
- if (farthestShortTp != null) {
- String tpOrderId = farthestShortTp.getShortTakeProfitOrderId();
- GridElement finalFarthestShortTp = farthestShortTp;
- executor.cancelConditionalOrder(tpOrderId, oid -> {
- shortTakeProfitTraderIdParam(finalFarthestShortTp, null, false);
- log.info("[Gate] 空仓止损触发, 取消最远止盈 gridId:{}, orderId:{}", finalFarthestShortTp.getId(), tpOrderId);
- });
- }
- }
-
- // ========== 止盈/止损取消辅助方法 ==========
-
- /**
- * 止盈触发后检查跨度是否达至要求,满足条件则重启策略。
- *
- * <h3>跨度定义</h3>
- * {@code restartGridSpan} 表示多少倍的绝对步长 step(= 短基价 × gridRate)。
- *
- * <h3>判断逻辑</h3>
- * <ol>
- * <li>多空双边均有持仓:longEntryPrice − shortEntryPrice > span × step</li>
- * <li>仅持多仓:currentPrice − longEntryPrice > span × step</li>
- * <li>仅持空仓:shortEntryPrice − currentPrice > span × step</li>
- * </ol>
- * restartGridSpan=0 时禁用此功能。重启复用仓位归零模式:取消全部条件单 → 平仓 → 延迟启动。
- */
- private void checkLastTakeProfitAndRestart() {
- int span = config.getRestartGridSpan();
- if (span <= 0) {
- return;
- }
-
- // 检查是否还有剩余止盈单,只有多空止盈全部清空才继续
- if (GridElement.getLongTakeProfitCount() > 0 || GridElement.getShortTakeProfitCount() > 0) {
- log.info("[Gate] 尚有未触发止盈单, 暂不检查跨度重启 longTpCount:{}, shortTpCount:{}",
- GridElement.getLongTakeProfitCount(), GridElement.getShortTakeProfitCount());
- return;
- }
-
- BigDecimal step = config.getStep();
- if (step == null || step.compareTo(BigDecimal.ZERO) == 0) {
- return;
- }
- BigDecimal threshold = step.multiply(new BigDecimal(span));
-
- BigDecimal currentPrice = lastKlinePrice;
- if (currentPrice == null || currentPrice.compareTo(BigDecimal.ZERO) == 0) {
- return;
- }
-
- // 查交易所获取最新持仓均价和持仓量,不用本地缓存避免 WS 时序竞态
- Position longPos = queryPosition(Position.ModeEnum.DUAL_LONG);
- Position shortPos = queryPosition(Position.ModeEnum.DUAL_SHORT);
- boolean hasLong = longPos != null && Math.abs(Integer.parseInt(longPos.getSize())) > 0;
- boolean hasShort = shortPos != null && Math.abs(Integer.parseInt(shortPos.getSize())) > 0;
- BigDecimal longAvgPrice = (longPos != null && longPos.getEntryPrice() != null)
- ? new BigDecimal(longPos.getEntryPrice()) : BigDecimal.ZERO;
- BigDecimal shortAvgPrice = (shortPos != null && shortPos.getEntryPrice() != null)
- ? new BigDecimal(shortPos.getEntryPrice()) : BigDecimal.ZERO;
-
- boolean shouldRestart = false;
- String reason = "";
-
- if (hasLong && hasShort) {
- // 多空双边持仓:|多均价 − 空均价| > span × step
- BigDecimal gap = shortAvgPrice.subtract(longAvgPrice);
- if (gap.compareTo(threshold) >= 0) {
- shouldRestart = true;
- reason = StrUtil.format("双边跨度 |多均价:{} − 空均价:{}| = {} >= {} (span:{}×step:{})",
- longAvgPrice, shortAvgPrice, gap, threshold, span, step);
- }
- } else if (hasLong) {
- // 仅持多仓:当前价 − 多均价 > span × step
- BigDecimal gap = currentPrice.subtract(longAvgPrice);
- if (gap.compareTo(threshold) >= 0) {
- shouldRestart = true;
- reason = StrUtil.format("多仓跨度 当前价:{} − 多均价:{} = {} > {} (span:{}×step:{})",
- currentPrice, longAvgPrice, gap, threshold, span, step);
- }
- } else if (hasShort) {
- // 仅持空仓:空均价 − 当前价 > span × step
- BigDecimal gap = shortAvgPrice.subtract(currentPrice);
- if (gap.compareTo(threshold) >= 0) {
- shouldRestart = true;
- reason = StrUtil.format("空仓跨度 空均价:{} − 当前价:{} = {} > {} (span:{}×step:{})",
- shortAvgPrice, currentPrice, gap, threshold, span, step);
- }
- }
-
- if (shouldRestart) {
- log.info("[Gate] 跨度已达要求 → {},最后一个止盈触发策略重启", reason);
- try {
- futuresApi.cancelPriceTriggeredOrderList(SETTLE, config.getContract());
- } catch (ApiException ex) {
- log.warn("[Gate] 重启前清理条件单失败", ex);
- }
- closeExistingPositions();
- state = StrategyState.STOPPED;
- executor.submitTask(() -> {
- try { Thread.sleep(3000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); }
- startGrid();
- });
- }
- }
-
- /**
- * 取消最远的多仓止损订单。
- * 多仓止损在 gridId 负方向,最远 = id 最小。
- */
- private void cancelFarthestLongStopLoss() {
- GridElement farthest = null;
- for (GridElement e : config.getGridElements()) {
- if (e.getLongStopLossOrderId() != null) {
- if (farthest == null || e.getId() < farthest.getId()) {
- farthest = e;
- }
- }
- }
- if (farthest != null) {
- String slId = farthest.getLongStopLossOrderId();
- farthest.setLongStopLossOrderId(null);
- GridElement.refreshIndices();
- GridElement finalFarthest = farthest;
- executor.cancelConditionalOrder(slId, oid ->
- log.info("[Gate] 止盈触发, 取消最远多仓止损 gridId:{}, orderId:{}", finalFarthest.getId(), slId));
- }
- }
-
- /**
- * 取消最远的空仓止损订单。
- * 空仓止损在 gridId 正方向,最远 = id 最大。
- */
- private void cancelFarthestShortStopLoss() {
- GridElement farthest = null;
- for (GridElement e : config.getGridElements()) {
- if (e.getShortStopLossOrderId() != null) {
- if (farthest == null || e.getId() > farthest.getId()) {
- farthest = e;
- }
- }
- }
- if (farthest != null) {
- String slId = farthest.getShortStopLossOrderId();
- farthest.setShortStopLossOrderId(null);
- GridElement.refreshIndices();
- GridElement finalFarthest = farthest;
- executor.cancelConditionalOrder(slId, oid ->
- log.info("[Gate] 止盈触发, 取消最远空仓止损 gridId:{}, orderId:{}", finalFarthest.getId(), slId));
- }
- }
-
- /**
- * 取消所有多仓止盈 + 多仓止损订单(加仓后重建前清场)。
- */
- private void cancelAllLongTakeProfitsAndStopLosses() {
- for (GridElement e : config.getGridElements()) {
- String tpId = e.getLongTakeProfitOrderId();
- if (tpId != null) {
- e.setLongTakeProfitOrderId(null);
- executor.cancelConditionalOrder(tpId, oid -> {});
- }
- String slId = e.getLongStopLossOrderId();
- if (slId != null) {
- e.setLongStopLossOrderId(null);
- executor.cancelConditionalOrder(slId, oid -> {});
- }
- }
- GridElement.refreshIndices();
- log.info("[Gate] 已提交取消所有多仓止盈+止损");
- }
-
- /**
- * 取消所有空仓止盈 + 空仓止损订单(加仓后重建前清场)。
- */
- private void cancelAllShortTakeProfitsAndStopLosses() {
- for (GridElement e : config.getGridElements()) {
- String tpId = e.getShortTakeProfitOrderId();
- if (tpId != null) {
- e.setShortTakeProfitOrderId(null);
- executor.cancelConditionalOrder(tpId, oid -> {});
- }
- String slId = e.getShortStopLossOrderId();
- if (slId != null) {
- e.setShortStopLossOrderId(null);
- executor.cancelConditionalOrder(slId, oid -> {});
- }
- }
- GridElement.refreshIndices();
- log.info("[Gate] 已提交取消所有空仓止盈+止损");
- }
-
- // ========== 止损追单 ==========
-
- private void extendLongStopLoss(int filledQty,int gridId) {
- int furthestSlId = 0;
- for (GridElement e : config.getGridElements()) {
- if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
- furthestSlId = e.getId();
- }
- }
-
- int interval = 1;
- if (furthestSlId == 0) {
- furthestSlId = gridId;
- interval = 2;
- }
- int stopLossCount = filledQty / Integer.parseInt(config.getQuantity());
- log.info("[Gate] 多仓追挂止损, 当前最远止损gridId:{}, 成交{}张, 追加{}个止损单", furthestSlId, filledQty, stopLossCount);
- for (int i = 0; i < stopLossCount; i++) {
- int newSlId = furthestSlId - i - interval;
- GridElement elem = GridElement.findById(newSlId);
- if (elem == null) {
- continue;
- }
- BigDecimal triggerPrice = elem.getGridPrice();
- int finalSlId = newSlId;
- executor.placeTakeProfit(
- triggerPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_2,
- ORDER_TYPE_CLOSE_LONG,
- negate(config.getQuantity()),
- profitId -> {
- elem.setLongStopLossOrderId(profitId);
- GridElement.refreshIndices();
- log.info("[Gate] 多仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
- }
- );
- }
- }
-
- private void extendShortStopLoss(int filledQty, int gridId) {
- int furthestSlId = 0;
- for (GridElement e : config.getGridElements()) {
- if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
- furthestSlId = e.getId();
- }
- }
-
- int interval = 1;
- if (furthestSlId == 0) {
- furthestSlId = gridId;
- interval = 2;
- }
- int stopLossCount = filledQty / Integer.parseInt(config.getQuantity());
- log.info("[Gate] 空仓追挂止损, 当前最远止损gridId:{}, 成交{}张, 追加{}个止损单", furthestSlId, filledQty, stopLossCount);
- for (int i = 0; i < stopLossCount; i++) {
- int newSlId = furthestSlId + i + interval;
- GridElement elem = GridElement.findById(newSlId);
- if (elem == null) {
- continue;
- }
- BigDecimal triggerPrice = elem.getGridPrice();
- int finalSlId = newSlId;
- executor.placeTakeProfit(
- triggerPrice,
- FuturesPriceTrigger.RuleEnum.NUMBER_1,
- ORDER_TYPE_CLOSE_SHORT,
- config.getQuantity(),
- profitId -> {
- elem.setShortStopLossOrderId(profitId);
- GridElement.refreshIndices();
- log.info("[Gate] 空仓止损追加, gridId:{}, 触发价:{}, stopLossId:{}", finalSlId, triggerPrice, profitId);
- }
- );
- }
- }
-
- // ---- 工具 ----
-
- /**
- * 取反字符串数字。如 "1" → "-1","-2" → "2"。
- * 用于开空单时将正数张数转为负数。
- */
- private String negate(String qty) {
- return qty.startsWith("-") ? qty.substring(1) : "-" + qty;
- }
-
- /**
- * 预设标志位后提交条件开仓单,防止异步回调导致的竞态重复挂单。
- *
- * <p>在调用 {@link GateTradeExecutor#placeConditionalEntryOrder} 之前同步设置
- * {@code isHasLongOrder / isHasShortOrder},关闭 WS 线程与 Executor 线程之间的
- * 检查-下单时间窗口。API 失败时自动回滚标志位。
- *
- * @param gridElement 目标网格元素
- * @param isLong true=多仓下单,false=空仓下单
- * @param triggerPrice 触发价
- * @param rule 触发规则
- * @param size 开仓张数
- */
- private void placeEntryOrderWithPreFlag(GridElement gridElement, boolean isLong,
- BigDecimal triggerPrice,
- FuturesPriceTrigger.RuleEnum rule,
- String size) {
- if (isLong) {
- gridElement.setHasLongOrder(true);
- } else {
- gridElement.setHasShortOrder(true);
- }
- executor.placeConditionalEntryOrder(triggerPrice, rule, size,
- orderId -> {
- if (isLong) {
- longEntryTraderIdParam(gridElement, orderId, true);
- } else {
- shortEntryTraderIdParam(gridElement, orderId, true);
- }
- },
- () -> {
- if (isLong) {
- gridElement.setHasLongOrder(false);
- gridElement.setLongOrderId(null);
- } else {
- gridElement.setHasShortOrder(false);
- gridElement.setShortOrderId(null);
- }
- GridElement.refreshIndices();
- log.warn("[Gate] 条件单创建失败,回滚标志位 gridId:{}, isLong:{}", gridElement.getId(), isLong);
- }
- );
- }
-
- /**
- * 根据持仓和当前价格计算未实现盈亏。
- *
- * <h3>正向合约公式</h3>
- * <pre>
- * 多仓: 持仓量 × 合约乘数 × (计价价格 − 开仓均价)
- * 空仓: 持仓量 × 合约乘数 × (开仓均价 − 计价价格)
- * </pre>
- * 计价价格由 {@link GateConfig.PnLPriceMode} 决定:LAST_PRICE 用最新成交价,MARK_PRICE 用标记价格。
- */
- private void updateUnrealizedPnl() {
- BigDecimal price = resolvePnlPrice();
- if (price == null || price.compareTo(BigDecimal.ZERO) == 0) {
- return;
- }
- BigDecimal multiplier = config.getContractMultiplier();
- BigDecimal longPnl = BigDecimal.ZERO;
- BigDecimal shortPnl = BigDecimal.ZERO;
- if (longPositionSize.compareTo(BigDecimal.ZERO) > 0 && longEntryPrice.compareTo(BigDecimal.ZERO) > 0) {
- longPnl = longPositionSize.multiply(multiplier).multiply(price.subtract(longEntryPrice));
- }
- if (shortPositionSize.compareTo(BigDecimal.ZERO) > 0 && shortEntryPrice.compareTo(BigDecimal.ZERO) > 0) {
- shortPnl = shortPositionSize.multiply(multiplier).multiply(shortEntryPrice.subtract(price));
- }
- unrealizedPnl = longPnl.add(shortPnl);
-
- log.info("[Gate] 未实现盈亏: {}", unrealizedPnl);
- }
-
- /**
- * 根据配置的 PnLPriceMode 返回计价价格。
- * MARK_PRICE 模式优先使用标记价格(外部注入),未注入时回退到最新成交价。
- *
- * @return 计价价格,可能为 null
- */
- private BigDecimal resolvePnlPrice() {
- if (config.getUnrealizedPnlPriceMode() == GateConfig.PnLPriceMode.MARK_PRICE
- && markPrice.compareTo(BigDecimal.ZERO) > 0) {
- return markPrice;
- }
- return lastKlinePrice;
- }
-
- /** @return 最新 K 线价格(每次 onKline 更新) */
- public BigDecimal getLastKlinePrice() { return lastKlinePrice; }
- /** 设置标记价格(外部注入,MARK_PRICE 模式时用于盈亏计算) */
- public void setMarkPrice(BigDecimal markPrice) { this.markPrice = markPrice; }
- /** @return 策略是否处于活跃状态(非 STOPPED 且非 WAITING_KLINE) */
- public boolean isStrategyActive() { return state != StrategyState.STOPPED && state != StrategyState.WAITING_KLINE; }
- /** @return 累计已实现盈亏(平仓推送驱动累加) */
- public BigDecimal getCumulativePnl() { return cumulativePnl; }
- /** @return 当前未实现盈亏(每根 K 线实时计算) */
- public BigDecimal getUnrealizedPnl() { return unrealizedPnl; }
- /** @return Gate 用户 ID(用于私有频道订阅 payload) */
- public Long getUserId() { return userId; }
- /** @return 当前策略状态 */
- public StrategyState getState() { return state; }
- /** 注入WS客户端,用于订阅状态检查 */
- public void setWsClient(GateKlineWebSocketClient wsClient) { this.wsClient = wsClient; }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java
deleted file mode 100644
index 811bb20..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateKlineWebSocketClient.java
+++ /dev/null
@@ -1,380 +0,0 @@
-package com.xcong.excoin.modules.gateApi;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.gateApi.wsHandler.GateChannelHandler;
-import com.xcong.excoin.modules.okxNewPrice.utils.SSLConfig;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-import org.java_websocket.handshake.ServerHandshake;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Gate WebSocket 连接管理器。
- *
- * <h3>职责</h3>
- * 负责 TCP 连接的建立、维持和恢复。频道逻辑(订阅/解析)全部委托给 {@link GateChannelHandler} 实现类。
- *
- * <h3>生命周期</h3>
- * <pre>
- * init() → connect() → startHeartbeat()
- * destroy() → unsubscribe 所有 handler → closeBlocking() → shutdown 线程池
- * onClose() → reconnectWithBackoff() (最多 3 次,指数退避)
- * </pre>
- *
- * <h3>消息路由</h3>
- * <pre>
- * onMessage → handleMessage:
- * 1. futures.pong → cancelPongTimeout
- * 2. subscribe/unsubscribe → 日志
- * 3. error → 错误日志
- * 4. update/all → 遍历 channelHandlers → handler.handleMessage(response)
- * </pre>
- *
- * <h3>心跳机制</h3>
- * 采用双重检测:TCP 层的 WebSocket ping/pong + 应用层 futures.ping/futures.pong。
- * 10 秒未收到任何消息 → 发送 futures.ping;25 秒周期检查。
- *
- * <h3>线程安全</h3>
- * 连接状态用 AtomicBoolean(isConnected, isConnecting, isInitialized)。
- * 消息时间戳用 AtomicReference。心跳任务用 synchronized 保护。
- *
- * @author Administrator
- */
-@SuppressWarnings("ALL")
-@Slf4j
-public class GateKlineWebSocketClient {
-
- private static final String FUTURES_PING = "futures.ping";
- private static final String FUTURES_PONG = "futures.pong";
- private static final int HEARTBEAT_TIMEOUT = 10;
-
- /** WebSocket 地址,由 GateConfig 提供 */
- private final String wsUrl;
-
- /** Java-WebSocket 客户端实例 */
- private WebSocketClient webSocketClient;
- /** 心跳检测调度器 */
- private ScheduledExecutorService heartbeatExecutor;
- /** 心跳超时 Future */
- private volatile ScheduledFuture<?> pongTimeoutFuture;
- /** 最后收到消息的时间戳(毫秒) */
- private final AtomicReference<Long> lastMessageTime = new AtomicReference<>(System.currentTimeMillis());
-
- /** 连接状态 */
- private final AtomicBoolean isConnected = new AtomicBoolean(false);
- /** 连接中标记,防重入 */
- private final AtomicBoolean isConnecting = new AtomicBoolean(false);
- /** 初始化标记,防重复 init */
- private final AtomicBoolean isInitialized = new AtomicBoolean(false);
-
- /** 频道处理器列表,通过 addChannelHandler 注册 */
- private final List<GateChannelHandler> channelHandlers = new ArrayList<>();
-
- /** 重连等异步任务的缓存线程池(daemon 线程) */
- private final ExecutorService sharedExecutor = Executors.newCachedThreadPool(r -> {
- Thread t = new Thread(r, "gate-ws-worker");
- t.setDaemon(true);
- return t;
- });
-
- /**
- * @param wsUrl Gate WebSocket 地址(由 {@link GateConfig#getWsUrl()} 提供)
- */
- public GateKlineWebSocketClient(String wsUrl) {
- this.wsUrl = wsUrl;
- }
-
- /**
- * 注册频道处理器。需在 init() 前调用。
- *
- * @param handler 实现了 {@link GateChannelHandler} 接口的频道处理器
- */
- public void addChannelHandler(GateChannelHandler handler) {
- channelHandlers.add(handler);
- }
-
- /**
- * 初始化:建立 WebSocket 连接 → 启动心跳检测。
- * 使用 {@code AtomicBoolean} 防重入,同一实例只允许初始化一次。
- */
- public void init() {
- if (!isInitialized.compareAndSet(false, true)) {
- log.warn("[WS] 已初始化过,跳过重复初始化");
- return;
- }
- connect();
- startHeartbeat();
- }
-
- /**
- * 销毁:取消所有频道订阅 → 关闭 WebSocket 连接 → 关闭线程池。
- *
- * <h3>执行顺序</h3>
- * 先取消订阅(等待 500ms 确保发送完成),再 closeBlocking 关闭连接,
- * 最后 shutdown 线程池。先关连接再关线程池,避免 onClose 回调中的重连任务访问已关闭的线程池。
- */
- public void destroy() {
- log.info("[WS] 开始销毁...");
-
- if (webSocketClient != null && webSocketClient.isOpen()) {
- for (GateChannelHandler handler : channelHandlers) {
- handler.unsubscribe(webSocketClient);
- }
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("[WS] 取消订阅等待被中断");
- }
- }
-
- if (webSocketClient != null && webSocketClient.isOpen()) {
- try {
- webSocketClient.closeBlocking();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("[WS] 关闭连接时被中断");
- }
- }
-
- if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
- sharedExecutor.shutdown();
- }
-
- shutdownExecutorGracefully(heartbeatExecutor);
- if (pongTimeoutFuture != null) {
- pongTimeoutFuture.cancel(true);
- }
- shutdownExecutorGracefully(sharedExecutor);
-
- log.info("[WS] 销毁完成");
- }
-
- /**
- * 建立 WebSocket 连接。使用 SSLContext 配置 TLS 协议。
- *
- * <h3>连接成功回调</h3>
- * <ol>
- * <li>设置 isConnected=true,isConnecting=false</li>
- * <li>重置心跳计时器</li>
- * <li>依次订阅所有已注册的频道处理器</li>
- * <li>发送首次 ping</li>
- * </ol>
- *
- * <h3>连接关闭回调</h3>
- * 设置断连状态 → 取消心跳超时 → 异步触发指数退避重连(最多3次)。
- *
- * <h3>线程安全</h3>
- * 使用 {@code AtomicBoolean.isConnecting} 防止并发重连。
- */
- private void connect() {
- if (isConnecting.get() || !isConnecting.compareAndSet(false, true)) {
- log.info("[WS] 连接进行中,跳过重复请求");
- return;
- }
- try {
- SSLConfig.configureSSL();
- System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
- URI uri = new URI(wsUrl);
- if (webSocketClient != null) {
- try { webSocketClient.closeBlocking(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
- }
- webSocketClient = new WebSocketClient(uri) {
- @Override
- public void onOpen(ServerHandshake handshake) {
- log.info("[WS] 连接成功");
- isConnected.set(true);
- isConnecting.set(false);
- if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
- resetHeartbeatTimer();
- for (GateChannelHandler handler : channelHandlers) {
- handler.subscribe(webSocketClient);
- }
- sendPing();
- } else {
- log.warn("[WS] 应用正在关闭,忽略连接成功回调");
- }
- }
-
- @Override
- public void onMessage(String message) {
- lastMessageTime.set(System.currentTimeMillis());
- handleMessage(message);
- resetHeartbeatTimer();
- }
-
- @Override
- public void onClose(int code, String reason, boolean remote) {
- 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] 重连失败", e); }
- });
- } else {
- log.warn("[WS] 线程池已关闭,不执行重连");
- }
- }
-
- @Override
- public void onError(Exception ex) {
- log.error("[WS] 发生错误", ex);
- isConnected.set(false);
- }
- };
- webSocketClient.setConnectionLostTimeout(0);
- webSocketClient.connect();
- } catch (URISyntaxException e) {
- log.error("[WS] URI格式错误", e);
- isConnecting.set(false);
- }
- }
-
- /**
- * 消息分发:先处理系统事件(pong/subscribe/error),
- * 再把 update/all 事件路由到各 channelHandler。
- * <p>每个 handler 内部通过 channel 名称做二次匹配,匹配成功返回 true 则停止遍历。
- */
- private void handleMessage(String message) {
- try {
- JSONObject response = JSON.parseObject(message);
- String channel = response.getString("channel");
- String event = response.getString("event");
-
- if (FUTURES_PONG.equals(channel)) {
- log.debug("[WS] 收到 pong 响应");
- cancelPongTimeout();
- return;
- }
- if ("subscribe".equals(event)) {
- log.info("[WS] {} 订阅成功: {}", channel, response.getJSONObject("result"));
- for (GateChannelHandler handler : channelHandlers) {
- if (channel.equals(handler.getChannelName())) {
- handler.setSubscribed(true);
- break;
- }
- }
- return;
- }
- if ("unsubscribe".equals(event)) {
- log.info("[WS] {} 取消订阅成功", channel);
- return;
- }
- if ("error".equals(event)) {
- JSONObject error = response.getJSONObject("error");
- log.error("[WS] {} 错误, code:{}, msg:{}",
- channel,
- error != null ? error.getInteger("code") : "N/A",
- error != null ? error.getString("message") : response.getString("msg"));
- return;
- }
- if ("update".equals(event) || "all".equals(event)) {
- for (GateChannelHandler handler : channelHandlers) {
- if (handler.handleMessage(response)) return;
- }
- }
- } catch (Exception e) {
- log.error("[WS] 处理消息失败: {}", message, e);
- }
- }
-
- /**
- * 检查所有已注册的频道是否都已收到订阅成功确认。
- */
- public boolean areAllSubscribed() {
- if (channelHandlers.isEmpty()) return false;
- for (GateChannelHandler h : channelHandlers) {
- if (!h.isSubscribed()) return false;
- }
- return true;
- }
-
- // ---- heartbeat ----
-
- /**
- * 启动心跳检测器。
- * 使用单线程 ScheduledExecutor,每 25 秒检查一次心跳超时。
- */
- private void startHeartbeat() {
- if (heartbeatExecutor != null && !heartbeatExecutor.isTerminated()) heartbeatExecutor.shutdownNow();
- heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(r -> { Thread t = new Thread(r, "gate-ws-heartbeat"); t.setDaemon(true); return t; });
- heartbeatExecutor.scheduleWithFixedDelay(this::checkHeartbeatTimeout, 25, 25, TimeUnit.SECONDS);
- }
-
- /**
- * 重置心跳计时器:取消旧超时任务,提交新的 10 秒超时检测。
- */
- private synchronized void resetHeartbeatTimer() {
- cancelPongTimeout();
- if (heartbeatExecutor != null && !heartbeatExecutor.isShutdown()) {
- pongTimeoutFuture = heartbeatExecutor.schedule(this::checkHeartbeatTimeout, HEARTBEAT_TIMEOUT, TimeUnit.SECONDS);
- }
- }
-
- /**
- * 检查心跳超时:如果距离上次收到消息超过 10 秒,主动发送 futures.ping。
- */
- private void checkHeartbeatTimeout() {
- if (!isConnected.get()) return;
- if (System.currentTimeMillis() - lastMessageTime.get() >= HEARTBEAT_TIMEOUT * 1000L) sendPing();
- }
-
- /**
- * 发送应用层 futures.ping 消息。
- */
- private void sendPing() {
- try {
- if (webSocketClient != null && webSocketClient.isOpen()) {
- JSONObject pingMsg = new JSONObject();
- pingMsg.put("time", System.currentTimeMillis() / 1000);
- pingMsg.put("channel", FUTURES_PING);
- webSocketClient.send(pingMsg.toJSONString());
- log.debug("[WS] 发送 ping 请求");
- }
- } catch (Exception e) { log.warn("[WS] 发送 ping 失败", e); }
- }
-
- /**
- * 取消心跳超时检测任务。
- */
- private synchronized void cancelPongTimeout() {
- if (pongTimeoutFuture != null && !pongTimeoutFuture.isDone()) pongTimeoutFuture.cancel(true);
- }
-
- // ---- reconnect ----
-
- /**
- * 指数退避重连。初始延迟 5 秒,每次翻倍,最多重试 3 次。
- *
- * @throws InterruptedException 线程被中断
- */
- private void reconnectWithBackoff() throws InterruptedException {
- int attempt = 0, maxAttempts = 3;
- long delayMs = 5000;
- while (attempt < maxAttempts) {
- try { Thread.sleep(delayMs); connect(); return; } catch (Exception e) { log.warn("[WS] 第{}次重连失败", attempt + 1, e); delayMs *= 2; attempt++; }
- }
- log.error("[WS] 超过最大重试次数({}),放弃重连", maxAttempts);
- }
-
- /**
- * 优雅关闭线程池:先 shutdown,等待 5 秒,超时则 shutdownNow 强制中断。
- *
- * @param executor 需要关闭的线程池
- */
- private void shutdownExecutorGracefully(ExecutorService executor) {
- if (executor == null || executor.isTerminated()) return;
- try { executor.shutdown(); if (!executor.awaitTermination(5, TimeUnit.SECONDS)) executor.shutdownNow(); }
- catch (InterruptedException e) { Thread.currentThread().interrupt(); executor.shutdownNow(); }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
deleted file mode 100644
index b5de170..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateTradeExecutor.java
+++ /dev/null
@@ -1,402 +0,0 @@
-package com.xcong.excoin.modules.gateApi;
-
-import io.gate.gateapi.ApiClient;
-import io.gate.gateapi.api.FuturesApi;
-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.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * Gate REST API 异步执行器,所有下单/撤单操作经此类提交。
- *
- * <h3>设计目的</h3>
- * REST API 调用可能耗时数百毫秒,若在 WebSocket 回调线程中同步执行会阻塞消息处理,
- * 导致心跳超时误判。本类将所有网络 I/O 提交到独立单线程池异步执行。
- *
- * <h3>线程模型</h3>
- * <ul>
- * <li><b>单线程 + 有界队列(64)</b> — 保证下单顺序,避免并发竞争</li>
- * <li><b>CallerRunsPolicy</b> — 队列满时由提交线程直接执行,形成自然背压</li>
- * <li><b>Daemon 线程</b> — 60s 空闲自动回收</li>
- * </ul>
- *
- * <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>
- *
- * <h3>容错</h3>
- * <ul>
- * <li>止盈单创建失败 → 立即 marketClose() 市价平仓</li>
- * <li>取消订单失败 → 仅 warn 日志(可能已成交/已取消)</li>
- * </ul>
- *
- * @author Administrator
- */
-@Slf4j
-public class GateTradeExecutor {
-
- private static final String SETTLE = "usdt";
-
- /** Gate U 本位合约 REST API */
- private final FuturesApi futuresApi;
- /** 合约名称(如 ETH_USDT) */
- private final String contract;
-
- /** 交易线程池:单线程 + 有界队列 + 背压策略 */
- private final ExecutorService executor;
-
- public GateTradeExecutor(ApiClient apiClient, String contract) {
- this.futuresApi = new FuturesApi(apiClient);
- this.contract = contract;
- this.executor = new ThreadPoolExecutor(
- 1, 1,
- 60L, TimeUnit.SECONDS,
- new LinkedBlockingQueue<>(64),
- r -> {
- Thread t = new Thread(r, "gate-trade-worker");
- t.setDaemon(true);
- return t;
- },
- new ThreadPoolExecutor.CallerRunsPolicy()
- );
- ((ThreadPoolExecutor) executor).allowCoreThreadTimeOut(true);
- }
-
- /**
- * 优雅关闭:等待 10 秒让队列中的任务执行完毕,超时则强制中断。
- * 关闭后的 REST 调用将通过 CallerRunsPolicy 直接在提交线程执行。
- */
- public void shutdown() {
- executor.shutdown();
- try {
- executor.awaitTermination(10, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- executor.shutdownNow();
- }
- }
- /**
- * 提交一个通用任务到交易线程池末尾。
- * 利用单线程池的 FIFO 特性确保任务按提交顺序执行。
- */
- public void submitTask(Runnable task) {
- executor.execute(task);
- }
-
- /**
- * 异步 IOC 市价开多。quantity 为正数(如 "1")。
- *
- * @param quantity 开仓张数(正数)
- * @param onSuccess 成交成功回调(可为 null)
- * @param onFailure 成交失败回调(可为 null)
- */
- public void openLong(String quantity, Consumer<String> onSuccess, Runnable onFailure) {
- openPosition(quantity, "t-grid-long", "开多", onSuccess, onFailure);
- }
-
- /**
- * 异步 IOC 市价开空。quantity 为负数(如 "-1")。
- *
- * @param quantity 开仓张数(负数)
- * @param onSuccess 成交成功回调(可为 null)
- * @param onFailure 成交失败回调(可为 null)
- */
- public void openShort(String quantity, Consumer<String> onSuccess, Runnable onFailure) {
- openPosition(quantity, "t-grid-short", "开空", onSuccess, onFailure);
- }
-
- /**
- * 通用异步 IOC 市价下单。
- *
- * @param size 下单张数(正=开多 / 负=开空)
- * @param text 订单标记文本(如 "t-grid-long"),用于区分订单来源
- * @param label 日志标签(如 "开多"/"开空")
- * @param onSuccess 成功回调
- * @param onFailure 失败回调
- */
- private void openPosition(String size, String text, String label, Consumer<String> onSuccess, Runnable onFailure) {
- executor.execute(() -> {
- try {
- FuturesOrder order = new FuturesOrder();
- order.setContract(contract);
- order.setSize(size);
- order.setPrice("0");
- order.setTif(FuturesOrder.TifEnum.IOC);
- order.setText(text);
- FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, order, null);
- log.info("[TradeExec] {}成功, 价格:{}, id:{}", label, result.getFillPrice(), result.getId());
- String orderId = String.valueOf(result.getId());
- if (onSuccess != null) {
- onSuccess.accept(orderId);
- }
- } catch (Exception e) {
- log.error("[TradeExec] {}失败", label, e);
- if (onFailure != null) {
- onFailure.run();
- }
- }
- });
- }
-
- /**
- * 异步创建止盈条件单(仓位计划止盈止损)。
- *
- * <p>使用 Gate 的 {@code PriceTriggeredOrder} API:服务器监控价格,达到触发价后自动平指定张数。
- * order_type 使用 {@code plan-close-*-position}(仓位计划止盈止损),
- * 支持指定 size 部分平仓,多次触发的止盈单互不影响。
- *
- * <h3>为何不用 close-*-position</h3>
- * {@code close-long-position} / {@code close-short-position} 仅支持全部平仓(size=0),
- * 且双仓模式还需额外设置 {@code auto_size}。网格策略需要指定张数部分平仓,
- * 因此必须使用 {@code plan-close-long-position} / {@code plan-close-short-position}。
- *
- * @param triggerPrice 触发价格
- * @param rule 触发规则(NUMBER_1: ≥ 触发价,NUMBER_2: ≤ 触发价)
- * @param orderType stop 类型(plan-close-long-position / plan-close-short-position)
- * @param size 平仓张数(正=平空,负=平多)
- */
- public void placeTakeProfit(BigDecimal triggerPrice,
- FuturesPriceTrigger.RuleEnum rule,
- String orderType,
- String size,
- Consumer<String> onSuccess) {
- executor.execute(() -> {
- FuturesPriceTriggeredOrder order = buildTriggeredOrder(triggerPrice, rule, orderType, size);
- try {
- TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order);
- log.info("[TradeExec] 止盈单已创建, 触发价:{}, 类型:{}, size:{}, id:{},idstr:{}",
- triggerPrice, orderType, size, response.getId(), response.getIdString());
- String orderId = String.valueOf(response.getId());
- if (onSuccess != null) {
- onSuccess.accept(orderId);
- }
- } catch (Exception e) {
- log.error("[TradeExec] 止盈单创建失败, 触发价:{}, size:{}, 立即市价止盈", triggerPrice, size, e);
- marketClose(size);
- }
- });
- }
-
- /**
- * 市价止盈:在止盈条件单创建失败时立即市价平仓。
- * size 与止盈单保持一致(负=平多,正=平空)。
- */
- private void marketClose(String size) {
- try {
- FuturesOrder order = new FuturesOrder();
- order.setContract(contract);
- order.setSize(size);
- order.setPrice("0");
- order.setTif(FuturesOrder.TifEnum.IOC);
- order.setReduceOnly(true);
- order.setText("t-grid-mkt-close");
- FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, order, null);
- log.info("[TradeExec] 市价止盈成功, 价格:{}, size:{}, id:{}", result.getFillPrice(), size, result.getId());
- } catch (Exception e) {
- log.error("[TradeExec] 市价止盈也失败, size:{}", size, e);
- }
- }
-
- /**
- * 异步清除指定合约的所有止盈止损条件单。
- */
- public void cancelAllPriceTriggeredOrders() {
- executor.execute(() -> {
- try {
- futuresApi.cancelPriceTriggeredOrderList(SETTLE, contract);
- log.info("[TradeExec] 已清除所有止盈止损条件单");
- } catch (Exception e) {
- log.error("[TradeExec] 清除止盈止损条件单失败", e);
- }
- });
- }
-
- /**
- * <b>遗留方法 — 当前条件单策略未使用</b>
- *
- * <p>异步挂限价单(GTC),用于旧版网格限价开仓。当前策略改用
- * {@link #placeConditionalEntryOrder(BigDecimal, FuturesPriceTrigger.RuleEnum, String, Consumer, Runnable)}。
- *
- * @param price 限价价格
- * @param size 下单张数(正=多 / 负=空)
- * @param onSuccess 成功回调,接收 orderId(可为 null)
- * @param onFailure 失败回调(可为 null)
- */
- public void placeGridLimitOrder(BigDecimal price, String size, Consumer<String> onSuccess, Runnable onFailure) {
- executor.execute(() -> {
- try {
- FuturesOrder order = new FuturesOrder();
- order.setContract(contract);
- order.setSize(size);
- order.setPrice(price.toString());
- order.setTif(FuturesOrder.TifEnum.GTC);
- order.setText(size.startsWith("-") ? "t-grid-limit-short" : "t-grid-limit-long");
- FuturesOrder result = futuresApi.createFuturesOrder(SETTLE, order, null);
- log.info("[TradeExec] 限价单已挂, price:{}, size:{}, id:{}, status:{}", price, size, result.getId(), result.getStatus());
- if (onSuccess != null) {
- onSuccess.accept(String.valueOf(result.getId()));
- }
- } catch (Exception e) {
- log.error("[TradeExec] 限价单挂单失败, price:{}, size:{}", price, size, e);
- if (onFailure != null) {
- onFailure.run();
- }
- }
- });
- }
-
- /**
- * <b>遗留方法 — 当前条件单策略未使用</b>
- *
- * <p>异步取消指定限价单。当前策略改用 {@link #cancelConditionalOrder(String)}。
- *
- * @param orderId 订单 ID,为 null 时跳过
- */
- public void cancelOrder(String orderId,Consumer<String> onSuccess) {
- if (orderId == null) {
- return;
- }
- executor.execute(() -> {
- try {
- FuturesOrder cancelled = futuresApi.cancelFuturesOrder(SETTLE, orderId, null);
- log.info("[TradeExec] 订单已取消, id:{}, status:{}", orderId, cancelled.getStatus());
- if (onSuccess != null) {
- onSuccess.accept(orderId);
- }
- } catch (Exception e) {
- log.warn("[TradeExec] 取消订单失败(可能已成交), id:{}", orderId);
- }
- });
- }
-
- /**
- * 异步创建条件开仓单(价格触发后市价开仓)。
- *
- * <p>服务器监控价格,达到触发价后以市价 IOC 开仓。与止盈单不同,不设 order_type(默认开仓),
- * reduce_only=false。
- *
- * @param triggerPrice 触发价格
- * @param rule 触发规则(NUMBER_1: 最新价≥触发价时执行;NUMBER_2: 最新价≤触发价时执行)
- * @param size 开仓张数(正=开多,负=开空)
- * @param onSuccess 成功回调,接收 conditionOrderId
- * @param onFailure 失败回调
- */
- public void placeConditionalEntryOrder(BigDecimal triggerPrice,
- FuturesPriceTrigger.RuleEnum rule,
- String size,
- Consumer<String> onSuccess,
- Runnable onFailure) {
- executor.execute(() -> {
- 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(Long.parseLong(size));
- initial.setPrice("0");
- initial.setTif(FuturesInitialOrder.TifEnum.IOC);
- initial.setReduceOnly(false);
-
- FuturesPriceTriggeredOrder order = new FuturesPriceTriggeredOrder();
- order.setTrigger(trigger);
- order.setInitial(initial);
-
- TriggerOrderResponse response = futuresApi.createPriceTriggeredOrder(SETTLE, order);
- String orderId = String.valueOf(response.getId());
- String orderIdStr = response.getIdString();
- log.info("[TradeExec] 条件开仓单已创建, trigger:{}, rule:{}, size:{}, id:{},idStr:{}",
- triggerPrice, rule, size, orderId, orderIdStr);
- if (onSuccess != null) {
- onSuccess.accept(orderId);
- }
- } catch (Exception e) {
- log.error("[TradeExec] 条件开仓单创建失败, trigger:{}, size:{}", triggerPrice, size, e);
- if (onFailure != null) {
- onFailure.run();
- }
- }
- });
- }
-
- /**
- * 异步取消单个条件单。
- *
- * @param orderId 条件单 ID,为 null 时跳过
- */
- public void cancelConditionalOrder(String orderId,Consumer<String> onSuccess) {
- if (orderId == null) {
- return;
- }
- executor.execute(() -> {
- try {
- futuresApi.cancelPriceTriggeredOrder(SETTLE, Long.parseLong(orderId));
- log.info("[TradeExec] 条件单已取消, id:{}", orderId);
- if (onSuccess != null) {
- onSuccess.accept(orderId);
- }
- } catch (Exception e) {
- log.warn("[TradeExec] 取消条件单失败(可能已触发), id:{}", orderId);
- }
- });
- }
-
- /**
- * 构建 FuturesPriceTriggeredOrder 对象。
- *
- * <p>策略=0(价格触发),price_type=0(最新价),expiration=0(永不过期),
- * tif=IOC(立即成交或取消),reduce_only=true(只减仓不开新仓)。
- *
- * <h3>size 参数说明</h3>
- * <ul>
- * <li>plan-close-long-position:size 为负,表示平多仓多少张</li>
- * <li>plan-close-short-position:size 为正,表示平空仓多少张</li>
- * </ul>
- * 每次只平指定张数,不会全平仓位,多个止盈单可并存且互不影响。
- */
- private FuturesPriceTriggeredOrder buildTriggeredOrder(BigDecimal triggerPrice,
- FuturesPriceTrigger.RuleEnum rule,
- String orderType,
- String size) {
- 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(Long.parseLong(size));
- initial.setPrice("0");
- initial.setTif(FuturesInitialOrder.TifEnum.IOC);
- initial.setReduceOnly(true);
-
- FuturesPriceTriggeredOrder order = new FuturesPriceTriggeredOrder();
- order.setTrigger(trigger);
- order.setInitial(initial);
- order.setOrderType(orderType);
- return order;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientMain.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientMain.java
deleted file mode 100644
index 6725789..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientMain.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.xcong.excoin.modules.gateApi;
-
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.support.ClassPathXmlApplicationContext;
-
-/**
- * Gate 网格交易的独立测试入口(main 方法)。
- * 通过 Spring XML 上下文初始化管理器,运行一段时间后手动关闭。
- *
- * @author Administrator
- */
-public class GateWebSocketClientMain {
- /**
- * 独立启动入口:加载 Spring 上下文 → 获取管理器 Bean → 长时间运行 → 销毁。
- *
- * @param args 命令行参数(未使用)
- */
- public static void main(String[] args) throws InterruptedException {
- ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
- GateWebSocketClientManager manager = context.getBean(GateWebSocketClientManager.class);
-
- Thread.sleep(1200000000L);
-
- manager.destroy();
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java b/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
deleted file mode 100644
index aee7b1a..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateWebSocketClientManager.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.xcong.excoin.modules.gateApi;
-
-import com.xcong.excoin.modules.gateApi.wsHandler.handler.AutoOrdersChannelHandler;
-import com.xcong.excoin.modules.gateApi.wsHandler.handler.CandlestickChannelHandler;
-import com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionClosesChannelHandler;
-import com.xcong.excoin.modules.gateApi.wsHandler.handler.PositionsChannelHandler;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import java.math.BigDecimal;
-
-/**
- * Gate 模块 Spring 容器入口 — 组件组装 + 生命周期管理。
- *
- * <h3>组装顺序({@code @PostConstruct})</h3>
- * <ol>
- * <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>6 个频道处理器</h3>
- * <ol>
- * <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>
- *
- * @author Administrator
- */
-@Slf4j
-@Component
-public class GateWebSocketClientManager {
-
- /** WebSocket 连接管理器 */
- private GateKlineWebSocketClient wsClient;
- /** 网格交易策略服务 */
- private GateGridTradeService gridTradeService;
- /** 统一配置 */
- private GateConfig config;
-
- @PostConstruct
- public void init() {
- log.info("[管理器] 开始初始化...");
-
- try {
- //实盘
- config = GateConfig.builder()
- .apiKey("8a995c9662bbdfb0b2577f5f56cf4210")
- .apiSecret("4aaf5e7c97cf3bd14450c6703d4dc2af8647dee4c0a72272fa04280e83545d96")
- .contract("ETH_USDT")
- .leverage("100")
- .marginMode("CROSS")
- .positionMode("dual")
- .gridRate(new BigDecimal("0.003"))
- .expectedProfit(new BigDecimal("25"))
- .maxLoss(new BigDecimal("15"))
- .baseQuantity("15")
- .quantity("15")
- .restartGridSpan(6)
- .maxPositionSize(2)
- .priceScale(2)
- .contractMultiplier(new BigDecimal("0.01"))
- .unrealizedPnlPriceMode(GateConfig.PnLPriceMode.LAST_PRICE)
- .isProduction(true)
- .reopenMaxRetries(3)
- .build();
-
- // 1. 初始化交易服务:查用户ID → 切持仓模式 → 清条件单 → 平已有仓位 → 设杠杆
- gridTradeService = new GateGridTradeService(config);
- gridTradeService.init();
-
- // 2. 创建 WS 客户端并注册频道处理器
- wsClient = new GateKlineWebSocketClient(config.getWsUrl());
- wsClient.addChannelHandler(new CandlestickChannelHandler(config.getContract(), gridTradeService));
- wsClient.addChannelHandler(new PositionsChannelHandler(
- config.getApiKey(), config.getApiSecret(), config.getContract(), gridTradeService));
- wsClient.addChannelHandler(new PositionClosesChannelHandler(
- config.getApiKey(), config.getApiSecret(), config.getContract(), gridTradeService));
- wsClient.addChannelHandler(new AutoOrdersChannelHandler(
- config.getApiKey(), config.getApiSecret(), config.getContract(), gridTradeService));
- gridTradeService.setWsClient(wsClient);
- wsClient.init();
- log.info("[管理器] WS已连接, 已注册 4 个频道处理器");
-
- // 3. 激活策略,等待首根 K 线触发基底双开
- gridTradeService.startGrid();
- } catch (Exception e) {
- log.error("[管理器] 初始化失败", e);
- }
- }
-
- /**
- * 销毁:停止策略 → 关闭交易线程池 → 取消 WS 订阅 → 断开连接 → 关闭 WS 线程池。
- */
- @PreDestroy
- public void destroy() {
- log.info("[管理器] 开始销毁...");
- if (gridTradeService != null) {
- gridTradeService.stopGrid();
- }
- if (wsClient != null) {
- wsClient.destroy();
- }
- log.info("[管理器] 销毁完成");
- }
-
- /**
- * @return WebSocket 连接管理器实例
- */
- public GateKlineWebSocketClient getKlineWebSocketClient() { return wsClient; }
- /**
- * @return 网格交易策略服务实例
- */
- public GateGridTradeService getGridTradeService() { return gridTradeService; }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/celue.out b/src/main/java/com/xcong/excoin/modules/gateApi/celue.out
deleted file mode 100644
index 7d06d00..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/celue.out
+++ /dev/null
Binary files differ
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/gate-api.txt b/src/main/java/com/xcong/excoin/modules/gateApi/gate-api.txt
deleted file mode 100644
index f2d8ce7..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/gate-api.txt
+++ /dev/null
@@ -1,3067 +0,0 @@
-访问链接
-REST API BaseURL:
-
-实盘交易: https://api.gateio.ws/api/v4
-模拟交易:https://api-testnet.gateapi.io/api/v4
-合约实盘交易备选入口(只适用合约 API):https://fx-api.gateio.ws/api/v4
-#SDK
-可用 SDK:
-
-Py
-Python
-Java
-Java
-PHP
-PHP
-Go
-Go
-C#
-C#
-Node
-NodeJS
-JS
-Javascript
-部分 SDK 除了各接口的示例文档以外,还额外提供了调用 SDK 的示例应用程序。 示例应用程序提供了一个相对更加丰富的 SDK 使用示例,可以完整构建运行, 具体使用方式参考各 SDK 的示例程序说明
-
-Python(opens new window)
-Java(opens new window)
-C#(opens new window)
-Go(opens new window)
-#关于APIv4 Key升级
-2020 年 4 月
-
-APIv4 最开始合约的 Key 与现货的是分开管理的。不过从现在开始我们对 APIv4 的 Key 进行了升级改造。 用户现在可以创建多个 Key,而且可以为每个 Key 配置不同的操作权限。比如你可以创建一个 Key 同时拥有现货的读写和合约的读写权限,这样的话只需要一个 Key 就可以同时操作现货和合约交易。
-
-用户原有的 Key 会无缝迁移到新的管理方案,原先的 Key 在新的管理模式下,对应一个 Key 只配置了现货的权限,另一个 Key 只配置了合约的权限,用户可以使用新的管理模式对迁移后的 Key 重新配置权限。
-
-#与 APIv2 的区别
-APIv4 是一套全新的 HTTP REST API ,独立于 APIv2 。APIv4 提供了完整的交易功能,增强了 API 的认证鉴权。另外 APIv4 基于 OpenAPI 标准 (opens new window)书写,SDK 和文档通过同一套 API 标准生成,确保文档和程序的完整一致。
-
-APIv4 与 APIv2 在使用上有如下区别:
-
-两者的 API Key 是独立的。 APIv2 在个人账户的 APIKeys 入口申请。而 APIv4 在个人账户的 APIv4Keys 页面申请。
-APIv2 在交易操作上只支持现货交易,APIv4 支持现货、杠杆和合约的完整交易操作
-如何选择:
-
-如果需要杠杆或合约交易,选择 APIv4
-如果只需要现货交易或者钱包操作,则根据个人当前状况自行选择
-#市商申请
-为进一步提高平台盘口深度和交易流动性,我们将通过公开透明的方式招募机构做市商,根据为平台流动性做出的贡献情况,为专业机构做市商提供专业的做市商手续费率方案。
-
-提供 Gate UID
-提供他所交易量截图或VIP等级等
-简述做市方法及规模
-提供以上内容提交至 mm@gate.com ,我们将在3个工作日内受理。
-
-VIP11及以上需在个人中心开启GT抵扣,才可享有专业市商费率。
-
-#技术支持
-使用过程中如有问题或者建议,您可以选择以下任意方式联系我们:
-
-提交工单反馈
-在线工单反馈
-将您的联系方式与问题发送至 mm@gate.com ,我们将为您分配技术专员为您服务
-如您遇到API错误,建议您整理以下内容,方便我们为您快速分析问题:
-
-问题描述
-Gate UID
-请求的地址与参数
-错误代码
-返回结果
-即使是提交问题,也切勿将API key信息提交给客服及他人,否则会有严重的资产风险。如果已经不小心泄漏,请将已有API删除并重新生成。
-
-#Changelog
-v4.106.79
-
-2026-05-07
-
-补充统一账户快捷还款 definitions/unified/ 模型(QuickEstimatedRepayment、QuickRepaymentRequest、QuickRepaymentResponse、UnifiedQuickRepayDebtItem、UnifiedQuickRepayAvailableItem、UnifiedQuickRepayRepaidInfo、UnifiedQuickRepayUsedInfo)及 examples/unified/ 示例 JSON,与 unified-api.yaml 中 $ref 对齐,保证仓库内可解析及 collect/SDK/文档链路可用
-v4.106.78
-
-2026-05-04
-
-crossex-api.yaml:GET /crossex/coin_discount_rate(listCrossexCoinDiscountRate)的 summary 由「查询币种折扣率(分所模式下,保证金币种的折扣率)」精简为「查询币种折扣率」,去掉括号内的「分所模式/保证金币种」说明性补充
-v4.106.75
-
-2026-04-28
-
-p2p-merchant-api.yaml:示例与演示数据脱敏刷新(商家/对手方场景、含 SWIFT 等支付方式);示例字段命名与文档 schema 对齐(如 seller_realname、currency_type);精简冗长历史示例块
-quant-api.yaml:SpotGridStrategy.strategy_params_preview 的 additionalProperties 收紧为 type: string;SpotGridStrategyDetail 的 base_info、metrics、position 改为动态 additionalProperties 映射,不再 $ref 至 AIHubPortfolio*,并从本规格中移除独立的 AIHubPortfolioBaseInfo / AIHubPortfolioMetrics / AIHubPortfolioPosition 模型定义
-crossex-api.yaml:杠杆映射类响应的英文描述;账单流水分页接口移除查询参数 statement_type;列表项字段由 statement_type 更名为 type 并更新说明;精简 CrossexSymbol 等字段的中英描述措辞
-wallet-api.yaml:GET /wallet/withdraw_status 的 page 参数英文说明改为 “Page number”
-文档 i18n:抽取 P2P 商家接口新增文案并补齐英文 messages.po 译文
-v4.106.73
-
-2026-04-27
-
-Contract 模型(definitions/mercury/Contract.yaml):新增 enable_circuit_breaker,表示新上市合约是否启用标记价格熔断机制(平台若对某新开盘合约市场启用该机制以降低开盘后剧烈波动与爆仓风险,将提前公告)
-ContractStat 模型(definitions/mercury/ContractStat.yaml):新增 long_liq_usd_new、short_liq_usd_new(USDT 结算下按 long_liq_size / short_liq_size、multiplier 与 mark_price 计算的计价币种爆仓量);新增 top_long_size、top_short_size、long_taker_size、short_taker_size、top_long_account、top_short_account、long_users、short_users 等大户持仓、吃单量与用户数量类字段
-v4.106.72
-
-2026-04-27
-
-文档构建:新增 slate/gate/widdershins-split.yaml 与 slate/gate/zh_CN/widdershins-split.yaml,用于按 tag 拆分的 Widdershins 生成;移除 includedDocs,使每个 tag 子页仅包含该 tag 的路径与相关 schema(避免在每个 tag 页面重复注入 changelog/general/api/errors/authentication/faq 等通用章节);新增 tagGroups,为 TradFi tag 配置分组展示标题
-v4.106.71
-
-2026-04-27
-
-#统一账户 — 快捷还款(新增接口)
-新增 GET /unified/estimated_quick_repayment(getEstimatedQuickRepayment)— 返回预估快捷还款信息(按币种负债与可用于还款的币种及余额等)。
-新增 POST /unified/quick_repayment(createQuickRepayment)— 执行快捷还款;请求体为 debt_currencies、available_currencies 字符串数组。上述接口仅适用于统一账户 跨币种保证金模式 或 组合保证金模式。
-请求/响应拆至 definitions/unified/,title 与代码生成一致(QuickEstimatedRepayment、QuickRepaymentInfo、QuickRepaymentResp,数组元素类型 UnifiedDebtCurrencies、UnifiedAvailableCurrencies、RepaidInfo、UsedInfo;对应文件名 UnifiedQuickRepayDebtItem.yaml、UnifiedQuickRepayAvailableItem.yaml、UnifiedQuickRepayRepaidInfo.yaml、UnifiedQuickRepayUsedInfo.yaml),示例在 examples/unified/。
-两个接口均补充典型 400 / 401 / 403 响应,schema 使用 definitions/GateErrorResponse.yaml。
-文案修正:去掉重复「模式」,统一为「跨币种保证金模式与组合保证金模式」。
-#其他变更
-wallet-api.yaml:子账户余额列表接口查询参数 page 的英文说明更新
-futures-api.yaml:从 FuturesOrderAmendment 移除内部字段 contract,并精简改单请求示例
-crossex-api.yaml:摘要与示例更新,示例字段对齐(tif、fee_currency、avg_price 等);账户流水相关:去掉 statement_type 查询参数,响应字段更名为 type,及其他示例/文案调整
-quant-api.yaml:量化/网格策略相关 schema 与字段说明调整
-p2p-merchant-api.yaml:P2P 商家端接口路径与 schema 更新
-文档翻译:补全快捷还款 definitions 与 4xx 文案的英文词条,并同步修正快捷还款 scope 文案;另含量化接口「交易数量」多行说明等译文
-v4.106.70
-
-2026-04-21
-
-文档:BizType 参考表新增 5 个子账号内部资金划转 biz_id —— 150215、150216、150217、150218、150219(均归类为「子账号资金划转」)
-assetswap-api.yaml:新增响应包装 schema ConfigResp(GET /asset-swap/config 使用)和 OrderQueryV1Resp(GET /asset-swap/orders/v1/{id} 使用),对应响应中的 data $ref 切换到新 wrapper
-quant-api.yaml(SpotGridStrategy.strategy_params_preview、SpotGridStrategyDetail.base_info / metrics / position):将 additionalProperties 由强制 type: string 放宽为任意类型({});强类型 SDK 将由原本的 Map<String, String> 变为 Map<String, Object>
-v4.106.61
-
-2026-04-14
-
-SubAccountBalance 模型,新增 locking 字段(币种锁定金额)
-为 FuturesPriceTriggeredOrder、TriggerOrderResponse 补充 id_string 字段说明:与数值 id 表示同一自动订单,为 id 的十进制字符串,避免 JavaScript 等环境下 int64 精度问题;展示或需要字符串唯一标识时建议使用;与 futures.orders、futures.autoorders 等推送中同名字段含义一致。覆盖合约价格触发相关 REST:/futures/{settle}/price_orders(创建、列表、批量撤销、查询、撤销、修改)。
-PartnerCommissionHistory、AgencyCommissionHistory 中合伙人/代理商返佣列表项:将 commission_amount、commission_asset 字段说明由「交易金额」调整为「返佣金额」,与字段语义一致
-Contract 模型:补充 interest_rate 字段说明(字符串形式的小数比率),适用于 GET /futures/{settle}/contracts 与 GET /futures/{settle}/contracts/{contract} 返回
-开发者文档:将 Agent 相关文档品牌由 Gate for AI 更名为 Gate for AI Agent(页面标题、正文与侧栏文案);产品主页与帮助中心链接更新为 gate-for-ai-agent 路径
-v4.106.60
-
-2026-04-13
-
-文档翻译:补全双币理财相关接口与查询参数英文译文
-MCP 接口映射:新增 GET /earn/dual/project-recommend(getDualProjectRecommend)
-v4.106.59
-
-2026-04-10
-
-新增 GET /earn/dual/order-refund-preview(getDualOrderRefundPreview)— 双币投资提前赎回预览
-新增 POST /earn/dual/order-refund(placeDualOrderRefund)— 双币订单提前赎回
-新增 POST /earn/dual/modify-order-reinvest(modifyDualOrderReinvest)— 双币订单复投修改
-新增 GET /earn/dual/project-recommend(getDualProjectRecommend)— 双币推荐项目
-GET /earn/dual/investment_plan:新增可选查询参数 coin、type、quote_currency、sort、page、page_size
-GET /earn/dual/orders:新增可选查询参数 type、status、coin
-双币理财相关新增模型 DualOrderRefundPreview、DualOrderRefundParams、DualModifyOrderReinvestParams、DualProjectRecommend 及对应示例
-v4.106.58
-
-2026-04-03
-
-Contract 模型:新增 contract_type 字段 — 合约分类类型(如 stocks-股票、metals-金属、indices-指数、forex-外汇、commodities-大宗商品等)
-GET /wallet/sub_account_balances 接口:新增 page 和 limit 查询参数,支持分页
-v4.106.57
-
-2026-04-01
-
-WithdrawalRecord 模型:调整提现终态时间字段说明 — 当 status = DONE 时表示提现成功时间(不再与 block_number > 0 绑定)
-WithdrawalRecord、WithdrawalsDel 模型:精简 DONE 状态枚举说明(移除「block_number > 0 时表示已完成上链」的表述)
-GET /crossex/rule/risk_limits 返回:CrossexRiskLimitTier 新增必填字符串字段 quick_cal_amount(速算额)
-v4.106.56
-
-2026-03-31
-
-文档与翻译:修复英文 messages.po 重复条目与损坏的 msgid;对卡券中心两处超长 Markdown 说明清空不兼容的英文 msgstr(JSON 字面量中的 % 与 gettext 占位规则冲突,后续补全英文翻译)
-v4.106.55
-
-2026-03-30
-
-新增 GET /api/v4/rebate/partner/data/aggregated 接口(getPartnerAgentDataAggregated)— 合伙人代理数据聚合统计(返佣金额、交易量、净手续费、客户数;按业务类型可选返回交易人数)
-新增聚合返佣接口对应的 PartnerDataAggregated、PartnerDataAggregatedResponse 模型定义
-v4.106.54
-
-2026-03-20
-
-新增 POST /api/v4/earn/autoinvest/plans/create 接口 - 创建定投计划
-新增 POST /api/v4/earn/autoinvest/plans/update 接口 - 更新定投计划
-新增 POST /api/v4/earn/autoinvest/plans/stop 接口 - 停止定投计划
-新增 POST /api/v4/earn/autoinvest/plans/add_position 接口 - 立即加仓
-新增 GET /api/v4/earn/autoinvest/coins 接口 - 查询支持定投的币种
-新增 POST /api/v4/earn/autoinvest/min_invest_amount 接口 - 查询可投资的最小金额
-新增 GET /api/v4/earn/autoinvest/plans/records 接口 - 查询计划执行记录
-新增 GET /api/v4/earn/autoinvest/orders 接口 - 查询计划执行记录详情(订单明细)
-新增 GET /api/v4/earn/autoinvest/config 接口 - 查询投资币种配置
-新增 GET /api/v4/earn/autoinvest/plans/detail 接口 - 查询定投计划详情
-新增 GET /api/v4/earn/autoinvest/plans/list_info 接口 - 查询定投计划列表
-v4.106.52
-
-2026-03-29
-
-FuturesInitialOrder 模型:新增可选 amount 字段(字符串),用于小数合约张数;与 size 同时存在时以 amount 为准
-FuturesUpdatePriceTriggeredOrder 模型:新增可选 amount 字段(字符串),语义与 size 相同
-SpotPricePutOrder 模型:将 time_in_force 列入必填字段
-v4.106.51
-
-2026-03-27
-
-文档站点新增 Gate for AI 开发者指南菜单:全面指导 AI Agent 通过 MCP(Model Context Protocol)协议与 CLI 工具接入 Gate.io API
-文档包含:MCP 服务端点、AI Skills(40+ 预置工作流)、接入方式(Cursor、Claude、CLI)、鉴权方式、代码示例等
-v4.106.50
-
-2026-03-26
-
-下线理财 ETH2 质押相关接口:POST /earn/staking/eth2/swap(swapETH2)、GET /earn/staking/eth2/rate_records(rateListETH2)
-下线理财结构性产品相关接口:GET /earn/structured/products(listStructuredProducts)、GET /earn/structured/orders(listStructuredOrders)、POST /earn/structured/orders(placeStructuredOrder)
-移除上述功能在 definitions/earn/、examples/earn/ 下的相关模型定义与示例
-v4.106.49
-
-2026-03-25
-
-新增 FuturesOrderTimerange 模型;GET /futures/{settle}/orders_timerange 返回列表项 schema 由 FuturesOrder 调整为 FuturesOrderTimerange
-FuturesOrder 模型:order_value、trade_value 设为只读并标记 x-external: false(公开 SDK 不包含)
-GET /futures/{settle}/orders/{order_id}:优化路径参数说明(按订单 ID / 自定义 text 查询的规则)
-v4.106.48
-
-2026-03-25
-
-Currency 模型:新增 category 字段(字符串数组,币种分类,取值示例见字段说明:stocks/metals/indices/forex/commodities 等)
-v4.106.45
-
-2026-03-20
-
-BatchOrder 模型:完善 finish_as 字段 ioc 和 poc 枚举值描述,准确反映基于 time_in_force 设置的订单撤销原因
-Order 模型:完善 finish_as 字段 ioc 和 poc 枚举值描述,准确反映基于 time_in_force 设置的订单撤销原因
-UnifiedAccount 模型:新增 mode 字段(统一账户模式:经典账户/跨币种保证金/组合保证金/单币种保证金)
-UnifiedAccount 模型:新增 balance_version 字段(余额版本号)
-UnifiedAccount 模型:优化 available、freeze、equity、iso_balance 等多个保证金相关字段的描述
-更新 UidPushWithdrawalResp 模型:id 字段类型从 integer (int64) 变更为 string
-CrossEx API GET /crossex/fee 费率接口:响应结构调整为数组形式,支持返回多个交易所(BINANCE、OKX、GATE、BYBIT)的费率信息,新增 exchange_type 字段标识交易所类型
-CrossEx API POST /crossex/convert 闪兑接口:响应新增 order_id(订单号)和 text(订单号文本)字段
-CrossEx API 利率扣息类型说明优化:interest_type 字段新增 PERIODIC_ISOLATED 枚举值(整点负债收息)
-v4.106.44
-
-2026-03-20
-
-更新 TradFi API:GET /tradfi/symbols/detail(querySymbolDetail)查询交易对详情接口改为需认证(移除 security: [])
-v4.106.43
-
-2026-03-19
-
-新增理财定期 API:GET /earn/fixed-term/product(listEarnFixedTermProducts)、GET /earn/fixed-term/product/{asset}/list(listEarnFixedTermProductsByAsset)、POST /earn/fixed-term/user/lend(createEarnFixedTermLend)、GET /earn/fixed-term/user/lend(listEarnFixedTermLends)、POST /earn/fixed-term/user/pre-redeem(createEarnFixedTermPreRedeem)、GET /earn/fixed-term/user/history(listEarnFixedTermHistory)
-FuturesUpdatePriceTriggeredOrder 模型:order_id 字段新增 format: int64
-futures-auto-order-api.yaml:价格触发订单响应 ID schema 新增 format: int64
-parameters.yaml:pos_margin_mode 和 dual_side 参数从选填改为必填
-v4.106.42
-
-2026-03-19
-
-UnifiedAccount 模型:新增 mode 字段(统一账户模式:经典账户/跨币种保证金/组合保证金/单币种保证金)
-UnifiedAccount 模型:新增 balance_version 字段(余额版本号)
-UnifiedAccount 模型:优化 available、freeze、equity、iso_balance 等多个保证金相关字段的描述
-v4.106.38
-
-2026-03-14
-
-更新 amendOptionsOrder 请求体:新增必填字段 contract(期权合约名称)
-v4.106.36
-
-2026-03-13
-
-新增 PUT /options/orders/{order_id} 期权改单接口(amendOptionsOrder)
-更新 TradFi API:GET /tradfi/users/mt5-account 查询 mt5 账号信息接口改为需认证(移除 security: [])
-v4.106.34
-
-2026-03-12
-
-更新 Alpha API /alpha/orders 查询订单接口:currency、side、status 参数从必填改为选填
-CrossEx API 新增支持 BYBIT 交易所
-v4.106.33
-
-2026-03-10
-
-BrokerCommission、BrokerTransaction 模型:source 字段新增 TradFi 类型(返佣交易类型:Spot、Futures、Options、Alpha、TradFi)
-v4.106.32
-
-2026-03-05
-
-更新 P2P Merchant API:请求内容类型从 multipart/form-data 改为 application/json,请求体 schema 提取到独立定义文件
-更新 P2P Merchant API:移除各接口独立的 security: [],改为继承全局 apiv4 认证
-更新 P2P Merchant API:complete_rate_month、orders_buy_rate_month、transactions_month、transactions_all 字段类型从 integer 改为 number
-v4.106.31
-
-2026-03-03
-
-PUT /futures/{settle}/price_orders/{order_id} 接口路径变更为 PUT /futures/{settle}/price_orders/amend/{order_id}
-FuturesUpdatePriceTriggeredOrder 模型:order_id 字段类型从 string 变更为 integer
-BatchOrder 模型:finish_as 字段新增枚举值:liquidate_cancelled(爆仓撤销)、small(数量太小)、depth_not_enough(深度不足)、trader_not_enough(对手方不足)、poc(未满足挂单策略)、fok(未完全成交)、price_protect_cancelled(价格保护撤单)、unknown(未知)
-Order 模型:更新 cancelled_reason 字段中 ioc 枚举值的描述,明确该状态也适用于 poc/rvt/rat/rpi 类型订单被判定为 taker 而拒绝的场景
-v4.106.30
-
-2026-03-03
-
-PUT /futures/{settle}/price_orders/{order_id} 接口路径变更为 PUT /futures/{settle}/price_orders/amend/{order_id}
-FuturesUpdatePriceTriggeredOrder 模型:order_id 字段类型从 string 变更为 integer
-v4.106.29
-
-2026-03-02
-
-CrossEx API 所有接口路径新增 /crossex 前缀
-POST /crossex/orders 接口:限频描述新增最大挂单数限制(1,000 个)
-GET /crossex/positions/leverage 接口:响应结构从数组改为 Map
-GET /crossex/margin_positions/leverage 接口:响应结构从数组改为 Map
-平仓接口从 DELETE /position 变更为 POST /crossex/position
-POST /unified/portfolio_calculator 接口:更新描述,支持所有已开放期权交易的标的物币种
-MockSpotBalance 模型:equity 字段描述移除币种限制
-MockSpotOrder 模型:count 字段描述移除币种限制
-MockFuturesPosition、MockFuturesOrder 模型:contract 字段描述更新为支持所有已开放期权交易的标的物的USDT永续合约
-MockOptionsPosition、MockOptionsOrder 模型:options_name 字段描述更新为支持所有期权合约市场
-v4.106.28
-
-2026-02-28
-
-更新翻译内容,优化TradFi文档描述
-v4.106.27
-
-2026-02-26
-
-新增闪兑相关错误码文档
-新增币种 total_supply(总供应量)和 market_cap(市值)字段描述
-Contract 模型:新增 funding_impact_value 字段(资金费用深度影响额)
-FuturesBBOOrder 模型:删除 limit_vip 字段
-v4.106.26
-
-2026-02-12
-
-新增 P2P Merchant API 接口:POST /p2p/merchant/books/ads_list(获取广告列表)
-v4.106.25
-
-2026-02-11
-
-POST /earn/dual/orders 接口:分离请求和响应模型
-新增 PlaceDualInvestmentOrderParams 模型(双币理财下单请求参数)
-PlaceDualInvestmentOrder 模型:新增响应字段(id、copies、invest_amount、settlement_amount、create_time、complete_time、status、invest_currency、exercise_currency、exercise_price、settlement_price、settlement_currency、apy_display、apy_settlement、delivery_time)
-DualGetPlans 模型:标记 per_value 字段为已废弃
-v4.106.24
-
-2026-02-09
-
-新增 OTC API 接口:POST /otc/quote(法币和稳定币询价)、POST /otc/order/create(法币下单)、POST /otc/stable_coin/order/create(稳定币下单)、GET /otc/get_user_def_bank(获取用户默认银行卡信息)、GET /otc/bank_list(获取用户银行卡列表)、POST /otc/order/paid(法币订单设置已付款)、POST /otc/order/cancel(法币订单取消)、GET /otc/order/list(法币订单列表)、GET /otc/stable_coin/order/list(稳定币订单列表)、GET /otc/order/detail(法币订单详情)
-v4.106.23
-
-2026-02-06
-
-OptionsOrder 模型:为 id 字段添加 x-is-bigint: true 标记,确保 JavaScript SDK 正确处理大数类型
-v4.106.22
-
-2026-02-03
-
-Order 模型:新增 price_protect_cancelled 状态,表示因价格保护导致撤单的订单
-v4.106.21
-
-2026-02-03
-
-更新子账户划转及划转记录相关 amount 字段描述
-v4.106.20
-
-2026-02-02
-
-新增合约追踪委托接口:POST /futures/{settle}/autoorder/v1/trail/create、POST /futures/{settle}/autoorder/v1/trail/stop、POST /futures/{settle}/autoorder/v1/trail/stop_all、GET /futures/{settle}/autoorder/v1/trail/list、GET /futures/{settle}/autoorder/v1/trail/detail、POST /futures/{settle}/autoorder/v1/trail/update、GET /futures/{settle}/autoorder/v1/trail/change_log
-新增 TrailOrder、TrailChangeLog 模型(追踪委托及改单记录)
-SwapCoin 模型:side 字段类型改为 integer(0-质押、1-赎回),移除 pid 的 int32 格式限制
-新增 BatchFundingRatesRequest、BatchFundingRatesResponse 模型(批量查询资金费率的请求/响应结构)
-FuturesAccount 模型:删除 cross_settle 字段
-TotalBalance 模型:新增账户类型(meme_box、options、payment)
-UnifiedMarginTiers 模型:修复描述格式
-GET /futures/{settle}/funding_rate 接口:时间间隔枚举新增 10s 选项
-v4.106.19
-
-2026-01-27
-
-Contract 模型,新增 enable_decimal 字段,用于标识是否支持小数字符串类型合约张数。当该字段为 true 时,表示该合约支持小数张数(即 size 字段可以使用小数字符串类型);当为 false 时,表示该合约不支持小数张数(即 size 字段只能使用整数类型)
-v4.106.18
-
-2026-01-26
-
-更新 API 文档:添加现货限频规则说明章节,包括现有限频规则、新增限频规则和成交率计算公式
-新增 P2P Merchant API 接口:POST /p2p/merchant/account/get_user_info(获取账户信息)、POST /p2p/merchant/account/get_counterparty_user_info(获取对手方信息)、POST /p2p/merchant/account/get_myself_payment(获取支付方式列表)、POST /p2p/merchant/transaction/get_pending_transaction_list(获取进行中订单)、POST /p2p/merchant/transaction/get_completed_transaction_list(获取所有/历史订单)、POST /p2p/merchant/transaction/get_transaction_details(查询订单详情)、POST /p2p/merchant/transaction/confirm-payment(确认付款)、POST /p2p/merchant/transaction/confirm-receipt(确认收款)、POST /p2p/merchant/transaction/cancel(取消订单)、POST /p2p/merchant/books/place_biz_push_order(发布广告挂单)、POST /p2p/merchant/books/ads_update_status(广告单状态更新)、POST /p2p/merchant/books/ads_detail(查询广告详情)、POST /p2p/merchant/books/my_ads_list(获取我的广告列表)、POST /p2p/merchant/chat/get_chats_list(获取聊天记录)、POST /p2p/merchant/chat/send_chat_message(发送文本消息)、POST /p2p/merchant/chat/upload_chat_file(上传聊天文件)
-v4.106.17
-
-2026-01-22
-
-GET /loan/multi_collateral/currency_quota 接口,CurrencyQuota 模型新增两个返回字段:left_quota_fixed(定期币种剩余可借/质押限额)和 left_quote_usdt_fixed(定期币种换算成USDT的剩余币种限额)
-下线:移除单币抵押借币 API 接口(/loan/collateral/**)。这些接口已不再可用。
-FuturesUpdatePriceTriggeredOrder 模型:order_id 字段类型修改为字符串,新增 close 字段
-SpotPriceTrigger 模型:expiration 字段从必填项中移除
-v4.106.16
-
-2026-01-20
-
-更新订单状态描述:将订单模型(mercury 和 pilot 模块中的 BatchOrder、InternalOrder、Order、OrderCancel,以及 portfolio 模块中的 PortfolioSpotOrders)中 "closed" 状态的描述从 "全部成交" 改为 "已结束的订单",使描述更加清晰准确
-v4.106.15
-
-添加crossex菜单、其子菜单websocket和rest页
-新增完整的crossex API端点,支持跨交易所交易,包括订单管理、仓位管理、账户管理和历史数据查询
-实现crossex WebSocket支持,提供实时市场数据和订单更新
-v4.106.14
-
-Order 模型,text 字段描述更新,补充强平订单场景说明(pm_liquidate、comb_margin_liquidate、scm_liquidate 这三种场景代表全仓强平订单,liquidate 代表逐仓强平订单)
-Trade 模型,text 字段描述更新,补充强平订单场景说明(pm_liquidate、comb_margin_liquidate、scm_liquidate 这三种场景代表全仓强平订单,liquidate 代表逐仓强平订单)
-v4.106.13
-
-新增 OTC API 接口:POST /otc/quote(法币和稳定币询价)、POST /otc/order/create(法币下单)、POST /otc/stable_coin/order/create(稳定币下单)、GET /otc/get_user_def_bank(获取用户默认银行卡信息)、POST /otc/order/paid(法币订单设置已付款)、POST /otc/order/cancel(法币订单取消)、GET /otc/order/list(法币订单列表)、GET /otc/stable_coin/order/list(稳定币订单列表)、GET /otc/order/detail(法币订单详情)、GET /otc/stable_coin/order/detail(稳定币订单详情)、GET /otc/get_api_order_uid(获取通过api/v4接口下单的用户)
-修复 OTC API 规范:为所有接口添加 operationId,将 requestBody 内容类型从 multipart/form-data 更新为 application/json,为所有 tags 和 responses 添加描述,配置 servers 和 security 方案
-v4.106.12
-
-新增 GET /earn/dual/balance 接口,查询双币理财资产
-新增 GET /futures/{settle}/get_leverage/{contract} 接口,获取指定模式的杠杆信息
-新增 POST /futures/{settle}/positions/{contract}/set_leverage 接口,更新指定模式的杠杆,简化leverage接口复杂逻辑
-新增 POST /futures/{settle}/set_position_mode 接口,设置持仓模式,替换dual_mode接口
-POST /futures/{settle}/positions/{contract}/reverse 接口,新增 pos_margin_mode 参数(仓位保证金模式,分仓模式必传,取值isolated/cross)
-FuturesAccount 模型,新增 enable_dual_plus 字段,表示是否支持分仓模式
-FuturesAccount 模型,position_mode 字段描述更新,明确持仓模式:single-单向持仓,dual-双向持仓,dual_plus-分仓
-FuturesOrder 模型,新增 pos_margin_mode 字段,表示仓位保证金模式(isolated - 逐仓, cross - 全仓,只在简易分仓模式下传递)
-Position 模型,新增 pos_margin_mode 字段,表示仓位保证金模式(isolated - 逐仓, cross - 全仓)
-Position 模型,新增 lever 字段,表示仓位当前杠杆,逐仓和全仓都可以该字段表示,逐步替换当前的leverage和cross_leverage_limit
-新增 FuturesLeverage 模型定义,用于杠杆查询响应
-v4.106.11
-
-BatchOrder、CurrencyPair、Order 模型中 slippage 字段描述更新,明确为现货市价下单支持的最大滑点比率,以下单时的市场最新价格为基准计算(示例:0.03即3%)
-POST /spot/batch_orders 接口,响应描述更新,说明响应包含多个订单对象,订单对象具体结构参考 /spot/orders 下单接口的结构
-v4.106.10
-
-GET /account/rate_limit 接口,新增描述说明该接口暂未开放使用
-GET /wallet/order_status 接口,更新接口说明和描述,明确为查询主子账户划转状态
-v4.106.9
-
-PUT /futures/{settle}/price_orders/{order_id} 接口,修复请求体模型 FuturesUpdatePriceTriggeredOrder 字段名错误:contact 字段改为 order_id,并将 order_id 字段标记为必填
-POST /loan/collateral/orders 接口标记为已废弃/下线
-v4.106.8
-
-Contract 模型,新增 market_order_slip_ratio 字段,表示合约市价下单支持的最大滑点比率,比率计算以市场最新价格为基准
-Contract 模型,新增 market_order_size_max 字段,表示合约市价下单支持的最大张数,默认值为0,为默认值时取order_size_max字段作为最大张数限制
-FuturesOrder 模型,新增 market_order_slip_ratio 字段,表示市价下单自定义最大滑点比率,不传和未返回表示使用合约默认配置
-BatchFuturesOrder 模型,新增 market_order_slip_ratio 字段,表示订单响应中返回的最大滑点比率
-v4.106.7
-
-Contract 模型,新增 market_order_slip_ratio 字段,表示合约市价下单支持的最大滑点比率,比率计算以市场最新价格为基准
-Contract 模型,新增 market_order_size_max 字段,表示合约市价下单支持的最大张数,默认值为0,为默认值时取order_size_max字段作为最大张数限制
-FuturesOrder 模型,新增 market_order_slip_ratio 字段,表示市价下单自定义最大滑点比率,不传和未返回表示使用合约默认配置
-BatchFuturesOrder 模型,新增 market_order_slip_ratio 字段,表示订单响应中返回的最大滑点比率
-GET /unified/batch_borrowable 接口,currencies 参数添加 style: form 和 explode: false,优化数组参数格式
-GET /unified/estimate_rate 接口,currencies 参数添加 style: form 和 explode: false,优化数组参数格式
-GET /loan/multi_collateral/current_rate 接口,currencies 参数添加 style: form 和 explode: false,优化数组参数格式
-更新限频规则文档:在钱包私有接口限频规则中新增 POST /withdrawals/push 接口限频说明(1r/10s)
-v4.106.6
-
-GET /options/order_book 接口响应模型从 FuturesOrderBook 更新为 OptionsOrderBook,实现期权与合约 API 的更好分离
-GET /options/trades 接口响应模型从 FuturesTrade 更新为 OptionsTrade,实现期权与合约 API 的更好分离
-GET /options/candlesticks 接口响应模型从 FuturesCandlestick 更新为 OptionsCandlestick,实现期权与合约 API 的更好分离
-新增 OptionsOrderBook 和 OptionsTrade 模型定义,为期权 API 响应提供专门的模型
-v4.106.5
-
-新增资产流水编码:150102(币种回购-扣款)、150101(币种回购-加款)、143(币种更名扣款)、144(币种更名加款)、707(现货同币种结算-转出)、708(现货同币种结算-转入)
-v4.106.4
-
-CurrencyPair 模型,新增 up_rate 和 down_rate 字段,展示报价最大涨跌幅百分比
-Contract 模型,新增 funding_rate_limit 字段,表示资金费率上限值
-v4.106.3
-
-GET /futures/{settle}/accounts 接口说明更新,支持查询经典合约账户和统一账户
-PUT /futures/{settle}/accounts 接口,enable_evolved_classic 字段标记为已废弃
-GET /futures/{settle}/positions/{contract} 接口说明更新,明确在同一合约市场下持有双向仓位时的查询方式
-POST /futures/{settle}/positions/{contract}/margin 接口说明更新,附加新风险限额规则链接,说明应使用杠杆调整接口而非直接修改保证金
-POST /futures/{settle}/positions/{contract}/leverage 接口参数说明更新,明确 leverage 参数(用于逐仓,需要 cross_leverage_limit 为空)和 cross_leverage_limit 参数(用于全仓,需要 leverage 设为 0)
-POST /futures/{settle}/dual_comp/positions/{contract}/risk_limit 接口说明更新,附加风险限额规则链接
-GET /options/accounts 接口说明更新,支持查询经典期权账户和统一账户
-Position 模型字段描述更新,优化杠杆和风险管理相关说明:leverage(逐仓杠杆倍数,0 表示全仓模式)、leverage_max(基于当前仓位规模的最大杠杆)、maintenance_rate(梯度式维持保证金率计算)、liq_price(预估强平价,仅供参考)、initial_margin 和 maintenance_margin(扩大适用范围说明)、realised_pnl(详细说明包含平仓结算、资金费和手续费)、pnl_pnl(平仓结算盈亏)、pnl_fund(资金费盈亏)、pnl_fee(总手续费)、history_pnl(所有历史平仓结算盈亏)、cross_leverage_limit(简化说明)
-FuturesAccount 和 DeliveryAccount 模型更新:total 字段更新说明"仅适用于经典合约账户",position_margin 标记为已废弃,order_margin 更新为"所有未完成订单的起始保证金",enable_evolved_classic 和 enable_new_dual_mode 标记为已废弃,margin_mode 枚举值扩展新增值 3(单币种保证金模式)
-OptionsAccount 模型更新:total 和 equity 字段添加统一账户限制说明,liq_triggered 描述更新为"账户是否处于强平状态",margin_mode 枚举值扩展新增值 3,unrealised_pnl 描述增强附带计算公式
-MarginAccount 模型更新:account_type 描述更新删除"risk"选项,risk 字段标记为已废弃,mmr 描述优化
-Contract 和 DeliveryContract 模型更新:quanto_multiplier 概念更新为"合约乘数",maintenance_rate 明确为"第一档维持保证金率要求",mark_type 和 funding_cap_ratio 标记为已废弃,mark_price_round 简化为"标记价格的最小单位"
-OptionsContract 模型更新:tag 更新为"到期周期,有 day、week、month",multiplier 更新为"期权合约乘数",underlying_price 更新为"对应交割日期的远期期货价格",mark_price_round 简化说明,order_price_deviate 和 trade_id 标记为已废弃,orders_limit 优化为"每个用户在该盘口最多可挂的订单数量"
-FuturesTicker 和 DeliveryTicker 模型更新:quanto_base_rate 字段标记为已废弃
-OptionsTicker 模型更新:leverage 计算公式更新并添加参考说明
-FuturesTrade 和 DeliveryTrade 模型更新:is_internal 字段标记为已废弃
-OptionsMyTrade 和 OptionsPosition 模型更新:underlying_price 字段更新为"对应交割日期的远期期货价格"
-PositionTimerange 模型更新:leverage_max 和 maintenance_rate 描述优化
-FuturesAutoDeleverage 模型更新:leverage 和 cross_leverage_limit 描述明确,更好理解保证金模式
-风险限额梯度模型更新(FuturesRiskLimitTier、FuturesLimitRiskTiers、DeliveryLimitRiskTiers):统一 maintenance_rate 描述为"第一档维持保证金率要求"
-MarginLeverageTier 模型更新:优化 upper_limit(基于杠杆的最大借币限额)、mmr(梯度保证金要求下的综合维持保证金率)和 leverage(基于当前负债规模的最大杠杆倍数)描述
-CreateUniLoan 和 UniLoanInterestRecord 模型更新:type 字段描述更新为"借贷类型,margin 表示为杠杆借币"
-v4.106.2
-
-新增 GET /wallet/getLowCapExchangeList 接口,获取低价值币种列表
-v4.106.1
-
-Position 和 FuturesAutoDeleverage 模型中 leverage 和 cross_leverage_limit 字段描述更新,明确全仓/逐仓模式下的使用说明
-POST /futures/{settle}/positions/{contract}/leverage 接口中 leverage 和 cross_leverage_limit 参数描述更新,明确使用要求
-PUT /sub_accounts/{user_id}/keys/{key} 接口说明更新,提示此接口无法修改 mode 账户类型属性
-v4.106.0
-
-为解决支持小数下单问题,将所有 Futures 相关接口中的 size、数量相关字段从 integer 类型统一改为 string 类型。受影响的模型包括:FuturesOrder(size、iceberg、left)、BatchFuturesOrder(size、iceberg、left)、Position(size、trade_long_size、trade_short_size)、PositionTimerange(size)、MyFuturesTrade(size、close_size)、MyFuturesTradeTimeRange(size、close_size)、FuturesTrade(size)、FuturesOrderBook(asks 和 bids 中的 size)、FuturesCandlestick(v)、FuturesLiquidate(size、order_size)、FuturesLiqOrder(position_size、size、order_size)、FuturesAutoDeleverage(size、entry_size)、FuturesCollusionOrder(size、left)、FuturesBatchAmendOrderRequest(size)、Contract(order_size_min、order_size_max、trade_size、position_size)、ContractStat(long_liq_size、short_liq_size、open_interest)
-Position 模型中新增 pid 字段,用于分仓仓位 ID
-所有语言 SDK(Go、C#、Java、Python、PHP、TypeScript)统一添加 X-Gate-Size-Decimal: 1 默认请求头,确保服务端正确处理 size 字段
-新建 12 个独立的 Delivery Schema 文件,用于分离 Delivery 和 Futures 对象:DeliveryAccount、DeliveryAccountBook、DeliveryOrder、DeliveryOrderBook、DeliveryPosition、DeliveryPositionClose、DeliveryTrade、DeliveryMyTrade、DeliveryLiquidate、DeliveryInsuranceRecord、DeliveryLimitRiskTiers、DeliveryCloseAllPositionsResponse
-v4.105.32
-
-POST /futures/{settle}/positions/{contract}/leverage 接口描述更新,新增仓位模式切换规则详解、使用示例和风险警告,提升接口使用的安全性和清晰度
-v4.105.31
-
-TradeFee 模型中 taker_fee 和 maker_fee 字段描述更新,明确为现货交易费率
-v4.105.29
-
-GET /spot/my_trades 接口响应中新增 deal 字段,显示本次成交总额
-GET /unified/accounts 接口响应中新增 options_order_loss 字段,显示期权挂单损失(单位USDT),在组合保证金模式下有效,接口响应中 spot_order_loss 字段描述更新,明确表示现货挂单损失,在跨币种模式和组合保证金模式下有效, cross_balance 和 iso_balance 字段描述更新,现在在单币种保证金模式、跨币种保证金模式下均有效
-v4.105.28
-
-DELETE /withdrawals/{withdrawal_id} 接口响应中新增 block_number 字段,显示区块编号
-GET /unified/accounts 接口响应中 cross_balance 和 iso_balance 字段描述更新,现在在单币种保证金模式、跨币种保证金模式下均有效
-v4.105.27
-
-更新 DepositRecord、WithdrawalRecord 和 WithdrawalsDel 模型中 status 字段的描述,使其更加清晰
-充值状态枚举中移除 FINAL 状态,优化 DONE 状态描述
-v4.105.24
-
-GET /wallet/currency_chains 接口描述更新,流通性或者价值极低的币种不支持api操作,请通过Web或App页面进行查询以及处理
-GET /wallet/withdraw_status 接口描述更新,流通性或者价值极低的币种不支持api操作,请通过Web或App页面进行查询以及处理
-v4.105.20
-
-DELETE /withdrawals/{withdrawal_id} 接口响应模型更新为使用 WithdrawalsDel 结构
-GET /wallet/deposits 接口响应中新增 BLOCKED、DEP_CREDITED、FINAL 状态值
-GET /wallet/currency_chains 接口响应中新增 is_deposit_disabled 字段
-v4.105.19
-
-GET /spot/currency_pairs 和 GET /spot/currency_pairs/{currency_pair} 接口,fee 字段标记为已废弃
-POST /earn/staking/eth2/swap 接口描述从"ETH2兑换"更新为"ETH兑换",ETH2更名为GTETH
-GET /earn/staking/eth2/rate_records 接口描述更新为查询GTETH历史收益率
-v4.105.18
-
-新增 PUT /futures/{settle}/price_orders 接口,用于修改单个价格触发订单
-修改价格触发订单请求中新增 settle、order_id、size、price、trigger_price、price_type 和 auto_size 参数
-v4.105.11
-
-新增 GET /account/main_keys 接口,查询主账户所有API Key信息
-GET /spot/currency_pairs 和 GET /spot/currency_pairs/{currency_pair} 接口,fee 字段标记为已废弃
-v4.105.10
-
-新增 POST /futures/{settle}/bbo_orders 接口,档位BBO合约下单功能
-POST /futures/{settle}/price_orders 接口,合约价格触发单中的 price 和 rule 字段更新为必填
-v4.105.9
-
-GET /futures/{settle}/positions 接口,返回值增加 settlement_currency 字段,支持多结算币种
-POST /earn/uni/lends 接口,请求参数新增 auto_renew 字段,支持自动续借功能
-GET /spot/trades 接口,查询参数新增 trade_type 字段,支持按交易类型过滤
-v4.105.8
-
-GET /unified/accounts 接口,返回值增加 margin_mode 字段,标识账户保证金模式
-GET /spot/my_trades 接口,返回值增加 fee_currency 字段,显示手续费币种
-v4.105.7
-
-GET /futures/{settle}/positions 接口,返回值增加 liquidation_price 字段,用于风险管理
-POST /spot/orders 接口,请求参数新增 stop_loss 和 take_profit 字段,支持高级订单类型
-GET /unified/accounts 接口,返回值增加 total_balance 字段,显示总余额
-v4.105.6
-
-新增 GET /wallet/saved_address 接口,查询已保存的提现地址
-GET /wallet/withdrawals 接口,返回值增加 network_fee 字段,显示网络手续费
-GET /spot/currency_pairs 接口,返回值增加 min_amount 和 max_amount 字段,显示交易限额
-v4.105.5
-
-GET /futures/{settle}/orders 接口,返回值增加 order_type 字段,区分订单类型
-GET /spot/candlesticks 接口,interval 参数新增支持 30s 时间间隔
-GET /margin/accounts 接口,返回值增加 available_balance 字段,显示可用余额
-v4.105.4
-
-GET /futures/{settle}/tickers 接口,返回值增加 funding_rate_next 字段,显示下期资金费率
-GET /unified/accounts 接口,返回值增加 cross_leverage 字段,显示全仓杠杆倍数
-v4.105.3
-
-GET /futures/{settle}/positions 接口,查询参数新增 position_side 字段,支持对冲模式
-GET /earn/dual/orders 接口,查询参数新增 investment_type 字段,按投资类型过滤
-GET /futures/{settle}/accounts 接口,返回值增加 unrealized_pnl 字段,显示未实现盈亏
-v4.105.2
-
-GET /spot/fee 接口,返回值增加 maker_fee_rate 和 taker_fee_rate 字段,显示手续费率
-GET /futures/{settle}/contracts 接口,返回值增加 settle_currency 字段,显示结算币种
-v4.105.1
-
-POST /futures/{settle}/orders 接口,请求参数新增 time_in_force 字段,设置订单有效期类型
-GET /wallet/deposits 接口,查询参数新增 network 字段,按网络过滤充值记录
-v4.105.0
-
-GET /unified/accounts 接口,返回值增加 portfolio_margin 字段,显示组合保证金信息
-POST /futures/{settle}/positions/mode 接口,请求参数新增 position_mode 字段,设置持仓模式
-v4.104.9
-
-POST /futures/{settle}/orders 接口,请求参数新增 reduce_only 字段,支持只减仓订单
-GET /spot/orders 接口,查询参数新增 account_type 字段,按账户类型过滤
-GET /futures/{settle}/accounts 接口,返回值增加 funding_balance 字段,显示资金余额
-v4.104.8
-
-GET /earn/uni/currencies 接口,返回值增加 apr 字段,显示年化收益率
-GET /earn/dual/investment_plan 接口,返回值增加 lock_period 字段,显示锁定期
-v4.104.7
-
-GET /spot/order_book 接口,返回值增加 order_book_id 字段,用于订单簿版本控制
-POST /wallet/transfers 接口,请求参数新增 client_order_id 字段,支持客户端订单ID
-GET /spot/accounts 接口,返回值增加 trading_fee_rate 字段,显示交易手续费率
-v4.104.6
-
-GET /futures/{settle}/tickers 接口,返回值增加 mark_price 字段,显示标记价格
-新增 GET /futures/{settle}/insurance 接口,查询保险基金信息
-GET /futures/{settle}/positions 接口,返回值增加 isolated_margin 字段,显示逐仓保证金
-v4.104.5
-
-GET /spot/my_trades 接口,返回值增加 order_id 字段,用于交易与订单关联
-GET /unified/loans 接口,查询参数新增 currency 字段,按币种过滤借贷记录
-GET /unified/accounts 接口,返回值增加 borrow_amount 字段,显示借贷金额
-v4.104.4
-
-GET /spot/price_orders 接口,返回值增加 trigger_price 字段,显示触发价格
-GET /futures/{settle}/contracts 接口,返回值增加 maintenance_rate 字段,显示维持保证金率
-v4.104.3
-
-GET /futures/{settle}/positions 接口,查询参数新增 hedge_mode 字段,支持对冲模式查询
-GET /earn/dual/orders 接口,查询参数新增 status 字段,按状态过滤订单
-GET /unified/accounts 接口,返回值增加 cross_margin_leverage 字段,显示全仓杠杆倍数
-v4.104.2
-
-GET /futures/{settle}/my_trades 接口,返回值增加 settlement_size 字段,显示结算数量
-新增 GET /wallet/total_balance 接口,查询账户总余额
-GET /margin/accounts 接口,返回值增加 available_margin 字段,显示可用保证金
-v4.104.1
-
-POST /spot/orders 接口,请求参数新增 post_only 字段,支持只做maker订单
-GET /futures/{settle}/orders 接口,查询参数新增 contract 字段,按合约过滤订单
-GET /futures/{settle}/funding_rate 接口,返回值增加 funding_time 字段,显示资金费率时间
-v4.104.0
-
-新增 GET /unified/risk_units 接口,统一账户风险单位计算
-GET /unified/accounts 接口,返回值增加 risk_level 字段,显示风险等级
-POST /unified/orders 接口,请求参数新增 auto_borrow 字段,支持自动借贷
-v4.103.0
-
-GET /spot/account_book 接口新增 code 查询参数和响应字段,支持按特定编码过滤账户流水记录
-closeAllPositions 操作新增 text 参数,支持在一键平仓时添加订单备注
-新增资产流水编码详细文档,包含超过300个交易编码的详细说明
-v4.102.6
-
-优化赚币兑换响应结构 SwapCoinStruct,新增 pid、subtype、exchange_amount、updateStamp、protocol_type、client_order_id、source 字段
-v4.102.0
-
-GET /unified/accounts接口, 返回值增加is_all_collateral(是否所有币种均作为保证金)、balances下新增字段enabled_collateral(币种开启作为保证金)
-新增 POST /unified/collateral_currencies 接口,跨币种下可设置抵押币种
-v4.101.9
-
-新增 GET /futures/{settle}/risk_limit_table 接口,根据table_id查询风险限额梯度表
-合约账户模型新增 enable_tiered_mm 字段,支持梯度式维持保证金计算
-持仓模型新增 risk_limit_table(风险限额表)和 average_maintenance_rate(平均维持保证金率)字段,增强风险管理
-合约风险限额梯度新增 deduction 字段,用于维持保证金速算扣减额
-新增模型:FuturesRiskLimitTier(风险限额梯度档位)和 FuturesRiskLimitTierList(风险限额梯度表列表)
-优化 POST /earn/staking/swap 接口响应结构,增强兑换订单详情信息
-v4.100.0
-
-新增alpha账户查询和账户流水查询功能
-优化alpha API币种和ticker查询接口描述,参数使用说明
-新增 GET /earn/staking/coins 接口,查询链上赚币币种
-新增 POST /earn/staking/swap 接口,链上赚币兑换
-经纪商佣金和交易API新增 sub_broker_info 子经纪商信息对象字段
-v4.99.0
-
-GET /spot/accounts 接口, 返回值增加refresh_time字段
-移除 PUT /earn/uni/interest_reinvest 接口
-v4.98.0
-
-新增 /earn/uni/rate 接口, 币种预估年化利率
-GET /spot/currency_pairs、GET /spot/currency_pairs/{currency_pair}接口, 返回值增加delisting_time、trade_url 字段
-v4.97.0
-
-新增 GET /unified/batch_borrowable 接口, 批量查询统一账户最多可借
-GET /spot/candlesticks 接口, interval支持1s粒度
-新增 GET /earn/uni/chart 接口, 余币宝币种年化走势图
-新增 POST /futures/{settle}/positions/cross_mode 接口, 切换全逐仓模式
-v4.96.0
-
-GET /futures/{settle}/accounts 接口, 返回值新增cross_margin_balance,cross_mmr,cross_imr字段
-v4.95.0
-
-GET /spot/account_book 接口, 查询参数新增 code 字段,返回值增加 code 字段
-新增 GET /unified/transferables 接口, 批量查询统一账户最多可转出
-新增 GET /margin/user/loan_margin_tiers 接口, 查询当前市场下用户自身杠杆借贷梯度
-新增 GET /margin/loan_margin_tiers 接口, 查询当前市场杠杆借贷梯度
-新增 POST /margin/leverage/user_market_setting 接口, 设置用户市场杠杆倍数
-新增 GET /margin/user/account 接口, 查询用户逐仓杠杆账户列表
-v4.94.0
-
-新增 GET /unified/currencies 接口, 统一账户支持的借贷币种列表
-GET /unified/accounts 接口, 查询参数新增 sub_uid 字段
-v4.93.0
-
-GET /earn/dual/investment_plan 接口, 查询参数新增 plan_id 字段
-GET /earn/dual/orders 接口, 查询参数新增 from、to、page、limit 字段;
-GET /earn/dual/orders 接口, 返回值增加 text 字段
-POST /earn/dual/orders 接口, 返回值增加 text 字段
-新增 GET /earn/staking/eth2/rate_records 接口, GTETH历史收益率查询
-v4.92.1
-
-2025-02-27
-
-GET /spot/currencies 接口,返回值增加 chains 字段
-GET /spot/currencies/{currency} 接口,返回值增加 chains 字段
-GET /spot/currencies 接口,返回值废弃 withdraw_disabled、withdraw_delayed、deposit_disabled 字段
-GET /spot/currencies/{currency} 接口,返回值废弃 withdraw_disabled、withdraw_delayed、deposit_disabled 字段
-v4.92.0
-
-2025-02-24
-
-GET /spot/currencies 接口,返回值增加 name 字段
-GET /spot/currency_pairs 接口,返回值增加 base_name、quote_name 字段
-GET /spot/price_orders 接口, 查询参数新增 unified 类型
-GET /unified/accounts 接口, 查询参数新增 sub_uid 字段
-v4.91.0
-
-2025-02-10
-
-2025-04-01 之后我们将移除以下接口,请尽快迁移至新接口
-
-v4.90.0
-
-2025-01-20
-
-GET /wallet/push 接口, 查询参数新增transaction_type
-新增 GET /rebate/user/sub_relation 接口, 查询指定用户是否在体系内
-GET /futures/{settle}/liq_orders 接口, 返回值新增order_size字段
-GET /spot/currency_pairs 接口,返回值增加 type 字段
-v4.88.0
-
-2024-12-24
-
-新增 GET /spot/insurance_history 接口, 查询现货保险基金历史数据
-GET /unified/accounts 接口,返回值增加 cross_balance、iso_balance、im、mm、imr、mmr、margin_balance、available_margin
-PUT /unified/unified_mode 接口,新增单币种保证金模式
-v4.87.0
-
-新增 GET /unified/history_loan_rate 接口, 获取历史借币利率
-v4.86.0
-
-2024-12-02
-
-新增 GET /wallet/order_status 划转状态查询
-GET /futures/{settle}/positions 接口,返回值增加 update_id
-v4.85.0
-
-2024-11-11
-
-POST /futures/{settle}/orders、POST /spot/batch_order 接口,请求头新增 x-gate-exptime字段
-POST /futures/{settle}/dual_mode 接口,返回增加 cross_order_margin、cross_initial_margin、cross_maintenance_margin、cross_unrealised_pnl、cross_available、isolated_position_margin 字段
-v4.84.0
-
-2024-11-04
-
-新增 GET /loan/multi_collateral/current_rate 接口, 查询币种活期利率
-GET /spot/tickers 接口, 返回值增加 lowest_size、highest_size 字段
-POST /earn/dual/orders 接口, 请求体新增 amount 字段
-v4.83.0
-
-2024-10-28
-
-新增 GET /unified/leverage/user_currency_config 接口, 查询用户最大、最小可设置币种杠杆倍数
-新增 GET /unified/leverage/user_currency_setting 接口, 获取用户币种杠杆倍数
-新增 POST /unified/leverage/user_currency_setting 接口, 设置币种杠杆倍数
-GET /futures/{settle}/account_book 接口,返回值增加 id 字段
-GET /unified/currency_discount_tiers 接口,返回值增加 leverage 字段
-v4.82.0
-
-2024-10-14
-
-新增 GET /account/rate_limit 接口, 获取用户限流信息. 详情请见成交比率限频
-GET /account/detail 接口, 返回值增加 copy_trading_role 字段
-v4.81.0
-
-2024-09-30
-
-新增 POST /options/countdown_cancel_all 接口, 倒计时取消订单
-GET /wallet/push 接口, 返回值增加 message 字段
-GET /futures/{settle}/funding_rate 接口, 新增 from、to 查询字段
-POST /earn/dual/orders 接口, 返回值增加 is_max 字段
-v4.80.0
-
-2024-09-09
-
-新增 GET /options/mmp 接口, MMP查询
-新增 POST /options/mmp 接口, MMP设置
-新增 POST /options/mmp/reset 接口, MMP重置
-GET /wallet/withdrawals接口, 返回值增加 block_number 字段
-v4.79.0
-
-2024-09-02
-
-GET /unified/interest_records 接口,新增 from、to 查询字段
-GET /unified/unified_mode 接口,返回值增加 options 字段
-PUT /unified/unified_mode 接口,新增 options 字段
-v4.78.0
-
-2024-08-19
-
-新增 GET /wallet/push 接口, 获取记录
-新增 POST /withdrawals/push 接口, 现货主账号之间划转,划转双方不可为子账号
-新增 GET /futures/{settle}/batch_amend_orders 接口, 批量修改指定 ID 的订单
-GET /futures/{settle}/my_trades 接口, 返回增加 close_size 字段
-POST /wallet/transfers 接口, 返回增加 tx_id 字段
-v4.77.0
-
-2024-08-05
-
-新增 GET /sub_accounts/unified_mode 接口,获取子帐号模式
-GET /rebate/broker/commission_history 接口,新增 from、to 查询字段
-GET /rebate/broker/transaction_history 接口,新增 from、to 查询字段
-v4.76.0
-
-2024-07-22
-
-新增 GET /rebate/partner/sub_list 接口,伙人下级列表
-GET /flash_swap/currency_pairs 接口,新增 page、limit 查询字段
-PATCH /spot/orders/{order_id} 接口,新增 order_id、currency_pair、account 字段
-DELETE /spot/orders/{order_id} 接口,新增 order_id、currency_pair、account 字段
-v4.75.1
-
-2024-07-08
-
-新增 GET /delivery/{settle}/risk_limit_tiers 接口,查询风险限额等级
-新增 GET /rebate/partner/transaction_history 接口,合伙人获取推荐用户的交易记录
-GET /unified/loan_records 接口,返回值增加 borrow_type 字段
-GET /futures/{settle}/position_close 接口,返回值增加 accum_size 字段
-v4.75.0
-
-2024-06-24
-
-新增 GET /account/debit_fee 接口,查询GT抵扣配置
-新增 POST /account/debit_fee 接口,设定GT抵扣
-v4.74.1
-
-2024-06-11
-
-针对移动端可视区域DOM优化
-v4.74.0
-
-2024-05-29
-
-新增 GET /unified/loan_margin_tiers 接口, 查询统一账户借贷梯度保证金
-v4.73.0
-
-2024-05-27
-
-POST /wallet/small_balance接口,新增 is_all 字段
-POST /spot/cancel_batch_orders接口,返回值增加 text字段
-GET /unified/accounts接口,返回值增加 funding、funding_version、use_funding字段
-v4.72.0
-
-2024-05-13
-
-GET /sub_accounts/{user_id}/keys接口,返回值增加 last_access字段
-GET /futures/{settle}/risk_limit_tiers接口,返回值增加 contract字段
-v4.71.0
-
-2024-04-23
-
-GET /wallet/saved_address接口,新增 page 查询字段
-新增 GET /api/v4/rebate/user/info 接口, 获取用户返佣信息
-新增 POST /unified/portfolio_calculator 接口,组合保证金计算器计算
-新增 GET /unified/risk_units 接口, 获取用户风险单元详情
-新增 PUT /unified/unified_mode 接口, 设置统一账户模式
-新增 GET /unified/unified_mode 接口, 查询统一账户模式
-v4.70.0
-
-2024-04-08
-
-GET /futures/{settle}/positions接口,返回值增加 pnl_pnl、pnl_fund、pnl_fee字段
-GET /futures/{settle}/position_close接口,返回值增加 pnl_pnl、pnl_fund、pnl_fee字段
-v4.69.0
-
-2024-03-25
-
-POST /delivery/{settle}/price_orders接口,返回值增加 text 字段
-v4.68.0
-
-2024-03-18
-
-新增 GET /unified/currency_discount_tiers 接口,查询统一账户梯度式discount
-GET /unified/loans接口,新增 type 查询字段,返回值增加 type 字段
-GET /unified/interest_records接口,新增 type 查询字段,返回值增加 type 字段
-v4.67.0
-
-2024-03-11
-
-POST /spot/orders,POST /spot/batch_orders接口,返回值增加 filled_amount 字段
-限频规则中,钱包提现接口限速描述,由10r/10s更正为1r/3s(并无修改原本限流行为)
-v4.66.1
-
-2024-02-19
-
-新增 GET /wallet/small_balance 接口,获取可兑换的小额币种清单
-新增 GET /wallet/small_balance_history 接口,获取可兑换的小额币种历史纪录
-新增 GET /unified/estimate_rate 接口,查询统一账户的预估利率
-v4.65.0
-
-2024-01-29
-
-GET /spot/batch_fee 接口,返回值增加 debit_fee 字段
-DELETE /account/stp_groups/{stp_id}/users 接口,新增 user_id 请求字段
-现货API下单增加异步支持模式,ACK,RESULT,FULL,详情请见SPOT API
-v4.64.0
-
-2024-01-22
-
-GET /loan/multi_collateral/orders 接口,新增 order_type 查询字段
-GET /loan/multi_collateral/orders 接口,返回值增加 order_type,fixed_type,fixed_rate,expire_time,auto_renew,auto_repay 字段
-GET /loan/multi_collateral/repay 接口,返回值增加before_ltv,after_ltv 字段
-新增 GET /loan/multi_collateral/fixed_rate 接口,查询币种7日固定利率和30日固定利率
-GET /wallet/total_balance 接口,返回值增加unrealised_pnl,borrowed 字段
-v4.63.0
-
-2024-01-15
-
-GET /wallet/currency_chains 接口,返回值增加 decimal 字段
-新增 GET /futures/{settle}/risk_limit_tiers 接口,查询风险限额等级
-v4.62.0
-
-2024-01-02
-
-新增 POST /futures/{settle}/batch_cancel_orders 接口,用户可批量撤销订单
-新增多币质押API (/loan/multi_collateral/**)
-v4.61.0
-
-2023-12-18
-
-GET /rebate/broker/commission_history 和 GET /rebate/broker/commission_history 接口,新增经纪商获取返佣记录
-v4.60.0
-
-2023-12-01
-
-新的 Unified API 已经上线, 旧的 /portfoli/* 接口已被弃用 (2023-12-31 移除)
-新增理财产品 API (/earn/**)
-GET /futures/{settle}/account_book 接口,返回值增加 trade_id 字段
-v4.59.0
-
-2023-11-22
-
-GET /futures/{settle}/contracts 接口,返回值增加 funding_cap_ratio 字段
-GET /delivery/{settle}/account_book 接口,返回值增加 contract 字段
-GET /wallet/withdraw_status 接口,返回值增加 withdraw_percent_on_chains 字段
-GET /portfolio/accounts 接口,返回值增加 leverage 字段
-v4.58.0
-
-2023-11-03
-
-GET /account/detail 接口, 返回值新增 tier 字段
-GET /spot/currency_pairs 接口, 返回值新增 max_base_amount、max_quote_amount 字段
-v4.57.0
-
-2023-10-20
-
-新增进出网关时间记录,详情参考网关入出站时间
-POST /spot/orders 接口,新增支持保证金帐户类型
-GET /spot/trades 接口,返回值新增 sequence_id 字段
-GET /spot/account_book 接口,返回值新增 text 字段
-GET /spot/my_trades 接口,返回值新增 text 字段
-新增 POST /spot/amend_batch_orders 接口,用户可以批量修改订单
-新增 PUT /earn/uni/interest_reinvest 接口,用户可以设置利息复投开关
-GET /portfolio/spot/orders、 GET /portfolio/spot/orders、GET /portfolio/spot/orders/{order_id}、DELETE /portfolio/spot/orders/{order_id} and PATCH /portfolio/spot/orders/{order_id} 这些接口将被弃用, 我们预计在十月底移除这些接口的支持, 用户可以转用 /spot/orders 相关接口来替代
-v4.56.0
-
-2023-09-25
-
-GET /portfolio/loan_records 接口,新增 repayment_type 字段
-GET /futures/{settle}/positions 接口, 请求参数新增 holding 字段
-GET /futures/{settle}/my_trades_timerange 接口, 请求参数新增 role 字段
-GET /futures/{settle}/position_close 接口, 请求参数新增 side and pnl 字段
-v4.55.0
-
-2023-09-12
-
-新增 POST /portfolio/account_mode 接口,新增模式切换
-v4.54.0
-
-2023-08-28
-
-GET /wallet/currency_chains 接口,新增 contract_address 字段
-GET /portfolio/spot/currency_pairs 与 GET /portfolio/spot/currency_pairs/{currency_pair},新增查询保证金现货市场列表
-v4.53.0
-
-2023-08-14
-
-DELETE /account/stp_groups/{stp_id}/users,新增删除 STP Group 用户接口
-v4.52.0
-
-2023-08-07
-
-新增抵押借币相关 API
-v4.51.0
-
-2023-07-29
-
-调整优化资产流水类型
-GET /account/detail 接口,新增 mode 字段
-v4.50.0
-
-2023-07-14
-
-新增保证金账户体系相关的API,目前服务只对白名单用户开放,有兴趣的用户可以聯繫機構部門
-新增 GET /flash_swap/currency_pairs 接口,查询支持闪兑的所有交易对列表
-v4.49.0
-
-2023-07-03
-
-新增新版限频规则 ,新版预计于 2023-07-10 (utc+8) 开始生效
-GET /futures/{settle}/orders接口,调整请求字段 contract 改为非必填
-v4.48.0
-
-2023-06-16
-
-GET /wallet/sub_account_transfers接口,新增 client_order_id 请求字段
-v4.47.0
-
-2023-05-23
-
-GET /margin/uni/estimate_rate,新增逐仓借贷利率查询接口
-GET /futures/{settle}/orders_timerange,新增查询合约历史订单列表(时间区间)接口
-GET /options/positions/{contract}接口,新增 underlying、underlying_price、mark_iv、delta、gamma、vega、theta 等字段
-新增 STP Group 管理 API 接口
-v4.46.0
-
-2023-05-08
-
-GET /spot/account_book,新增查询现货账户变动历史接口
-GET /futures/{settle}/fee,新增查询合约市场交易费率接口
-v4.45.0
-
-2023-04-21
-
-逐仓借贷迁移到余币宝,详细资讯可参考逐仓迁移说明
-POST /futures/{settle}/batch_orders 接口,新增 STP 功能
-v4.44.0
-
-2023-04-07
-
-新增 ORDER_BOOK_NOT_FOUND、FAILED_RETRIEVE_ASSETS 错误信息
-v4.43.0
-
-2023-03-27
-
-现货下单接口,新增 Self-Trade Prevention 功能,详细资讯可参考STP介绍
-GET /account/detail,新增查询 API Key 的 IP 白名单接口
-PATCH /spot/orders/{order_id} 接口,新增 amend_text 字段
-v4.42.0
-
-2023-03-13
-
-新增余币宝接口
-合约下单接口,新增 Self-Trade Prevention 功能,详细资讯可参考STP介绍
-POST /wallet/sub_account_transfers 接口,新增 交割合约账户 类型支持
-v4.40.0
-
-2023-02-24
-
-GET /futures/{settle}/candlesticks 接口,新增 sum 字段
-GET /futures/{settle}/auto_deleverages 接口,新增查询ADL自动减仓订单信息
-v4.39.0
-
-2023-02-09
-
-GET /spot/batch_fee 接口,新增批量查询账户费率接口
-GET /futures/{settle}/contracts 接口,新增 enable_bonus、enable_credit 字段
-v4.38.0
-
-2023-02-04
-
-GET /futures/{settle}/my_trades_timerange 接口,新增时间范围查询合約成交记录接口
-POST /withdrawals 接口,新增 withdraw_order_id 字段
-v4.37.0
-
-2023-01-20
-
-新增查询反佣相关接口
-v4.36.0
-
-2022-12-23
-
-POST /spot/orders 和 POST /spot/batch_orders 接口,下单的时候 iceberg 字段不再支持全部订单隐藏
-v4.35.0
-
-2022-12-09
-
-GET /spot/orders 接口, 新增订单平均价格字段
-PATCH /spot/orders/{order_id} 接口, 新增订单修改单
-POST /spot/batch_orders 接口, 新增现货市价单类型
-v4.34.0
-
-2022-11-25
-
-POST /spot/orders 接口, 现货下单增加市价单
-v4.33.0
-
-2022-11-11
-
-K 线图接口 GET /futures/{settle}/premium_index
-创建子帐号的时候可以指定密码与 Eamil
-v4.32.0
-
-2022-10-28
-
-优化期权API文档
-v4.31.0
-
-2022-10-14
-
-POST /wallet/sub_account_to_sub_account 子帐号划转接口,新增合约与全仓杠杆划转
-v4.30.0
-
-2022-09-23
-
-新增管理子帐号 API Key 接口
-新增子帐号冻结与解冻接口
-POST /wallet/sub_account_to_sub_account 接口,新增子帐号与子帐号划转
-v4.29.0
-
-2022-09-09
-
-新增创建、查询子帐号功能
-GET /wallet/fee 接口,新增 settle 查询字段
-期权订单新增 refr 字段
-创建 API Key 数量上限调整至 20
-v4.28.0
-
-2022-08-12
-
-GET /futures/{settle}/trades 接口,新增 offset 查询字段
-新增现货与合约的定时取消订单接口
-v4.27.0
-
-2022-07-29
-
-新增 X-Client-Request-Id 请求 ID header,可以用来追踪请求
-新增合约批量下单接口,POST /futures/{settle}/batch_orders
-新增合约交易下单FOK订单类型
-v4.26.0
-
-2022-07-15
-
-现货自动单支持统一帐户
-新增 GET /wallet/saved_address 接口,获取提币白名单地址
-POST /wallet/transfers 接口,支援操作单号返回
-新增 GET /wallet/sub_account_cross_margin_balances 接口,查询子账号全仓杠杆账户余额信息
-GET /margin/currency_pairs 接口、新增 status 字段
-v4.25.1
-
-2022-07-06
-
-新增 GET /spot/time 获取服务器当前时间接口
-获取交易对 ticker 信息 GET /spot/tickers,新增 change_utc0, change_utc8 字段
-新增 GET /options/my_settlements 查询期权个人结算记录接口
-v4.25.0
-
-2022-06-24
-
-支持统一帐户 API
-全仓账户增加多个返回字段,详细请查看接口描述
-现货交易增加接口POST /spot/cross_liquidate_orders,处理全仓币种禁用时平仓买入下单
-获取合约账号接口新增 bonus 理财金字段和 history 累计统计数据字段
-合约个人成交记录新增 text 订单的自定义信息,fee 成交手续费,point_fee成交点卡手续费等字段
-订正撤销单个自动单名称
-POST /wallet/sub_account_transfers 支持划转到全仓杠杆账户
-v4.24.0
-
-2022-05-20
-
-新增 /flash_swap 闪兑 API ,支持直接通过 API 方式进行闪兑操作,接口组关联现货权限
-钱包新增 GET /wallet/sub_account_margin_balances , GET /wallet/sub_account_futures_balances 接口,方便主账号查询子账号的逐仓杠杆账户和永续合约账户的余额信息
-永续合约新增 API GET /futures/{settle}/index_constituents/{index} 查询指数来源信息
-修复合约自动订单 FuturesPriceTriggeredOrder 缺少 order_type 等字段的定义
-v4.23.4
-
-2022-04-25
-
-现货 K 线查询支持 30d 粒度
-v4.23.3
-
-2022-04-01
-
-现货 K 线增加基础货币成交量返回
-现货币种信息返回增加该币所在链的信息
-钱包接口 GET /wallet/currency_chains 增加币种的充提状态返回
-永续合约双仓模式下增加缺失的全仓杠杆 cross_leverage_limit 参数
-永续、交割合约查询 K 线新增多个时间粒度的支持
-v4.23.2
-
-2022-01-21
-
-提现充值历史增加 fee 字段返回
-现货 Currency 币种模型增加固定费率
-v4.23.1
-
-2021-12-23
-
-现货下单 time_in_force 增加新类型 FOK
-新增错误代码 FOK_NOT_FILL
-v4.23.0
-
-2021-12-09
-
-新增期权 API
-新增详细的限速规则说明
-新增 GET /wallet/currency_chains 查询币种支持的链
-提现充值历史增加新的 status 可选值
-v4.22.4
-
-2021-11-01
-
-SpotPriceTriggeredOrder 字段 ctime 和 ftime 更正为 int64
-v4.22.3
-
-2021-10-27
-
-GET /spot/trades 支持指定 from 和 to 按时间范围筛选的查询
-v4.22.2
-
-2021-09-29
-
-提现充值记录新增 status 字段的可选值
-合约下单新增 auto_size 只写字段用于双向持仓模式的平仓操作
-v4.22.1
-
-2021-09-07
-
-新增钱包接口 GET /wallet/total_balance 获取用户总资产
-逐仓杠杆账户返回增加locked 和 risk 字段
-逐仓和全仓杠杆借入支持输入自定义 text
-v4.22.0
-
-2021-08-13
-
-交割合约支持 BTC 结算
-现货 API GET /spot/orders 和 GET /spot/my_trades 支持按时间范围筛选
-逐仓和全仓支持查询用户最大借入额度
-v4.21.6
-
-2021-08-12
-
-修复 GET /wallet/deposit_address 地址链字段错误名称
-v4.21.5
-
-2021-06-30
-
-针对已结束的单子, GET /spot/orders, GET /spot/orders/{order_id} 以及 GET /spot/my_trades 接口可以不用指定 currency_pair 参数
-GET /wallet/withdraw_status 返回增加多链的固定提现手续费返回
-新增 GET /margin/transferable API 查询逐仓和全仓账户允许的最大转出额度
-合约平仓历史 API 新增 from 和 to 的时间范围查询参数
-v4.21.4
-
-2021-06-23
-
-逐仓账户历史 GET /margin/account_book 返回增加毫秒时间戳
-v4.21.3
-
-2021-06-17
-
-现货、合约深度增加时间戳返回
-v4.21.2
-
-2021-06-07
-
-合约 API 新增对全仓杠杆调整的支持
-新增 /margin/cross 全仓杠杆 API
-新增现货下单对全仓杠杆账户的支持
-逐仓杠杆账户查询返回新增账户未还利息
-现货订单信息新增 create_time_ms 和 update_time_ms 毫秒时间返回
-新增取消提现接口 DELETE /withdrawals/{withdrawal_id}
-v4.20.1
-
-2021-04-14
-
-更新文档部分链接
-v4.20.0
-
-2021-03-25
-
-增加现货自动订单接口组 /spot/price_orders
-v4.19.6
-
-2021-03-22
-
-现货交易对查询增加开盘时间返回
-v4.19.5
-
-2021-03-18
-
-指定订单 ID 的现货、永续合约操作支持使用用户自定义 ID(只在订单创建后的 30 分钟内有效)。
-v4.19.4
-
-2021-03-10
-
-/wallet/sub_account_transfers 接口支持转账到子账户的合约账户
-v4.19.3
-
-2021-03-04
-
-新增杠杆借贷自动还款设置和查询接口 /margin/auto_repay
-/wallet/deposit_address 接口新增 multichain_address 字段,返回某些币种的多充值地址
-优化部分文档内容
-v4.19.2
-
-2021-03-01
-
-新增 /wallet/fee 接口用于查询交易费率,原有 /spot/fee 接口废弃
-提现操作增加 chain 字段
-合约深度查询 /futures/{settle}/order_book 增加 with_id 字段的说明,返回内容增加深度 ID 的说明
-合约个人平仓历史查询 /futures/{settle}/position_close 增加 offset 参数,用于翻页获取历史平仓记录
-增加合约价值的计算方法说明,具体参考 Contract 模型的描述
-修复合约统计数据字段类型错误
-v4.18.4
-
-2021-01-22
-
-现货 Trade 模型新增 create_time_ms 字段
-ETF 交易对 Ticker 增加净值等相关信息返回
-v4.18.1
-
-2021-01-07
-
-现货下单新增冰山委托支持
-修复 /futures/{settle}/contract_stats 返回数据的错误字段类型
-v4.18.0
-
-2020-12-21
-
-现货新增 /spot/currencies 和 /spot/currencies/{currency} 查询币种信息
-合约 ContractStat 返回新增 top_lsr_account 和 top_lsr_size 等多个字段
-v4.17.1
-
-2020-12-16
-
-/spot/order_book 接口 limit 字段最大值增加到 100
-v4.17.0
-
-2020-12-15
-
-新增子账号余额查询接口 /wallet/sub_account_balances
-v4.16.1
-
-2020-12-10
-
-修复 Position 模型定义里的错误字段名称 dual_mode 。正确应该是 mode
-v4.16.0
-
-2020-12-09
-
-现货
-
-POST /spot/batch_orders 每个交易对可以创建的 order 数量提高到 10 个
-现货 GET /spot/trades 新增 reverse 参数支持时间逆序查询历史记录
-合约
-
-永续合约新增双仓支持 API ,/futures/{settle}/dual_mode 接口设置是否开启双仓。 双仓相关的仓位操作参照 /futures/{settle}/dual_comp/positions 接口组
-永续合约账户返回新增 in_dual_mode ,仓位返回新增 dual_mode
-永续合约新增 /futures/{settle}/liq_orders 支持查询市场强平记录
-v4.15.5
-
-2020-11-04
-
-新增 API /futures/{settle}/contract_stats 获取合约统计信息
-新增 API /margin/{currency_pair} 获取单个杠杆交易对详情
-v4.15.4
-
-2020-09-01
-
-GET /spot/fee 接口返回新增 point_type 字段
-新增 API GET /wallet/withdraw_status
-新增了 C# SDK 入口
-v4.15.2
-
-2020-08-12
-
-新增 GET /spot/fee 获取个人现货交易费率
-v4.15.1
-
-2020-08-04
-
-新增获取现货市场所有当前委托 GET /spot/open_orders
-新增杠杆账户余额变更历史 GET /margin/account_book
-v4.14.1
-
-2020-07-08
-
-订单里的 text 字段最大允许长度提高到了 28 个字节(不包括前缀)
-v4.14.0
-
-2020-07-06
-
-交割合约引擎 API /delivery 上线
-v4.13.1
-
-2020-06-28
-
-新增 GET /wallet/sub_account_transfers 接口查询主子账号划转历史
-v4.13.0
-
-2020-05-20
-
-增加了对提现操作的支持,详情关注 POST /withdrawals 接口和“认证”一节
-账户划转 POST /wallet/transfers 支持现货到合约了。
-钱包接口新增了提现充值历史记录查询
-合约订单和个人成交列表支持传入 offset 参数
-合约 Contract 模型添加新字段 in_delisting
-v4.12.0
-
-2020-04-08
-
-升级 APIv4 的 Key ,不再按功能独立 Key,每个 Key 都可以独立配置多个功能的权限, 详细说明参考 “关于 APIv4 Key 升级” 一节
-新增接口 POST /wallet/sub_account_transfers 支持主子账号余额划转
-GET /spot/candlesticks 增加请求参数 from 和 to ,方便获取历史数据
-v4.11.2
-
-2020-03-29
-
-Order 模型增加字段 filled_total 替换命名容易引起误解的 fill_price
-添加新的错误码标识 POC_FILL_IMMEDIATELY
-v4.11.1
-
-2020-03-23
-
-个人成交记录 GET /spot/my_trades 返回增加 role 交易角色信息
-修复查询币币理财账户 GET /margin/funding_accounts 缺少未使用过理财的币种的问题
-v4.11.0
-
-2020-03-20
-
-现货下单支持 GT 抵扣费率折扣
-现货下单 Time in force 支持传入 poc
-v4.10.1
-
-2020-02-24
-
-现货交易对新增字段 trade_status
-v4.10.0
-
-2020-02-17
-
-杠杆下单支持传入 auto_borrow 字段(只写),在余额不足时由系统自动借入不足部分
-增加指定订单 ID 的批量撤单接口 POST /spot/cancel_batch_orders
-补充“错误处理”和“与 APIv2 的区别”文档
-v4.9.1
-
-2020-01-07
-
-Order 和 BatchOrder 新增订单最近修改时间、成交费率的返回
-GET /spot/my_trades 增加成交费率返回
-v4.9.0
-
-2019-12-17
-
-GET /futures/{settle}/trades 不再支持 last_id 参数,改用 from 和 to 来获取历史成交
-v4.8.2
-
-2019-12-02
-
-新增 /spot/batch_orders 支持现货和杠杆的批量下单操作
-杠杆还款产生的手续费率支持用户等级折扣
-Loan 模型里增加了 fee_rate 字段标识杠杆借出单的手续费率,orig_id 标识续借单的原始借出单 ID
-v4.8.1
-
-2019-11-27
-
-修复 GET /futures/{settle}/positions 文档和代码示例缺少 settle 字段的说明和使用
-v4.8.0
-
-2019-11-07
-
-合约 API 支持以 USDT 结算
-原有的以 /futures 为前缀的所有接口前缀统一调整为 /futures/{settle} ,以支持基于多种结算货币的合约操作。
-/futures/{settle}/accounts 返回的账户信息里 currency 增加 USDT 的返回 volume_24h_settle 字段,取代原有 volume_24h_btc 和 volume_24h_usd 的使用。 后两个字段为了兼容依然保留,但是新的操作不推荐继续使用。
-将 /futures 替换成 /futures/usdt 就可以使用 USDT 结算的合约操作, 例如 GET /futures/usdt/accounts 能够获取 USDT 的合约账户,而 GET /futures/btc/accounts 则是用来获取 BTC 的合约账户。
-
-为了保持兼容, 原有的 GET /futures/xxx 的 API 都会默认为 BTC GET /futures/btc/xxx 。如 GET /futures/accounts 会被服务默认按 GET /futures/btc/accounts 请求来处理
-
-v4.7.3
-
-2019-07-18
-
-现货、合约下单增加 text ,支持用户自定义信息
-v4.6.3
-
-2019-06-11
-
-合约账户和仓位信息里增加点卡相关信息
-v4.7.2
-
-2019-05-29
-
-理财借出 Loan 的 rate 字段调整为非必选
-v4.7.1
-
-2019-04-17
-
-新增 wallet v4 API ,目前仅支持现货与杠杆账户转账操作
-GET /margin/loans 接口支持按 rate 排序,支持可选 currency_pair 输入
-修复各种文档问题
-v4.6.2
-
-2019-04-24
-
-修复合约价格单文档覆盖了普通合约单接口 GET /futures/orders/{order_id} 和 DELETE /futures/orders/{order_id} 的文档
-v4.6.1
-
-2019-04-02
-
-合约 Ticker 返回新增字段 high_24h 、 low_24h 和 funding_rate_indicative
-v4.6.0
-
-2019-03-21
-
-只影响 SDK
-
-修改合约相关的下单函数名,防止在 Go SDK 中与现货下单冲突
-修复验签时没有对请求参数做 decode 的问题
-v4.5.2
-
-2019-03-14
-
-/spot/order_book 的参数 currency_pair 应为必选
-优化代码示例
-v4.5.1
-
-2019-03-11
-
-修复缺少 URL 参数的说明
-v4.5.0
-
-为了避免引起版本混乱,APIv4 此后的版本统一以 4 作为大版本号开头(包括文档和 SDK)
-
-2019-03-05
-
-新增现货 v4 API,在原有 API 基础之上提供更多功能
-新增杠杆 v4 API,提供杠杆借贷功能;杠杆交易API 复用现货 v4 交易API
-提供合约止盈止损自动单 API 支持,详情见 /futures/price_orders 一组接口
-实盘交易统一 APIv4 统一 Base URL 到 https://api.gateio.ws/api/v4
-v1.3.0
-
-2019-02-13
-
-重要更新
-
-base URLs 域名更新为 fx-api.gateio.ws 和 fx-api-testnet.gateio.ws 原有 *.gateio.io 已废弃
-v1.2.1
-
-2019-02-13
-
-合约 Ticker 接口返回增加 volumn_24h_usd 和 volume_24h_btc
-v1.2.0
-
-2019-01-17
-
-新增 GET /futures/contracts/{contract} 查询单个合约
-新增 GET /futures/positions/{contract} 查询单个合约的仓位
-新增 GET /futures/account_book 查询用户合约账户历史
-Contract 模型新增 config_change_time 字段
-修复各种文档问题
-v1.1.0
-
-2019-01-08
-
-Contract, Position, FuturesOrder 模型增加更多参数
-新增 API GET /futures/position_close 获取平仓记录
-API GET /futures/my_trades 增加可选参数 order_id 支持
-DELETE /futures/orders 和 DELETE /futures/orders/{order_id} 返回状态码从 204 改为 200, 并在请求成功后返回撤销的订单列表
-DELETE /futures/orders/{order_id} 对无效订单不忽略错误,改为返回 404
-POST /futures/orders 支持 POC 和冰山委托
-v1.0.0
-
-2018-12-30
-
-初次发布
-#General
-#匹配机制
-#匹配优先级
-Gate 订单匹配遵循 价格优先 > 时间优先 的原则
-
-假设订单簿情况如下:
-
-匹配优先级
-订单 下单时间 Ask/卖价
-A 10:00 100
-B 10:00 102
-C 10:01 100
-如果 10:02 分 的现价买单 102,最终成交顺序为: A、C、B
-
-#订单生命周期
-发送到匹配引擎的有效订单会立即被接受,并跟已有挂单进行匹配,然后将匹配结果返回给客户端。
-
-匹配结果若是完全执行,则订单结束。若结果是不执行或部分执行,TimeInForce 为 IOC 的订单会立即结束; 其他情况的订单,则被挂在相应价格订单队尾,等待被匹配或者被撤销。
-
-#数据中心
-Gate 数据中心位于 AWS 日本东京 (ap-northeast-1) 地区。
-
-#接口概览
-接口概览
-接口分类 分类链接 概述
-host + /api/v4/spot/* 现货交易 包含币种状态、行情信息、下单、成交记录等功能
-host + /api/v4/margin/* 杠杆交易 杠杆账户管理、借贷、还款等
-host + /api/v4/futures/* 永续合约交易 永续合约账户管理、行情信息、下单、成交记录等功能
-host + /api/v4/delivery/* 交割合约交易 交割合约账户管理、行情信息、下单、成交记录等功能
-host + /api/v4/options/* 期权交易 期权账户管理、行情信息、下单、成交记录等功能
-host + /api/v4/wallet/* 钱包管理 充提记录、查询余额、资金划转等
-host + /api/v4/withdrawals/* 提现 数字货币提现
-#逐仓迁移说明
-平台于 2023 年 4 月 13 日 14:00(UTC+8)到 2023 年 4 月 23 日 14:00(UTC+8)期间陆续对币币理财市场中未被借贷的资产进行系统自动迁移,迁移至余币宝市场,同时也会对已经被借贷出去的资产进行取消自动放贷处理,迁移完成后您可到余币宝市场中,查看您的理财详情。在此期间将暂停通过币币理财借出资金,您也可以手动将资产从币币理财转到余币宝市场,提前获得理财收益。
-自动迁移后老版本的借贷接口将被弃用,新版借贷使用/margin/uni接口组,详细的接口迁移可以参考下表
-
-逐仓杠杆账户相关接口:
-
-逐仓迁移说明
-接口名称 路径 接口是否下线 新路径
-杠杆账户列表 GET /margin/accounts 否 -
-查询杠杆账户变动历史 GET /margin/account_book 否 -
-理财账户列表 GET /margin/funding_accounts 否 -
-修改用户自动还款设置 POST /margin/auto_repay 否 -
-查询用户自动还款设置 GET /margin/auto_repay 否 -
-逐仓杠杆允许的最大转出 GET /margin/transferable 否 -
-逐仓杠杆借贷相关接口(迁移至/margin/uni接口组):
-
-逐仓迁移说明
-接口名称 路径 接口是否下线 新路径
-查询支持杠杆交易的所有交易对 GET /margin/currency_pairs 是 GET /margin/uni/currency_pairs
-查询单个杠杆交易对 GET /margin/currency_pairs/{currency_pair} 是 GET /margin/uni/currency_pairs/{currency_pair}
-借入或借出 POST /margin/loans 是 POST /margin/uni/loans
-查询借贷订单详情 GET /margin/loans/{loan_id} 是 -
-查询借贷订单列表 GET /margin/loans 是 GET /margin/uni/loans
-归还借贷 POST /margin/loans/{loan_id}/repayment 是 POST /margin/uni/loans
-查询借贷归还记录 GET /margin/loans/{loan_id}/repayment 是 GET /margin/uni/loan_records
-逐仓杠杆用户最多可借入 GET /margin/borrowable 是 GET /margin/uni/borrowable
-查询扣息记录 - - GET /margin/uni/interest_records
-理财相关接口(迁移至/earn/uni接口组):
-
-逐仓迁移说明
-接口名称 路径 接口是否下线 新路径
-查询支持杠杆交易的所有交易对 GET /margin/currency_pairs 是 GET /earn/uni/currencies
-查询单个杠杆交易对 GET /margin/currency_pairs/{currency_pair} 是 GET /earn/uni/currencies/{currency}
-借入或借出 POST /margin/loans 是 POST /earn/uni/lends
-查询借贷订单列表 GET /margin/loans 是 GET /earn/uni/lends
-借出市场的深度 GET /margin/funding_book 是 -
-合并多个借贷订单 POST /margin/merged_loans 是 -
-修改借贷订单 PATCH /margin/loans/{loan_id} 是 PATCH /earn/uni/lends
-撤销借出贷款订单 DELETE /margin/loans/{loan_id} 是 POST /earn/uni/lends
-查看某个借贷订单的借出记录 GET /margin/loan_records 是 GET /earn/uni/lend_records
-查看单个借出记录 GET /margin/loan_records/{loan_record_id} 是 -
-修改单个借出记录 PATCH /margin/loan_records/{loan_record_id} 是 -
-查询用户派息记录 - - GET /earn/uni/interest_records
-#API
-#HTTP 通用格式
-所有读操作都是 GET 方法,只接受请求参数,不读取任何请求体。
-DELETE 方法移除指定资源(如委托),但因为 DELETE 方法也不读取任何请求体, 并不是所有移除操作都是用 DELETE 方法。复杂的移除操作通过 POST 方法配合请求体来实现。
-更新操作使用 POST, PUT 或 PATCH 方法,不同的请求有不同的参数传递方式, 但是他们要么是通过请求体,要么是请求参数,不会二者混用。
-所有操作成功时都会返回 HTTP 状态码 2xx 。401 说明认证有问题。其他 4xx 状态码说明请求无效。 如果是 5xx 错误,则是服务端处理请求时遇上了未知的严重错误,碰到的话请第一时间反馈。
-#时间
-所有时间字段,如果没有额外说明,格式都是秒级的 Unix 时间戳,但是返回的格式可能不同 int64, number 或者 string ),示例值如:
-
-1596531048
-"1596531048"
-1596531048.285
-"1596531048.285"
-最佳方式是按有小数点的 number 去解析 (string 需要实现转换)。 如果不需要高精度, 再强转成整数(或长整形)。上面的 SDK 会对不同格式的时间字段按照相应格式做好反序列化处理。
-
-#网关入出站时间
-每次请求API响应头(response header)中都会包含如下字段:
-
-X-In-Time: API网关接收请求时的时间戳,格式:Unix的时间戳,单位微秒。
-
-X-Out-Time: API网关返回响应时的时间戳,格式:Unix的时间戳,单位微秒
-
-例如:
-
-X-In-Time: 1695715091540163
-X-Out-Time: 1695715091551905
-#分页
-分页的实现有如下两种方式:
-
-page, limit 方式
-limit, offset 方式
-这两种方式里,limit 字段都是用来限制单次请求里列表返回的最大数量。没有特殊说明的情况下, 它的默认值是 100 ,最大允许 1000 。
-
-page 方式类似于网页的翻页,从 1 开始计数。遍历完整列表的方法是使用同一个 limit,每次请求将 page 加 1,直到返回的列表长度小于 limit
-
-offset 方式类似于数据库检索,遍历完整列表的方式是每次累加 limit 到 offset 上, 直到返回的列表长度小于 limit
-
-举例说明,如果订单总数是 201 个。使用 page-limit 方式,请求参数可以按照如下发送:
-
-page=1&limit=100
-page=2&limit=100
-page=3&limit=100
-如果使用 limit-offset 方法,则是:
-
-limit=100&offset=0
-limit=100&offset=100
-limit=100&offset=200
-有一些请求可能会返回额外的分页元数据信息。这些元数据信息都存储在返回头部。以 GET /futures/{settle}/orders 为例,这个请求会在返回头部追加如下分页元数据信息:
-
-X-Pagination-Limit: 请求指定的 limit 参数
-X-Pagination-Offset: 请求指定的 offset 参数
-X-Pagination-Total: 满足条件的所有条目总数
-#限频规则
-限频规则
-市场 入口 限速 依据 包含
-公共接口 公共接口 单个接口 200r/10s IP 深度、K线、交易对信息等
-钱包 私有接口 提现接口(POST /withdrawals) 1r/3s
-提现uid转账接口(POST /withdrawals/push) 1r/10s
-交易账户互转接口 (POST /wallet/transfers) 80r/10s
-主子账号互转 (POST /wallet/sub_account_transfers) 80r/10s
-子账号与子帐号互转 (POST /wallet/sub_account_to_sub_account) 80r/10s
-查询个人账户总额 (GET /wallet/total_balance) 80r/10s
-查询子账号余额信息 (GET /wallet/sub_account_balances) 80r/10s
-查询子账号逐仓杠杆账户余额信息 (GET /wallet/sub_account_margin_balances) 80r/10s
-查询子账号永续合约账户余额信息 (GET /wallet/sub_account_futures_balances) 80r/10s
-查询子账号全仓杠杆账户余额信息 (GET /wallet/sub_account_cross_margin_balances) 80r/10s
-钱包其他单个接口 200r/10s
-UID 提现
-个人账户余额查询
-子账户余额查询
-现货 私有接口 现货批量/单个下单/单个修改接口一共订单数 10r/s (uid+市场)
-现货批量/单个撤单接口一共 200r/s
-现货其他单个接口 200r/10s
-UID 现货下单、撤单
-成交历史、费率查询等
-永续合约 私有接口 合约批量/单个下单/修改单个订单接口一共 100r/s
-合约批量/单个撤单接口一共 200r/s
-永续合约其他单个接口 200r/10s
-UID 合约下单、撤单
-成交历史、费率查询等
-交割合约 私有接口 单个下单接口 500r/10s
-单个撤单接口 500r/10s
-交割其他单个接口 200r/10s
-UID 下单、撤单
-期权合约 私有接口 单个下单接口 200r/s
-单个撤单接口 200r/s
-期权其他单个接口 200r/10s
-UID 下单、撤单
-子账户 私有接口 单个子账户相关接口 80r/10s UID 创建普通子账户
-查询子账户列表
-禁用、启用子账户APIKEY
-保证金 私有接口 借入或还款 15/10s UID 借入或还款(POST /unified/loans)
-其他私有接口 私有接口 单个接口 150r/10s UID 理财、抵押借币等
-限速是每个子账号或主账号单独计算的
-
-限流字段
-
-每次请求API响应头(response header)中都会包含如下字段:
-
-X-Gate-RateLimit-Requests-Remain - 该接口当前时间窗口剩余可用请求数
-X-Gate-RateLimit-Limit - 该接口当前频率限制上限
-X-Gate-RateLimit-Reset-Timestamp - 如果您已超过该接口当前窗口频率限制,该字段表示下个可用时间窗口的时间戳(秒),即什么时候可以恢复访问;如果您未超过该接口当前窗口频率限制,该字段表示返回的是当前服务器时间(秒).
-WebSocket:
-
-现货: 现货批量/单个下单/单个改单一共 10r/s
-合约: 合约批量/单个下单/单个改单/单个撤单/批量撤单s一共 100r/s
-其他:无限制
-每个 IP 最大连接数: ≤ 300
-#成交比率限频
-为提升交易效率,我们决定为成交比率较高的用户实施更为优越的子账户频率限制。该评估将依据过去七天的交易数据,于每日 00:00 UTC 进行计算。请注意,此项规则仅适用于 VIP14 及以上的用户。
-
-#1. 术语介绍
-#1.1 交易产品系数
-为了更为精细化管理不同交易产品对成交比率的影响,我们引入了交易产品系数的概念。这一系数的设定允许我们根据产品的特性调整其对于总体成交量的影响。对于那些系数小于1的产品,它们通常涉及较小的合约规模,因此需要更多的交易指令来达到相同的交易量。通常情况下,所有交易产品都配备了一个默认系数,然而,部分产品根据其特性被赋予了独立的系数。相关产品的具体系数信息,请参考所提供的表格资料。
-
-1.1 交易产品系数
-业务线 基于 独立系数 默认系数
-USDT 永续合约 合约市场 1
-合约市场:
-BTC-USDT
-ETH-USDT 0.4
-现货 现货市场 1
-合约市场:
-BTC-USDT
-ETH-USDT 0.4
-请注意:现货本期暂时不会上线
-
-#1.2 交易量权重定义
-我们会根据市场来波动,评估 maker 和 taker的行为模式,并据此设计 maker 与 taker 的交易量比例权重。此外,我们将定期对这些权重进行评估,并在必要时进行同步调整。
-
-本次 maker交易量占比权重:100%, taker交易量占比权重:90%
-
-#1.3 成交比例计算公式
-系统将在每日 08:00 UTC,依据 00:00 UTC 的数据快照,从子账户成交比率和母账户综合成交比率中选取较高的值,以确定子账户的未来限频。对于独立经纪商,系统将仅考虑其子账户的成交比率。需要注意的是,母账户也会被视作一个“子账户”。
-
-子账户成交比率:该比率的计算方式为(子账户 永续合约Taker 的 USDT 成交量 × 0.9 + 永续合约Maker 的 USDT 成交量 × 1)/(各交易产品的新增和修改请求总数 × 交易产品系数总和)。
-母账户综合成交比率:此比率的计算方式为(母账户的 永续合约Taker USDT 成交量 × 0.9 + 永续合约Maker USDT 成交量 × 1)/(所有子账户的各交易产品新增和修改请求总数 × 交易产品系数总和)。
-#2. 现货下单频率限制规则
-#2.1 现有限频规则
-现货批量/单个下单/单个修改接口一共订单数 10r/s (市场)
-
-同一个UID在不同的市场限速均为10r/s,在不同现货市场的限速相互独立
-#2.2 新增限频规则
-平台将对在短期内频繁进行下单、撤单或修改订单操作,但成交率较低的交易行为实施限速,具体规则内容如下:
-
-1. 统计周期
-
-统计周期:统计最近24小时的数据,每小时进行一次统计。
-2. 请求类型
-
-统计项:统计所有请求,包括下单、撤单和修改订单中成功请求和失败请求(如被动委托成交、资金不足等)。
-3. 限频标准
-
-对下单(POST /spot/orders)和修改订单(PATCH /spot/orders/{order_id})两个API接口进行限速。基于UID降至每10秒不超过10次请求,建议客户保持自己的成交率在0.1以上。
-4. 解锁机制
-
-系统将对用户的交易行为进行动态评估,在每小时一次的检测中,一旦检测到用户策略调整并符合我们的效率标准,将会解除限制。
-说明:
-
-系统每小时统计最近24小时成交率,至少限制一个小时。如果用户在当前小时被限制,按照每个小时统计一次成交率,若当前小时成交率大于阈值时,下一个小时则限制解除。
-#2.3 成交率计算公式
-成交率 = USDT成交金额 / (下单&撤单&修改订单请求数之和)
-
-#3. 合约下单频率限制规则
-3. 合约下单频率限制规则
-合约限频规则
-Tier ratio rate limit (uid)
-Tier 1 [0,1) 100r/s
-Tier 2 [1,3) 150r/s
-Tier 3 [3,5) 200r/s
-Tier 4 [5,10) 250r/s
-Tier 5 [10,20) 300r/s
-Tier 6 [20,50) 350r/s
-Tier 7 >= 50 400r/s
-现货敬请期待
-
-#4. 成交比例详细规则
-面向客户群体:VIP≥ 14
-计算周期:7天
-更新时间:每日08:00 (UTC),系统将根据UTC时间 00:00 的数据,更新成交比例的数据。
-若成交比率和预期限速有所改善,则提升将于 08:00 (UTC) 立即生效。
-但若成交比率下降,则将会立即进行降低限频。
-若用户的VIP等级下降级为 VIP14以下,其限速将降低为最低档位,立即生效。
-若用户的VIP等级上升为VIP14以上,其根据目前所在等级立刻调整。
-若子账户7日交易量低于1,000,000 USDT,则按照母账户的合计成交比率实施限速。
-对于新创建的子账户,创建时将应用最低档位限速,在 T+1 08:00 (UTC) 进行计算,开始应用上述限速规则。
-WebSocket和REST 同时适用该规则
-#5. 示例
-假设用户拥有三个账户,交易永续合约产品 BTC-USDT 和 SOL-USDT 的系数分别为 1 和 0.4。
-
-账户 A(主账户):
-BTC-USDT Maker 交易量为 100 USDT,订单请求数为 10,Taker 交易量为 200 USDT,订单请求数为 20。
-SOL-USDT Maker 交易量为 20 USDT,订单请求数为 15,Taker 交易量为 20 USDT,订单请求数为 20。
-子账户成交比率 = ((100+20)*1 + (200+20)*0.9) / ((10+20) * 1 + (15+20) * 0.4) = 7.23
-账户 B (子账户):
-BTC-USDT Maker 交易量为 200 USDT,订单请求数为 20,Taker 交易量为 200 USDT,订单请求数为 30。
-SOL-USDT Maker 交易量为 20 USDT,订单请求数为 5,Taker 交易量为 30 USDT,订单请求数为 5。
-子账户成交比率 = ((200+20)*1 + (200+30)*0.9) / ((20+30) * 1 + (5+5) * 0.4) = 7.91
-账户 C (子账户):
-BTC-USDT Maker 交易量为 50 USDT,订单请求数为 5,Taker 交易量为 60 USDT,订单请求数为 8。
-SOL-USDT Maker 交易量为 100 USDT,订单请求数为 20,Taker 交易量为 120 USDT,订单请求数为 25。
-子账户成交比率 = ((50+100)*1 + (60+120)*0.9) / ((5+8) * 1 + (20+25) * 0.4) = 10.06
-母账户综合成交比率 = ((100+20+200+20+50+100)*1 + (200+20+200+30+60+120)*0.9) / ((10+20+20+30+5+8)*1 + (15+20+5+5+20+25)*0.4) = 8.19
-账户限频:
-账户 A = max(7.23, 8.19) = 8.19 -> 250r/s
-账户 B = max(7.91, 8.19) = 8.19 -> 250r/s
-账户 C = max(10.06, 8.19) = 10.06 -> 300r/s
-#6. 备注
-永续合约成交比例限频发布时间后续将会公布,敬请期待。
-原有永续合约滥用限频规则依然存在,即:
-成交率 = USDT成交金额 / (下单&撤单&修改订单请求数之和)
-24小时内请求次数超过86,400次请求,并且24小时内无任何订单成交,在下一小时内,永续合约下单限频被限制10r/10s;
-24小时内请求次数超过86,400次请求,并且成交阈值低于1%,在下一小时内,永续合约下单限频为被限制20r/10s。
-现货的成交比例限频,敬请期待。
-#返回格式
-所有的接口返回都是 JSON 格式,需要用户自行转换提取数据。 所有操作成功时都会返回 HTTP 状态码 2xx 。401 说明认证有问题。其他 4xx 状态码说明请求无效。 如果是 5xx 错误,则是服务端处理请求时遇上了未知的严重错误,碰到的话请第一时间反馈。
-
-返回状态
-
-返回格式
-状态码 说明
-200/201 请求执行成功
-202 请求已被服务端接受,但是仍在处理中
-204 请求成功,服务端没有提供返回体
-400 无效请求
-401 认证失败
-404 未找到
-429 请求过于频繁
-5xx 服务器错误
-#数据类型
-数据类型
-类型 说明
-string 字符串类型,以双引号表示,涉及金额和价格也会使用字符串标识
-integer 32位整数,主要涉及到状态码、大小、次数等
-integer(int64) 64位整数,主要涉及到ID和高精度时间戳
-number 浮点数,部分时间或统计数据会以浮点形式返回
-object 对象,包含一个子对象{}
-array 数组,包含多组内容
-boolean true 为真,false 为假
-#统一帐户(旧)
-统一账户旧版已不再维护,新版统一账户请查询统一账户
-
-我们从 4.25.0 版本之后开始引入对统一账户的支持 。统一账户是Gate新⼀代的交易系统,主要功能在于打破经典账户 (Classic Account) 内各个账户之间的资金隔离,实现多产品业务线之间的多币种保证金共享,用户无需各类交易之间进行资金划转,不同交易产品之间仓位盈亏可以互相抵消,有效提升用户的资金利用率。统一帐户(旧)更详细介绍可以查看 帮助中心 。
-
-用户在使用统一账户(旧) API 功能之前需要先在官网 API 管理页面创建统一账号(旧)的 API Key , 目前统一账户(旧)只支持现货全仓杠杆和永续合约交易。
-
-如果创建 API Key 的权限选项无法选择,首先确保现货全仓账户已经开通并且有初始资金。
-
-#资金划转
-经典帐户与统一帐户是两个不同的帐户,如果想实现多产品业务线之间的多币种保证金共享则必须使用统一帐户。
-
-统一账户(旧)的资金来源于经典账户,由于涉及到经典账户的资金变动,资金的转入转出操作都只能使用经典账户的 API Key 来执行。
-
-统一账户(旧)基于原有经典账户的全仓杠杆账户升级,因此经典账户只需要将自己的现货资金划转到现货全仓杠杆账户,即可为统一账户(旧)充值。 同理资金的转出操作也必须由经典账户从全仓杠杆账户划转到自己的现货账户。
-
-统一账户(旧)的 API Key 只能在自己的多个账户之间划转, 由于保证金共享,统一账户(旧)也无需将全仓账户的资金划转到合约账户 (我们也限制了向合约账户的转入功能)。 但是如果合约账户有资金需要提取,必须由统一账户(旧)划转到自己的现货全仓杠杆账户,才可以交给经典账户执行资金转出动作。
-
-#现货交易
-统一账户(旧)的现货交易与经典账户几乎完全一致,除了在订单操作中需要指定 account 的地方必须要指定 cross_margin , 比如想挂 BTC_USDT 交易对的买单,下单请求会类似于
-
-POST /spot/orders
-
-{
- "currency_pair": "BTC_USDT",
- "account": "cross_margin",
- "side": "buy",
- ...
-}
-其他订单相关限制请直接参考各接口说明。
-
-需要特别说明的是,统一账户(旧)升级自经典账户的现货全仓杠杆账户,经典账户的 API Key 原先就支持操作现货全仓杠杆账户, 为了不影响经典账户已有的操作,我们依然保留了经典账户的这个功能。所以不管是经典账户还是统一账户(旧)的 API Key 都可以操作同一个现货全仓杠杆账户(注意合约账户是分开的)
-
-#永续合约交易
-统一账户(旧)永续合约的 API 操作与经典账户完全相同,不过当前只支持 USD 结算
-
-需要留意一下在合约交易的部份,并没有像现货交易在使用经典帐户 API Key 的时候有做对全仓杠杆帐户的兼容性处理,所以当使用经典帐户 API Key 进行合约交易的时候,资产是保留在 经典帐户-合约 下面,而使用统一帐户 API Key 进行合约交易的时候,资产是保留在 统一帐户-合约 下面,这两个是不同的合约帐号。另外在 经典帐户-现货 下的资金, 是无法与 经典帐户-合约 共享保证金的。
-
-#Trace ID
-API响应会携带header: X-Gate-Trace-ID 。这个header用于链路追踪。
-
-#Self-Trade Prevention(STP)
-#名词解释
-Self-Trade Prevention: 自成交保护机制,后文中都简称为STP
-
-CN: Cancel new,取消新订单,保留老订单
-
-CO: Cancel old,取消⽼订单,保留新订单
-
-CB: Cancel both,新旧订单都取消
-
-#成交策略
-目前支持三种自成交保护策略,分别是CN、CO、CB。
-
-我们实现了STP用户组来提供给用户,被添加到同一个组内的多个用户账户ID,自成交或彼此成交会受到限制,限制策略取决于用户账户以taker 角色下单时指定的stp_act参数。
-
-当用户的账户ID添加到指定STP用户组后,下单时可以自定义stp_act参数,系统会按照用户传入的策略进行STP用户组 内撮合成交的限制;如果下单时没有指定stp_act策略,成交时默认按照CN策略执行。
-
-当下单用户的账户ID没有进入任何STP用户组,同时下单又指定了stp_act参数,此时系统会报错,无法下单。用户需要去掉stp_act 参数,此时用户成交不受任何自成交策略限制。
-
-#接口参数调整
-以合约下单为例,有如下调整:
-
-POST /futures/{settle}/orders
-新增请求参数
-
-接口参数调整
-名称 位置 类型 必选 描述
-stp_act body string 否 stp策略,包括:
-- cn 取消新订单,保留旧订单
-- co 取消老订单,保留新订单
-- cb 取消老订单和新订单
-新增返回参数(查询订单列表接口同理)
-
-接口参数调整
-名称 类型 必选 限制 描述
-stp_act string 否 none STP策略,包括:
-- cn 取消新订单,保留旧订单
-- co 取消老订单,保留新订单
-- cb 取消老订单和新订单
-stp_id integer(int64) 否 只读 用户所在STP用户组的id,是引擎判断用户之间订单是否可以成交的依据。如果账户没有进入STP用户组,stp_id不返回。
-finish_as string 否 只读 结束方式:
-- stp: 订单发生自成交限制而被撤销
-#使用场景
-组织A下有多个账户,其中几个账户id分别为101,102,103。
-
-为了防止组织内部账户下单发生自成交,管理员创建了一个STP用户组,用户组id为100,将账户101和102加入到该STP用户组 中,此时组内成员为[101,102]
-
-T1: STP策略版本上线。
-
-T2: 组织A账户101下做空单后,市场订单深度没有与之匹配的订单可以撮合成交,此时下单角色是maker,订单状态是open 。返回参数会增加如下信息返回
-
-{
- "status":"open",
- "stp_act":"cn",
- "stp_id":100
-}
-T3: 组织A账户101/102下做多单后,市场深度匹配到101账户下的做空订单可以撮合成交,当前角色是taker ,系统判断到两个订单的stp_id均为100,此时会限制成交,限制策略以taker为准。
-
-如果选择了cn,taker下的订单会被取消,订单会结束,返回关键参数为:
-{
- "status":"finished",
- "stp_act":"cn",
- "stp_id":100,
- "finish_as":"stp"
-}
-如果选择了co,taker下的订单会被保留,订单状态为open,系统默认会取消maker的订单(maker可以通过查询订单列表,订单状态会跟上一种情况一致。)
-
-如果选择了cb,taker下的订单会被取消,订单会结束。系统默认会取消maker的订单(maker 可以通过查询订单列表)。两者的订单结束原因都为stp
-
-{
- "status":"finished",
- "stp_act":"cb",
- "stp_id":100,
- "finish_as":"stp"
-}
-T3': 组织A账户103下做多单后,市场深度匹配到101账户下的做空订单可以撮合成交,由于103用户没有被添加到组里,stp_id 为0,系统判断两边的stp_id不一致,可以成交。订单信息为:
-
-{
- "status":"finished",
- "stp_id":0,
- "finish_as":"filled"
-}
-#统一账户
-#说明
-用户开通统一账户后,可以使用现货账户中的资产作为交易的保证金, 账户内的各币种资产将会根据其流动性,定义相应的调整系数,再统一折算为USD,来统一计算账户的资产及持仓价值。
-
-统一账户最大可借贷额度是用户对于当前交易市场的最大借贷额度。平台将根据用户的可用保证金数量以及平台风控规则等限制,计算用户当前的最大借贷额度。统一账户产生自动借款后,平台即时对所借数字资产开始计息。
-
-目前统一账户支持开通多币种保证金模式,后续将推出组合保证金模式,可根据自身需求进行账户模式切换,敬请期待。
-
-相关的接口请查看统一账户文挡,开通统一账户后可进行调用。更详细介绍可以查看 帮助中心 。
-
-#API接入流程
-创建新 API KEY 或者更新已有 API KEY 权限,勾选 unified 权限
-使用经典账户 API KEY 调用 PUT /unified/unified_mode 接口,或者 WEB 页面升级新版统一账户
-使用 /api/v4/spot/** 接口进行现货相关操作(下单、修改订单、查询订单等), account 类型增加 unified 选项
-使用 /api/v4/futures/** 接口进行永续合约相关操作(下单、修改订单、查询订单等)
-使用 /api/v4/unified/** 接口进行统一账户相关操作(账户查询、借贷查询)
-#现货交易
-统一账户的现货交易与经典账户一致,在订单操作中指定 account=unified,或者指定account=spot系统检测账户为统一账户时会自动处理为统一账户订单, 比如想挂 BTC_USDT 交易对的买单,下单请求会类似于
-
-POST /spot/orders
-
-{
- "currency_pair": "BTC_USDT",
- "account": "unified",
- "side": "buy",
- ...
-}
-其他订单相关限制请直接参考各接口说明。
-
-#永续合约交易
-统一账户永续合约的 API 操作与经典账户完全相同,当前只支持 USDT 结算
-
-#保证金公式
-保证金公式详情见: 保证金公式
-
-#资产流水类型
-#通用
-unknown : 未知
-login : 登陆
-withdraw : 提现
-ch_pass : 修改密码
-ch_fund_pass : 修改资金密码
-login_failed : 登录失败
-axs_account : 访问账号
-req_pass_ch : 修改密码请求
-req_fund_pass_ch : 修改资金密码请求
-fund_pass_ent : 输入交易密码
-bank_card_add : 绑定银行卡
-frw : 提现人脸验证
-#订单
-new_order : 下单
-cancel_order : 取消订单
-order_fill : 成交
-order_rej : 订单退回
-order_fee : 成交手续费
-system_fee : 系统成交手续费
-#充提
-withdraw : 提现
-deposit : 充值
-deposit_rej : 充值退回
-withdraw_rej : 提现退回
-cancel_withdraw : 取消提现
-withdraw_gatecode : 充值码提现
-withdraw_fireblock : Fireblock提现
-withdraw_copper : Copper提现
-startup_withdraw : Startup项目提现
-deposit_gatecode : 充值码充值
-deposit_fireblock : Fireblock充值
-deposit_copper : Copper充值
-buy_cl : 法币入金 Legend
-buy_cc : 法币入金 Cabital
-deposit_finmo : Finmo充值
-#Startup
-startup_prtcp : 参加Startup
-startup_refund : 退回Startup
-startup_sale : Startup认购
-startup_sale_rb : Startup认购退回
-#返佣
-referral_rebate : 推荐奖励
-sec_rebate_out : 二级返佣系统账户转出
-sec_rebate_in : 用户获得二级返佣
-ab_rebate : API Broker返佣上级收入
-eb_rebate : Exchange Broker返佣上级收入
-u_rebate : 普通邀请返佣用户收入
-ads_rebate : 代理商直接上级返佣收入
-au_rebate : 代理商返佣用户收入
-pis_rebate : 合伙人间接上级返佣收入
-pds_rebate : 合伙人直接上级返佣收入
-pu_rebate : 合伙人用户返佣收入
-#兑换
-eth_swap : ETH分叉兑换
-dust_swap_dctd : 小额资产兑换
-dust_swap_gt_add : 小额资产GT增加
-dust_swap_fee : 小额币种手续费扣除
-cv_buy : 闪兑买入
-cv_sell : 闪兑卖出
-#C2C
-c2c_mop : C2C商家下单
-c2c_moc : C2C取消商家单
-c2c_rop : C2C用户下单
-c2c_roc : C2C取消用户单
-c2c_om : C2C成交
-c2c_or : C2C退回
-c2c_fee : C2C手续费
-#奖励
-deposit_bonus : 充值奖励
-trading_rewards : 交易奖励
-purchase_bonus : 买入奖励
-airdrop : 空投奖励
-award : 限时奖励
-mining_rewards : 挖矿奖励
-#账户出入金
-margin_in : 逐仓杠杆转入
-margin_out : 逐仓杠杆转出
-spot_settle_out: 现货同币种结算转出
-spot_settle_in: 现货同币种结算转入
-lending_in : 理财转入
-lending_out : 理财转出
-cross_in : 统一账户转入
-cross_out : 统一账户转出
-perp_in : 永续合约转入
-perp_out : 永续合约转出
-perp_settle_in: 永续合约多币种结算转入
-perp_settle_out: 永续合约多币种结算转出
-delivery_in : 交割合约转入
-delivery_out : 交割合约转出
-ai_in : 定投转入
-ai_out : 定投转出
-e_options_in : 短时期权转入
-e_options_out : 短时期权转出
-options_in : 期权转入
-options_out : 期权转出
-cbbc_in : 牛熊证转入
-cbbc_out : 牛熊证转出
-warrant_in : 窝轮转入
-warrant_out : 窝轮转出
-subaccount_trf : 子账户资金划转
-quant_in : 量化交易转入
-quant_out : 量化交易转出
-pay_in : 支付账户转入
-pay_out : 支付账户转出
-fct_in : 合约跟单交易转入
-fct_out : 合约跟单交易转出
-#点卡
-points_purchase : 购买点卡
-points_expiration : 限时点卡
-points_trf : 点卡转让
-points_trf_rej : 点卡转让退回
-#金融
-lending_lent : 理财借出
-collected : 收款
-interest_in : 利息收入
-lending_fee : 理财手续费抵扣
-hodl_int : PoS利息收益
-redeem : 赎回
-lend : 借出
-dual_purchased : 双币理财申购
-dual_settled : 双币理财结算
-liq_add : 流动性添加
-liq_rm : 流动性赎回
-liq_rebalanced : 流动性调仓
-slot_int_in : 插槽解锁利息收益
-str_int_in : 结构性理财利息收益
-#借贷
-borrow : 借款
-repay : 还款
-margin_borrow : 逐仓杠杆借入
-margin_repay : 逐仓杠杆还款
-margin_interest_out : 逐仓杠杆扣息
-cl_borrow : 抵押借入
-cl_repay : 抵押还款
-cl_dctd : 抵押扣除
-cl_rtd : 抵押退还
-cross_borrow : 统一账户借入
-cross_repay : 统一账户还款
-interest_out : 利息
-#币圈
-donation : 捐款
-rp_sent : 发送红包
-rp_rcvd : 收取红包
-rp_rej : 红包退回
-ls_offered : 直播打赏
-ls_rcvd : 直播被打赏
-pt_offered : 币圈打赏
-pt_rcvd : 币圈被打赏
-subs_deduct : 订阅扣费
-subs_in : 订阅费入账
-subs_refund : 订阅费退回
-subs_in_rcvd : 订阅费用退回入账
-#PUSH交易
-push_dctd : push扣除
-push_rcvd_dctd : push接收扣除
-push_canceled : push撤单
-push_rej : push拒绝
-push_sent : push转让
-push_rcvd : push接收
-#量化跟单
-quant_return : 量化交易退回
-quant_cmn_in : 量化复制分红转入
-quant_cmn_out : 量化复制分红转出
-quant_cmn_rtd : 量化复制分红退回
-fct_refund : 合约跟单交易资金退回
-fct_rcvd : 合约带单分红转入
-fct_fee : 合约跟单分红转出
-fct_fee_refund : 合约跟单分红资金退回
-#NFT
-nft_mp : 拍卖支付保证金
-nft_bm : 拍卖购买
-nft_om : 拍卖出售
-ntf_mr : 拍卖保证金退回
-nft_amr : 拍卖流拍获得保证金
-nft_ocb : 订单取消退回
-nft_fb : 一口价购买
-nft_fs : 一口价出售
-nft_ob : 报价购买
-nft_os : 报价出售
-nft_cr : 取消报价退款
-nft_ir : 报价失效退款
-nft_wf : 提现服务费
-nft_wfr : 提现手续费退回
-ntf_mf : 多副本创作服务费
-ntf_mfr : 多副本创作服务费退回
-ntf_royalty : 用户版税收入
-nft_cd : 订单取消扣款
-nft_crd : 交易撤销版税扣款
-nft_cf : 众筹差额扣款
-nft_cfr : 众筹差额退款
-nft_ammf : 流动性池充值冻结
-nft_ammw : 流动性池提现
-nft_ammdf : 流动性池手续费
-nft_ammd : 流动性池交易成功打款
-#资产流水编码
-#资产
-#C2C
-301 : C2C商家下单
-302 : C2C 取消商家单
-303 : C2C用户卖出
-304 : C2C取消用户单
-305 : C2C用户买入
-308 : C2C手续费
-309 : C2C 保证金冻结
-310 : C2C 保证金退回
-311 : C2C 保证金赔付
-312 : C2C共享资产退回
-313 : C2C申诉冻结
-314 : C2C 申诉解冻
-315 : C2C 快捷闪兑买入
-110106 : C2C 商家保证金理财利息
-#充值
-110 : 链上充值
-121 : 充值码
-122 : Fireblocks充值
-123 : Wrongdeposit Fee
-124 : Copper 充值
-125 : 资产寻回手续费退回
-1907 : push转让
-1908 : 手机/邮箱/UID 充值
-2650 : Bitgo 充值
-5107 :
-#兑换
-1301 : 小额资产兑换
-1302 : 小额资产GT增加
-1307 : 小额资产兑换
-1310 : 小额资产兑换
-1322 : 小额资产兑换(USDT)
-1323 : 小额兑换USDT增加
-2601 : 闪兑买入
-2602 : 闪兑卖出
-2603 : 一键还款出账
-2604 : 一键还款入账
-2605 : 闪兑买入
-2606 : 闪兑卖出
-2612 : 闪兑买入
-2613 : 闪兑卖出
-2615 : OTC-订单成交
-2616 : OTC-订单成交
-#其他
-106 : 捐款
-115 : 快照
-118 : 币种基数调整
-131 : 集合竞价冻结
-132 : 集合竞价解冻
-141 : ETF资产合并-扣款
-142 : ETF资产合并-加款
-143 : 币种更名扣款
-144 : 币种更名加款
-181 : ETH分叉兑换
-182 : ETH2兑换
-329 : Gate Connect-退款
-330 : Gate Connect-购买
-331 : Gate Connect-卖出
-501 : 领取分叉币
-502 : 返还分叉币
-801 : 发送红包
-802 : 收取红包
-803 : 红包退回
-804 : 直播打赏
-805 : 直播被打赏
-806 : 币圈打赏
-807 : 币圈被打赏
-903 : 限时点卡
-913 : 点卡兑换商品
-915 : 点卡兑换商品退回
-917 : 点卡过期回收
-1001 : 法币借贷发布广告单
-1002 : 法币借贷撤销广告单
-1003 : 法币借贷下单
-1004 : 法币借贷完成还款
-1005 : 法币借贷撤单
-1006 : 法币借贷手续费
-1007 : 法币借贷平仓
-1008 : 法币借贷补仓
-1311 : 小额资产兑换
-1312 : 小额资产USDT增加
-1501 : 订阅扣费
-1502 : 订阅费入账
-1503 : 订阅费退回
-1504 : 订阅费用退回入账
-2950 : BUGSFUNDED 报名费
-2951 : BUGSFUNDED 报名费退款
-2952 : BUGSFUNDED 实盘资金-扣款
-2953 : BUGSFUNDED 利润划出
-2954 : BUGSFUNDED 利润划入
-2956 : BUGSFUNDED 实盘资金-划入
-2970 : PUMP售卖活动-退回购买金额
-2971 : PUMP售卖活动-发放代币
-2972 : PUMP售卖活动-扣除购买金额
-3701 : OTC交易-买入
-3702 : OTC交易-卖出
-3703 : OTC交易-取消
-5104 : Fireblocks手续费退回
-5105 : 预扣 Gas 费
-5106 : 回滚预扣 Gas 费
-100101 : 平仓尾差转出
-100102 : 平仓尾差转入
-110101 : 低流通币种提现手续费退回
-110102 : 低流通币种提现手续费
-130101 : 创建代币
-130102 : 创建代币失败退回
-130111 : KOL 代币未上市退款
-130112 : KOL 代币超额认购退款
-130113 : KOL 代币赎回
-130114 : KOL 代币认购
-130115 : KOL 代币链上手续费
-130118 : KOL 代币链上手续费退款
-130119 : KOL 代币认购失败退款
-150101 : 币种回购-加款
-150102 : 货币回购-借记
-150201 : 借入映射资金
-150202 : 归还映射资金
-150203 : 转入资金
-150204 : 转出资金
-150208 :
-180101 : Alpha 代币下架退款
-#划转
-601 : 转出至杠杆账户
-602 : 从杠杆账户转入
-701 : 转出至永续合约账户
-702 : 从永续合约账户转入
-703 : 转出至交割合约账户
-704 : 从交割合约账户转入
-1401 : 子账号资金划转
-150215 : 子账号资金划转
-150216 : 子账号资金划转
-150217 : 子账号资金划转
-150218 : 子账号资金划转
-150219 : 子账号资金划转
-1603 : 转出至期权账户
-1604 : 从期权账户转入
-3001 : 转出至支付账户
-3008 : 从支付账户转入
-3028 : 支付账户划转(退款)
-100202 : 转出至 TradFi 账户
-170201 : 从跨所账户转入
-170204 : 转出至跨所账户
-#奖励
-120102 : 合约积分空投
-#提现
-4 : 链上提现
-17 : 充值码提现
-18 : Fireblocks提现
-19 : Copper 提现
-104 : 取消链上提现
-1901 : 手机/邮箱/UID 提现
-1903 : push接收扣除
-1905 : 手机/邮箱/UID 取消提现
-1906 : 手机/邮箱/UID 提现退回
-2651 : Bitgo 提现
-#支付
-2609 : 闪兑买入
-2610 : 闪兑卖出
-2611 : 闪兑退款
-3001 : 转出至支付账户
-3017 :
-3018 : 划转(下发)
-3019 : 提现法币
-3020 : 提现法币退款
-3024 :
-3026 : 收款
-3027 : 退款
-3519 : 礼品卡 - 创建
-3520 : 礼品卡 - 兑换
-190101 : 结算 - 增加
-190301 : 待接收
-190305 : 转账成功
-190306 : 超时退回
-190307 : 已撤销
-#活动&奖励
-401 : 充值奖励
-402 : 交易奖励
-403 : 买入奖励
-404 : 空投奖励
-405 : 反馈奖励
-3101 : 卡券中心点卡兑换
-3104 : 现货代币空投
-3105 : 卡券手续费返现
-3120 : Candydrop 奖励
-3150 : 活动代币发放失败
-3801 : 体验券盈利结转
-120101 : 合约积分奖励
-140203 : 现金券奖励兑换
-140204 : 激励空投
-140205 : 激励空投解锁
-140206 : 激励空投回收
-140207 : 锁仓扣除
-#现货大宗交易
-3401 : 现货大宗交易转入
-3402 : 现货大宗交易转出
-#返佣
-109 : 普通邀请上级推荐返佣
-162 : 代理商间接上级返佣收入
-164 : 代理商直接上级返佣收入
-166 : 代理商返佣用户收入
-191 : 普通邀请返佣用户收入
-3301 : 超级代理商直接上级返佣
-3321 : Affiliate Ultra Indirect Superior Rebate
-3341 : 超级代理商自返佣
-3381 : 观察期返佣
-3390 : API Broker返佣上级收入
-3410 : Exchange Broker返佣上级收入
-4002 : 佣金提取收入
-4009 : 用户奖励自提
-4011 : 扣除负maker
-#交易
-#期权
-dnw : 转入转出
-fee : 交易手续费
-prem : 权利金
-refr : 推荐人返佣
-set : 结算盈利
-#永续
-bonus_dnw : 体验金充提
-bonus_offset : 体验金抵扣
-dnw : 转入转出
-fee : 交易手续费
-fund : 资金费用
-pnl : 减仓盈亏
-point_convert : 点卡转化
-point_dnw : 点卡转入转出
-point_fee : 点卡交易手续费
-point_refr : 点卡推荐人返佣
-pv_dnw : 体验仓位赠金充值与回收
-refr : 推荐人返佣
-#交割
-dnw : 转入转出
-fee : 交易手续费
-pnl : 减仓盈亏
-point_dnw : 点卡转入转出
-point_fee : 点卡交易手续费
-point_refr : 点卡推荐人返佣
-refr : 推荐人返佣
-settle : 结算
-settle_fee : 结算手续费
-#Alpha
-6001 : Alpha 下单
-6002 : Alpha 下单
-6003 : Alpha 成交
-6004 : Alpha 成交
-6005 : Alpha 交易失败
-6006 : Alpha 交易失败
-6007 : MemeBox 交易手续费
-6010 : Alpha 空投
-6011 : Alpha 买入订单撤销
-6012 : Alpha 买入订单撤销
-130103 : Alpha 闪兑卖出
-130104 : Alpha 闪兑买入
-130105 : Alpha 闪兑失败退回
-130106 : Alpha 闪兑卖出
-130107 : Alpha 闪兑买入
-130108 : Alpha 闪兑失败退回
-#交易机器人
-1701 : 机器人交易转入
-1702 : 机器人交易转出
-1703 : 机器人交易退回
-2401 : 机器人复制分红转入
-2402 : 机器人复制分红转出
-2403 : 机器人复制分红退回
-150210 : 期权机器人交易转入
-150211 : 期权机器人交易转出
-#保证金交易
-659 : 异币种还款入账
-660 : 异币种还款出账
-670 : 保证金交易借入
-671 : 保证金交易还款
-672 : 保证金交易扣息
-682 : 补充保险基金
-683 : 保险基金填补损失
-685 : 平台借币扣息
-#现货
-101 : 卖出
-102 : 买入
-151 : 成交手续费
-#跟单
-3151 : 跟单无忧金赔付
-3201 : 合约跟单交易转入
-3202 : 合约跟单交易转出
-3203 : 合约跟单交易资金退回
-3204 : 合约带单分红转入
-3205 : 合约跟单分红转出
-3206 : 合约跟单分红资金退回
-3601 : 现货带单交易转入
-3602 : 现货带单交易转出
-3603 : 现货带单交易退回
-3604 : 现货跟单交易转入
-3605 : 现货跟单交易转出
-3606 : 现货跟单交易资金退回
-3607 : 现货带单分红转入
-3608 : 现货跟单分红转出
-3609 : 现货跟单分红资金退回
-210101 : 跟单体验金回收
-210102 : 跟单体验金发放
-#逐仓杠杆
-601 : 转出至杠杆账户
-602 : 从杠杆账户转入
-605 : 逐仓杠杆借入
-606 : 逐仓杠杆还款
-616 : 强平手续费
-675 : 逐仓杠杆计息
-676 : 逐仓杠杆扣息
-#理财
-#HODLer Airdrop
-2614 : HODLer Airdrop
-#Launchpad
-1134 : Launchpad 扣款
-1135 : Launchpad 退回
-1136 : Launchpad 分发
-1203 : Launchpad 锁仓
-#Launchpool
-1174 : 邀请奖励
-1251 : 质押
-1253 : 主动赎回
-1255 : 派息奖励
-1258 : 自动赎回
-#余币宝
-661 : 赎回-活期
-662 : 申购-活期
-669 : 利息收益-活期
-681 : 额外奖励-活期
-686 : 申购-定期
-687 : 赎回-定期
-688 : 利息收益-定期
-689 : 额外奖励-定期
-160301 : 定期理财利息收益
-160302 : 定期理财额外奖励
-160303 : 定期理财赎回
-160304 : 定期理财申购
-160401 : 加息收益-定期
-160402 : 定期理财加息收益
-160406 : Boost 奖励-定期
-#双币宝
-2001 : 双币投资申购
-2004 : 双币投资结算
-2011 : 申购抄底宝产品
-2012 : 抄底宝产品到期回款
-2021 : 申购逃顶宝产品
-2022 : 逃顶宝产品到期回款
-160201 : 双币投资到期回款
-160202 : 双币投资申购
-#定投理财
-911 : 定投转出
-912 : 定投转入
-#抵押借币
-635 : 固定利率抵押利息
-640 : 活期扺押借入
-641 : 活期抵押还款
-642 : 活期平仓还款
-643 : 活期平仓利息
-644 : 活期抵押利息
-645 : 抵押扣除
-646 : 抵押退还
-647 : 抵押调整
-648 : 平仓退还
-649 : 平仓手续费
-655 : 固定利率抵押借入
-656 : 固定利率抵押还款
-657 : 固定利率平仓还款
-658 : 固定利率平仓利息
-696 : 提前还款违约金
-697 : 提前还款违约金退还
-160601 : 平仓赎回余币宝保证金扣款
-160602 : 平仓剩余余币宝保证金退还
-#持币生息
-120103 : 合约持币生息
-160501 : 现货持币生息
-#杠杆无忧
-160608 : 杠杆无忧到期回款
-160609 : 杠杆无忧申购
-#私募基金
-160101 : 私募基金到期回款
-160102 : 私募基金申购
-160103 : 私募基金手动赎回回款
-160104 : 私募基金收益
-#第三方基金
-170101 : 第三方基金申购款
-170111 : 第三方基金赎回款
-170112 : 第三方基金清算款
-170113 : 第三方基金申购失败退款
-170206 : 第三方基金现金分红款
-#量化基金
-739 : 财富管理返佣
-751 : 量化基金锁仓
-753 : 量化基金解锁
-754 : 量化基金解锁收益
-#链上赚币
-1171 : 额外奖励
-1173 : 额外奖励
-1181 : 质押
-1184 : 赎回
-1186 : 利息派发
-1191 : 质押
-1194 : 赎回
-1196 : 利息派发
-160607 : 撤销赎回
-#异常处理
-APIv4 对于所有的异常请求,会设置状态码为非 2xx ,同时返回一个 JSON 格式的返回体来描述具体的错误信息。
-
-返回体的格式通常如下所示:
-
-{
- "label": "INVALID_PARAM_VALUE",
- "message": "Invalid parameter `text` with value: abc"
-}
-label 用于标识某种错误的类型,格式 string ,它的值是一个固定的列表(见下方)。程序处理可以使用 label 字段的内容来设定和捕获异常
-message (或 detail) 表示详细的错误信息,方便 API 对接时,理解具体是因为什么样的参数设置导致请求出现了异常。 该字段内容不建议用于异常的捕获或识别。
-以 Python requests (opens new window)为例,异常的处理流程可以参考如下所示:
-
-以下示例的异常捕获流程只涉及到业务相关的异常,网络连接超时等其他非业务相关的异常还需要自行处理。
-
-import requests
-
-r = requests.get("https://api.gateio.ws/api/v4/futures/btc/contracts/BTC_USD")
-try:
- r.raise_for_status()
-except requests.HTTPError:
- # 捕获非 2xx 错误,尝试解析 body 里返回的错误消息,并根据不同 label 做不同的异常处理
- if r.json()['label'] == 'xxx':
- print(r.json())
-以 Python SDK (opens new window)为示例:
-
-import json
-from gate_api import FuturesApi
-from gate_api.rest import ApiException
-
-api = FuturesApi()
-try:
- api.get_futures_contract(settle='btc', contract="BTC_USD")
-except ApiException as e: # ApiException 封装了异常的各种信息,详情可参看类定义
- detail = json.loads(e.value.body)
- if detail['label'] == 'xxx':
- print(detail)
-#异常 label 列表
-请求参数或格式问题
-异常 label 列表
-label 含义
-INVALID_PARAM_VALUE 参数输入值无效
-INVALID_PROTOCOL 参数输入值无效
-INVALID_ARGUMENT 参数无效
-INVALID_REQUEST_BODY 无效请求体
-MISSING_REQUIRED_PARAM 缺少必选参数
-BAD_REQUEST 无效请求
-INVALID_CONTENT_TYPE 无效的 Content-Type 头部格式
-NOT_ACCEPTABLE Accept 头部无法满足
-METHOD_NOT_ALLOWED 请求方法不接受
-NOT_FOUND 资源 URL 不存在
-认证相关
-异常 label 列表
-label 含义
-INVALID_CREDENTIALS 认证接口缺少用户认证信息
-INVALID_KEY 无效的 API Key
-IP_FORBIDDEN 请求 IP 不在白名单
-READ_ONLY 请求账户只读,不可执行写操作
-INVALID_SIGNATURE 无效签名
-MISSING_REQUIRED_HEADER 缺少必要的认证头部
-REQUEST_EXPIRED 客户端时间与服务端时间相差过大
-ACCOUNT_LOCKED 账户被锁定
-FORBIDDEN 账户无权执行该操作
-API_WITHDRAW_DISABLED API提现操作临时禁用
-INVALID_WITHDRAW_ID 无效的提现ID
-INVALID_WITHDRAW_CANCEL_STATUS 当前提现状态无法取消
-钱包相关
-异常 label 列表
-label 含义
-SUB_ACCOUNT_NOT_FOUND 子账户不存在
-SUB_ACCOUNT_LOCKED 子账号被冻结
-MARGIN_BALANCE_EXCEPTION 杠杆账户异常
-MARGIN_TRANSFER_FAILED 杠杆资金划转失败
-TOO_MUCH_FUTURES_AVAILABLE 合约账户总资产达到上限
-FUTURES_BALANCE_NOT_ENOUGH 合约账户余额不足
-ACCOUNT_EXCEPTION 账户异常
-SUB_ACCOUNT_TRANSFER_FAILED 子账户资金划转失败
-ADDRESS_NOT_USED 指定的钱包地址未在网页执行过划转
-TOO_FAST 提现频率过快
-WITHDRAWAL_OVER_LIMIT 超出提现额度
-DUPLICATE_REQUEST 重复请求
-ORDER_EXISTS 订单已存在
-INVALID_CLIENT_ORDER_ID 无效的client_order_id
-RISK_ERROR 触发风控
-NEGATIVE_ASSETS 安全提现检查存在负资产
-RECEIVE_ERROR 接收失败
-DEDUCTION_ERROR 转账人扣款失败
-FINANCE_ERROR 财务账户扣款失败
-BALANCE_NOT_ENOUGH 余额不足
-现货和杠杆相关
-异常 label 列表
-label 含义
-INVALID_PRECISION 无效的精度
-INVALID_CURRENCY 无效的币种信息
-INVALID_CURRENCY_PAIR 无效的交易对
-POC_FILL_IMMEDIATELY 被动委托会立即成交
-ORDER_NOT_FOUND 订单不存在
-ORDER_CLOSED 订单已结束
-ORDER_CANCELLED 订单已撤销
-QUANTITY_NOT_ENOUGH 数量不足
-BALANCE_NOT_ENOUGH 余额不足
-MARGIN_NOT_SUPPORTED 该交易对不支持杠杆交易
-MARGIN_BALANCE_NOT_ENOUGH 杠杆账户余额不足
-AMOUNT_TOO_LITTLE 数额小于最低值
-AMOUNT_TOO_MUCH 数额过大
-REPEATED_CREATION 重复创建
-LOAN_NOT_FOUND 借贷订单不存在
-LOAN_RECORD_NOT_FOUND 借贷记录不存在
-NO_MATCHED_LOAN 没有借贷记录能满足借入需求
-NOT_MERGEABLE 借贷单不可合并
-NO_CHANGE 修改的参数与当前状态无区别
-REPAY_TOO_MUCH 还款数额超出借款数额
-TOO_MANY_CURRENCY_PAIRS 批量下单指定了过多交易对
-TOO_MANY_ORDERS 批量下单的单个交易对下单数过多
-MIXED_ACCOUNT_TYPE 批量下单中使用了多个账户类型
-AUTO_BORROW_TOO_MUCH 自动借入超出最多可借
-TRADE_RESTRICTED 高负债率导致交易操作被限制
-FOK_NOT_FILL FOK 订单无法全部成交
-INITIAL_MARGIN_TOO_LOW 用户总初始保证金率太低
-NO_MERGEABLE_ORDERS 找不到能够合并的借贷订单
-ORDER_BOOK_NOT_FOUND 市场深度不足
-FAILED_RETRIEVE_ASSETS 获取账户资产失败
-CANCEL_FAIL 订单撤销失败
-PRICE_THRESHOLD_EXCEEDED 触发限价保护
-合约相关
-异常 label 列表
-label 含义
-USER_NOT_FOUND 用户无合约账户
-CONTRACT_NO_COUNTER 没有匹配的对手单
-CONTRACT_NOT_FOUND 合约未找到
-NOT_FOUND 请求路径不存在
-RISK_LIMIT_EXCEEDED 委托超出风险限额
-INSUFFICIENT_AVAILABLE 余额不足
-LIQUIDATE_IMMEDIATELY 操作可能导致爆仓
-LEVERAGE_TOO_HIGH 杠杆倍数设置过高
-LEVERAGE_TOO_LOW 杠杆倍数设置过低
-ORDER_NOT_FOUND 委托不存在
-ORDER_NOT_OWNED 委托不存在
-ORDER_FINISHED 委托已结束
-TOO_MANY_ORDERS 过多未完成的委托
-POSITION_CROSS_MARGIN 全仓不支持更新保证金
-POSITION_IN_LIQUIDATION 仓位在强制平仓中
-POSITION_IN_CLOSE 仓位正在平仓中
-POSITION_EMPTY 仓位为空
-REMOVE_TOO_MUCH 保证金超过可调范围
-RISK_LIMIT_NOT_MULTIPLE 风险限额未按照步长调整
-RISK_LIMIT_TOO_HIGH 超出最大风险限额
-RISK_LIMIT_TOO_lOW 风险限额设置过低
-PRICE_TOO_DEVIATED 下单价与标记价格相差过大
-SIZE_TOO_LARGE 下单数量超过上限
-SIZE_TOO_SMALL 下单数量不足下限
-PRICE_OVER_LIQUIDATION 增加仓位时价格不能超过平仓价
-PRICE_OVER_BANKRUPT 减少仓位时价格不能超过破产价
-ORDER_POC_IMMEDIATE 被动委托会立即成交
-INCREASE_POSITION 只减仓委托会增加仓位
-CONTRACT_IN_DELISTING 当前合约市场处于下线过渡期,只允许创建只减仓委托或者平仓委托
-POSITION_NOT_FOUND 仓位不存在
-POSITION_DUAL_MODE 双向持仓模式不允许此操作
-ORDER_PENDING 有委托存在则不允许此操作
-POSITION_HOLDING 有持仓则不允许此操作
-REDUCE_EXCEEDED 双向持仓模式下,减仓单超过仓位大小
-NO_CHANGE 没有改变发生
-AMEND_WITH_STOP 有止盈止损单时不能修改委托
-ORDER_FOK FOK 订单无法全部成交
-抵押借币相关
-异常 label 列表
-label 含义
-COL_NOT_ENOUGH 质押物余额不足
-COL_TOO_MUCH 超过质押币种质押额度
-INIT_LTV_TOO_HIGH 初始质押率过高
-REDEEMED_LTV_TOO_HIGH 提取后质押率过高
-BORROWABLE_NOT_ENOUGH 剩余可借余额不足
-ORDER_TOO_MANY_TOTAL 超过平台每天下单数量
-ORDER_TOO_MANY_DAILY 超过单一用户每天下单数量
-ORDER_TOO_MANY_USER 超过单一用户总下单数量
-ORDER_NOT_EXIST 订单不存在
-ORDER_FINISHED 订单已结束
-ORDER_NO_PAY 订单待还金额为0
-ORDER_EXIST 订单已存在
-ORDER_HISTORY_EXIST 订单历史记录已存在
-ORDER_REPAYING 订单已在还款中
-ORDER_LIQUIDATING 订单已在平仓中
-BORROW_TOO_LITTLE 小于币种最小可借
-BORROW_TOO_LARGE 借款金额超过最大剩余可借
-REPAY_AMOUNT_INVALID 还款金额无效
-REPAY_GREATER_THAN_AVAILABLE 还款数额大于剩余可用
-POOL_BALANCE_NOT_ENOUGH 资金池余额不足
-CURRENCY_SETTLING 币种结算中,不允许还款
-RISK_REJECT 风控检查中,请稍后再试
-LOAN_FAILED 放款失败,可以再次发起借款
-现货保证金相关
-异常 label 列表
-label 含义
-USER_LIAB 用户存在负债
-USER_PENDING_ORDERS 用户存在挂单
-MODE_SET 保证金模式已设置
-理财相关
-异常 label 列表
-label 含义
-ERR_BALANCE_NOT_ENOUGH 余额不足
-ERR_PRODUCT_SELL_OUT 项目售罄
-ERR_PRODUCT_BUY 项目未开始
-ERR_CREATE_ORDER 下单失败
-ERR_QUOTA_LOWER_LIMIT 不满足最小下单金额
-ERR_QUOTA_SUPERIOR_LIMIT 已达最大下单额度
-ERR_ORDER_NUMBER_LIMIT 已达最大下单数量
-ERR_PRODUCT_CLOSE 项目已结束
-COPIES_NOT_ENOUGH 可申购仓位不足
-COPIES_TOO_SMALL 投资份额不足
-COPIES_TOO_BIG 投资份额超过最大购买限制
-TOTAL_AMOUNT_24 24小时内质押与赎回总量超限
-TOTAL_BUYCOUNT_24 24小时内质押与赎回次数超限
-REDEEM_24_LIMIT 质押后24小时内不能赎回
-服务异常
-异常 label 列表
-label 含义
-INTERNAL 内部错误
-SERVER_ERROR 内部错误
-TOO_BUSY 服务当前忙
-闪兑相关
-异常 label 列表
-label 含义
-INVALID_PARAM_VALUE 参数不合法
-INVALID_CURRENCY 不支持的币种
-INVALID_CURRENCY_PAIR 无效交易对
-PRICE_OBSOLETE 价格已失效/过期/作废
-ORDER_NOT_FOUND 订单不存在
-ORDER_BOOK_NOT_FOUND 订单簿不存在
-BALANCE_NOT_ENOUGH 余额不足,划转失败
-TOO_MANY_REQUESTS 请求过于频繁
-QUOTA_NOT_ENOUGH 额度不足
-SERVER_TIMEOUT 服务超时
-MISSING_REQUIRED_PARAM 缺失必要参数
-REQUEST_FORBIDDEN 资管账户访问限制
-CONVERT_PREVIEW_EXPIRED 预览缓存过期
-CONVERT_PREVIEW_NOT_MATCH 预览数据不一致
-AMOUNT_TOO_LITTLE 金额太小
-AMOUNT_TOO_MUCH 金额太大
-#认证
-#生成 API key
-在调用私有API接口前,需要生成账户的API key来验证身份。 您可以在网页端登录成功后,在【账户管理】-> 【APIv4 Keys】中生成, 或点击 这里 生成 API keys
-
-每个账户最多可以创建 20 个 API key,每个 Key 的权限配置都是相互独立的。 建议给每个 Key 设置能够标明用途的备注名
-
-Key 访问密钥
-Secret Key 签名认证加密所使用的密钥
-
-除此之外,还可以配置 IP 白名单,只允许服务端接收来自 IP 白名单里的客户端请求。每个 Key 最多可配置 20 个 IP 地址,IP 地址按照 IPv4 配置, 不支持 IP 地址段。若不设置 IP 白名单,服务端不会验证客户端 IP 来源。
-
-注:如果发现 Key 的名字是 spot 或者 futures ,该 Key 很有可能是迁移之后系统的默认命名, 详情参考 “关于 APIv4 Key 升级” 一节。
-
-创建的 Key 还可以更新和删除,不过需要注意的是 Key 的更新和删除,最多需要 5 分钟才能生效。
-
-另外模拟合约与实盘合约属于两套不同的环境,实盘合约的 API Key 不可用于模拟合约。 如果需要使用模拟合约做 API 接口联调测试,需要在个人账户 APIv4Keys 页面的模拟合约入口单独申请。 模拟合约与实盘合约的接口请求方式完全相同,区别只是在 API 的 Base URL 和使用的 API Key
-
-#APIv4 权限
-创建 Key 的时候,可以为该 Key 配置是否开启现货杠杆、合约、钱包或者提现的权限, 开启的权限可以配置读写或者只读。
-
-APIv4 权限
-产品 权限
-现货/杠杆 只读查询订单 读写查询订单&下单
-永续合约 只读查询订单 读写查询订单&下单
-交割合约 只读查询订单 读写查询订单&下单
-钱包 只读查询充提划转记录 读写 查询账户记录&资金划转
-提现 只读查询提现记录 读写 查询提现记录&提现
-所有请求方法为 GET 的都是读操作,其他的则是写请求。每个权限组可以设置为禁用、只读或读写。
-
-值得注意的一点是,尽管提现操作组只有一个 API (即 POST /withdrawals ),考虑到一般使用情况, 还是将其从钱包操作里独立成一个权限组,而包括了提现记录的账户转出流水记录查询(即 GET /wallet/withdrawals )还是保留在钱包 API 权限组了。
-
-#APIv4 验签请求接口发送要求
-在官网个人中心申请 APIv4 Key ,并确保该 Key 拥有对应操作的读写权限。
-在发送请求头部传入 KEY ,即 APIv4 密钥对的 Key
-在发送请求头部传入 Timestamp ,即请求发送的时间,格式是秒级精度的 Unix 时间戳。 同时该时间不能与当前时间差距超过 60 秒。
-在发送请求头部传入 SIGN ,即将请求生成签名字符串并用 APIv4 Secret 加密后生成的签名。 签名字符串生成方法参看下节,加密算法为 HexEncode(HMAC_SHA512(secret, signature_string)) , 即通过 HMAC-SHA512 加密算法,将 APIv4 Secret 作为加密密钥,签名字符串作为加密消息, 生成加密结果的 16 进制输出。
-确保发送请求的客户端 IP 地址在所使用的密钥的 IP 地址白名单里。
-#APIv4 签名字符串生成方式
-APIv4 中签名字符串按照如下方式拼接生成:
-
-Request Method + "\n" + Request URL + "\n" + Query String + "\n" + HexEncode(SHA512(Request Payload)) + "\n" + Timestamp
-
-#Request Method
-请求方法,全大写, 如 POST, GET
-
-#Request URL
-请求 URL,不包括服务地址和端口,如 /api/v4/futures/orders
-
-#Query String
-没有使用 URL 编码的请求参数,请求参数在参与计算签名时的顺序一定要保证和实际请求里的顺序一致。 如 status=finished&limit=50 。
-
-如果没有请求参数,使用空字符串 ("")
-
-#HexEncode(SHA512(Request Payload))
-将请求体字符串使用 SHA512 哈希之后的结果。如果没有请求体,使用空字符串的哈希结果,即 cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
-
-#Timestamp
-设置在请求头部 Timestamp 里的值
-
-示例
-
-注:示例中所有的换行都是为了方便显示人为添加的,实际只有示例中的一个 \n 保留
-
-假设使用的 Key 为 key ,Secret 为 secret
-
-查询所有合约订单
- GET /api/v4/futures/orders?contract=BTC_USD&status=finished&limit=50 HTTP/1.1
-签名字符串:
-
- GET\n
- /api/v4/futures/orders\n
- contract=BTC_USD&status=finished&limit=50\n
- cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e\n
- 1541993715
-说明
-
-/api/v4/futures/orders: 请求 URL
-contract=BTC_USD&status=finished&limit=50: 请求参数,与实际请求的顺序完全一致
-请求体为空,使用空字符串的哈希输出
-1541993715: Unix 时间戳
-签名结果
-
-55f84ea195d6fe57ce62464daaa7c3c02fa9d1dde954e4c898289c9a2407a3d6fb3faf24deff16790d726b66ac9f74526668b13bd01029199cc4fcc522418b8a
-
-创建合约委托
- POST /api/v4/futures/orders HTTP/1.1
-
- {"contract":"BTC_USD","type":"limit","size":100,"price":6800,"time_in_force":"gtc"}
-签名字符串:
-
- POST\n
- /api/v4/futures/orders\n
- \n
- ad3c169203dc3026558f01b4df307641fa1fa361f086b2306658886d5708767b1854797c68d9e62fef2f991645aa82673622ebf417e091d0bd22bafe5d956cca\n
- 1541993715
-说明
-
-请求参数为空,使用空字符串
-使用 JSON 序列化之后的字符串的哈希输出
-签名结果
-
-eae42da914a590ddf727473aff25fc87d50b64783941061f47a3fdb92742541fc4c2c14017581b4199a1418d54471c269c03a38d788d802e2c306c37636389f0
-
-# coding: utf-8
-
-# Python 示例验签代码
-
-"""
-本示例仅作为演示签名计算方式使用,推荐使用各语言的 SDK ,因为已经集成了验签规则
-"""
-
-# coding: utf-8
-import time
-import hashlib
-import hmac
-import requests
-import json
-
-def gen_sign(method, url, query_string=None, payload_string=None):
- key = '' # api_key
- secret = '' # api_secret
-
- t = time.time()
- m = hashlib.sha512()
- m.update((payload_string or "").encode('utf-8'))
- hashed_payload = m.hexdigest()
- s = '%s\n%s\n%s\n%s\n%s' % (method, url, query_string or "", hashed_payload, t)
- sign = hmac.new(secret.encode('utf-8'), s.encode('utf-8'), hashlib.sha512).hexdigest()
- return {'KEY': key, 'Timestamp': str(t), 'SIGN': sign}
-
-if __name__ == "__main__":
- host = "https://api.gateio.ws"
- prefix = "/api/v4"
- common_headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
-
- url = '/futures/orders'
- body = {"contract": "BTC_USD", "size": 100, "price": "30", "tif": "gtc"}
- request_content = json.dumps(body)
- sign_headers = gen_sign('POST', prefix + url, "", request_content)
- sign_headers.update(common_headers)
- print('signature headers: %s' % sign_headers)
- res = requests.post(host + prefix + url, headers=sign_headers, data=request_content)
- print(res.status_code)
- print(res.content)
-#常见问题
-POST /wallet/transfers 的操作记录怎么查询?
-
-通过 POST /wallet/transfers 执行的转账操作,按不同的账户类型,查询入口分在不同的服务地址,包括:
-
-GET /margin/account_book 查询杠杆账户的转入转入历史
-GET /futures/{settle}/account_book?type=dnw 查询永续合约账户的转入转出历史
-GET /delivery/{settle}/account_book?type=dnw 查询交割合约账户的转入转出历史
-杠杆下单怎么操作?
-
-杠杆下单复用了现货下单接口,只需要在 POST /spot/orders 或 POST /spot/batch_orders 时将请求体里的 account 参数设置为 margin 即可
-
-合约操作返回 USER_NOT_FOUND
-
-原因是因为合约账户没有开户,只需要执行一笔账户划转操作即可。注意不同结算币种有不同的合约账户
-
-合约提示 CONTRACT_NOT_FOUND
-
-不同结算币种的合约也是不同的,请确认指定的合约名称是否在对应结算币种支持的合约列表中。即
-
-GET /futures/{settle}/contracts 和 GET /delivery/{settle}/contracts
-
-主子账号的区别
-
-子账号 API Key 不能操作主子账户互转API, 即 POST /wallet/sub_account_transfers
-子账号 API Key 不能使用API进行提现,即 POST /withdrawals
-如果创建子账号时没有开启对应业务的权限,即时子账号 API key 开启了权限,请求也一样会被拒绝
-上面没有我的问题
-
-联系客服咨询具体问题。如果有使用到某一个语言的 SDK ,也可以在相应 SDK 的 github 项目中提交 issue
-
-提交问题的时候,建议至少包含以下信息,可以更快速定位问题:
-
-用户 ID
-原始的请求 URL、请求参数和请求体内容
-使用的 API Key 是实盘还是模拟的,具体的 Key 是什么(不需要提供 Secret)
-编程语言,最好提供一段发送请求的代码片段
-是否使用了 SDK ,如果使用了 SDK ,具体是调用哪个方法
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/gate-v3.out b/src/main/java/com/xcong/excoin/modules/gateApi/gate-v3.out
deleted file mode 100644
index a9dec95..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/gate-v3.out
+++ /dev/null
@@ -1,263 +0,0 @@
-
- . ____ _ __ _ _
- /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
-( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
- \\/ ___)| |_)| | | | | || (_| | ) ) ) )
- ' |____| .__|_| |_|_| |_\__, | / / / /
- =========|_|==============|___/=/_/_/_/
- :: Spring Boot :: (v2.2.6.RELEASE)
-
-2026-06-04 14:16:09.663 INFO 48526 --- [ main] com.xcong.excoin.ExcoinApplication : Starting ExcoinApplication v0.0.1-SNAPSHOT on VM-0-4-ubuntu with PID 48526 (/home/gate-v3.jar started by root in /root)
-2026-06-04 14:16:09.666 INFO 48526 --- [ main] com.xcong.excoin.ExcoinApplication : The following profiles are active: test
-2026-06-04 14:16:11.805 INFO 48526 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
-2026-06-04 14:16:11.809 INFO 48526 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
-2026-06-04 14:16:11.868 INFO 48526 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 41ms. Found 0 Redis repository interfaces.
-2026-06-04 14:16:13.516 INFO 48526 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration' of type [org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
-2026-06-04 14:16:13.527 INFO 48526 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'objectPostProcessor' of type [org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
-2026-06-04 14:16:13.531 INFO 48526 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@1e4f4a5c' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
-2026-06-04 14:16:13.535 INFO 48526 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration' of type [org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
-2026-06-04 14:16:13.544 INFO 48526 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
-2026-06-04 14:16:14.135 INFO 48526 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 30001 (http)
-2026-06-04 14:16:14.152 INFO 48526 --- [ main] o.a.coyote.http11.Http11NioProtocol : Initializing ProtocolHandler ["http-nio-30001"]
-2026-06-04 14:16:14.154 INFO 48526 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
-2026-06-04 14:16:14.160 INFO 48526 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.33]
-2026-06-04 14:16:14.249 INFO 48526 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
-2026-06-04 14:16:14.249 INFO 48526 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 4401 ms
-2026-06-04 14:16:15.083 INFO 48526 --- [ main] c.a.d.s.b.a.DruidDataSourceAutoConfigure : Init DruidDataSource
-2026-06-04 14:16:42.052 INFO 48526 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
- _ _ |_ _ _|_. ___ _ | _
-| | |\/|_)(_| | |_\ |_)||_|_\
- / |
- 3.3.1.tmp
-2026-06-04 14:16:46.026 INFO 48526 --- [ main] c.x.e.m.g.GateWebSocketClientManager : [管理器] 开始初始化...
-2026-06-04 14:16:48.243 INFO 48526 --- [ main] c.x.e.m.gateApi.GateGridTradeService : [Gate] 用户ID: 52314967
-2026-06-04 14:16:48.291 INFO 48526 --- [ main] c.x.e.m.gateApi.GateGridTradeService : [Gate] 初始本金: 9.461955504201 USDT
-2026-06-04 14:16:48.328 INFO 48526 --- [ main] c.x.e.m.gateApi.GateGridTradeService : [Gate] 旧条件单已清除
-2026-06-04 14:16:48.388 INFO 48526 --- [ main] c.x.e.m.gateApi.GateGridTradeService : [Gate] 平已有仓位, 方向:多, size:10, mode:dual_long
-2026-06-04 14:16:48.399 INFO 48526 --- [ main] c.x.e.m.gateApi.GateGridTradeService : [Gate] 平已有仓位, 方向:空, size:-10, mode:dual_short
-2026-06-04 14:16:48.433 INFO 48526 --- [ main] c.x.e.m.gateApi.GateGridTradeService : [Gate] 持仓模式: dual 余额: 7.346243611701
-2026-06-04 14:16:48.434 INFO 48526 --- [ main] c.x.e.m.gateApi.GateGridTradeService : [Gate] 杠杆: 100x CROSS
-2026-06-04 14:16:48.472 INFO 48526 --- [ main] c.x.e.m.g.GateWebSocketClientManager : [管理器] WS已连接, 已注册 6 个频道处理器
-2026-06-04 14:16:48.512 INFO 48526 --- [ main] c.x.e.m.gateApi.GateGridTradeService : [Gate] 网格策略已启动, 当前本金: 9.280881004201 USDT
-2026-06-04 14:16:48.529 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.GateKlineWebSocketClient : [WS] 连接成功
-2026-06-04 14:16:48.723 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.w.h.CandlestickChannelHandler : [futures.candlesticks] 订阅成功, 合约:ETH_USDT, 周期:1m
-2026-06-04 14:16:48.729 INFO 48526 --- [ctReadThread-23] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.positions] 订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:48.734 INFO 48526 --- [ctReadThread-23] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.position_closes] 订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:48.735 INFO 48526 --- [ctReadThread-23] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.orders] 订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:48.736 INFO 48526 --- [ctReadThread-23] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.usertrades] 订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:48.737 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.w.h.AutoOrdersChannelHandler : [futures.autoorders] 订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:48.811 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.candlesticks 订阅成功: {"status":"success"}
-2026-06-04 14:16:49.172 INFO 48526 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
-2026-06-04 14:16:49.564 INFO 48526 --- [ main] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
-2026-06-04 14:16:49.745 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:16:49.745 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 首根K线到达,开基底仓位 多空各10张...
-2026-06-04 14:16:49.792 INFO 48526 --- [te-trade-worker] c.x.e.modules.gateApi.GateTradeExecutor : [TradeExec] 开多成功, 价格:1790.67, id:63894851787033490
-2026-06-04 14:16:49.809 INFO 48526 --- [te-trade-worker] c.x.e.modules.gateApi.GateTradeExecutor : [TradeExec] 开空成功, 价格:1790.66, id:63894851787033516
-2026-06-04 14:16:49.875 INFO 48526 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@601cbd8c, org.springframework.security.web.context.SecurityContextPersistenceFilter@456abb66, org.springframework.security.web.header.HeaderWriterFilter@654b72c0, org.springframework.web.filter.CorsFilter@7180e701, org.springframework.security.web.authentication.logout.LogoutFilter@91c4a3f, com.xcong.excoin.configurations.security.TokenFilter@4e2c95ee, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@3003697, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@46b695ec, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@11ce2e22, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@d0ec63, org.springframework.security.web.session.SessionManagementFilter@6034e75d, org.springframework.security.web.access.ExceptionTranslationFilter@867ba60, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@e93f3d5]
-2026-06-04 14:16:50.364 INFO 48526 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
-2026-06-04 14:16:50.795 INFO 48526 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
-2026-06-04 14:16:50.900 INFO 48526 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
-2026-06-04 14:16:50.936 INFO 48526 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
-2026-06-04 14:16:50.984 INFO 48526 --- [ main] s.d.s.w.s.ApiListingReferenceScanner : Scanning for api listing references
-2026-06-04 14:16:51.743 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:16:51.800 INFO 48526 --- [ main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [120.27.238.55:5672]
-2026-06-04 14:16:52.237 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.positions 订阅成功: {"status":"success"}
-2026-06-04 14:16:52.237 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.position_closes 订阅成功: {"status":"success"}
-2026-06-04 14:16:52.238 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.orders 订阅成功: {"status":"success"}
-2026-06-04 14:16:52.238 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.usertrades 订阅成功: {"status":"success"}
-2026-06-04 14:16:52.239 INFO 48526 --- [ctReadThread-23] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.autoorders 订阅成功: {"status":"success"}
-2026-06-04 14:16:53.728 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:16:53.789 INFO 48526 --- [ main] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#58326051:0/SimpleConnection@1fde4f40 [delegate=amqp://ct_rabbit@120.27.238.55:5672/, localPort= 36746]
-2026-06-04 14:16:55.741 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:16:56.879 INFO 48526 --- [ main] o.a.coyote.http11.Http11NioProtocol : Starting ProtocolHandler ["http-nio-30001"]
-2026-06-04 14:16:56.907 INFO 48526 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 30001 (http) with context path ''
-2026-06-04 14:16:56.911 INFO 48526 --- [ main] com.xcong.excoin.ExcoinApplication : Started ExcoinApplication in 48.716 seconds (JVM running for 49.493)
-2026-06-04 14:16:57.757 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:16:59.740 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:00.725 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:01.764 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:03.735 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:05.760 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:07.733 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:09.755 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:11.808 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:13.707 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:15.736 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:17.750 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:19.802 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:21.711 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:23.749 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:25.723 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:27.785 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:29.749 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:31.791 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:33.744 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:35.833 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:37.700 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:39.770 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:41.757 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:43.794 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:45.754 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:47.771 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:49.744 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:51.767 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:53.722 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:55.739 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:57.752 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:17:59.761 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:00.428 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:01.741 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:03.788 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:05.756 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:07.726 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:09.707 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:11.764 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:13.746 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:15.758 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:17.733 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:19.862 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:21.776 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:23.750 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:25.738 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:27.760 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:29.715 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:31.816 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:33.770 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:35.731 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:37.718 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:39.779 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:41.756 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:43.817 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:45.738 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:47.835 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:49.743 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:51.797 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:53.745 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:55.727 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:57.758 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:18:59.716 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:00.137 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:01.757 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:03.766 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:05.741 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:07.713 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:09.730 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:11.733 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:13.756 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:15.716 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:17.754 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:19.752 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:21.744 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:23.746 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:25.755 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:27.787 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:29.723 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:31.741 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:33.734 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:35.753 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:37.721 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:39.767 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:41.718 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:43.713 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:45.726 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:47.757 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:49.732 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:51.739 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:53.735 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:55.721 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:57.726 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:19:59.712 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:00.220 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:01.766 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:03.768 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:05.767 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:07.765 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:09.746 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:11.722 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:13.743 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:15.714 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:17.735 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:19.739 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:21.708 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:23.724 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:25.743 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:27.711 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:29.734 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:31.749 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:33.712 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:35.736 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:37.741 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:39.707 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:41.724 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:43.722 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:45.773 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:47.742 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:49.757 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:51.747 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:53.735 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:55.715 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:57.753 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:20:59.733 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:00.303 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:01.796 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:03.763 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:05.749 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:07.774 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:09.759 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:11.745 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:13.702 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:15.698 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:17.739 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:19.773 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:21.747 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:23.792 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:25.715 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:27.729 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:29.723 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:31.751 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:33.730 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:35.727 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:37.733 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:39.710 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:41.721 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:21:43.756 INFO 48526 --- [ctReadThread-23] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:16:07.479 INFO 44913 --- [extShutdownHook] o.s.a.r.l.SimpleMessageListenerContainer : Waiting for workers to finish.
-2026-06-04 14:16:07.747 INFO 44913 --- [ctReadThread-22] c.x.e.m.gateApi.GateGridTradeService : [Gate] 未实现盈亏: 0
-2026-06-04 14:16:08.060 INFO 44913 --- [extShutdownHook] o.s.a.r.l.SimpleMessageListenerContainer : Successfully waited for workers to finish.
-2026-06-04 14:16:08.061 INFO 44913 --- [extShutdownHook] o.s.s.c.ThreadPoolTaskScheduler : Shutting down ExecutorService 'taskScheduler'
-2026-06-04 14:16:08.066 INFO 44913 --- [extShutdownHook] o.s.a.r.l.SimpleMessageListenerContainer : Shutdown ignored - container is not active already
-2026-06-04 14:16:08.067 INFO 44913 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
-2026-06-04 14:16:08.078 INFO 44913 --- [extShutdownHook] c.x.e.m.g.GateWebSocketClientManager : [管理器] 开始销毁...
-2026-06-04 14:16:08.178 ERROR 44913 --- [te-trade-worker] c.x.e.modules.gateApi.GateTradeExecutor : [TradeExec] 清除止盈止损条件单失败
-
-io.gate.gateapi.GateApiException: label: MISSING_REQUIRED_HEADER, message: Missing required header: Timestamp
- at io.gate.gateapi.ApiClient.handleResponse(ApiClient.java:1000)
- at io.gate.gateapi.ApiClient.execute(ApiClient.java:914)
- at io.gate.gateapi.api.FuturesApi.cancelPriceTriggeredOrderListWithHttpInfo(FuturesApi.java:9739)
- at io.gate.gateapi.api.FuturesApi.cancelPriceTriggeredOrderList(FuturesApi.java:9719)
- at com.xcong.excoin.modules.gateApi.GateTradeExecutor.lambda$cancelAllPriceTriggeredOrders$3(GateTradeExecutor.java:216)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
- at java.lang.Thread.run(Thread.java:748)
-
-2026-06-04 14:16:08.178 INFO 44913 --- [extShutdownHook] c.x.e.m.gateApi.GateGridTradeService : [Gate] 策略已停止, 累计盈亏: 0
-2026-06-04 14:16:08.178 INFO 44913 --- [extShutdownHook] c.x.e.m.g.GateKlineWebSocketClient : [WS] 开始销毁...
-2026-06-04 14:16:08.179 INFO 44913 --- [extShutdownHook] c.x.e.m.g.w.h.CandlestickChannelHandler : [futures.candlesticks] 取消订阅成功
-2026-06-04 14:16:08.179 INFO 44913 --- [extShutdownHook] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.positions] 取消订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:08.180 INFO 44913 --- [extShutdownHook] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.position_closes] 取消订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:08.181 INFO 44913 --- [extShutdownHook] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.orders] 取消订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:08.181 INFO 44913 --- [extShutdownHook] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.usertrades] 取消订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:08.182 INFO 44913 --- [extShutdownHook] .x.e.m.g.w.AbstractPrivateChannelHandler : [futures.autoorders] 取消订阅成功, 合约:ETH_USDT
-2026-06-04 14:16:08.186 INFO 44913 --- [ctReadThread-22] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.candlesticks 取消订阅成功
-2026-06-04 14:16:08.186 INFO 44913 --- [ctReadThread-22] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.positions 取消订阅成功
-2026-06-04 14:16:08.186 INFO 44913 --- [ctReadThread-22] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.position_closes 取消订阅成功
-2026-06-04 14:16:08.187 INFO 44913 --- [ctReadThread-22] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.orders 取消订阅成功
-2026-06-04 14:16:08.187 INFO 44913 --- [ctReadThread-22] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.usertrades 取消订阅成功
-2026-06-04 14:16:08.187 INFO 44913 --- [ctReadThread-22] c.x.e.m.g.GateKlineWebSocketClient : [WS] futures.autoorders 取消订阅成功
-2026-06-04 14:16:08.687 WARN 44913 --- [ctReadThread-22] c.x.e.m.g.GateKlineWebSocketClient : [WS] 连接关闭, code:1000, reason:
-2026-06-04 14:16:13.689 INFO 44913 --- [extShutdownHook] c.x.e.m.g.GateKlineWebSocketClient : [WS] 销毁完成
-2026-06-04 14:16:13.689 INFO 44913 --- [extShutdownHook] c.x.e.m.g.GateWebSocketClientManager : [管理器] 销毁完成
-2026-06-04 14:16:13.819 INFO 44913 --- [extShutdownHook] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} closing ...
-2026-06-04 14:16:13.840 INFO 44913 --- [ctReadThread-57] c.x.e.m.g.GateKlineWebSocketClient : [WS] 连接成功
-2026-06-04 14:16:13.840 WARN 44913 --- [ctReadThread-57] c.x.e.m.g.GateKlineWebSocketClient : [WS] 应用正在关闭,忽略连接成功回调
-2026-06-04 14:16:13.843 INFO 44913 --- [extShutdownHook] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} closed
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/gate-websocket.txt b/src/main/java/com/xcong/excoin/modules/gateApi/gate-websocket.txt
deleted file mode 100644
index d763d63..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/gate-websocket.txt
+++ /dev/null
@@ -1,5011 +0,0 @@
-v4.0.0 · Stable
-#Gate Futures WebSocket v4.0.0
-Gate 提供简单而强大的 Websocket API,将 Gate BTCUSDT 永续合约交易状态集成到您的业务或应用程序中。
-
-我们在 Python 和 Golang 中有语言绑定,将来还会有更多!您可以在右侧的深色区域中查看代码示例,并且可以通过右上角的选项卡切换示例的编程语言
-
-#服务地址
-我们提供 BTC/USDT 结算永续合约交易服务器地址,您可以根据自己的情况选择其中之一
-
-#BTC Contract
-地址列表:
-
-线上交易: wss://fx-ws.gateio.ws/v4/ws/btc
-模拟盘交易: wss://fx-ws-testnet.gateio.ws/v4/ws/btc
-#USDT Contract
-地址列表:
-
-线上交易: wss://fx-ws.gateio.ws/v4/ws/usdt
-线上SBE: wss://fx-ws.gateio.ws/v4/ws/usdt/sbe
-模拟盘交易: wss://ws-testnet.gate.com/v4/ws/futures/usdt
-建议使用SBE以获取更快的行情和更小的带宽成本
-
-如果你使用老的服务地址(wss://fx-ws.gateio.ws/v4/ws 或 wss://fx-ws-testnet.gateio.ws/v4/ws), 将默认是 BTC 结算的 websocket 服务.
-
-#变更日志
-2026-04-14
-
-部分频道支持SBE数据推送: futures.trades、futures.obu、futures.book_ticker、futures.tickers、futures.candlesticks、futures.order_book、futures.order_book_update、futures.usertrades、futures.positions、futures.orders。
-具体的使用查看SBE 数据推送章节
-2026-03-30
-
-futures.order_place 下单请求:iceberg 字段说明修正为类型 string(原文档为 int64)、可选(原文档为必填)
-2026-02-09
-
-模拟盘部分频道支持SBE数据推送
-正式环境实装另行同步
-2026-02-04
-
-futures.obu 模拟盘新增立即的首次快照推送,该次快照推送将会在订阅请求的响应之前推送。此行为与之前快照在订阅请求的响应之后推送不同,请注意该行为变更。
-正式环境实装另行同步
-2026-01-07
-
-futures.orders 新增字段 market_order_slip_ratio (市价单的预设滑点比例)
-futures.order_place futures.order_batch_place 入参新增字段market_order_slip_ratio (允许自定义市价单的最大滑点)
-2025-12-09
-
-合约的张数支持小数,所有的推送的张数、成交量等均改为字符串(可能是小数)。
-
-如何对接websocket的小数支持:
-
-在请求链接websocket时,加入:"X-Gate-Size-Decimal": "1" 的header即可,推送的张数、成交量等将会是字符串(可能是小数)。
-如果在链接websocket时,不加入:"X-Gate-Size-Decimal": "1" 的header,推送将保持原始的字段类型(整形)。
-请尽快切换到字符串的推送支持,后续整形的推送将会下线(下线时间会另行通知)。
-如果出现小数的size,用户如果还是在使用整形的推送,那么推送出来的size将会是向下取整,例如1.1、1.5、1.7的size推送出去都将会是1。
-将影响到以下的推送频道,和对应的字段。
-
-频道 字段
-futures.trades size
-futures.tickers total_size
-futures.book_ticker A B
-futures.order_book_update a.s、b.s
-futures.order_book a.s、b.s
-futures.obu size 会有小数
-futures.candlesticks v
-futures.liquidates left、size、 order_size
-futures.public_liquidates size
-futures.contract_stats long_liq_size、short_liq_size、open_interest
-futures.orders iceberg、left、size
-futures.usertrades size
-futures.auto_deleverages position_size、trade_size
-futures.positions size
-futures.autoorders position_size、 trade_size
-将影响到以下的API请求频道,主要涉及张数,成交量等字段。
-
-频道 字段
-futures.order_place size、left
-futures.order_batch_place size
-futures.order_cancel size、left
-futures.order_cancel_cp size、left
-futures.order_amend size、left
-futures.order_list size、left
-futures.order_status size、left
-2025-09-25
-
-新增 仓位 Adl 排名频道 文档
-2025-05-22
-
-新增深度频道V2文档说明
-futures.order_book_update 新增字段 full
-2025-04-25
-
-账户交易API新增通道 futures.order_cancel_ids
-futures.order_book 和 futures.order_book_update 新增深度档位字段 l
-2025-04-18
-
-补充文档代码示例
-2025-03-24
-
-修复了深度频道部分文档的错误描述
-修复了订单频道部分文档的错误描述
-2025-03-21
-
-更新了频道 futures.orders 文档, 新增了 update_id, update_time, biz_info, stop_profit_price 和 stop_loss_price 等字段的说明
-2025-03-12
-
-合约统计信息频道增加 contract 字段
-更新账户交易 API,新增了 x-gate-exptime 字段
-修复了账户交易 API 部分文档描述性错误
-2025-02-19
-
-新增频道 futures.public_liquidates 用于推送合约强平订单的快照信息
-2025-02-10
-
-更新账户交易 API,新增了 x_in_time, x_out_time, conn_trace_id, trace_id 字段
-futures.order_place, futures.order_batch_place, futures.order_cancel, futures.order_cancel_cp 和 futures.order_amend 新增了 x_gate_ratelimit_requests_remain, x_gate_ratelimit_limit 和 x_gat_ratelimit_reset_timestamp 字段
-2024-11-18
-
-在频道 futures.order_book_update 移除 10 档位 1000ms 推送间隔支持
-2023-09-21
-
-在频道futures.trades推送参数中新增is_internal字段
-2023-08-18
-
-添加 WebSocket API 操作
-WebSocket API 允许通过 WebSocket 连接创建、取消、修改、查询订单。
-2023-07-07
-
-在频道futures.order_book_update中添加新的间隔“20ms”,请注意,20ms 的间隔仅支持 20 档位
-2023-06-20
-
-在频道 futures.positions 增加update_id 字段
-2022-12-22
-
-在频道 futures.autoorders 初始结构中添加新字段 auto_size,将字段详细信息添加到 http api
-2022-11-22
-
-在通用的返回结果中添加新字段“time_ms”,以表示创建消息的时间
-2022-08-11
-
-在频道futures.autoorders通知中添加新字段text
-在频道futures.tickers通知中添加新字段low_24h和high_24h
-2022-04-15
-
-在频道futures.balances通知中添加新字段 currency
-2021-03-31
-
-在频道futures.book_ticker和futures.order_book推送中添加毫秒字段t
-2021-03-10
-
-添加新的订单簿频道 futures.book_ticker 以实时推送最佳卖价/买价
-添加新的订单簿频道 futures.order_book_update 以与用户推送订单簿更改 指定更新频率
-添加本地订单簿维护文档
-2021-03-01
-
-在通用的返回结果中添加以_ms结尾的新毫秒精度时间戳
-在futures.book all通知中添加新字段id
-2020-8-08
-
-添加完整代码 demo(golang, python)
-2020-8-07
-
-添加futures.autoorders频道
-2020-7-07
-
-添加futures.order_book频道
-2020-4-30
-
-添加futures.position频道
-2019-11-06
-
-新增 USDT 结算永续合约的 websocket 推送
-为futures.tickers添加volume_24h_base字段、volume_24h_settle字段、volume_24h_quote字段
-删除旧服务器地址(wss://fx-ws.gateio.ws/v4/ws 或 wss://fx-ws-testnet.gateio.ws/v4/ws)
-如果您使用旧的服务器地址(wss://fx-ws.gateio.ws/v4/ws 或 wss://fx-ws-testnet.gateio.ws/v4/ws),我们 将为您使用 BTC 结算永续合约的 websocket 推送
-
-2019-10-22
-
-添加应用层 ping/pong 消息
-2019-04-30
-
-添加index和mark futures.candlesticks 订阅
-为futures.tickers添加funding_rate_indicative字段
-为futures.orders添加 is_reduce_only 和状态字段
-2019-02-13
-
-更改 webSocket 基本 url
-为futures.tickers添加volume_24h_usd字段和volume_24h_btc字段
-2019-01-11
-
-添加futures.position_closes 和 futures.balance 订阅
-删除频道 futures.auto_deleverages 和futures.liquidates的 finish_time 字段
-为频道 futures.auto_deleverages 和futures.liquidates 添加 time字段
-WebSocket 应用示例
-
-# !/usr/bin/env python
-# coding: utf-8
-
-import hashlib
-import hmac
-import json
-import logging
-import time
-import threading
-
-from websocket import WebSocketApp
-
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-event = threading.Event()
-
-class GateWebSocketApp(WebSocketApp):
-
- def __init__(self, url, api_key, api_secret, **kwargs):
- super(GateWebSocketApp, self).__init__(url, **kwargs)
- self._api_key = api_key
- self._api_secret = api_secret
-
- def _send_ping(self):
- while not event.wait(10):
- self.last_ping_tm = time.time()
- if self.sock:
- try:
- self.sock.ping()
- except Exception as ex:
- logger.warning("send_ping routine terminated: {}".format(ex))
- break
- try:
- self._request("futures.ping", auth_required=False)
- except Exception as e:
- raise e
-
- def _request(self, channel, event=None, payload=None, auth_required=True):
- current_time = int(time.time())
- data = {
- "time": current_time,
- "channel": channel,
- "event": event,
- "payload": payload,
- }
- if auth_required:
- message = 'channel=%s&event=%s&time=%d' % (channel, event, current_time)
- data['auth'] = {
- "method": "api_key",
- "KEY": self._api_key,
- "SIGN": self.get_sign(message),
- }
- data = json.dumps(data)
- logger.info('request: %s', data)
- self.send(data)
-
- def get_sign(self, message):
- h = hmac.new(self._api_secret.encode("utf8"), message.encode("utf8"), hashlib.sha512)
- return h.hexdigest()
-
- def subscribe(self, channel, payload=None, auth_required=True):
- self._request(channel, "subscribe", payload, auth_required)
-
- def unsubscribe(self, channel, payload=None, auth_required=True):
- self._request(channel, "unsubscribe", payload, auth_required)
-
-
-def on_message(ws, message):
- # type: (GateWebSocketApp, str) -> None
- # handle message received
- logger.info("message received from server: {}".format(message))
-
-
-def on_open(ws):
- # type: (GateWebSocketApp) -> None
- # subscribe to channels interested
- logger.info('websocket connected')
- ws.subscribe("futures.tickers", ['BTC_USDT'], False)
-
-# custom header
-custom_headers = {
- "X-Gate-Size-Decimal": "1"
-}
-
-if __name__ == "__main__":
- logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.DEBUG)
- app = GateWebSocketApp("wss://fx-ws.gateio.ws/v4/ws/usdt",
- "YOUR_API_KEY",
- "YOUR_API_SECRET",
- on_open=on_open,
- on_message=on_message,
- header=custom_headers)
- app.run_forever(ping_interval=5)
-Copy
-#Websocket API 概述
-#事件
-每个通用 订阅频道/channel(例如ticker、order_book等)都支持一些不同的事件消息,它们是:
-
-subscribe (推荐使用)
-
-订阅,接受服务器的新数据通知。
-
-unsubscribe
-
-如果取消订阅,服务器将不会发送新数据通知。
-
-update
-
-服务器将向客户端发送新的订阅数据(增量数据)。
-
-all
-
-如果有新订阅的数据(所有数据)可用,服务器将向客户端发送通知。
-
-#请求
-每个请求都遵循通用格式,其中包含time、channel、event和payload。
-
-请求
-名称 类型 必选 描述
-id Integer 否 可选的请求 ID,将由服务器发回,以帮助您识别服务器响应哪个请求
-time Integer 是 请求时间
-channel String 是 请求 subscribe/unsubscribe 频道
-auth String 否 请求身份验证信息,请参阅身份验证部分了解详细信息
-event String 是 请求event (subscribe/unsubscribe/update/all/api)
-payload Array 是 请求详细参数
-#响应
-与请求类似,响应遵循以下通用格式,其中包含: time, channel, event , error 和 result.
-
-响应
-名称 类型 必选 描述
-time Integer 是 响应时间
-time_ms Integer 是 毫秒响应时间
-channel String 是 响应频道
-event String 是 响应频道事件 (update/all)
-error Object 是 响应错误
-result Any 是 返回来自服务端的新数据通知 或 对客户端请求的响应。如果有错误返回则error 不为空,没有错误则此字段为空。
-注意:如果它是服务端发起的数据更新通知 那么 result 的类型是基于 channel 的,不同 channel 的 result 类型有所不同。
-
-但如果是对客户端订阅请求的响应,那么 result 为固定的 {"status": "success"}。 验证订阅请求是否成功,您只需要检查 error 字段是否为空即可,不需要再解析 result 字段。
-
-为了简单起见,下面的频道(channel)描述只给出对应频道的 payload 格式。
-
-#错误
-如果出现错误,您会收到error字段,其中包含错误代码和错误的类型。
-
-错误
-Code Message
-1 invalid argument struct
-2 invalid argument
-3 service error
-4 authentication fail
-#鉴权
-如果频道是私有的,则请求体需要携带认证信息, 例如futures.usertrades
-
-WebSocket 认证使用与 HTTP API 相同的签名计算方法,但具有 以下差异:
-
-签名字符串拼接方式:channel=<channel>&event=<event>&time=<time>, 其中<channel>、<event>、<time>是对应的请求信息
-身份验证信息在请求正文中的auth字段中发送。
-您可以登录账户获取永续合约账户的 api_key 和 secret。
-
-名称 类型 描述
-method String 验证方式:api_key
-KEY String apiKey 的值
-SIGN String 签名结果
-代码示例
-
-# example WebSocket signature calculation implementation in Python
-import hmac, hashlib, json, time
-
-
-def gen_sign(channel, event, timestamp):
- # GateAPIv4 key pair
- api_key = 'YOUR_API_KEY'
- api_secret = 'YOUR_API_SECRET'
-
- s = 'channel=%s&event=%s&time=%d' % (channel, event, timestamp)
- sign = hmac.new(api_secret.encode('utf-8'), s.encode('utf-8'), hashlib.sha512).hexdigest()
- return {'method': 'api_key', 'KEY': api_key, 'SIGN': sign}
-
-
-request = {
- 'id': int(time.time() * 1e6),
- 'time': int(time.time()),
- 'channel': 'futures.orders',
- 'event': 'subscribe',
- 'payload': ["20011", "BTC_USD"]
-}
-request['auth'] = gen_sign(request['channel'], request['event'], request['time'])
-print(json.dumps(request))
-Copy
-#SBE 数据推送
-#对接SBE
-使用地址,在现有的地址后添加/sbe:
-prod: wss://fx-ws.gateio.ws/v4/ws/usdt/sbe
-testnet: wss://ws-testnet.gate.com/v4/ws/futures/usdt/sbe
-schema地址:
-prod: gate_fex_ws_prod_latest.xml(opens new window)
-testnet: gate_fex_ws_testnet_latest.xml(opens new window)
-如果需要指定sbe_schema_id,则通过query的形式传入sbe_schema_id的参数,例如:wss://fx-ws.gateio.ws/v4/ws/usdt/sbe?sbe_schema_id=1
-目前支持的sbe_schema_id为0和1;sbe_schema_id为0用于客户端测试sbe schema不兼容升级的逻辑
-不传入sbe_schema_id则默认使用最新的schema版本
-传入不合法的sbe_schema_id在连接之后会返回系统通知,并将sbe_schema_id调整为最新的schema版本
-传入旧版本的sbe_schema_id在连接之后会返回系统通知,提醒更新新版本的SBE schema,依旧使用客户端指定的旧版本schema
-无效的sbe_schema_id的系统通知
-
-{
- "time": 1770600979,
- "time_ms": 1770600979609,
- "channel": "futures.system",
- "event": "update",
- "result": {
- "type": "invalid_sbe_schema_id",
- "msg": "Your sbe_schema_id '011' does not exist, it has been adjusted to the default sbe_schema_id '1'."
- }
-}
-Copy
-过时的sbe_schema_id的系统通知
-
-{
- "time": 1770601096,
- "time_ms": 1770601096665,
- "channel": "futures.system",
- "event": "update",
- "result": {
- "type": "outdated_sbe_schema_id",
- "msg": "Your sbe_schema_id '0' is outdated, please upgrade to the latest version '1'."
- }
-}
-Copy
-#SBE使用说明
-使用JSON进行请求和首次响应;使用SBE作为数据推送;
-同一条连接上同时存在JSON和SBE的消息,请使用opcode来区分数据:opcode为1代表JSON,opcode为2代表SBE。
-SBE 的解码:
-MessageHeader:每条 SBE 二进制帧均为「MessageHeader + 消息体」。Header 中包含 blockLength、templateId、schemaId、version,解码时必须先读 Header,再根据 schemaId 和 templateId 选择对应 Schema 与消息类型解码消息体。
-解码流程建议:
-读取 MessageHeader(固定长度),得到 schemaId、templateId、blockLength、version。
-根据 schemaId 选择解码器:0 → 使用旧版本进行解码;1 → 使用新版本进行解码。
-根据 templateId 确定具体消息类型(如 PublicTrade、OrderBook、Bbo 等),再按该 Schema 的布局解码消息体。
-使用 SBE 时,仅可订阅以下频道,其余频道不支持 SBE 推送。后续将扩展到其余频道。
-订阅不支持SBE的频道时,将返回订阅失败的消息
-通道名 说明
-futures.trades 公共成交
-futures.order_book 订单簿(深度)
-futures.order_book_update 订单簿增量更新
-futures.book_ticker 最优买卖(BBO)
-futures.obu 订单簿增量(OBU)
-futures.candlesticks K 线
-futures.tickers 行情
-futures.usertrades 用户成交
-futures.positions 持仓
-futures.orders 订单
-不支持sbe将返回订阅失败:
-
-{
- "time": 1770603321,
- "time_ms": 1770603321767,
- "conn_id": "57a8765578ea837e",
- "trace_id": "3c75ba05569b3b292a2f36cfdd90d868",
- "channel": "futures.autoorders",
- "event": "subscribe",
- "payload": [
- "15760812",
- "!all"
- ],
- "error": {
- "code": 2,
- "message": "channel futures.autoorders does not support SBE"
- },
- "result": {
- "status": "fail"
- }
-}
-Copy
-#System API
-提供系统状态检查,如 ping/pong.
-
-#Ping/Pong
-检查服务器/客户端连接.
-
-Gate websocket 使用协议层 ping/pong 消息。服务器会发起 ping 操作。如果客户端没有回复,客户端将被断开。
-
-websocket rfc 协议(opens new window)
-
-如果想主动检测连接状态,可以发送应用层 ping 消息,并接收 pong 消息。
-
-#请求参数
-频道
-
-futures.ping
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send('{"time" : 123456, "channel" : "futures.ping"}')
-print(ws.recv())
-Copy
-futures.ping操作返回 JSON 结构如下:
-
-{
- "time": 1545404023,
- "time_ms": 1545404023123,
- "channel": "futures.pong",
- "event": "",
- "result": null
-}
-Copy
-#服务升级通知
-服务在即将关闭进行升级时,会向当前连接主动推送一条系统通知,客户端收到后应尽快重连。
-
-服务端推送格式(SystemNotifyDTO):
-
-字段 类型 说明
-type String 通知类型,如 upgrade
-msg String 提示文案
-data Object 可选,扩展数据
-示例(服务升级):
-
-{
- "type": "upgrade",
- "msg": "The connection will soon be closed for a service upgrade. Please reconnect."
-}
-#ticker 频道
-ticker是合约状态的高级概述。它向你展示了最高的, 最低的、最后的交易价格。它还包括每日交易量和价格等信息
-
-#订阅操作
-订阅永续合约 24hr 价格变动情况.
-
-#请求参数
-channel
-
-futures.tickers
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-payload Array 是 合约列表
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send('{"time" : 123456, "channel" : "futures.tickers","event": "subscribe", "payload" : ["BTC_USD"]}')
-print(ws.recv())
-Copy
-上面的订阅请求返回 JSON 结构如下:
-
-{
- "time": 1545404023,
- "time_ms": 1545404023123,
- "channel": "futures.tickers",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#ticker 推送
-永续合约 24hr 价格变动情况推送
-
-#推送参数
-channel
-
-futures.tickers
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-contract String 合约名称
-last String 最新成交价
-change_percentage String 涨跌幅
-funding_rate String 资金费率
-funding_rate_indicative String 下一周期预测资金费率(已弃用,改用funding_rate)
-mark_price String 标记价格
-index_price String 指数价格
-total_size String 总数量
-volume_24h String 24 小时成交量
-quanto_base_rate String 双币种合约中基础货币与结算货币的汇率。不存在于其他类型的合同中
-volume_24h_btc String 近 24 小时 BTC 交易量(已弃用,请使用volume_24h_base、volume_24h_quote、volume_24h_settle代替)
-volume_24h_usd String 近 24 小时美元交易量(已弃用,请使用volume_24h_base、volume_24h_quote、volume_24h_settle 代替)
-volume_24h_quote String 近 24 小时交易量,以计价货币计
-volume_24h_settle String 近 24 小时交易量,以结算货币计
-volume_24h_base String 近 24 小时交易量,以基础货币计
-low_24h String 近 24 小时最低交易价
-high_24h String 近 24 小时最高交易价
-{
- "time": 1541659086,
- "time_ms": 1541659086123,
- "channel": "futures.tickers",
- "event": "update",
- "result": [
- {
- "contract": "BTC_USD",
- "last": "118.4",
- "change_percentage": "0.77",
- "funding_rate": "-0.000114",
- "funding_rate_indicative": "0.01875",
- "mark_price": "118.35",
- "index_price": "118.36",
- "total_size": "73648",
- "volume_24h": "745487577",
- "volume_24h_btc": "117",
- "volume_24h_usd": "419950",
- "quanto_base_rate": "",
- "volume_24h_quote": "1665006",
- "volume_24h_settle": "178",
- "volume_24h_base": "5526",
- "low_24h": "99.2",
- "high_24h": "132.5"
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅
-
-#请求参数
-channel
-
-futures.tickers
-
-event
-
-unsubscribe
-
-代码示例
-
-import json
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": 123456,
- "channel": "futures.tickers",
- "event": "unsubscribe",
- "payload": ["BTC_USD"]
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545404900,
- "time_ms": 1545404900123,
- "channel": "futures.tickers",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#公有成交频道
-每当 Gate 发生交易时,该频道都会发送交易消息。它包括价格、金额、时间和类型等交易信息
-
-#公有成交订阅
-订阅公有成交更新通知
-
-#请求参数
-channel
-
-futures.trades
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-payload Array 是 合约列表
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send('{"time" : 123456, "channel" : "futures.trades","event": "subscribe", "payload" : ["BTC_USD"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545405058,
- "time_ms": 1545405058123,
- "channel": "futures.trades",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#公有成交推送
-通知最新交易更新
-
-#推送参数
-channel
-
-futures.trades
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-contract string 合约名称
-size string/int 交易数量
-id int 交易 ID
-create_time int 交易消息创建时间
-create_time_ms int 交易消息创建时间(以毫秒为单位)
-price string 交易价格
-is_internal bool 是否为内部成交。内部成交是指保险资金和 ADL 用户对强平指令的接管。由于不是市场深度上的正常撮合,交易价格可能会出现偏差,不会记录在 K 线上。如果不是内部交易,则该字段不会被返回。
-size 正数表示买家,负数表示卖家
-
-{
- "channel": "futures.trades",
- "event": "update",
- "time": 1541503698,
- "time_ms": 1541503698123,
- "result": [
- {
- "size": "-108",
- "id": 27753479,
- "create_time": 1545136464,
- "create_time_ms": 1545136464123,
- "price": "96.4",
- "contract": "BTC_USD",
- "is_internal": true
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅公有成交更新通知
-
-#请求参数
-channel
-
-futures.trades
-
-event
-
-unsubscribe
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send(
- '{"time" : 123456, "channel" : "futures.trades", "event": "subscribe", "payload" : ["BTC_USD"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545404900,
- "time_ms": 1545404900123,
- "channel": "futures.trades",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#深度频道
-order_book 频道允许您跟踪 Gate 订单簿深度的状态。它以价格聚合的方式提供,可自定义精度。
-
-共有三种不同的订单簿渠道可供订阅:
-
-futures.order_book
-
-全量频道,定期使用all推送完整的有限级别订单簿.
-
-futures.book_ticker
-
-实时推送最佳买价和卖价.
-
-futures.order_book_update
-
-以指定的更新频率向用户订单簿推送订单簿的更新内容.
-
-不建议通过futures.order_book接收订单簿更新,使用 futures.order_book_update 可以以更少的流量提供更及时的更新
-
-如何维护本地订单簿:
-
-订阅 futures.order_book_update 并指定级别和更新频率,例如 ["BTC_USDT", "100ms", "100"] 每 100ms 推送 BTC_USDT 订单簿前 100 个级别的更新
-缓存 WebSocket 通知。每个通知都使用“U”和“u”来告诉第一个和最后一个 自上次通知以来更新 ID。
-使用 REST API 检索基本订单簿,并确保记录了订单簿 ID(参考 如下面的“baseID”) 例如https://api.gateio.ws/api/v4/futures/usdt/order_book?contract=BTC_USDT&limit=10&with_id=true 获取 BTC_USDT 的 10 级基础订单簿
-迭代缓存的 WebSocket 通知,找到第一个包含 baseID 的通知, 即 U <= baseId+1 和 u >= baseId+1,然后开始从中消费。请注意,size为 通知都是全量的 size,即应该使用它们覆盖替换原始的size。 如果 size 等于 0,则从订单簿中删除价格。
-转储所有满足 u < baseID+1 的通知。如果 baseID+1 < 第一个通知 U,则 意味着当前的基本订单簿落后于通知。从步骤 3 开始检索更新的内容基本订单簿。
-如果后续发现满足 U > baseID+1 的通知,则说明有更新 丢失的。从步骤 3 重建本地订单簿。
-注意:
-
-即使 WebSocket 推送中的 a, b 或者 asks, bids 为空,用户也需要更新本地订单簿的 id 和 timestamp,以确保本地订单簿与服务器保持一致。
-WebSocket 订阅的 level(档位数)应与 REST 快照的 limit 一致,否则可能导致增量更新无法覆盖快照档位,造成本地订单簿错位或不完整。
-#深度全量更新频道
-订阅深度.
-
-#请求参数
-channel
-
-futures.order_book
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-contract String 是 合约名称
-limit String 是 深度层级: 100, 50, 20, 10, 5, 1
-interval String 是 价格合并精度: "0"
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send('{"time" : 123456, "channel" : "futures.order_book","event": "subscribe", "payload" : ["BTC_USD", "20", "0"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545405058,
- "time_ms": 1545405058123,
- "channel": "futures.order_book",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#全量深度推送
-全量深度更新推送
-
-#推送参数
-channel
-
-futures.order_book
-
-event
-
-all
-
-params
-
-推送参数
-名称 类型 描述
-result object 深度信息
-»t Integer 深度生成时间戳(以毫秒为单位)
-»contract String 合约名称
-»id Integer 深度 ID
-»asks Array 深度卖方的档位列表
-»»p String 档位价格
-»»s String 档位的数量
-»bids Array 深度买方的档位列表
-»»p String 档位价格
-»»s String 档位的数量
-»l String 深度层级(例如 100 即代表 100 层的深度更新)
-{
- "channel": "futures.order_book",
- "event": "all",
- "time": 1541500161,
- "time_ms": 1541500161123,
- "result": {
- "t": 1541500161123,
- "contract": "BTC_USD",
- "id": 93973511,
- "asks": [
- {
- "p": "97.1",
- "s": "2245"
- },
- {
- "p": "97.1",
- "s": "2245"
- }
- ],
- "bids": [
- {
- "p": "97.1",
- "s": "2245"
- },
- {
- "p": "97.1",
- "s": "2245"
- }
- ],
- "l": "20"
- }
-}
-Copy
-#全量深度取消订阅
-取消订阅指定市场的深度
-
-#请求参数
-channel
-
-futures.order_book
-
-event
-
-unsubscribe
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send('{"time" : 123456, "channel" : "futures.order_book","event": "unsubscribe", "payload" : ["BTC_USD", "20", "0"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545445847,
- "time_ms": 1545445847123,
- "channel": "futures.order_book",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#最佳买卖价订阅
-订阅深度最佳买卖价
-
-#请求参数
-channel
-
-futures.book_ticker
-
-event
-
-subscribe
-
-params
-
-payload是一个包含合约市场的列表.
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-ws.send('{"time" : 123456, "channel" : "futures.book_ticker","event": "subscribe", "payload" : ["BTC_USDT"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545405058,
- "time_ms": 1545405058123,
- "channel": "futures.book_ticker",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#最佳买卖价的推送
-如果 a 为空字符串,则表示空买价;如果 b 为空字符串,则表示空卖价。
-
-最新买卖价的推送
-
-#推送参数
-channel
-
-futures.book_ticker
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result object 深度的最佳买卖价
-»t Integer 最佳买卖价行情生成的时间戳(以毫秒为单位)
-»u Integer 深度的 ID
-»s String 合约名称
-»b String 最佳买方的价格,如果没有买方,则为空串
-»B String/Integer 最佳买方的数量,如果没有买方,则为 0
-»a String 最佳卖方的价格,如果没有卖方,则为空串
-»A String/Integer 最佳卖方的数量,如果没有卖方,则为 0
-{
- "time": 1615366379,
- "time_ms": 1615366379123,
- "channel": "futures.book_ticker",
- "event": "update",
- "result": {
- "t": 1615366379123,
- "u": 2517661076,
- "s": "BTC_USD",
- "b": "54696.6",
- "B": "37000",
- "a": "54696.7",
- "A": "47061"
- }
-}
-Copy
-#最佳买卖价的取消订阅
-取消指定合约市场的最佳买卖订阅
-
-#请求
-channel
-
-futures.book_ticker
-
-event
-
-unsubscribe
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-ws.send('{"time" : 123456, "channel" : "futures.book_ticker","event": "unsubscribe", "payload" : ["BTC_USDT"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545445847,
- "time_ms": 1545445847123,
- "channel": "futures.book_ticker",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#合约深度更新推送订阅
-订阅深度更新推送
-
-#请求参数
-channel
-
-futures.order_book_update
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-contract String 是 合约名称
-frequency String 是 更新频率,20ms or 100ms
-level String 否 可选的深度层级。允许以下层级:100、50、20;20ms频率 只支持 20层
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-ws.send('{"time" : 123456, "channel" : "futures.order_book_update","event": "subscribe", "payload" : ["BTC_USDT", "100ms", "100"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545405058,
- "time_ms": 1545405058123,
- "channel": "futures.order_book_update",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#深度更新推送
-深度更新推送
-
-#推送参数
-channel
-
-futures.order_book_update
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Object 自上次更新以来发生变更要价和出价
-»t Integer 订单簿生成时间戳(以毫秒为单位)
-»full Boolean true 代表全量的深度(假设订阅 level 为 100,推送出来则是 100 层的深度);用户接收到之后需要替换本地深度;false 代表增量的深度,为 false 时,不传输该字段
-»s String 合约名称
-»U Integer 本次更新开始的订单簿更新 ID
-»u Integer 本次更新结束的订单簿更新 ID
-»b Array 买方变动列表
-»»p String 变更的档位价格
-»»s String/Integer 档位的待成交数量。如果为 0,则从订单簿中删除该价格
-»a Array 卖方变动列表
-»»p String 变更的档位价格
-»»s String/Integer 档位的待成交数量。如果为 0,则从订单簿中删除该价格
-»l String 深度层级(例如 100 即代表 100 层的深度更新)
-{
- "time": 1615366381,
- "time_ms": 1615366381123,
- "channel": "futures.order_book_update",
- "event": "update",
- "result": {
- "t": 1615366381417,
- "s": "BTC_USD",
- "U": 2517661101,
- "u": 2517661113,
- "b": [
- {
- "p": "54672.1",
- "s": "0"
- },
- {
- "p": "54664.5",
- "s": "58794"
- }
- ],
- "a": [
- {
- "p": "54743.6",
- "s": "0"
- },
- {
- "p": "54742",
- "s": "95"
- }
- ],
- "l": "100"
- }
-}
-Copy
-#深度更新取消订阅
-取消指定合约的市场的深度更新订阅
-
-#请求参数
-channel
-
-futures.order_book_update
-
-event
-
-unsubscribe
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-ws.send(
- '{"time" : 123456, "channel" : "futures.order_book_update", "event": "unsubscribe", "payload" : ["BTC_USDT", "100ms"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545445847,
- "time_ms": 1545445847123,
- "channel": "futures.order_book_update",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#深度频道V2
-提供一种更快更新的获取深度信息的方法.
-
-#维护本地深度
-说明:
-
-全量深度推送(full=true): 当频道推送的深度为全量深度时,需要将该深度数据完整替换本地深度,并将深度ID更新为消息中的字段 u。服务端可能会重复推送全量深度。
-订阅该频道时,首次推送为全量深度。
-增量深度推送(full=false): 增量消息中不会显示 full 字段,此时消息包含字段 U(深度起始ID)和 u(深度结束ID)。
-如果 U = 本地深度ID + 1,则表示深度连续更新:
-将本地深度ID替换为消息中的 u。
-若更新中的 a 和 b 不为空,分别按价格更新对应的买、卖深度数量(level[0] 为价格,level[1] 为数量)。当数量 level[1]= "0" 时,需移除对应档位。
-若 U ≠ 本地深度ID + 1,则深度数据不连续,需要取消订阅该市场,并重新订阅以获取初始化深度。
-订阅限制: 针对同一合约的同一深度流,一个链接只允许订阅一次,重复订阅会返回错误。失败示例:
-{
- "time": 1747391482,
- "time_ms": 1747391482960,
- "id": 1,
- "conn_id": "d9db9373dc5e081e",
- "trace_id": "ee001938590e183db957bd5ba71651c0",
- "channel": "futures.obu",
- "event": "subscribe",
- "payload": [
- "ob.BTC_USDT.400"
- ],
- "error": {
- "code": 2,
- "message": "Alert sub ob.BTC_USDT.400"
- },
- "result": {
- "status": "fail"
- }
-}
-Copy
-#深度频道V2订阅
-#请求参数
-channel
-
-futures.obu
-
-event
-
-subscribe
-
-params
-
-payload是一个包含流名称的列表. 格式为: ob.{symbol}.{level}; 例如 ob.BTC_USDT.400、ob.BTC_USDT.50
-
-其中level枚举为:400、50;更新频率: 400档为100ms;50档为20ms;
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/usdt")
-ws.send('{"time" : 123456, "channel" : "futures.obu",
- "event": "subscribe", "payload" : ["ob.BTC_USDT.400"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1747391482,
- "time_ms": 1747391482384,
- "id": 1,
- "conn_id": "d9db9373dc5e081e",
- "trace_id": "ee001938590e183db957bd5ba71651c0",
- "channel": "futures.obu",
- "event": "subscribe",
- "payload": [
- "ob.BTC_USDT.400"
- ],
- "result": {
- "status": "success"
- }
-}
-Copy
-#深度v2订阅推送
-深度频道V2的消息推送
-
-#推送参数
-channel
-
-futures.obu
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Object 自上次更新以来发生变更要价和出价
-»t Integer 订单簿生成时间戳(以毫秒为单位)
-»full Boolean true 代表全量的深度;false 代表增量的深度,为 false 时,不传输该字段
-»s String 深度流的名称
-»U Integer 本次更新开始的订单簿更新 ID
-»u Integer 本次更新结束的订单簿更新 ID
-»b Array[OrderBookArray] 自上次更新以来的 bids 更新
-»» OrderBookArray Array[String] [Price, Amount] 数组对, Amount=0应当从本地深度中移除
-»a Array[OrderBookArray] 自上次更新以来的 asks 更新
-»» OrderBookArray Array[String] [Price, Amount] 数组对, Amount=0应当从本地深度中移除
-全量推送示例:
-
-{
- "channel": "futures.obu",
- "result": {
- "t": 1743673026995,
- "full": true,
- "s": "ob.BTC_USDT.400",
- "u": 79072179673,
- "b": [
- ["83705.9", "30166"]
- ],
- "a": [
- ["83706", "4208"]
- ]
- },
- "time_ms": 1743673026999
-}
-Copy
-增量推送示例:
-
-{
- "channel": "futures.obu",
- "result": {
- "t": 1743673027017,
- "s": "ob.BTC_USDT.400",
- "U": 79072179674,
- "u": 79072179694,
- "b": [
- ["83702.2", "62"],
- ["83702.1", "0"],
- ["83702", "0"],
- ["83685.6", "120"],
- ["83685", "239"]
- ]
- },
- "time_ms": 1743673027020
-}
-Copy
-#深度频道V2取消订阅
-取消指定合约的市场的深度频道V2订阅
-
-#请求参数
-channel
-
-futures.obu
-
-event
-
-unsubscribe
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send(
- '{"time" : 123456, "channel" : "futures.obu", "event": "unsubscribe", "payload" : ["ob.BTC_USDT.400"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1743673617,
- "time_ms": 1743673617242,
- "id": 1,
- "conn_id": "7b06ff199a98ab0e",
- "trace_id": "8f86e4021a84440e502f73fde5b94918",
- "channel": "futures.obu",
- "event": "unsubscribe",
- "payload": ["ob.BTC_USDT.400"],
- "result": {
- "status": "success"
- }
-}
-Copy
-#K 线频道
-提供一种访问 K 线信息的方法.
-
-#K 线订阅
-如果在contract前面加上mark_,则将订阅合约的标记价格 K 线;如果 前缀为“index_”,将订阅指数价格 K 线.
-
-#请求参数
-channel
-
-futures.candlesticks
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 描述
-interval String Interval : "10s", "1m", "5m", "15m", "30m", "1h", "4h", "8h", "1d", "7d"
-contract String 合约名称
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send('{"time" : 123456, "channel" : "futures.candlesticks","event": "subscribe", "payload" : ["1m", "BTC_USD"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545445847,
- "time_ms": 1545445847123,
- "channel": "futures.candlesticks",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#k 线消息推送
-k 线的消息推送
-
-#推送参数
-channel
-
-futures.candlesticks
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-t Integer 时间
-o String 开盘价格
-c String 收盘价格
-h String 最高价格
-l String 最低价格
-v String/Integer 成交量
-n String 合约名称
-a String 成交原始币种数量
-w Boolean true 表示窗口已关闭。注:可能会缺失 true 的消息,但不影响数据的完整性
-{
- "time": 1542162490,
- "time_ms": 1542162490123,
- "channel": "futures.candlesticks",
- "event": "update",
- "result": [
- {
- "t": 1545129300,
- "v": "27525555",
- "c": "95.4",
- "h": "96.9",
- "l": "89.5",
- "o": "94.3",
- "n": "1m_BTC_USD",
- "a": "314732.87412",
- "w": false
- },
- {
- "t": 1545129300,
- "v": "27525555",
- "c": "95.4",
- "h": "96.9",
- "l": "89.5",
- "o": "94.3",
- "n": "1m_BTC_USD",
- "a": "314732.87412",
- "w": true
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅指定市场 K 线信息
-
-#请求参数
-channel
-
-futures.candlesticks
-
-event
-
-unsubscribe
-
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send(
- '{"time" : 123456, "channel" : "futures.candlesticks", "event": "unsubscribe", "payload" : ["1m", "BTC_USD"]}')
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545445847,
- "time_ms": 1545445847123,
- "channel": "futures.candlesticks",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#公共强平订单频道
-提供一种接收Gate强平订单信息的方式,每个合约每1秒最多推一条强平订单数据
-
-#公共强平订单订阅
-如果您想订阅所有合约中的强平订单推送,请在订阅请求列表中使用 !all
-
-订阅公共强平订单推送
-
-#请求参数
-channel
-
-futures.public_liquidates
-
-event
-
-subscribe
-
-params
-
-名称 类型 必选 描述
-contract String 是 合约名称列表
-代码示例
-
-import json
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": 123456,
- "channel": "futures.public_liquidates",
- "event": "subscribe",
- "payload": ["BTC_USD","ETH_USD"],
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.public_liquidates",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#公共强平订单推送
-推送公共强制平仓更新
-
-#推送参数
-channel
-
-futures.public_liquidates
-
-event
-
-update
-
-params
-
-名称 类型 描述
-result Array Array of objects
-名称 类型 描述
-price Float 订单价格
-size String/Integer 强平订单数量
-time_ms Integer 时间(以毫秒为单位)
-contract String 合约名称
-{
- "channel": "futures.public_liquidates",
- "event": "update",
- "time": 1541505434,
- "time_ms": 1541505434123,
- "result": [
- {
- "price": 215.1,
- "size": "-124",
- "time_ms": 1541486601123,
- "contract": "BTC_USD",
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅公共强平订单更新
-
-#请求参数
-channel
-
-futures.public_liquidates
-
-event
-
-unsubscribe
-
-代码示例
-
-import json
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": 123456,
- "channel": "futures.public_liquidates",
- "event": "unsubscribe",
- "payload": ["BTC_USD"],
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
- {
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.public_liquidates",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#合约统计信息频道
-contract_stats 通道允许您获取合约统计信息
-
-#订阅操作
-订阅合约统计信息
-
-#请求参数
-channel
-
-futures.contract_stats
-
-event
-
-subscribe
-
-params
-
-名称 类型 必选 描述
-contract String Yes 合约名称
-interval String Yes Interval : "1m", "5m", "15m", "30m", "1h", "4h", "8h", "1d", "3d", "7d"
-代码示例
-
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-ws.send('{"time" : 123456, "channel" : "futures.contract_stats","event": "subscribe", "payload" : ["BTC_USD","1m"]}')
-print(ws.recv())
-Copy
-上面的订阅请求返回 JSON 结构如下:
-
-{
- "time": 1545404023,
- "time_ms": 1545404023123,
- "channel": "futures.contract_stats",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#contract_stats 推送
-合约统计信息推送
-
-#推送参数
-channel
-
-futures.contract_stats
-
-event
-
-update
-
-params
-
-名称 类型 描述
-result Array Array of objects
-名称 类型 描述
-time Integer 统计时间(时间戳)
-contract string 合约名称
-mark_price Float 当前标记价格
-lsr_taker Float 多空吃单比
-lsr_account Float 多空持仓用户比
-long_liq_size string/Integer 做多爆仓量(张)
-long_liq_amount Float 做多爆仓量(交易币种)
-long_liq_usd Float 做多爆仓量(计价币种)
-short_liq_size string/Integer 做空爆仓量(张)
-short_liq_amount Float 做空爆仓量(交易币种)
-short_liq_usd Float 做空爆仓量(计价币种)
-open_interest string/Integer 总持仓量(张)
-open_interest_usd Float 总持仓量(计价币种)
-top_lsr_account Float 大户多空账户比
-top_lsr_size Float 大户多空持仓比
-{
- "time": 1541659086,
- "time_ms": 1541659086123,
- "channel": "futures.contract_stats",
- "event": "update",
- "result": [
- {
- "time": 1603865400,
- "contract":"BTC_USDT",
- "lsr_taker": 100,
- "lsr_account": 0.5,
- "long_liq_size": "0",
- "short_liq_size": "0",
- "open_interest": "124724",
- "short_liq_usd": 0,
- "mark_price": "8865",
- "top_lsr_size": 1.02,
- "short_liq_amount": 0,
- "long_liq_amount": 0,
- "open_interest_usd": 1511,
- "top_lsr_account": 1.5,
- "long_liq_usd": 0
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅
-
-#请求参数
-channel
-
-futures.contract_stats
-
-event
-
-unsubscribe
-
-params
-
-名称 类型 必选 描述
-contract String Yes 合约名称
-interval String Yes Interval : "1m", "5m", "15m", "30m", "1h", "4h", "8h", "1d", "3d", "7d"
-注意:contract为unsub_all,表示全部取消
-
-代码示例
-
-import json
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": 123456,
- "channel": "futures.contract_stats",
- "event": "unsubscribe",
- "payload": ["BTC_USD","1m"]
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的请求返回 JSON 结构如下:
-
-{
- "time": 1545404900,
- "time_ms": 1545404900123,
- "channel": "futures.contract_stats",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#订单频道
-提供接收用户订单的推送
-
-需要认证.
-
-#订单订阅
-如果您想订阅所有合约中的订单更新,请在合约列表中使用 !all。
-
-订阅订单更新推送
-
-#请求参数
-channel
-
-futures.orders
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-user id String 是 用户 ID
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.orders",
- "event": "subscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.orders",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#订单推送
-下单、更新或完成时通知用户订单信息
-
-#推送参数
-channel
-
-futures.orders
-
-event
-
-update
-
-params
-
-推送结果参数含义请参考http接口.
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-create_time Integer 订单创建时间(已弃用)
-create_time_ms Integer 订单创建时间戳(以毫秒为单位)
-fill_price Float 订单成交价格
-finish_as String 订单是如何完成的。
-- filled:全部成交
-- cancelled:手动取消
-- liquidated:因清算而取消
-- ioc:生效时间为 IOC,立即完成
-- auto_deleveraging:ADL 完成
-- reduce_only:因减仓设置而增仓而取消
-- position_close:因平仓而取消
-- stp:订单发生自成交限制而被撤销
-- _new:新建
-- _update:成交或部分成交或更新订单
-- reduce_out: 只减仓被排除的不容易成交的挂单
-iceberg String/Integer 冰山下单显示的数量,不指定或传 0 都默认为普通下单。目前不支持全部冰山。
-id Integer 订单 ID
-is_close Bool 该订单是否为 close position
-is_liq Bool 该订单是否为 liquidation
-left String/Integer 剩余可交易数量
-mkfr Float Maker 费用
-is_reduce_only Bool 该订单是否为 reduce-only
-status String 订单状态
-- open: 等待交易
-- finished: 完成
-tkfr Float taker 费用
-price Float 订单价格。 0 表示市价订单,tif 设置为 ioc
-refu Integer 推荐用户 ID
-refr Float
-size String/Integer 订单大小。指定正数进行出价,指定负数进行询问
-text String 用户定义的信息
-tif String 有效时间
-- gtc:GoodTillCancelled
-- ioc:ImmediateOrCancelled,仅接受者
-- poc:PendingOrCancelled,只进行后订单,始终享受挂单费用
-- fok: FillOrKill,完全填充或不填充
-type=market 时仅支持 ioc 和 fok
-finish_time Integer 订单结束 unix 时间戳(以秒为单位),未结束订单此字段返回0
-finish_time_ms Integer 订单结束 unix 时间戳(以毫秒为单位),未结束订单此字段返回0
-user String 用户 ID
-contract String 合约名称
-stp_id String 同一 stp_id 组内的用户之间的订单不允许自交易
-1.如果匹配的两个订单的 stp_id 非零且相等,则不会被执行。而是根据 taker 的 stp_act 执行相应的策略。
-2.对于未设置 STP 组的订单,stp_id 默认返回 0
-stp_act String 自我交易预防行动。用户可以通过该字段设置自我交易防范策略
-1.用户加入 STP Group 后,可以通过 stp_act 来限制用户的自我交易防范策略。如果不传 stp_act,则默认为 cn 策略。
-2.当用户没有加入 STP 组时,传递 stp_act 参数时会返回错误。
-3.如果用户下单时没有使用'stp_act','stp_act'将返回'-'
-- cn: 取消最新订单,取消新订单并保留旧订单
-- co: 取消最旧订单,取消旧订单并保留新订单
-- cb:取消两者,新旧订单都会被取消
-amend_text String 用户修改订单时备注的自定义数据
-update_id Integer 更新id
-update_time Integer 更新时间 (毫秒时间戳)
-biz_info String 用户可以备注这次修改的信息
-stop_profit_price String 止盈价格
-stop_loss_price String 止损价格
-market_order_slip_ratio String 该笔市价单的预设的最大滑点比率(不代表实际的滑点),0.03代表3%的最大滑点
-{
- "channel": "futures.orders",
- "event": "update",
- "time": 1541505434,
- "time_ms": 1541505434123,
- "result": [
- {
- "contract": "BTC_USD",
- "create_time": 1628736847,
- "create_time_ms": 1628736847325,
- "fill_price": 40000.4,
- "finish_as": "filled",
- "finish_time": 1628736848,
- "finish_time_ms": 1628736848321,
- "iceberg": "0",
- "id": 4872460,
- "is_close": false,
- "is_liq": false,
- "is_reduce_only": false,
- "left": "0",
- "mkfr": -0.00025,
- "price": 40000.4,
- "refr": 0,
- "refu": 0,
- "size": "1",
- "status": "finished",
- "text": "-",
- "tif": "gtc",
- "tkfr": 0.0005,
- "user": "110xxxxx",
- "update_id": 1,
- "update_time": 1541505434123,
- "stop_loss_price": "",
- "stop_profit_price": "",
- "market_order_slip_ratio": "0.03"
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅订单更新通知
-
-#请求参数
-channel
-
-futures.orders
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.orders",
- "event": "unsubscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.orders",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#用户私有成交频道
-提供接收用户交易的方式
-
-需要认证
-
-#用户私有成交订阅
-如果您想订阅所有的市场交易更新,请在请求参数列表中使用!all。
-
-订阅私有成交更新
-
-#请求参数
-channel
-
-futures.usertrades
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-user id String 是 用户 ID
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.usertrades",
- "event": "subscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.usertrades",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#用户私有成交推送
-推送用户私有成交更新
-
-#推送参数
-channel
-
-futures.usertrades
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-contract String 合约名称
-create_time Integer 创建时间
-create_time_ms Integer 创建时间(以毫秒为单位)
-id String 交易 ID
-order_id String 订单 ID
-price String 交易价格
-size String/Integer 交易数量
-role String 用户角色 (maker/taker)
-text String 订单自定义信息,用户可以用该字段设置自定义 ID,用户自定义字段必须满足以下条件:
-
-1. 必须以 t- 开头
-2. 不计算 t- ,长度不能超过 28 字节
-3. 输入内容只能包含数字、字母、下划线(_)、中划线(-) 或者点(.)
-
-除用户自定义信息以外,以下为内部保留字段,标识订单来源:
-
-- web: 网页
-- api: API 调用
-- app: 移动端
-- auto_deleveraging: 自动减仓
-- liquidation: 老经典模式仓位强制平仓
-- liq-xxx: a. 新经典模式仓位强制平仓,包含逐仓、单向全仓、双向全仓非对冲仓位强平。 b. 统一账户单币种保证金模式逐仓强制平仓
-- hedge-liq-xxx: 新经典模式双向全仓对冲部分强制平仓,即同时平多空仓位
-- pm_liquidate: 统一账户跨币种保证金模式强制平仓
-- comb_margin_liquidate: 统一账户组合保证金模式强制平仓
-- scm_liquidate: 统一账户单币种保证金模式仓位强制平仓
-- insurance: 保险
-- clear: 合约下架清退
-fee Float 手续费
-point_fee Float 点卡手续费
-{
- "time": 1543205083,
- "time_ms": 1543205083123,
- "channel": "futures.usertrades",
- "event": "update",
- "result": [
- {
- "id": "3335259",
- "create_time": 1628736848,
- "create_time_ms": 1628736848321,
- "contract": "BTC_USD",
- "order_id": "4872460",
- "size": "1",
- "price": "40000.4",
- "role": "maker",
- "text": "api",
- "fee": 0.0009290592,
- "point_fee": 0
- }
- ]
-}
-Copy
-#取消订阅
-取消私有成交订阅
-
-#请求参数
-channel
-
-futures.usertrades
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.usertrades",
- "event": "unsubscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.usertrades",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#强制平仓频道
-提供一种接收用户强制平仓信息的方式
-
-需要认证
-
-#清算订阅
-如果您想订阅所有合约中的强制平仓推送,请在订阅请求列表中使用 !all
-
-订阅用户强制平仓推送
-
-#请求参数
-channel
-
-futures.liquidates
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-user id String 是 用户 ID
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.liquidates",
- "event": "subscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.liquidates",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#强制平仓推送
-推送强制平仓更新
-
-#推送参数
-channel
-
-futures.liquidates
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-entry_price Float 平均入场价
-fill_price Float 平均执行价格
-leverage Float 杠杆大小
-liq_price Float 清算价格
-margin Float Margin
-mark_price Float 标记价格
-order_id Integer 订单 ID
-order_price Float 订单价格
-left String/Integer 订单未完成数量
-size Integer 原始订单数量
-time Integer 时间
-time_ms Integer 时间(以毫秒为单位)
-user String 用户 ID
-contract String 合约名称
-{
- "channel": "futures.liquidates",
- "event": "update",
- "time": 1541505434,
- "time_ms": 1541505434123,
- "result": [
- {
- "entry_price": 209,
- "fill_price": 215.1,
- "left": 0,
- "leverage": 0.0,
- "liq_price": 213,
- "margin": 0.007816722941,
- "mark_price": 213,
- "order_id": 4093362,
- "order_price": 215.1,
- "size": "-124",
- "time": 1541486601,
- "time_ms": 1541486601123,
- "contract": "BTC_USD",
- "user": "1040xxxx"
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅清算更新
-
-#请求参数
-channel
-
-futures.liquidates
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.liquidates",
- "event": "unsubscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.liquidates",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#自动减仓频道
-提供一种接收用户自动减仓信息的方法
-
-需要认证
-
-#自动减仓订阅
-如果您想订阅所有合约的自动减仓更新,请在请求参数列表中使用!all
-
-订阅用户自动减仓更新
-
-#请求参数
-channel
-
-futures.auto_deleverages
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-user id String 是 用户 ID
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.auto_deleverages",
- "event": "subscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.auto_deleverages",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#自动减仓推送
-自动减仓消息
-
-#推送参数
-channel
-
-futures.auto_deleverages
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-entry_price Float 入场价格
-fill_price Float 执行价格
-position_size Integer 持仓规模
-trade_size Integer 交易数量
-time Integer 时间
-time_ms Integer 时间(以毫秒为单位)
-user String 用户 ID
-contract String 合约名称
-{
- "channel": "futures.auto_deleverages",
- "event": "update",
- "time": 1541505434,
- "time_ms": 1541505434123,
- "result": [
- {
- "entry_price": 209,
- "fill_price": 215.1,
- "position_size": "10",
- "trade_size": "10",
- "time": 1541486601,
- "time_ms": 1541486601123,
- "contract": "BTC_USD",
- "user": "1040"
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅自动减仓
-
-#请求参数
-channel
-
-futures.auto_deleverages
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.auto_deleverages",
- "event": "unsubscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.auto_deleverages",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#平仓频道
-提供一种接收用户仓位平仓信息的方法
-
-需要认证
-
-#平仓订阅
-如果您想订阅所有合约的平仓更新,请在合约列表中使用 !all
-
-订阅用户平仓信息更新
-
-#请求参数
-channel
-
-futures.position_closes
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-user id String 是 用户 ID
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.position_closes",
- "event": "subscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.position_closes",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#平仓信息推送
-平仓信息推送
-
-#推送参数
-channel
-
-futures.position_closes
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-contract String 合约名称
-pnl Number 利润损失
-side String 方向 (long or short)
-text String 附带信息
-time Integer 时间
-time_ms Integer 时间(以毫秒为单位)
-user String 用户 ID
-{
- "channel": "futures.position_closes",
- "event": "update",
- "time": 1541505434,
- "time_ms": 1541505434123,
- "result": [
- {
- "contract": "BTC_USD",
- "pnl": -0.000624354791,
- "side": "long",
- "text": "web",
- "time": 1547198562,
- "time_ms": 1547198562123,
- "user": "211xxxx"
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅平仓更新
-
-#请求参数
-channel
-
-futures.position_closes
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.position_closes",
- "event": "unsubscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.position_closes",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#余额频道
-提供一种接收用户余额信息的方法
-
-需要认证
-
-#余额信息订阅
-订阅用户余额更新
-
-#请求参数
-channel
-
-futures.balances
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-user id String 是 用户 ID
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.balances",
- "event": "subscribe",
- "payload": ["20011"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.balances",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#余额更新推送
-通知余额更新信息
-
-#推送参数
-channel
-
-futures.balances
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-balance Number 余额最终数量
-change Number 余额变化数量
-text String 附带信息
-time Integer 时间
-time_ms Integer 时间(以毫秒为单位)
-type String 变更类型:
-dnw: 出入金
-pnl:盈亏
-refr: 推荐人费用
-fund: 资金费用
-cross_settle: 统一账户结算
-point_dnw: 点卡出入金
-point_fee: 点卡手续费
-point_refr: 点卡推荐人费用
-bonus_dnw: 体验金出入金
-pv_dnw: 仓位体验券充值提现
-fee: 手续费
-user String 用户 ID
-currency String 币种
-{
- "channel": "futures.balances",
- "event": "update",
- "time": 1541505434,
- "time_ms": 1541505434123,
- "result": [
- {
- "balance": 9.998739899488,
- "change": -0.000002074115,
- "text": "BTC_USD:3914424",
- "time": 1547199246,
- "time_ms": 1547199246123,
- "type": "fee",
- "user": "211xxx",
- "currency": "btc"
- }
- ]
-}
-Copy
-#取消订阅
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.balances",
- "event": "unsubscribe",
- "payload": ["20011"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.balances",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#降低风险率频道
-推送用户降低风险率信息
-
-需要认证
-
-#降低风险率订阅
-如果您想订阅所有合约的降低风险率更新,请在合约列表中使用 !all。
-
-订阅用户降低风险率更新
-
-#请求参数
-channel
-
-futures.reduce_risk_limits
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-user id String 是 用户 ID
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.reduce_risk_limits",
- "event": "subscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.reduce_risk_limits",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#降低风险率推送
-通知降低风险限制更新
-
-#推送参数
-channel
-
-futures.reduce_risk_limits
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-cancel_orders Integer Cancel orders
-contract String 合约名称
-leverage_max Integer 最大杠杆
-liq_price Float 清算价格
-maintenance_rate Float Maintenance rate
-risk_limit Integer 风险限额
-time Integer 时间
-time_ms Integer 时间(以毫秒为单位)
-user String 用户 ID
-{
- "time": 1551858330,
- "time_ms": 1551858330123,
- "channel": "futures.reduce_risk_limits",
- "event": "update",
- "result": [
- {
- "cancel_orders": 0,
- "contract": "ETH_USD",
- "leverage_max": 10,
- "liq_price": 136.53,
- "maintenance_rate": 0.09,
- "risk_limit": 450,
- "time": 1551858330,
- "time_ms": 1551858330123,
- "user": "20011"
- }
- ]
-}
-Copy
-#取消订阅
-退订降低风险限制更新
-
-#请求参数
-channel
-
-futures.reduce_risk_limits
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.reduce_risk_limits",
- "event": "unsubscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-#仓位频道
-提供一种接收用户仓位信息的方法
-
-需要认证
-
-#仓位订阅
-如果您想订阅所有合约的持仓更新,请在合约列表中使用 !all
-
-订阅用户仓位更新
-
-#请求参数
-channel
-
-futures.positions
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-user id String 是 用户 ID (该字段已废弃,仅做占位符使用)
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.positions",
- "event": "subscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.positions",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#仓位信息推送
-推送仓位更新.
-
-#推送参数
-channel
-
-futures.positions
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-contract String 合约名称
-cross_leverage_limit Float 全仓模式下的杠杆倍数
-entry_price Float 开仓价格
-history_pnl Float 已平仓的仓位总盈亏
-history_point Float 已平仓的点卡总盈亏
-last_close_pnl Float 最近一次平仓的盈亏
-leverage Integer 杠杆倍数,0代表全仓,正数代表逐仓
-leverage_max Integer 当前风险限额下,允许的最大杠杆倍数
-liq_price Float 爆仓价格
-maintenance_rate Float 当前风险限额下,维持保证金比例
-margin Float 保证金
-realised_pnl Float 已实现盈亏
-realised_point Float 点卡已实现盈亏
-risk_limit Integer 风险限额
-size String/Integer 合约 size
-time Integer 更新 unix 时间戳
-time_ms Integer 更新 unix 时间戳(以毫秒为单位)
-user String 用户 ID
-update_id Integer 消息序列号,每次推送 order 之后会自增 1
-{
- "time": 1588212926,
- "time_ms": 1588212926123,
- "channel": "futures.positions",
- "event": "update",
- "result": [
- {
- "contract": "BTC_USD",
- "cross_leverage_limit": 0,
- "entry_price": 40000.36666661111,
- "history_pnl": -0.000108569505,
- "history_point": 0,
- "last_close_pnl": -0.000050123368,
- "leverage": 0,
- "leverage_max": 100,
- "liq_price": 0.1,
- "maintenance_rate": 0.005,
- "margin": 49.999890611186,
- "mode": "single",
- "realised_pnl": -1.25e-8,
- "realised_point": 0,
- "risk_limit": 100,
- "size": "3",
- "time": 1628736848,
- "time_ms": 1628736848321,
- "user": "110xxxxx",
- "update_id": 170919
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅仓位更新
-
-#请求参数
-channel
-
-futures.positions
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.positions",
- "event": "unsubscribe",
- "payload": ["20011", "BTC_USD"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.positions",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#仓位 Adl 排名频道
-推送用户仓位 adl 排名信息的频道
-
-需要认证
-
-#仓位 adl 订阅
-如果您想订阅所有合约的 adl 更新,请在合约列表中使用 !all
-
-订阅用户仓位 adl 更新
-
-#请求参数
-channel
-
-futures.position_adl_rank
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://ws-testnet.gate.com/v4/ws/futures/usdt")
-req = {
- "time": int(time.time()),
- "channel": "futures.position_adl_rank",
- "event": "subscribe",
- "payload": ["BTC_USDT"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.position_adl_rank",
- "event": "subscribe",
- "payload": [
- "BTC_USDT"
- ],
- "result": {
- "status": "success"
- }
-}
-Copy
-#仓位 adl 信息推送
-推送仓位更新.
-
-#推送参数
-channel
-
-futures.position_adl_rank
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-contract String 合约名称
-mode String 持仓模式
-rank_division Integer 排名区间(1~6,1为最优先被ADL的区间,5为最靠后被ADL的区间。6:爆仓中不参与ADL排序)
-time_ms Integer 消息推送的毫秒时间戳
-user_id Integer 用户 ID
-{
- "time": 1588212926,
- "time_ms": 1588212926123,
- "channel": "futures.position_adl_rank",
- "event": "update",
- "result": [
- {
- "contract": "BTC_USDT",
- "mode": "single",
- "rank_division": 1,
- "time_ms": 1588212926119,
- "user_id": 2124426495
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅仓位更新
-
-#请求参数
-channel
-
-futures.position_adl_rank
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://ws-testnet.gate.com/v4/ws/futures/usdt")
-req = {
- "time": int(time.time()),
- "channel": "futures.position_adl_rank",
- "event": "unsubscribe",
- "payload": ["BTC_USDT"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.position_adl_rank",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#自动订单频道
-提供一种接收用户自动订单信息的方法
-
-需要认证
-
-#自动订单订阅
-如果您想订阅所有合约中的自动订单更新,请在合约列表中使用!all
-
-订阅用户自动订单更新
-
-#请求参数
-channel
-
-futures.autoorders
-
-event
-
-subscribe
-
-params
-
-请求参数
-名称 类型 必选 描述
-contract String 是 合约名称
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.autoorders",
- "event": "subscribe",
- "payload": ["BTC_USDT"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }
-}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.autoorders",
- "event": "subscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#自动订单消息推送
-通知自动订单更新
-
-#推送参数
-channel
-
-futures.autoorders
-
-event
-
-update
-
-params
-
-推送参数
-名称 类型 描述
-result Array Array of objects
-推送参数
-名称 类型 描述
-user Number 用户 ID
-trigger Object
-initial Object
-id Number 自动订单 ID
-trade_id Number 交易 ID
-status String 订单状态
-reason String 变更原因
-create_time Number 创建时间
-name String 名称
-is_stop_order boolean 是否停止
-stop_trigger Object
-order_type String 止盈/止损类型,详情参见 http api
-me_order_id Number 订单止盈/止损对应订单 ID.
-{
- "time": 1596798126,
- "time_ms": 1596798126123,
- "channel": "futures.autoorders",
- "event": "update",
- "result": [
- {
- "user": 123456,
- "trigger": {
- "strategy_type": 0,
- "price_type": 0,
- "price": "10000",
- "rule": 2,
- "expiration": 86400
- },
- "initial": {
- "contract": "BTC_USDT",
- "size": "10",
- "price": "10000",
- "tif": "gtc",
- "text": "web",
- "iceberg": "0",
- "is_close": false,
- "is_reduce_only": false,
- "auto_size": ""
- },
- "id": 9256,
- "trade_id": 0,
- "status": "open",
- "reason": "",
- "create_time": 1596798126,
- "name": "price_autoorders",
- "is_stop_order": false,
- "stop_trigger": {
- "rule": 0,
- "trigger_price": "",
- "order_price": ""
- },
- "order_type": "close-long-order",
- "me_order_id": "213867453823"
- }
- ]
-}
-Copy
-#取消订阅
-取消订阅自动订单更新
-
-#请求参数
-channel
-
-futures.autoorders
-
-event
-
-unsubscribe
-
-代码示例
-
-import json, time
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws-testnet.gateio.ws/v4/ws/btc")
-req = {
- "time": int(time.time()),
- "channel": "futures.autoorders",
- "event": "unsubscribe",
- "payload": ["BTC_USDT"],
- "auth": {
- "method": "api_key",
- "KEY": "xxxx",
- "SIGN": "xxxx"
- }}
-ws.send(json.dumps(req))
-print(ws.recv())
-Copy
-上面的命令返回 JSON 结构如下:
-
-{
- "time": 1545459681,
- "time_ms": 1545459681123,
- "channel": "futures.autoorders",
- "event": "unsubscribe",
- "result": {
- "status": "success"
- }
-}
-Copy
-#账户交易 API
-#Websocket 交易 API
-WebSocket API 允许通过 WebSocket 连接下单、取消、修改、查询订单.
-
-#Websocket API 客户端请求
-客户端发起的 api 请求遵循通用的 JSON 格式, 包含以下字段:
-
-名称 类型 必选 描述
-time Integer 是 请求时间(以秒为单位)。请求时间和服务器时间之间的差距不得超过 60 秒
-id Integer 否 可选的请求 ID,将由服务器发回,以帮助您识别服务器响应哪个请求
-channel String 是 要访问的 WebSocket 频道
-event String 是 固定为api
-payload Object 是 可选请求详细参数
-»req_id String 是 消息的唯一标识符由客户端提供,将在响应消息中返回,用于标识相应的请求。
-»timestamp String 是 签名时间(秒)
-»api_key String 是 Gate APIv4 APIKey
-»signature String 是 使用 GateAPIv4 密钥和请求信息生成的身份验证签名,
-详细信息请参见[Websocket API 身份验证](#Websocket API 身份验证)部分
-»req_param []Byte 是 请求 api 参数
-请注意,payload.req_param 的类型是与频道(channel字段)绑定的,频道不同payload.req_param 的字段也不同,以 futures.order_place 为例,payload.req_param 与 apiv4 /futures/{settle}/orders (opens new window)。 例如,您可以对 BTC_USDT 下限价单
-
-代码示例
-
-#!/usr/bin/python
-
-import time
-import json
-import hmac
-import hashlib
-import websocket
-import threading
-
-
-API_KEY = "xxxxx"
-SECRET = "xxxxx"
-WS_URL = "wss://fx-ws.gateio.ws/v4/ws/usdt"
-CHANNEL_LOGIN = "futures.login"
-CHANNEL_ORDER_PLACE = "futures.order_place"
-
-def get_ts():
- return int(time.time())
-
-def get_ts_ms():
- return int(time.time() * 1000)
-
-def get_signature(secret, channel, request_param_bytes, ts):
- key = f"api\n{channel}\n{request_param_bytes.decode()}\n{ts}"
- return hmac.new(secret.encode(), key.encode(), hashlib.sha512).hexdigest()
-
-def build_login_request():
- ts = get_ts()
- req_id = f"{get_ts_ms()}-1"
- request_param = b""
-
- sign = get_signature(SECRET, CHANNEL_LOGIN, request_param, ts)
-
- payload = {
- "api_key": API_KEY,
- "signature": sign,
- "timestamp": str(ts),
- "req_id": req_id
- }
-
- return {
- "time": ts,
- "channel": CHANNEL_LOGIN,
- "event": "api",
- "payload": payload
- }
-
-
-def build_order_request():
- ts = get_ts()
- req_id = f"{get_ts_ms()}-2"
- order_param = {
- "contract": "BTC_USDT",
- "size": 6024,
- "iceberg": 0,
- "price": "3765",
- "tif": "gtc",
- "text": "t-my-custom-id",
- "stp_act": "-"
- }
-
-
- payload = {
- "req_id": req_id,
- "req_param": order_param
- }
-
- return {
- "time": ts,
- "channel": CHANNEL_ORDER_PLACE,
- "event": "api",
- "payload": payload
- }
-
-
-def on_message(ws, message):
- print(f"recv: {message}")
-
-def on_error(ws, error):
- print(f"error: {error}")
-
-def on_close(ws, close_status_code, close_msg):
- print("connection closed")
-
-def on_open(ws):
- print("WebSocket opened")
-
- login_payload = build_login_request()
- print("login payload:", login_payload)
- ws.send(json.dumps(login_payload))
-
- def delayed_order():
- time.sleep(2)
- order_payload = build_order_request()
- print("order payload:", order_payload)
- ws.send(json.dumps(order_payload))
-
- threading.Thread(target=delayed_order).start()
-
-custom_headers = {
- "X-Gate-Size-Decimal": "1"
-}
-
-if __name__ == "__main__":
- ws = websocket.WebSocketApp(
- WS_URL,
- on_message=on_message,
- on_error=on_error,
- on_close=on_close,
- on_open=on_open,
- header=custom_headers
- )
- ws.run_forever()
-
-Copy
-{
- "time": 1680772890,
- "channel": "futures.order_place",
- "event": "api",
- "payload": {
- "req_id": "xxxx",
- "req_param": {
- "contract": "BTC_USDT",
- "size": "10",
- "price": "80048.240000",
- "tif": "gtc",
- "text": "t-my-custom-id"
- }
- }
-}
-Copy
-#Websocket API 服务响应
-服务器响应包括对客户端请求的 ack 响应和 api 结果消息推送。 服务器响应遵循通用的 JSON 格式,其中包含以下字段:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»x_gate_ratelimit_requests_remain Integer (仅涉及下单/改单/撤单)当前时间窗口剩余可用请求数(为0不展示)
-»x_gate_ratelimit_limit Integer (仅涉及下单/改单/撤单)当前频率限制上限(为0不展示)
-»x_gat_ratelimit_reset_timestamp Integer (仅涉及下单/改单/撤单)已超过当前窗口频率限制,表示下个可用时间窗口的时间戳(毫秒),即什么时候可以恢复访问;未超过当前窗口频率限制,表示返回的是当前服务器时间(毫秒)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-data Object 请求响应的数据
-»result Object 如果这是 ack 响应,则结果是请求的payload,否则结果是 api 的响应
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-服务器回声确认响应示例(目前仅在下单请求中有回声响应)
-
-{
- "request_id": "request-id-1",
- "ack": true,
- "header": {
- "response_time": "1681195121499",
- "status": "200",
- "channel": "futures.order_place",
- "event": "api",
- "client_id": "::1-0x140031563c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gate_ratelimit_reset_timestamp": 1681195121499
- },
- "data": {
- "result": {
- "req_id": "request-id-1",
- "req_param": {
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31503.280000",
- "tif": "gtc",
- "text": "t-my-custom-id"
- }
- }
- }
-}
-Copy
-服务器 API 响应示例
-
-{
- "request_id": "request-id-1",
- "ack": false,
- "header": {
- "response_time": "1681195121639",
- "status": "200",
- "channel": "futures.order_place",
- "event": "api",
- "client_id": "::1-0x140031563c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gate_ratelimit_reset_timestamp": 1681195121639
- },
- "data": {
- "result": {
- "id": 74046511,
- "user": 6790020,
- "create_time": 1681195121.754,
- "finish_time": 1681195121.754,
- "finish_as": "filled",
- "status": "finished",
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31503.3",
- "tif": "gtc",
- "fill_price": "31500",
- "text": "t-my-custom-id",
- "tkfr": "0.0003",
- "mkfr": "0",
- "stp_id": 2,
- "stp_act": "cn",
- "amend_text": "-"
- }
- }
-}
-Copy
-#错误
-错误响应详情具有以下格式:
-
-
-名称 类型 描述
-label String 错误类型
-message String 详细错误信息
-限频相关的错误码说明:
-
-错误码 描述
-100 限流内部异常错误
-311 合约限流
-312 合约成交比率限流
-错误响应通知示例
-
-{
- "request_id": "request-id-1",
- "ack": false,
- "header": {
- "response_time": "1681195360034",
- "status": "401",
- "channel": "futures.login",
- "event": "api",
- "client_id": "::1-0x140001a2600",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d"
- },
- "data": {
- "errs": {
- "label": "INVALID_KEY",
- "message": "Invalid key provided"
- }
- }
-}
-Copy
-Error 推送示例(限速)
-
-{
- "request_id": "xxxx",
- "header": {
- "response_time": "1677816784084",
- "status": "429",
- "channel": "futures.order_place",
- "event": "api",
- "client_id": "::1-0x14002ba2300",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_limit": 100,
- "x_gate_ratelimit_reset_timestamp": 1677816785084
- },
- "data": {
- "errs": {
- "label": "TOO_MANY_REQUESTS",
- "message": "Request Rate limit Exceeded (311)"
- }
- }
-}
-Copy
-#登录
-注意:您使用的 GateAPIv4 密钥对必须具有合约账户对应的权限(例如:order-place 频道必须具有合约账户写入权限), 如果启用了密钥的白名单,则您的出站 IP 地址必须在密钥的 IP 白名单中.
-
-#登录请求
-客户端 API 请求
-
-payload参数:
-
-名称 类型 必选 描述
-req_id string 是 请求 id,将由服务器发回,以帮助您识别服务器响应哪个请求,
-它与外部的id不同
-api_key string 是 Apiv4 key
-req_header object 是 Apiv4 自定义 header
-signature string 是 Apiv4 签名
-timestamp string 是 Unix 时间戳(以秒为单位)
-WebSocket api 操作认证使用与 Gate APIv4 API 相同的签名计算方法,即 HexEncode(HMAC_SHA512(secret,signature_string)),但有以下区别:
-
-签名字符串拼接方式:<event>\n<channel>\n<req_param>\n<timestamp>, 其中<event>、<channel>、<req_param>、<timestamp>是对应的请求信息
-login频道中的 req_param始终为空字符串
-身份验证信息在请求正文中的payload字段中发送。
-代码示例
-
-import hmac
-import hashlib
-import json
-import time
-import websocket
-import ssl
-
-def get_api_signature(secret, channel, request_param, ts):
- key = f"api\n{channel}\n{request_param}\n{ts}"
- hash_object = hmac.new(secret.encode(), key.encode(), hashlib.sha512)
- return hash_object.hexdigest()
-
-class ApiPayload:
- def __init__(self, api_key, signature, timestamp, req_id, request_param):
- self.api_key = api_key
- self.signature = signature
- self.timestamp = timestamp
- self.req_id = req_id
- self.request_param = request_param
-
-class ApiRequest:
- def __init__(self, ts, channel, event, payload):
- self.time = ts
- self.channel = channel
- self.event = event
- self.payload = payload
-
-def main():
- api_key = "YOUR_API_KEY"
- secret = "YOUR_API_SECRET"
- request_param = ""
- channel = "futures.login"
- ts = int(time.time())
- request_id = f"{int(time.time() * 1000)}-1"
-
- payload = ApiPayload(
- api_key=api_key,
- signature=get_api_signature(secret, channel, request_param, ts),
- timestamp=str(ts),
- req_id=request_id,
- request_param=request_param
- )
-
- req = ApiRequest(ts=ts, channel=channel, event="api", payload=payload)
-
- print(get_api_signature(secret, channel, request_param, ts))
-
- req_json = json.dumps(req, default=lambda o: o.__dict__)
- print(req_json)
-
- # Connect to WebSocket
- ws_url = "wss://fx-ws.gateio.ws/v4/ws/usdt" # Replace with your WebSocket URL
- websocket.enableTrace(False)
- ws = websocket.create_connection(ws_url, sslopt={"cert_reqs": ssl.CERT_NONE})
-
- # Function to receive messages
- def recv_messages():
- while True:
- try:
- message = ws.recv()
- print(f"recv: {message}")
- except Exception as e:
- print(f"Error receiving message: {e}")
- ws.close()
- break
-
- # Start receiving messages in a separate thread
- import threading
- receive_thread = threading.Thread(target=recv_messages)
- receive_thread.start()
-
- # Send the request
- ws.send(req_json)
-
- # Keep the main thread running
- receive_thread.join()
-
-if __name__ == "__main__":
- main()
-Copy
-请求示例
-
-{
- "time": 1681984544,
- "channel": "futures.login",
- "event": "api",
- "payload": {
- "api_key": "ea83fad2604399da16bf97e6eea772a6",
- "signature": "6fa3824c8141f2b2283108558ec50966d7caf749bf04a3b604652325b50b47d2343d569d848373d58e65c49d9622ba2e73dc25797abef11c9f20c07da741591e",
- "timestamp": "1681984544",
- "req_id": "request-1"
- }
-}
-Copy
-#登录响应
-响应参数:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-data Object 请求响应的数据
-»result Object 如果这是 ack 响应,则结果是请求的payload,否则结果是 api 的响应
-»»api_key String 登录成功的 apikey
-»»uid String 登录成功的用户 ID
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-登录响应示例
-
-{
- "request_id": "request-1",
- "header": {
- "response_time": "1681985856666",
- "status": "200",
- "channel": "futures.login",
- "event": "api",
- "clientId": "",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d"
- },
- "data": {
- "result": {
- "api_key": "ea83fad2604399da16bf97e6eea772a6",
- "uid": "110284739"
- }
- }
-}
-Copy
-#下单
-futures.order_place
-
-您可以通过此频道进行下单操作.
-
-本频道和以下的 APIV4 功能相同:
-
-POST /futures/{
- settle
-}/orders
-#下单请求
-payload参数:
-
-名称 类型 必选 描述
-req_id string 是 请求 id,服务器会发回,帮助你识别服务器响应的是哪个请求,
-它与外部的id不同
-req_param object 是 使用 api 下单参数; api 下单参数详细信息api(opens new window)
-req_header object 否 Apiv4 自定义请求头
-req_param API 订单模型的 JSON 字节数据:
-
-字段 类型 必选 描述
-contract string 是 合约
-size int64 是 订单大小。指定正数进行出价,指定负数进行询问
-iceberg string 否 冰山订单的显示尺寸。0 表示非冰山。请注意,您需要支付隐藏尺寸的接受者费用
-price string 否 订单价格。0 表示市价订单,tif设置为ioc
-close bool 否 设置为true平仓,size设置为 0
-reduce_only bool 否 设置为true仅减少订单
-tif string 否 有效时间
-text string 否 用户定义的信息。如果不为空,则必须遵循以下规则:
-auto_size string 否 将侧面设置为关闭双模式位置。close_long闭合长边;而close_short短的。注意size还需要设置为 0
-stp_act string 否 自我交易预防行动。用户可以通过该字段设置自助交易防范策略
-market_order_slip_ratio string 否 该笔市价单的预设的最大滑点比率(不代表实际的滑点),0.03代表3%的最大滑点
-req_header 自定义 header 数据:
-
-字段 类型 必选 描述
-x-gate-exptime string 否 指定过期的时间戳(毫秒)。如果 ws 收到请求的时间大于过期时间,请求将被拒绝
-#详细描述
-tif: 有效时间
-
-gtc:取消前有效
-ioc: ImmediateOrCancelled, 仅接受者
-poc:PendingOrCancelled,仅发布订单,始终享受挂单费用
-fok:FillOrKill,完全填充或不填充
-text: 订单自定义信息,用户可以用该字段设置自定义 ID,用户自定义字段必须满足以下条件:
-
-必须以 t- 开头
-不计算 t- ,长度不能超过 28 字节
-输入内容只能包含数字、字母、下划线(_)、中划线(-) 或者点(.)
-不填,默认 apiv4-ws,来自 ws
-web:来自网络
-api:来自 API
-app:来自手机
-auto_deleveraging:来自 ADL
-liquidation:来自清算
-insurance:来自保险
-stp_act:自我交易预防行动。用户可以通过该字段设置自助交易防范策略 用户加入后STP Group,他可以通过stp_act限制用户的自我交易防范策略。如果stp_act不通过则默认为cn策略。 当用户没有加入时STP group,传递参数时会返回错误stp_act。 如果用户下单时没有使用stp_act,stp_act将返回-
-
-cn:取消最新订单,取消新订单并保留旧订单
-co:取消最旧的订单,取消旧订单并保留新订单
-cb:取消两者,新旧订单都会被取消
-代码示例:请求前要先登录
-
-import time
-import json
-
-# pip install websocket_client
-from websocket import create_connection
-
-api_order = {
- "contract": "BTC_USDT",
- "size": 10,
- "price": "31503.280000",
- "tif": "gtc",
- "text": "t-my-custom-id"
- }
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-ws.send(json.dumps(
- {"time": int(time.time()),
- "channel": "futures.order_place",
- "event": "api",
- "payload": {
- "req_id": "1ewq-3123w-5",
- "req_param": api_order
- }}
-))
-
-print(ws.recv())
-Copy
-请求示例
-
-{
- "time": 1681195484,
- "channel": "futures.order_place",
- "event": "api",
- "payload": {
- "req_id": "request-id-1",
- "req_param": {
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31503.280000",
- "tif": "gtc",
- "text": "t-my-custom-id"
- }
- }
-}
-Copy
-#订单请求回声消息
-订单确认回声通知示例
-
-{
- "request_id": "request-id-1",
- "ack": true,
- "header": {
- "response_time": "1681195484268",
- "status": "200",
- "channel": "futures.order_place",
- "event": "api",
- "client_id": "::1-0x140001a2600",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gat_ratelimit_reset_timestamp": 1736408263764
- },
- "data": {
- "result": {
- "req_id": "request-id-1",
- "req_header": null,
- "req_param": {
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31503.280000",
- "tif": "gtc",
- "text": "t-my-custom-id"
- }
- }
- }
-}
-Copy
-#下单结果通知
-下单时返回订单信息 响应参数:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-ack Bool "ack"消息的返回表示 WebSocket 的确认消息(目前在下单接口中存在)。
-如果ack为 false(false 该字段不会出现在响应中),则说明该消息是响应消息,可以判断请求是否成功<br / >通过检查data.errs。
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-»x_gate_ratelimit_requests_remain Integer 当前时间窗口剩余可用请求数(为0不展示)
-»x_gate_ratelimit_limit Integer 当前频率限制上限(为0不展示)
-»x_gat_ratelimit_reset_timestamp Integer 已超过当前窗口频率限制,表示下个可用时间窗口的时间戳(毫秒),即什么时候可以恢复访问;未超过当前窗口频率限制,表示返回的是当前服务器时间(毫秒)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-data Object 请求响应的数据
-»result Object 如果这是 ack 响应,则结果是请求的payload,否则结果是 api 的响应
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-响应返回示例
-
-{
- "request_id": "request-id-1",
- "ack": false,
- "header": {
- "response_time": "1681195484360",
- "status": "200",
- "channel": "futures.order_place",
- "event": "api",
- "client_id": "::1-0x140001a2600",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gat_ratelimit_reset_timestamp": 1736408263764
- },
- "data": {
- "result": {
- "id": 74046514,
- "user": 6790020,
- "create_time": 1681195484.462,
- "finish_time": 1681195484.462,
- "finish_as": "filled",
- "status": "finished",
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31503.3",
- "tif": "gtc",
- "fill_price": "31500",
- "text": "t-my-custom-id",
- "tkfr": "0.0003",
- "mkfr": "0",
- "stp_id": 2,
- "stp_act": "cn",
- "amend_text": "-"
- }
- }
-}
-Copy
-#批量下单
-futures.order_batch_place
-
-您可以通过该频道批量下单
-
-本频道和以下的 APIV4 功能相同:
-
-POST /futures/{
- settle
-}/batch_orders
-#批量下单请求
-payload 参数:
-
-名称 类型 必选 描述
-req_id string 是 请求 id,服务器会发回,帮助你识别服务器响应的是哪个请求,
-它与外部的id不同
-req_param object 是 参考 api 批量下单的请求数组; api 批量下单详情api(opens new window)
-req_header object 否 Apiv4 自定义 header
-req_param API 订单模型的 JSON 字节数据可以参考单个下单,是多个单个下单数组,详情参考下单
-
-req_header 自定义 header 数据:
-
-字段 类型 必选 描述
-x-gate-exptime string 否 指定过期的时间戳(毫秒)。如果 ws 收到请求的时间大于过期时间,请求将被拒绝
-代码示例:请求前要先登录
-
-import time
-import json
-
-# pip install websocket_client
-from websocket import create_connection
-
-api_order=[
- {
- "contract": "BTC_USDT",
- "size": 10,
- "price": "31403.180000",
- "tif": "gtc",
- "text": "t-my-custom-id"
- }
- ]
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-ws.send(json.dumps({
- "time": int(time.time()),
- "channel": "futures.order_batch_place",
- "event": "api",
- "payload": {
- "header":{
- "x-gate-channel-id":"xxxx",
- },
- "req_id": "1ewq-3123w-5",
- "req_param": api_order
- }
-}))
-
-print(ws.recv())
-Copy
-请求示例
-
-{
- "time": 1681196536,
- "channel": "futures.order_batch_place",
- "event": "api",
- "payload": {
- "req_id": "request-id-6",
- "req_param": [
- {
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31403.180000",
- "tif": "gtc",
- "text": "t-my-custom-id"
- }
- ]
- }
-}
-Copy
-#批量下单确认通知
-确认通知示例
-
-{
- "request_id": "request-id-6",
- "ack": true,
- "header": {
- "response_time": "1681196536283",
- "status": "200",
- "channel": "futures.order_batch_place",
- "event": "api",
- "client_id": "::1-0x14002cfa0c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gat_ratelimit_reset_timestamp": 1736408263764
- },
- "data": {
- "result": {
- "req_id": "request-id-6",
- "req_header": null,
- "req_param": [
- {
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31403.180000",
- "tif": "gtc",
- "text": "t-my-custom-id"
- }
- ]
- }
- }
-}
-Copy
-#批量下单结果通知
-批量下单订单信息返回
-
-响应参数:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-ack Bool "ack"消息的返回表示 WebSocket 的确认消息(目前在下单接口中存在)。
-如果ack为 false(false 该字段不会出现在响应中),则说明该消息是响应消息,可以判断请求是否成功<br / >通过检查data.errs。
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-»x_gate_ratelimit_requests_remain Integer 当前时间窗口剩余可用请求数(为0不展示)
-»x_gate_ratelimit_limit Integer 当前频率限制上限(为0不展示)
-»x_gat_ratelimit_reset_timestamp Integer 已超过当前窗口频率限制,表示下个可用时间窗口的时间戳(毫秒),即什么时候可以恢复访问;未超过当前窗口频率限制,表示返回的是当前服务器时间(毫秒)
-data Object 请求响应的数据
-»result Object 如果这是 ack 响应,则结果是请求的payload,否则结果是 api 的响应
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-响应返回示例
-
-{
- "request_id": "request-id-6",
- "ack": false,
- "header": {
- "response_time": "1681196536532",
- "status": "200",
- "channel": "futures.order_batch_place",
- "event": "api",
- "client_id": "::1-0x14002cfa0c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gat_ratelimit_reset_timestamp": 1736408263764
- },
- "data": {
- "result": [
- {
- "succeeded": true,
- "id": 74046545,
- "user": 6790020,
- "create_time": 1681196536.592,
- "status": "open",
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31403.2",
- "tif": "gtc",
- "left": "10",
- "fill_price": "0",
- "text": "t-my-custom-id",
- "tkfr": "0.0003",
- "mkfr": "0"
- }
- ]
- }
-}
-Copy
-#订单取消
-futures.order_cancel
-
-您可以通过此频道取消订单
-
-本频道和以下的 APIV4 功能相同:
-
-DELETE /futures/{
- settle
-}/orders/{
- order_id
-}
-#订单取消请求
-payload 参数:
-
-名称 类型 必选 描述
-req_id string 是 请求 id,服务器会发回,帮助你识别服务器响应的是哪个请求,
-它与外部的id不同
-req_param object 是 API 取消订单,详情至api(opens new window)
-req_header object 否 Apiv4 自定义请求头
-req_param API 订单模型的 JSON 字节数据:
-
-字段 类型 必选 描述
-order_id string 是 成功创建订单时返回的订单 ID 或者用户创建时指定的自定义 ID(即text 字段)。
-req_header 自定义 header 数据:
-
-字段 类型 必选 描述
-x-gate-exptime string 否 指定过期的时间戳(毫秒)。如果 ws 收到请求的时间大于过期时间,请求将被拒绝
-代码示例:请求前要先登录
-
-import time
-import json
-
-# pip install websocket_client
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-api_cancel_order = {
- "order_id": "74046514"
- }
-ws.send(json.dumps({
- "time": int(time.time()),
- "channel": "futures.order_cancel",
- "event": "api",
- "payload": {
- "req_id": "1ewq-3123w-5",
- "req_param": api_cancel_order
- }
-}))
-
-print(ws.recv())
-Copy
-订单取消请求示例
-
-{
- "time": 1681195485,
- "channel": "futures.order_cancel",
- "event": "api",
- "payload": {
- "req_id": "request-id-5",
- "req_param": {
- "order_id": "74046514"
- }
- }
-}
-Copy
-#订单取消通知
-响应参数:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-»x_gate_ratelimit_requests_remain Integer 当前时间窗口剩余可用请求数(为0不展示)
-»x_gate_ratelimit_limit Integer 当前频率限制上限(为0不展示)
-»x_gat_ratelimit_reset_timestamp Integer 已超过当前窗口频率限制,表示下个可用时间窗口的时间戳(毫秒),即什么时候可以恢复访问;未超过当前窗口频率限制,表示返回的是当前服务器时间(毫秒)
-data Object 请求响应的数据
-»result Object 订单取消参数,详情至api(opens new window)
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-订单取消返回示例
-
-{
- "request_id": "request-id-5",
- "header": {
- "response_time": "1681196536282",
- "status": "200",
- "channel": "futures.order_cancel",
- "event": "api",
- "client_id": "::1-0x14002cfa0c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gat_ratelimit_reset_timestamp": 1736408263764
- },
- "data": {
- "result": {
- "id": 74046543,
- "user": 6790020,
- "create_time": 1681196535.01,
- "finish_time": 1681196536.343,
- "finish_as": "cancelled",
- "status": "finished",
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31303.2",
- "tif": "gtc",
- "left": "10",
- "fill_price": "0",
- "text": "t-my-custom-id",
- "tkfr": "0.0003",
- "mkfr": "0",
- "stp_id": 2,
- "stp_act": "cn",
- "amend_text": "-"
- }
- }
-}
-Copy
-#取消所有 ID 列表内的订单
-您可以使用此频道futures.order_cancel_ids取消所有 ID 列表内的订单。
-
-可以指定多个不同的订单id。一次请求最多只能撤销 20 条记录
-
-以下是 API 的功能:
-
-POST /futures/{settle}/batch_cancel_orders
-#取消所有 ID 列表内的订单请求
-Payload 格式:
-
-字段 类型 必选 描述
-req_id string 是 服务器将发送回的请求 ID,用于帮助您识别服务器响应的是哪个请求,它与外部的 id 不同。
-req_param array 是 订单 ID 列表
-req_header object 否 apiv4 自定义 header
-req_header 自定义 header 数据:
-
-字段 类型 必选 描述
-x-gate-exptime string 否 指定过期的时间戳(毫秒)。如果 ws 收到请求的时间大于过期时间,请求将被拒绝
-代码示例:请求前要先登录
-
-#!/usr/bin/python
-
-import time
-import json
-# pip install websocket_client
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-cancelWithIdsParam = ["1694883366","123"]
-ws.send(json.dumps({
- "time":int(time.time()),
- "channel":"futures.order_cancel_ids",
- "event":"api",
- "payload":{
- "req_id":"test_1",
- "req_param": cancelWithIdsParam
- }
-}))
-
-print(ws.recv())
-Copy
-客户端请求示例
-
-{
- "time": 1681986208,
- "channel": "futures.order_cancel_ids",
- "event": "api",
- "payload": {
- "req_id": "request-9",
- "req_param": [
- "1700664343",
- "123"
- ]
- }
-}
-Copy
-#取消所有 ID 列表内的订单推送
-推送格式:
-
-字段 类型 描述
-request_id String 消息的唯一标识符
-header Map 响应的元信息
-»response_time String 响应发送时间(以毫秒为单位)
-»channel String 请求的频道
-»event String 请求事件
-»client_id String 唯一客户端标识 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-data Object 请求响应的数据
-»result Object 响应详见api(opens new window)
-»errs Object 只有在请求失败时才可用
-»»label String 以字符串格式表示错误类型
-»»message String 错误信息详情
-取消订单推送示例
-
-{
- "request_id": "request-9",
- "header": {
- "response_time": "1681986208564",
- "status": "200",
- "channel": "futures.order_cancel_ids",
- "event": "api",
- "client_id": "::1-0x140001623c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d"
- },
- "data": {
- "result": [
- {
- "id": "1694883366",
- "user_id": 111,
- "succeeded": true
- },
- {
- "id": "123",
- "user_id": 111,
- "message": "ORDER_NOT_FOUND"
- }
- ]
- }
-}
-Copy
-#取消匹配的未结束订单
-futures.order_cancel_cp
-
-您可以通过此渠道取消所有匹配的未结束的订单
-
-本频道和以下的 APIV4 功能相同:
-
-DELETE /futures/{
- settle
-}/orders
-#请求
-payload 参数:
-
-名称 类型 必选 描述
-req_id string 是 请求 id,服务器会发回,帮助你识别服务器响应的是哪个请求,
-它与外部的id不同
-req_param object 是 详情至api(opens new window)
-req_header object 否 Apiv4 自定义请求头
-req_param API 订单模型的 JSON 字节数据:
-
-字段 类型 必选 描述
-contract string 是 合约
-side string 否 所有出价或要价。如果没有特别说明,两者都包括在内。
-req_header 自定义 header 数据:
-
-字段 类型 必选 描述
-x-gate-exptime string 否 指定过期的时间戳(毫秒)。如果 ws 收到请求的时间大于过期时间,请求将被拒绝
-代码示例:请求前要先登录
-
-import time
-import json
-
-# pip install websocket_client
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-api_cancel_all_order = {
- "contract": "BTC_USDT",
- "side": "bid"
- }
-ws.send(json.dumps({
- "time": int(time.time()),
- "channel": "futures.order_cancel_cp",
- "event": "api",
- "payload": {
- "req_id": "1ewq-3123w-5",
- "req_param": api_cancel_all_order
- }
-}))
-
-print(ws.recv())
-Copy
-客户请求示例
-
-{
- "time": 1681196537,
- "channel": "futures.order_cancel_cp",
- "event": "api",
- "payload": {
- "req_id": "request-id-7",
- "req_param": {
- "contract": "BTC_USDT",
- "side": "bid"
- }
- }
-}
-Copy
-#响应结果
-响应参数:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-»x_gate_ratelimit_requests_remain Integer 当前时间窗口剩余可用请求数(为0不展示)
-»x_gate_ratelimit_limit Integer 当前频率限制上限(为0不展示)
-»x_gat_ratelimit_reset_timestamp Integer 已超过当前窗口频率限制,表示下个可用时间窗口的时间戳(毫秒),即什么时候可以恢复访问;未超过当前窗口频率限制,表示返回的是当前服务器时间(毫秒)
-data Object 请求响应的数据
-»result Object 详情至api(opens new window)
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-订单取消返回示例
-
-{
- "request_id": "request-id-7",
- "header": {
- "response_time": "1681196537567",
- "status": "200",
- "channel": "futures.order_cancel_cp",
- "event": "api",
- "client_id": "::1-0x14002cfa0c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gat_ratelimit_reset_timestamp": 1736408263764
- },
- "data": {
- "result": [
- {
- "id": 74046545,
- "user": 6790020,
- "create_time": 1681196536.592,
- "finish_time": 1681196537.626,
- "finish_as": "cancelled",
- "status": "finished",
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31403.2",
- "tif": "gtc",
- "left": "10",
- "fill_price": "0",
- "text": "t-my-custom-id",
- "tkfr": "0.0003",
- "mkfr": "0",
- "stp_id": 2,
- "stp_act": "cn",
- "amend_text": "-"
- }
- ]
- }
-}
-Copy
-#修改订单
-futures.order_amend
-
-您可以通过此频道修改未结束的订单
-
-本频道和以下的 APIV4 功能相同:
-
-PUT /futures/{settle}/orders/{order_id}
-#请求
-payload 参数:
-
-名称 类型 必选 描述
-req_id string 是 请求 id,服务器会发回,帮助你识别服务器响应的是哪个请求,
-它与外部的id不同
-req_param object 是 API 修改订单参数,详情至api(opens new window)
-req_header object 否 Apiv4 自定义请求头
-req_param API 订单模型的 JSON 字节数据:
-
-字段 类型 必选 描述
-order_id string 是 成功创建订单时返回的订单 ID 或者用户创建时指定的自定义 ID(即text 字段)。
-size int64 否 必选。交易数量,正数为买入,负数为卖出。平仓委托则设置为 0。
-price string 否 价格
-amend_text int64 否 修改订单时的自定义信息
-req_header 自定义 header 数据:
-
-字段 类型 必选 描述
-x-gate-exptime string 否 指定过期的时间戳(毫秒)。如果 ws 收到请求的时间大于过期时间,请求将被拒绝
-#详细描述
-=> size: 新订单尺寸,包括已填充部分。
-
-如果新尺寸小于或等于已填充尺寸,订单将被取消。
-订单面必须与原始面相同。
-平仓订单大小无法更改。
-对于仅减少订单,增加尺寸可能会导致其他仅减少订单被取消。
-如果价格不变,减少数量不会改变其在订单簿中的优先级,而增加数量则会以当前价格将其移至最后。
-代码示例:请求前要先登录
-
-import time
-import json
-
-# pip install websocket_client
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-api_amend_order = {
- "order_id": "74046543",
- "price": "31303.180000"
- }
-ws.send(json.dumps({
- "time": int(time.time()),
- "channel": "futures.order_amend",
- "event": "api",
- "payload": {
- "req_id": "1ewq-3123w-5",
- "req_param": api_amend_order
- }
-}))
-
-print(ws.recv())
-Copy
-客户请求示例
-
-{
- "time": 1681196536,
- "channel": "futures.order_amend",
- "event": "api",
- "payload": {
- "req_id": "request-id-4",
- "req_param": {
- "order_id": "74046543",
- "price": "31303.180000"
- }
- }
-}
-Copy
-#响应结果
-响应参数:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-»x_gate_ratelimit_requests_remain Integer 当前时间窗口剩余可用请求数(为0不展示)
-»x_gate_ratelimit_limit Integer 当前频率限制上限(为0不展示)
-»x_gat_ratelimit_reset_timestamp Integer 已超过当前窗口频率限制,表示下个可用时间窗口的时间戳(毫秒),即什么时候可以恢复访问;未超过当前窗口频率限制,表示返回的是当前服务器时间(毫秒)
-data Object 请求响应的数据
-»result Object 详情至api(opens new window)
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-订单修改返回示例
-
-{
- "request_id": "request-id-4",
- "header": {
- "response_time": "1681196536251",
- "status": "200",
- "channel": "futures.order_amend",
- "event": "api",
- "client_id": "::1-0x14002cfa0c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d",
- "x_gate_ratelimit_requests_remain": 99,
- "x_gate_ratelimit_limit": 100,
- "x_gat_ratelimit_reset_timestamp": 1736408263764
- },
- "data": {
- "result": {
- "id": 74046543,
- "user": 6790020,
- "create_time": 1681196535.01,
- "status": "open",
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31303.2",
- "tif": "gtc",
- "left": "10",
- "fill_price": "0",
- "text": "t-my-custom-id",
- "tkfr": "0.0003",
- "mkfr": "0",
- "stp_id": 2,
- "stp_act": "cn",
- "amend_text": "-"
- }
- }
-}
-Copy
-#获取订单列表
-futures.order_list
-
-您可以通过此频道获取订单列表
-
-本频道和以下的 APIV4 功能相同:
-
-GET /futures/{settle}/orders
-#订单列表请求
-payload 参数:
-
-名称 类型 必选 描述
-req_id string 是 请求 id,服务器会发回,帮助你识别服务器响应的是哪个请求,
-它与外部的id不同
-req_param object 是 API 请求订单列表参数,详情至api(opens new window)
-req_param API 订单模型的 JSON 字节数据:
-
-字段 类型 必选 描述
-contract string 否 合约标识,如果指定则只返回该合约相关数据
-status string 是 只列出具有此状态的订单
-limit int 否 单个列表中返回的最大记录数
-offset int 否 列表偏移量,从 0 开始
-last_id string 否 使用先前列表查询结果中最后一条记录的id 指定列表起始点
-代码示例:请求前要先登录
-
-import time
-import json
-
-# pip install websocket_client
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-api_list_order = {
- "contract": "BTC_USDT",
- "status": "open"
- }
-ws.send(json.dumps({
- "time": int(time.time()),
- "channel": "futures.order_list",
- "event": "api",
- "payload": {
- "req_id": "1ewq-3123w-5",
- "req_param": api_list_order
- }
-}
-))
-
-print(ws.recv())
-Copy
-客户请求示例
-
-{
- "time": 1681196535,
- "channel": "futures.order_list",
- "event": "api",
- "payload": {
- "req_id": "request-id-3",
- "req_param": {
- "contract": "BTC_USDT",
- "status": "open"
- }
- }
-}
-Copy
-#订单列表响应
-响应参数:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-data Object 请求响应的数据
-»result Object 详情至api(opens new window)
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-订单列表返回示例
-
-{
- "request_id": "request-id-3",
- "header": {
- "response_time": "1681196536017",
- "status": "200",
- "channel": "futures.order_list",
- "event": "api",
- "client_id": "::1-0x14002cfa0c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d"
- },
- "data": {
- "result": [
- {
- "id": 74046543,
- "user": 6790020,
- "create_time": 1681196535.01,
- "finish_time": 1681196535.01,
- "update_time": 1681196535.01,
- "finish_as": "filled",
- "status": "open",
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31403.2",
- "tif": "gtc",
- "left": "10",
- "fill_price": "0",
- "text": "t-my-custom-id",
- "tkfr": "0.0003",
- "mkfr": "0",
- "stp_id": 2,
- "stp_act": "cn",
- "amend_text": "-"
- }
- ]
- }
-}
-Copy
-#查询订单详情
-futures.order_status
-
-您可以通过该频道查询订单详情
-
-本频道和以下的 APIV4 功能相同:
-
-GET /futures/{settle}/orders/{order_id}
-#订单详情请求
-payload 参数:
-
-名称 类型 必选 描述
-req_id string 是 请求 id,服务器会发回,帮助你识别服务器响应的是哪个请求,
-它与外部的id不同
-req_param object 是 详情至api(opens new window)
-req_param` API 订单模型的 JSON 字节数据:
-
-字段 类型 必选 描述
-order_id string 是 成功创建订单时返回的订单 ID 或者用户创建时指定的自定义 ID(即text 字段)。
-代码示例:请求前要先登录
-
-import time
-import json
-
-# pip install websocket_client
-from websocket import create_connection
-
-ws = create_connection("wss://fx-ws.gateio.ws/v4/ws/usdt")
-
-api_status_order = {
- "order_id": "74046543"
- }
-
-ws.send(json.dumps({
- "time": int(time.time()),
- "channel": "futures.order_status",
- "event": "api",
- "payload": {
- "req_id": "1ewq-3123w-5",
- "req_param": api_status_order
- }
-}))
-
-print(ws.recv())
-Copy
-客户请求示例
-
-{
- "time": 1681196535,
- "channel": "futures.order_status",
- "event": "api",
- "payload": {
- "req_id": "request-id-2",
- "req_param": {
- "order_id": "74046543"
- }
- }
-}
-Copy
-#订单详情响应
-响应参数:
-
-名称 类型 描述
-request_id String 对应的请求 ID
-header Map 响应元信息
-»response_time String 响应发送时间(毫秒)
-»channel String 请求频道
-»event String 请求event
-»client_id String 唯一的客户端 ID
-»x_in_time Integer ws 接收请求的时间(以微秒为单位)
-»x_out_time Integer ws 返回响应的时间(以微秒为单位)
-»conn_id String 与客户端建立连接的链接Id(同一个连接的链接Id保持一致)
-»conn_trace_id String 与客户端建立连接的TraceId
-»trace_id String 执行下单操作的TraceId
-data Object 请求响应的数据
-»result Object 详情至api(opens new window)
-»errs Object 仅当请求失败时可用
-»»label String 错误类型
-»»message String 详细错误信息
-订单详情返回示例
-
-{
- "request_id": "request-id-2",
- "header": {
- "response_time": "1681196535985",
- "status": "200",
- "channel": "futures.order_status",
- "event": "api",
- "client_id": "::1-0x14002cfa0c0",
- "x_in_time": 1681985856667508,
- "x_out_time": 1681985856667598,
- "conn_id": "5e74253e9c793974",
- "conn_trace_id": "1bde5aaa0acf2f5f48edfd4392e1fa68",
- "trace_id": "e410abb5f74b4afc519e67920548838d"
- },
- "data": {
- "result": {
- "id": 74046543,
- "user": 6790020,
- "create_time": 1681196535.01,
- "status": "open",
- "contract": "BTC_USDT",
- "size": "10",
- "price": "31403.2",
- "tif": "gtc",
- "left": "10",
- "fill_price": "0",
- "text": "t-my-custom-id",
- "tkfr": "0.0003",
- "mkfr": "0",
- "stp_id": 2,
- "stp_act": "cn",
- "amend_text": "-"
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md b/src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md
deleted file mode 100644
index 42a2dc6..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/gateApi-logic.md
+++ /dev/null
@@ -1,583 +0,0 @@
-# Gate Api 模块 — 网格交易系统
-
-## 文件列表
-
-| 文件 | 类型 | 说明 |
-|------|------|------|
-| [GateWebSocketClientManager](#gatewebsocketclientmanager) | `@Component` | Spring 启动入口,组装组件 + 生命周期 |
-| [GateConfig](#gateconfig) | 配置 | Builder 模式:API 密钥、合约、策略参数、环境切换 |
-| [GateKlineWebSocketClient](#gateklinewebsocketclient) | WS 连接管理 | 连接/心跳/重连/消息路由 |
-| [GateGridTradeService](#gategridtradeservice) | 策略核心 | 网格队列 + 条件开仓单 + 订单订阅止盈 + 反向条件单 |
-| [GateTradeExecutor](#gatetradeexecutor) | 异步执行器 | 独立线程池执行 REST 下单,成功/失败双回调 |
-
-### wsHandler 子包
-
-| 文件 | 类型 | 说明 |
-|------|------|------|
-| `wsHandler/GateChannelHandler.java` | **接口** | subscribe / unsubscribe / handleMessage / getChannelName |
-| `wsHandler/AbstractPrivateChannelHandler.java` | **抽象类** | 私有频道基类:HMAC-SHA512 签名 + 认证请求 |
-| `wsHandler/handler/CandlestickChannelHandler.java` | 公开频道 | K 线解析 → `onKline()` |
-| `wsHandler/handler/PositionsChannelHandler.java` | 私有频道 | 仓位推送 → `onPositionUpdate()`,日志输出全部 20 个推送字段 |
-| `wsHandler/handler/PositionClosesChannelHandler.java` | 私有频道 | 平仓推送 → `onPositionClose()` |
-| `wsHandler/handler/OrdersChannelHandler.java` | 私有频道 | 订单推送 → `onOrderUpdate()`(订单订阅止盈匹配) |
-
----
-
-## 架构总览
-
-```
-┌──────────────────────────────────────────────────────────────────┐
-│ GateWebSocketClientManager │
-│ (Spring @Component) │
-│ │
-│ GateConfig.builder() → GateGridTradeService + WS Client │
-│ │ │ │ │
-│ │ REST BasePath │ GateTradeExecutor │ WS URL │
-│ ▼ ▼ ▼ │
-│ Gate API (REST) 独立线程池 (async) Gate WebSocket │
-│ onSuccess/onFailure双回调 ┌──────────────┐ │
-│ │Candlestick H │ │
-│ │Positions H │ │
-│ │PosCloses H │ │
-│ │Orders H │ │
-│ └──────────────┘ │
-└──────────────────────────────────────────────────────────────────┘
-```
-
----
-
-## WebSocket 频道
-
-| 频道 | 类型 | 作用 |
-|------|------|------|
-| `futures.candlesticks` | 公开 | K 线推送 → `onKline()` 驱动网格触发 |
-| `futures.positions` | 私有 | 仓位推送 → `onPositionUpdate()` 记录入场价/持仓量、处理反向单 |
-| `futures.position_closes` | 私有 | 平仓推送 → `onPositionClose()` 累加已实现盈亏 |
-| `futures.orders` | 私有 | 订单推送 → `onOrderUpdate()` 订阅条件单成交,匹配止盈价 |
-
----
-
-## 数据流
-
-```
-WebSocket → GateKlineWebSocketClient.handleMessage → 路由 dispatch
-│
-├─ futures.pong → cancelPongTimeout
-├─ subscribe / unsubscribe / error → log
-│
-├─ futures.candlesticks (公开)
-│ └─ CandlestickChannelHandler
-│ └─ gridTradeService.onKline(closePrice, timestamp)
-│ ├─ 更新 unrealizedPnl(浮动盈亏)
-│ ├─ state=WAITING_KLINE → 异步双开基底仓位
-│ └─ state=ACTIVE → 方向区分: closePrice>longPriceQueue[0]→processLongGrid,
-│ closePrice<shortPriceQueue[0]→processShortGrid, 其余跳过(一个K线只触发一个方向)
-│
-├─ futures.positions (私有, HMAC-SHA512)
-│ └─ PositionsChannelHandler
-│ ├─ 解析 mode → Position.ModeEnum(DUAL_LONG / DUAL_SHORT)
-│ ├─ 日志输出全部 20 个推送字段
-│ └─ gridTradeService.onPositionUpdate(contract, mode, size, entryPrice)
-│ ├─ 有仓位: 基底首次成交记录入场价 → tryGenerateQueues
-│ ├─ 仓位净减少(size < positionSize)→ 检查反向条件单条件 →
-│ │ 满足则开反向市价单(止盈价 = entryPrice ± step),orderId+止盈价存入 Map
-│ ├─ 仓位不变或增加 → 仅更新 positionSize
-│ └─ 无仓位(size=0): 清空活跃标记和持仓量
-│
-├─ futures.position_closes (私有, HMAC-SHA512)
-│ └─ PositionClosesChannelHandler
-│ └─ gridTradeService.onPositionClose(contract, side, pnl)
-│ └─ cumulativePnl += pnl → checkStopConditions()
-│
-├─ futures.orders (私有, HMAC-SHA512) ← 订单订阅止盈核心
-│ └─ OrdersChannelHandler
-│ └─ gridTradeService.onOrderUpdate(orderId, status, finishAs)
-│ └─ 条件单成交(status=finished, finish_as=filled)→
-│ 从 Map 中 remove(orderId) 取出止盈价 →
-│ executor.placeTakeProfit(止盈价, ...)
-│
-└─ 所有下单操作
- └─ GateTradeExecutor (单线程 + 64队列 + CallerRunsPolicy)
- ├─ openLong/openShort → 市价 IOC 开仓(基底双开 + 反向开仓)
- ├─ placeConditionalEntryOrder → 条件开仓单(网格触发,服务端监控价格触发后市价 IOC)
- ├─ cancelConditionalOrder → 取消指定条件单
- ├─ placeTakeProfit → 止盈条件单(plan-close-*-position)
- ├─ cancelAllPriceTriggeredOrders → 清除所有条件单(停止时)
- ├─ placeGridLimitOrder → 【遗留】限价 GTC 单(当前策略未使用)
- └─ cancelOrder → 【遗留】取消限价单(当前策略未使用)
-```
-
----
-
-## 策略状态机
-
-```
-WAITING_KLINE ──onKline──→ OPENING ──双基底都成交──→ ACTIVE
- │ │ │
- │ 下单异常 ├─ 每根K线: 更新 unrealizedPnl
- │ │ ├─ cumPnl ≥ overallTp → STOPPED
- │ │ ├─ cumPnl ≤ -maxLoss → STOPPED
- │ │ ├─ 保证金超限 → 跳过挂单,队列照常更新
- │ │ ├─ 方向区分 → processShortGrid 或 processLongGrid(二者选一)
- │ │ ├─ 网格触发 → 匹配队列→本队补充→挂新条件单
- │ │ │ (止盈价随 orderId 存入 Map)
- │ │ ├─ 订单推送(futures.orders) → 订阅匹配止盈价 →
- │ │ │ 挂止盈条件单(plan-close-*-position)
- │ │ └─ 仓位推送(净减少) → 反向市价单
- ▼ ▼
- STOPPED ←──────────────────┘
-```
-
-| 状态 | 含义 |
-|------|------|
-| `WAITING_KLINE` | 等待首次K线价格 |
-| `OPENING` | 正在异步开基底多空仓位(已提交到GateTradeExecutor) |
-| `ACTIVE` | 网格队列激活,K线触发网格 → 挂条件单 → 订单订阅匹配止盈 |
-| `STOPPED` | 停止(盈利达标 / 亏损超限) |
-
----
-
-## 策略核心:网格队列 + 条件单 + 订单订阅止盈
-
-### 概述
-
-策略采用"基底 + 价格网格队列"模式:先开一对基底多空仓位,然后以基底入场价为基准生成价格队列。每当 K 线穿破队列元素,匹配后更新队列并挂新条件单。条件单成交后通过 `futures.orders` 推送匹配止盈价并挂止盈条件单。
-
-### 核心数据结构
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| shortPriceQueue | `List<BigDecimal>` 同步列表 | 空仓价格队列,降序(大→小),容量 gridQueueSize |
-| longPriceQueue | `List<BigDecimal>` 同步列表 | 多仓价格队列,升序(小→大),容量 gridQueueSize |
-| currentLongOrderIds | `Map<String, BigDecimal>` 同步 LinkedHashMap | **多仓条件单映射**:orderId → 止盈价,超 5 个时截断保留最新 |
-| currentShortOrderIds | `Map<String, BigDecimal>` 同步 LinkedHashMap | **空仓条件单映射**:orderId → 止盈价,超 5 个时截断保留最新 |
-| shortBaseEntryPrice | `BigDecimal` | 基底空头入场价(仅记录,用于队列生成基准) |
-| longBaseEntryPrice | `BigDecimal` | 基底多头入场价(仅记录,当前未被业务逻辑消费) |
-| shortEntryPrice | `BigDecimal` | 当前空仓加权均价(推送实时更新) |
-| longEntryPrice | `BigDecimal` | 当前多仓加权均价(推送实时更新) |
-| shortPositionSize | `BigDecimal` | 当前空仓持仓量(绝对值) |
-| longPositionSize | `BigDecimal` | 当前多仓持仓量 |
-| cumulativePnl | `BigDecimal` | 累计已实现盈亏(平仓推送驱动) |
-| unrealizedPnl | `BigDecimal` | 未实现盈亏(每根K线更新) |
-| initialPrincipal | `BigDecimal` | 初始本金(启动时账户总资产) |
-
-### 条件单设计
-
-**为什么用条件单而不是限价单?**
-
-限价单 `price=X, tif=GTC` 在价格位于挂单价有利侧时**会立即成交**。改用 Gate API `FuturesPriceTriggeredOrder`(价格触发条件单),由服务端监控价格,**只有当前价格抵达触发价时才执行**,避免提前成交。
-
-**条件单结构**:
-```
-FuturesPriceTriggeredOrder
- ├─ trigger: { price=触发价, rule=NUMBER_1(≥) 或 NUMBER_2(≤), expiration=0(永久有效) }
- ├─ initial: { contract, size(正=开多/负=开空), price="0", tif=IOC, reduce_only=false }
- └─ order_type: strategy_type=NUMBER_0(价格触发), price_type=NUMBER_0(最新价)
-```
-
-### 订单订阅止盈机制(核心)
-
-**这是当前版本的止盈流程,替代了旧版的止盈队列消费模式。**
-
-```
-挂条件开仓单时 → onSuccess 回调 → currentXxxOrderIds.put(orderId, 止盈价)
- ↓
-条件单成交 → futures.orders 推送 → onOrderUpdate(orderId, "finished", "filled")
- ↓
- currentXxxOrderIds.remove(orderId) 取出止盈价
- ↓
- executor.placeTakeProfit(止盈价, 方向参数)
-```
-
-止盈价计算:
-- **网格触发**(`processShortGrid`/`processLongGrid`):`newFirst ± step`
-- **初始条件单**(`tryGenerateQueues`):`queue[0] ± step`
-- **反向市价单**(`onPositionUpdate`):`entryPrice ± step`
-
-### Map 截断机制
-
-`currentLongOrderIds` / `currentShortOrderIds` 为 `LinkedHashMap`(保持插入顺序)。在 `onPositionUpdate` 中,当 Map size > 5 时,从头部(最旧条目)开始删除,只保留最新 5 条。防止条件单挂单失败导致 Map 无限膨胀。
-
-### 基底开仓
-
-```
-K线到达 → 双开基底(市价开多 + 市价开空,IOC)
- → 成交回调: baseLongOpened=true, longActive=true
- → 成交回调: baseShortOpened=true, shortActive=true
- → 两者都成交 → generateShortQueue() + generateLongQueue()
- + 挂初始条件开仓单 → state=ACTIVE
-```
-
-### 初始条件开仓单
-
-队列生成后立即用队列首元素挂两个价格触发条件单:
-
-| 方向 | 触发价 | rule | size | 止盈价(存入Map) |
-|------|--------|------|------|-------------------|
-| 多仓条件单 | longPriceQueue[0] | NUMBER_1 (≥触发价) | +quantity | 触发价 + step |
-| 空仓条件单 | shortPriceQueue[0] | NUMBER_2 (≤触发价) | -quantity | 触发价 − step |
-
-### 网格队列生成
-
-以空头基底入场价 `shortBaseEntryPrice` 为唯一基准,计算绝对步长 `step = shortBaseEntryPrice × gridRate`(保留1位小数)。
-
-两个队列均从 `shortBaseEntryPrice` 出发,按 `step` 绝对偏移生成 N 个价格(N = gridQueueSize,默认 50):
-
-| 队列 | 计算方式 | 排序 |
-|------|---------|------|
-| 空仓队列 shortPriceQueue | 首元素 = shortBaseEntryPrice − step,后续依次 −step | 降序(大→小) |
-| 多仓队列 longPriceQueue | 首元素 = shortBaseEntryPrice + step,后续依次 +step | 升序(小→大) |
-
----
-
-## K线触发网格
-
-```
-K线到达(ACTIVE状态):
-│
-├─ closePrice > longPriceQueue[0] → processLongGrid(价格涨超队列首)
-│ ├─ 匹配: 收集所有 < closePrice 的多仓队列元素(升序,一旦遇≥即停止)
-│ ├─ 多仓队列: 移除 matched,尾部补充(尾价 + step 循环递增)
-│ ├─ 空仓队列: 不再更新(队列转移逻辑已移除)
-│ ├─ 保证金检查:
-│ │ ├─ 安全 → 挂新多仓条件单(触发价=新 long[0], 止盈=新 long[0]+step, orderId→Map)
-│ │ │ → 空仓守卫: newShortFirst = newLongFirst − step×2,
-│ │ │ 若 > shortEntryPrice → 挂新空仓条件单(止盈=新short[0]−step, orderId→Map)
-│ │ └─ 超限 → 跳过挂单(队列照常更新)
-│ └─ 条件单成交后由 futures.orders 推送 → onOrderUpdate 匹配止盈价 → 挂止盈条件单
-│
-└─ closePrice < shortPriceQueue[0] → processShortGrid(价格跌穿队列首)
- ├─ 匹配: 收集所有 > closePrice 的空仓队列元素(降序,一旦遇≤即停止)
- ├─ 空仓队列: 移除 matched,尾部补充(尾价 − step 循环递减)
- ├─ 多仓队列: 不再更新(队列转移逻辑已移除)
- ├─ 保证金检查:
- │ ├─ 安全 → 挂新空仓条件单(触发价=新 short[0], 止盈=新short[0]−step, orderId→Map)
- │ │ → 多仓守卫: newLongFirst = newShortFirst + step×2,
- │ │ 若 < longEntryPrice → 挂新多仓条件单(止盈=新long[0]+step, orderId→Map)
- │ └─ 超限 → 跳过挂单(队列照常更新)
- └─ 条件单成交后由 futures.orders 推送 → onOrderUpdate 匹配止盈价 → 挂止盈条件单
-
-closePrice 既不 > longPriceQueue[0] 也不 < shortPriceQueue[0] → 跳过本次K线
-```
-
-> **关键变更**:不再有"队列转移"(对方队列不再更新)、不再有"取消旧条件单"(旧单由 Map 自动覆盖或服务端自动取消)、不再有"止盈队列"(改为订单订阅匹配)。
-> 反向条件单不再在 process*Grid 中处理,改为在 onPositionUpdate 仓位净减少时触发。
-
-### 队列更新示意
-
-```
-ETH_USDT, gridRate=0.0035, shortBaseEntryPrice=2270, step=2270×0.0035≈7.9, gridQueueSize=4:
-
-初始状态:
- 空仓队列: [2262.1, 2254.2, 2246.3, 2238.4] (降序)
- 多仓队列: [2277.9, 2285.8, 2293.7, 2301.6] (升序)
- 初始条件单: 多仓触发价=2277.9(止盈=2285.8→currentLongOrderIds),
- 空仓触发价=2262.1(止盈=2254.2→currentShortOrderIds)
-
-价格涨到 2290 → processLongGrid 触发:
- 匹配: [2277.9, 2285.8](都 < 2290)
-
- 多仓队列: 移除[2277.9,2285.8] → [2293.7,2301.6]
- 补充: 2301.6+7.9=2309.5 → 2309.5+7.9=2317.4
- → [2293.7, 2301.6, 2309.5, 2317.4]
-
- 空仓队列: 不变(队列转移已移除)→ [2262.1, 2254.2, 2246.3, 2238.4]
-
- 挂新多仓条件单(触发价=2293.7, 止盈=2301.6→currentLongOrderIds)
- 空仓守卫: newShortFirst=2293.7−15.8=2277.9 > 2262.1? → 满足,
- 挂新空仓条件单(触发价=2277.9, 止盈=2270.0→currentShortOrderIds)
-
-条件单 2293.7 成交 → futures.orders 推送:
- → onOrderUpdate(orderId, "finished", "filled")
- → currentLongOrderIds.remove(orderId) = 2301.6
- → placeTakeProfit(2301.6, NUMBER_1, plan-close-long-position, -1)
-```
-
----
-
-## 仓位更新逻辑(onPositionUpdate)
-
-```
-仓位推送:
-├─ size ≠ 0:
-│ ├─ 基底首次成交 → 记录 baseEntryPrice → tryGenerateQueues
-│ ├─ 仓位净减少(size.abs() < positionSize):
-│ │ ├─ DUAL_LONG 仓位减少且有反向条件单(newShortFirst > shortEntryPrice
-│ │ │ 且 < longEntryPrice 且 shortPositionSize < 3):
-│ │ │ → 市价开空(止盈=longEntryPrice−step→currentShortOrderIds)
-│ │ │ → 累加 positionSize 标记(最多3次)
-│ │ └─ DUAL_SHORT 仓位减少且有反向条件单(newLongFirst > shortEntryPrice
-│ │ 且 < longEntryPrice 且 longPositionSize < 3):
-│ │ → 市价开多(止盈=shortEntryPrice+step→currentLongOrderIds)
-│ │ → 累加 positionSize 标记(最多3次)
-│ └─ 仓位不变或增加 → 仅更新 positionSize
-└─ size = 0:
- └─ 清空活跃标记,重置持仓量为0
-
-每次更新后: 截断 currentLongOrderIds / currentShortOrderIds 到最多 5 个元素
-```
-
----
-
-## 策略时序
-
-### 阶段 1:启动与初始化
-
-```
-Spring @PostConstruct
- → GateConfig.builder()...build()
- → GateGridTradeService(config)
- → init():
- 1. 查用户ID(用于私有频道订阅)
- 2. 查账户 → 记录初始本金 initialPrincipal → 如需要切持仓模式
- 3. 清除旧止盈止损条件单
- 4. 查当前合约所有仓位 → 逐个市价平仓(reduce_only, IOC)
- - 单向持仓: size=相反数平仓
- - 双向持仓: size=0, close=false, autoSize=LONG/SHORT
- 5. 设杠杆
- → GateKlineWebSocketClient(config.getWsUrl())
- → addChannelHandler x4 → init() → connect()
- → onOpen: handlers依次subscribe → sendPing
- → gridTradeService.startGrid() → state=WAITING_KLINE
-```
-
-### 阶段 2:基底开仓 → 生成网格队列 → 挂条件单
-
-```
-K线推送 → onKline(closePrice) → state=OPENING
- → executor.openLong(qty, onSuccess, onFailure)
- → 成交 → 仓位推送: DUAL_LONG, size>0, entryPrice=X
- → baseLongOpened=true, longBaseEntryPrice=X
- → tryGenerateQueues(): 双基底都成交? → 生成队列+挂初始条件单 → state=ACTIVE
- → executor.openShort(-qty, onSuccess, onFailure)
- → 成交 → 仓位推送: DUAL_SHORT, size<0, entryPrice=Y
- → baseShortOpened=true, shortBaseEntryPrice=Y
- → tryGenerateQueues(): 双基底都成交? → 生成队列+挂初始条件单 → state=ACTIVE
-```
-
-### 阶段 3:ACTIVE 状态 — K线驱动网格 + 订单订阅止盈
-
-```
-每根K线 → onKline → updateUnrealizedPnl → 方向区分(一个K线只触发一个方向)
-
-processShortGrid / processLongGrid:
- → 匹配 → 本队补充 → 保证金检查
- → 挂新条件单(止盈价随 orderId 存入 Map)
- → 对方守卫:满足条件时挂对方方向新条件单
-
-条件单被触发成交后 → futures.orders 推送:
- → onOrderUpdate(orderId, "finished", "filled")
- → Map.remove(orderId) 取出止盈价
- → executor.placeTakeProfit(止盈价, 方向参数, plan-close-*-position)
-
-仓位净减少时 → onPositionUpdate:
- → 满足反向条件 → 开反向市价单(止盈价 = entryPrice ± step,orderId+止盈价存入 Map)
-```
-
-### 阶段 4:停止
-
-```
-平仓推送: pnl=+0.6 → cumulativePnl=0.6 ≥ overallTp(0.5) → state=STOPPED
-平仓推送: pnl=-8.0 → cumulativePnl=-8.0 ≤ -maxLoss(7.5) → state=STOPPED
-```
-
----
-
-## GateConfig
-
-**角色**: 统一配置中心。Builder模式管理所有参数,提供 REST/WS URL 环境自动切换。
-
-**核心方法**:
-- `getRestBasePath()`: isProduction ? 生产网 : 测试网
-- `getWsUrl()`: 同上
-
-**配置项**(含默认值):
-
-| 参数 | 默认值 | 说明 |
-|------|--------|------|
-| contract | BTC_USDT | 合约 |
-| leverage | 10 | 倍数 |
-| marginMode | cross | 全仓 |
-| positionMode | dual | 双向持仓 |
-| gridRate | 0.0035 | 网格间距比率 0.35% |
-| step | 运行时计算 | 网格绝对步长 = shortBaseEntryPrice × gridRate(保留1位小数) |
-| overallTp | 0.5 USDT | 整体止盈 |
-| maxLoss | 7.5 USDT | 最大亏损 |
-| quantity | 1 | 下单张数 |
-| gridQueueSize | 50 | 网格价格队列容量 |
-| marginRatioLimit | 0.2 | 保证金占初始本金比例上限(20%),超限跳过开仓 |
-| contractMultiplier | 0.001 | 合约乘数(单张合约代表的基础资产数量) |
-| unrealizedPnlPriceMode | LAST_PRICE | 未实现盈亏计价模式:LAST_PRICE / MARK_PRICE |
-
----
-
-## GateTradeExecutor
-
-**角色**: 独立线程池执行 REST API 下单。采用成功/失败双回调模式。
-
-**线程模型**:
-- `ThreadPoolExecutor(1, 1, 60s, LinkedBlockingQueue(64), CallerRunsPolicy)`
-- 单线程保序 + 有界队列防堆积 + CallerRuns背压
-- allowCoreThreadTimeOut: 60s 空闲后线程回收
-
-**回调设计**:
-- `openLong`/`openShort`/`placeTakeProfit`/`placeConditionalEntryOrder` 接受 `onSuccess` 和 `onFailure` 两个 `Consumer<String>`
-- REST 调用成功 → 执行 `onSuccess`(标记基底已开、记录 orderId+止盈价到 Map 等)
-- REST 调用失败 → 执行 `onFailure`(当前版本多为 null,依赖 position 推送修正)
-
-### 核心方法
-
-| 方法 | 说明 |
-|------|------|
-| `openLong(qty, onSuccess, onFailure)` | 异步 IOC 市价开多,双回调 |
-| `openShort(qty, onSuccess, onFailure)` | 异步 IOC 市价开空,双回调 |
-| `placeConditionalEntryOrder(triggerPrice, rule, size, onSuccess, onFailure)` | 异步**条件开仓单**。triggerPrice=监控价,rule 决定触发方向,size 正=开多/负=开空。触发后 price="0" + IOC 市价成交 |
-| `cancelConditionalOrder(orderId)` | 异步取消指定条件单(orderId 为 null 时跳过) |
-| `placeTakeProfit(trigger, rule, orderType, size)` | 异步**止盈条件单**(plan-close-*-position)。triggerPrice=止盈触发价,触发后 price="0" + IOC 市价平仓,reduce_only=true |
-| `cancelAllPriceTriggeredOrders()` | 清除所有条件单 |
-| `shutdown()` | 等待10秒,超时强制关闭 |
-
-### 遗留方法(当前策略未使用)
-
-| 方法 | 说明 |
-|------|------|
-| `placeGridLimitOrder(price, size, onSuccess, onFailure)` | 旧版限价 GTC 单,已被条件单替代 |
-| `cancelOrder(orderId)` | 旧版取消限价单,已被 cancelConditionalOrder 替代 |
-
-### 条件单 order_type 说明
-
-| order_type | 用途 | size 语义 |
-|-----------|------|----------|
-| `plan-close-long-position` | 部分/全部平多仓 | size<0 表示平多仓张数 |
-| `plan-close-short-position` | 部分/全部平空仓 | size>0 表示平空仓张数 |
-
-> **为何不用 close-*-position**:`close-long-position` 和 `close-short-position` 仅支持全部平仓(size=0),且双仓模式还需设置 `auto_size`。网格策略需要指定张数部分平仓,因此必须使用 `plan-close-*-position`。
-
-### 条件单构建 (buildTriggeredOrder)
-
-`FuturesPriceTriggeredOrder` 结构:
-
-| 组件 | 字段 | 开仓单值 | 止盈单值 |
-|------|------|---------|---------|
-| trigger | price | 触发价 | 止盈价 |
-| trigger | rule | NUMBER_1(≥) 或 NUMBER_2(≤) | 同左 |
-| trigger | strategy_type | 0 (价格触发) | 0 (价格触发) |
-| trigger | price_type | 0 (最新价) | 0 (最新价) |
-| trigger | expiration | 0 (永久有效) | 0 (永久有效) |
-| initial | contract | 合约名 | 合约名 |
-| initial | size | +qty(开多)/-qty(开空) | -qty(平多)/+qty(平空) |
-| initial | price | "0" (市价) | "0" (市价) |
-| initial | tif | IOC | IOC |
-| initial | reduce_only | false | true |
-| order_type | — | 不设置 | plan-close-long-position 或 plan-close-short-position |
-
----
-
-## GateGridTradeService
-
-**角色**: 策略核心,管理网格队列状态和执行下单。
-
-**状态**: `StrategyState` enum: `WAITING_KLINE` / `OPENING` / `ACTIVE` / `STOPPED`
-
-**关键常量**:
-```java
-// 止盈条件单 order_type:仓位计划止盈止损,支持指定张数部分平仓
-private static final String ORDER_TYPE_CLOSE_LONG = "plan-close-long-position";
-private static final String ORDER_TYPE_CLOSE_SHORT = "plan-close-short-position";
-```
-
-**回调方法**:
-
-| 方法 | 触发源 | 逻辑 |
-|------|--------|------|
-| `onKline(closePrice, timestamp)` | CandlestickChannelHandler | 更新 lastKlinePrice → 计算 unrealizedPnl → WAITING_KLINE 时触发基底双开 → ACTIVE 时方向区分,一个K线只触发一个方向 |
-| `onPositionUpdate(contract, mode, size, entryPrice)` | PositionsChannelHandler | 基底首次成交记录入场价 + tryGenerateQueues → 仓位净减少时触反向市价单 → Map截断(>5) |
-| `onPositionClose(contract, side, pnl)` | PositionClosesChannelHandler | 累加已实现盈亏 → 检查停止条件 |
-| `onOrderUpdate(orderId, status, finishAs)` | OrdersChannelHandler | 条件单成交(finished+filled) → Map.remove 取止盈价 → placeTakeProfit |
-
-**processShortGrid / processLongGrid 核心逻辑**:
-
-| 步骤 | processShortGrid(空仓触发) | processLongGrid(多仓触发) |
-|------|---------------------------|---------------------------|
-| 匹配 | 收集 shortPriceQueue 中 > currentPrice 的元素 | 收集 longPriceQueue 中 < currentPrice 的元素 |
-| 本队补充 | 尾价 − step 循环递减 × matched.size() 次 | 尾价 + step 循环递增 × matched.size() 次 |
-| 对方队列 | 不再更新(队列转移已移除) | 不再更新(队列转移已移除) |
-| 保证金检查 | marginRatio ≥ 20% 则跳过挂单 | 同左 |
-| 挂新条件单 | 空仓条件单(止盈价=新short[0]−step→currentShortOrderIds) | 多仓条件单(止盈价=新long[0]+step→currentLongOrderIds) |
-| 对方守卫 | newLongFirst + step×2 < longEntryPrice → 挂多仓条件单 | newShortFirst − step×2 > shortEntryPrice → 挂空仓条件单 |
-
-**未实现盈亏计算** (`updateUnrealizedPnl()`):
-
-正向合约公式(含合约乘数):
-
-| 方向 | 公式 |
-|------|------|
-| 多仓 | 持仓量 × contractMultiplier × (计价价格 − 开仓均价) |
-| 空仓 | 持仓量(绝对值)× contractMultiplier × (开仓均价 − 计价价格) |
-
-计价价格由 `unrealizedPnlPriceMode` 决定:
-- `LAST_PRICE`:使用最新成交价(`lastKlinePrice`,每根 K 线更新)
-- `MARK_PRICE`:使用标记价格(通过 `setMarkPrice()` 外部注入,如未注入则回退到最新成交价)
-
-**保证金安全阀** (`isMarginSafe()`):
-- 实时查询 `positionInitialMargin / initialPrincipal`
-- 比例 ≥ marginRatioLimit(默认20%)→ 跳过开仓,队列照常更新
-- REST 查询失败 → 默认放行(避免因查询异常阻塞策略)
-
-**REST API 调用**:
-
-| 操作 | API | 方法 | 说明 |
-|------|-----|------|------|
-| 获取用户ID | `GET /account/detail` | `AccountApi.getAccountDetail()` | |
-| 切持仓模式 | `POST /futures/usdt/set_position_mode` | `FuturesApi.setPositionMode()` | |
-| 查仓位 | `GET /futures/usdt/positions` | `FuturesApi.listPositions()` | 遍历所有仓位,按合约过滤 |
-| 市价平仓 | `POST /futures/usdt/orders` | `FuturesApi.createFuturesOrder()` | reduce_only, IOC |
-| 设杠杆 | `POST /futures/usdt/dual_comp/positions/{contract}/leverage` | `FuturesApi.updateDualModePositionLeverageCall()` | 双向模式专用 |
-| 查账户 | `GET /futures/usdt/accounts` | `FuturesApi.listFuturesAccounts()` | 获取初始本金 |
-| 查市价 | `GET /futures/usdt/order_book` | `FuturesApi.listFuturesOrderBook()` | 获取合约实时市价(用于市价单 price="0" 的参照) |
-
----
-
-## GateChannelHandler 接口体系
-
-```
-GateChannelHandler (接口)
- ├── CandlestickChannelHandler (公开频道)
- └── AbstractPrivateChannelHandler (私有频道基类: HMAC-SHA512)
- ├── PositionsChannelHandler
- ├── PositionClosesChannelHandler
- └── OrdersChannelHandler
-```
-
-- **subscribe**: 发送订阅请求。私有 Handler 自动附加 auth 字段
-- **unsubscribe**: 发送取消订阅请求(私有频道也带签名认证)
-- **handleMessage**: 解析推送数据并回调 GateGridTradeService,返回 true 表示已处理
-- 消息路由: update/all 事件 → 遍历 channelHandlers → handler 内部二次匹配 channel 名 → 匹配成功回调并停止遍历
-
-### PositionsChannelHandler 完整推送字段(20个)
-
-| 字段 | 类型 | 描述 |
-|------|------|------|
-| contract | String | 合约名称 |
-| mode | String | 持仓模式:dual_long / dual_short |
-| size | String/Integer | 合约张数(正=多头,负=空头) |
-| entry_price | Float | 开仓均价 |
-| cross_leverage_limit | Float | 全仓模式下的杠杆倍数上限 |
-| history_pnl | Float | 已平仓的仓位总盈亏 |
-| history_point | Float | 已平仓的点卡总盈亏 |
-| last_close_pnl | Float | 最近一次平仓的盈亏 |
-| leverage | Integer | 杠杆倍数(0=全仓,正数=逐仓) |
-| leverage_max | Integer | 当前风险限额下允许的最大杠杆倍数 |
-| liq_price | Float | 爆仓价格 |
-| maintenance_rate | Float | 当前风险限额下维持保证金比例 |
-| margin | Float | 保证金 |
-| realised_pnl | Float | 已实现盈亏 |
-| realised_point | Float | 点卡已实现盈亏 |
-| risk_limit | Integer | 风险限额 |
-| time | Integer | 更新 unix 时间戳(秒) |
-| time_ms | Integer | 更新 unix 时间戳(毫秒) |
-| user | String | 用户 ID |
-| update_id | Integer | 消息序列号,每次推送 order 后自增1 |
-
-> 以上 20 个字段在 PositionsChannelHandler.handleMessage() 中通过 SLF4J 日志全部输出。
-> 回调给 GateGridTradeService.onPositionUpdate() 的仅有 mode、size、entryPrice 三个核心字段。
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/simulation/GridSimulator.java b/src/main/java/com/xcong/excoin/modules/gateApi/simulation/GridSimulator.java
deleted file mode 100644
index 0a1c64e..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/simulation/GridSimulator.java
+++ /dev/null
@@ -1,812 +0,0 @@
-package com.xcong.excoin.modules.gateApi.simulation;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.*;
-
-/**
- * 网格策略模拟器 — 模拟 GateGridTradeService 完整生命周期并发现逻辑 BUG。
- *
- * <h3>预设参数</h3>
- * shortBaseEntryPrice = 100.0, gridRate = 0.005 (0.5%), step = 0.5, priceScale = 1, baseQuantity = 10, quantity = 1
- */
-public class GridSimulator {
-
- static int SIM_PRICE_PREC = 1;
- static BigDecimal BASE_PRICE = new BigDecimal("100.0");
- static BigDecimal STEP = BASE_PRICE.multiply(new BigDecimal("0.005")).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP); // 0.5
- static int BASE_QTY = 10;
- static int QTY = 1;
- static int GRID_QUEUE_SIZE = 10; // 模拟用缩减队列
-
- // ---- 网格数据结构 ----
- static class Grid {
- int id;
- BigDecimal price;
- boolean hasLongOrder, hasShortOrder;
- String longOrderId, shortOrderId;
- String longTpOrderId, shortTpOrderId; // 止盈
- String longSlOrderId, shortSlOrderId; // 止损
- BigDecimal longTpPrice, shortTpPrice;
- int longFilledQty = QTY, shortFilledQty = QTY;
-
- Grid(int id, BigDecimal price) {
- this.id = id; this.price = price;
- this.longTpPrice = price.add(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
- this.shortTpPrice = price.subtract(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
- }
- @Override public String toString() {
- return String.format("G[%2d] p=%.1f | L:o=%s tp=%s sl=%s | S:o=%s tp=%s sl=%s",
- id, price,
- hasLongOrder ? "Y" : "N", longTpOrderId != null ? longTpOrderId : "-", longSlOrderId != null ? longSlOrderId : "-",
- hasShortOrder ? "Y" : "N", shortTpOrderId != null ? shortTpOrderId : "-", shortSlOrderId != null ? shortSlOrderId : "-");
- }
- }
-
- static Map<Integer, Grid> INDEX = new LinkedHashMap<>();
- static List<Grid> gridElements = new ArrayList<>();
-
- // ---- 队列 ----
- static List<BigDecimal> shortQueue = new ArrayList<>(); // 降序
- static List<BigDecimal> longQueue = new ArrayList<>(); // 升序
- static List<BigDecimal> totalShortQ = new ArrayList<>(); // 降序
- static List<BigDecimal> totalLongQ = new ArrayList<>(); // 升序
-
- // ---- 状态 ----
- enum State { WAITING_KLINE, OPENING, ACTIVE, STOPPED }
- static State state = State.WAITING_KLINE;
- static boolean baseLongOpened = false, baseShortOpened = false;
- static boolean longActive = false, shortActive = false;
- static BigDecimal longPositionSize = BigDecimal.ZERO;
- static BigDecimal shortPositionSize = BigDecimal.ZERO;
- static BigDecimal longEntryPrice = BigDecimal.ZERO;
- static BigDecimal shortEntryPrice = BigDecimal.ZERO;
- static BigDecimal cumulativePnl = BigDecimal.ZERO;
- static BigDecimal unrealizedPnl = BigDecimal.ZERO;
- static BigDecimal shortBaseEntryPrice = BigDecimal.ZERO;
- static BigDecimal longBaseEntryPrice = BigDecimal.ZERO;
-
- static int orderIdCounter = 1000;
- static List<String> eventLog = new ArrayList<>();
- static List<String> bugLog = new ArrayList<>();
-
- static void log(String msg) { eventLog.add(msg); System.out.println(" " + msg); }
- static void bug(String msg) { String s = " [BUG] " + msg; bugLog.add(s); System.out.println(s); }
- static String newOrderId() { return "O-" + (orderIdCounter++); }
- static String repeat(String s, int n) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < n; i++) sb.append(s);
- return sb.toString();
- }
-
- // ================================================================
- // 步骤1: 初始化
- // ================================================================
- static void init() {
- log("=== Phase 1: 初始化 ===");
- log("参数: basePrice=" + BASE_PRICE + " step=" + STEP + " baseQty=" + BASE_QTY + " qty=" + QTY);
- state = State.WAITING_KLINE;
- }
-
- // ================================================================
- // 步骤2: 首根K线 → 市价双开基底
- // ================================================================
- static void onFirstKline() {
- log("\n=== Phase 2: 首根K线到达,市价双开基底 ===");
- state = State.OPENING;
- log("市价开多 " + BASE_QTY + " 张,开空 " + BASE_QTY + " 张");
-
- // 模拟成交
- longPositionSize = new BigDecimal(BASE_QTY);
- shortPositionSize = new BigDecimal(BASE_QTY);
- longEntryPrice = BASE_PRICE;
- shortEntryPrice = BASE_PRICE;
- longActive = true;
- shortActive = true;
-
- // 模拟 onPositionUpdate → 基底成交
- log("基底多成交价: " + BASE_PRICE);
- longBaseEntryPrice = BASE_PRICE;
- baseLongOpened = true;
- log("基底空成交价: " + BASE_PRICE);
- shortBaseEntryPrice = BASE_PRICE;
- baseShortOpened = true;
-
- tryGenerateQueues();
- }
-
- // ================================================================
- // 步骤3: 生成队列和网格
- // ================================================================
- static void tryGenerateQueues() {
- if (!baseLongOpened || !baseShortOpened) return;
- log("\n=== Phase 3: 生成网格队列 ===");
-
- // 生成队列
- generateShortQueue();
- generateLongQueue();
- updateGridElements();
-
- // 标记基底元素 (模拟 baseLongTraderParam/baseShortTraderParam)
- Grid baseG = INDEX.get(0);
- baseG.hasLongOrder = true;
- baseG.longOrderId = "BASE-LONG-IOC";
- baseG.hasShortOrder = true;
- baseG.shortOrderId = "BASE-SHORT-IOC";
- log("基底元素 ID=0 标记 hasLongOrder=true, hasShortOrder=true");
-
- // 初始化止损单
- initStopLossOrders();
-
- state = State.ACTIVE;
- log("状态切换为 ACTIVE");
- printGridState();
- }
-
- static void generateShortQueue() {
- shortQueue.clear();
- BigDecimal elem = BASE_PRICE.subtract(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
- for (int i = 0; i < GRID_QUEUE_SIZE; i++) {
- shortQueue.add(elem);
- totalLongQ.add(elem);
- totalShortQ.add(elem);
- elem = elem.subtract(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
- if (elem.compareTo(BigDecimal.ZERO) <= 0) break;
- }
- shortQueue.sort((a, b) -> b.compareTo(a));
- log("空仓队列(降序): " + shortQueue);
- }
-
- static void generateLongQueue() {
- longQueue.clear();
- BigDecimal elem = BASE_PRICE.add(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
- for (int i = 0; i < GRID_QUEUE_SIZE; i++) {
- longQueue.add(elem);
- totalLongQ.add(elem);
- totalShortQ.add(elem);
- elem = elem.add(STEP).setScale(SIM_PRICE_PREC, RoundingMode.HALF_UP);
- }
- longQueue.sort(BigDecimal::compareTo);
- log("多仓队列(升序): " + longQueue);
- }
-
- static void updateGridElements() {
- gridElements.clear();
- INDEX.clear();
- int shortSize = shortQueue.size();
- int longSize = longQueue.size();
-
- // 空仓区域: id 从 -1 自减
- for (int i = 0; i < shortSize; i++) {
- int id = -(i + 1);
- BigDecimal price = shortQueue.get(i);
- Grid g = new Grid(id, price);
- g.longFilledQty = QTY;
- g.shortFilledQty = QTY;
- gridElements.add(g);
- INDEX.put(id, g);
- }
-
- // 位置 0
- Grid g0 = new Grid(0, BASE_PRICE);
- g0.longFilledQty = QTY;
- g0.shortFilledQty = QTY;
- gridElements.add(g0);
- INDEX.put(0, g0);
-
- // 多仓区域: id 从 1 自增
- for (int i = 0; i < longSize; i++) {
- int id = i + 1;
- BigDecimal price = longQueue.get(i);
- Grid g = new Grid(id, price);
- g.longFilledQty = QTY;
- g.shortFilledQty = QTY;
- gridElements.add(g);
- INDEX.put(id, g);
- }
-
- log("网格元素: " + gridElements.size() + " 个");
- }
-
- static void initStopLossOrders() {
- log("\n--- 初始化止损单 ---");
- // 空仓止损: ID 2 到 BASE_QTY+1
- int shortTime = BASE_QTY + 1;
- for (int id = 2; id <= shortTime; id++) {
- Grid g = INDEX.get(id);
- if (g == null) continue;
- String slId = newOrderId();
- g.shortSlOrderId = slId;
- log(String.format(" 空仓止损 gridId:%d price:%.1f slId:%s (CLOSE_SHORT NUMBER_1)", id, g.price, slId));
- }
-
- // 多仓止损: ID -2 到 -(BASE_QTY+1)
- int longTime = BASE_QTY + 1;
- for (int id = -2; id >= -longTime; id--) {
- Grid g = INDEX.get(id);
- if (g == null) continue;
- String slId = newOrderId();
- g.longSlOrderId = slId;
- log(String.format(" 多仓止损 gridId:%d price:%.1f slId:%s (CLOSE_LONG NUMBER_2)", id, g.price, slId));
- }
- log("止损单初始化完成");
- }
-
- // ================================================================
- // 步骤4: onKline ACTIVE 状态下的网格处理
- // ================================================================
- static void onKline(BigDecimal closePrice) {
- if (state != State.ACTIVE) return;
-
- if (!longActive && longPositionSize.compareTo(BigDecimal.ZERO) == 0) {
- processShortGrid(closePrice);
- }
- if (!shortActive && shortPositionSize.compareTo(BigDecimal.ZERO) == 0) {
- processLongGrid(closePrice);
- }
- }
-
- static void processShortGrid(BigDecimal currentPrice) {
- log("\n--- processShortGrid (多仓归零, 触发空仓队列) 当前价:" + currentPrice + " ---");
- BigDecimal matched = BigDecimal.ZERO;
- for (BigDecimal p : totalLongQ) {
- if (p.compareTo(currentPrice) >= 0) {
- matched = p;
- break;
- }
- }
- if (matched.compareTo(BigDecimal.ZERO) == 0) {
- log(" 未匹配到价格");
- return;
- }
- log(" 匹配价格: " + matched);
- Grid matchedG = findByPrice(matched);
- if (matchedG == null) { log(" Grid不存在"); return; }
-
- if (!matchedG.hasLongOrder) {
- // 在 upId 位置挂多单
- int upId = matchedG.id + 1; // upId逻辑简化
- Grid newEntryG = INDEX.get(upId);
- if (newEntryG != null && !newEntryG.hasLongOrder) {
- String oid = newOrderId();
- newEntryG.hasLongOrder = true;
- newEntryG.longOrderId = oid;
- newEntryG.longFilledQty = BASE_QTY;
- log(String.format(" 在gridId:%d挂多单(size=%d) orderId:%s", upId, BASE_QTY, oid));
- }
-
- // 取消upId+1位置的多单
- int cancelId = upId + 1;
- Grid cancelG = INDEX.get(cancelId);
- if (cancelG != null && cancelG.hasLongOrder) {
- log(String.format(" 取消gridId:%d的多单 orderId:%s", cancelId, cancelG.longOrderId));
- cancelG.hasLongOrder = false;
- cancelG.longOrderId = null;
- }
-
- // BUG: 这里新挂的单 size = BASE_QTY,但如果之前已有其他追单
- // 可能导致持仓远超预期
- }
- }
-
- static void processLongGrid(BigDecimal currentPrice) {
- log("\n--- processLongGrid (空仓归零, 触发多仓队列) 当前价:" + currentPrice + " ---");
- BigDecimal matched = BigDecimal.ZERO;
- for (BigDecimal p : totalShortQ) {
- if (p.compareTo(currentPrice) <= 0) {
- matched = p;
- break;
- }
- }
- if (matched.compareTo(BigDecimal.ZERO) == 0) {
- log(" 未匹配到价格");
- return;
- }
- log(" 匹配价格: " + matched);
- Grid matchedG = findByPrice(matched);
- if (matchedG == null) { log(" Grid不存在"); return; }
-
- if (!matchedG.hasShortOrder) {
- int downId = matchedG.id - 1;
- Grid newEntryG = INDEX.get(downId);
- if (newEntryG != null && !newEntryG.hasShortOrder) {
- String oid = newOrderId();
- newEntryG.hasShortOrder = true;
- newEntryG.shortOrderId = oid;
- newEntryG.shortFilledQty = BASE_QTY;
- log(String.format(" 在gridId:%d挂空单(size=%d) orderId:%s", downId, BASE_QTY, oid));
- }
-
- int cancelId = downId - 1;
- Grid cancelG = INDEX.get(cancelId);
- if (cancelG != null && cancelG.hasShortOrder) {
- log(String.format(" 取消gridId:%d的空单 orderId:%s", cancelId, cancelG.shortOrderId));
- cancelG.hasShortOrder = false;
- cancelG.shortOrderId = null;
- }
- }
- }
-
- // ================================================================
- // 步骤5: onAutoOrder 条件单成交回调
- // ================================================================
- static void onAutoOrder(String orderId, String status, String orderType, String tradeId) {
- if (state == State.STOPPED) return;
- if (!"finished".equals(status)) return;
-
- log("\n--- onAutoOrder: id=" + orderId + " type=" + orderType + " tradeId=" + tradeId + " ---");
-
- // 1. 检查是否是止损单成交
- Grid longSl = findByLongSlOrderId(orderId);
- if (longSl != null && tradeId != null && !"0".equals(tradeId)) {
- handleLongStopLossTriggered(longSl);
- return;
- }
- Grid shortSl = findByShortSlOrderId(orderId);
- if (shortSl != null && tradeId != null && !"0".equals(tradeId)) {
- handleShortStopLossTriggered(shortSl);
- return;
- }
-
- // 2. 检查是否是空仓挂单成交
- Grid shortG = findByShortOrderId(orderId);
- if (shortG != null && shortG.hasShortOrder && tradeId != null && !"0".equals(tradeId)) {
- int filledQty = shortG.shortFilledQty;
- shortG.hasShortOrder = false;
- shortG.shortOrderId = null;
- extendShortStopLoss(filledQty);
- log(String.format(" 空单成交 gridId:%d filledQty:%d", shortG.id, filledQty));
-
- // 新逻辑: 持仓超过baseQuantity时,从gridId-2开始追挂止盈
- BigDecimal baseQty = new BigDecimal(BASE_QTY);
- BigDecimal qty = new BigDecimal(QTY);
- if (shortPositionSize.compareTo(baseQty) > 0) {
- BigDecimal excess = shortPositionSize.subtract(baseQty);
- int excessCount = excess.divide(qty, 0, RoundingMode.DOWN).intValue();
- for (int i = 0; i < excessCount; i++) {
- int tpId = shortG.id - 2 - i;
- Grid tpG = INDEX.get(tpId);
- if (tpG == null || tpG.shortTpOrderId != null) continue;
- String tpIdStr = newOrderId();
- tpG.shortTpOrderId = tpIdStr;
- log(String.format(" 空仓止盈挂单 gridId:%d price:%.1f tpId:%s", tpId, tpG.price, tpIdStr));
- }
- }
- }
-
- // 3. 检查是否是多仓挂单成交
- Grid longG = findByLongOrderId(orderId);
- if (longG != null && longG.hasLongOrder && tradeId != null && !"0".equals(tradeId)) {
- int filledQty = longG.longFilledQty;
- longG.hasLongOrder = false;
- longG.longOrderId = null;
- extendLongStopLoss(filledQty);
- log(String.format(" 多单成交 gridId:%d filledQty:%d", longG.id, filledQty));
-
- BigDecimal baseQty = new BigDecimal(BASE_QTY);
- BigDecimal qty = new BigDecimal(QTY);
- if (longPositionSize.compareTo(baseQty) > 0) {
- BigDecimal excess = longPositionSize.subtract(baseQty);
- int excessCount = excess.divide(qty, 0, RoundingMode.DOWN).intValue();
- for (int i = 0; i < excessCount; i++) {
- int tpId = longG.id + 2 + i;
- Grid tpG = INDEX.get(tpId);
- if (tpG == null || tpG.longTpOrderId != null) continue;
- String tpIdStr = newOrderId();
- tpG.longTpOrderId = tpIdStr;
- log(String.format(" 多仓止盈挂单 gridId:%d price:%.1f tpId:%s", tpId, tpG.price, tpIdStr));
- }
- }
- }
- }
-
- static void handleLongStopLossTriggered(Grid g) {
- log(" 多仓止损触发 gridId:" + g.id);
- g.longSlOrderId = null;
- // 模拟平仓: longPositionSize 减少
- longPositionSize = longPositionSize.subtract(new BigDecimal(QTY));
- if (longPositionSize.compareTo(BigDecimal.ZERO) < 0) longPositionSize = BigDecimal.ZERO;
-
- // 在gridId+1位置挂新追单
- int newId = g.id + 1;
- Grid newG = INDEX.get(newId);
- if (newG == null) {
- log(" gridId:" + newId + " 不存在,止损追单失败");
- return;
- }
-
- // 计算size
- BigDecimal baseQty = new BigDecimal(BASE_QTY);
- BigDecimal subtract = baseQty.subtract(longPositionSize);
- int size = QTY + 1;
- if (subtract.compareTo(BigDecimal.ZERO) >= 0) {
- size = subtract.intValue() + 1;
- }
- log(String.format(" 挂多单 gridId:%d size:%d (pos=%s)", newId, size, longPositionSize));
- newG.hasLongOrder = true;
- newG.longOrderId = newOrderId();
- newG.longFilledQty = size;
-
- // 取消gridId+2的多单
- int cancelId = g.id + 2;
- Grid cancelG = INDEX.get(cancelId);
- if (cancelG != null && cancelG.hasLongOrder) {
- log(String.format(" 取消gridId:%d的多单 orderId:%s", cancelId, cancelG.longOrderId));
- cancelG.hasLongOrder = false;
- cancelG.longOrderId = null;
- }
-
- // 取消最远多仓止盈
- Grid farthest = null;
- for (Grid e : gridElements) {
- if (e.longTpOrderId != null) {
- if (farthest == null || e.price.compareTo(farthest.price) > 0) {
- farthest = e;
- }
- }
- }
- if (farthest != null) {
- log(String.format(" 取消最远多仓止盈 gridId:%d orderId:%s", farthest.id, farthest.longTpOrderId));
- farthest.longTpOrderId = null;
- }
-
- // 持仓归零检查
- if (longPositionSize.compareTo(BigDecimal.ZERO) == 0) {
- longActive = false;
- log(" 多仓已全部止损,longActive=false");
- }
- }
-
- static void handleShortStopLossTriggered(Grid g) {
- log(" 空仓止损触发 gridId:" + g.id);
- g.shortSlOrderId = null;
- shortPositionSize = shortPositionSize.subtract(new BigDecimal(QTY));
- if (shortPositionSize.compareTo(BigDecimal.ZERO) < 0) shortPositionSize = BigDecimal.ZERO;
-
- int newId = g.id - 1;
- Grid newG = INDEX.get(newId);
- if (newG == null) {
- log(" gridId:" + newId + " 不存在,止损追单失败");
- return;
- }
-
- BigDecimal baseQty = new BigDecimal(BASE_QTY);
- BigDecimal subtract = baseQty.subtract(shortPositionSize);
- int size = QTY + 1;
- if (subtract.compareTo(BigDecimal.ZERO) >= 0) {
- size = subtract.intValue() + 1;
- }
- log(String.format(" 挂空单 gridId:%d size:%d (pos=%s)", newId, size, shortPositionSize));
- newG.hasShortOrder = true;
- newG.shortOrderId = newOrderId();
- newG.shortFilledQty = size;
-
- int cancelId = g.id - 2;
- Grid cancelG = INDEX.get(cancelId);
- if (cancelG != null && cancelG.hasShortOrder) {
- log(String.format(" 取消gridId:%d的空单 orderId:%s", cancelId, cancelG.shortOrderId));
- cancelG.hasShortOrder = false;
- cancelG.shortOrderId = null;
- }
-
- Grid farthest = null;
- for (Grid e : gridElements) {
- if (e.shortTpOrderId != null) {
- if (farthest == null || e.price.compareTo(farthest.price) < 0) {
- farthest = e;
- }
- }
- }
- if (farthest != null) {
- log(String.format(" 取消最远空仓止盈 gridId:%d orderId:%s", farthest.id, farthest.shortTpOrderId));
- farthest.shortTpOrderId = null;
- }
-
- if (shortPositionSize.compareTo(BigDecimal.ZERO) == 0) {
- shortActive = false;
- log(" 空仓已全部止损,shortActive=false");
- }
- }
-
- static void extendLongStopLoss(int filledQty) {
- // 找到最远止损
- int furthestId = 0;
- for (Grid e : gridElements) {
- if (e.longSlOrderId != null && e.id < furthestId) {
- furthestId = e.id;
- }
- }
- if (furthestId == 0) furthestId = -11;
- for (int i = 0; i < filledQty; i++) {
- int newId = furthestId - i - 1;
- Grid g = INDEX.get(newId);
- if (g == null) continue;
- String slId = newOrderId();
- g.longSlOrderId = slId;
- log(String.format(" 多仓止损追加 gridId:%d slId:%s", newId, slId));
- }
- }
-
- static void extendShortStopLoss(int filledQty) {
- int furthestId = 0;
- for (Grid e : gridElements) {
- if (e.shortSlOrderId != null && e.id > furthestId) {
- furthestId = e.id;
- }
- }
- if (furthestId == 0) furthestId = 11;
- for (int i = 0; i < filledQty; i++) {
- int newId = furthestId + i + 1;
- Grid g = INDEX.get(newId);
- if (g == null) continue;
- String slId = newOrderId();
- g.shortSlOrderId = slId;
- log(String.format(" 空仓止损追加 gridId:%d slId:%s", newId, slId));
- }
- }
-
- // ================================================================
- // 模拟 onPositionUpdate
- // ================================================================
- static void onPositionUpdate(boolean isLong, BigDecimal size, BigDecimal entryPrice) {
- if (isLong) {
- if (size.compareTo(BigDecimal.ZERO) > 0) {
- longActive = true;
- longPositionSize = size;
- longEntryPrice = entryPrice;
- } else {
- longActive = false;
- longPositionSize = BigDecimal.ZERO;
- longEntryPrice = BigDecimal.ZERO;
- }
- } else {
- if (size.compareTo(BigDecimal.ZERO) > 0) {
- shortActive = true;
- shortPositionSize = size;
- shortEntryPrice = entryPrice;
- } else {
- shortActive = false;
- shortPositionSize = BigDecimal.ZERO;
- shortEntryPrice = BigDecimal.ZERO;
- }
- }
- }
-
- // ================================================================
- // 查找方法
- // ================================================================
- static Grid findByPrice(BigDecimal price) {
- for (Grid g : gridElements) {
- if (g.price.compareTo(price) == 0) return g;
- }
- return null;
- }
- static Grid findByLongSlOrderId(String oid) {
- for (Grid g : gridElements) if (oid.equals(g.longSlOrderId)) return g;
- return null;
- }
- static Grid findByShortSlOrderId(String oid) {
- for (Grid g : gridElements) if (oid.equals(g.shortSlOrderId)) return g;
- return null;
- }
- static Grid findByLongOrderId(String oid) {
- for (Grid g : gridElements) if (oid.equals(g.longOrderId)) return g;
- return null;
- }
- static Grid findByShortOrderId(String oid) {
- for (Grid g : gridElements) if (oid.equals(g.shortOrderId)) return g;
- return null;
- }
-
- // ================================================================
- // 打印
- // ================================================================
- static void printGridState() {
- System.out.println("\n === 网格状态 ===");
- System.out.println(" 多仓: pos=" + longPositionSize + " active=" + longActive + " entryPrice=" + longEntryPrice);
- System.out.println(" 空仓: pos=" + shortPositionSize + " active=" + shortActive + " entryPrice=" + shortEntryPrice);
- for (Grid g : gridElements) {
- System.out.println(" " + g);
- }
- }
-
- // ================================================================
- // 主模拟流程
- // ================================================================
- public static void main(String[] args) {
- System.out.println(repeat("=",70));
- System.out.println("Gate 网格策略模拟器");
- System.out.println("参数: basePrice=100.0, step=0.5, baseQty=10, qty=1, gridSize=10");
- System.out.println(repeat("=",70));
-
- init();
-
- // ---- 场景 A: 价格稳定 → 基底双开 → 进入ACTIVE ----
- System.out.println("\n\n◆◆◆ 场景A: 首根K线,基底双开 ◆◆◆");
- onFirstKline();
- // 模拟 onPositionUpdate 设置基底持仓
- onPositionUpdate(true, new BigDecimal(BASE_QTY), BASE_PRICE);
- onPositionUpdate(false, new BigDecimal(BASE_QTY), BASE_PRICE);
-
- printGridState();
-
- // ---- 场景 B: 价格下跌触发多仓止损 ----
- System.out.println("\n\n◆◆◆ 场景B: 价格跌至99.0,触发多仓止损(gridId:-2) ◆◆◆");
- Grid gMinus2 = INDEX.get(-2);
- String slOrderId = gMinus2.longSlOrderId;
- if (slOrderId != null) {
- // 模拟止损成交
- onAutoOrder(slOrderId, "finished", "plan-close-long-position", "T001");
- // 模拟 onPositionUpdate: 多仓从10降到9
- onPositionUpdate(true, new BigDecimal("9"), new BigDecimal("99.5"));
- printGridState();
- } else {
- log(" gridId:-2 无止损单!");
- }
-
- // ---- 场景 C: 价格持续下跌,再次触发止损 ----
- System.out.println("\n\n◆◆◆ 场景C: 价格跌至98.5,再次触发多仓止损(gridId:-3) ◆◆◆");
- Grid gMinus3 = INDEX.get(-3);
- slOrderId = gMinus3.longSlOrderId;
- if (slOrderId != null) {
- onAutoOrder(slOrderId, "finished", "plan-close-long-position", "T002");
- onPositionUpdate(true, new BigDecimal("8"), new BigDecimal("99.0"));
- printGridState();
- }
-
- // ---- 场景 D: 止损追单成交,测试止盈追挂 ----
- System.out.println("\n\n◆◆◆ 场景D: 追单成交,触发止盈追挂 ◆◆◆");
- // 找到最近挂的多单
- Grid pendingLong = null;
- for (Grid g : gridElements) {
- if (g.hasLongOrder) {
- pendingLong = g;
- break;
- }
- }
- if (pendingLong != null) {
- // 模拟追单成交,追了2张
- log("\n模拟追单成交: gridId:" + pendingLong.id + " orderId:" + pendingLong.longOrderId);
- longPositionSize = longPositionSize.add(new BigDecimal(pendingLong.longFilledQty));
- log("多仓持仓变为: " + longPositionSize);
- onAutoOrder(pendingLong.longOrderId, "finished", "", "T003");
- onPositionUpdate(true, longPositionSize, BASE_PRICE);
- printGridState();
- }
-
- // ---- 场景 E: 多仓全部止损归零 ----
- System.out.println("\n\n◆◆◆ 场景E: 价格继续暴跌,多仓全部止损归零 ◆◆◆");
- log("模拟多仓全部平仓...");
- onPositionUpdate(true, BigDecimal.ZERO, BigDecimal.ZERO);
- log("多仓归零, longActive=" + longActive + ", pos=" + longPositionSize);
-
- // ACTIVE状态下多仓归零 → 下一根K线触发processShortGrid
- System.out.println("\n 下一根K线触发 processShortGrid:");
- onKline(new BigDecimal("95.0"));
- printGridState();
-
- // ---- 场景 F: 空仓止损触发 ----
- System.out.println("\n\n◆◆◆ 场景F: 价格暴涨至101.0,触发空仓止损(gridId:2) ◆◆◆");
- Grid g2 = INDEX.get(2);
- if (g2 != null && g2.shortSlOrderId != null) {
- onAutoOrder(g2.shortSlOrderId, "finished", "plan-close-short-position", "T004");
- onPositionUpdate(false, new BigDecimal("9"), new BigDecimal("100.5"));
- printGridState();
- }
-
- // ================================================================
- // BUG 分析
- // ================================================================
- System.out.println("\n\n" + repeat("=",70));
- System.out.println("BUG 分析报告");
- System.out.println(repeat("=",70));
-
- int bugNum = 1;
-
- // BUG 1
- System.out.println("\n[BUG-" + (bugNum++) + "] 基座IOC成交不触发onAutoOrder,hasLongOrder/hasShortOrder永久为true");
- System.out.println(" 位置: GateGridTradeService.onKline() + tryGenerateQueues()");
- System.out.println(" 描述: 基底市价IOC开仓后,tryGenerateQueues中在ID=0设置了hasLongOrder=true/hasShortOrder=true。");
- System.out.println(" 但IOC成交不会触发onAutoOrder(条件单回调),导致ID=0的挂单状态永远不会被清除。");
- System.out.println(" 影响: ID=0的hasLongOrder永远为true。当止损触发在gridId=-2时,会尝试取消gridId=0的多单");
- System.out.println(" (cancelGridId = -2 + 2 = 0),由于IOC已成交,取消会失败但只产生warn日志,无严重后果。");
- System.out.println(" 但状态不一致可能影响后续processShortGrid/processLongGrid的匹配逻辑。");
- System.out.println(" 修复: tryGenerateQueues中不要用IOC的orderId标记hasLongOrder,或者增加onOrderUpdate回调处理普通订单。");
-
- // BUG 2
- System.out.println("\n[BUG-" + (bugNum++) + "] handleShortStopLossTriggered 使用了longPositionSize计算size");
- System.out.println(" 位置: handleShortStopLossTriggered() line 1176");
- System.out.println(" 代码: BigDecimal subtract = baseQuantity.subtract(longPositionSize);");
- System.out.println(" 描述: 空仓止损触发时,用 longPositionSize 来计算追单size,应该用 shortPositionSize。");
- System.out.println(" 影响: 空仓止损追单的size计算基于多头持仓而非空头持仓,导致补仓数量错误。");
- System.out.println(" 例如多仓持仓5,空仓持仓9,空仓止损触发时: subtract = 10 - 5 = 5, size = 5+1 = 6.");
- System.out.println(" 应该是: subtract = 10 - 9 = 1, size = 1+1 = 2.");
- System.out.println(" 修复: 将 longPositionSize 改为 shortPositionSize。");
-
- // BUG 3
- System.out.println("\n[BUG-" + (bugNum++) + "] onAutoOrder中持仓判断存在竞态问题");
- System.out.println(" 位置: onAutoOrder() 追挂止盈逻辑 (line 617/655)");
- System.out.println(" 描述: onAutoOrder被调用时,longPositionSize/shortPositionSize可能尚未被onPositionUpdate更新。");
- System.out.println(" WebSocket消息顺序虽然是顺序的,但onAutoOrder基于orders/autoorders频道,");
- System.out.println(" onPositionUpdate基于positions频道,两者到达顺序不一定严格匹配。");
- System.out.println(" 影响: 追挂止盈的excessCount计算可能偏小(持仓尚未更新),导致少挂止盈单。");
- System.out.println(" 下次fill时会再次尝试追挂,重复计算可能产生多余止盈单。");
- System.out.println(" 修复: 考虑在onPositionUpdate中做止盈追挂,或基于filledQty累加预估新持仓。");
-
- // BUG 4
- System.out.println("\n[BUG-" + (bugNum++) + "] 止盈追挂从gridId±2开始,可能覆盖已有止损单的网格位置");
- System.out.println(" 位置: onAutoOrder() 止盈追挂逻辑");
- System.out.println(" 描述: 止盈追挂从 filledGridId+2(多)/filledGridId-2(空) 开始。");
- System.out.println(" 但这些位置可能已经被止损单占用(如initStopLossOrders在ID 2~11和-2~-11挂止损)。");
- System.out.println(" 虽然止盈和止损是不同的订单(分别存在takeProfitOrderId和stopLossOrderId中),");
- System.out.println(" 但同一个GridElement同时有止盈和止损,在状态追踪上可能模糊。");
- System.out.println(" 影响: 同一个网格位置可能同时有止盈和止损订单,当订单成交回调时可能匹配错误。");
- System.out.println(" 风险: 中低。onAutoOrder先用findByLongStopLossOrderId匹配止损,再匹配入口单,止盈不会被匹配。");
-
- // BUG 5 → 已确认为设计意图,移除
- System.out.println("\n[OK-5] processShortGrid/processLongGrid 只在对方持仓归零时触发 — 属于设计意图,非BUG");
- System.out.println(" 位置: onKline() line 389-400");
- System.out.println(" 描述: 策略有两套驱动机制:有持仓时走订单驱动(onAutoOrder处理挂单成交/止损追单),");
- System.out.println(" 当某方向持仓归零后切换到价格驱动(onKline→processGrid),挂新入场单后回到订单驱动。");
- System.out.println(" 两套机制交替运作,确保仓位归零后能基于当前价格重新入场。");
-
- // BUG 6
- System.out.println("\n[BUG-" + (bugNum++) + "] processShortGrid/processLongGrid 中 size 使用 BASE_QTY 而非 QTY");
- System.out.println(" 位置: processShortGrid() line 1027, processLongGrid() line 1077");
- System.out.println(" 描述: 仓位归零后重新挂单时,使用 baseQuantity 而不是 quantity。");
- System.out.println(" 例如 baseQuantity=10,会在新位置挂10张,而其他网格层级只挂1张。");
- System.out.println(" 影响: 每次仓位归零后的重新入场都是10张,可能导致持仓量远超预期。");
- System.out.println(" 风险: 可能是设计意图(仓位归零后需要重建基底仓位),但需确认。");
-
- // BUG 7
- System.out.println("\n[BUG-" + (bugNum++) + "] extendLongStopLoss/extendShortStopLoss 不停向外扩展");
- System.out.println(" 位置: extendLongStopLoss() line 1216, extendShortStopLoss() line 1249");
- System.out.println(" 描述: 每次成交都调用extend,从当前最远止损位置继续向外挂filledQty个止损单。");
- System.out.println(" 如果成交次数多,止损单会无限向外扩展(比如挂到gridId=-100)。");
- System.out.println(" 影响: 大量止损单可能超出交易所限制(Gate条件单上限通常200个)。并且止损位置离当前价");
- System.out.println(" 越来越远,实际作用不大但占用资源。");
- System.out.println(" 风险: 低中。止损位置越来越远意味着回撤越来越大,风控上可能已经触发maxLoss停止策略。");
-
- // BUG 8 → 已确认为设计意图,移除
- System.out.println("\n[OK-8] onPositionUpdate 中两仓归零异步重启策略 — 属于设计意图,非BUG");
- System.out.println(" 位置: onPositionUpdate() line 522-538");
- System.out.println(" 描述: 当 shortActive==false && longActive==false 时,取消所有条件单并平仓,");
- System.out.println(" 然后异步sleep 3s后调用startGrid重启;双仓归零后重新开启一轮新策略。");
-
- // BUG 9
- System.out.println("\n[BUG-" + (bugNum++) + "] onAutoOrder中stopLoss匹配缺少positionSize校验");
- System.out.println(" 位置: onAutoOrder() line 595-604");
- System.out.println(" 描述: 原始代码有 longPositionSize > 0 的校验但被注释掉了:");
- System.out.println(" // if (longStopLossElem != null && longPositionSize.compareTo(BigDecimal.ZERO) > 0 ...");
- System.out.println(" 现在只要tradeId非空非0就触发handler。");
- System.out.println(" 影响: 如果多仓已全部平仓但止损单尚未取消(异步延迟),止损单的finished回调");
- System.out.println(" 仍会触发handleLongStopLossTriggered,产生多余的追单操作。");
- System.out.println(" 修复: 恢复positionSize > 0的校验。");
-
- // BUG 10
- System.out.println("\n[BUG-" + (bugNum++) + "] tryGenerateQueues中上/下ID分配有误导性命名");
- System.out.println(" 位置: updateGridElements() line 960-967");
- System.out.println(" 描述: ID=0的upId=1(指向高价), downId=-1(指向低价)。");
- System.out.println(" 在注释和文档中写的是 upId=-1, downId=1,但代码实际是反过来的。");
- System.out.println(" 实际语义: upId=\"向上的价格\"(更大ID), downId=\"向下的价格\"(更小ID)。");
- System.out.println(" 影响: 代码实际工作正确,但注释和代码不一致,容易误导维护者。");
- System.out.println(" 状态: 可能是早期代码重构遗留,实际语义是\"up=价格向上, down=价格向下\"。");
-
- // BUG 11
- System.out.println("\n[BUG-" + (bugNum++) + "] currentLongOrderIds/currentShortOrderIds 声明但从未使用");
- System.out.println(" 位置: GateGridTradeService line 119-121");
- System.out.println(" 描述: currentLongOrderIds 和 currentShortOrderIds 是 synchronized LinkedHashMap,");
- System.out.println(" 声明但代码中没有任何put操作,只有clear。");
- System.out.println(" 影响: 死代码,占用内存但无实际作用。代码中对此Map的使用意图已过时。");
-
- // BUG 12
- System.out.println("\n[BUG-" + (bugNum++) + "] onAutoOrder中已成交的挂单仍通过isHasLongOrder检测");
- System.out.println(" 位置: onAutoOrder() line 608/617");
- System.out.println(" 描述: 当查找shortGridElement时,检查 isHasShortOrder() 为true才处理。");
- System.out.println(" 但同一个orderId被匹配到说明它确实是挂单,检查 isHasShortOrder 可能因");
- System.out.println(" placeEntryOrderWithPreFlag的竞态导致问题(API失败→回滚→但WS已推送)。");
- System.out.println(" 影响: 如果挂单API失败但WS已推送finished回调,isHasShortOrder可能已回滚为false,");
- System.out.println(" 导致成交回调被忽略,持仓状态与实际不符。");
- System.out.println(" 风险: 极低(需要API失败+WS推送的精确时序)。");
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/AbstractPrivateChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/AbstractPrivateChannelHandler.java
deleted file mode 100644
index f5dac39..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/AbstractPrivateChannelHandler.java
+++ /dev/null
@@ -1,176 +0,0 @@
-package com.xcong.excoin.modules.gateApi.wsHandler;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.gateApi.GateGridTradeService;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.nio.charset.StandardCharsets;
-
-/**
- * 私有频道 WS 处理器的抽象基类,封装 HMAC-SHA512 签名认证与订阅/取消订阅逻辑。
- *
- * @author Administrator
- */
-@Slf4j
-public abstract class AbstractPrivateChannelHandler implements GateChannelHandler {
-
- private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
-
- private final String channelName;
- protected final String apiKey;
- protected final String apiSecret;
- private final String contract;
- private final GateGridTradeService gridTradeService;
-
- private volatile boolean subscribed = false;
-
- public AbstractPrivateChannelHandler(String channelName,
- String apiKey, String apiSecret,
- String contract,
- GateGridTradeService gridTradeService) {
- this.channelName = channelName;
- this.apiKey = apiKey;
- this.apiSecret = apiSecret;
- this.contract = contract;
- this.gridTradeService = gridTradeService;
- }
-
- /** @return 频道名称(如 "futures.positions") */
- @Override
- public String getChannelName() { return channelName; }
-
- /**
- * 发送带签名的订阅请求。
- *
- * <h3>请求格式</h3>
- * <pre>
- * {
- * "id": <唯一请求ID>,
- * "time": <unix时间戳(秒)>,
- * "channel":"futures.positions",
- * "event": "subscribe",
- * "payload":[userId, contract],
- * "auth": {"method":"api_key", "KEY":<APIKEY>, "SIGN":<HMAC-SHA512签名>}
- * }
- * </pre>
- */
- @Override
- public void subscribe(WebSocketClient ws) {
- long timeSec = System.currentTimeMillis() / 1000;
- JSONObject msg = buildAuthRequest("subscribe", buildUid(), timeSec);
- ws.send(msg.toJSONString());
- log.info("[{}] 订阅成功, 合约:{}", channelName, contract);
- }
-
- /**
- * 发送带签名的取消订阅请求,与 subscribe 结构一致。
- * payload: [contract],无 userId(取消订阅不需要用户ID)。
- */
- @Override
- public void unsubscribe(WebSocketClient ws) {
- long timeSec = System.currentTimeMillis() / 1000;
- JSONObject msg = new JSONObject();
- msg.put("id", timeSec * 1000000 + (System.currentTimeMillis() % 1000));
- msg.put("time", timeSec);
- msg.put("channel", channelName);
- msg.put("event", "unsubscribe");
- JSONArray payload = new JSONArray();
- payload.add(contract);
- msg.put("payload", payload);
- JSONObject auth = new JSONObject();
- auth.put("method", "api_key");
- auth.put("KEY", apiKey);
- auth.put("SIGN", hs512Sign("unsubscribe", timeSec));
- msg.put("auth", auth);
- ws.send(msg.toJSONString());
- log.info("[{}] 取消订阅成功, 合约:{}", channelName, contract);
- }
-
- /** @return 网格交易服务实例 */
- protected GateGridTradeService getGridTradeService() { return gridTradeService; }
- /** @return 当前订阅的合约名称 */
- protected String getContract() { return contract; }
-
- /**
- * 从策略服务获取用户 ID,用于私有频道订阅的 payload[0]。
- *
- * @return 用户 ID 字符串,获取失败返回空字符串
- */
- private String buildUid() {
- return gridTradeService != null && gridTradeService.getUserId() != null
- ? String.valueOf(gridTradeService.getUserId()) : "";
- }
-
- /**
- * 构建认证请求 JSON。
- * 包含 id、time、channel、event、payload[userId, contract]、auth 字段。
- *
- * @param event 事件类型("subscribe" / "unsubscribe")
- * @param uid 认证用户 ID
- * @param timeSec unix 时间戳(秒)
- * @return 完整的认证请求 JSONObject
- */
- private JSONObject buildAuthRequest(String event, String uid, long timeSec) {
- JSONObject msg = new JSONObject();
- msg.put("id", timeSec * 1000000 + (System.currentTimeMillis() % 1000));
- msg.put("time", timeSec);
- msg.put("channel", channelName);
- msg.put("event", event);
- JSONArray payload = new JSONArray();
- payload.add(uid);
- payload.add(contract);
- msg.put("payload", payload);
- JSONObject auth = new JSONObject();
- auth.put("method", "api_key");
- auth.put("KEY", apiKey);
- auth.put("SIGN", hs512Sign(event, timeSec));
- msg.put("auth", auth);
- return msg;
- }
-
- /**
- * HMAC-SHA512 签名计算。
- *
- * <h3>签名算法</h3>
- * <pre>
- * message = "channel={channelName}&event={event}&time={timeSec}"
- * SIGN = Hex(HmacSHA512(apiSecret(UTF-8), message(UTF-8)))
- * </pre>
- *
- * <h3>错误处理</h3>
- * 签名计算失败时返回空字符串(日志记录错误),不抛异常,
- * 避免阻塞 WebSocket 回调线程。
- *
- * @param event 事件类型
- * @param timeSec unix 时间戳(秒)
- * @return 十六进制签名字符串,失败返回 ""
- */
- protected String hs512Sign(String event, long timeSec) {
- try {
- String message = "channel=" + channelName + "&event=" + event + "&time=" + timeSec;
- Mac mac = Mac.getInstance("HmacSHA512");
- SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
- mac.init(spec);
- byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
- StringBuilder hex = new StringBuilder(hash.length * 2);
- for (byte b : hash) {
- hex.append(HEX_ARRAY[(b >> 4) & 0xF]);
- hex.append(HEX_ARRAY[b & 0xF]);
- }
- return hex.toString();
- } catch (Exception e) {
- log.error("[{}] 签名计算失败", channelName, e);
- return "";
- }
- }
-
- @Override
- public boolean isSubscribed() { return subscribed; }
-
- @Override
- public void setSubscribed(boolean subscribed) { this.subscribed = subscribed; }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/GateChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/GateChannelHandler.java
deleted file mode 100644
index 8d4a0c3..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/GateChannelHandler.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.xcong.excoin.modules.gateApi.wsHandler;
-
-import com.alibaba.fastjson.JSONObject;
-import org.java_websocket.client.WebSocketClient;
-
-/**
- * WebSocket 频道处理器接口。
- *
- * <p>每个 Gate 频道对应一个实现类。新增频道只需实现此接口,
- * 然后通过 {@code GateKlineWebSocketClient.addChannelHandler()} 注册即可。
- *
- * <h3>实现类</h3>
- * <ul>
- * <li>{@code CandlestickChannelHandler} — 公开频道,K 线数据</li>
- * <li>{@code AbstractPrivateChannelHandler} — 私有频道抽象基类(签名+认证)</li>
- * <li>{@code PositionsChannelHandler} — 私有频道,仓位更新</li>
- * <li>{@code PositionClosesChannelHandler} — 私有频道,平仓推送</li>
- * </ul>
- *
- * <h3>路由机制</h3>
- * {@code handleMessage()} 返回 {@code true} 表示消息已被该 handler 处理,
- * 路由循环会停止遍历。返回 {@code false} 表示不匹配(channel 名不相等)。
- *
- * @author Administrator
- */
-public interface GateChannelHandler {
-
- /** 频道名称,如 {@code "futures.candlesticks"} */
- String getChannelName();
-
- /** 发送订阅请求 */
- void subscribe(WebSocketClient ws);
-
- /** 发送取消订阅请求 */
- void unsubscribe(WebSocketClient ws);
-
- /**
- * 处理频道推送消息。
- *
- * @param response WebSocket 推送的完整 JSON
- * @return true 表示已处理(循环停止),false 表示频道不匹配(继续遍历下一个 handler)
- */
- boolean handleMessage(JSONObject response);
-
- /** 是否已收到订阅成功确认 */
- boolean isSubscribed();
-
- /** 标记订阅已确认 */
- void setSubscribed(boolean subscribed);
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/AutoOrdersChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/AutoOrdersChannelHandler.java
deleted file mode 100644
index 972d2b6..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/AutoOrdersChannelHandler.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.xcong.excoin.modules.gateApi.wsHandler.handler;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.gateApi.GateGridTradeService;
-import com.xcong.excoin.modules.gateApi.wsHandler.AbstractPrivateChannelHandler;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-/**
- * 自动订单频道处理器(futures.autoorders)。
- *
- * <h3>数据用途</h3>
- * 订阅用户自动订单(条件单)更新推送。当条件单状态变更(已触发、已取消等)时,
- * 获得订单 ID、状态、触发信息等,可用于跟踪条件单生命周期。
- *
- * <h3>与 futures.orders 的区别</h3>
- * {@code futures.autoorders} 推送的是条件订单(价格触发单)的状态变更,
- * 而 {@code futures.orders} 推送的是普通订单(市价单、限价单)的成交/取消。
- *
- * <h3>关键推送字段</h3>
- * <ul>
- * <li>id:自动订单 ID</li>
- * <li>status:订单状态(open / finished / cancelled)</li>
- * <li>reason:变更原因</li>
- * <li>trigger.price / trigger.rule:触发条件</li>
- * <li>order_type:止盈/止损类型</li>
- * <li>trade_id:关联交易 ID</li>
- * </ul>
- *
- * <h3>注意</h3>
- * 此频道的 payload 仅需 [contract],无需 userId(与 futures.orders 不同)。
- *
- * @author Administrator
- */
-@Slf4j
-public class AutoOrdersChannelHandler extends AbstractPrivateChannelHandler {
-
- private static final String CHANNEL_NAME = "futures.autoorders";
-
- public AutoOrdersChannelHandler(String apiKey, String apiSecret,
- String contract,
- GateGridTradeService gridTradeService) {
- super(CHANNEL_NAME, apiKey, apiSecret, contract, gridTradeService);
- }
-
- /**
- * 发送订阅请求,payload 仅需 [contract](此频道不需要 userId)。
- */
- @Override
- public void subscribe(WebSocketClient ws) {
- long timeSec = System.currentTimeMillis() / 1000;
- JSONObject msg = new JSONObject();
- msg.put("id", timeSec * 1000000 + (System.currentTimeMillis() % 1000));
- msg.put("time", timeSec);
- msg.put("channel", CHANNEL_NAME);
- msg.put("event", "subscribe");
- JSONArray payload = new JSONArray();
- payload.add(getContract());
- msg.put("payload", payload);
- JSONObject auth = new JSONObject();
- auth.put("method", "api_key");
- auth.put("KEY", apiKey);
- auth.put("SIGN", hs512Sign("subscribe", timeSec));
- msg.put("auth", auth);
- ws.send(msg.toJSONString());
- log.info("[{}] 订阅成功, 合约:{}", CHANNEL_NAME, getContract());
- }
-
- @Override
- public boolean handleMessage(JSONObject response) {
- if (!CHANNEL_NAME.equals(response.getString("channel"))) {
- return false;
- }
- try {
- JSONArray resultArray = response.getJSONArray("result");
- if (resultArray == null || resultArray.isEmpty()) {
- return true;
- }
- for (int i = 0; i < resultArray.size(); i++) {
- JSONObject autoOrder = resultArray.getJSONObject(i);
- JSONObject initial = autoOrder.getJSONObject("initial");
- if (initial == null) {
- continue;
- }
- if (!getContract().equals(initial.getString("contract"))) {
- continue;
- }
- String orderId = String.valueOf(autoOrder.getLong("id"));
- String status = autoOrder.getString("status");
- String reason = autoOrder.getString("reason");
- String orderType = autoOrder.getString("order_type");
- String tradeId = autoOrder.getString("trade_id");
- JSONObject trigger = autoOrder.getJSONObject("trigger");
- String triggerPrice = trigger != null ? trigger.getString("price") : null;
- log.info("[{}] 自动订单更新, id:{}, status:{}, reason:{}, order_type:{}, trigger_price:{}, trade_id:{}",
- CHANNEL_NAME, orderId, status, reason, orderType, triggerPrice,
- tradeId);
- if (getGridTradeService() != null) {
- getGridTradeService().onAutoOrder(orderId, status, reason, orderType, tradeId);
- }
- }
- } catch (Exception e) {
- log.error("[{}] 处理数据失败", CHANNEL_NAME, e);
- }
- return true;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java
deleted file mode 100644
index 64354f5..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/CandlestickChannelHandler.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.xcong.excoin.modules.gateApi.wsHandler.handler;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.blackchain.service.DateUtil;
-import com.xcong.excoin.modules.gateApi.GateGridTradeService;
-import com.xcong.excoin.modules.gateApi.wsHandler.GateChannelHandler;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-import java.math.BigDecimal;
-
-/**
- * K 线频道处理器(futures.candlesticks)— 策略的唯一价格时间驱动源。
- *
- * <h3>定位</h3>
- * 订阅 1 分钟 K 线实时推送,每收到一根 K 线(不等待完结)即触发
- * {@link GateGridTradeService#onKline(BigDecimal)},由策略引擎决定是否开仓/止盈。
- *
- * <h3>订阅格式</h3>
- * 公开频道,无需认证签名。payload: {@code ["1m", contract]}。
- *
- * @author Administrator
- */
-@Slf4j
-public class CandlestickChannelHandler implements GateChannelHandler {
-
- private static final String CHANNEL_NAME = "futures.candlesticks";
- /** K 线周期,固定 1 分钟 */
- private static final String INTERVAL = "1m";
-
- /** 合约名称 */
- private final String contract;
- /** 网格交易服务,接收 K 线回调 */
- private final GateGridTradeService gridTradeService;
-
- private volatile boolean subscribed = false;
-
- /**
- * @param contract 合约名称(如 ETH_USDT)
- * @param gridTradeService 网格交易策略服务实例
- */
- public CandlestickChannelHandler(String contract, GateGridTradeService gridTradeService) {
- this.contract = contract;
- this.gridTradeService = gridTradeService;
- }
-
- /** @return 频道名称 "futures.candlesticks" */
- @Override
- public String getChannelName() { return CHANNEL_NAME; }
-
- /**
- * 发送 K 线频道订阅请求(公开频道,无需签名)。
- *
- * <h3>订阅格式</h3>
- * <pre>
- * {
- * "time": <unix时间戳(秒)>,
- * "channel": "futures.candlesticks",
- * "event": "subscribe",
- * "payload": ["1m", "{contract}"]
- * }
- * </pre>
- */
- @Override
- public void subscribe(WebSocketClient ws) {
- JSONObject msg = new JSONObject();
- msg.put("time", System.currentTimeMillis() / 1000);
- msg.put("channel", CHANNEL_NAME);
- msg.put("event", "subscribe");
- JSONArray payload = new JSONArray();
- payload.add(INTERVAL);
- payload.add(contract);
- msg.put("payload", payload);
- ws.send(msg.toJSONString());
- log.info("[{}] 订阅成功, 合约:{}, 周期:{}", CHANNEL_NAME, contract, INTERVAL);
- }
-
- /**
- * 发送 K 线频道取消订阅请求。
- */
- @Override
- public void unsubscribe(WebSocketClient ws) {
- JSONObject msg = new JSONObject();
- msg.put("time", System.currentTimeMillis() / 1000);
- msg.put("channel", CHANNEL_NAME);
- msg.put("event", "unsubscribe");
- JSONArray payload = new JSONArray();
- payload.add(INTERVAL);
- payload.add(contract);
- msg.put("payload", payload);
- ws.send(msg.toJSONString());
- log.info("[{}] 取消订阅成功", CHANNEL_NAME);
- }
-
- /**
- * 处理 K 线推送消息。
- *
- * <h3>数据提取</h3>
- * result[0] 中提取:
- * <ul>
- * <li>c(close):收盘价 → 传给 gridTradeService.onKline()</li>
- * <li>n(name):烛线名称(如 "1m_ETH_USDT")</li>
- * <li>t(time):烛线起始时间戳</li>
- * <li>w(window_close):烛线是否完结(仅日志输出,不做门控)</li>
- * </ul>
- *
- * <h3>注意</h3>
- * 不判断 w(已完结)——策略需要 tick 级实时响应价格变动,
- * 而非等 1 分钟烛线完结后才行动。
- *
- * @param response WebSocket 推送的完整 JSON
- * @return true 表示已处理(匹配成功)
- */
- @Override
- public boolean handleMessage(JSONObject response) {
- if (!CHANNEL_NAME.equals(response.getString("channel"))) {
- return false;
- }
- try {
- JSONArray resultArray = response.getJSONArray("result");
- if (resultArray == null || resultArray.isEmpty()) { log.warn("[{}] 数据为空", CHANNEL_NAME); return true; }
- JSONObject data = resultArray.getJSONObject(0);
- BigDecimal closePx = new BigDecimal(data.getString("c"));
-
- if (gridTradeService != null) {
- gridTradeService.onKline(closePx);
- }
- } catch (Exception e) { log.error("[{}] 处理数据失败", CHANNEL_NAME, e); }
- return true;
- }
-
- @Override
- public boolean isSubscribed() { return subscribed; }
-
- @Override
- public void setSubscribed(boolean subscribed) { this.subscribed = subscribed; }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java
deleted file mode 100644
index c58ea39..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionClosesChannelHandler.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.xcong.excoin.modules.gateApi.wsHandler.handler;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.gateApi.GateGridTradeService;
-import com.xcong.excoin.modules.gateApi.wsHandler.AbstractPrivateChannelHandler;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-
-/**
- * 平仓频道处理器(futures.position_closes),接收平仓盈亏推送并回调 {@link GateGridTradeService#onPositionClose}。
- *
- * @author Administrator
- */
-@Slf4j
-public class PositionClosesChannelHandler extends AbstractPrivateChannelHandler {
-
- private static final String CHANNEL_NAME = "futures.position_closes";
-
- /**
- * @param apiKey Gate API v4 密钥,用于签名认证
- * @param apiSecret Gate API v4 签名密钥
- * @param contract 合约名称(如 ETH_USDT)
- * @param gridTradeService 网格交易策略服务实例
- */
- public PositionClosesChannelHandler(String apiKey, String apiSecret,
- String contract,
- GateGridTradeService gridTradeService) {
- super(CHANNEL_NAME, apiKey, apiSecret, contract, gridTradeService);
- }
-
- /**
- * 处理平仓推送消息。
- *
- * <h3>数据提取</h3>
- * result 数组中每个元素包含:
- * <ul>
- * <li>contract:合约名称</li>
- * <li>side:平仓方向("long" / "short")</li>
- * <li>pnl:本次平仓的盈亏金额(字符串格式,如 "+0.2" / "-0.1")</li>
- * </ul>
- *
- * <h3>数据处理</h3>
- * 按合约名称过滤 → 提取 pnl 和 side → 调用 gridTradeService.onPositionClose() 累加盈亏。
- * pnl 来自服务端,不受本地计算误差影响。
- *
- * @param response WebSocket 推送的完整 JSON
- * @return true 表示已处理(匹配成功)
- */
- @Override
- public boolean handleMessage(JSONObject response) {
- if (!CHANNEL_NAME.equals(response.getString("channel"))) {
- return false;
- }
- try {
- JSONArray resultArray = response.getJSONArray("result");
- if (resultArray == null || resultArray.isEmpty()) {
- return true;
- }
- for (int i = 0; i < resultArray.size(); i++) {
- JSONObject item = resultArray.getJSONObject(i);
- if (!getContract().equals(item.getString("contract"))) {
- continue;
- }
- BigDecimal pnl = new BigDecimal(item.getString("pnl"));
- String side = item.getString("side");
- log.info("[{}] 平仓更新, 方向:{}, 盈亏:{}", CHANNEL_NAME, side, pnl);
- if (getGridTradeService() != null) {
- getGridTradeService().onPositionClose(getContract(), side, pnl);
- }
- }
- } catch (Exception e) { log.error("[{}] 处理数据失败", CHANNEL_NAME, e); }
- return true;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java b/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java
deleted file mode 100644
index 9608782..0000000
--- a/src/main/java/com/xcong/excoin/modules/gateApi/wsHandler/handler/PositionsChannelHandler.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.xcong.excoin.modules.gateApi.wsHandler.handler;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.gateApi.GateGridTradeService;
-import com.xcong.excoin.modules.gateApi.wsHandler.AbstractPrivateChannelHandler;
-import io.gate.gateapi.models.Position;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-
-/**
- * 仓位频道处理器(futures.positions),接收仓位更新推送并回调 {@link GateGridTradeService#onPositionUpdate}。
- *
- * @author Administrator
- */
-@Slf4j
-public class PositionsChannelHandler extends AbstractPrivateChannelHandler {
-
- private static final String CHANNEL_NAME = "futures.positions";
-
- /**
- * @param apiKey Gate API v4 密钥,用于签名认证
- * @param apiSecret Gate API v4 签名密钥
- * @param contract 合约名称(如 ETH_USDT)
- * @param gridTradeService 网格交易策略服务实例
- */
- public PositionsChannelHandler(String apiKey, String apiSecret,
- String contract,
- GateGridTradeService gridTradeService) {
- super(CHANNEL_NAME, apiKey, apiSecret, contract, gridTradeService);
- }
-
- @Override
- public boolean handleMessage(JSONObject response) {
- if (!CHANNEL_NAME.equals(response.getString("channel"))) {
- return false;
- }
- try {
- JSONArray resultArray = response.getJSONArray("result");
- if (resultArray == null || resultArray.isEmpty()) {
- return true;
- }
- for (int i = 0; i < resultArray.size(); i++) {
- JSONObject pos = resultArray.getJSONObject(i);
- if (!getContract().equals(pos.getString("contract"))) {
- continue;
- }
- String modeStr = pos.getString("mode");
- Position.ModeEnum mode = Position.ModeEnum.fromValue(modeStr);
- BigDecimal size = new BigDecimal(pos.getString("size"));
- BigDecimal entryPrice = new BigDecimal(pos.getString("entry_price"));
- log.info("[{}] 持仓更新, 合约:{}, 模式:{}, 数量:{}, 入场价:{}, 全仓杠杆上限:{}, 历史盈亏:{}, 历史点卡:{}, 最近平仓盈亏:{}, 杠杆:{}, 最大杠杆:{}, 爆仓价:{}, 维持保证金率:{}, 保证金:{}, 已实现盈亏:{}, 点卡已实现盈亏:{}, 风险限额:{}, 时间:{}, 时间ms:{}, 用户:{}, 更新ID:{}",
- CHANNEL_NAME, pos.getString("contract"), modeStr, size, entryPrice,
- pos.get("cross_leverage_limit"), pos.get("history_pnl"), pos.get("history_point"),
- pos.get("last_close_pnl"), pos.get("leverage"), pos.get("leverage_max"),
- pos.get("liq_price"), pos.get("maintenance_rate"), pos.get("margin"),
- pos.get("realised_pnl"), pos.get("realised_point"), pos.get("risk_limit"),
- pos.get("time"), pos.get("time_ms"), pos.get("user"), pos.get("update_id"));
- if (getGridTradeService() != null) {
- getGridTradeService().onPositionUpdate(getContract(), mode, size, entryPrice);
- }
- }
- } catch (Exception e) { log.error("[{}] 处理数据失败", CHANNEL_NAME, e); }
- return true;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java b/src/main/java/com/xcong/excoin/modules/okxApi/GridElement.java
similarity index 96%
rename from src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
rename to src/main/java/com/xcong/excoin/modules/okxApi/GridElement.java
index dbcfe19..09e893c 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GridElement.java
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/GridElement.java
@@ -1,4 +1,4 @@
-package com.xcong.excoin.modules.gateApi;
+package com.xcong.excoin.modules.okxApi;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -48,7 +48,6 @@
* <h3>何时填充 TraderParam</h3>
* 初始化时 {@code updateGridElements()} 为每个元素预填充 longTraderParam 和 shortTraderParam
* (含 direction/entryPrice/takeProfitPrice/quantity),订单ID字段在挂单成功后由
- * {@link GateGridTradeService} 的 4 个辅助方法写入。
*
* <h3>使用示例</h3>
* <pre>
@@ -92,17 +91,17 @@
/** 空仓止损订单 ID */
private String shortStopLossOrderId;
- /** 全局 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
+ /** 全局 ID 索引,由 { GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
private static final Map<Integer, GridElement> INDEX = new ConcurrentHashMap<>();
- /** 全局价格索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
+ /** 全局价格索引,由 { GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
private static final Map<BigDecimal, GridElement> PRICE_INDEX = new ConcurrentHashMap<>();
- /** 全局多仓订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
+ /** 全局多仓订单 ID 索引,由 { GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
private static final Map<String, GridElement> LONG_ORDER_ID_INDEX = new ConcurrentHashMap<>();
- /** 全局空仓订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
+ /** 全局空仓订单 ID 索引,由 { GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
private static final Map<String, GridElement> SHORT_ORDER_ID_INDEX = new ConcurrentHashMap<>();
- /** 全局多仓止盈订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
+ /** 全局多仓止盈订单 ID 索引,由 { GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
private static final Map<String, GridElement> LONG_TP_ORDER_ID_INDEX = new ConcurrentHashMap<>();
- /** 全局空仓止盈订单 ID 索引,由 {@link GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
+ /** 全局空仓止盈订单 ID 索引,由 { GateConfig#setGridElements(List)} 触发重建,O(1) 查找 */
private static final Map<String, GridElement> SHORT_TP_ORDER_ID_INDEX = new ConcurrentHashMap<>();
/** 全局多仓止损订单 ID 索引 */
private static final Map<String, GridElement> LONG_SL_ORDER_ID_INDEX = new ConcurrentHashMap<>();
@@ -191,7 +190,7 @@
/**
* 从列表中重建全局 ID 索引和价格索引。
- * 由 {@link GateConfig#setGridElements(List)} 在每次列表变更后调用。
+ * 由 { GateConfig#setGridElements(List)} 在每次列表变更后调用。
*/
public static void rebuildIndex(List<GridElement> elements) {
INDEX.clear();
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java b/src/main/java/com/xcong/excoin/modules/okxApi/OkxConfig.java
similarity index 75%
rename from src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
rename to src/main/java/com/xcong/excoin/modules/okxApi/OkxConfig.java
index 81e2209..4b6e74b 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/GateConfig.java
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/OkxConfig.java
@@ -1,21 +1,31 @@
-package com.xcong.excoin.modules.gateApi;
+package com.xcong.excoin.modules.okxApi;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
- * Gate 交易模块全局配置,策略的唯一参数入口。
+ * OKX 交易模块全局配置,策略的唯一参数入口。
*
* <h3>定位</h3>
* 通过 Builder 模式将所有运行参数集中管理,避免策略参数散落在多个类中。
* 运行时动态参数(step、gridElements、baseLongTraderParam、baseShortTraderParam)
- * 由 {@link GateGridTradeService} 在策略执行过程中写入。
+ * 由 OkxGridTradeService 在策略执行过程中写入。
+ *
+ * <h3>与 GateConfig 的主要差异</h3>
+ * <ul>
+ * <li>新增 {@code passphrase} 字段 — OKX API 认证需要 passphrase</li>
+ * <li>REST 基础路径:生产环境 {@code https://www.okx.com},测试环境 {@code https://www.okx.cab}</li>
+ * <li>WebSocket 分为 public/private 两个地址</li>
+ * <li>合约格式:{@code ETH-USDT-SWAP}(OKX 用短横线分隔)</li>
+ * <li>持仓模式默认值:{@code long_short_mode}(OKX)替代 {@code dual}(Gate)</li>
+ * <li>数据模型(GridElement、TraderParam)复用 gateApi 包中的定义</li>
+ * </ul>
*
* <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>apiKey, apiSecret, passphrase</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>
@@ -25,26 +35,17 @@
* <tr><td>运行时</td><td>step, gridElements, baseLongTraderParam, baseShortTraderParam</td><td>由策略动态填充</td></tr>
* </table>
*
- * <h3>priceScale 说明</h3>
- * 价格精度表示交易所允许的最小价格单位的小数位数:
- * <ul>
- * <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>gridElements 生命周期</h3>
* <ol>
* <li>项目启动时初始化为空 ArrayList</li>
- * <li>{@code tryGenerateQueues()} 中通过 {@code updateGridElements()} 填充,同时触发
+ * <li>队列生成逻辑中通过 {@code updateGridElements()} 填充,同时触发
* {@link GridElement#rebuildIndex(List)} 建立全局 O(1) 索引</li>
* <li>每次挂单/止盈操作后通过 {@link GridElement#refreshIndices()} 更新索引</li>
* </ol>
*
* @author Administrator
*/
-public class GateConfig {
+public class OkxConfig {
/**
* 未实现盈亏(unrealizedPnl)计价模式。
@@ -57,22 +58,32 @@
public enum PnLPriceMode {
/** 按最新成交价计算未实现盈亏 */
LAST_PRICE,
- /** 按标记价格计算未实现盈亏,需通过 {@link GateGridTradeService#setMarkPrice(BigDecimal)} 注入 */
+ /** 按标记价格计算未实现盈亏,需通过外部注入 */
MARK_PRICE
}
- /** Gate API v4 密钥 */
+ // ==================== 认证信息 ====================
+
+ /** OKX API v5 密钥 */
private final String apiKey;
- /** Gate API v4 签名密钥 */
+ /** OKX API v5 签名密钥 */
private final String apiSecret;
- /** 合约名称(如 XAU_USDT) */
+ /** OKX API v5 口令密码(passphrase),创建 API Key 时设置 */
+ private final String passphrase;
+
+ // ==================== 交易标的 ====================
+
+ /** 合约名称(如 ETH-USDT-SWAP,注意 OKX 使用短横线分隔) */
private final String contract;
/** 杠杆倍数 */
private final String leverage;
/** 保证金模式(cross / isolated) */
private final String marginMode;
- /** 持仓模式(single / dual / dual_plus) */
+ /** 持仓模式(long_short_mode=双向 / net_mode=单向) */
private final String positionMode;
+
+ // ==================== 策略参数 ====================
+
/** 网格间距比例(如 0.0035 表示 0.35%) */
private final BigDecimal gridRate;
/** 整体止盈阈值(USDT) */
@@ -93,9 +104,9 @@
private final int gridQueueSize;
/** 保证金占初始本金比例上限 */
private final BigDecimal marginRatioLimit;
- /** 合约乘数(单张合约代表的基础资产数量,如 BTC_USDT=0.001, ETH_USDT=0.01) */
+ /** 合约乘数(单张合约代表的基础资产数量,如 ETH-USDT-SWAP=0.01) */
private final BigDecimal contractMultiplier;
- /** 价格精度(交易所价格的最小小数位数,如 XAU_USDT=1 表示 0.1 精度,ETH_USDT=2 表示 0.01 精度) */
+ /** 价格精度(交易所价格的最小小数位数,如 1 表示 0.1 精度,2 表示 0.01 精度) */
private final int priceScale;
/** 未实现盈亏计价模式:最新价 / 标记价格 */
private final PnLPriceMode unrealizedPnlPriceMode;
@@ -103,18 +114,24 @@
private final int maxPositionSize;
/** 策略重启跨度阈值:多空两边止盈触发数量均达到此值后触发重启,0=禁用 */
private final int restartGridSpan;
+
+ // ==================== 运行时参数 ====================
+
/** 网格绝对步长(shortBaseEntryPrice × gridRate),运行时由队列生成逻辑设置 */
private BigDecimal step;
/** 网格元素列表,由队列初始化时同步填充,包含完整的多空仓挂单状态 */
private volatile List<GridElement> gridElements = new ArrayList<>();
- /** 基座多头挂单参数,在基座成交后由 tryGenerateQueues 填充 */
+ /** 基座多头挂单参数,在基座成交后由队列生成逻辑填充 */
private TraderParam baseLongTraderParam;
- /** 基座空头挂单参数,在基座成交后由 tryGenerateQueues 填充 */
+ /** 基座空头挂单参数,在基座成交后由队列生成逻辑填充 */
private TraderParam baseShortTraderParam;
- private GateConfig(Builder builder) {
+ // ==================== 构造器 ====================
+
+ private OkxConfig(Builder builder) {
this.apiKey = builder.apiKey;
this.apiSecret = builder.apiSecret;
+ this.passphrase = builder.passphrase;
this.contract = builder.contract;
this.leverage = builder.leverage;
this.marginMode = builder.marginMode;
@@ -139,41 +156,54 @@
// ==================== REST/WS 地址 ====================
/**
- * 根据环境返回 REST API 基础路径。
+ * 返回 REST API 基础路径。
+ * <p>实盘和模拟盘使用相同的 REST 地址,通过 {@code x-simulated-trading: 1} 请求头区分。
* <ul>
- * <li>测试网: {@code https://api-testnet.gateapi.io/api/v4}</li>
- * <li>生产网: {@code https://api.gateio.ws/api/v4}</li>
+ * <li>生产网/测试网: {@code https://openapi.okx.com}</li>
* </ul>
*/
public String getRestBasePath() {
- return isProduction
- ? "https://api.gateio.ws/api/v4"
- : "https://api-testnet.gateapi.io/api/v4";
+ return "https://openapi.okx.com";
}
/**
- * 根据环境返回 WebSocket 地址。
+ * 根据环境返回 WebSocket 公开频道地址。
* <ul>
- * <li>测试网: {@code wss://ws-testnet.gate.com/v4/ws/futures/usdt}</li>
- * <li>生产网: {@code wss://fx-ws.gateio.ws/v4/ws/usdt}</li>
+ * <li>测试网: {@code wss://wspap.okx.com:8443/ws/v5/public}</li>
+ * <li>生产网: {@code wss://ws.okx.com:8443/ws/v5/public}</li>
* </ul>
*/
- public String getWsUrl() {
+ public String getWsPublicUrl() {
return isProduction
- ? "wss://fx-ws.gateio.ws/v4/ws/usdt"
- : "wss://ws-testnet.gate.com/v4/ws/futures/usdt";
+ ? "wss://ws.okx.com:8443/ws/v5/public"
+ : "wss://wspap.okx.com:8443/ws/v5/public";
+ }
+
+ /**
+ * 根据环境返回 WebSocket 私有频道地址。
+ * <ul>
+ * <li>测试网: {@code wss://wspap.okx.com:8443/ws/v5/private}</li>
+ * <li>生产网: {@code wss://ws.okx.com:8443/ws/v5/private}</li>
+ * </ul>
+ */
+ public String getWsPrivateUrl() {
+ return isProduction
+ ? "wss://ws.okx.com:8443/ws/v5/private"
+ : "wss://wspap.okx.com:8443/ws/v5/private";
}
// ==================== 认证信息 ====================
- /** @return Gate API v4 密钥 */
+ /** @return OKX API v5 密钥 */
public String getApiKey() { return apiKey; }
- /** @return Gate API v4 签名密钥,用于 HMAC-SHA512 签名 */
+ /** @return OKX API v5 签名密钥,用于 HMAC-SHA256 签名 */
public String getApiSecret() { return apiSecret; }
+ /** @return OKX API v5 口令密码(passphrase) */
+ public String getPassphrase() { return passphrase; }
// ==================== 交易标的 ====================
- /** @return 合约名称(如 ETH_USDT、XAU_USDT) */
+ /** @return 合约名称(如 ETH-USDT-SWAP,OKX 使用短横线分隔) */
public String getContract() { return contract; }
/** @return 杠杆倍数(如 "100" 表示 100x) */
public String getLeverage() { return leverage; }
@@ -182,7 +212,7 @@
/** @return 保证金模式(cross=全仓 / isolated=逐仓) */
public String getMarginMode() { return marginMode; }
- /** @return 持仓模式(single=单向 / dual=双向 / dual_plus) */
+ /** @return 持仓模式(long_short_mode=双向 / net_mode=单向) */
public String getPositionMode() { return positionMode; }
// ==================== 策略参数 ====================
@@ -211,7 +241,7 @@
// ==================== 盈亏计算 ====================
- /** @return 合约乘数(单张合约代表的基础资产数量,如 ETH_USDT=0.01) */
+ /** @return 合约乘数(单张合约代表的基础资产数量,如 ETH-USDT-SWAP=0.01) */
public BigDecimal getContractMultiplier() { return contractMultiplier; }
/** @return 价格精度(交易所价格的小数位数,如 1=0.1精度,2=0.01精度),用于价格四舍五入 */
public int getPriceScale() { return priceScale; }
@@ -226,7 +256,7 @@
/** @return 网格绝对步长(shortBaseEntryPrice × gridRate),运行时设置 */
public BigDecimal getStep() { return step; }
- /** 设置网格绝对步长(由 generateShortQueue 在运行时计算并注入) */
+ /** 设置网格绝对步长(由队列生成逻辑在运行时计算并注入) */
public void setStep(BigDecimal step) { this.step = step; }
// ==================== 网格元素列表 ====================
@@ -243,12 +273,12 @@
/** @return 基座多头挂单参数 */
public TraderParam getBaseLongTraderParam() { return baseLongTraderParam; }
- /** 设置基座多头挂单参数(由 tryGenerateQueues 在基座成交后填充) */
+ /** 设置基座多头挂单参数(由队列生成逻辑在基座成交后填充) */
public void setBaseLongTraderParam(TraderParam baseLongTraderParam) { this.baseLongTraderParam = baseLongTraderParam; }
/** @return 基座空头挂单参数 */
public TraderParam getBaseShortTraderParam() { return baseShortTraderParam; }
- /** 设置基座空头挂单参数(由 tryGenerateQueues 在基座成交后填充) */
+ /** 设置基座空头挂单参数(由队列生成逻辑在基座成交后填充) */
public void setBaseShortTraderParam(TraderParam baseShortTraderParam) { this.baseShortTraderParam = baseShortTraderParam; }
// ==================== 环境 ====================
@@ -256,33 +286,37 @@
/** @return 是否为生产环境(true=实盘生产网 / false=模拟盘测试网) */
public boolean isProduction() { return isProduction; }
+ // ==================== Builder ====================
+
public static Builder builder() {
return new Builder();
}
/**
- * GateConfig 的流式构造器,提供合理的默认值。
+ * OkxConfig 的流式构造器,提供合理的默认值。
*
* <h3>必填项</h3>
- * {@code apiKey} 和 {@code apiSecret} 必须设置,其余参数均有默认值。
+ * {@code apiKey}、{@code apiSecret}、{@code passphrase} 必须设置,其余参数均有默认值。
*
* <h3>默认值</h3>
- * BTC_USDT / 10x / cross(全仓) / dual(双向) / gridRate=0.35% /
+ * ETH-USDT-SWAP / 10x / cross(全仓) / long_short_mode(双向) / gridRate=0.35% /
* overallTp=0.5 / maxLoss=7.5 / quantity=1 / isProduction=false
*/
public static class Builder {
- /** Gate API v4 密钥(必填) */
+ /** OKX API v5 密钥(必填) */
private String apiKey;
- /** Gate API v4 签名密钥(必填) */
+ /** OKX API v5 签名密钥(必填) */
private String apiSecret;
- /** 合约名称,默认 BTC_USDT */
- private String contract = "BTC_USDT";
+ /** OKX API v5 口令密码(必填) */
+ private String passphrase;
+ /** 合约名称,默认 ETH-USDT-SWAP */
+ private String contract = "ETH-USDT-SWAP";
/** 杠杆倍数,默认 "10" */
private String leverage = "10";
/** 保证金模式,默认 "cross"(全仓) */
private String marginMode = "cross";
- /** 持仓模式,默认 "dual"(双向) */
- private String positionMode = "dual";
+ /** 持仓模式,默认 "long_short_mode"(双向) */
+ private String positionMode = "long_short_mode";
/** 网格间距比例,默认 0.0035(0.35%) */
private BigDecimal gridRate = new BigDecimal("0.0035");
/** 整体止盈阈值(USDT),默认 0.5 */
@@ -299,14 +333,14 @@
private boolean isProduction = false;
/** 补仓最大重试次数,默认 3 */
private int reopenMaxRetries = 3;
- /** 网格队列容量,默认 50 */
+ /** 网格队列容量,默认 300 */
private int gridQueueSize = 300;
/** 保证金占初始本金比例上限,默认 0.2(20%) */
private BigDecimal marginRatioLimit = new BigDecimal("0.2");
- /** 合约乘数,默认 0.001 */
- private BigDecimal contractMultiplier = new BigDecimal("0.001");
- /** 价格精度(交易所价格的小数位数),默认 1(0.1 精度) */
- private int priceScale = 1;
+ /** 合约乘数,默认 0.01(ETH-USDT-SWAP=0.01) */
+ private BigDecimal contractMultiplier = new BigDecimal("0.01");
+ /** 价格精度(交易所价格的小数位数),默认 2(0.01 精度,适配 ETH) */
+ private int priceScale = 2;
/** 未实现盈亏计价模式,默认 LAST_PRICE(最新成交价) */
private PnLPriceMode unrealizedPnlPriceMode = PnLPriceMode.LAST_PRICE;
/** 最大持仓张数(单方向),默认 0=不限制 */
@@ -318,13 +352,15 @@
public Builder apiKey(String apiKey) { this.apiKey = apiKey; return this; }
/** 设置 API Secret */
public Builder apiSecret(String apiSecret) { this.apiSecret = apiSecret; return this; }
- /** 设置合约名称 */
+ /** 设置 API Passphrase */
+ public Builder passphrase(String passphrase) { this.passphrase = passphrase; return this; }
+ /** 设置合约名称(如 ETH-USDT-SWAP) */
public Builder contract(String contract) { this.contract = contract; return this; }
/** 设置杠杆倍数 */
public Builder leverage(String leverage) { this.leverage = leverage; return this; }
/** 设置保证金模式(cross=全仓 / isolated=逐仓) */
public Builder marginMode(String marginMode) { this.marginMode = marginMode; return this; }
- /** 设置持仓模式(single=单向 / dual=双向) */
+ /** 设置持仓模式(long_short_mode=双向 / net_mode=单向) */
public Builder positionMode(String positionMode) { this.positionMode = positionMode; return this; }
/** 设置网格间距比例 */
public Builder gridRate(BigDecimal gridRate) { this.gridRate = gridRate; return this; }
@@ -357,8 +393,8 @@
/** 设置策略重启跨度阈值:多空两边止盈触发数均达到此值后触发重启,0=禁用 */
public Builder restartGridSpan(int restartGridSpan) { this.restartGridSpan = restartGridSpan; return this; }
- public GateConfig build() {
- return new GateConfig(this);
+ public OkxConfig build() {
+ return new OkxConfig(this);
}
}
}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/OkxGridTradeService.java b/src/main/java/com/xcong/excoin/modules/okxApi/OkxGridTradeService.java
new file mode 100644
index 0000000..6db2ab3
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/OkxGridTradeService.java
@@ -0,0 +1,1224 @@
+package com.xcong.excoin.modules.okxApi;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.utils.dingtalk.DingTalkUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+
+/**
+ * OKX 网格交易策略引擎 — 多空对冲网格。
+ *
+ * <h3>策略原理</h3>
+ * 以空仓基底入场价(shortBaseEntryPrice)为价格基准,向上/向下各生成一个价格网格队列。
+ * 价格触发网格层级时挂条件单,成交后自动挂止盈单。每笔止盈盈利 = step - minTick。
+ *
+ * <h3>与 GateGridTradeService 的关系</h3>
+ * 策略逻辑完全一致,仅 API 层(REST/WS/签名)替换为 OKX 实现。
+ * GridElement 和 TraderParam 从 gateApi 包复用(交易所无关的数据模型)。
+ *
+ * <h3>完整生命周期</h3>
+ * <pre>
+ * 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
+ * ↓
+ * onKline() → processLongGrid() + processShortGrid()
+ * ├── 匹配队列元素 → 队列补偿 → 保证金检查
+ * ├── 首元素方向:挂条件开仓单 → 订单ID + GridElement状态同步
+ * └── 反向守卫:在 downGrid 位置挂对向单
+ * ↓
+ * onAutoOrder() ← orders-algo WS 推送
+ * ├── 匹配止盈单ID → 清空止盈状态(已成交)
+ * └── 匹配挂单ID → 挂止盈条件单 → 止盈ID + GridElement状态同步
+ * </pre>
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class OkxGridTradeService {
+
+ public enum StrategyState {
+ WAITING_KLINE, OPENING, ACTIVE, STOPPED
+ }
+
+ /** 止盈条件单 order_type:平多仓 */
+ private static final String ORDER_TYPE_CLOSE_LONG = "plan-close-long-position";
+ /** 止盈条件单 order_type:平空仓 */
+ private static final String ORDER_TYPE_CLOSE_SHORT = "plan-close-short-position";
+
+ private final OkxConfig config;
+ private final OkxTradeExecutor executor;
+
+ private volatile StrategyState state = StrategyState.WAITING_KLINE;
+
+ /** 空仓价格队列,降序排列(大→小),容量 gridQueueSize */
+ private final List<BigDecimal> shortPriceQueue = Collections.synchronizedList(new ArrayList<>());
+ /** 多仓价格队列,升序排列(小→大),容量 gridQueueSize */
+ private final List<BigDecimal> longPriceQueue = Collections.synchronizedList(new ArrayList<>());
+ private final List<BigDecimal> totalLongPriceQueue = Collections.synchronizedList(new ArrayList<>());
+ private final List<BigDecimal> totalShortPriceQueue = Collections.synchronizedList(new ArrayList<>());
+
+ /** 当前多仓条件单映射 */
+ private final Map<String, BigDecimal> currentLongOrderIds = Collections.synchronizedMap(new LinkedHashMap<>());
+ /** 当前空仓条件单映射 */
+ private final Map<String, BigDecimal> currentShortOrderIds = Collections.synchronizedMap(new LinkedHashMap<>());
+
+ /** 基底空头入场价 */
+ private BigDecimal shortBaseEntryPrice;
+ /** 基底多头入场价 */
+ private BigDecimal longBaseEntryPrice;
+ /** 基底多头是否已开 */
+ private volatile boolean baseLongOpened = false;
+ /** 基底空头是否已开 */
+ private volatile boolean baseShortOpened = false;
+
+ /** 空头是否活跃 */
+ private volatile boolean shortActive = false;
+ /** 多头是否活跃 */
+ private volatile boolean longActive = false;
+
+ /** 多头累计止损张数 */
+ private volatile int accumulatedLongLossCount = 0;
+ /** 空头累计止损张数 */
+ private volatile int accumulatedShortLossCount = 0;
+
+ private volatile BigDecimal lastKlinePrice;
+ private volatile BigDecimal markPrice = BigDecimal.ZERO;
+ private volatile BigDecimal cumulativePnl = BigDecimal.ZERO;
+ private volatile BigDecimal unrealizedPnl = BigDecimal.ZERO;
+ private volatile BigDecimal longEntryPrice = BigDecimal.ZERO;
+ private volatile BigDecimal shortEntryPrice = BigDecimal.ZERO;
+ private volatile BigDecimal longPositionSize = BigDecimal.ZERO;
+ private volatile BigDecimal shortPositionSize = BigDecimal.ZERO;
+ private volatile BigDecimal initialPrincipal = BigDecimal.ZERO;
+ private volatile OkxKlineWebSocketClient wsClient;
+
+ public OkxGridTradeService(OkxConfig config) {
+ this.config = config;
+ this.executor = new OkxTradeExecutor(config);
+ }
+
+ // ---- 仓位模式(替代 Gate 的 Position.ModeEnum)----
+
+ /** OKX 持仓方向枚举 */
+ public enum OkxPosMode { LONG, SHORT }
+
+ /** 持仓查询结果 */
+ static class OkxPositionInfo {
+ String size;
+ String entryPrice;
+ }
+
+ // ---- 初始化 ----
+
+ /**
+ * 初始化策略环境:获取账户信息 → 切双向持仓 → 清旧条件单 → 平仓 → 设杠杆。
+ */
+ public void init() {
+ try {
+ JSONObject account = executorGet("/api/v5/account/balance");
+ JSONArray details = account.getJSONArray("data");
+ if (details != null && !details.isEmpty()) {
+ JSONObject total = details.getJSONObject(0);
+ this.initialPrincipal = total.getBigDecimal("totalEq");
+ log.info("[OKX] 初始本金: {} USDT", initialPrincipal);
+ }
+
+ // 设置双向持仓模式
+ JSONObject posModeBody = new JSONObject();
+ posModeBody.put("posMode", config.getPositionMode());
+ executorPost("/api/v5/account/set-position-mode", posModeBody.toJSONString());
+ log.info("[OKX] 持仓模式已设为: {}", config.getPositionMode());
+
+ // 设置杠杆
+ JSONObject levBody = new JSONObject();
+ levBody.put("instId", config.getContract());
+ levBody.put("lever", config.getLeverage());
+ levBody.put("mgnMode", config.getMarginMode());
+ executorPost("/api/v5/account/set-leverage", levBody.toJSONString());
+ log.info("[OKX] 杠杆已设为: {}x {}", config.getLeverage(), config.getMarginMode());
+
+ executor.cancelAllPriceTriggeredOrders();
+ log.info("[OKX] 旧条件单已清除");
+ closeExistingPositions();
+
+ log.info("[OKX] 初始化完成");
+ } catch (Exception e) {
+ log.error("[OKX] 初始化失败", e);
+ }
+ }
+
+ /**
+ * 平掉当前合约的所有已有仓位。
+ */
+ private void closeExistingPositions() {
+ try {
+ JSONObject resp = executorGet("/api/v5/account/positions?instType=SWAP");
+ JSONArray data = resp.getJSONArray("data");
+ if (data == null || data.isEmpty()) {
+ log.info("[OKX] 无已有仓位");
+ return;
+ }
+ for (int i = 0; i < data.size(); i++) {
+ JSONObject pos = data.getJSONObject(i);
+ String instId = pos.getString("instId");
+ if (instId == null || !instId.equals(config.getContract())) {
+ continue;
+ }
+ String posVal = pos.getString("pos");
+ if (posVal == null || "0".equals(posVal)) {
+ continue;
+ }
+ String posSide = pos.getString("posSide");
+ String side = "long".equals(posSide) ? "sell" : "buy";
+
+ JSONObject body = new JSONObject();
+ body.put("instId", config.getContract());
+ body.put("tdMode", "cross");
+ body.put("side", side);
+ body.put("posSide", posSide);
+ body.put("ordType", "market");
+ body.put("sz", posVal);
+ executorPost("/api/v5/trade/order", body.toJSONString());
+ log.info("[OKX] 平已有仓位, posSide:{}, sz:{}", posSide, posVal);
+ }
+ } catch (Exception e) {
+ log.warn("[OKX] 平仓位异常", e);
+ }
+ }
+
+ // ---- 启动/停止 ----
+
+ public void startGrid() {
+ if (state != StrategyState.WAITING_KLINE && state != StrategyState.STOPPED) {
+ log.warn("[OKX] 策略已在运行中, state:{}", state);
+ return;
+ }
+ state = StrategyState.WAITING_KLINE;
+ cumulativePnl = BigDecimal.ZERO;
+ unrealizedPnl = BigDecimal.ZERO;
+ markPrice = BigDecimal.ZERO;
+ longEntryPrice = BigDecimal.ZERO;
+ shortEntryPrice = BigDecimal.ZERO;
+ longPositionSize = BigDecimal.ZERO;
+ shortPositionSize = BigDecimal.ZERO;
+ baseLongOpened = false;
+ baseShortOpened = false;
+ longActive = false;
+ shortActive = false;
+ accumulatedLongLossCount = 0;
+ accumulatedShortLossCount = 0;
+ shortPriceQueue.clear();
+ longPriceQueue.clear();
+ totalShortPriceQueue.clear();
+ totalLongPriceQueue.clear();
+ currentLongOrderIds.clear();
+ currentShortOrderIds.clear();
+ refreshInitialPrincipal();
+ log.info("[OKX] 网格策略已启动, 当前本金: {} USDT", initialPrincipal);
+ }
+
+ private void refreshInitialPrincipal() {
+ try {
+ JSONObject account = executorGet("/api/v5/account/balance");
+ JSONArray details = account.getJSONArray("data");
+ if (details != null && !details.isEmpty()) {
+ this.initialPrincipal = details.getJSONObject(0).getBigDecimal("totalEq");
+ }
+ } catch (Exception e) {
+ log.warn("[OKX] 获取初始化本金失败,使用旧值: {}", initialPrincipal);
+ }
+ }
+
+ public void stopGrid() {
+ state = StrategyState.STOPPED;
+ executor.cancelAllPriceTriggeredOrders();
+ closeExistingPositions();
+ executor.shutdown();
+ log.info("[OKX] 策略已停止, 累计盈亏: {}", cumulativePnl);
+ }
+
+ // ---- K线回调 ----
+
+ public void onKline(BigDecimal closePrice) {
+ lastKlinePrice = closePrice;
+
+ if (state == StrategyState.WAITING_KLINE) {
+ if (wsClient == null || !wsClient.areAllSubscribed()) {
+ return;
+ }
+ state = StrategyState.OPENING;
+ String size = config.getBaseQuantity();
+ log.info("[OKX] 首根K线到达,开基底仓位 多空各{}张...", size);
+ executor.openLong(size, (orderId) -> {
+ TraderParam baseLongTp = TraderParam.builder()
+ .entryOrderId(orderId)
+ .build();
+ config.setBaseLongTraderParam(baseLongTp);
+ }, null);
+ executor.openShort(size, (orderId) -> {
+ TraderParam baseShortTp = TraderParam.builder()
+ .entryOrderId(orderId)
+ .build();
+ config.setBaseShortTraderParam(baseShortTp);
+ }, null);
+ return;
+ }
+
+ if (state == StrategyState.ACTIVE &&
+ !longActive &&
+ longPositionSize.compareTo(BigDecimal.ZERO) == 0) {
+ processShortGrid(closePrice);
+ }
+
+ if (state == StrategyState.ACTIVE &&
+ !shortActive &&
+ shortPositionSize.compareTo(BigDecimal.ZERO) == 0) {
+ processLongGrid(closePrice);
+ }
+ }
+
+ // ---- 仓位推送回调 ----
+
+ /**
+ * 仓位推送回调。由 PositionsOkxChannelHandler 调用。
+ * direction 使用 TraderParam.Direction:LONG 表示多头,SHORT 表示空头。
+ */
+ public void onPositionUpdate(String contract, TraderParam.Direction direction, BigDecimal size,
+ BigDecimal entryPrice) {
+ if (state == StrategyState.STOPPED || state == StrategyState.WAITING_KLINE) {
+ return;
+ }
+
+ boolean hasPosition = size.abs().compareTo(BigDecimal.ZERO) > 0;
+ boolean isLong = (direction == TraderParam.Direction.LONG);
+
+ if (state == StrategyState.OPENING) {
+ if (isLong && hasPosition && !baseLongOpened) {
+ longActive = true;
+ longPositionSize = size;
+ longEntryPrice = entryPrice;
+ longBaseEntryPrice = entryPrice;
+ baseLongOpened = true;
+ log.info("[OKX] 基底多成交价: {}", longBaseEntryPrice);
+ tryGenerateQueues();
+ } else if (!isLong && hasPosition && !baseShortOpened) {
+ shortActive = true;
+ shortPositionSize = size.abs();
+ shortEntryPrice = entryPrice;
+ shortBaseEntryPrice = entryPrice;
+ baseShortOpened = true;
+ log.info("[OKX] 基底空成交价: {}", shortBaseEntryPrice);
+ tryGenerateQueues();
+ }
+ }
+
+ if (state == StrategyState.ACTIVE) {
+ if (isLong) {
+ if (hasPosition) {
+ longActive = true;
+ longPositionSize = size;
+ longEntryPrice = entryPrice;
+ } else {
+ log.info("[OKX-0]多仓归零: {}", shortBaseEntryPrice);
+ longActive = false;
+ longPositionSize = BigDecimal.ZERO;
+ longEntryPrice = BigDecimal.ZERO;
+ }
+ } else {
+ if (hasPosition) {
+ shortActive = true;
+ shortPositionSize = size.abs();
+ shortEntryPrice = entryPrice;
+ } else {
+ log.info("[OKX-0]空仓归零: {}", shortBaseEntryPrice);
+ shortActive = false;
+ shortPositionSize = BigDecimal.ZERO;
+ shortEntryPrice = BigDecimal.ZERO;
+ }
+ }
+ }
+
+ if (state == StrategyState.ACTIVE && !shortActive && !longActive) {
+ executor.cancelAllPriceTriggeredOrders();
+ closeExistingPositions();
+ state = StrategyState.STOPPED;
+ executor.submitTask(() -> {
+ try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
+ startGrid();
+ });
+ log.info("[OKX] 重置策略");
+ }
+ }
+
+ // ---- 平仓推送回调 ----
+
+ /**
+ * 平仓盈亏累加(OKX 目前没有独立的 position_closes 频道,
+ * 盈亏信息可从 orders 频道或 REST API 获取,这里保留接口以备将来使用)。
+ */
+ public void onPositionClose(String contract, String side, BigDecimal pnl) {
+ if (state == StrategyState.STOPPED) {
+ return;
+ }
+ cumulativePnl = cumulativePnl.add(pnl);
+ updateUnrealizedPnl();
+ BigDecimal totalPnl = cumulativePnl.add(unrealizedPnl);
+ log.info("[OKX] 已实现:{}, 未实现:{}, 合计:{}",
+ cumulativePnl, unrealizedPnl, totalPnl);
+ if (totalPnl.compareTo(config.getMaxLoss().negate()) <= 0) {
+ String logMessage = StrUtil.format("[OKX] 已达亏损风险值(合计{}), 已实现:{}, 未实现:{}",
+ totalPnl, cumulativePnl, unrealizedPnl);
+ log.info(logMessage);
+ DingTalkUtils.getDefault().sendActionCard("风险提醒", logMessage, config.getApiKey(), "");
+ }
+ }
+
+ // ---- 自动订单(条件单)状态变更回调 ----
+
+ /**
+ * 自动订单状态变更回调。由 OrderAlgoOkxChannelHandler 调用。
+ */
+ public void onAutoOrder(String orderId, String status, String reason, String orderType, String tradeId) {
+ if (state == StrategyState.STOPPED) {
+ return;
+ }
+ log.info("[OKX] 条件单状态变更, id:{}, status:{}, reason:{}, order_type:{}",
+ orderId, status, reason, orderType);
+ if (!"finished".equals(status)) {
+ return;
+ }
+
+ // 多仓止盈触发
+ GridElement longTpElem = GridElement.findByLongTakeProfitOrderId(orderId);
+ if (longTpElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
+ longTakeProfitTraderIdParam(longTpElem, null, false);
+ log.info("[OKX] 多仓止盈触发 gridId:{}, orderId:{}", longTpElem.getId(), orderId);
+ cancelFarthestLongStopLoss();
+ checkLastTakeProfitAndRestart();
+ return;
+ }
+ // 空仓止盈触发
+ GridElement shortTpElem = GridElement.findByShortTakeProfitOrderId(orderId);
+ if (shortTpElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
+ shortTakeProfitTraderIdParam(shortTpElem, null, false);
+ log.info("[OKX] 空仓止盈触发 gridId:{}, orderId:{}", shortTpElem.getId(), orderId);
+ cancelFarthestShortStopLoss();
+ checkLastTakeProfitAndRestart();
+ return;
+ }
+
+ // 多仓止损触发
+ GridElement longStopLossElem = GridElement.findByLongStopLossOrderId(orderId);
+ if (longStopLossElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
+ handleLongStopLossTriggered(longStopLossElem);
+ return;
+ }
+ // 空仓止损触发
+ GridElement shortStopLossElem = GridElement.findByShortStopLossOrderId(orderId);
+ if (shortStopLossElem != null && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
+ handleShortStopLossTriggered(shortStopLossElem);
+ return;
+ }
+
+ // 空仓挂单成交
+ GridElement shortGridElement = GridElement.findByShortOrderId(orderId);
+ if (shortGridElement != null) {
+ if (shortGridElement.isHasShortOrder() && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
+ int filledQty = Integer.parseInt(shortGridElement.getShortTraderParam().getQuantity());
+ shortEntryTraderIdParam(shortGridElement, null, false);
+ cancelAllShortTakeProfitsAndStopLosses();
+ int posSize = queryPositionSize(OkxPosMode.SHORT);
+ extendShortStopLoss(posSize, shortGridElement.getId());
+ accumulatedShortLossCount = 0;
+ log.info("[OKX] 空单成交 gridId:{}, 当前持仓:{}张", filledQty, posSize);
+
+ BigDecimal shortBaseQty = new BigDecimal(config.getBaseQuantity());
+ BigDecimal shortGridQty = new BigDecimal(config.getQuantity());
+ if (BigDecimal.valueOf(posSize).compareTo(shortBaseQty) > 0) {
+ BigDecimal shortExcess = BigDecimal.valueOf(posSize).subtract(shortBaseQty);
+ int shortExcessCount = shortExcess.divide(shortGridQty, 0, RoundingMode.DOWN).intValue();
+ for (int i = 0; i < shortExcessCount; i++) {
+ int tpGridId = shortGridElement.getId() - 2 - i;
+ if (i > 0) { tpGridId = tpGridId - 1; }
+ GridElement tpElem = GridElement.findById(tpGridId);
+ if (tpElem == null || tpElem.getShortTakeProfitOrderId() != null) {
+ continue;
+ }
+ BigDecimal tpPrice = tpElem.getGridPrice();
+ int finalTpGridId = tpGridId;
+ executor.placeTakeProfit(
+ tpPrice,
+ "close_short",
+ config.getQuantity(),
+ profitId -> {
+ shortTakeProfitTraderIdParam(tpElem, profitId, true);
+ log.info("[OKX] 空仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
+ finalTpGridId, tpPrice, profitId);
+ }
+ );
+ }
+ }
+ }
+ }
+ // 多仓挂单成交
+ GridElement longGridElement = GridElement.findByLongOrderId(orderId);
+ if (longGridElement != null) {
+ if (longGridElement.isHasLongOrder() && StrUtil.isNotEmpty(tradeId) && !tradeId.equals("0")) {
+ int filledQty = Integer.parseInt(longGridElement.getLongTraderParam().getQuantity());
+ longEntryTraderIdParam(longGridElement, null, false);
+ cancelAllLongTakeProfitsAndStopLosses();
+ int posSize = queryPositionSize(OkxPosMode.LONG);
+ extendLongStopLoss(posSize, longGridElement.getId());
+ accumulatedLongLossCount = 0;
+ log.info("[OKX] 多单成交 gridId:{}, 当前持仓:{}张", filledQty, posSize);
+
+ BigDecimal longBaseQty = new BigDecimal(config.getBaseQuantity());
+ BigDecimal longGridQty = new BigDecimal(config.getQuantity());
+ if (BigDecimal.valueOf(posSize).compareTo(longBaseQty) > 0) {
+ BigDecimal longExcess = BigDecimal.valueOf(posSize).subtract(longBaseQty);
+ int longExcessCount = longExcess.divide(longGridQty, 0, RoundingMode.DOWN).intValue();
+ for (int i = 0; i < longExcessCount; i++) {
+ int tpGridId = longGridElement.getId() + 2 + i;
+ if (i > 0) { tpGridId = tpGridId + 1; }
+ GridElement tpElem = GridElement.findById(tpGridId);
+ if (tpElem == null || tpElem.getLongTakeProfitOrderId() != null) {
+ continue;
+ }
+ BigDecimal tpPrice = tpElem.getGridPrice();
+ int finalTpGridId = tpGridId;
+ executor.placeTakeProfit(
+ tpPrice,
+ "close_long",
+ config.getQuantity(),
+ profitId -> {
+ longTakeProfitTraderIdParam(tpElem, profitId, true);
+ log.info("[OKX] 多仓止盈挂单, gridId:{}, 触发价:{}, takeProfitId:{}",
+ finalTpGridId, tpPrice, profitId);
+ }
+ );
+ }
+ }
+ }
+ }
+ }
+
+ // ========== REST 查询辅助 ==========
+
+ private int queryPositionSize(OkxPosMode mode) {
+ OkxPositionInfo p = queryPosition(mode);
+ if (p != null) {
+ return new BigDecimal(p.size).abs().intValue();
+ }
+ return 0;
+ }
+
+ private BigDecimal queryEntryPrice(OkxPosMode mode) {
+ OkxPositionInfo p = queryPosition(mode);
+ if (p != null && p.entryPrice != null) {
+ return new BigDecimal(p.entryPrice);
+ }
+ return BigDecimal.ZERO;
+ }
+
+ private OkxPositionInfo queryPosition(OkxPosMode mode) {
+ try {
+ JSONObject resp = executorGet("/api/v5/account/positions?instType=SWAP");
+ JSONArray data = resp.getJSONArray("data");
+ if (data != null) {
+ for (int i = 0; i < data.size(); i++) {
+ JSONObject p = data.getJSONObject(i);
+ if (config.getContract().equals(p.getString("instId"))
+ && mode.name().toLowerCase().equals(p.getString("posSide"))) {
+ OkxPositionInfo info = new OkxPositionInfo();
+ info.size = p.getString("pos");
+ info.entryPrice = p.getString("avgPx");
+ return info;
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.warn("[OKX] 查询{}持仓失败", mode, e);
+ }
+ return null;
+ }
+
+ // ---- REST 快捷方法 ----
+
+ private JSONObject executorGet(String path) throws Exception {
+ return executor.okGet(path);
+ }
+
+ private JSONObject executorPost(String path, String body) throws Exception {
+ return executor.okPost(path, body);
+ }
+
+ // ---- 网格队列处理 ----
+
+ private void tryGenerateQueues() {
+ if (baseLongOpened && baseShortOpened) {
+ generateShortQueue();
+ generateLongQueue();
+ updateGridElements();
+
+ GridElement baseGridElement = GridElement.findById(0);
+ TraderParam baseLongTraderParam = config.getBaseLongTraderParam();
+ baseGridElement.setLongOrderId(baseLongTraderParam.getEntryOrderId());
+ baseGridElement.setHasLongOrder(true);
+ TraderParam baseShortTraderParam = config.getBaseShortTraderParam();
+ baseGridElement.setShortOrderId(baseShortTraderParam.getEntryOrderId());
+ baseGridElement.setHasShortOrder(true);
+
+ // 挂基座止盈
+ {
+ BigDecimal tpPrice = baseGridElement.getGridPrice().add(config.getStep());
+ executor.placeTakeProfit(tpPrice, "close_long", config.getBaseQuantity(),
+ profitId -> {
+ longTakeProfitTraderIdParam(baseGridElement, profitId, true);
+ log.info("[OKX] 基座多仓止盈已挂, gridId:0, 触发价:{}, tpId:{}", tpPrice, profitId);
+ });
+ }
+ {
+ BigDecimal tpPrice = baseGridElement.getGridPrice().subtract(config.getStep());
+ executor.placeTakeProfit(tpPrice, "close_short", config.getBaseQuantity(),
+ profitId -> {
+ shortTakeProfitTraderIdParam(baseGridElement, profitId, true);
+ log.info("[OKX] 基座空仓止盈已挂, gridId:0, 触发价:{}, tpId:{}", tpPrice, profitId);
+ });
+ }
+
+ // 挂初始止损
+ int stopCount = Integer.parseInt(config.getBaseQuantity()) / Integer.parseInt(config.getQuantity()) + 1;
+ for (int id = 2; id <= stopCount; id++) {
+ GridElement elem = GridElement.findById(id);
+ if (elem == null) {
+ continue;
+ }
+ BigDecimal triggerPrice = elem.getGridPrice();
+ int finalId = id;
+ executor.placeTakeProfit(triggerPrice, "close_short", config.getQuantity(),
+ profitId -> {
+ elem.setShortStopLossOrderId(profitId);
+ GridElement.refreshIndices();
+ log.info("[OKX] 空仓止损已挂, gridId:{}, 触发价:{}, slId:{}", finalId, triggerPrice, profitId);
+ });
+ }
+ for (int id = -2; id >= -stopCount; id--) {
+ GridElement elem = GridElement.findById(id);
+ if (elem == null) {
+ continue;
+ }
+ BigDecimal triggerPrice = elem.getGridPrice();
+ int finalId = id;
+ executor.placeTakeProfit(triggerPrice, "close_long", config.getQuantity(),
+ profitId -> {
+ elem.setLongStopLossOrderId(profitId);
+ GridElement.refreshIndices();
+ log.info("[OKX] 多仓止损已挂, gridId:{}, 触发价:{}, slId:{}", finalId, triggerPrice, profitId);
+ });
+ }
+ log.info("[OKX] 止损单已全部挂完, 空仓止损: 2~{}, 多仓止损: -2~-{}", stopCount, stopCount);
+
+ // 挂初始条件开仓单
+ GridElement longFirst = GridElement.findById(1);
+ if (longFirst != null && !longFirst.isHasLongOrder()) {
+ BigDecimal triggerPrice = longFirst.getGridPrice();
+ log.info("[OKX] 挂初始多仓条件单, gridId:1, trigger:{}", triggerPrice);
+ placeEntryOrderWithPreFlag(longFirst, true, triggerPrice, config.getBaseQuantity());
+ }
+ GridElement shortFirst = GridElement.findById(-1);
+ if (shortFirst != null && !shortFirst.isHasShortOrder()) {
+ BigDecimal triggerPrice = shortFirst.getGridPrice();
+ log.info("[OKX] 挂初始空仓条件单, gridId:-1, trigger:{}", triggerPrice);
+ placeEntryOrderWithPreFlag(shortFirst, false, triggerPrice, negate(config.getBaseQuantity()));
+ }
+
+ state = StrategyState.ACTIVE;
+ }
+ }
+
+ // ---- TraderParam 更新辅助方法 ----
+
+ private void longTakeProfitTraderIdParam(GridElement e, String profitId, boolean flag) {
+ TraderParam tp = e.getLongTraderParam();
+ tp.setTakeProfitOrderId(profitId);
+ tp.setTakeProfitPlaced(flag);
+ e.setLongTakeProfitOrderId(profitId);
+ GridElement.refreshIndices();
+ }
+
+ private void shortTakeProfitTraderIdParam(GridElement e, String profitId, boolean flag) {
+ TraderParam tp = e.getShortTraderParam();
+ tp.setTakeProfitOrderId(profitId);
+ tp.setTakeProfitPlaced(flag);
+ e.setShortTakeProfitOrderId(profitId);
+ GridElement.refreshIndices();
+ }
+
+ private void longEntryTraderIdParam(GridElement e, String entryId, boolean flag) {
+ TraderParam tp = e.getLongTraderParam();
+ tp.setEntryOrderId(entryId);
+ tp.setEntryOrderPlaced(flag);
+ e.setHasLongOrder(flag);
+ e.setLongOrderId(entryId);
+ GridElement.refreshIndices();
+ }
+
+ private void shortEntryTraderIdParam(GridElement e, String entryId, boolean flag) {
+ TraderParam tp = e.getShortTraderParam();
+ tp.setEntryOrderId(entryId);
+ tp.setEntryOrderPlaced(flag);
+ e.setHasShortOrder(flag);
+ e.setShortOrderId(entryId);
+ GridElement.refreshIndices();
+ }
+
+ // ---- 队列生成 ----
+
+ private void generateShortQueue() {
+ shortPriceQueue.clear();
+ totalShortPriceQueue.clear();
+ totalLongPriceQueue.clear();
+ int prec = config.getPriceScale();
+ BigDecimal step = shortBaseEntryPrice.multiply(config.getGridRate()).setScale(prec, RoundingMode.HALF_UP);
+ config.setStep(step);
+ BigDecimal elem = shortBaseEntryPrice.subtract(step).setScale(prec, RoundingMode.HALF_UP);
+ for (int i = 0; i < config.getGridQueueSize(); i++) {
+ shortPriceQueue.add(elem);
+ totalLongPriceQueue.add(elem);
+ totalShortPriceQueue.add(elem);
+ elem = elem.subtract(step).setScale(prec, RoundingMode.HALF_UP);
+ if (elem.compareTo(BigDecimal.ZERO) <= 0) {
+ break;
+ }
+ }
+ shortPriceQueue.sort((a, b) -> b.compareTo(a));
+ log.info("[OKX] 空队列:{}", shortPriceQueue);
+ }
+
+ private void generateLongQueue() {
+ longPriceQueue.clear();
+ int prec = config.getPriceScale();
+ BigDecimal step = config.getStep();
+ BigDecimal elem = shortBaseEntryPrice.add(step).setScale(prec, RoundingMode.HALF_UP);
+ for (int i = 0; i < config.getGridQueueSize(); i++) {
+ longPriceQueue.add(elem);
+ totalLongPriceQueue.add(elem);
+ totalShortPriceQueue.add(elem);
+ elem = elem.add(step).setScale(prec, RoundingMode.HALF_UP);
+ }
+ longPriceQueue.sort(BigDecimal::compareTo);
+ log.info("[OKX] 多队列:{}", longPriceQueue);
+ totalShortPriceQueue.sort((a, b) -> b.compareTo(a));
+ totalLongPriceQueue.sort(BigDecimal::compareTo);
+ }
+
+ private void updateGridElements() {
+ List<GridElement> elements = new ArrayList<>();
+ int shortSize = shortPriceQueue.size();
+ int longSize = longPriceQueue.size();
+ int prec = config.getPriceScale();
+ BigDecimal step = config.getStep();
+ String qty = config.getQuantity();
+
+ // 空仓队列:id 从 -1 自减
+ for (int i = 0; i < shortSize; i++) {
+ int id = -(i + 1);
+ Integer upId = (i == 0) ? 0 : id + 1;
+ Integer downId = (i == shortSize - 1) ? null : id - 1;
+ BigDecimal price = shortPriceQueue.get(i);
+ elements.add(GridElement.builder()
+ .id(id).gridPrice(price).upId(upId).downId(downId)
+ .longTraderParam(TraderParam.builder().direction(TraderParam.Direction.LONG)
+ .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP))
+ .quantity(qty).build())
+ .shortTraderParam(TraderParam.builder().direction(TraderParam.Direction.SHORT)
+ .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP))
+ .quantity(qty).build())
+ .build());
+ }
+
+ // 位置 0
+ {
+ BigDecimal price = shortBaseEntryPrice;
+ elements.add(GridElement.builder()
+ .id(0).gridPrice(price)
+ .upId(longSize > 0 ? 1 : null).downId(shortSize > 0 ? -1 : null)
+ .longTraderParam(TraderParam.builder().direction(TraderParam.Direction.LONG)
+ .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP))
+ .quantity(qty).build())
+ .shortTraderParam(TraderParam.builder().direction(TraderParam.Direction.SHORT)
+ .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP))
+ .quantity(qty).build())
+ .build());
+ }
+
+ // 多仓队列:id 从 1 自增
+ for (int i = 0; i < longSize; i++) {
+ int id = i + 1;
+ Integer downId = (i == 0) ? 0 : id - 1;
+ Integer upId = (i == longSize - 1) ? null : id + 1;
+ BigDecimal price = longPriceQueue.get(i);
+ elements.add(GridElement.builder()
+ .id(id).gridPrice(price).upId(upId).downId(downId)
+ .longTraderParam(TraderParam.builder().direction(TraderParam.Direction.LONG)
+ .entryPrice(price).takeProfitPrice(price.add(step).setScale(prec, RoundingMode.HALF_UP))
+ .quantity(qty).build())
+ .shortTraderParam(TraderParam.builder().direction(TraderParam.Direction.SHORT)
+ .entryPrice(price).takeProfitPrice(price.subtract(step).setScale(prec, RoundingMode.HALF_UP))
+ .quantity(qty).build())
+ .build());
+ }
+
+ config.setGridElements(elements);
+ log.info("[OKX] 网格元素列表已构建, 共{}个元素 (空仓:{} 位置:0 多仓:{})", elements.size(), shortSize, longSize);
+ }
+
+ // ---- processShortGrid / processLongGrid ----
+
+ private void processShortGrid(BigDecimal currentPrice) {
+ BigDecimal matched = BigDecimal.ZERO;
+ synchronized (totalLongPriceQueue) {
+ for (BigDecimal p : totalLongPriceQueue) {
+ if (p.compareTo(currentPrice) >= 0) { matched = p; break; }
+ }
+ if (BigDecimal.ZERO.compareTo(matched) == 0) {
+ return;
+ }
+ GridElement matchedUpGridElement = GridElement.findByPrice(matched);
+ if (matchedUpGridElement != null && !matchedUpGridElement.isHasLongOrder()) {
+ Integer upId = matchedUpGridElement.getUpId();
+ GridElement newEntryGrid = GridElement.findById(upId);
+ if (newEntryGrid != null) {
+ GridElement cancelGridElement = GridElement.findById(newEntryGrid.getUpId());
+ String quantity = cancelGridElement != null
+ ? cancelGridElement.getLongTraderParam().getQuantity()
+ : config.getBaseQuantity();
+ if (cancelGridElement != null && cancelGridElement.isHasLongOrder()) {
+ String longOrderId = cancelGridElement.getLongOrderId();
+ executor.cancelConditionalOrder(longOrderId, oid -> {
+ longEntryTraderIdParam(cancelGridElement, null, false);
+ log.info("[OKX] 多仓仓位归零, 取消gridId:{}的多单,{}", cancelGridElement.getId(), longOrderId);
+ });
+ }
+ if (!newEntryGrid.isHasLongOrder()) {
+ BigDecimal triggerPrice = newEntryGrid.getGridPrice();
+ String size = quantity;
+ log.info("[OKX] 多仓仓位归零 gridId:{}, 挂{}基础张多单", newEntryGrid.getId(), size);
+ newEntryGrid.getLongTraderParam().setQuantity(size);
+ placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, size);
+ }
+ }
+ }
+ }
+ }
+
+ private void processLongGrid(BigDecimal currentPrice) {
+ BigDecimal matched = BigDecimal.ZERO;
+ synchronized (totalShortPriceQueue) {
+ for (BigDecimal p : totalShortPriceQueue) {
+ if (p.compareTo(currentPrice) <= 0) { matched = p; break; }
+ }
+ if (BigDecimal.ZERO.compareTo(matched) == 0) {
+ return;
+ }
+ GridElement matchedUpGridElement = GridElement.findByPrice(matched);
+ if (matchedUpGridElement != null && !matchedUpGridElement.isHasShortOrder()) {
+ Integer downId = matchedUpGridElement.getDownId();
+ GridElement newEntryGrid = GridElement.findById(downId);
+ if (newEntryGrid != null) {
+ GridElement cancelGridElement = GridElement.findById(newEntryGrid.getDownId());
+ String quantity = cancelGridElement != null
+ ? cancelGridElement.getShortTraderParam().getQuantity()
+ : config.getBaseQuantity();
+ if (cancelGridElement != null && cancelGridElement.isHasShortOrder()) {
+ String shortOrderId = cancelGridElement.getShortOrderId();
+ executor.cancelConditionalOrder(shortOrderId, oid -> {
+ shortEntryTraderIdParam(cancelGridElement, null, false);
+ log.info("[OKX] 空仓仓位归零, 取消gridId:{}的空单{}", cancelGridElement.getId(), shortOrderId);
+ });
+ }
+ if (!newEntryGrid.isHasShortOrder()) {
+ BigDecimal triggerPrice = newEntryGrid.getGridPrice();
+ String size = quantity;
+ log.info("[OKX] 空仓仓位归零 gridId:{}, 挂{}基础张空单", newEntryGrid.getId(), size);
+ newEntryGrid.getShortTraderParam().setQuantity(size);
+ placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, negate(size));
+ }
+ }
+ }
+ }
+ }
+
+ // ---- 止损处理 ----
+
+ private void handleLongStopLossTriggered(GridElement gridElement) {
+ gridElement.setLongStopLossOrderId(null);
+ int gridId = gridElement.getId();
+ log.info("[OKX] 多仓止损触发 gridId:{}, 开始追单", gridId);
+ int newEntryGridId = gridId + 1;
+ GridElement newEntryGrid = GridElement.findById(newEntryGridId);
+ if (newEntryGrid == null) {
+ log.warn("[OKX] 多仓止损触发 but gridId:{} 不存在", newEntryGridId);
+ GridElement.refreshIndices();
+ return;
+ }
+ if (!newEntryGrid.isHasLongOrder()) {
+ BigDecimal triggerPrice = newEntryGrid.getGridPrice();
+ accumulatedLongLossCount += Integer.parseInt(config.getQuantity());
+ String size = String.valueOf(accumulatedLongLossCount + Integer.parseInt(config.getQuantity()));
+ log.info("[OKX] 多仓止损触发 gridId:{}, 在gridId:{}挂{}基础张多单", gridId, newEntryGridId, size);
+ newEntryGrid.getLongTraderParam().setQuantity(size);
+ placeEntryOrderWithPreFlag(newEntryGrid, true, triggerPrice, size);
+ } else {
+ log.warn("[OKX] 多仓止损触发 gridId:{}, 目标gridId:{}已有挂单,跳过", gridId, newEntryGridId);
+ }
+ int cancelGridId = gridId + 2;
+ GridElement cancelGrid = GridElement.findById(cancelGridId);
+ if (cancelGrid != null && cancelGrid.isHasLongOrder()) {
+ executor.cancelConditionalOrder(cancelGrid.getLongOrderId(), oid -> {
+ longEntryTraderIdParam(cancelGrid, null, false);
+ log.info("[OKX] 多仓止损触发, 取消gridId:{}的多单", cancelGridId);
+ });
+ }
+ GridElement farthestLongTp = null;
+ for (GridElement e : config.getGridElements()) {
+ if (e.getLongTakeProfitOrderId() != null) {
+ if (farthestLongTp == null || e.getGridPrice().compareTo(farthestLongTp.getGridPrice()) > 0) {
+ farthestLongTp = e;
+ }
+ }
+ }
+ if (farthestLongTp != null) {
+ String tpOrderId = farthestLongTp.getLongTakeProfitOrderId();
+ GridElement finalFarthest = farthestLongTp;
+ executor.cancelConditionalOrder(tpOrderId, oid -> {
+ longTakeProfitTraderIdParam(finalFarthest, null, false);
+ log.info("[OKX] 多仓止损触发, 取消最远止盈 gridId:{}, orderId:{}", finalFarthest.getId(), tpOrderId);
+ });
+ }
+ }
+
+ private void handleShortStopLossTriggered(GridElement gridElement) {
+ gridElement.setShortStopLossOrderId(null);
+ int gridId = gridElement.getId();
+ log.info("[OKX] 空仓止损触发 gridId:{}, 开始追单", gridId);
+ int newEntryGridId = gridId - 1;
+ GridElement newEntryGrid = GridElement.findById(newEntryGridId);
+ if (newEntryGrid == null) {
+ log.warn("[OKX] 空仓止损触发 but gridId:{} 不存在", newEntryGridId);
+ GridElement.refreshIndices();
+ return;
+ }
+ if (!newEntryGrid.isHasShortOrder()) {
+ BigDecimal triggerPrice = newEntryGrid.getGridPrice();
+ accumulatedShortLossCount += Integer.parseInt(config.getQuantity());
+ String size = String.valueOf(accumulatedShortLossCount + Integer.parseInt(config.getQuantity()));
+ log.info("[OKX] 空仓止损触发 gridId:{}, 在gridId:{}挂{}基础张空单", gridId, newEntryGridId, size);
+ newEntryGrid.getShortTraderParam().setQuantity(size);
+ placeEntryOrderWithPreFlag(newEntryGrid, false, triggerPrice, negate(size));
+ } else {
+ log.warn("[OKX] 空仓止损触发 gridId:{}, 目标gridId:{}已有挂单,跳过", gridId, newEntryGridId);
+ }
+ int cancelGridId = gridId - 2;
+ GridElement cancelGrid = GridElement.findById(cancelGridId);
+ if (cancelGrid != null && cancelGrid.isHasShortOrder()) {
+ executor.cancelConditionalOrder(cancelGrid.getShortOrderId(), oid -> {
+ shortEntryTraderIdParam(cancelGrid, null, false);
+ log.info("[OKX] 空仓止损触发, 取消gridId:{}的空单", cancelGridId);
+ });
+ }
+ GridElement farthestShortTp = null;
+ for (GridElement e : config.getGridElements()) {
+ if (e.getShortTakeProfitOrderId() != null) {
+ if (farthestShortTp == null || e.getGridPrice().compareTo(farthestShortTp.getGridPrice()) < 0) {
+ farthestShortTp = e;
+ }
+ }
+ }
+ if (farthestShortTp != null) {
+ String tpOrderId = farthestShortTp.getShortTakeProfitOrderId();
+ GridElement finalFarthest = farthestShortTp;
+ executor.cancelConditionalOrder(tpOrderId, oid -> {
+ shortTakeProfitTraderIdParam(finalFarthest, null, false);
+ log.info("[OKX] 空仓止损触发, 取消最远止盈 gridId:{}, orderId:{}", finalFarthest.getId(), tpOrderId);
+ });
+ }
+ }
+
+ // ---- 止盈/止损取消辅助 ----
+
+ private void checkLastTakeProfitAndRestart() {
+ int span = config.getRestartGridSpan();
+ if (span <= 0) {
+ return;
+ }
+
+ if (GridElement.getLongTakeProfitCount() > 0 || GridElement.getShortTakeProfitCount() > 0) {
+ log.info("[OKX] 尚有未触发止盈单, 暂不检查跨度重启 longTpCount:{}, shortTpCount:{}",
+ GridElement.getLongTakeProfitCount(), GridElement.getShortTakeProfitCount());
+ return;
+ }
+
+ BigDecimal step = config.getStep();
+ if (step == null || step.compareTo(BigDecimal.ZERO) == 0) {
+ return;
+ }
+ BigDecimal threshold = step.multiply(new BigDecimal(span));
+ BigDecimal currentPrice = lastKlinePrice;
+ if (currentPrice == null || currentPrice.compareTo(BigDecimal.ZERO) == 0) {
+ return;
+ }
+
+ // 查交易所获取最新持仓均价
+ OkxPositionInfo longPos = queryPosition(OkxPosMode.LONG);
+ OkxPositionInfo shortPos = queryPosition(OkxPosMode.SHORT);
+ boolean hasLong = longPos != null && Math.abs(Integer.parseInt(longPos.size)) > 0;
+ boolean hasShort = shortPos != null && Math.abs(Integer.parseInt(shortPos.size)) > 0;
+ BigDecimal longAvgPrice = (longPos != null && longPos.entryPrice != null)
+ ? new BigDecimal(longPos.entryPrice) : BigDecimal.ZERO;
+ BigDecimal shortAvgPrice = (shortPos != null && shortPos.entryPrice != null)
+ ? new BigDecimal(shortPos.entryPrice) : BigDecimal.ZERO;
+
+ boolean shouldRestart = false;
+ String reason = "";
+
+ if (hasLong && hasShort) {
+ BigDecimal gap = shortAvgPrice.subtract(longAvgPrice);
+ if (gap.compareTo(threshold) >= 0) {
+ shouldRestart = true;
+ reason = StrUtil.format("双边跨度 |多均价:{} − 空均价:{}| = {} >= {} (span:{}×step:{})",
+ longAvgPrice, shortAvgPrice, gap, threshold, span, step);
+ }
+ } else if (hasLong) {
+ BigDecimal gap = currentPrice.subtract(longAvgPrice);
+ if (gap.compareTo(threshold) >= 0) {
+ shouldRestart = true;
+ reason = StrUtil.format("多仓跨度 当前价:{} − 多均价:{} = {} > {} (span:{}×step:{})",
+ currentPrice, longAvgPrice, gap, threshold, span, step);
+ }
+ } else if (hasShort) {
+ BigDecimal gap = shortAvgPrice.subtract(currentPrice);
+ if (gap.compareTo(threshold) >= 0) {
+ shouldRestart = true;
+ reason = StrUtil.format("空仓跨度 空均价:{} − 当前价:{} = {} > {} (span:{}×step:{})",
+ shortAvgPrice, currentPrice, gap, threshold, span, step);
+ }
+ }
+
+ if (shouldRestart) {
+ log.info("[OKX] 跨度已达要求 → {},最后一个止盈触发策略重启", reason);
+ executor.cancelAllPriceTriggeredOrders();
+ closeExistingPositions();
+ state = StrategyState.STOPPED;
+ executor.submitTask(() -> {
+ try { Thread.sleep(3000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); }
+ startGrid();
+ });
+ }
+ }
+
+ private void cancelFarthestLongStopLoss() {
+ GridElement farthest = null;
+ for (GridElement e : config.getGridElements()) {
+ if (e.getLongStopLossOrderId() != null) {
+ if (farthest == null || e.getId() < farthest.getId()) {
+ farthest = e;
+ }
+ }
+ }
+ if (farthest != null) {
+ String slId = farthest.getLongStopLossOrderId();
+ farthest.setLongStopLossOrderId(null);
+ GridElement.refreshIndices();
+ GridElement finalFarthest = farthest;
+ executor.cancelConditionalOrder(slId, oid ->
+ log.info("[OKX] 止盈触发, 取消最远多仓止损 gridId:{}, orderId:{}", finalFarthest.getId(), slId));
+ }
+ }
+
+ private void cancelFarthestShortStopLoss() {
+ GridElement farthest = null;
+ for (GridElement e : config.getGridElements()) {
+ if (e.getShortStopLossOrderId() != null) {
+ if (farthest == null || e.getId() > farthest.getId()) {
+ farthest = e;
+ }
+ }
+ }
+ if (farthest != null) {
+ String slId = farthest.getShortStopLossOrderId();
+ farthest.setShortStopLossOrderId(null);
+ GridElement.refreshIndices();
+ GridElement finalFarthest = farthest;
+ executor.cancelConditionalOrder(slId, oid ->
+ log.info("[OKX] 止盈触发, 取消最远空仓止损 gridId:{}, orderId:{}", finalFarthest.getId(), slId));
+ }
+ }
+
+ private void cancelAllLongTakeProfitsAndStopLosses() {
+ for (GridElement e : config.getGridElements()) {
+ String tpId = e.getLongTakeProfitOrderId();
+ if (tpId != null) { e.setLongTakeProfitOrderId(null); executor.cancelConditionalOrder(tpId, oid -> {}); }
+ String slId = e.getLongStopLossOrderId();
+ if (slId != null) { e.setLongStopLossOrderId(null); executor.cancelConditionalOrder(slId, oid -> {}); }
+ }
+ GridElement.refreshIndices();
+ log.info("[OKX] 已提交取消所有多仓止盈+止损");
+ }
+
+ private void cancelAllShortTakeProfitsAndStopLosses() {
+ for (GridElement e : config.getGridElements()) {
+ String tpId = e.getShortTakeProfitOrderId();
+ if (tpId != null) { e.setShortTakeProfitOrderId(null); executor.cancelConditionalOrder(tpId, oid -> {}); }
+ String slId = e.getShortStopLossOrderId();
+ if (slId != null) { e.setShortStopLossOrderId(null); executor.cancelConditionalOrder(slId, oid -> {}); }
+ }
+ GridElement.refreshIndices();
+ log.info("[OKX] 已提交取消所有空仓止盈+止损");
+ }
+
+ // ---- 止损追单 ----
+
+ private void extendLongStopLoss(int filledQty, int gridId) {
+ int furthestSlId = 0;
+ for (GridElement e : config.getGridElements()) {
+ if (e.getLongStopLossOrderId() != null && e.getId() < furthestSlId) {
+ furthestSlId = e.getId();
+ }
+ }
+ int interval = 1;
+ if (furthestSlId == 0) { furthestSlId = gridId; interval = 2; }
+ int stopLossCount = filledQty / Integer.parseInt(config.getQuantity());
+ log.info("[OKX] 多仓追挂止损, 当前最远止损gridId:{}, 成交{}张, 追加{}个止损单", furthestSlId, filledQty, stopLossCount);
+ for (int i = 0; i < stopLossCount; i++) {
+ int newSlId = furthestSlId - i - interval;
+ GridElement elem = GridElement.findById(newSlId);
+ if (elem == null) {
+ continue;
+ }
+ BigDecimal triggerPrice = elem.getGridPrice();
+ int finalSlId = newSlId;
+ executor.placeTakeProfit(triggerPrice, "close_long", config.getQuantity(),
+ profitId -> {
+ elem.setLongStopLossOrderId(profitId);
+ GridElement.refreshIndices();
+ log.info("[OKX] 多仓止损追加, gridId:{}, 触发价:{}, slId:{}", finalSlId, triggerPrice, profitId);
+ });
+ }
+ }
+
+ private void extendShortStopLoss(int filledQty, int gridId) {
+ int furthestSlId = 0;
+ for (GridElement e : config.getGridElements()) {
+ if (e.getShortStopLossOrderId() != null && e.getId() > furthestSlId) {
+ furthestSlId = e.getId();
+ }
+ }
+ int interval = 1;
+ if (furthestSlId == 0) { furthestSlId = gridId; interval = 2; }
+ int stopLossCount = filledQty / Integer.parseInt(config.getQuantity());
+ log.info("[OKX] 空仓追挂止损, 当前最远止损gridId:{}, 成交{}张, 追加{}个止损单", furthestSlId, filledQty, stopLossCount);
+ for (int i = 0; i < stopLossCount; i++) {
+ int newSlId = furthestSlId + i + interval;
+ GridElement elem = GridElement.findById(newSlId);
+ if (elem == null) {
+ continue;
+ }
+ BigDecimal triggerPrice = elem.getGridPrice();
+ int finalSlId = newSlId;
+ executor.placeTakeProfit(triggerPrice, "close_short", config.getQuantity(),
+ profitId -> {
+ elem.setShortStopLossOrderId(profitId);
+ GridElement.refreshIndices();
+ log.info("[OKX] 空仓止损追加, gridId:{}, 触发价:{}, slId:{}", finalSlId, triggerPrice, profitId);
+ });
+ }
+ }
+
+ // ---- 工具 ----
+
+ private String negate(String qty) {
+ return qty.startsWith("-") ? qty.substring(1) : "-" + qty;
+ }
+
+ private void placeEntryOrderWithPreFlag(GridElement gridElement, boolean isLong,
+ BigDecimal triggerPrice, String size) {
+ if (isLong) {
+ gridElement.setHasLongOrder(true);
+ } else {
+ gridElement.setHasShortOrder(true);
+ }
+ executor.placeConditionalEntryOrder(triggerPrice, isLong, size,
+ orderId -> {
+ if (isLong) {
+ longEntryTraderIdParam(gridElement, orderId, true);
+ } else {
+ shortEntryTraderIdParam(gridElement, orderId, true);
+ }
+ },
+ () -> {
+ if (isLong) {
+ gridElement.setHasLongOrder(false);
+ gridElement.setLongOrderId(null);
+ } else {
+ gridElement.setHasShortOrder(false);
+ gridElement.setShortOrderId(null);
+ }
+ GridElement.refreshIndices();
+ log.warn("[OKX] 条件单创建失败,回滚标志位 gridId:{}, isLong:{}", gridElement.getId(), isLong);
+ });
+ }
+
+ private void updateUnrealizedPnl() {
+ BigDecimal price = resolvePnlPrice();
+ if (price == null || price.compareTo(BigDecimal.ZERO) == 0) {
+ return;
+ }
+ BigDecimal multiplier = config.getContractMultiplier();
+ BigDecimal longPnl = BigDecimal.ZERO;
+ BigDecimal shortPnl = BigDecimal.ZERO;
+ if (longPositionSize.compareTo(BigDecimal.ZERO) > 0 && longEntryPrice.compareTo(BigDecimal.ZERO) > 0) {
+ longPnl = longPositionSize.multiply(multiplier).multiply(price.subtract(longEntryPrice));
+ }
+ if (shortPositionSize.compareTo(BigDecimal.ZERO) > 0 && shortEntryPrice.compareTo(BigDecimal.ZERO) > 0) {
+ shortPnl = shortPositionSize.multiply(multiplier).multiply(shortEntryPrice.subtract(price));
+ }
+ unrealizedPnl = longPnl.add(shortPnl);
+ log.info("[OKX] 未实现盈亏: {}", unrealizedPnl);
+ }
+
+ private BigDecimal resolvePnlPrice() {
+ if (config.getUnrealizedPnlPriceMode() == OkxConfig.PnLPriceMode.MARK_PRICE
+ && markPrice.compareTo(BigDecimal.ZERO) > 0) {
+ return markPrice;
+ }
+ return lastKlinePrice;
+ }
+
+ // ---- getters ----
+
+ public BigDecimal getLastKlinePrice() { return lastKlinePrice; }
+ public void setMarkPrice(BigDecimal markPrice) { this.markPrice = markPrice; }
+ public boolean isStrategyActive() { return state != StrategyState.STOPPED && state != StrategyState.WAITING_KLINE; }
+ public BigDecimal getCumulativePnl() { return cumulativePnl; }
+ public BigDecimal getUnrealizedPnl() { return unrealizedPnl; }
+ public StrategyState getState() { return state; }
+ public void setWsClient(OkxKlineWebSocketClient wsClient) { this.wsClient = wsClient; }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/OkxKlineWebSocketClient.java b/src/main/java/com/xcong/excoin/modules/okxApi/OkxKlineWebSocketClient.java
new file mode 100644
index 0000000..33b26f0
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/OkxKlineWebSocketClient.java
@@ -0,0 +1,753 @@
+package com.xcong.excoin.modules.okxApi;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.modules.okxApi.wsHandler.OkxChannelHandler;
+import com.xcong.excoin.modules.okxNewPrice.utils.SSLConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * OKX WebSocket 连接管理器 — 双通道架构。
+ *
+ * <h3>与 Gate 版本的关键区别</h3>
+ * OKX 使用<b>两条独立的 WebSocket 连接</b>:
+ * <ul>
+ * <li><b>公开 WS</b> ({@code wss://ws.okx.com:8443/ws/v5/public}):
+ * 无需认证,订阅 K 线等公开数据。</li>
+ * <li><b>私有 WS</b> ({@code wss://ws.okx.com:8443/ws/v5/private}):
+ * 需要登录认证(login 消息),订阅仓位、条件订单等私有数据。</li>
+ * </ul>
+ * 而 Gate 只有一条 WS 连接,通过签名区分公开/私有频道。
+ *
+ * <h3>登录认证(私有 WS)</h3>
+ * <pre>
+ * {
+ * "op": "login",
+ * "args": [{
+ * "apiKey": "...",
+ * "passphrase": "...",
+ * "timestamp": "1734567890", // Unix 秒级时间戳
+ * "sign": "base64(HMAC-SHA256(timestamp + 'GET' + '/users/self/verify'))"
+ * }]
+ * }
+ * </pre>
+ *
+ * <h3>心跳机制</h3>
+ * OKX 标准格式为 JSON {@code {"op":"ping"}} / {@code {"op":"pong"}},
+ * 同时兼容纯文本 {@code "ping"} / {@code "pong"} 格式。
+ *
+ * <h3>消息路由</h3>
+ * <pre>
+ * onMessage → handleMessage(message, isPrivate):
+ * 1. "pong" (纯文本) → 日志 + cancelPongTimeout
+ * 2. "ping" (纯文本) → 回复 "pong"
+ * 3. {"op":"pong"} (JSON) → 日志 + cancelPongTimeout
+ * 4. {"op":"ping"} (JSON) → 回复 {"op":"pong"}
+ * 5. {"event":"login"} → 登录成功 → 订阅所有私有 handlers
+ * 6. {"event":"subscribe"} → 标记对应 handler subscribed=true
+ * 7. {"event":"error"} → 错误日志
+ * 8. {"arg":{...}, "data":[...]} → 遍历 handlers 路由
+ * </pre>
+ *
+ * <h3>生命周期</h3>
+ * <pre>
+ * init() → connect(public) + connect(private,true) → startHeartbeat()
+ * destroy() → unsubscribe 所有 handler → closeBlocking() 两条连接 → shutdown 线程池
+ * onClose() → reconnectWithBackoff() 重连对应连接(最多 3 次,指数退避)
+ * </pre>
+ *
+ * <h3>线程安全</h3>
+ * 连接状态用 AtomicBoolean(isPublicConnected, isPrivateConnected, isConnecting, isInitialized)。
+ * 消息时间戳用 AtomicReference。心跳任务用 synchronized 保护。
+ *
+ * @author Administrator
+ */
+@SuppressWarnings("ALL")
+@Slf4j
+public class OkxKlineWebSocketClient {
+
+ // ==================== 常量 ====================
+
+ /** 心跳超时时间(秒) */
+ private static final int HEARTBEAT_TIMEOUT = 10;
+
+ /** ISO 8601 时间格式化器(毫秒精度,UTC 时区) */
+ private static final DateTimeFormatter ISO_8601_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
+ .withZone(ZoneId.of("UTC"));
+
+ // ==================== 配置 ====================
+
+ /** OKX 配置(提供 WS URL、API 密钥等) */
+ private final OkxConfig config;
+
+ /** OKX API Key */
+ private final String apiKey;
+
+ /** OKX API Secret */
+ private final String apiSecret;
+
+ /** OKX API Passphrase */
+ private final String passphrase;
+
+ // ==================== WebSocket 客户端 ====================
+
+ /** 公开频道 WebSocket 客户端(K线等) */
+ private WebSocketClient publicWsClient;
+
+ /** 私有频道 WebSocket 客户端(仓位、条件单等) */
+ private WebSocketClient privateWsClient;
+
+ /** 心跳检测调度器 */
+ private ScheduledExecutorService heartbeatExecutor;
+
+ /** 心跳超时 Future */
+ private volatile ScheduledFuture<?> pongTimeoutFuture;
+
+ /** 最后收到消息的时间戳(毫秒) */
+ private final AtomicReference<Long> lastMessageTime = new AtomicReference<>(System.currentTimeMillis());
+
+ // ==================== 连接状态 ====================
+
+ /** 公开频道连接状态 */
+ private final AtomicBoolean isPublicConnected = new AtomicBoolean(false);
+
+ /** 私有频道连接状态 */
+ private final AtomicBoolean isPrivateConnected = new AtomicBoolean(false);
+
+ /** 连接中标记,防重入 */
+ private final AtomicBoolean isConnecting = new AtomicBoolean(false);
+
+ /** 初始化标记,防重复 init */
+ private final AtomicBoolean isInitialized = new AtomicBoolean(false);
+
+ /** 私有频道登录成功标记 */
+ private final AtomicBoolean isPrivateLoggedIn = new AtomicBoolean(false);
+
+ // ==================== 频道处理器 ====================
+
+ /** 公开频道处理器列表(如 K线) */
+ private final List<OkxChannelHandler> publicHandlers = new ArrayList<>();
+
+ /** 私有频道处理器列表(如 仓位、条件单) */
+ private final List<OkxChannelHandler> privateHandlers = new ArrayList<>();
+
+ // ==================== 异步线程池 ====================
+
+ /** 重连等异步任务的缓存线程池(daemon 线程) */
+ private final ExecutorService sharedExecutor = Executors.newCachedThreadPool(r -> {
+ Thread t = new Thread(r, "okx-ws-worker");
+ t.setDaemon(true);
+ return t;
+ });
+
+ // ==================== 重连配置 ====================
+
+ /** 重连最大次数 */
+ private static final int MAX_RECONNECT_ATTEMPTS = 3;
+
+ /** 重连初始延迟(毫秒) */
+ private static final long INITIAL_RECONNECT_DELAY_MS = 5000;
+
+ // ==================== 构造器 ====================
+
+ /**
+ * 构造 OKX WebSocket 客户端。
+ *
+ * @param config OKX 配置(提供 WS URL、API 密钥等)
+ */
+ public OkxKlineWebSocketClient(OkxConfig config) {
+ this.config = config;
+ this.apiKey = config.getApiKey();
+ this.apiSecret = config.getApiSecret();
+ this.passphrase = config.getPassphrase();
+ }
+
+ // ==================== Handler 注册 ====================
+
+ /**
+ * 注册公开频道处理器(如 K线)。需在 init() 前调用。
+ *
+ * @param handler 实现了 {@link OkxChannelHandler} 接口的公开频道处理器
+ */
+ public void addPublicHandler(OkxChannelHandler handler) {
+ publicHandlers.add(handler);
+ log.info("[OKX-WS] 注册公开频道处理器: {}", handler.getChannelName());
+ }
+
+ /**
+ * 注册私有频道处理器(如 仓位、条件单)。需在 init() 前调用。
+ *
+ * @param handler 实现了 {@link OkxChannelHandler} 接口的私有频道处理器
+ */
+ public void addPrivateHandler(OkxChannelHandler handler) {
+ privateHandlers.add(handler);
+ log.info("[OKX-WS] 注册私有频道处理器: {}", handler.getChannelName());
+ }
+
+ // ==================== 生命周期 ====================
+
+ /**
+ * 初始化:建立公开 WS 连接 + 私有 WS 连接 → 启动心跳检测。
+ * 使用 {@code AtomicBoolean} 防重入,同一实例只允许初始化一次。
+ */
+ public void init() {
+ if (!isInitialized.compareAndSet(false, true)) {
+ log.warn("[OKX-WS] 已初始化过,跳过重复初始化");
+ return;
+ }
+ connect(false); // 公开 WS
+ connect(true); // 私有 WS
+ startHeartbeat();
+ }
+
+ /**
+ * 销毁:取消所有频道订阅 → 关闭两条 WebSocket 连接 → 关闭线程池。
+ *
+ * <h3>执行顺序</h3>
+ * 先取消订阅(等待 500ms 确保发送完成),再 closeBlocking 关闭连接,
+ * 最后 shutdown 线程池。先关连接再关线程池,避免 onClose 回调中的重连任务访问已关闭的线程池。
+ */
+ public void destroy() {
+ log.info("[OKX-WS] 开始销毁...");
+
+ // 取消公开频道订阅
+ if (publicWsClient != null && publicWsClient.isOpen()) {
+ for (OkxChannelHandler handler : publicHandlers) {
+ handler.unsubscribe(publicWsClient);
+ }
+ }
+ // 取消私有频道订阅
+ if (privateWsClient != null && privateWsClient.isOpen()) {
+ for (OkxChannelHandler handler : privateHandlers) {
+ handler.unsubscribe(privateWsClient);
+ }
+ }
+
+ // 等待取消订阅消息发出
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.warn("[OKX-WS] 取消订阅等待被中断");
+ }
+
+ // 关闭公开 WS
+ closeWebSocket(publicWsClient);
+ publicWsClient = null;
+
+ // 关闭私有 WS
+ closeWebSocket(privateWsClient);
+ privateWsClient = null;
+
+ // 关闭心跳
+ shutdownExecutorGracefully(heartbeatExecutor);
+ if (pongTimeoutFuture != null) {
+ pongTimeoutFuture.cancel(true);
+ }
+
+ // 关闭共享线程池
+ shutdownExecutorGracefully(sharedExecutor);
+
+ log.info("[OKX-WS] 销毁完成");
+ }
+
+ /**
+ * 安全关闭 WebSocket 连接。
+ */
+ private void closeWebSocket(WebSocketClient ws) {
+ if (ws != null && ws.isOpen()) {
+ try {
+ ws.closeBlocking();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.warn("[OKX-WS] 关闭连接时被中断");
+ }
+ }
+ }
+
+ // ==================== 连接管理 ====================
+
+ /**
+ * 建立 WebSocket 连接。
+ *
+ * <h3>公开 WS 连接成功回调</h3>
+ * 订阅所有公开 handlers(K线等)。
+ *
+ * <h3>私有 WS 连接成功回调</h3>
+ * 先发送 login 认证消息,登录成功后再订阅所有私有 handlers。
+ *
+ * <h3>连接关闭回调</h3>
+ * 设置断连状态 → 异步触发指数退避重连(最多3次)。
+ *
+ * @param isPrivate true=私有 WS(需登录),false=公开 WS
+ */
+ private void connect(boolean isPrivate) {
+ String wsUrl = isPrivate ? config.getWsPrivateUrl() : config.getWsPublicUrl();
+ String label = isPrivate ? "私有" : "公开";
+
+ if (isConnecting.get() || !isConnecting.compareAndSet(false, true)) {
+ log.info("[OKX-WS] 连接进行中,跳过重复{} WS请求", label);
+ return;
+ }
+ try {
+ SSLConfig.configureSSL();
+ System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
+ URI uri = new URI(wsUrl);
+
+ WebSocketClient client = new WebSocketClient(uri) {
+ @Override
+ public void onOpen(ServerHandshake handshake) {
+ log.info("[OKX-WS] {} WS连接成功", label);
+ isConnecting.set(false);
+
+ if (isPrivate) {
+ isPrivateConnected.set(true);
+ // 私有 WS 需要先登录
+ sendLogin(this);
+ } else {
+ isPublicConnected.set(true);
+ // 公开 WS 直接订阅
+ if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
+ resetHeartbeatTimer();
+ for (OkxChannelHandler handler : publicHandlers) {
+ handler.subscribe(this);
+ }
+ } else {
+ log.warn("[OKX-WS] 应用正在关闭,忽略{} WS连接成功回调", label);
+ }
+ }
+ }
+
+ @Override
+ public void onMessage(String message) {
+ lastMessageTime.set(System.currentTimeMillis());
+ handleMessage(message, isPrivate, this);
+ resetHeartbeatTimer();
+ }
+
+ @Override
+ public void onClose(int code, String reason, boolean remote) {
+ log.warn("[OKX-WS] {} WS连接关闭, code:{}, reason:{}, remote:{}", label, code, reason, remote);
+ if (isPrivate) {
+ isPrivateConnected.set(false);
+ isPrivateLoggedIn.set(false);
+ } else {
+ isPublicConnected.set(false);
+ }
+ isConnecting.set(false);
+ cancelPongTimeout();
+
+ if (sharedExecutor != null && !sharedExecutor.isShutdown() && !sharedExecutor.isTerminated()) {
+ sharedExecutor.execute(() -> {
+ try {
+ reconnectWithBackoff(isPrivate);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (Exception e) {
+ log.error("[OKX-WS] {} WS重连失败", label, e);
+ }
+ });
+ } else {
+ log.warn("[OKX-WS] 线程池已关闭,不执行{} WS重连", label);
+ }
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ log.error("[OKX-WS] {} WS发生错误", label, ex);
+ if (isPrivate) {
+ isPrivateConnected.set(false);
+ } else {
+ isPublicConnected.set(false);
+ }
+ }
+ };
+ client.setConnectionLostTimeout(0);
+ client.connect();
+
+ if (isPrivate) {
+ this.privateWsClient = client;
+ } else {
+ this.publicWsClient = client;
+ }
+ } catch (URISyntaxException e) {
+ log.error("[OKX-WS] URI格式错误: {}", wsUrl, e);
+ isConnecting.set(false);
+ }
+ }
+
+ // ==================== 登录认证 ====================
+
+ /**
+ * 发送 OKX 私有 WS 登录消息。
+ *
+ * <h3>签名算法</h3>
+ * <pre>
+ * timestamp = ISO 8601 当前时间 (UTC, 毫秒精度)
+ * message = timestamp + "GET" + "/users/self/verify" + ""
+ * sign = Base64(HMAC-SHA256(apiSecret, message))
+ * </pre>
+ *
+ * <h3>登录消息格式</h3>
+ * <pre>
+ * {
+ * "op": "login",
+ * "args": [{
+ * "apiKey": "...",
+ * "passphrase": "...",
+ * "timestamp": "2023-01-01T00:00:00.000Z",
+ * "sign": "..."
+ * }]
+ * }
+ * </pre>
+ *
+ * @param ws 私有频道 WebSocket 客户端
+ */
+ private void sendLogin(WebSocketClient ws) {
+ try {
+ // OKX WS 登录必须使用 Unix 秒级时间戳(非 ISO 8601!)
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ String message = timestamp + "GET" + "/users/self/verify";
+ String sign = hmacSha256Base64(apiSecret, message);
+
+ JSONObject loginMsg = new JSONObject();
+ loginMsg.put("op", "login");
+
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("apiKey", apiKey);
+ arg.put("passphrase", passphrase);
+ arg.put("timestamp", timestamp);
+ arg.put("sign", sign);
+ args.add(arg);
+ loginMsg.put("args", args);
+
+ ws.send(loginMsg.toJSONString());
+ log.info("[OKX-WS] 发送登录消息, timestamp: {}", timestamp);
+ } catch (Exception e) {
+ log.error("[OKX-WS] 发送登录消息失败", e);
+ }
+ }
+
+ /**
+ * HMAC-SHA256 签名并 Base64 编码。
+ *
+ * @param secret 密钥
+ * @param message 待签名消息
+ * @return Base64 编码的签名字符串
+ */
+ private String hmacSha256Base64(String secret, String message) {
+ try {
+ Mac mac = Mac.getInstance("HmacSHA256");
+ SecretKeySpec spec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+ mac.init(spec);
+ byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
+ return Base64.getEncoder().encodeToString(hash);
+ } catch (Exception e) {
+ log.error("[OKX-WS] HMAC-SHA256签名失败", e);
+ return "";
+ }
+ }
+
+ // ==================== 消息路由 ====================
+
+ /**
+ * 消息分发:先处理系统事件(ping/pong/login/subscribe/error),
+ * 再把数据推送路由到对应的 channelHandler。
+ *
+ * <h3>路由规则</h3>
+ * <ol>
+ * <li>"pong" → 日志(忽略)</li>
+ * <li>"ping" → 回复 "pong"</li>
+ * <li>{"event":"login"} → 登录成功 → 订阅所有私有 handlers</li>
+ * <li>{"event":"subscribe"} → 标记对应 handler subscribed=true</li>
+ * <li>{"event":"error"} → 错误日志</li>
+ * <li>{"arg":{...}, "data":[...]} → 遍历 handlers 路由</li>
+ * </ol>
+ *
+ * @param message 原始消息文本
+ * @param isPrivate true=私有频道消息,false=公开频道消息
+ * @param ws 接收消息的 WebSocket 客户端
+ */
+ private void handleMessage(String message, boolean isPrivate, WebSocketClient ws) {
+ try {
+ // OKX ping/pong 混合格式兼容:JSON {"op":"ping"} 与纯文本 "ping" 均支持
+ if ("pong".equals(message)) {
+ log.debug("[OKX-WS] 收到 pong 响应(纯文本)");
+ cancelPongTimeout();
+ return;
+ }
+ if ("ping".equals(message)) {
+ log.debug("[OKX-WS] 收到 ping(纯文本),回复 pong");
+ if (ws != null && ws.isOpen()) {
+ ws.send("pong");
+ }
+ return;
+ }
+
+ JSONObject response = JSON.parseObject(message);
+
+ // JSON 格式的 ping/pong (OKX 文档标准格式)
+ String op = response.getString("op");
+ if ("pong".equals(op)) {
+ log.debug("[OKX-WS] 收到 pong 响应");
+ cancelPongTimeout();
+ return;
+ }
+ if ("ping".equals(op)) {
+ log.debug("[OKX-WS] 收到 ping,回复 pong");
+ if (ws != null && ws.isOpen()) {
+ ws.send("{\"op\":\"pong\"}");
+ }
+ return;
+ }
+
+ // 登录响应
+ String event = response.getString("event");
+ if ("login".equals(event)) {
+ String code = response.getString("code");
+ if ("0".equals(code)) {
+ log.info("[OKX-WS] 私有频道登录成功");
+ isPrivateLoggedIn.set(true);
+ // 登录成功后订阅所有私有频道
+ for (OkxChannelHandler handler : privateHandlers) {
+ handler.subscribe(ws);
+ }
+ } else {
+ log.error("[OKX-WS] 私有频道登录失败, code:{}, msg:{}",
+ code, response.getString("msg"));
+ }
+ return;
+ }
+
+ // 订阅确认
+ if ("subscribe".equals(event)) {
+ JSONObject arg = response.getJSONObject("arg");
+ if (arg != null) {
+ String channel = arg.getString("channel");
+ log.info("[OKX-WS] 订阅成功: {}", channel);
+ List<OkxChannelHandler> handlers = isPrivate ? privateHandlers : publicHandlers;
+ for (OkxChannelHandler handler : handlers) {
+ if (channel.equals(handler.getChannelName())) {
+ handler.setSubscribed(true);
+ break;
+ }
+ }
+ }
+ return;
+ }
+
+ // 取消订阅确认
+ if ("unsubscribe".equals(event)) {
+ JSONObject arg = response.getJSONObject("arg");
+ log.info("[OKX-WS] 取消订阅成功: {}",
+ arg != null ? arg.getString("channel") : "unknown");
+ return;
+ }
+
+ // 错误
+ if ("error".equals(event)) {
+ log.error("[OKX-WS] 错误, code:{}, msg:{}",
+ response.getString("code"), response.getString("msg"));
+ return;
+ }
+
+ // 数据推送: {"arg":{"channel":"positions",...}, "data":[...]}
+ JSONObject arg = response.getJSONObject("arg");
+ if (arg != null && response.getJSONArray("data") != null) {
+ String channel = arg.getString("channel");
+ if (channel == null) {
+ return;
+ }
+ List<OkxChannelHandler> handlers = isPrivate ? privateHandlers : publicHandlers;
+ for (OkxChannelHandler handler : handlers) {
+ if (handler.handleMessage(response)) {
+ return;
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("[OKX-WS] 处理消息失败: {}", message, e);
+ }
+ }
+
+ // ==================== 订阅状态检查 ====================
+
+ /**
+ * 检查所有已注册的频道是否都已收到订阅成功确认。
+ * 同时检查公开和私有频道的 handlers。
+ *
+ * @return true 如果所有 handlers 都已订阅确认
+ */
+ public boolean areAllSubscribed() {
+ List<OkxChannelHandler> allHandlers = new ArrayList<>();
+ allHandlers.addAll(publicHandlers);
+ allHandlers.addAll(privateHandlers);
+
+ if (allHandlers.isEmpty()) {
+ return false;
+ }
+ for (OkxChannelHandler h : allHandlers) {
+ if (!h.isSubscribed()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // ==================== 心跳机制 ====================
+
+ /**
+ * 启动心跳检测器。
+ * 使用单线程 ScheduledExecutor,每 25 秒检查一次心跳超时。
+ */
+ private void startHeartbeat() {
+ if (heartbeatExecutor != null && !heartbeatExecutor.isTerminated()) {
+ heartbeatExecutor.shutdownNow();
+ }
+ heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread t = new Thread(r, "okx-ws-heartbeat");
+ t.setDaemon(true);
+ return t;
+ });
+ heartbeatExecutor.scheduleWithFixedDelay(
+ this::checkHeartbeatTimeout, 25, 25, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 重置心跳计时器:取消旧超时任务,提交新的 10 秒超时检测。
+ */
+ private synchronized void resetHeartbeatTimer() {
+ cancelPongTimeout();
+ if (heartbeatExecutor != null && !heartbeatExecutor.isShutdown()) {
+ pongTimeoutFuture = heartbeatExecutor.schedule(
+ this::checkHeartbeatTimeout, HEARTBEAT_TIMEOUT, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * 检查心跳超时:如果距离上次收到消息超过 10 秒,主动发送 ping。
+ * OKX 服务端收到 ping 后会回复 pong。
+ */
+ private void checkHeartbeatTimeout() {
+ boolean isAnyConnected = isPublicConnected.get() || isPrivateConnected.get();
+ if (!isAnyConnected) {
+ return;
+ }
+ long elapsed = System.currentTimeMillis() - lastMessageTime.get();
+ if (elapsed >= HEARTBEAT_TIMEOUT * 1000L) {
+ log.debug("[OKX-WS] 心跳超时 {}ms, 主动发送ping", elapsed);
+ sendPing();
+ }
+ }
+
+ /**
+ * 向两条 WS 连接主动发送 ping(OKX 文档标准 JSON 格式)。
+ */
+ private void sendPing() {
+ try {
+ String pingMsg = "{\"op\":\"ping\"}";
+ if (publicWsClient != null && publicWsClient.isOpen()) {
+ publicWsClient.send(pingMsg);
+ }
+ if (privateWsClient != null && privateWsClient.isOpen()) {
+ privateWsClient.send(pingMsg);
+ }
+ } catch (Exception e) {
+ log.warn("[OKX-WS] 发送ping失败", e);
+ }
+ }
+
+ /**
+ * 取消心跳超时检测任务。
+ */
+ private synchronized void cancelPongTimeout() {
+ if (pongTimeoutFuture != null && !pongTimeoutFuture.isDone()) {
+ pongTimeoutFuture.cancel(true);
+ }
+ }
+
+ // ==================== 重连机制 ====================
+
+ /**
+ * 指数退避重连。初始延迟 5 秒,每次翻倍,最多重试 3 次。
+ *
+ * @param isPrivate true=重连私有 WS,false=重连公开 WS
+ * @throws InterruptedException 线程被中断
+ */
+ private void reconnectWithBackoff(boolean isPrivate) throws InterruptedException {
+ String label = isPrivate ? "私有" : "公开";
+ int attempt = 0;
+ long delayMs = INITIAL_RECONNECT_DELAY_MS;
+
+ while (attempt < MAX_RECONNECT_ATTEMPTS) {
+ try {
+ Thread.sleep(delayMs);
+ connect(isPrivate);
+ log.info("[OKX-WS] {} WS第{}次重连成功", label, attempt + 1);
+ return;
+ } catch (Exception e) {
+ log.warn("[OKX-WS] {} WS第{}次重连失败", label, attempt + 1, e);
+ delayMs *= 2;
+ attempt++;
+ }
+ }
+ log.error("[OKX-WS] {} WS超过最大重试次数({}),放弃重连", label, MAX_RECONNECT_ATTEMPTS);
+ }
+
+ // ==================== 工具方法 ====================
+
+ /**
+ * 优雅关闭线程池:先 shutdown,等待 5 秒,超时则 shutdownNow 强制中断。
+ *
+ * @param executor 需要关闭的线程池
+ */
+ private void shutdownExecutorGracefully(ExecutorService executor) {
+ if (executor == null || executor.isTerminated()) {
+ return;
+ }
+ try {
+ executor.shutdown();
+ if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+ executor.shutdownNow();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ executor.shutdownNow();
+ }
+ }
+
+ // ==================== 状态查询 ====================
+
+ /** @return 公开 WS 是否已连接 */
+ public boolean isPublicConnected() {
+ return isPublicConnected.get();
+ }
+
+ /** @return 私有 WS 是否已连接并登录成功 */
+ public boolean isPrivateConnected() {
+ return isPrivateConnected.get() && isPrivateLoggedIn.get();
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/OkxTradeExecutor.java b/src/main/java/com/xcong/excoin/modules/okxApi/OkxTradeExecutor.java
new file mode 100644
index 0000000..0b8cb40
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/OkxTradeExecutor.java
@@ -0,0 +1,597 @@
+package com.xcong.excoin.modules.okxApi;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * OKX REST API 异步执行器,所有下单/撤单操作经此类提交。
+ *
+ * <h3>设计目的</h3>
+ * REST API 调用可能耗时数百毫秒,若在 WebSocket 回调线程中同步执行会阻塞消息处理,
+ * 导致心跳超时误判。本类将所有网络 I/O 提交到独立单线程池异步执行。
+ *
+ * <h3>与 GateTradeExecutor 的主要差异</h3>
+ * <ul>
+ * <li>使用 OkHttp 直接调用 OKX REST API,而非 gateapi SDK</li>
+ * <li>签名算法:HMAC-SHA256(Gate 使用 HMAC-SHA512)</li>
+ * <li>认证头:OK-ACCESS-KEY / OK-ACCESS-SIGN / OK-ACCESS-TIMESTAMP / OK-ACCESS-PASSPHRASE</li>
+ * <li>时间戳格式:ISO 8601(如 2023-01-01T00:00:00.000Z)</li>
+ * <li>合约格式:ETH-USDT-SWAP(短横线分隔)</li>
+ * </ul>
+ *
+ * <h3>线程模型</h3>
+ * <ul>
+ * <li><b>单线程 + 有界队列(64)</b> — 保证下单顺序,避免并发竞争</li>
+ * <li><b>CallerRunsPolicy</b> — 队列满时由提交线程直接执行,形成自然背压</li>
+ * <li><b>Daemon 线程</b> — 60s 空闲自动回收</li>
+ * </ul>
+ *
+ * <h3>对外接口</h3>
+ * <table>
+ * <tr><th>方法</th><th>用途</th></tr>
+ * <tr><td>openLong / openShort</td><td>市价基底开仓</td></tr>
+ * <tr><td>placeConditionalEntryOrder</td><td>挂条件开仓单(价格触发后市价开仓)</td></tr>
+ * <tr><td>placeTakeProfit</td><td>挂止盈条件单</td></tr>
+ * <tr><td>cancelConditionalOrder</td><td>取消单个条件单</td></tr>
+ * <tr><td>cancelAllPriceTriggeredOrders</td><td>取消所有条件单(策略停止时)</td></tr>
+ * </table>
+ *
+ * <h3>容错</h3>
+ * <ul>
+ * <li>止盈单创建失败 → 立即 marketClose() 市价平仓</li>
+ * <li>取消订单失败 → 仅 warn 日志(可能已成交/已取消)</li>
+ * </ul>
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class OkxTradeExecutor {
+
+ /** JSON content-type */
+ private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
+
+ /** OKX 配置 */
+ private final OkxConfig config;
+
+ /** 合约名称(如 ETH-USDT-SWAP) */
+ private final String contract;
+
+ /** OKHttp 客户端 */
+ private final OkHttpClient httpClient;
+
+ /** 交易线程池:单线程 + 有界队列 + 背压策略 */
+ private final ExecutorService executor;
+
+ /**
+ * 构造 OKX 交易执行器。
+ *
+ * @param config OKX 配置对象(包含 API 密钥、合约、URL 等信息)
+ */
+ public OkxTradeExecutor(OkxConfig config) {
+ this.config = config;
+ this.contract = config.getContract();
+ this.httpClient = new OkHttpClient.Builder()
+ .connectTimeout(10, TimeUnit.SECONDS)
+ .readTimeout(10, TimeUnit.SECONDS)
+ .writeTimeout(10, TimeUnit.SECONDS)
+ .build();
+ this.executor = new ThreadPoolExecutor(
+ 1, 1,
+ 60L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>(64),
+ r -> {
+ Thread t = new Thread(r, "okx-trade-worker");
+ t.setDaemon(true);
+ return t;
+ },
+ new ThreadPoolExecutor.CallerRunsPolicy()
+ );
+ ((ThreadPoolExecutor) executor).allowCoreThreadTimeOut(true);
+ }
+
+ // ==================== 生命周期 ====================
+
+ /**
+ * 优雅关闭:等待 10 秒让队列中的任务执行完毕,超时则强制中断。
+ * 关闭后的 REST 调用将通过 CallerRunsPolicy 直接在提交线程执行。
+ */
+ public void shutdown() {
+ executor.shutdown();
+ try {
+ executor.awaitTermination(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ executor.shutdownNow();
+ }
+ }
+
+ /**
+ * 提交一个通用任务到交易线程池末尾。
+ * 利用单线程池的 FIFO 特性确保任务按提交顺序执行。
+ *
+ * @param task 待执行的任务
+ */
+ public void submitTask(Runnable task) {
+ executor.execute(task);
+ }
+
+ // ==================== 市价开仓 ====================
+
+ /**
+ * 异步市价开多。
+ *
+ * @param quantity 开仓张数(正数,如 "15")
+ * @param onSuccess 成交成功回调,接收 ordId(可为 null)
+ * @param onFailure 成交失败回调(可为 null)
+ */
+ public void openLong(String quantity, Consumer<String> onSuccess, Runnable onFailure) {
+ openPosition(quantity, "buy", "开多", onSuccess, onFailure);
+ }
+
+ /**
+ * 异步市价开空。
+ *
+ * @param quantity 开仓张数(正数,如 "15")
+ * @param onSuccess 成交成功回调,接收 ordId(可为 null)
+ * @param onFailure 成交失败回调(可为 null)
+ */
+ public void openShort(String quantity, Consumer<String> onSuccess, Runnable onFailure) {
+ openPosition(quantity, "sell", "开空", onSuccess, onFailure);
+ }
+
+ /**
+ * 通用异步市价下单。
+ *
+ * @param sz 下单张数
+ * @param side 交易方向(buy=开多 / sell=开空)
+ * @param label 日志标签
+ * @param onSuccess 成功回调,接收 ordId
+ * @param onFailure 失败回调
+ */
+ private void openPosition(String sz, String side, String label,
+ Consumer<String> onSuccess, Runnable onFailure) {
+ executor.execute(() -> {
+ try {
+ // long_short_mode 双向持仓下,开仓须指定 posSide
+ String posSide = "buy".equals(side) ? "long" : "short";
+ JSONObject body = new JSONObject();
+ body.put("instId", contract);
+ body.put("tdMode", "cross");
+ body.put("side", side);
+ body.put("posSide", posSide);
+ body.put("ordType", "market");
+ body.put("sz", sz);
+
+ JSONObject resp = okPost("/api/v5/trade/order", body.toJSONString());
+ String code = resp.getString("code");
+ if (!"0".equals(code)) {
+ log.error("[TradeExec-OKX] {}失败, code:{}, msg:{}", label, code, resp.getString("msg"));
+ if (onFailure != null) {
+ onFailure.run();
+ }
+ return;
+ }
+ JSONArray data = resp.getJSONArray("data");
+ String ordId = (data != null && !data.isEmpty())
+ ? data.getJSONObject(0).getString("ordId") : null;
+ log.info("[TradeExec-OKX] {}成功, sz:{}, ordId:{}", label, sz, ordId);
+ if (onSuccess != null) {
+ onSuccess.accept(ordId);
+ }
+ } catch (Exception e) {
+ log.error("[TradeExec-OKX] {}失败", label, e);
+ if (onFailure != null) {
+ onFailure.run();
+ }
+ }
+ });
+ }
+
+ // ==================== 止盈条件单 ====================
+
+ /**
+ * 异步创建止盈条件单(OKX 算法订单 — conditional 类型)。
+ *
+ * <p>服务器监控价格,达到触发价后自动以市价平仓。
+ * 使用 OKX 的 {@code order-algo} 接口,ordType=conditional。
+ *
+ * <h3>止盈失败兜底</h3>
+ * 止盈单创建失败时立即调用 {@link #marketClose(String, String)} 市价平仓,
+ * 确保仓位不会因止损条件未挂上而无保护。
+ *
+ * @param triggerPrice 触发价格
+ * @param orderType 平仓类型:"close_long" 平多 / "close_short" 平空
+ * @param size 平仓张数(正数,如 "15")
+ * @param onSuccess 成功回调,接收 algoId(可为 null)
+ */
+ public void placeTakeProfit(BigDecimal triggerPrice,
+ String orderType,
+ String size,
+ Consumer<String> onSuccess) {
+ executor.execute(() -> {
+ String posSide = null;
+ try {
+ String side;
+ if ("close_long".equals(orderType)) {
+ side = "sell";
+ posSide = "long";
+ } else if ("close_short".equals(orderType)) {
+ side = "buy";
+ posSide = "short";
+ } else {
+ log.error("[TradeExec-OKX] 未知止盈类型: {}", orderType);
+ return;
+ }
+
+ // OKX conditional 止盈止损使用 tpTriggerPx/tpOrdPx 或 slTriggerPx/slOrdPx
+ JSONObject body = new JSONObject();
+ body.put("instId", contract);
+ body.put("tdMode", "cross");
+ body.put("side", side);
+ body.put("posSide", posSide);
+ body.put("ordType", "conditional");
+ body.put("sz", size);
+ // "close_long"=平多(止盈), "close_short"=平空(止盈)
+ body.put("tpTriggerPx", triggerPrice.stripTrailingZeros().toPlainString());
+ body.put("tpTriggerPxType", "last");
+ body.put("tpOrdPx", "-1");
+
+ JSONObject resp = okPost("/api/v5/trade/order-algo", body.toJSONString());
+ String code = resp.getString("code");
+ if (!"0".equals(code)) {
+ log.error("[TradeExec-OKX] 止盈单创建失败, code:{}, msg:{}, 立即市价止盈",
+ code, resp.getString("msg"));
+ marketClose(size, posSide);
+ return;
+ }
+ JSONArray data = resp.getJSONArray("data");
+ String algoId = (data != null && !data.isEmpty())
+ ? data.getJSONObject(0).getString("algoId") : null;
+ log.info("[TradeExec-OKX] 止盈单已创建, triggerPx:{}, type:{}, sz:{}, algoId:{}",
+ triggerPrice, orderType, size, algoId);
+ if (onSuccess != null) {
+ onSuccess.accept(algoId);
+ }
+ } catch (Exception e) {
+ log.error("[TradeExec-OKX] 止盈单创建失败, triggerPx:{}, sz:{}, 立即市价止盈",
+ triggerPrice, size, e);
+ if (posSide != null) {
+ marketClose(size, posSide);
+ }
+ }
+ });
+ }
+
+ /**
+ * 市价止盈兜底:在止盈条件单创建失败时立即市价平仓。
+ *
+ * <p>通过 posSide 指定平仓方向:
+ * <ul>
+ * <li>posSide=long:平多(side=sell)</li>
+ * <li>posSide=short:平空(side=buy)</li>
+ * </ul>
+ *
+ * @param size 平仓张数(正数)
+ * @param posSide 持仓方向(long / short)
+ */
+ private void marketClose(String size, String posSide) {
+ String side = "long".equals(posSide) ? "sell" : "buy";
+ marketClose(size, side, posSide);
+ }
+
+ /**
+ * 指定方向的市价平仓。
+ *
+ * @param sz 平仓张数
+ * @param side 交易方向(sell=平多 / buy=平空)
+ * @param posSide 持仓方向(long / short)
+ */
+ private void marketClose(String sz, String side, String posSide) {
+ try {
+ JSONObject body = new JSONObject();
+ body.put("instId", contract);
+ body.put("tdMode", "cross");
+ body.put("side", side);
+ body.put("posSide", posSide);
+ body.put("ordType", "market");
+ body.put("sz", sz);
+
+ JSONObject resp = okPost("/api/v5/trade/order", body.toJSONString());
+ String code = resp.getString("code");
+ if (!"0".equals(code)) {
+ log.warn("[TradeExec-OKX] 市价止盈失败, side:{}, posSide:{}, sz:{}, code:{}, msg:{}",
+ side, posSide, sz, code, resp.getString("msg"));
+ return;
+ }
+ JSONArray data = resp.getJSONArray("data");
+ String ordId = (data != null && !data.isEmpty())
+ ? data.getJSONObject(0).getString("ordId") : null;
+ log.info("[TradeExec-OKX] 市价止盈成功, side:{}, posSide:{}, sz:{}, ordId:{}",
+ side, posSide, sz, ordId);
+ } catch (Exception e) {
+ log.error("[TradeExec-OKX] 市价止盈也失败, sz:{}", sz, e);
+ }
+ }
+
+ // ==================== 条件开仓单 ====================
+
+ /**
+ * 异步创建条件开仓单(价格触发后市价开仓)。
+ *
+ * <p>使用 OKX 的 {@code order-algo} 接口,ordType=trigger(计划委托)。
+ * 服务器监控价格,达到触发价后以市价开仓。
+ *
+ * <h3>与止盈止损的区别</h3>
+ * <ul>
+ * <li>开仓 = ordType=trigger,字段 triggerPx + orderPx</li>
+ * <li>止盈 = ordType=conditional,字段 tpTriggerPx + tpOrdPx</li>
+ * <li>止损 = ordType=conditional,字段 slTriggerPx + slOrdPx</li>
+ * </ul>
+ *
+ * @param triggerPrice 触发价格
+ * @param isLong true=开多(side=buy)/ false=开空(side=sell)
+ * @param size 开仓张数(正数,如 "1")
+ * @param onSuccess 成功回调,接收 algoId(可为 null)
+ * @param onFailure 失败回调(可为 null)
+ */
+ public void placeConditionalEntryOrder(BigDecimal triggerPrice,
+ boolean isLong,
+ String size,
+ Consumer<String> onSuccess,
+ Runnable onFailure) {
+ executor.execute(() -> {
+ try {
+ String side = isLong ? "buy" : "sell";
+
+ JSONObject body = new JSONObject();
+ body.put("instId", contract);
+ body.put("tdMode", "cross");
+ body.put("side", side);
+ body.put("ordType", "trigger"); // 计划委托 = 触发后开仓
+ body.put("sz", size);
+ body.put("triggerPx", triggerPrice.stripTrailingZeros().toPlainString());
+ body.put("triggerPxType", "last");
+ body.put("orderPx", "-1"); // OKX 使用 orderPx,非 ordPx
+
+ JSONObject resp = okPost("/api/v5/trade/order-algo", body.toJSONString());
+ String code = resp.getString("code");
+ if (!"0".equals(code)) {
+ log.error("[TradeExec-OKX] 条件开仓单创建失败, code:{}, msg:{}",
+ code, resp.getString("msg"));
+ if (onFailure != null) {
+ onFailure.run();
+ }
+ return;
+ }
+ JSONArray data = resp.getJSONArray("data");
+ String algoId = (data != null && !data.isEmpty())
+ ? data.getJSONObject(0).getString("algoId") : null;
+ log.info("[TradeExec-OKX] 条件开仓单已创建, triggerPx:{}, isLong:{}, sz:{}, algoId:{}",
+ triggerPrice, isLong, size, algoId);
+ if (onSuccess != null) {
+ onSuccess.accept(algoId);
+ }
+ } catch (Exception e) {
+ log.error("[TradeExec-OKX] 条件开仓单创建失败, triggerPx:{}, sz:{}",
+ triggerPrice, size, e);
+ if (onFailure != null) {
+ onFailure.run();
+ }
+ }
+ });
+ }
+
+ // ==================== 取消订单 ====================
+
+ /**
+ * 异步取消单个算法订单(条件单)。
+ *
+ * @param algoId 算法订单 ID,为 null 时跳过
+ * @param onSuccess 成功回调,接收 algoId(可为 null)
+ */
+ public void cancelConditionalOrder(String algoId, Consumer<String> onSuccess) {
+ if (algoId == null) {
+ return;
+ }
+ executor.execute(() -> {
+ try {
+ JSONArray bodyArr = new JSONArray();
+ JSONObject item = new JSONObject();
+ item.put("algoId", algoId);
+ item.put("instId", contract);
+ bodyArr.add(item);
+
+ JSONObject resp = okPost("/api/v5/trade/cancel-algos", bodyArr.toJSONString());
+ String code = resp.getString("code");
+ if (!"0".equals(code)) {
+ log.warn("[TradeExec-OKX] 取消条件单失败(可能已触发), algoId:{}, code:{}, msg:{}",
+ algoId, code, resp.getString("msg"));
+ return;
+ }
+ log.info("[TradeExec-OKX] 条件单已取消, algoId:{}", algoId);
+ if (onSuccess != null) {
+ onSuccess.accept(algoId);
+ }
+ } catch (Exception e) {
+ log.warn("[TradeExec-OKX] 取消条件单失败(可能已触发), algoId:{}", algoId, e);
+ }
+ });
+ }
+
+ /**
+ * 异步清除指定合约的所有算法订单(条件单)。
+ * 发送不含 algoId 的取消请求,OKX 会取消该合约下所有待触发算法单。
+ */
+ public void cancelAllPriceTriggeredOrders() {
+ executor.execute(() -> {
+ try {
+ JSONArray bodyArr = new JSONArray();
+ JSONObject item = new JSONObject();
+ item.put("instId", contract);
+ bodyArr.add(item);
+
+ JSONObject resp = okPost("/api/v5/trade/cancel-algos", bodyArr.toJSONString());
+ String code = resp.getString("code");
+ if (!"0".equals(code)) {
+ log.warn("[TradeExec-OKX] 清除所有条件单失败, code:{}, msg:{}",
+ code, resp.getString("msg"));
+ return;
+ }
+ log.info("[TradeExec-OKX] 已清除所有条件单");
+ } catch (Exception e) {
+ log.error("[TradeExec-OKX] 清除条件单失败", e);
+ }
+ });
+ }
+
+ // ==================== HTTP 请求帮助方法 ====================
+
+ /**
+ * 发送 OKX 签名 POST 请求并返回解析后的 JSONObject。
+ *
+ * <p>自动添加 OK-ACCESS-KEY、OK-ACCESS-SIGN、OK-ACCESS-TIMESTAMP、OK-ACCESS-PASSPHRASE
+ * 四个认证头。签名算法:base64(HMAC-SHA256(timestamp + method + path + body))。
+ *
+ * @param path API 路径(如 /api/v5/trade/order)
+ * @param body 请求体 JSON 字符串
+ * @return 解析后的响应 JSONObject
+ * @throws IOException 网络异常或业务错误
+ */
+ JSONObject okPost(String path, String body) throws IOException {
+ String method = "POST";
+ String timestamp = getIsoTimestamp();
+ String sign = null;
+ try {
+ sign = sign(timestamp, method, path, body);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ Request.Builder builder = new Request.Builder()
+ .url(config.getRestBasePath() + path)
+ .header("OK-ACCESS-KEY", config.getApiKey())
+ .header("OK-ACCESS-SIGN", sign)
+ .header("OK-ACCESS-TIMESTAMP", timestamp)
+ .header("OK-ACCESS-PASSPHRASE", config.getPassphrase())
+ .header("Content-Type", "application/json; charset=utf-8")
+ .post(RequestBody.create(JSON_MEDIA_TYPE, body));
+ // 模拟盘需加 x-simulated-trading 头,与生产网共用同一 REST 地址
+ if (!config.isProduction()) {
+ builder.header("x-simulated-trading", "1");
+ }
+ Request request = builder.build();
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "{}";
+ if (!response.isSuccessful()) {
+ log.error("[TradeExec-OKX] HTTP {} POST {}: {}", response.code(), path, responseBody);
+ throw new IOException("HTTP " + response.code() + ": " + responseBody);
+ }
+ return JSON.parseObject(responseBody);
+ }
+ }
+
+ /**
+ * 发送 OKX 签名 GET 请求并返回解析后的 JSONObject。
+ *
+ * <p>GET 请求的签名中 body 为空字符串。
+ *
+ * @param path API 路径(如 /api/v5/account/positions)
+ * @return 解析后的响应 JSONObject
+ * @throws IOException 网络异常
+ */
+ JSONObject okGet(String path) throws IOException {
+ String method = "GET";
+ String timestamp = getIsoTimestamp();
+ String sign = null;
+ try {
+ sign = sign(timestamp, method, path, "");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ Request.Builder builder = new Request.Builder()
+ .url(config.getRestBasePath() + path)
+ .header("OK-ACCESS-KEY", config.getApiKey())
+ .header("OK-ACCESS-SIGN", sign)
+ .header("OK-ACCESS-TIMESTAMP", timestamp)
+ .header("OK-ACCESS-PASSPHRASE", config.getPassphrase())
+ .get();
+ // 模拟盘需加 x-simulated-trading 头
+ if (!config.isProduction()) {
+ builder.header("x-simulated-trading", "1");
+ }
+ Request request = builder.build();
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "{}";
+ if (!response.isSuccessful()) {
+ log.error("[TradeExec-OKX] HTTP {} GET {}: {}", response.code(), path, responseBody);
+ throw new IOException("HTTP " + response.code() + ": " + responseBody);
+ }
+ return JSON.parseObject(responseBody);
+ }
+ }
+
+ // ==================== 签名工具方法 ====================
+
+ /**
+ * 生成 OKX API 签名。
+ *
+ * <p>签名算法:
+ * <ol>
+ * <li>拼接签名字符串:{@code timestamp + method + path + body}</li>
+ * <li>使用 apiSecret 对签名字符串做 HMAC-SHA256</li>
+ * <li>Base64 编码</li>
+ * </ol>
+ *
+ * @param timestamp OKX 格式时间戳(ISO 8601)
+ * @param method HTTP 方法(GET/POST)
+ * @param path API 路径(如 /api/v5/trade/order)
+ * @param body 请求体(GET 请求传 "")
+ * @return Base64 编码的签名字符串
+ * @throws Exception 签名计算异常
+ */
+ private String sign(String timestamp, String method, String path, String body) throws Exception {
+ String signString = timestamp + method + path + body;
+ Mac sha256Hmac = Mac.getInstance("HmacSHA256");
+ SecretKeySpec secretKey = new SecretKeySpec(config.getApiSecret().getBytes(), "HmacSHA256");
+ sha256Hmac.init(secretKey);
+ byte[] signedBytes = sha256Hmac.doFinal(signString.getBytes());
+ return Base64.getEncoder().encodeToString(signedBytes);
+ }
+
+ /**
+ * 获取 OKX 格式的 ISO 8601 时间戳。
+ *
+ * <p>格式示例:{@code 2023-01-01T00:00:00.000Z}
+ *
+ * @return ISO 8601 格式的 UTC 时间戳字符串
+ */
+ private String getIsoTimestamp() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return sdf.format(new Date());
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/OkxWebSocketClientMain.java b/src/main/java/com/xcong/excoin/modules/okxApi/OkxWebSocketClientMain.java
new file mode 100644
index 0000000..8fdcdbb
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/OkxWebSocketClientMain.java
@@ -0,0 +1,63 @@
+package com.xcong.excoin.modules.okxApi;
+
+import com.xcong.excoin.modules.okxApi.wsHandler.handler.MarkPriceOkxChannelHandler;
+import com.xcong.excoin.modules.okxApi.wsHandler.handler.OrderAlgoOkxChannelHandler;
+import com.xcong.excoin.modules.okxApi.wsHandler.handler.PositionsOkxChannelHandler;
+
+import java.math.BigDecimal;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * OKX 网格策略独立启动入口(用于测试或非 Spring 环境)。
+ *
+ * @author Administrator
+ */
+public class OkxWebSocketClientMain {
+
+ public static void main(String[] args) throws InterruptedException {
+ OkxConfig config = OkxConfig.builder()
+ .apiKey("YOUR_OKX_API_KEY")
+ .apiSecret("YOUR_OKX_API_SECRET")
+ .passphrase("YOUR_OKX_PASSPHRASE")
+ .contract("ETH-USDT-SWAP")
+ .leverage("100")
+ .marginMode("cross")
+ .positionMode("long_short_mode")
+ .gridRate(new BigDecimal("0.003"))
+ .expectedProfit(new BigDecimal("25"))
+ .maxLoss(new BigDecimal("15"))
+ .baseQuantity("15")
+ .quantity("15")
+ .restartGridSpan(6)
+ .priceScale(2)
+ .contractMultiplier(new BigDecimal("0.01"))
+ .isProduction(false)
+ .build();
+
+ OkxGridTradeService gridTradeService = new OkxGridTradeService(config);
+ gridTradeService.init();
+
+ OkxKlineWebSocketClient wsClient = new OkxKlineWebSocketClient(config);
+
+ wsClient.addPublicHandler(new MarkPriceOkxChannelHandler(
+ config.getContract(), gridTradeService));
+ wsClient.addPrivateHandler(new PositionsOkxChannelHandler(
+ config, gridTradeService));
+ wsClient.addPrivateHandler(new OrderAlgoOkxChannelHandler(
+ config, gridTradeService));
+
+ gridTradeService.setWsClient(wsClient);
+ wsClient.init();
+
+ gridTradeService.startGrid();
+
+ // 保持主线程不退出
+ CountDownLatch latch = new CountDownLatch(1);
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ gridTradeService.stopGrid();
+ wsClient.destroy();
+ latch.countDown();
+ }));
+ latch.await();
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/OkxWebSocketClientManager.java b/src/main/java/com/xcong/excoin/modules/okxApi/OkxWebSocketClientManager.java
new file mode 100644
index 0000000..6eb12b2
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/OkxWebSocketClientManager.java
@@ -0,0 +1,117 @@
+package com.xcong.excoin.modules.okxApi;
+
+import com.xcong.excoin.modules.okxApi.wsHandler.handler.MarkPriceOkxChannelHandler;
+import com.xcong.excoin.modules.okxApi.wsHandler.handler.OrderAlgoOkxChannelHandler;
+import com.xcong.excoin.modules.okxApi.wsHandler.handler.PositionsOkxChannelHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.math.BigDecimal;
+
+/**
+ * OKX 模块 Spring 容器入口 — 组件组装 + 生命周期管理。
+ *
+ * <h3>组装顺序({@code @PostConstruct})</h3>
+ * <ol>
+ * <li>{@link OkxConfig} — 构建配置(API 密钥、合约、策略参数)</li>
+ * <li>{@link OkxGridTradeService} — init():切双向持仓 → 清旧条件单 → 平仓 → 设杠杆</li>
+ * <li>{@link OkxKlineWebSocketClient} — 注册 3 个频道处理器 → init():建立 WS 连接并订阅</li>
+ * <li>{@code gridTradeService.startGrid()} — 状态重置,等待首根 K 线</li>
+ * </ol>
+ *
+ * <h3>3 个频道处理器</h3>
+ * <ol>
+ * <li>MarkPriceOkxChannelHandler — 公开频道,标记价格 → onKline() + setMarkPrice()</li>
+ * <li>PositionsOkxChannelHandler — 私有频道,仓位 → onPositionUpdate()</li>
+ * <li>OrderAlgoOkxChannelHandler — 私有频道,条件单状态 → onAutoOrder()</li>
+ * </ol>
+ *
+ * <h3>销毁顺序({@code @PreDestroy})</h3>
+ * <ol>
+ * <li>gridTradeService.stopGrid():取消所有条件单 → 关闭交易线程池</li>
+ * <li>wsClient.destroy():取消订阅 → 断开 WS → 关闭线程池</li>
+ * </ol>
+ *
+ * @author Administrator
+ */
+@Slf4j
+@Component
+public class OkxWebSocketClientManager {
+
+ private OkxKlineWebSocketClient wsClient;
+ private OkxGridTradeService gridTradeService;
+ private OkxConfig config;
+
+ @PostConstruct
+ public void init() {
+ log.info("[OKX管理器] 开始初始化...");
+
+ try {
+ // TODO: 替换为实际 API 密钥
+ config = OkxConfig.builder()
+ .apiKey("ac76252d-e717-4459-a6f9-80512aed5ea0")
+ .apiSecret("A8168543EF4F08A6DBFE27AB23956898")
+ .passphrase("Aa12345678@")
+ .contract("ETH-USDT-SWAP")
+ .leverage("100")
+ .marginMode("cross")
+ .positionMode("long_short_mode")
+ .gridRate(new BigDecimal("0.003"))
+ .expectedProfit(new BigDecimal("25"))
+ .maxLoss(new BigDecimal("15"))
+ .baseQuantity("15")
+ .quantity("15")
+ .restartGridSpan(6)
+ .maxPositionSize(2)
+ .priceScale(2)
+ .contractMultiplier(new BigDecimal("0.01"))
+ .unrealizedPnlPriceMode(OkxConfig.PnLPriceMode.LAST_PRICE)
+ .isProduction(true)
+ .reopenMaxRetries(3)
+ .build();
+
+ // 1. 初始化交易服务
+ gridTradeService = new OkxGridTradeService(config);
+ gridTradeService.init();
+
+ // 2. 创建 WS 客户端并注册频道处理器
+ wsClient = new OkxKlineWebSocketClient(config);
+
+ // 公开频道:标记价格(替代 K 线,同时驱动策略 onKline 和 PnL 计算)
+ wsClient.addPublicHandler(new MarkPriceOkxChannelHandler(
+ config.getContract(), gridTradeService));
+ // 私有频道:仓位
+ wsClient.addPrivateHandler(new PositionsOkxChannelHandler(
+ config, gridTradeService));
+ // 私有频道:条件单
+ wsClient.addPrivateHandler(new OrderAlgoOkxChannelHandler(
+ config, gridTradeService));
+
+ gridTradeService.setWsClient(wsClient);
+ wsClient.init();
+ log.info("[OKX管理器] WS已连接, 已注册 3 个频道处理器");
+
+ // 3. 激活策略
+ gridTradeService.startGrid();
+ } catch (Exception e) {
+ log.error("[OKX管理器] 初始化失败", e);
+ }
+ }
+
+ @PreDestroy
+ public void destroy() {
+ log.info("[OKX管理器] 开始销毁...");
+ if (gridTradeService != null) {
+ gridTradeService.stopGrid();
+ }
+ if (wsClient != null) {
+ wsClient.destroy();
+ }
+ log.info("[OKX管理器] 销毁完成");
+ }
+
+ public OkxKlineWebSocketClient getKlineWebSocketClient() { return wsClient; }
+ public OkxGridTradeService getGridTradeService() { return gridTradeService; }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java b/src/main/java/com/xcong/excoin/modules/okxApi/TraderParam.java
similarity index 99%
rename from src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java
rename to src/main/java/com/xcong/excoin/modules/okxApi/TraderParam.java
index 08ba24a..3a7bf58 100644
--- a/src/main/java/com/xcong/excoin/modules/gateApi/TraderParam.java
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/TraderParam.java
@@ -1,4 +1,4 @@
-package com.xcong.excoin.modules.gateApi;
+package com.xcong.excoin.modules.okxApi;
import java.math.BigDecimal;
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/AbstractOkxPrivateChannelHandler.java b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/AbstractOkxPrivateChannelHandler.java
new file mode 100644
index 0000000..ce9f6c9
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/AbstractOkxPrivateChannelHandler.java
@@ -0,0 +1,160 @@
+package com.xcong.excoin.modules.okxApi.wsHandler;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.modules.okxApi.OkxGridTradeService;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+
+/**
+ * OKX 私有频道 WS 处理器的抽象基类。
+ *
+ * <h3>与 Gate 版本的关键区别</h3>
+ * <ul>
+ * <li>OKX 私有频道连接到<b>独立的私有 WebSocket 端点</b> ({@code wss://ws.okx.com:8443/ws/v5/private}),
+ * 需要先发送 login 消息认证,不同于 Gate 的单一 WS + 签名订阅模式。</li>
+ * <li>订阅格式使用 {@code op/subscribe} 而非 event/subscribe。</li>
+ * <li>签名算法使用 HMAC-SHA256 + Base64,而非 Gate 的 HMAC-SHA512 + Hex。</li>
+ * </ul>
+ *
+ * <h3>架构</h3>
+ * 公有频道(如 k-line)连接到 public WS 端点,无需认证。
+ * 私有频道(如 positions、orders-algo)连接到 private WS 端点,由
+ * {@link com.xcong.excoin.modules.okxApi.OkxKlineWebSocketClient} 负责
+ * 在连接建立时发送 login 消息认证。
+ *
+ * <h3>订阅格式</h3>
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"positions","instType":"SWAP"}]}
+ * {"op":"subscribe","args":[{"channel":"orders-algo","instType":"SWAP","instId":"ETH-USDT-SWAP"}]}
+ * </pre>
+ *
+ * <h3>取消订阅格式</h3>
+ * <pre>
+ * {"op":"unsubscribe","args":[{"channel":"positions","instType":"SWAP"}]}
+ * </pre>
+ *
+ * @author Administrator
+ */
+@Slf4j
+public abstract class AbstractOkxPrivateChannelHandler implements OkxChannelHandler {
+
+ /** 频道名称,如 "positions"、"orders-algo" */
+ private final String channelName;
+
+ /** OKX API Key */
+ protected final String apiKey;
+
+ /** OKX API Secret(用于签名) */
+ protected final String apiSecret;
+
+ /** OKX API Passphrase */
+ protected final String passphrase;
+
+ /** 交易对标识,如 "ETH-USDT-SWAP" */
+ private final String instId;
+
+ /** 网格交易服务实例 */
+ private final OkxGridTradeService gridTradeService;
+
+ /** 订阅确认状态 */
+ private volatile boolean subscribed = false;
+
+ /**
+ * 构造私有频道处理器。
+ *
+ * @param channelName 频道名称(如 "positions"、"orders-algo")
+ * @param apiKey OKX API Key
+ * @param apiSecret OKX API Secret
+ * @param passphrase OKX API Passphrase
+ * @param instId 交易对标识(如 "ETH-USDT-SWAP")
+ * @param gridTradeService 网格交易服务实例
+ */
+ public AbstractOkxPrivateChannelHandler(String channelName,
+ String apiKey, String apiSecret,
+ String passphrase,
+ String instId,
+ OkxGridTradeService gridTradeService) {
+ this.channelName = channelName;
+ this.apiKey = apiKey;
+ this.apiSecret = apiSecret;
+ this.passphrase = passphrase;
+ this.instId = instId;
+ this.gridTradeService = gridTradeService;
+ }
+
+ /**
+ * @return 频道名称(如 "positions")
+ */
+ @Override
+ public String getChannelName() {
+ return channelName;
+ }
+
+ /**
+ * @return 交易对标识(如 "ETH-USDT-SWAP")
+ */
+ @Override
+ public String getInstId() {
+ return instId;
+ }
+
+ /**
+ * 发送订阅请求。
+ * 默认实现发送 {@code {"op":"subscribe","args":[{"channel":channelName,"instType":"SWAP"}]}}。
+ * 子类可覆盖以添加额外参数(如 instId)。
+ *
+ * @param ws 私有频道 WebSocket 客户端
+ */
+ @Override
+ public void subscribe(WebSocketClient ws) {
+ JSONObject msg = new JSONObject();
+ msg.put("op", "subscribe");
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("channel", channelName);
+ arg.put("instType", "SWAP");
+ args.add(arg);
+ msg.put("args", args);
+ ws.send(msg.toJSONString());
+ log.info("[OKX-WS] 订阅私有频道: {}, instType: SWAP", channelName);
+ }
+
+ /**
+ * 发送取消订阅请求。
+ *
+ * @param ws 私有频道 WebSocket 客户端
+ */
+ @Override
+ public void unsubscribe(WebSocketClient ws) {
+ JSONObject msg = new JSONObject();
+ msg.put("op", "unsubscribe");
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("channel", channelName);
+ arg.put("instType", "SWAP");
+ args.add(arg);
+ msg.put("args", args);
+ ws.send(msg.toJSONString());
+ log.info("[OKX-WS] 取消订阅私有频道: {}", channelName);
+ }
+
+ /**
+ * @return 网格交易服务实例
+ */
+ protected OkxGridTradeService getGridTradeService() {
+ return gridTradeService;
+ }
+
+ // ==================== 订阅状态 ====================
+
+ @Override
+ public boolean isSubscribed() {
+ return subscribed;
+ }
+
+ @Override
+ public void setSubscribed(boolean subscribed) {
+ this.subscribed = subscribed;
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/OkxChannelHandler.java b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/OkxChannelHandler.java
new file mode 100644
index 0000000..34e2df8
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/OkxChannelHandler.java
@@ -0,0 +1,102 @@
+package com.xcong.excoin.modules.okxApi.wsHandler;
+
+import com.alibaba.fastjson.JSONObject;
+import org.java_websocket.client.WebSocketClient;
+
+/**
+ * OKX WebSocket 频道处理器接口。
+ *
+ * <h3>定位</h3>
+ * 每个 OKX 频道对应一个实现类。新增频道只需实现此接口,
+ * 然后通过 {@code OkxKlineWebSocketClient.addPublicHandler()} 或
+ * {@code addPrivateHandler()} 注册即可。
+ *
+ * <h3>与 Gate 版本的区别</h3>
+ * OKX 的公开频道和私有频道使用不同的 WebSocket 端点(public/private),
+ * 订阅格式为 {@code {"op":"subscribe","args":[{"channel":"candle1m","instId":"ETH-USDT-SWAP"}]}},
+ * 与 Gate 的 {@code event/payload} 格式不同。
+ *
+ * <h3>实现类</h3>
+ * <ul>
+ * <li>{@code CandlestickOkxChannelHandler} — 公开频道,K线数据</li>
+ * <li>{@code AbstractOkxPrivateChannelHandler} — 私有频道抽象基类(separate WS + login auth)</li>
+ * <li>{@code PositionsOkxChannelHandler} — 私有频道,仓位更新</li>
+ * <li>{@code OrderAlgoOkxChannelHandler} — 私有频道,条件订单(algo)状态推送</li>
+ * </ul>
+ *
+ * <h3>OKX 订阅确认格式</h3>
+ * {@code {"event":"subscribe","arg":{"channel":"candle1m"}}} 表示订阅成功。
+ *
+ * <h3>OKX 数据推送格式</h3>
+ * {@code {"arg":{"channel":"positions","instType":"SWAP"},"data":[...]}}。
+ *
+ * @author Administrator
+ */
+public interface OkxChannelHandler {
+
+ /**
+ * 频道名称,如 {@code "candle1m"}、{@code "positions"}、{@code "orders-algo"}。
+ *
+ * @return OKX 频道标识字符串
+ */
+ String getChannelName();
+
+ /**
+ * 交易对标识,如 {@code "ETH-USDT-SWAP"}。
+ * OKX 订阅需要 instId 参数来指定订阅的交易对。
+ *
+ * @return OKX 格式的交易对标识
+ */
+ String getInstId();
+
+ /**
+ * 发送订阅请求到指定的 WebSocket 连接。
+ *
+ * <h3>公开频道格式</h3>
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"candle1m","instId":"ETH-USDT-SWAP"}]}
+ * </pre>
+ *
+ * <h3>私有频道格式</h3>
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"positions","instType":"SWAP"}]}
+ * </pre>
+ *
+ * @param ws 目标 WebSocket 客户端(公开或私有端点)
+ */
+ void subscribe(WebSocketClient ws);
+
+ /**
+ * 发送取消订阅请求到指定的 WebSocket 连接。
+ *
+ * @param ws 目标 WebSocket 客户端
+ */
+ void unsubscribe(WebSocketClient ws);
+
+ /**
+ * 处理频道推送消息。
+ *
+ * <h3>路由规则</h3>
+ * 如果消息的 {@code arg.channel} 匹配当前处理器的频道名,
+ * 则提取 {@code data} 数组并处理业务逻辑,返回 {@code true};
+ * 否则返回 {@code false}(让路由器继续遍历其他 handler)。
+ *
+ * @param response WebSocket 推送的完整 JSON
+ * @return true 表示已处理(循环停止),false 表示频道不匹配(继续遍历下一个 handler)
+ */
+ boolean handleMessage(JSONObject response);
+
+ /**
+ * 是否已收到订阅成功确认(即收到 {@code event:"subscribe"} 响应)。
+ *
+ * @return true 表示订阅已确认
+ */
+ boolean isSubscribed();
+
+ /**
+ * 标记订阅已确认/未确认。
+ *
+ * @param subscribed true=已确认,false=未确认
+ */
+ void setSubscribed(boolean subscribed);
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/CandlestickOkxChannelHandler.java b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/CandlestickOkxChannelHandler.java
new file mode 100644
index 0000000..9a8d754
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/CandlestickOkxChannelHandler.java
@@ -0,0 +1,195 @@
+package com.xcong.excoin.modules.okxApi.wsHandler.handler;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.modules.okxApi.OkxGridTradeService;
+import com.xcong.excoin.modules.okxApi.wsHandler.OkxChannelHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+
+import java.math.BigDecimal;
+
+/**
+ * OKX K线频道处理器(candle1m)— 策略的唯一价格时间驱动源。
+ *
+ * <h3>定位</h3>
+ * 这是一个<b>公开频道</b>,连接到 OKX 公开 WebSocket 端点,
+ * 无需登录认证。订阅 1 分钟 K 线实时推送,每收到一根 K 线即触发
+ * {@link OkxGridTradeService#onKline(BigDecimal)},由策略引擎决定是否开仓/止盈。
+ *
+ * <h3>订阅格式</h3>
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"candle1m","instId":"ETH-USDT-SWAP"}]}
+ * </pre>
+ *
+ * <h3>数据推送格式</h3>
+ * <pre>
+ * {
+ * "arg": {"channel":"candle1m","instId":"ETH-USDT-SWAP"},
+ * "data": [
+ * ["timestamp","open","high","low","close","vol","volCcy","volCcyQuote","confirm"]
+ * ]
+ * }
+ * </pre>
+ *
+ * <h3>字段说明</h3>
+ * <table>
+ * <tr><th>索引</th><th>字段</th><th>含义</th></tr>
+ * <tr><td>0</td><td>ts</td><td>K线起始时间(Unix ms)</td></tr>
+ * <tr><td>1</td><td>o</td><td>开盘价</td></tr>
+ * <tr><td>2</td><td>h</td><td>最高价</td></tr>
+ * <tr><td>3</td><td>l</td><td>最低价</td></tr>
+ * <tr><td>4</td><td>c</td><td><b>收盘价</b>(用于驱动策略)</td></tr>
+ * <tr><td>5</td><td>vol</td><td>成交量(张)</td></tr>
+ * <tr><td>6</td><td>volCcy</td><td>成交量(币)</td></tr>
+ * <tr><td>7</td><td>volCcyQuote</td><td>成交量(USDT)</td></tr>
+ * <tr><td>8</td><td>confirm</td><td>K线状态("0"=未完结,"1"=已完结)</td></tr>
+ * </table>
+ *
+ * <h3>注意</h3>
+ * 不判断 confirm 字段(K线是否完结)——策略需要 tick 级实时响应价格变动,
+ * 而非等 1 分钟烛线完结后才行动。
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class CandlestickOkxChannelHandler implements OkxChannelHandler {
+
+ /** 频道名称 */
+ private static final String CHANNEL_NAME = "candle1m";
+
+ /** 交易对标识,如 "ETH-USDT-SWAP" */
+ private final String instId;
+
+ /** 网格交易服务,接收 K 线回调 */
+ private final OkxGridTradeService gridTradeService;
+
+ /** 订阅确认状态 */
+ private volatile boolean subscribed = false;
+
+ /**
+ * 构造 K 线频道处理器。
+ *
+ * @param instId 交易对标识(如 "ETH-USDT-SWAP")
+ * @param gridTradeService OKX 网格交易策略服务实例
+ */
+ public CandlestickOkxChannelHandler(String instId, OkxGridTradeService gridTradeService) {
+ this.instId = instId;
+ this.gridTradeService = gridTradeService;
+ }
+
+ /**
+ * @return 频道名称 "candle1m"
+ */
+ @Override
+ public String getChannelName() {
+ return CHANNEL_NAME;
+ }
+
+ /**
+ * @return 交易对标识(如 "ETH-USDT-SWAP")
+ */
+ @Override
+ public String getInstId() {
+ return instId;
+ }
+
+ /**
+ * 发送 K 线频道订阅请求(公开频道,无需签名)。
+ *
+ * <h3>订阅格式</h3>
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"candle1m","instId":"ETH-USDT-SWAP"}]}
+ * </pre>
+ *
+ * @param ws OKX 公开 WebSocket 客户端
+ */
+ @Override
+ public void subscribe(WebSocketClient ws) {
+ JSONObject msg = new JSONObject();
+ msg.put("op", "subscribe");
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("channel", CHANNEL_NAME);
+ arg.put("instId", instId);
+ args.add(arg);
+ msg.put("args", args);
+ ws.send(msg.toJSONString());
+ log.info("[OKX-WS] 订阅K线频道, instId: {}", instId);
+ }
+
+ /**
+ * 发送 K 线频道取消订阅请求。
+ *
+ * @param ws OKX 公开 WebSocket 客户端
+ */
+ @Override
+ public void unsubscribe(WebSocketClient ws) {
+ JSONObject msg = new JSONObject();
+ msg.put("op", "unsubscribe");
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("channel", CHANNEL_NAME);
+ arg.put("instId", instId);
+ args.add(arg);
+ msg.put("args", args);
+ ws.send(msg.toJSONString());
+ log.info("[OKX-WS] 取消订阅K线频道, instId: {}", instId);
+ }
+
+ /**
+ * 处理 K 线推送消息。
+ *
+ * <h3>数据提取</h3>
+ * 从 {@code data[0]} 数组中提取索引 4(收盘价 close),
+ * 传给 {@code gridTradeService.onKline(closePrice)}。
+ *
+ * <h3>OKX 数据格式</h3>
+ * data 是一个二维数组,第一层是 K 线条数(通常 1 条),
+ * 第二层是各字段值。data[0][4] = 收盘价。
+ *
+ * @param response WebSocket 推送的完整 JSON
+ * @return true 表示已处理(匹配成功)
+ */
+ @Override
+ public boolean handleMessage(JSONObject response) {
+ JSONObject arg = response.getJSONObject("arg");
+ if (arg == null || !CHANNEL_NAME.equals(arg.getString("channel"))) {
+ return false;
+ }
+ try {
+ JSONArray dataArray = response.getJSONArray("data");
+ if (dataArray == null || dataArray.isEmpty()) {
+ log.debug("[OKX-WS] candle1m 数据为空");
+ return true;
+ }
+ // data[0] 是一个数组: [ts, o, h, l, c, vol, volCcy, volCcyQuote, confirm]
+ JSONArray candle = dataArray.getJSONArray(0);
+ if (candle == null || candle.size() < 5) {
+ log.warn("[OKX-WS] candle1m 数据格式异常: {}", dataArray);
+ return true;
+ }
+ // 索引 4 = 收盘价 close
+ BigDecimal closePrice = candle.getBigDecimal(4);
+
+ if (gridTradeService != null && closePrice != null) {
+ gridTradeService.onKline(closePrice);
+ }
+ } catch (Exception e) {
+ log.error("[OKX-WS] 处理 candle1m 数据失败", e);
+ }
+ return true;
+ }
+
+ // ==================== 订阅状态 ====================
+
+ @Override
+ public boolean isSubscribed() {
+ return subscribed;
+ }
+
+ @Override
+ public void setSubscribed(boolean subscribed) {
+ this.subscribed = subscribed;
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/MarkPriceOkxChannelHandler.java b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/MarkPriceOkxChannelHandler.java
new file mode 100644
index 0000000..cdcd091
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/MarkPriceOkxChannelHandler.java
@@ -0,0 +1,188 @@
+package com.xcong.excoin.modules.okxApi.wsHandler.handler;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.modules.okxApi.OkxGridTradeService;
+import com.xcong.excoin.modules.okxApi.wsHandler.OkxChannelHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+
+import java.math.BigDecimal;
+
+/**
+ * OKX 标记价格频道处理器(mark-price)— 策略的价格驱动源。
+ *
+ * <h3>定位</h3>
+ * 这是一个<b>公开频道</b>,连接到 OKX 公开 WebSocket 端点,无需登录认证。
+ * 替代 candle1m K 线频道,作为策略唯一的实时价格来源。
+ * 标记价格变化时每 200ms 推送一次,无变化时每 10s 推送一次。
+ *
+ * <h3>双回调</h3>
+ * <ul>
+ * <li>{@link OkxGridTradeService#onKline(BigDecimal)} — 驱动网格策略(处理开仓/止盈逻辑)</li>
+ * <li>{@link OkxGridTradeService#setMarkPrice(BigDecimal)} — PnLPriceMode.MARK_PRICE 计算未实现盈亏</li>
+ * </ul>
+ *
+ * <h3>订阅格式</h3>
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"mark-price","instId":"ETH-USDT-SWAP"}]}
+ * </pre>
+ *
+ * <h3>数据推送格式</h3>
+ * <pre>
+ * {
+ * "arg": {"channel":"mark-price","instId":"ETH-USDT-SWAP"},
+ * "data": [{
+ * "instType": "SWAP",
+ * "instId": "ETH-USDT-SWAP",
+ * "markPx": "42310.6",
+ * "ts": "1630049139746"
+ * }]
+ * }
+ * </pre>
+ *
+ * <h3>推送频率</h3>
+ * <ul>
+ * <li>标记价格变化 → 每 200ms 推送一次</li>
+ * <li>标记价格无变化 → 每 10s 推送一次</li>
+ * </ul>
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class MarkPriceOkxChannelHandler implements OkxChannelHandler {
+
+ /** 频道名称 */
+ private static final String CHANNEL_NAME = "mark-price";
+
+ /** 交易对标识,如 "ETH-USDT-SWAP" */
+ private final String instId;
+
+ /** 网格交易服务,接收标记价格回调 */
+ private final OkxGridTradeService gridTradeService;
+
+ /** 订阅确认状态 */
+ private volatile boolean subscribed = false;
+
+ /**
+ * 构造标记价格频道处理器。
+ *
+ * @param instId 交易对标识(如 "ETH-USDT-SWAP")
+ * @param gridTradeService OKX 网格交易策略服务实例
+ */
+ public MarkPriceOkxChannelHandler(String instId, OkxGridTradeService gridTradeService) {
+ this.instId = instId;
+ this.gridTradeService = gridTradeService;
+ }
+
+ /**
+ * @return 频道名称 "mark-price"
+ */
+ @Override
+ public String getChannelName() {
+ return CHANNEL_NAME;
+ }
+
+ /**
+ * @return 交易对标识(如 "ETH-USDT-SWAP")
+ */
+ @Override
+ public String getInstId() {
+ return instId;
+ }
+
+ /**
+ * 发送标记价格频道订阅请求(公开频道,无需签名)。
+ *
+ * @param ws OKX 公开 WebSocket 客户端
+ */
+ @Override
+ public void subscribe(WebSocketClient ws) {
+ JSONObject msg = new JSONObject();
+ msg.put("op", "subscribe");
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("channel", CHANNEL_NAME);
+ arg.put("instId", instId);
+ args.add(arg);
+ msg.put("args", args);
+ ws.send(msg.toJSONString());
+ log.info("[OKX-WS] 订阅标记价格频道, instId: {}", instId);
+ }
+
+ /**
+ * 发送标记价格频道取消订阅请求。
+ *
+ * @param ws OKX 公开 WebSocket 客户端
+ */
+ @Override
+ public void unsubscribe(WebSocketClient ws) {
+ JSONObject msg = new JSONObject();
+ msg.put("op", "unsubscribe");
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("channel", CHANNEL_NAME);
+ arg.put("instId", instId);
+ args.add(arg);
+ msg.put("args", args);
+ ws.send(msg.toJSONString());
+ log.info("[OKX-WS] 取消订阅标记价格频道, instId: {}", instId);
+ }
+
+ /**
+ * 处理标记价格推送消息。
+ *
+ * <h3>数据提取</h3>
+ * 从 {@code data[0].markPx} 提取标记价格,同时回调:
+ * <ol>
+ * <li>{@code gridTradeService.onKline(markPrice)} — 驱动网格策略</li>
+ * <li>{@code gridTradeService.setMarkPrice(markPrice)} — PnL 计算用</li>
+ * </ol>
+ *
+ * @param response WebSocket 推送的完整 JSON
+ * @return true 表示已处理(匹配成功)
+ */
+ @Override
+ public boolean handleMessage(JSONObject response) {
+ JSONObject arg = response.getJSONObject("arg");
+ if (arg == null || !CHANNEL_NAME.equals(arg.getString("channel"))) {
+ return false;
+ }
+ try {
+ JSONArray dataArray = response.getJSONArray("data");
+ if (dataArray == null || dataArray.isEmpty()) {
+ return true;
+ }
+ // data[0] 是一个 JSONObject: {instType, instId, markPx, ts}
+ JSONObject markData = dataArray.getJSONObject(0);
+ if (markData == null) {
+ return true;
+ }
+ String markPxStr = markData.getString("markPx");
+ if (markPxStr == null || markPxStr.isEmpty()) {
+ return true;
+ }
+ BigDecimal markPrice = new BigDecimal(markPxStr);
+
+ if (gridTradeService != null) {
+ gridTradeService.setMarkPrice(markPrice);
+ gridTradeService.onKline(markPrice);
+ }
+ } catch (Exception e) {
+ log.error("[OKX-WS] 处理 mark-price 数据失败", e);
+ }
+ return true;
+ }
+
+ // ==================== 订阅状态 ====================
+
+ @Override
+ public boolean isSubscribed() {
+ return subscribed;
+ }
+
+ @Override
+ public void setSubscribed(boolean subscribed) {
+ this.subscribed = subscribed;
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/OrderAlgoOkxChannelHandler.java b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/OrderAlgoOkxChannelHandler.java
new file mode 100644
index 0000000..7247d98
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/OrderAlgoOkxChannelHandler.java
@@ -0,0 +1,230 @@
+package com.xcong.excoin.modules.okxApi.wsHandler.handler;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.modules.okxApi.OkxConfig;
+import com.xcong.excoin.modules.okxApi.OkxGridTradeService;
+import com.xcong.excoin.modules.okxApi.wsHandler.AbstractOkxPrivateChannelHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+
+/**
+ * OKX 条件订单频道处理器(orders-algo)。
+ *
+ * <h3>定位</h3>
+ * 订阅用户条件订单(algo order)状态推送。当条件单状态变更(触发、取消等)时,
+ * 获得 algoId、状态、订单类型等信息。相当于 Gate 的 {@code futures.autoorders} 频道。
+ *
+ * <h3>订阅格式</h3>
+ * 私有频道,需要先登录认证。订阅时需要指定 instType 和 instId:
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"orders-algo","instType":"SWAP","instId":"ETH-USDT-SWAP"}]}
+ * </pre>
+ *
+ * <h3>数据推送格式(条件单触发/成交)</h3>
+ * <pre>
+ * {
+ * "arg": {"channel":"orders-algo","instType":"SWAP","instId":"ETH-USDT-SWAP"},
+ * "data": [{
+ * "algoId": "1234567890",
+ * "state": "effective", // 状态: "effective"=已触发, "canceled"=已取消
+ * "ordType": "conditional", // 订单类型
+ * "actualSide": "buy", // 实际买卖方向: "buy"/"sell"
+ * "posSide": "long", // 持仓方向: "long"/"short"/"net"
+ * "sz": "1", // 委托数量
+ * "triggerPx": "3000", // 触发价格
+ * "ordPx": "3000.5", // 委托价格
+ * "tradeId": "9876543210" // 关联交易ID
+ * }]
+ * }
+ * </pre>
+ *
+ * <h3>状态映射</h3>
+ * <ul>
+ * <li>"effective" → status="finished"(已触发/已完成)</li>
+ * <li>"canceled" → status="cancelled"(已取消)</li>
+ * </ul>
+ *
+ * <h3>订单类型映射(orderType)</h3>
+ * 根据 posSide 和 actualSide 组合推断订单类型:
+ * <ul>
+ * <li>posSide=long, actualSide=sell → "plan-close-long-position"(平多仓)</li>
+ * <li>posSide=short, actualSide=buy → "plan-close-short-position"(平空仓)</li>
+ * <li>posSide=net, actualSide=buy → "entry-long"(开多仓)</li>
+ * <li>posSide=net, actualSide=sell → "entry-short"(开空仓)</li>
+ * </ul>
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class OrderAlgoOkxChannelHandler extends AbstractOkxPrivateChannelHandler {
+
+ /** OKX 条件订单频道名称 */
+ private static final String CHANNEL_NAME = "orders-algo";
+
+ /** OKX 配置 */
+ private final OkxConfig config;
+
+ /**
+ * 构造条件订单频道处理器。
+ *
+ * @param config OKX 配置实例(提供合约名称等)
+ * @param gridTradeService OKX 网格交易策略服务实例
+ */
+ public OrderAlgoOkxChannelHandler(OkxConfig config, OkxGridTradeService gridTradeService) {
+ super(CHANNEL_NAME,
+ config.getApiKey(), config.getApiSecret(), config.getPassphrase(),
+ config.getContract(),
+ gridTradeService);
+ this.config = config;
+ }
+
+ /**
+ * 发送订阅请求,需指定 instType 和 instId。
+ *
+ * @param ws 私有频道 WebSocket 客户端
+ */
+ @Override
+ public void subscribe(WebSocketClient ws) {
+ JSONObject msg = new JSONObject();
+ msg.put("op", "subscribe");
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("channel", CHANNEL_NAME);
+ arg.put("instType", "SWAP");
+ arg.put("instId", getInstId());
+ args.add(arg);
+ msg.put("args", args);
+ ws.send(msg.toJSONString());
+ log.info("[OKX-WS] 订阅条件订单频道, instId: {}", getInstId());
+ }
+
+ /**
+ * 发送取消订阅请求。
+ *
+ * @param ws 私有频道 WebSocket 客户端
+ */
+ @Override
+ public void unsubscribe(WebSocketClient ws) {
+ JSONObject msg = new JSONObject();
+ msg.put("op", "unsubscribe");
+ JSONArray args = new JSONArray();
+ JSONObject arg = new JSONObject();
+ arg.put("channel", CHANNEL_NAME);
+ arg.put("instType", "SWAP");
+ arg.put("instId", getInstId());
+ args.add(arg);
+ msg.put("args", args);
+ ws.send(msg.toJSONString());
+ log.info("[OKX-WS] 取消订阅条件订单频道, instId: {}", getInstId());
+ }
+
+ /**
+ * 处理条件订单推送消息。
+ *
+ * <h3>处理流程</h3>
+ * <ol>
+ * <li>检查 arg.channel 是否匹配 "orders-algo"</li>
+ * <li>遍历 data 数组,提取 algoId、state、actualSide、posSide、tradeId</li>
+ * <li>映射 state → status(effective→finished, canceled→cancelled)</li>
+ * <li>根据 posSide + actualSide 推断 orderType</li>
+ * <li>调用 gridTradeService.onAutoOrder(algoId, status, reason, orderType, tradeId)</li>
+ * </ol>
+ *
+ * @param response WebSocket 推送的完整 JSON
+ * @return true 表示已处理(匹配成功)
+ */
+ @Override
+ public boolean handleMessage(JSONObject response) {
+ JSONObject arg = response.getJSONObject("arg");
+ if (arg == null || !CHANNEL_NAME.equals(arg.getString("channel"))) {
+ return false;
+ }
+ try {
+ JSONArray dataArray = response.getJSONArray("data");
+ if (dataArray == null || dataArray.isEmpty()) {
+ return true;
+ }
+
+ String contract = config.getContract();
+ for (int i = 0; i < dataArray.size(); i++) {
+ JSONObject orderData = dataArray.getJSONObject(i);
+
+ // 按 instId 过滤
+ String dataInstId = orderData.getString("instId");
+ String instId = arg.getString("instId");
+ if (instId != null && dataInstId != null
+ && !instId.equals(dataInstId)
+ && !dataInstId.startsWith(contract)) {
+ continue;
+ }
+
+ String algoId = orderData.getString("algoId");
+ String state = orderData.getString("state");
+ String actualSide = orderData.getString("actualSide");
+ String posSide = orderData.getString("posSide");
+ String tradeId = orderData.getString("tradeId");
+ String ordType = orderData.getString("ordType");
+
+ // 状态映射
+ String status;
+ if ("effective".equals(state)) {
+ status = "finished";
+ } else if ("canceled".equals(state)) {
+ status = "cancelled";
+ } else {
+ // 其他状态(如 "pending")暂不处理
+ log.debug("[OKX-WS] orders-algo 忽略状态: algoId:{}, state:{}", algoId, state);
+ continue;
+ }
+
+ // 推断 orderType
+ String orderType = mapOrderType(posSide, actualSide);
+
+ log.info("[OKX-WS] orders-algo 状态变更, algoId:{}, state:{}, status:{}, orderType:{}, ordType:{}, actualSide:{}, posSide:{}, tradeId:{}",
+ algoId, state, status, orderType, ordType, actualSide, posSide, tradeId);
+
+ if (getGridTradeService() != null) {
+ getGridTradeService().onAutoOrder(algoId, status, state, orderType, tradeId);
+ }
+ }
+ } catch (Exception e) {
+ log.error("[OKX-WS] 处理 orders-algo 数据失败", e);
+ }
+ return true;
+ }
+
+ /**
+ * 根据 OKX 的 posSide 和 actualSide 映射到策略内部的 orderType。
+ *
+ * <h3>映射规则</h3>
+ * <table>
+ * <tr><th>posSide</th><th>actualSide</th><th>含义</th><th>orderType</th></tr>
+ * <tr><td>long</td><td>sell</td><td>平多仓</td><td>plan-close-long-position</td></tr>
+ * <tr><td>short</td><td>buy</td><td>平空仓</td><td>plan-close-short-position</td></tr>
+ * <tr><td>net</td><td>buy</td><td>开多仓</td><td>entry-long</td></tr>
+ * <tr><td>net</td><td>sell</td><td>开空仓</td><td>entry-short</td></tr>
+ * </table>
+ *
+ * @param posSide OKX 持仓方向("long"/"short"/"net")
+ * @param actualSide OKX 实际买卖方向("buy"/"sell")
+ * @return 策略内部的订单类型字符串
+ */
+ private String mapOrderType(String posSide, String actualSide) {
+ // 平仓方向(止盈/止损触发)
+ if ("long".equals(posSide) && "sell".equals(actualSide)) {
+ return "plan-close-long-position";
+ } else if ("short".equals(posSide) && "buy".equals(actualSide)) {
+ return "plan-close-short-position";
+ }
+ // 开仓方向 — 覆盖 long_short_mode 和 net_mode 两种模式
+ if (("long".equals(posSide) || "net".equals(posSide)) && "buy".equals(actualSide)) {
+ return "entry-long";
+ } else if (("short".equals(posSide) || "net".equals(posSide)) && "sell".equals(actualSide)) {
+ return "entry-short";
+ }
+ // 默认值
+ log.warn("[OKX-WS] 未知的 orderType 映射, posSide:{}, actualSide:{}", posSide, actualSide);
+ return "unknown";
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/PositionsOkxChannelHandler.java b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/PositionsOkxChannelHandler.java
new file mode 100644
index 0000000..f4d08cc
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxApi/wsHandler/handler/PositionsOkxChannelHandler.java
@@ -0,0 +1,140 @@
+package com.xcong.excoin.modules.okxApi.wsHandler.handler;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.xcong.excoin.modules.okxApi.OkxConfig;
+import com.xcong.excoin.modules.okxApi.OkxGridTradeService;
+import com.xcong.excoin.modules.okxApi.wsHandler.AbstractOkxPrivateChannelHandler;
+import com.xcong.excoin.modules.okxApi.TraderParam;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * OKX 仓位频道处理器(positions),接收仓位更新推送并回调
+ * {@link OkxGridTradeService#onPositionUpdate(String, TraderParam.Direction, BigDecimal, BigDecimal)}。
+ *
+ * <h3>订阅格式</h3>
+ * 私有频道,需要先登录认证。订阅 arg 使用 instType: "SWAP"(不指定具体 instId,
+ * 会在 handleMessage 中通过 config.getContract() 过滤)。
+ * <pre>
+ * {"op":"subscribe","args":[{"channel":"positions","instType":"SWAP"}]}
+ * </pre>
+ *
+ * <h3>数据推送格式</h3>
+ * <pre>
+ * {
+ * "arg": {"channel":"positions","instType":"SWAP"},
+ * "data": [{
+ * "instId": "ETH-USDT-SWAP",
+ * "posSide": "long", // "long" 或 "short"
+ * "pos": "1", // 持仓张数
+ * "avgPx": "3000", // 开仓均价
+ * ...
+ * }]
+ * }
+ * </pre>
+ *
+ * <h3>数据映射</h3>
+ * <ul>
+ * <li>posSide "long" → {@link TraderParam.Direction#LONG},映射为 DUAL_LONG</li>
+ * <li>posSide "short" → {@link TraderParam.Direction#SHORT},映射为 DUAL_SHORT</li>
+ * <li>pos → 持仓张数(绝对值)</li>
+ * <li>avgPx → 开仓均价</li>
+ * </ul>
+ *
+ * @author Administrator
+ */
+@Slf4j
+public class PositionsOkxChannelHandler extends AbstractOkxPrivateChannelHandler {
+
+ /** OKX 仓位频道名称 */
+ private static final String CHANNEL_NAME = "positions";
+
+ /** OKX 配置,用于获取合约名称进行过滤 */
+ private final OkxConfig config;
+
+ /**
+ * 构造仓位频道处理器。
+ *
+ * @param config OKX 配置实例(提供合约名称等)
+ * @param gridTradeService OKX 网格交易策略服务实例
+ */
+ public PositionsOkxChannelHandler(OkxConfig config, OkxGridTradeService gridTradeService) {
+ super(CHANNEL_NAME,
+ config.getApiKey(), config.getApiSecret(), config.getPassphrase(),
+ config.getContract(),
+ gridTradeService);
+ this.config = config;
+ }
+
+ /**
+ * 处理仓位推送消息。
+ *
+ * <h3>处理流程</h3>
+ * <ol>
+ * <li>检查 arg.channel 是否匹配 "positions"</li>
+ * <li>遍历 data 数组,按 instId 过滤出 config.getContract() 对应的仓位</li>
+ * <li>映射 posSide → Direction(long=DualLong, short=DualShort)</li>
+ * <li>提取 pos(张数)、avgPx(均价)</li>
+ * <li>调用 gridTradeService.onPositionUpdate(contract, direction, size, entryPrice)</li>
+ * </ol>
+ *
+ * @param response WebSocket 推送的完整 JSON
+ * @return true 表示已处理(匹配成功)
+ */
+ @Override
+ public boolean handleMessage(JSONObject response) {
+ JSONObject arg = response.getJSONObject("arg");
+ if (arg == null || !CHANNEL_NAME.equals(arg.getString("channel"))) {
+ return false;
+ }
+ try {
+ JSONArray dataArray = response.getJSONArray("data");
+ if (dataArray == null || dataArray.isEmpty()) {
+ return true;
+ }
+
+ String contract = config.getContract(); // e.g., "ETH-USDT"
+ for (int i = 0; i < dataArray.size(); i++) {
+ JSONObject posData = dataArray.getJSONObject(i);
+
+ // 按 instId 精确过滤,只处理当前合约的仓位(避免误匹配交割合约)
+ String dataInstId = posData.getString("instId");
+ if (dataInstId == null || !dataInstId.equals(contract)) {
+ continue;
+ }
+
+ // 解析持仓方向:OKX 的 posSide 可以是 "long" 或 "short"
+ String posSide = posData.getString("posSide");
+ TraderParam.Direction direction;
+ if ("long".equals(posSide)) {
+ direction = TraderParam.Direction.LONG;
+ } else if ("short".equals(posSide)) {
+ direction = TraderParam.Direction.SHORT;
+ } else {
+ log.debug("[OKX-WS] positions 忽略 net 方向: {}", posSide);
+ continue;
+ }
+
+ String posStr = posData.getString("pos");
+ String avgPxStr = posData.getString("avgPx");
+ // 仓位归零时 OKX 推送 avgPx: ""(空串),需做防护
+ BigDecimal size = (posStr != null && !posStr.isEmpty())
+ ? new BigDecimal(posStr) : BigDecimal.ZERO;
+ BigDecimal entryPrice = (avgPxStr != null && !avgPxStr.isEmpty())
+ ? new BigDecimal(avgPxStr) : BigDecimal.ZERO;
+
+ log.info("[OKX-WS] positions 持仓更新, instId:{}, posSide:{}, pos:{}, avgPx:{}",
+ dataInstId, posSide, size, entryPrice);
+
+ if (getGridTradeService() != null) {
+ getGridTradeService().onPositionUpdate(contract, direction, size, entryPrice);
+ }
+ }
+ } catch (Exception e) {
+ log.error("[OKX-WS] 处理 positions 数据失败", e);
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX_QUANT_DOCUMENTATION.md b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX_QUANT_DOCUMENTATION.md
deleted file mode 100644
index bce4319..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX_QUANT_DOCUMENTATION.md
+++ /dev/null
@@ -1,333 +0,0 @@
-# OKX 量化交易系统文档
-
-## 1. 包结构概述
-
-```
-com.xcong.excoin.modules.okxNewPrice/
-├── celue/ # 策略实现模块
-│ ├── CaoZuoService.java # 策略接口
-│ └── CaoZuoServiceImpl.java # 策略实现类
-├── jiaoyi/ # 交易相关模块
-├── okxWs/ # OKX WebSocket 相关类
-│ ├── enums/ # WebSocket 相关枚举
-│ ├── param/ # WebSocket 请求参数
-│ ├── wanggeList/ # 网格列表管理
-│ ├── AccountWs.java # 账户信息处理
-│ ├── BalanceAndPositionWs.java # 余额和持仓处理
-│ ├── InstrumentsWs.java # 合约信息处理
-│ ├── LoginWs.java # 登录处理
-│ ├── OrderInfoWs.java # 订单信息处理
-│ ├── PositionsWs.java # 持仓信息处理
-│ └── TradeOrderWs.java # 交易订单处理
-├── okxpi/ # OKX API 接口封装
-├── utils/ # 工具类
-├── wangge/ # 网格相关模块
-├── zhanghu/ # 账户相关模块
-├── OkxNewPriceWebSocketClient.java # 价格 WebSocket 客户端
-├── OkxQuantWebSocketClient.java # 量化交易 WebSocket 客户端
-├── OkxWebSocketClientManager.java # WebSocket 客户端管理器
-└── OKX_QUANT_DOCUMENTATION.md # 本文档
-```
-
-## 2. 核心组件说明
-
-### 2.1 WebSocket 客户端管理
-
-#### OkxWebSocketClientManager
-
-**功能**:集中管理多个 OKX WebSocket 客户端实例,包括价格客户端和账号客户端。
-
-**核心属性**:
-- `quantClientMap`: 存储所有账号的 `OkxQuantWebSocketClient` 实例
-- `newPriceClient`: 存储价格数据的 `OkxNewPriceWebSocketClient` 实例
-
-**主要方法**:
-- `init()`: 初始化所有 WebSocket 客户端
-- `destroy()`: 销毁所有 WebSocket 客户端资源
-- `getClient()`: 获取指定账号的 WebSocket 客户端
-- `getAllClients()`: 获取所有账号的 WebSocket 客户端
-
-**使用流程**:
-1. Spring 容器启动时自动调用 `init()` 方法
-2. 初始化 `OkxNewPriceWebSocketClient` 用于获取价格数据
-3. 为每个账号创建 `OkxQuantWebSocketClient` 实例
-4. 所有客户端统一由管理器进行生命周期管理
-
-### 2.2 价格 WebSocket 客户端
-
-#### OkxNewPriceWebSocketClient
-
-**功能**:连接 OKX 公共 WebSocket 接口,实时获取标记价格数据,并触发量化交易操作。
-
-**核心属性**:
-- `webSocketClient`: WebSocket 连接客户端
-- `isConnected/isConnecting/isInitialized`: 连接状态标志
-- `lastMessageTime`: 最后收到消息的时间
-
-**主要方法**:
-- `init()`: 初始化 WebSocket 客户端
-- `destroy()`: 销毁 WebSocket 客户端资源
-- `connect()`: 建立 WebSocket 连接
-- `startHeartbeat()`: 启动心跳检测
-- `processPushData()`: 处理价格推送数据
-- `triggerQuantOperations()`: 触发量化交易操作
-
-**价格处理流程**:
-1. 连接 OKX WebSocket 公共接口
-2. 订阅标记价格通道
-3. 收到价格数据后保存到 Redis
-4. 调用 `triggerQuantOperations()` 触发量化交易
-5. 实现心跳检测和自动重连机制
-
-### 2.3 账号 WebSocket 客户端
-
-#### OkxQuantWebSocketClient
-
-**功能**:连接 OKX 私有 WebSocket 接口,处理账号登录、持仓、订单等私有数据。
-
-**核心属性**:
-- `account`: 账号信息枚举
-- `webSocketClient`: WebSocket 连接客户端
-- `isConnected/isConnecting`: 连接状态标志
-
-**主要方法**:
-- `init()`: 初始化 WebSocket 客户端
-- `destroy()`: 销毁 WebSocket 客户端资源
-- `connect()`: 建立 WebSocket 连接
-- `websocketLogin()`: 账号登录
-- `subscribeChannels()`: 订阅私有通道
-- `processPushData()`: 处理数据推送
-
-**登录与订阅流程**:
-1. 连接 OKX WebSocket 私有接口
-2. 发送登录请求
-3. 登录成功后订阅账户、持仓、订单等通道
-4. 接收并处理私有数据推送
-5. 实现心跳检测和自动重连机制
-
-## 3. 网格策略实现
-
-### 3.1 网格配置
-
-#### WangGeListEnum
-
-**功能**:定义不同价格区间的网格参数,包括价格上下限、方向、步距等。
-
-**核心属性**:
-- `name`: 网格名称
-- `jiage_shangxian`: 价格上限
-- `jiage_xiaxian`: 价格下限
-- `jian_ju`: 网格步距
-- `fang_xiang`: 持仓方向 (long/short)
-- `zhi_sun_dian`: 止损点
-
-**主要方法**:
-- `getGridByPrice()`: 根据当前价格获取对应的网格
-
-**网格定义示例**:
-```java
-UP("上层做空", "2", "3100", "3000", "2", "short", "3100"),
-CENTER("中间做多", "2", "3000", "2900", "2", "long", "2900"),
-CENTER_ONE("中间做空", "2", "2900", "2870", "2", "short", "2870"),
-DOWN("下层做多", "2", "2870", "2850", "2", "long", "2850");
-```
-
-### 3.2 策略实现
-
-#### CaoZuoService/CaoZuoServiceImpl
-
-**功能**:实现量化交易策略逻辑,包括加仓、减仓、止损等操作。
-
-**核心方法**:
-- `caoZuoHandler()`: 主要策略逻辑入口
-- `caoZuoZhiSunEvent()`: 止损事件处理
-- `caoZuoInitEvent()`: 初始化订单处理
-- `chooseEvent()`: 事件选择处理
-- `caoZuoLong()`: 多头策略处理
-- `caoZuoShort()`: 空头策略处理
-
-**策略执行流程**:
-1. 检查账户状态和系统开关
-2. 判断当前价格所在网格
-3. 检查是否需要止损
-4. 检查持仓状态:
- - 无持仓:执行初始化订单
- - 有持仓:根据网格策略决定加仓或减仓
-5. 根据多空方向执行相应策略
-
-### 3.3 订单执行
-
-#### TradeOrderWs
-
-**功能**:构建和发送订单请求到 OKX WebSocket 接口。
-
-**核心方法**:
-- `orderEvent()`: 执行订单事件
-
-**订单执行流程**:
-1. 验证下单参数和账户状态
-2. 检查账户和持仓通道是否就绪
-3. 构建订单请求 JSON
-4. 发送订单到 WebSocket 接口
-5. 更新订单状态和就绪标志
-
-## 4. 系统交互流程
-
-### 4.1 启动流程
-
-```
-[Spring 容器启动] → OkxWebSocketClientManager.init() → 初始化 newPriceClient → 初始化所有 quantClient → 建立 WebSocket 连接
-```
-
-### 4.2 价格触发交易流程
-
-```
-OkxNewPriceWebSocketClient.onMessage() → processPushData() → triggerQuantOperations() → WangGeListEnum.getGridByPrice() →
- 对每个账号执行:
- 1. 检查反向持仓并止损 → caoZuoZhiSunEvent()
- 2. 执行当前网格策略 → caoZuoHandler() → chooseEvent() → caoZuoLong()/caoZuoShort()
- 3. 发送订单 → TradeOrderWs.orderEvent()
-```
-
-### 4.3 订单执行流程
-
-```
-TradeOrderWs.orderEvent() → 验证参数 → 检查就绪状态 → 构建订单JSON → 发送订单 → 更新状态标志
-```
-
-## 5. 数据结构
-
-### 5.1 订单请求参数
-
-#### TradeRequestParam
-
-**功能**:封装交易订单请求参数。
-
-**核心属性**:
-- `accountName`: 账号名称
-- `markPx`: 标记价格
-- `instId`: 合约ID
-- `tdMode`: 交易模式
-- `posSide`: 持仓方向
-- `ordType`: 订单类型
-- `side`: 买卖方向
-- `sz`: 数量
-- `clOrdId`: 客户订单ID
-- `tradeType`: 交易类型
-
-### 5.2 数据存储结构
-
-系统使用双层 Map 结构存储不同账号的数据:
-
-```java
-// 第一层 key 为账号名称,第二层 key 为数据项
-Map<String, Map<String, String>> accountDataMap = new ConcurrentHashMap<>();
-```
-
-主要数据存储类:
-- `AccountWs.ACCOUNTWSMAP`: 账户信息
-- `PositionsWs.POSITIONSWSMAP`: 持仓信息
-- `OrderInfoWs.ORDERINFOWSMAP`: 订单信息
-- `TradeOrderWs.TRADEORDERWSMAP`: 交易订单信息
-
-## 6. 网格策略核心算法
-
-### 6.1 网格匹配算法
-
-```java
-public static WangGeListEnum getGridByPrice(BigDecimal price) {
- for (WangGeListEnum grid : WangGeListEnum.values()) {
- BigDecimal upperLimit = new BigDecimal(grid.jiage_shangxian);
- BigDecimal lowerLimit = new BigDecimal(grid.jiage_xiaxian);
-
- if (upperLimit.compareTo(lowerLimit) > 0) {
- if (price.compareTo(lowerLimit) > 0 && price.compareTo(upperLimit) <= 0) {
- return grid;
- }
- }
- }
- return null;
-}
-```
-
-### 6.2 交易决策算法
-
-```java
-public TradeRequestParam caoZuoHandler(String accountName, String markPx, String posSide) {
- // 1. 检查系统开关和账户状态
- // 2. 判断止损条件
- // 3. 检查保证金和仓位情况
- // 4. 根据持仓数量决定操作类型
- // 5. 返回交易请求参数
-}
-```
-
-## 7. 错误处理与容错机制
-
-### 7.1 WebSocket 连接管理
-- 实现心跳检测机制,定期发送 ping 请求
-- 自动重连机制,连接断开后使用指数退避策略重连
-- 连接状态管理,避免重复连接
-
-### 7.2 订单执行保障
-- 订单参数验证,确保参数完整性
-- 账户和持仓通道就绪检查
-- 订单状态跟踪,避免重复下单
-
-### 7.3 风险控制
-- 止损机制,限制单次交易亏损
-- 保证金检查,避免满仓操作
-- 系统开关,支持紧急暂停交易
-
-## 8. 扩展与定制
-
-### 8.1 网格参数配置
-可通过修改 `WangGeListEnum` 枚举值来配置不同的网格参数:
-
-```java
-// 格式:名称, 小数位数, 价格上限, 价格下限, 步距, 方向, 止损点
-NEW_GRID("新网格", "2", "4000", "3500", "5", "long", "3500")
-```
-
-### 8.2 策略扩展
-可通过实现 `CaoZuoService` 接口来扩展新的交易策略:
-
-```java
-public class CustomCaoZuoServiceImpl implements CaoZuoService {
- // 实现自定义策略逻辑
-}
-```
-
-### 8.3 多账号支持
-系统天然支持多账号管理,只需在 `ExchangeInfoEnum` 中添加新的账号配置即可。
-
-## 9. 性能优化
-
-1. **并发处理**:使用 `ConcurrentHashMap` 存储账号数据,支持高并发访问
-2. **线程管理**:使用线程池处理异步任务,避免线程泄漏
-3. **连接复用**:多个账号共享相同的 WebSocket 连接参数,减少连接开销
-4. **数据缓存**:使用 Redis 缓存价格数据,提高数据访问效率
-
-## 10. 监控与日志
-
-系统使用 SLF4J 日志框架,记录关键操作和错误信息:
-
-- WebSocket 连接状态
-- 价格变化和网格匹配
-- 订单执行过程
-- 错误和异常信息
-
-通过日志可以监控系统运行状态和排查问题。
-
-## 11. 总结
-
-OKX 量化交易系统是一个基于 WebSocket 的实时交易系统,实现了多网格策略、自动交易执行和风险控制功能。系统采用模块化设计,各组件职责明确,便于维护和扩展。
-
-核心功能包括:
-- 多网格策略配置和管理
-- 实时价格监控和网格匹配
-- 自动交易决策和执行
-- 多账号管理和统一控制
-- 完善的错误处理和风险控制
-
-系统可以根据市场价格变化自动执行交易策略,实现量化交易的自动化和智能化。
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
deleted file mode 100644
index cf5c9fd..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
+++ /dev/null
@@ -1,577 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.json.JSONException;
-import cn.hutool.json.JSONUtil;
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.blackchain.service.DateUtil;
-import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
-import com.xcong.excoin.modules.okxNewPrice.indicator.TradingStrategy;
-import com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy.MacdEmaStrategy;
-import com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy.MacdMaStrategy;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.*;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.Kline;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListService;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeInfoEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeLoginService;
-import com.xcong.excoin.modules.okxNewPrice.utils.SSLConfig;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
-import com.xcong.excoin.utils.RedisUtils;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-import org.java_websocket.handshake.ServerHandshake;
-
-import java.math.BigDecimal;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
-
-/**
- * OKX 新价格 WebSocket 客户端类,用于连接 OKX 的 WebSocket 接口,
- * 实时获取并处理标记价格(mark price)数据,并将价格信息存储到 Redis 中。
- * 同时支持心跳检测、自动重连以及异常恢复机制。
- * @author Administrator
- */
-@Slf4j
-public class OkxKlineWebSocketClient {
- private final RedisUtils redisUtils;
- private final CaoZuoService caoZuoService;
- private final OkxWebSocketClientManager clientManager;
- private final WangGeListService wangGeListService;
-
- private WebSocketClient webSocketClient;
- private ScheduledExecutorService heartbeatExecutor;
- private volatile ScheduledFuture<?> pongTimeoutFuture;
- private final AtomicReference<Long> lastMessageTime = new AtomicReference<>(System.currentTimeMillis());
-
- // 连接状态标志
- private final AtomicBoolean isConnected = new AtomicBoolean(false);
- private final AtomicBoolean isConnecting = new AtomicBoolean(false);
- private final AtomicBoolean isInitialized = new AtomicBoolean(false);
-
-// private static final String CHANNEL = "mark-price";
- private static final String CHANNEL = "candle1m";
-// private static final String CHANNEL = "candle15m";
-
- // 心跳超时时间(秒),小于30秒
- private static final int HEARTBEAT_TIMEOUT = 10;
-
- // 共享线程池用于重连等异步任务
- private final ExecutorService sharedExecutor = Executors.newCachedThreadPool(r -> {
- Thread t = new Thread(r, "okx-ws-kline-worker");
- t.setDaemon(true);
- return t;
- });
-
- public OkxKlineWebSocketClient(RedisUtils redisUtils,
- CaoZuoService caoZuoService, OkxWebSocketClientManager clientManager,
- WangGeListService wangGeListService) {
- this.redisUtils = redisUtils;
- this.caoZuoService = caoZuoService;
- this.clientManager = clientManager;
- this.wangGeListService = wangGeListService;
- }
-
- /**
- * 初始化方法,创建并初始化WebSocket客户端实例
- */
- public void init() {
- if (!isInitialized.compareAndSet(false, true)) {
- log.warn("OkxKlineWebSocketClient 已经初始化过,跳过重复初始化");
- return;
- }
- connect();
- startHeartbeat();
- }
-
- /**
- * 销毁方法,关闭WebSocket连接和相关资源
- */
- public void destroy() {
- log.info("开始销毁OkxKlineWebSocketClient");
-
- // 设置关闭标志,避免重连
- if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
- sharedExecutor.shutdown();
- }
-
- if (webSocketClient != null && webSocketClient.isOpen()) {
- try {
- webSocketClient.closeBlocking();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("关闭WebSocket连接时被中断");
- }
- }
-
- shutdownExecutorGracefully(heartbeatExecutor);
- if (pongTimeoutFuture != null) {
- pongTimeoutFuture.cancel(true);
- }
- shutdownExecutorGracefully(sharedExecutor);
-
- log.info("OkxKlineWebSocketClient销毁完成");
- }
-
- private static final String WS_URL_MONIPAN = "wss://wspap.okx.com:8443/ws/v5/business";
- private static final String WS_URL_SHIPAN = "wss://ws.okx.com:8443/ws/v5/business";
- private static final boolean isAccountType = false;
-
- /**
- * 建立与 OKX WebSocket 服务器的连接。
- * 设置回调函数以监听连接打开、接收消息、关闭和错误事件。
- */
- private void connect() {
- // 避免重复连接
- if (isConnecting.get()) {
- log.info("连接已在进行中,跳过重复连接请求");
- return;
- }
-
- if (!isConnecting.compareAndSet(false, true)) {
- log.info("连接已在进行中,跳过重复连接请求");
- return;
- }
-
- try {
- SSLConfig.configureSSL();
- System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
- String WS_URL = WS_URL_MONIPAN;
- if (isAccountType){
- WS_URL = WS_URL_SHIPAN;
- }
- URI uri = new URI(WS_URL);
-
- // 关闭之前的连接(如果存在)
- if (webSocketClient != null) {
- try {
- webSocketClient.closeBlocking();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("关闭之前连接时被中断");
- }
- }
-
- webSocketClient = new WebSocketClient(uri) {
- @Override
- public void onOpen(ServerHandshake handshake) {
- log.info("OKX kline WebSocket连接成功");
- isConnected.set(true);
- isConnecting.set(false);
-
- // 检查应用是否正在关闭
- if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
- resetHeartbeatTimer();
- subscribeChannels();
- } else {
- log.warn("应用正在关闭,忽略WebSocket连接成功回调");
- }
- }
-
- @Override
- public void onMessage(String message) {
- lastMessageTime.set(System.currentTimeMillis());
- handleWebSocketMessage(message);
- resetHeartbeatTimer();
- }
-
- @Override
- public void onClose(int code, String reason, boolean remote) {
- log.warn("OKX kline WebSocket连接关闭: 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();
- log.error("重连线程被中断", e);
- } catch (Exception e) {
- log.error("重连失败", e);
- }
- });
- } else {
- log.warn("共享线程池已关闭,无法执行重连任务");
- }
- }
-
- @Override
- public void onError(Exception ex) {
- log.error("OKX New Price WebSocket发生错误", ex);
- isConnected.set(false);
- }
- };
-
- webSocketClient.connect();
- } catch (URISyntaxException e) {
- log.error("WebSocket URI格式错误", e);
- isConnecting.set(false);
- }
- }
-
- /**
- * 订阅指定交易对的价格通道。
- * 构造订阅请求并发送给服务端。
- */
- private void subscribeChannels() {
- JSONObject subscribeMsg = new JSONObject();
- subscribeMsg.put("op", "subscribe");
-
- JSONArray argsArray = new JSONArray();
- JSONObject arg = new JSONObject();
- arg.put("channel", CHANNEL);
- arg.put("instId", CoinEnums.HE_YUE.getCode());
- argsArray.add(arg);
-
- subscribeMsg.put("args", argsArray);
- webSocketClient.send(subscribeMsg.toJSONString());
- log.info("已发送 K线频道订阅请求,订阅通道数: {}", argsArray.size());
- }
-
- /**
- * 处理从 WebSocket 收到的消息。
- * 包括订阅确认、错误响应、心跳响应以及实际的数据推送。
- *
- * @param message 来自 WebSocket 的原始字符串消息
- */
- private void handleWebSocketMessage(String message) {
- try {
- if ("pong".equals(message)) {
- log.debug("{}: 收到心跳响应");
- cancelPongTimeout();
- return;
- }
- JSONObject response = JSON.parseObject(message);
- String event = response.getString("event");
-
- if ("subscribe".equals(event)) {
- log.info(" K线频道订阅成功: {}", response.getJSONObject("arg"));
- } else if ("error".equals(event)) {
- log.error(" K线频道订阅错误: code={}, msg={}",
- response.getString("code"), response.getString("msg"));
- } else if ("pong".equals(event)) {
- log.debug("收到pong响应");
- cancelPongTimeout();
- } else {
-// processPushData(response);
- processPushDataV2(response);
- }
- } catch (Exception e) {
- log.error("处理WebSocket消息失败: {}", message, e);
- }
- }
-
- /**
- * 解析并处理价格推送数据。
- * 将最新的标记价格存入 Redis 并触发后续业务逻辑比较处理。
- * 当价格变化时,调用CaoZuoService的caoZuo方法,触发所有账号的量化操作
- *
- * @param response 包含价格数据的 JSON 对象
- */
- private void processPushDataV2(JSONObject response) {
- try {
- /**
- * {
- * "arg": {
- * "channel": "candle1D",
- * "instId": "BTC-USDT"
- * },
- * "data": [
- * [
- * "1629993600000",
- * "42500",
- * "48199.9",
- * "41006.1",
- * "41006.1",
- * "3587.41204591",
- * "166741046.22583129",
- * "166741046.22583129",
- * "0"
- * ]
- * ]
- * }
- */
- JSONObject arg = response.getJSONObject("arg");
- if (arg == null) {
- log.warn("{}: 无效的推送数据,缺少 'arg' 字段", response);
- return;
- }
-
- String channel = arg.getString("channel");
- if (channel == null) {
- log.warn("{}: 无效的推送数据,缺少 'channel' 字段", response);
- return;
- }
-
- String instId = arg.getString("instId");
- if (instId == null) {
- log.warn("{}: 无效的推送数据,缺少 'instId' 字段", response);
- return;
- }
-
- if (CHANNEL.equals(channel) && CoinEnums.HE_YUE.getCode().equals(instId)) {
- JSONArray dataArray = response.getJSONArray("data");
- if (dataArray == null || dataArray.isEmpty()) {
- log.warn("K线频道数据为空");
- return;
- }
- JSONArray data = dataArray.getJSONArray(0);
- BigDecimal openPx = new BigDecimal(data.getString(1));
- BigDecimal highPx = new BigDecimal(data.getString(2));
- BigDecimal lowPx = new BigDecimal(data.getString(3));
- BigDecimal closePx = new BigDecimal(data.getString(4));
- BigDecimal vol = new BigDecimal(data.getString(5));
- //ts String 开始时间,Unix时间戳的毫秒数格式,如 1597026383085 转日期:2020-08-07 15:13:03.085
- String time = DateUtil.TimeStampToDateTime(Long.parseLong(data.getString(0)));
- /**
- * K线状态
- * 0:K线未完结
- * 1:K线已完结
- */
- String confirm = data.getString(8);
- if ("1".equals(confirm)){
- //调用策略
- // 创建策略实例
- MacdEmaStrategy strategy = new MacdEmaStrategy();
-
- // 生成200个1m价格数据点
- List<Kline> kline1MinuteData = getKlineDataByInstIdAndBar(instId, "1m");
- List<BigDecimal> historicalPrices1M = kline1MinuteData.stream()
- .map(Kline::getC)
- .collect(Collectors.toList());
-
- // 使用策略分析最新价格数据
- MacdEmaStrategy.TradingOrder tradingOrderOpenOpen = strategy.generateTradingOrder(historicalPrices1M, MacdMaStrategy.OperationType.open.name());
- if (tradingOrderOpenOpen == null){
- return;
- }
-
- Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients();
- //如果为空,则直接返回
- if (allClients.isEmpty()) {
- return;
- }
- // 获取所有OkxQuantWebSocketClient实例
- for (OkxQuantWebSocketClient client : clientManager.getAllClients()) {
- String accountName = client.getAccountName();
- if (accountName != null) {
- if (ObjectUtil.isNotEmpty(tradingOrderOpenOpen)){
- log.info("{}开仓{}:{}",instId,tradingOrderOpenOpen.getPosSide(),tradingOrderOpenOpen.getSide());
- doOpen(client.getWebSocketClient(),accountName, tradingOrderOpenOpen, closePx);
- }
- }
- }
- }
- }
- } 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("-", "");
- }
-
- /**
- * 启动心跳检测任务。
- * 使用 ScheduledExecutorService 定期检查是否需要发送 ping 请求来维持连接。
- */
- private void startHeartbeat() {
- if (heartbeatExecutor != null && !heartbeatExecutor.isTerminated()) {
- heartbeatExecutor.shutdownNow();
- }
-
- heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
- Thread t = new Thread(r, "okx-kline-heartbeat");
- t.setDaemon(true);
- return t;
- });
-
- heartbeatExecutor.scheduleWithFixedDelay(this::checkHeartbeatTimeout, 25, 25, TimeUnit.SECONDS);
- }
-
- /**
- * 重置心跳计时器。
- * 当收到新消息或发送 ping 后取消当前超时任务并重新安排下一次超时检查。
- */
- private synchronized void resetHeartbeatTimer() {
- cancelPongTimeout();
-
- if (heartbeatExecutor != null && !heartbeatExecutor.isShutdown()) {
- pongTimeoutFuture = heartbeatExecutor.schedule(this::checkHeartbeatTimeout,
- HEARTBEAT_TIMEOUT, TimeUnit.SECONDS);
- }
- }
-
- /**
- * 检查心跳超时情况。
- * 若长时间未收到任何消息则主动发送 ping 请求保持连接活跃。
- */
- private void checkHeartbeatTimeout() {
- // 只有在连接状态下才检查心跳
- if (!isConnected.get()) {
- return;
- }
-
- long currentTime = System.currentTimeMillis();
- long lastTime = lastMessageTime.get();
-
- if (currentTime - lastTime >= HEARTBEAT_TIMEOUT * 1000L) {
- sendPing();
- }
- }
-
- /**
- * 发送 ping 请求至 WebSocket 服务端。
- * 用于维持长连接有效性。
- */
- private void sendPing() {
- try {
- if (webSocketClient != null && webSocketClient.isOpen()) {
- webSocketClient.send("ping");
- log.debug("发送ping请求");
- }
- } catch (Exception e) {
- log.warn("发送ping失败", e);
- }
- }
-
- /**
- * 取消当前的心跳超时任务。
- * 在收到 pong 或其他有效消息时调用此方法避免不必要的断开重连。
- */
- private synchronized void cancelPongTimeout() {
- if (pongTimeoutFuture != null && !pongTimeoutFuture.isDone()) {
- pongTimeoutFuture.cancel(true);
- }
- }
-
- /**
- * 执行 WebSocket 重连操作。
- * 在连接意外中断后尝试重新建立连接。
- */
- private void reconnectWithBackoff() throws InterruptedException {
- int attempt = 0;
- int maxAttempts = 3;
- long delayMs = 5000;
-
- while (attempt < maxAttempts) {
- try {
- Thread.sleep(delayMs);
- connect();
- return;
- } catch (Exception e) {
- log.warn("第{}次重连失败", attempt + 1, e);
- delayMs *= 2;
- attempt++;
- }
- }
-
- log.error("超过最大重试次数({})仍未连接成功", maxAttempts);
- }
-
- /**
- * 优雅关闭线程池
- */
- private void shutdownExecutorGracefully(ExecutorService executor) {
- if (executor == null || executor.isTerminated()) {
- return;
- }
- try {
- executor.shutdown();
- if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
- executor.shutdownNow();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- executor.shutdownNow();
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxNewPriceWebSocketClient.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxNewPriceWebSocketClient.java
deleted file mode 100644
index 782467e..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxNewPriceWebSocketClient.java
+++ /dev/null
@@ -1,468 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.TradeOrderWs;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListService;
-import com.xcong.excoin.modules.okxNewPrice.utils.SSLConfig;
-import com.xcong.excoin.utils.RedisUtils;
-import java.math.BigDecimal;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-import org.java_websocket.handshake.ServerHandshake;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * OKX 新价格 WebSocket 客户端类,用于连接 OKX 的 WebSocket 接口,
- * 实时获取并处理标记价格(mark price)数据,并将价格信息存储到 Redis 中。
- * 同时支持心跳检测、自动重连以及异常恢复机制。
- * @author Administrator
- */
-@Slf4j
-public class OkxNewPriceWebSocketClient {
- private final RedisUtils redisUtils;
- private final CaoZuoService caoZuoService;
- private final OkxWebSocketClientManager clientManager;
- private final WangGeListService wangGeListService;
-
- private WebSocketClient webSocketClient;
- private ScheduledExecutorService heartbeatExecutor;
- private volatile ScheduledFuture<?> pongTimeoutFuture;
- private final AtomicReference<Long> lastMessageTime = new AtomicReference<>(System.currentTimeMillis());
-
- // 连接状态标志
- private final AtomicBoolean isConnected = new AtomicBoolean(false);
- private final AtomicBoolean isConnecting = new AtomicBoolean(false);
- private final AtomicBoolean isInitialized = new AtomicBoolean(false);
-
- private static final String CHANNEL = "mark-price";
-
- // 心跳超时时间(秒),小于30秒
- private static final int HEARTBEAT_TIMEOUT = 10;
-
- // 共享线程池用于重连等异步任务
- private final ExecutorService sharedExecutor = Executors.newCachedThreadPool(r -> {
- Thread t = new Thread(r, "okx-ws-shared-worker");
- t.setDaemon(true);
- return t;
- });
-
- public OkxNewPriceWebSocketClient(RedisUtils redisUtils,
- CaoZuoService caoZuoService, OkxWebSocketClientManager clientManager,
- WangGeListService wangGeListService) {
- this.redisUtils = redisUtils;
- this.caoZuoService = caoZuoService;
- this.clientManager = clientManager;
- this.wangGeListService = wangGeListService;
- }
-
- /**
- * 初始化方法,创建并初始化WebSocket客户端实例
- */
- public void init() {
- if (!isInitialized.compareAndSet(false, true)) {
- log.warn("OkxNewPriceWebSocketClient 已经初始化过,跳过重复初始化");
- return;
- }
- connect();
- startHeartbeat();
- }
-
- /**
- * 销毁方法,关闭WebSocket连接和相关资源
- */
- public void destroy() {
- log.info("开始销毁OkxNewPriceWebSocketClient");
-
- // 设置关闭标志,避免重连
- if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
- sharedExecutor.shutdown();
- }
-
- if (webSocketClient != null && webSocketClient.isOpen()) {
- try {
- webSocketClient.closeBlocking();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("关闭WebSocket连接时被中断");
- }
- }
-
- shutdownExecutorGracefully(heartbeatExecutor);
- if (pongTimeoutFuture != null) {
- pongTimeoutFuture.cancel(true);
- }
- shutdownExecutorGracefully(sharedExecutor);
-
- log.info("OkxNewPriceWebSocketClient销毁完成");
- }
-
- private static final String WS_URL_MONIPAN = "wss://wspap.okx.com:8443/ws/v5/public";
- private static final String WS_URL_SHIPAN = "wss://ws.okx.com:8443/ws/v5/public";
- private static final boolean isAccountType = true;
-
- /**
- * 建立与 OKX WebSocket 服务器的连接。
- * 设置回调函数以监听连接打开、接收消息、关闭和错误事件。
- */
- private void connect() {
- // 避免重复连接
- if (isConnecting.get()) {
- log.info("连接已在进行中,跳过重复连接请求");
- return;
- }
-
- if (!isConnecting.compareAndSet(false, true)) {
- log.info("连接已在进行中,跳过重复连接请求");
- return;
- }
-
- try {
- SSLConfig.configureSSL();
- System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
- String WS_URL = WS_URL_MONIPAN;
- if (isAccountType){
- WS_URL = WS_URL_SHIPAN;
- }
- URI uri = new URI(WS_URL);
-
- // 关闭之前的连接(如果存在)
- if (webSocketClient != null) {
- try {
- webSocketClient.closeBlocking();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("关闭之前连接时被中断");
- }
- }
-
- webSocketClient = new WebSocketClient(uri) {
- @Override
- public void onOpen(ServerHandshake handshake) {
- log.info("OKX New Price WebSocket连接成功");
- isConnected.set(true);
- isConnecting.set(false);
-
- // 检查应用是否正在关闭
- if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
- resetHeartbeatTimer();
- subscribeChannels();
- } else {
- log.warn("应用正在关闭,忽略WebSocket连接成功回调");
- }
- }
-
- @Override
- public void onMessage(String message) {
- lastMessageTime.set(System.currentTimeMillis());
- handleWebSocketMessage(message);
- resetHeartbeatTimer();
- }
-
- @Override
- public void onClose(int code, String reason, boolean remote) {
- log.warn("OKX New Price WebSocket连接关闭: 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();
- log.error("重连线程被中断", e);
- } catch (Exception e) {
- log.error("重连失败", e);
- }
- });
- } else {
- log.warn("共享线程池已关闭,无法执行重连任务");
- }
- }
-
- @Override
- public void onError(Exception ex) {
- log.error("OKX New Price WebSocket发生错误", ex);
- isConnected.set(false);
- }
- };
-
- webSocketClient.connect();
- } catch (URISyntaxException e) {
- log.error("WebSocket URI格式错误", e);
- isConnecting.set(false);
- }
- }
-
- /**
- * 订阅指定交易对的价格通道。
- * 构造订阅请求并发送给服务端。
- */
- private void subscribeChannels() {
- JSONObject subscribeMsg = new JSONObject();
- subscribeMsg.put("op", "subscribe");
-
- JSONArray argsArray = new JSONArray();
- JSONObject arg = new JSONObject();
- arg.put("channel", CHANNEL);
- arg.put("instId", CoinEnums.HE_YUE.getCode());
- argsArray.add(arg);
-
- subscribeMsg.put("args", argsArray);
- webSocketClient.send(subscribeMsg.toJSONString());
- log.info("已发送价格订阅请求,订阅通道数: {}", argsArray.size());
- }
-
- /**
- * 处理从 WebSocket 收到的消息。
- * 包括订阅确认、错误响应、心跳响应以及实际的数据推送。
- *
- * @param message 来自 WebSocket 的原始字符串消息
- */
- private void handleWebSocketMessage(String message) {
- try {
- JSONObject response = JSON.parseObject(message);
- String event = response.getString("event");
-
- if ("subscribe".equals(event)) {
- log.info("价格订阅成功: {}", response.getJSONObject("arg"));
- } else if ("error".equals(event)) {
- log.error("价格订阅错误: code={}, msg={}",
- response.getString("code"), response.getString("msg"));
- } else if ("pong".equals(event)) {
- log.debug("收到pong响应");
- cancelPongTimeout();
- } else {
- processPushData(response);
- }
- } catch (Exception e) {
- log.error("处理WebSocket消息失败: {}", message, e);
- }
- }
-
- /**
- * 解析并处理价格推送数据。
- * 将最新的标记价格存入 Redis 并触发后续业务逻辑比较处理。
- * 当价格变化时,调用CaoZuoService的caoZuo方法,触发所有账号的量化操作
- *
- * @param response 包含价格数据的 JSON 对象
- */
- private void processPushData(JSONObject response) {
- try {
- JSONArray dataArray = response.getJSONArray("data");
- if (dataArray != null && !dataArray.isEmpty()) {
- for (int i = 0; i < dataArray.size(); i++) {
- try {
- JSONObject priceData = dataArray.getJSONObject(i);
- String instId = priceData.getString("instId");
- String markPx = priceData.getString("markPx");
- // 保存价格到Redis
- redisUtils.set(CoinEnums.HE_YUE.getCode(), markPx);
-
- log.debug("更新最新价格: {} = {}, 币种: {}", CoinEnums.HE_YUE.getCode(), markPx, instId);
-
- // 价格变化时,触发所有账号的量化操作
- triggerQuantOperations(markPx);
- } catch (Exception innerEx) {
- log.warn("处理单条价格数据失败", innerEx);
- }
- }
- }
- } catch (Exception e) {
- log.error("处理价格推送数据失败", e);
- }
- }
-
- /**
- * 触发所有账号的量化操作
- * @param markPx 当前标记价格
- */
- private void triggerQuantOperations(String markPx) {
- try {
- // 1. 判断当前价格属于哪个网格
- WangGeListEnum gridByPriceNew = WangGeListEnum.getGridByPrice(new BigDecimal(markPx));
- if (gridByPriceNew == null) {
- log.error("当前价格{}不在任何网格范围内,无法触发量化操作", markPx);
- return;
- }
- /**
- * 获取当前网格信息
- * 根据当前网格的持仓方向获取反方向是否存在持仓
- * 如果持有,直接止损
- */
- Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients();
- //如果为空,则直接返回
- if (allClients.isEmpty()) {
- return;
- }
- // 获取所有OkxQuantWebSocketClient实例
- for (OkxQuantWebSocketClient client : clientManager.getAllClients()) {
- String accountName = client.getAccountName();
- if (accountName != null) {
- /**
- * 处理历史网格的订单
- * 根据历史网格的开单方向,是否需要止损处理
- * 如果方向一致就不需要处理
- * 如果不一致则需要处理
- */
- String fangXiang = gridByPriceNew.getFang_xiang();
- String fangXiangOld = CoinEnums.POSSIDE_LONG.getCode().equals(fangXiang) ? CoinEnums.POSSIDE_SHORT.getCode() : CoinEnums.POSSIDE_LONG.getCode();
- log.info("历史网格方向为:{}", fangXiangOld);
- if (!fangXiang.equals(fangXiangOld)){
- TradeRequestParam tradeRequestParamOld = caoZuoService.caoZuoZhiSunEvent(accountName, markPx, fangXiangOld);
- TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParamOld);
- }
-
- /**
- * 处理当前网格的订单,触发量化操作
- */
- log.info("当前价格{}属于网格: {}-{}({}-{})", markPx, gridByPriceNew.getName(),gridByPriceNew.getFang_xiang(), gridByPriceNew.getJiage_xiaxian(), gridByPriceNew.getJiage_shangxian());
- wangGeListService.initWangGe(markPx);
- TradeRequestParam tradeRequestParam = caoZuoService.caoZuoHandler(accountName, markPx, gridByPriceNew.getFang_xiang());
- TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParam);
- log.info("价格变化触发量化操作: 账号={}, 价格={}", accountName, markPx);
- }
- }
- } catch (Exception e) {
- log.error("触发量化操作失败", e);
- }
- }
-
- /**
- * 构建 Redis Key
- */
- private String buildRedisKey(String instId) {
- return "PRICE_" + instId.replace("-", "");
- }
-
- /**
- * 启动心跳检测任务。
- * 使用 ScheduledExecutorService 定期检查是否需要发送 ping 请求来维持连接。
- */
- private void startHeartbeat() {
- if (heartbeatExecutor != null && !heartbeatExecutor.isTerminated()) {
- heartbeatExecutor.shutdownNow();
- }
-
- heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
- Thread t = new Thread(r, "okx-newprice-heartbeat");
- t.setDaemon(true);
- return t;
- });
-
- heartbeatExecutor.scheduleWithFixedDelay(this::checkHeartbeatTimeout, 25, 25, TimeUnit.SECONDS);
- }
-
- /**
- * 重置心跳计时器。
- * 当收到新消息或发送 ping 后取消当前超时任务并重新安排下一次超时检查。
- */
- private synchronized void resetHeartbeatTimer() {
- cancelPongTimeout();
-
- if (heartbeatExecutor != null && !heartbeatExecutor.isShutdown()) {
- pongTimeoutFuture = heartbeatExecutor.schedule(this::checkHeartbeatTimeout,
- HEARTBEAT_TIMEOUT, TimeUnit.SECONDS);
- }
- }
-
- /**
- * 检查心跳超时情况。
- * 若长时间未收到任何消息则主动发送 ping 请求保持连接活跃。
- */
- private void checkHeartbeatTimeout() {
- // 只有在连接状态下才检查心跳
- if (!isConnected.get()) {
- return;
- }
-
- long currentTime = System.currentTimeMillis();
- long lastTime = lastMessageTime.get();
-
- if (currentTime - lastTime >= HEARTBEAT_TIMEOUT * 1000L) {
- sendPing();
- }
- }
-
- /**
- * 发送 ping 请求至 WebSocket 服务端。
- * 用于维持长连接有效性。
- */
- private void sendPing() {
- try {
- if (webSocketClient != null && webSocketClient.isOpen()) {
- JSONObject ping = new JSONObject();
- ping.put("op", "ping");
- webSocketClient.send(ping.toJSONString());
- log.debug("发送ping请求");
- }
- } catch (Exception e) {
- log.warn("发送ping失败", e);
- }
- }
-
- /**
- * 取消当前的心跳超时任务。
- * 在收到 pong 或其他有效消息时调用此方法避免不必要的断开重连。
- */
- private synchronized void cancelPongTimeout() {
- if (pongTimeoutFuture != null && !pongTimeoutFuture.isDone()) {
- pongTimeoutFuture.cancel(true);
- }
- }
-
- /**
- * 执行 WebSocket 重连操作。
- * 在连接意外中断后尝试重新建立连接。
- */
- private void reconnectWithBackoff() throws InterruptedException {
- int attempt = 0;
- int maxAttempts = 3;
- long delayMs = 5000;
-
- while (attempt < maxAttempts) {
- try {
- Thread.sleep(delayMs);
- connect();
- return;
- } catch (Exception e) {
- log.warn("第{}次重连失败", attempt + 1, e);
- delayMs *= 2;
- attempt++;
- }
- }
-
- log.error("超过最大重试次数({})仍未连接成功", maxAttempts);
- }
-
- /**
- * 优雅关闭线程池
- */
- private void shutdownExecutorGracefully(ExecutorService executor) {
- if (executor == null || executor.isTerminated()) {
- return;
- }
- try {
- executor.shutdown();
- if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
- executor.shutdownNow();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- executor.shutdownNow();
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java
deleted file mode 100644
index fd30e06..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java
+++ /dev/null
@@ -1,517 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.*;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.ExchangeInfoEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import com.xcong.excoin.modules.okxNewPrice.utils.SSLConfig;
-import com.xcong.excoin.utils.RedisUtils;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-import org.java_websocket.handshake.ServerHandshake;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import java.math.BigDecimal;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * OKX 新价格 WebSocket 客户端类,用于连接 OKX 的 WebSocket 接口,
- * 实时获取并处理标记价格(mark price)数据,并将价格信息存储到 Redis 中。
- * 同时支持心跳检测、自动重连以及异常恢复机制。
- * @author Administrator
- */
-@Slf4j
-public class OkxQuantWebSocketClient {
- private final RedisUtils redisUtils;
- private final ExchangeInfoEnum account;
- private final CaoZuoService caoZuoService;
-
- private WebSocketClient webSocketClient;
- private ScheduledExecutorService heartbeatExecutor;
- private volatile ScheduledFuture<?> pongTimeoutFuture;
- private final AtomicReference<Long> lastMessageTime = new AtomicReference<>(System.currentTimeMillis());
-
- // 连接状态标志
- private final AtomicBoolean isConnected = new AtomicBoolean(false);
- private final AtomicBoolean isConnecting = new AtomicBoolean(false);
-
- /**
- * 获取WebSocketClient实例
- * @return WebSocketClient实例
- */
- public WebSocketClient getWebSocketClient() {
- return webSocketClient;
- }
-
- /**
- * 获取账号名称
- * @return 账号名称
- */
- public String getAccountName() {
- return account.name();
- }
-
- public OkxQuantWebSocketClient(ExchangeInfoEnum account,
- CaoZuoService caoZuoService,
- RedisUtils redisUtils) {
- this.account = account;
- this.caoZuoService = caoZuoService;
- this.redisUtils = redisUtils;
- }
-
- private static final String WS_URL_MONIPAN = "wss://wspap.okx.com:8443/ws/v5/private";
- private static final String WS_URL_SHIPAN = "wss://ws.okx.com:8443/ws/v5/private";
-
- /**
- * 订阅频道指令
- */
- private static final String SUBSCRIBE = "subscribe";
- private static final String UNSUBSCRIBE = "unsubscribe";
-
- // 心跳超时时间(秒),小于30秒
- private static final int HEARTBEAT_TIMEOUT = 10;
-
- // 共享线程池用于重连等异步任务
- private final ExecutorService sharedExecutor = Executors.newCachedThreadPool(r -> {
- Thread t = new Thread(r, "okx-ws-account-order-worker");
- t.setDaemon(true);
- return t;
- });
-
- // 在 OkxQuantWebSocketClient 中添加初始化标记
- private final AtomicBoolean isInitialized = new AtomicBoolean(false);
-
- /**
- * 初始化方法,在 Spring Bean 构造完成后执行。
- * 负责建立 WebSocket 连接并启动心跳检测任务。
- */
- @PostConstruct
- public void init() {
- // 防止重复初始化
- if (!isInitialized.compareAndSet(false, true)) {
- log.warn("OkxQuantWebSocketClient 已经初始化过,跳过重复初始化");
- return;
- }
-
- connect();
- startHeartbeat();
- }
-
- /**
- * 销毁方法,在 Spring Bean 销毁前执行。
- * 关闭 WebSocket 连接、停止心跳定时器及相关的线程资源。
- */
-// @PreDestroy
-// public void destroy() {
-// if (webSocketClient != null && webSocketClient.isOpen()) {
-// subscribeAccountChannel(UNSUBSCRIBE);
-// subscribePositionChannel(UNSUBSCRIBE);
-// subscribeOrderInfoChannel(UNSUBSCRIBE);
-// webSocketClient.close();
-// }
-// shutdownExecutorGracefully(heartbeatExecutor);
-// if (pongTimeoutFuture != null) {
-// pongTimeoutFuture.cancel(true);
-// }
-// shutdownExecutorGracefully(sharedExecutor);
-//
-// // 移除了 reconnectScheduler 的关闭操作
-// }
- @PreDestroy
- public void destroy() {
- log.info("开始销毁OkxQuantWebSocketClient");
-
- // 设置关闭标志,避免重连
- if (sharedExecutor != null && !sharedExecutor.isShutdown()) {
- sharedExecutor.shutdown();
- }
-
- if (webSocketClient != null && webSocketClient.isOpen()) {
- try {
- subscribeAccountChannel(UNSUBSCRIBE);
- subscribePositionChannel(UNSUBSCRIBE);
- subscribeOrderInfoChannel(UNSUBSCRIBE);
- webSocketClient.closeBlocking();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("关闭WebSocket连接时被中断");
- }
- }
-
- shutdownExecutorGracefully(heartbeatExecutor);
- if (pongTimeoutFuture != null) {
- pongTimeoutFuture.cancel(true);
- }
- shutdownExecutorGracefully(sharedExecutor);
-
- log.info("OkxQuantWebSocketClient销毁完成");
- }
-
- private void shutdownExecutorGracefully(ExecutorService executor) {
- if (executor == null || executor.isTerminated()) {
- return;
- }
- try {
- executor.shutdown();
- if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
- executor.shutdownNow();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- executor.shutdownNow();
- }
- }
-
- /**
- * 建立与 OKX WebSocket 服务器的连接。
- * 设置回调函数以监听连接打开、接收消息、关闭和错误事件。
- */
- private void connect() {
- // 避免重复连接
- if (isConnecting.get()) {
- log.info("连接已在进行中,跳过重复连接请求");
- return;
- }
-
- if (!isConnecting.compareAndSet(false, true)) {
- log.info("连接已在进行中,跳过重复连接请求");
- return;
- }
-
- try {
- InstrumentsWs.handleEvent(account.name());
- SSLConfig.configureSSL();
- System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
- String WS_URL = WS_URL_MONIPAN;
- if (account.isAccountType()){
- WS_URL = WS_URL_SHIPAN;
- }
- URI uri = new URI(WS_URL);
-
- // 关闭之前的连接(如果存在)
- if (webSocketClient != null) {
- try {
- webSocketClient.closeBlocking();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("关闭之前连接时被中断");
- }
- }
-
- webSocketClient = new WebSocketClient(uri) {
- @Override
- public void onOpen(ServerHandshake handshake) {
- log.info("OKX account-order WebSocket连接成功");
- isConnected.set(true);
- isConnecting.set(false);
-
- // 棜查应用是否正在关闭
- if (!sharedExecutor.isShutdown()) {
- resetHeartbeatTimer();
- websocketLogin(account);
- } else {
- log.warn("应用正在关闭,忽略WebSocket连接成功回调");
- }
- }
-
- @Override
- public void onMessage(String message) {
- lastMessageTime.set(System.currentTimeMillis());
- handleWebSocketMessage(message);
- resetHeartbeatTimer();
- }
-
- @Override
- public void onClose(int code, String reason, boolean remote) {
- log.warn("OKX account-order WebSocket连接关闭: 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();
- log.error("重连线程被中断", e);
- } catch (Exception e) {
- log.error("重连失败", e);
- }
- });
- } else {
- log.warn("共享线程池已关闭,无法执行重连任务");
- }
- }
-
- @Override
- public void onError(Exception ex) {
- log.error("OKX account-order WebSocket发生错误", ex);
- isConnected.set(false);
- }
- };
-
- webSocketClient.connect();
- } catch (URISyntaxException e) {
- log.error("WebSocket URI格式错误", e);
- isConnecting.set(false);
- }
- }
-
- private void websocketLogin(ExchangeInfoEnum account) {
- LoginWs.websocketLogin(webSocketClient, account);
- }
-
- private void subscribeBalanceAndPositionChannel(String option) {
- BalanceAndPositionWs.subscribeBalanceAndPositionChannel(webSocketClient, option);
- }
-
- private void subscribeOrderInfoChannel(String option) {
- OrderInfoWs.subscribeOrderInfoChannel(webSocketClient, option);
- }
-
- private void subscribeAccountChannel(String option) {
- AccountWs.subscribeAccountChannel(webSocketClient, option);
- }
-
- private void subscribePositionChannel(String option) {
- PositionsWs.subscribePositionChannel(webSocketClient, option);
- }
-
- /**
- * 处理从 WebSocket 收到的消息。
- * 包括订阅确认、错误响应、心跳响应以及实际的数据推送。
- *
- * @param message 来自 WebSocket 的原始字符串消息
- */
- private void handleWebSocketMessage(String message) {
- try {
- if ("pong".equals(message)) {
- log.debug("{}: 收到心跳响应", account.name());
- cancelPongTimeout();
- return;
- }
- JSONObject response = JSON.parseObject(message);
- String event = response.getString("event");
-
- if ("login".equals(event)) {
- String code = response.getString("code");
- if ("0".equals(code)) {
- String connId = response.getString("connId");
- log.info("{}: WebSocket登录成功, connId: {}", account.name(), connId);
- subscribeAccountChannel(SUBSCRIBE);
- subscribeOrderInfoChannel(SUBSCRIBE);
- subscribePositionChannel(SUBSCRIBE);
- } else {
- log.error("{}: WebSocket登录失败, code: {}, msg: {}", account.name(), code, response.getString("msg"));
- }
- } else if ("subscribe".equals(event)) {
- subscribeEvent(response);
- } else if ("error".equals(event)) {
- log.error("{}: 订阅错误: code={}, msg={}",
- account.name(), response.getString("code"), response.getString("msg"));
- } else if ("channel-conn-count".equals(event)) {
- log.info("{}: 连接限制更新: channel={}, connCount={}",
- account.name(), response.getString("channel"), response.getString("connCount"));
- } else {
- processPushData(response);
- }
- } catch (Exception e) {
- log.error("{}: 处理WebSocket消息失败: {}", account.name(), message, e);
- }
- }
-
- private void subscribeEvent(JSONObject response) {
- JSONObject arg = response.getJSONObject("arg");
- if (arg == null) {
- log.warn("无效的推送数据,缺少 'arg' 字段 :{}",response);
- return;
- }
-
- String channel = arg.getString("channel");
- if (channel == null) {
- log.warn("无效的推送数据,缺少 'channel' 字段{}",response);
- return;
- }
- if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) {
- OrderInfoWs.initEvent(response, account.name());
- }
- if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) {
- AccountWs.initEvent(response, account.name());
- }
- if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) {
- PositionsWs.initEvent(response, account.name());
- }
- }
-
- /**
- * 解析并处理价格推送数据。
- * 将最新的标记价格存入 Redis 并触发后续业务逻辑比较处理。
- *
- * @param response 包含价格数据的 JSON 对象
- */
- private void processPushData(JSONObject response) {
- String op = response.getString("op");
- if (op != null){
- if (TradeOrderWs.ORDERWS_CHANNEL.equals(op)) {
- // 直接使用Object类型接收,避免强制类型转换
- Object data = response.get("data");
- log.info("{}: 收到下单推送结果: {}", account.name(), JSON.toJSONString(data));
- return;
- }
- }
- JSONObject arg = response.getJSONObject("arg");
- if (arg == null) {
- log.warn("{}: 无效的推送数据,缺少 'arg' 字段 :{}", account.name(), response);
- return;
- }
-
- String channel = arg.getString("channel");
- if (channel == null) {
- log.warn("{}: 无效的推送数据,缺少 'channel' 字段{}", account.name(), response);
- return;
- }
-
- // 注意:当前实现中,OrderInfoWs等类使用静态Map存储数据
- // 这会导致多账号之间的数据冲突。需要进一步修改这些类的设计,让数据存储与特定账号关联
- if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) {
-// OrderInfoWs.handleEvent(response, redisUtils, account.name());
- List<TradeRequestParam> tradeRequestParams = OrderInfoWs.handleEvent(response, redisUtils, account.name());
- TradeOrderWs.orderZhiYingZhiSunEventNoState(webSocketClient, tradeRequestParams);
- }else if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) {
- AccountWs.handleEvent(response, account.name());
- } else if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) {
- PositionsWs.handleEvent(response, account.name());
- } else if (BalanceAndPositionWs.CHANNEL_NAME.equals(channel)) {
- BalanceAndPositionWs.handleEvent(response);
- }
- }
-
- /**
- * 启动心跳检测任务。
- * 使用 ScheduledExecutorService 定期检查是否需要发送 ping 请求来维持连接。
- */
- private void startHeartbeat() {
- if (heartbeatExecutor != null && !heartbeatExecutor.isTerminated()) {
- heartbeatExecutor.shutdownNow();
- }
-
- heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
- Thread t = new Thread(r, "okx-account-order-heartbeat");
- t.setDaemon(true);
- return t;
- });
-
- heartbeatExecutor.scheduleWithFixedDelay(this::checkHeartbeatTimeout,
- HEARTBEAT_TIMEOUT, HEARTBEAT_TIMEOUT, TimeUnit.SECONDS);
- }
-
- // 移除了 schedulePeriodicReconnect 方法
-
- /**
- * 重置心跳计时器。
- * 当收到新消息或发送 ping 后取消当前超时任务并重新安排下一次超时检查。
- */
- private void resetHeartbeatTimer() {
- cancelPongTimeout();
-
- // 检查线程池状态,避免在关闭过程中提交任务
- if (heartbeatExecutor != null && !heartbeatExecutor.isShutdown()) {
- pongTimeoutFuture = heartbeatExecutor.schedule(this::checkHeartbeatTimeout,
- HEARTBEAT_TIMEOUT, TimeUnit.SECONDS);
- }
- }
-
- // 移除了 performScheduledReconnect 方法
-
- /**
- * 检查心跳超时情况。
- * 若长时间未收到任何消息则主动发送 ping 请求保持连接活跃。
- */
- private void checkHeartbeatTimeout() {
- // 只有在连接状态下才检查心跳
- if (!isConnected.get()) {
- return;
- }
-
- long currentTime = System.currentTimeMillis();
- long lastTime = lastMessageTime.get();
-
- if (currentTime - lastTime >= HEARTBEAT_TIMEOUT * 1000L) {
- sendPing();
- }
- }
-
- /**
- * 发送 ping 请求至 WebSocket 服务端。
- * 用于维持长连接有效性。
- */
- private void sendPing() {
- try {
- if (webSocketClient != null && webSocketClient.isOpen()) {
- webSocketClient.send("ping");
- log.debug("发送ping请求");
- }
- } catch (Exception e) {
- log.warn("发送ping失败", e);
- }
- }
-
- /**
- * 取消当前的心跳超时任务。
- * 在收到 pong 或其他有效消息时调用此方法避免不必要的断开重连。
- */
- private void cancelPongTimeout() {
- if (pongTimeoutFuture != null && !pongTimeoutFuture.isDone()) {
- pongTimeoutFuture.cancel(true);
- }
- }
-
- /**
- * 执行 WebSocket 重连操作。
- * 在连接意外中断后尝试重新建立连接。
- */
- private void reconnectWithBackoff() throws InterruptedException {
- // 如果正在连接,则不重复发起重连
- if (isConnecting.get()) {
- log.info("连接已在进行中,跳过重连请求");
- return;
- }
-
- int attempt = 0;
- int maxAttempts = 3;
- long delayMs = 1000;
-
- while (attempt < maxAttempts && !isConnected.get()) {
- try {
- Thread.sleep(delayMs);
- connect();
-
- // 等待连接建立
- for (int i = 0; i < 10 && isConnecting.get(); i++) {
- Thread.sleep(500);
- }
-
- if (isConnected.get()) {
- log.info("重连成功");
- return;
- }
- } catch (Exception e) {
- log.warn("第{}次重连失败", attempt + 1, e);
- delayMs *= 2;
- attempt++;
- }
- }
-
- log.error("超过最大重试次数({})仍未连接成功", maxAttempts);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientMain.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientMain.java
deleted file mode 100644
index 9eebd2d..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientMain.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice;
-
-import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoServiceImpl;
-import com.xcong.excoin.modules.okxNewPrice.wangge.WangGeServiceImpl;
-import com.xcong.excoin.utils.RedisUtils;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.support.ClassPathXmlApplicationContext;
-
-public class OkxWebSocketClientMain {
- public static void main(String[] args) throws InterruptedException {
- // 使用Spring上下文初始化管理器
- ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
- OkxWebSocketClientManager manager = context.getBean(OkxWebSocketClientManager.class);
-
- // 运行一段时间以观察结果
- Thread.sleep(1200000000L); // 运行一小时
-
- // 关闭连接
- manager.destroy();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
deleted file mode 100644
index c00f893..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice;
-
-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.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * 管理多个OKX WebSocket客户端实例,每个实例对应一个账号
- */
-@Slf4j
-@Component
-@ConditionalOnProperty(prefix = "app", name = "quant", havingValue = "true")
-public class OkxWebSocketClientManager {
- @Autowired
- private CaoZuoService caoZuoService;
- @Autowired
- private RedisUtils redisUtils;
- @Autowired
- private WangGeListService wangGeListService;
-
- // 存储所有OkxQuantWebSocketClient实例,key为账号类型名称
- private final Map<String, OkxQuantWebSocketClient> quantClientMap = new ConcurrentHashMap<>();
-
- // 存储OkxNewPriceWebSocketClient实例
- private OkxKlineWebSocketClient klinePriceClient;
-
-
- /**
- * 初始化方法,在Spring Bean构造完成后执行
- * 创建并初始化所有账号的WebSocket客户端实例
- */
- @PostConstruct
- public void init() {
- log.info("开始初始化OkxWebSocketClientManager");
-
- // 初始化价格WebSocket客户端
- try {
- klinePriceClient = new OkxKlineWebSocketClient(redisUtils, caoZuoService, this, wangGeListService);
- klinePriceClient.init();
- log.info("已初始化OkxNewPriceWebSocketClient");
- } catch (Exception e) {
- log.error("初始化OkxNewPriceWebSocketClient失败", e);
- }
-
- // 获取所有ExchangeInfoEnum枚举值
- ExchangeInfoEnum[] accounts = ExchangeInfoEnum.values();
-
- // 为每个账号创建一个WebSocket客户端实例
- for (ExchangeInfoEnum account : accounts) {
- try {
- OkxQuantWebSocketClient client = new OkxQuantWebSocketClient(account, caoZuoService, redisUtils);
- quantClientMap.put(account.name(), client);
- client.init();
- log.info("已初始化账号 {} 的WebSocket客户端", account.name());
- } catch (Exception e) {
- log.error("初始化账号 {} 的WebSocket客户端失败", account.name(), e);
- }
- }
-
- log.info("OkxWebSocketClientManager初始化完成");
- }
-
- /**
- * 销毁方法,在Spring Bean销毁前执行
- * 关闭所有WebSocket客户端连接和相关资源
- */
- @PreDestroy
- public void destroy() {
- log.info("开始销毁OkxWebSocketClientManager");
-
- // 关闭价格WebSocket客户端
- if (klinePriceClient != null) {
- try {
- klinePriceClient.destroy();
- log.info("已销毁OkxNewPriceWebSocketClient");
- } catch (Exception e) {
- log.error("销毁OkxNewPriceWebSocketClient失败", e);
- }
- }
-
- // 关闭所有量化交易WebSocket客户端实例
- for (Map.Entry<String, OkxQuantWebSocketClient> entry : quantClientMap.entrySet()) {
- try {
- OkxQuantWebSocketClient client = entry.getValue();
- client.destroy();
- log.info("已销毁账号 {} 的WebSocket客户端", entry.getKey());
- } catch (Exception e) {
- log.error("销毁账号 {} 的WebSocket客户端失败", entry.getKey(), e);
- }
- }
-
- // 清空客户端映射
- quantClientMap.clear();
-
- log.info("OkxWebSocketClientManager销毁完成");
- }
-
- /**
- * 获取指定账号的OkxQuantWebSocketClient实例
- * @param accountName 账号类型名称
- * @return WebSocket客户端实例
- */
- public OkxQuantWebSocketClient getClient(String accountName) {
- return quantClientMap.get(accountName);
- }
-
- /**
- * 获取所有OkxQuantWebSocketClient实例
- * @return 所有客户端实例的集合
- */
- public Collection<OkxQuantWebSocketClient> getAllClients() {
- return quantClientMap.values();
- }
-
- /**
- * 获取OkxNewPriceWebSocketClient实例
- * @return 价格WebSocket客户端实例
- */
- public OkxKlineWebSocketClient getKlineWebSocketClient() {
- return klinePriceClient;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md b/src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md
deleted file mode 100644
index cb6e715..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md
+++ /dev/null
@@ -1,285 +0,0 @@
-# OKX 新价格量化交易模块文档
-
-## 1. 包结构
-
-```
-okxNewPrice/
-├── celue/ # 策略层
-│ ├── CaoZuoService.java # 操作服务接口
-│ └── CaoZuoServiceImpl.java # 操作服务实现
-├── jiaoyi/ # 交易层
-│ ├── IMQService.java # 消息队列服务接口
-│ └── IMQServiceImpl.java # 消息队列服务实现
-├── okxWs/ # OKX WebSocket 处理层
-│ ├── enums/ # WebSocket 相关枚举
-│ │ ├── CoinEnums.java # 币相关枚举
-│ │ ├── ExchangeInfoEnum.java # 交易所信息枚举
-│ │ └── OrderParamEnums.java # 订单参数枚举
-│ ├── wanggeList/ # 网格列表相关
-│ │ ├── WangGeListEnum.java # 网格枚举
-│ │ ├── WangGeListQueue.java # 网格队列
-│ │ ├── WangGeListService.java # 网格服务接口
-│ │ └── WangGeListServiceImpl.java # 网格服务实现
-│ ├── AccountWs.java # 账户信息处理
-│ ├── BalanceAndPositionWs.java # 余额和持仓处理
-│ ├── InstrumentsWs.java # 合约信息处理
-│ ├── LoginWs.java # 登录处理
-│ ├── OrderInfoWs.java # 订单信息处理
-│ ├── PositionsWs.java # 持仓信息处理
-│ └── TradeOrderWs.java # 交易订单处理
-├── okxpi/ # OKX API 相关
-│ ├── config/ # 配置相关
-│ ├── enumerates/ # 枚举
-│ ├── order/ # 订单相关
-│ ├── query/ # 查询相关
-│ ├── trade/ # 交易相关
-│ ├── verify/ # 验证相关
-│ └── ... # 其他API工具类
-├── utils/ # 工具类
-│ ├── FebsException.java # 异常类
-│ ├── FebsResponse.java # 响应类
-│ ├── SSLConfig.java # SSL配置
-│ ├── SignUtils.java # 签名工具
-│ ├── WsMapBuild.java # WebSocket Map构建工具
-│ └── WsParamBuild.java # WebSocket 参数构建工具
-├── wangge/ # 网格相关
-│ ├── WangGeEnum.java # 网格枚举
-│ ├── WangGeQueue.java # 网格队列
-│ ├── WangGeService.java # 网格服务接口
-│ └── WangGeServiceImpl.java # 网格服务实现
-├── zhanghu/ # 账户相关
-│ ├── ApiMessageServiceImpl.java # API消息服务实现
-│ ├── IApiMessageService.java # API消息服务接口
-│ └── ZhangHuEnum.java # 账户枚举
-├── OkxNewPriceWebSocketClient.java # 价格WebSocket客户端
-├── OkxQuantWebSocketClient.java # 量化WebSocket客户端
-├── OkxWebSocketClientMain.java # WebSocket客户端主类
-└── OkxWebSocketClientManager.java # WebSocket客户端管理器
-```
-
-## 2. 核心组件说明
-
-### 2.1 WebSocket 客户端管理
-
-#### OkxWebSocketClientManager
-- **作用**:统一管理所有 OKX WebSocket 客户端实例
-- **核心功能**:
- - 初始化价格 WebSocket 客户端和多账号量化客户端
- - 提供客户端的获取和销毁功能
- - 管理客户端生命周期
-- **关键方法**:
- - `init()`:初始化所有客户端
- - `destroy()`:销毁所有客户端
- - `getAllClients()`:获取所有量化客户端实例
-
-#### OkxNewPriceWebSocketClient
-- **作用**:价格 WebSocket 客户端,负责获取实时价格数据
-- **核心功能**:
- - 连接 OKX 公共 WebSocket 接口获取标记价格
- - 将价格数据保存到 Redis
- - 价格变化时触发量化操作
- - 支持心跳检测和自动重连
-- **关键方法**:
- - `init()`:初始化客户端
- - `destroy()`:销毁客户端
- - `processPushData()`:处理价格推送数据
- - `triggerQuantOperations()`:触发所有账号的量化操作
-
-#### OkxQuantWebSocketClient
-- **作用**:量化交易 WebSocket 客户端,每个账号对应一个实例
-- **核心功能**:
- - 连接 OKX 私有 WebSocket 接口
- - 处理账户、持仓、订单等私有数据
- - 支持多账号独立操作
- - 支持心跳检测和自动重连
-- **关键方法**:
- - `init()`:初始化客户端
- - `destroy()`:销毁客户端
- - `websocketLogin()`:登录 WebSocket
- - `subscribeChannels()`:订阅相关频道
-
-### 2.2 策略层
-
-#### CaoZuoService
-- **作用**:交易策略服务接口
-- **核心功能**:
- - 决定是否进行交易操作
- - 根据价格和网格信息决定交易方向
- - 处理多头和空头策略
-
-#### CaoZuoServiceImpl
-- **作用**:交易策略服务实现类
-- **核心功能**:
- - 检查账户和持仓状态
- - 根据当前价格获取对应的网格
- - 实现多头和空头的具体交易逻辑
- - 管理网格队列和交易决策
-- **关键方法**:
- - `caoZuo()`:主交易逻辑
- - `caoZuoLong()`:多头交易逻辑
- - `caoZuoShort()`:空头交易逻辑
-
-### 2.3 网格策略
-
-#### WangGeListEnum
-- **作用**:网格数据枚举,定义不同价格区间的网格参数
-- **核心参数**:
- - `name`:网格名称
- - `jiage_shangxian`:价格上限
- - `jiage_xiaxian`:价格下限
- - `jian_ju`:网格间距
- - `fang_xiang`:交易方向(long/short)
-- **关键方法**:
- - `getGridByPrice()`:根据价格获取对应的网格
-
-#### WangGeListService
-- **作用**:网格服务接口,提供网格相关操作
-- **核心功能**:
- - 初始化网格队列
- - 管理网格的开仓和平仓队列
-
-### 2.4 持仓管理
-
-#### PositionsWs
-- **作用**:持仓信息处理类
-- **核心功能**:
- - 管理持仓数据(双层 Map 结构:账号_方向 -> 数据)
- - 提供持仓数据的获取和更新方法
- - 支持多账号多方向持仓管理
-- **关键方法**:
- - `initAccountName()`:初始化带方向的账号名
- - `handleEvent()`:处理持仓数据推送
- - `getAccountMap()`:获取指定账号的持仓数据
-
-## 3. 工作流程
-
-### 3.1 系统初始化流程
-
-1. **客户端初始化**:
- - Spring 容器启动时,`OkxWebSocketClientManager` 自动初始化
- - 创建并初始化 `OkxNewPriceWebSocketClient` 实例
- - 为每个账号创建并初始化 `OkxQuantWebSocketClient` 实例
-
-2. **WebSocket 连接**:
- - `OkxNewPriceWebSocketClient` 连接公共价格 WebSocket
- - 每个 `OkxQuantWebSocketClient` 连接私有 WebSocket 并登录
- - 订阅相关频道(价格、账户、持仓、订单等)
-
-### 3.2 价格触发交易流程
-
-```
-┌─────────────────────────┐ ┌─────────────────────────┐
-│ OkxNewPriceWebSocketClient │ │ WangGeListEnum │
-│ └─ processPushData() │────▶│ └─ getGridByPrice() │
-└─────────────────────────┘ └─────────────────────────┘
- ▲ ▼
- │ ┌─────────────────────────┐
- │ │ CaoZuoServiceImpl │
- │ │ └─ caoZuo() │
- │ └─────────────────────────┘
- │ ▼
- │ ┌─────────────────────────┐
- │ │ TradeOrderWs │
- │ │ └─ orderEvent() │
- │ └─────────────────────────┘
- │ ▼
-┌────────┴─────────────────────────────────────────────┐
-│ OkxQuantWebSocketClient │
-│ └─ handleWebSocketMessage() │
-└──────────────────────────────────────────────────────┘
-```
-
-1. **价格接收**:
- - `OkxNewPriceWebSocketClient` 接收实时价格推送
- - 调用 `processPushData()` 处理价格数据
-
-2. **策略决策**:
- - 根据当前价格获取对应的网格参数(`WangGeListEnum.getGridByPrice()`)
- - 调用 `CaoZuoServiceImpl.caoZuo()` 进行策略决策
- - 根据网格方向调用对应的多头或空头策略
-
-3. **订单执行**:
- - 调用 `TradeOrderWs.orderEvent()` 执行交易订单
- - 通过 `OkxQuantWebSocketClient` 发送订单指令
-
-### 3.3 持仓数据管理流程
-
-1. **数据接收**:
- - `OkxQuantWebSocketClient` 接收持仓数据推送
- - 调用 `PositionsWs.handleEvent()` 处理持仓数据
-
-2. **数据存储**:
- - 使用双层 Map 存储持仓数据:`accountName_posSide -> data`
- - 支持多头和空头方向的独立存储
-
-3. **数据使用**:
- - 策略层通过 `PositionsWs.getAccountMap()` 获取持仓数据
- - 根据持仓数据和当前价格决定交易操作
-
-## 4. 关键特性
-
-### 4.1 多网格策略
-
-- **实现方式**:通过 `WangGeListEnum` 定义多个价格区间的网格
-- **核心功能**:
- - 每个网格可设置独立的交易方向(多头/空头)
- - 根据当前价格自动匹配对应的网格
- - 支持跨网格的仓位迁移和止损
-
-### 4.2 多账号管理
-
-- **实现方式**:每个账号对应一个 `OkxQuantWebSocketClient` 实例
-- **核心功能**:
- - 支持多个交易所账号独立操作
- - 每个账号可设置独立的交易参数
- - 账号间数据隔离,互不影响
-
-### 4.3 长/空头策略支持
-
-- **实现方式**:通过 `PositionsWs` 的双层 Map 结构
-- **核心功能**:
- - 支持多头和空头方向的独立持仓管理
- - 每个方向有独立的交易逻辑
- - 支持方向切换时的仓位调整
-
-### 4.4 自动重连和心跳机制
-
-- **实现方式**:在 WebSocket 客户端中实现
-- **核心功能**:
- - 定时发送心跳包维持连接
- - 连接断开时自动重连(指数退避策略)
- - 异常处理和资源清理
-
-## 5. 配置与扩展
-
-### 5.1 网格参数配置
-
-- **当前实现**:通过 `WangGeListEnum` 硬编码配置
-- **扩展建议**:
- - 将网格参数改为可配置项(数据库或配置文件)
- - 支持动态调整网格参数
- - 提供网格参数管理界面
-
-### 5.2 交易参数配置
-
-- **核心参数**:
- - 网格间距
- - 交易方向(多头/空头)
- - 止损点
- - 交易数量
-- **扩展建议**:
- - 支持每个账号独立配置交易参数
- - 提供参数优化建议
- - 支持回测功能
-
-## 6. 总结
-
-`okxNewPrice` 包是一个完整的 OKX 量化交易系统,具有以下特点:
-
-1. **模块化设计**:清晰的分层结构,便于维护和扩展
-2. **多账号支持**:每个账号独立运行,互不影响
-3. **多网格策略**:根据价格自动切换网格,支持多头和空头策略
-4. **实时响应**:基于 WebSocket 的实时数据推送和交易执行
-5. **高可靠性**:支持心跳检测、自动重连和异常处理
-
-该系统实现了从价格获取、策略决策到订单执行的完整流程,为量化交易提供了稳定可靠的基础架构。
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java
deleted file mode 100644
index 645c35d..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.celue;
-
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-
-/**
- * @author Administrator
- */
-public interface CaoZuoService {
-
- TradeRequestParam caoZuoStrategy(String accountName, String markPx, String posSide);
-
- TradeRequestParam caoZuoHandler(String accountName, String markPx, String posSide);
-
- /**
- * 止损 事件
- * @param accountName
- * @param markPx
- * @param posSide
- * @return
- */
- TradeRequestParam caoZuoZhiSunEvent(String accountName, String markPx, String posSide);
-
- /**
- * 初始化 事件
- * @param accountName
- * @param markPx
- * @param posSide
- * @return
- */
- TradeRequestParam caoZuoInitEvent(String accountName, String markPx, String posSide);
-
- TradeRequestParam chooseEvent(TradeRequestParam tradeRequestParam);
-
- TradeRequestParam caoZuoLong(TradeRequestParam tradeRequestParam);
-
- TradeRequestParam caoZuoShort(TradeRequestParam tradeRequestParam);
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
deleted file mode 100644
index 9d6a2e2..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
+++ /dev/null
@@ -1,613 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.celue;
-
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
-import com.xcong.excoin.modules.okxNewPrice.indicator.strategy.CoreTechnicalStrategy;
-import com.xcong.excoin.modules.okxNewPrice.indicator.strategy.TechnicalIndicatorStrategy;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.*;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListQueue;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListService;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
-import com.xcong.excoin.rabbit.pricequeue.AscBigDecimal;
-import com.xcong.excoin.rabbit.pricequeue.DescBigDecimal;
-import com.xcong.excoin.utils.RedisUtils;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Map;
-import java.util.concurrent.PriorityBlockingQueue;
-
-/**
- * 操作服务实现类,用于处理与交易相关的逻辑操作。
- * 包括根据市场行情判断是否进行加仓或减仓,并维护相关价格队列。
- *
- * @author Administrator
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class CaoZuoServiceImpl implements CaoZuoService {
-
- private final WangGeListService wangGeListService;
- private final RedisUtils redisUtils;
-
- @Override
- public TradeRequestParam caoZuoStrategy(String accountName, String markPx, String posSide) {
- TradeRequestParam tradeRequestParam = new TradeRequestParam();
- tradeRequestParam.setAccountName(accountName);
- tradeRequestParam.setMarkPx(markPx);
- tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode());
- tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode());
- tradeRequestParam.setPosSide(posSide);
- tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
-
- log.info("操作账户:{},当前价格: {},仓位方向: {}", accountName,markPx,posSide);
- /**
- * 准备工作
- * 1、准备好下单的基本信息
- */
- // 系统设置的开关,等于冷静中,则代表不开仓
- String outStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.OUT.name());
- if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){
- log.error("冷静中,不允许下单......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return tradeRequestParam;
- }
- BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("cashBal"));
- /**
- * 判断止损抗压
- */
- BigDecimal realKuiSunAmount = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("upl"));
- log.info("实际盈亏金额: {}", realKuiSunAmount);
- String zhiSunPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.ZHI_SUN.name());
- BigDecimal zhiSunAmount = cashBal.multiply(new BigDecimal(zhiSunPercent));
- log.info("预期亏损金额: {}", zhiSunAmount);
- String kangYaPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.KANG_CANG.name());
- BigDecimal kangYaAmount = cashBal.multiply(new BigDecimal(kangYaPercent));
- log.info("预期抗仓金额: {}", kangYaAmount);
-
- if (realKuiSunAmount.compareTo(BigDecimal.ZERO) < 0){
- realKuiSunAmount = realKuiSunAmount.multiply(new BigDecimal("-1"));
- // 账户预期亏损金额比这个还小时,立即止损
- if (realKuiSunAmount.compareTo(zhiSunAmount) > 0){
- log.error("账户冷静止损......");
- WsMapBuild.saveStringToMap(InstrumentsWs.getAccountMap(accountName), CoinEnums.OUT.name(), OrderParamEnums.OUT_YES.getValue());
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- return caoZuoZhiSunEvent(accountName, markPx, posSide);
- }
- // 判断抗压
- if (realKuiSunAmount.compareTo(kangYaAmount) > 0 && realKuiSunAmount.compareTo(zhiSunAmount) <= 0){
- log.error("账户紧张扛仓......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return tradeRequestParam;
- }
- }
-
- String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- // 判断是否保证金超标
- if (PositionsWs.getAccountMap(positionAccountName).get("imr") == null){
- log.error("没有获取到持仓信息,等待初始化......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return tradeRequestParam;
- }
- BigDecimal ordFrozImr = PositionsWs.getAccountMap(positionAccountName).get("imr");
- BigDecimal totalOrderUsdt = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDT.name()))
- .divide(new BigDecimal("2"), RoundingMode.DOWN);
- if (ordFrozImr.compareTo(totalOrderUsdt) >= 0){
- log.error("已满仓......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return tradeRequestParam;
- }
-
- if (PositionsWs.getAccountMap(positionAccountName).get("pos") == null){
- log.error("没有获取到持仓信息,等待初始化......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return tradeRequestParam;
- }
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- return tradeRequestParam;
- }
-
- /**
- * 执行主要的操作逻辑,包括读取合约状态、获取市场价格信息,
- * 并根据当前持仓均价和标记价格决定是否执行买卖操作。
- *
- * @return 返回操作类型字符串(如买入BUY、卖出SELL等),如果无有效操作则返回null
- */
- @Override
- public TradeRequestParam caoZuoHandler(String accountName, String markPx, String posSide) {
- TradeRequestParam tradeRequestParam = new TradeRequestParam();
- tradeRequestParam.setAccountName(accountName);
- tradeRequestParam.setMarkPx(markPx);
- tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode());
- tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode());
- tradeRequestParam.setPosSide(posSide);
- tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
-
- log.info("操作账户:{},当前价格: {},仓位方向: {}", accountName,markPx,posSide);
- /**
- * 准备工作
- * 1、准备好下单的基本信息
- */
- // 系统设置的开关,等于冷静中,则代表不开仓
- String outStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.OUT.name());
- if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){
- log.error("冷静中,不允许下单......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return chooseEvent(tradeRequestParam);
- }
- BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("cashBal"));
- /**
- * 判断止损抗压
- */
- BigDecimal realKuiSunAmount = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("upl"));
- log.info("实际盈亏金额: {}", realKuiSunAmount);
- String zhiSunPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.ZHI_SUN.name());
- BigDecimal zhiSunAmount = cashBal.multiply(new BigDecimal(zhiSunPercent));
- log.info("预期亏损金额: {}", zhiSunAmount);
- String kangYaPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.KANG_CANG.name());
- BigDecimal kangYaAmount = cashBal.multiply(new BigDecimal(kangYaPercent));
- log.info("预期抗仓金额: {}", kangYaAmount);
-
- if (realKuiSunAmount.compareTo(BigDecimal.ZERO) < 0){
- realKuiSunAmount = realKuiSunAmount.multiply(new BigDecimal("-1"));
- // 账户预期亏损金额比这个还小时,立即止损
- if (realKuiSunAmount.compareTo(zhiSunAmount) > 0){
- log.error("账户冷静止损......");
- WsMapBuild.saveStringToMap(InstrumentsWs.getAccountMap(accountName), CoinEnums.OUT.name(), OrderParamEnums.OUT_YES.getValue());
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- return caoZuoZhiSunEvent(accountName, markPx, posSide);
- }
- // 判断抗压
- if (realKuiSunAmount.compareTo(kangYaAmount) > 0 && realKuiSunAmount.compareTo(zhiSunAmount) <= 0){
- log.error("账户紧张扛仓......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return chooseEvent(tradeRequestParam);
- }
- }
-
- String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- // 判断是否保证金超标
- if (PositionsWs.getAccountMap(positionAccountName).get("imr") == null){
- log.error("没有获取到持仓信息,等待初始化......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return chooseEvent(tradeRequestParam);
- }
- BigDecimal ordFrozImr = PositionsWs.getAccountMap(positionAccountName).get("imr");
- BigDecimal totalOrderUsdt = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDT.name()))
- .divide(new BigDecimal("2"), RoundingMode.DOWN);
- if (ordFrozImr.compareTo(totalOrderUsdt) >= 0){
- log.error("已满仓......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return chooseEvent(tradeRequestParam);
- }
-
- if (PositionsWs.getAccountMap(positionAccountName).get("pos") == null){
- log.error("没有获取到持仓信息,等待初始化......");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return chooseEvent(tradeRequestParam);
- }
- BigDecimal pos = PositionsWs.getAccountMap(positionAccountName).get("pos");
- if (BigDecimal.ZERO.compareTo( pos) >= 0) {
- log.error("持仓数量为零,进行初始化订单");
- return caoZuoInitEvent(accountName, markPx, posSide);
- }
-
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- return chooseEvent(tradeRequestParam);
- }
-
- @Override
- public TradeRequestParam caoZuoZhiSunEvent(String accountName, String markPx, String posSide) {
-
- log.info("历史网格:操作账户:{},当前价格: {},仓位方向: {}", accountName,markPx,posSide);
- /**
- * 初始化订单请求参数
- * 获取仓位数量
- * 获取仓位方向
- */
- TradeRequestParam tradeRequestParam = new TradeRequestParam();
- tradeRequestParam.setAccountName(accountName);
- tradeRequestParam.setMarkPx(markPx);
- tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode());
- tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode());
- tradeRequestParam.setPosSide(posSide);
- tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
-
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- String side = null;
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)){
- side = CoinEnums.SIDE_SELL.getCode();
- }
- if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)){
- side = CoinEnums.SIDE_BUY.getCode();
- }
- tradeRequestParam.setSide(side);
-
- String clOrdId = WsParamBuild.getOrderNum(side);
- tradeRequestParam.setClOrdId(clOrdId);
-
- String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- BigDecimal pos = PositionsWs.getAccountMap(positionAccountName).get("pos");
- if (BigDecimal.ZERO.compareTo( pos) >= 0) {
- log.error("历史网格止损方向没有持仓");
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- }
- tradeRequestParam.setSz(String.valueOf( pos));
- return tradeRequestParam;
-
- }
-
- @Override
- public TradeRequestParam caoZuoInitEvent(String accountName, String markPx, String posSide) {
-
- log.info("当前网格初始化:操作账户:{},当前价格: {},仓位方向: {}", accountName,markPx,posSide);
- /**
- * 初始化订单请求参数
- * 获取仓位数量
- * 获取仓位方向
- */
- TradeRequestParam tradeRequestParam = new TradeRequestParam();
- tradeRequestParam.setAccountName(accountName);
- tradeRequestParam.setMarkPx(markPx);
- tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode());
- tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode());
- tradeRequestParam.setPosSide(posSide);
- tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
-
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- String side = null;
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)){
- side = CoinEnums.SIDE_BUY.getCode();
- }
- if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)){
- side = CoinEnums.SIDE_SELL.getCode();
- }
- 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);
-
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- return tradeRequestParam;
- }
-
- @Override
- public TradeRequestParam chooseEvent(TradeRequestParam tradeRequestParam) {
- log.info("开始执行chooseEvent......");
- if (OrderParamEnums.TRADE_NO.getValue().equals(tradeRequestParam.getTradeType())){
- return tradeRequestParam;
- }
- if (OrderParamEnums.TRADE_YES.getValue().equals(tradeRequestParam.getTradeType())){
- String posSide = tradeRequestParam.getPosSide();
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)){
- tradeRequestParam = caoZuoLong(tradeRequestParam);
- }else if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)){
- tradeRequestParam = caoZuoShort(tradeRequestParam);
- }
- }
- return tradeRequestParam;
- }
-
- @Override
- public TradeRequestParam caoZuoLong(TradeRequestParam tradeRequestParam) {
- log.info("开始做{}执行操作CaoZuoServiceImpl......",tradeRequestParam.getPosSide());
-
- String accountName = tradeRequestParam.getAccountName();
- String markPxStr = tradeRequestParam.getMarkPx();
- String posSide = tradeRequestParam.getPosSide();
-
- try {
-
- String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- // 获取标记价格和平均持仓价格
- BigDecimal markPx = new BigDecimal(markPxStr);
- BigDecimal avgPx = PositionsWs.getAccountMap(positionAccountName).get("avgPx");
- log.info("持仓价格: {}, 当前价格:{},匹配队列中......", avgPx, markPx);
- // 初始化网格队列
- PriorityBlockingQueue<AscBigDecimal> queueAsc = WangGeListQueue.getQueueAsc();
- PriorityBlockingQueue<DescBigDecimal> queueKaiCang = wangGeListService.initKaiCang(avgPx, queueAsc);
- PriorityBlockingQueue<AscBigDecimal> queuePingCang = wangGeListService.initPingCang(avgPx, queueAsc);
-
- // 处理订单价格在队列中的情况
- String orderPrice = OrderInfoWs.getAccountMap(accountName).get("orderPrice");
- log.info("上一次网格触发价格: {}", orderPrice);
- handleOrderPriceInQueues(orderPrice, queueKaiCang, queuePingCang);
- // 判断是加仓还是减仓
- if (avgPx.compareTo(markPx) > 0) {
- log.info("开始买入开多...");
- if (!queueKaiCang.isEmpty()) {
- DescBigDecimal kaiCang = queueKaiCang.peek();
- log.info("买入开多队列价格{}", kaiCang.getValue());
- if (kaiCang != null && markPx.compareTo(kaiCang.getValue()) <= 0 && avgPx.compareTo(kaiCang.getValue()) >= 0) {
- log.info("开始买入开多...买入开多队列价格价格大于当前价格{}>{}", kaiCang.getValue(), markPx);
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- String side = CoinEnums.SIDE_BUY.getCode();
- tradeRequestParam.setSide(side);
- String clOrdId = WsParamBuild.getOrderNum(side);
- tradeRequestParam.setClOrdId(clOrdId);
- String sz = buyCntTimeLongEvent(accountName, avgPx, markPx);
- tradeRequestParam.setSz(sz);
- log.info("买入开多参数准备成功......");
- } else {
- log.info("未触发加仓......,等待");
- }
- }else{
- // 队列为空
- log.info("超出了网格设置...");
- }
- } else if (avgPx.compareTo(markPx) < 0) {
- log.info("开始卖出平多...");
- if (!queuePingCang.isEmpty()) {
- AscBigDecimal pingCang = queuePingCang.peek();
- log.info("卖出平多队列价格:{}", pingCang.getValue());
- if (pingCang != null && avgPx.compareTo(pingCang.getValue()) < 0) {
- log.info("开始卖出平多...卖出平多队列价格大于开仓价格{}>{}", pingCang.getValue(), avgPx);
- // 手续费
- BigDecimal feeValue = PositionsWs.getAccountMap(positionAccountName).get("fee").multiply(new BigDecimal(2));
- //未实现收益
- BigDecimal uplValue = PositionsWs.getAccountMap(positionAccountName).get("upl");
- //已实现收益
- BigDecimal realizedPnlValue = PositionsWs.getAccountMap(positionAccountName).get("realizedPnl");
- realizedPnlValue = realizedPnlValue.add(feeValue);
-
- //持仓保证金
- BigDecimal imr = PositionsWs.getAccountMap(positionAccountName).get("imr");
- String pingCangImr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name());
- BigDecimal imrValue = imr.multiply(new BigDecimal(pingCangImr));
-
- if (realizedPnlValue.compareTo(BigDecimal.ZERO) <= 0) {
- BigDecimal realizedPnlValueZheng = realizedPnlValue.multiply(new BigDecimal("-1"));
- if (uplValue.compareTo(realizedPnlValue) > 0 && uplValue.compareTo(imrValue.add(realizedPnlValueZheng)) >= 0) {
- log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(realizedPnlValueZheng));
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- String side = CoinEnums.SIDE_SELL.getCode();
- tradeRequestParam.setSide(side);
- String clOrdId = WsParamBuild.getOrderNum(side);
- tradeRequestParam.setClOrdId(clOrdId);
- BigDecimal sz = PositionsWs.getAccountMap(positionAccountName).get("pos");
- tradeRequestParam.setSz(String.valueOf( sz));
- log.info("卖出平多参数准备成功......");
- }else{
- log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(realizedPnlValueZheng));
- }
- }else {
- if (uplValue.compareTo(imrValue.add(feeValue)) >= 0) {
- log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(feeValue));
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- String side = CoinEnums.SIDE_SELL.getCode();
- tradeRequestParam.setSide(side);
- String clOrdId = WsParamBuild.getOrderNum(side);
- tradeRequestParam.setClOrdId(clOrdId);
- BigDecimal sz = PositionsWs.getAccountMap(positionAccountName).get("pos");
- tradeRequestParam.setSz(String.valueOf( sz));
- log.info("卖出平多参数准备成功......");
- }else{
- log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(feeValue));
- }
- }
- } else {
- log.info("未触发减仓......,等待");
- }
- }else{
- // 队列为空
- log.info("超出了网格设置...");
- }
- } else {
- log.info("价格波动较小......,等待");
- }
- return tradeRequestParam;
- } catch (NumberFormatException e) {
- log.error("开多方向异常", e);
- return tradeRequestParam;
- }
- }
-
- @Override
- public TradeRequestParam caoZuoShort(TradeRequestParam tradeRequestParam) {
- log.info("开始做{}执行操作CaoZuoServiceImpl......",tradeRequestParam.getPosSide());
-
- String accountName = tradeRequestParam.getAccountName();
- String markPxStr = tradeRequestParam.getMarkPx();
- String posSide = tradeRequestParam.getPosSide();
-
- try {
- String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- // 获取标记价格和平均持仓价格
- BigDecimal markPx = new BigDecimal(markPxStr);
- BigDecimal avgPx = PositionsWs.getAccountMap(positionAccountName).get("avgPx");
- log.info("持仓价格: {}, 当前价格:{},匹配队列中......", avgPx, markPx);
-
- // 初始化网格队列
- PriorityBlockingQueue<AscBigDecimal> queueAsc = WangGeListQueue.getQueueAsc();
- PriorityBlockingQueue<DescBigDecimal> queueKaiCang = wangGeListService.initKaiCang(avgPx, queueAsc);
- PriorityBlockingQueue<AscBigDecimal> queuePingCang = wangGeListService.initPingCang(avgPx, queueAsc);
-
- // 处理订单价格在队列中的情况
- String orderPrice = OrderInfoWs.getAccountMap(accountName).get("orderPrice");
- log.info("上一次网格触发价格:{}", orderPrice);
- handleOrderPriceInQueues(orderPrice, queueKaiCang, queuePingCang);
- // 判断是加仓还是减仓
- if (avgPx.compareTo(markPx) > 0) {
- log.info("开始买入平空...");
- if (!queueKaiCang.isEmpty()) {
- DescBigDecimal kaiCang = queueKaiCang.peek();
- log.info("买入平空队列价格{}", kaiCang.getValue());
- if (kaiCang != null && avgPx.compareTo(kaiCang.getValue()) >= 0) {
- log.info("开始买入平空...买入平空队列价格小于开仓价格{}<{}", kaiCang.getValue(), avgPx);
-
- // 手续费
- BigDecimal feeValue = PositionsWs.getAccountMap(positionAccountName).get("fee").multiply(new BigDecimal("2"));
- //未实现收益
- BigDecimal uplValue = PositionsWs.getAccountMap(positionAccountName).get("upl");
- //已实现收益
- BigDecimal realizedPnlValue = PositionsWs.getAccountMap(positionAccountName).get("realizedPnl");
- realizedPnlValue = realizedPnlValue.add(feeValue);
-
- //持仓保证金
- BigDecimal imr = PositionsWs.getAccountMap(positionAccountName).get("imr");
- String pingCangImr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name());
- BigDecimal imrValue = imr.multiply(new BigDecimal(pingCangImr));
-
- if (realizedPnlValue.compareTo(BigDecimal.ZERO) <= 0) {
- BigDecimal realizedPnlValueZheng = realizedPnlValue.multiply(new BigDecimal("-1"));
- if (uplValue.compareTo(realizedPnlValue) > 0 && uplValue.compareTo(imrValue.add(realizedPnlValueZheng)) >= 0) {
- log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(realizedPnlValueZheng));
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- String side = CoinEnums.SIDE_BUY.getCode();
- tradeRequestParam.setSide(side);
- String clOrdId = WsParamBuild.getOrderNum(side);
- tradeRequestParam.setClOrdId(clOrdId);
- BigDecimal sz = PositionsWs.getAccountMap(positionAccountName).get("pos");
- tradeRequestParam.setSz(String.valueOf( sz));
- log.info("买入平空参数准备成功......");
- }else{
- log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(realizedPnlValueZheng));
- }
- }else {
- if (uplValue.compareTo(imrValue.add(feeValue)) >= 0) {
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(feeValue));
- String side = CoinEnums.SIDE_BUY.getCode();
- tradeRequestParam.setSide(side);
- String clOrdId = WsParamBuild.getOrderNum(side);
- tradeRequestParam.setClOrdId(clOrdId);
- BigDecimal sz = PositionsWs.getAccountMap(positionAccountName).get("pos");
- tradeRequestParam.setSz(String.valueOf( sz));
- log.info("买入平空参数准备成功......");
- }else{
- log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(feeValue));
- }
- }
- } else {
- log.info("未触发减仓......,等待");
- }
- }else{
- log.info("开始减仓,但是超出了网格设置...");
- }
- } else if (avgPx.compareTo(markPx) < 0) {
- log.info("开始卖出开空...");
- if (!queuePingCang.isEmpty()) {
- AscBigDecimal pingCang = queuePingCang.peek();
- log.info("上限队列价格: {}", pingCang.getValue());
- if (pingCang != null && markPx.compareTo(pingCang.getValue()) >= 0 && avgPx.compareTo(pingCang.getValue()) < 0) {
- log.info("开始加仓...上限队列价格小于当前价格{}<={}", pingCang.getValue(), markPx);
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- String side = CoinEnums.SIDE_SELL.getCode();
- tradeRequestParam.setSide(side);
- String clOrdId = WsParamBuild.getOrderNum(side);
- tradeRequestParam.setClOrdId(clOrdId);
- String sz = buyCntTimeShortEvent(accountName, avgPx, markPx);
- tradeRequestParam.setSz(sz);
- log.info("卖出开空参数准备成功......");
- } else {
- log.info("未触发加仓......,等待");
- }
- }else{
- // 队列为空
- log.info("超出了网格设置...");
- }
- } else {
- log.info("价格波动较小......,等待");
- }
- return tradeRequestParam;
- } catch (NumberFormatException e) {
- log.error("开空方向异常", e);
- return tradeRequestParam;
- }
- }
-
- private String buyCntTimeLongEvent(String accountName, BigDecimal avgPx, BigDecimal markPx){
- //判断当前价格和开仓价格直接间隔除以间距,取整,获取的数量是否大于等于0,如果大于0,则下单基础张数*倍数
- String buyCntTime = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_TIME.name());
- log.info("倍数次数间隔{}", buyCntTime);
- BigDecimal subtract = avgPx.subtract(markPx);
- log.info("倍数价格差距{}", subtract);
- BigDecimal divide = subtract.divide(new BigDecimal(buyCntTime), 0, RoundingMode.DOWN).add(BigDecimal.ONE);
- log.info("倍数次数{}", divide);
- String buyCntInit = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
- return String.valueOf(divide.multiply(new BigDecimal(buyCntInit)));
- }
-
- private String buyCntTimeShortEvent(String accountName, BigDecimal avgPx, BigDecimal markPx){
- //判断当前价格和开仓价格直接间隔除以间距,取整,获取的数量是否大于等于0,如果大于0,则下单基础张数*倍数
-
- String buyCntTime = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_TIME.name());
- log.info("倍数次数间隔{}", buyCntTime);
- BigDecimal subtract = markPx.subtract(avgPx);
- log.info("倍数价格差距{}", subtract);
- BigDecimal divide = subtract.divide(new BigDecimal(buyCntTime), 0, RoundingMode.DOWN).add(BigDecimal.ONE);
- log.info("倍数次数{}", divide);
- String buyCntInit = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
- return String.valueOf(divide.multiply(new BigDecimal(buyCntInit)));
- }
-
- /**
- * 根据订单价格更新开仓和平仓队列的内容。
- * 若该价格不在对应队列中则加入;若已存在,则从队列中移除。
- *
- * @param orderPrice 订单价格
- * @param queueKaiCang 开仓价格优先队列(降序)
- * @param queuePingCang 平仓价格优先队列(升序)
- */
- private void handleOrderPriceInQueues(String orderPrice,
- PriorityBlockingQueue<DescBigDecimal> queueKaiCang,
- PriorityBlockingQueue<AscBigDecimal> queuePingCang) {
- if (orderPrice == null) {
- return;
- }
- log.info("需要移除的价格: {}", orderPrice);
-
- BigDecimal priceDecimal;
- try {
- priceDecimal = new BigDecimal(orderPrice);
- } catch (NumberFormatException ex) {
- log.warn("无效的价格格式: {}", orderPrice);
- return;
- }
-
- // 删除比该价格大的数据
- queueKaiCang.removeIf(item -> item.getValue().compareTo(priceDecimal) >= 0);
-
- // 打印开仓队列
- StringBuilder kaiCangStr = new StringBuilder();
- kaiCangStr.append("下限队列: [");
- boolean first = true;
- for (DescBigDecimal item : queueKaiCang) {
- if (!first) {
- kaiCangStr.append(", ");
- }
- kaiCangStr.append(item.getValue());
- first = false;
- }
- kaiCangStr.append("]");
- log.info(kaiCangStr.toString());
-
- // 删除比该价格小的数据
- queuePingCang.removeIf(item -> item.getValue().compareTo(priceDecimal) <= 0);
-
- // 打印平仓队列
- StringBuilder pingCangStr = new StringBuilder();
- pingCangStr.append("上限队列: [");
- first = true;
- for (AscBigDecimal item : queuePingCang) {
- if (!first) {
- pingCangStr.append(", ");
- }
- pingCangStr.append(item.getValue());
- first = false;
- }
- pingCangStr.append("]");
- log.info(pingCangStr.toString());
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java
deleted file mode 100644
index 4308fcb..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/AdvancedMA.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.List;
-
-/**
- * Advanced MA (Moving Average) 指标实现
- * 支持扩展周期的指数移动平均线(EMA),用于三重EMA交叉系统
- *
- * 作用:
- * 1. 基于三重EMA交叉系统识别趋势方向和强度
- * 2. 当9EMA > 21EMA > 55EMA时形成多头排列,提示上涨趋势
- * 3. 当9EMA < 21EMA < 55EMA时形成空头排列,提示下跌趋势
- * 4. 计算三线粘合度,自动过滤震荡行情
- *
- * 价格参数类型:
- * - 参数名称:prices
- * - 参数类型:List<BigDecimal>
- * - 参数说明:需要至少1个价格数据点用于计算,根据不同周期需求更多数据点
- *
- * 推荐时间粒度及优缺点:
- * 1. 5分钟(5m):
- * - 优点:适合短线三重EMA交叉策略
- * - 缺点:需要频繁监控,容易受短期波动影响
- * 2. 15分钟(15m):
- * - 优点:平衡了信号可靠性和反应速度
- * - 缺点:仍有一定噪音
- * 3. 1小时(1h):
- * - 优点:信号较为可靠,适合中期趋势跟踪
- * - 缺点:反应较慢
- * 4. 4小时(4h)及以上:
- * - 优点:趋势信号明确,适合长期持仓
- * - 缺点:反应滞后,入场点较晚
- */
-@Slf4j
-@Getter
-@Setter
-public class AdvancedMA extends IndicatorBase {
-
- // 扩展周期 - 三重EMA交叉系统
- public static final int EMA9 = 9;
- public static final int EMA21 = 21;
- public static final int EMA55 = 55;
-
- private BigDecimal ema9 = BigDecimal.ZERO;
- private BigDecimal ema21 = BigDecimal.ZERO;
- private BigDecimal ema55 = BigDecimal.ZERO;
-
- private BigDecimal prevEma9 = null;
- private BigDecimal prevEma21 = null;
- private BigDecimal prevEma55 = null;
-
- /**
- * 计算三重EMA交叉系统的指标
- * @param prices 价格列表
- */
- public void calculateTripleEMA(List<BigDecimal> prices) {
- if (prices == null || prices.isEmpty()) {
- return;
- }
-
- // 计算三重EMA
- prevEma9 = calculateEMA(prices, EMA9, prevEma9);
- ema9 = prevEma9;
-
- prevEma21 = calculateEMA(prices, EMA21, prevEma21);
- ema21 = prevEma21;
-
- prevEma55 = calculateEMA(prices, EMA55, prevEma55);
- ema55 = prevEma55;
-
- log.info("三重EMA计算结果 - EMA9: {}, EMA21: {}, EMA55: {}",
- ema9, ema21, ema55);
- }
-
- /**
- * 判断三重EMA多头排列
- * 当9EMA > 21EMA > 55EMA时触发多头条件
- * @return 是否形成多头排列
- */
- public boolean isBullish() {
- return ema9.compareTo(ema21) > 0 && ema21.compareTo(ema55) > 0;
- }
-
- /**
- * 判断三重EMA空头排列
- * 当9EMA < 21EMA < 55EMA时触发空头条件
- * @return 是否形成空头排列
- */
- public boolean isBearish() {
- return ema9.compareTo(ema21) < 0 && ema21.compareTo(ema55) < 0;
- }
-
- /**
- * 计算三线粘合度
- * 用于自动过滤震荡行情
- * @return 粘合度百分比,值越小表示越粘合
- */
- public BigDecimal calculatePercent() {
- // 计算最大值和最小值
- BigDecimal max = ema9.max(ema21).max(ema55);
- BigDecimal min = ema9.min(ema21).min(ema55);
-
- // 计算平均价
- BigDecimal avg = ema9.add(ema21).add(ema55).divide(new BigDecimal(3), 8, RoundingMode.HALF_UP);
-
- // 计算粘合度: (最大值 - 最小值) / 平均值 * 100%
- if (avg.compareTo(BigDecimal.ZERO) == 0) {
- return BigDecimal.ZERO;
- }
-
- return max.subtract(min).divide(avg, 8, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
- }
-
- /**
- * 检查是否处于震荡行情(三线粘合)
- * 当三线粘合度 < 2% 时判定为震荡行情
- * @return 是否处于震荡行情
- */
- public boolean isUpAndDown() {
- BigDecimal bigDecimal = calculatePercent();
- return bigDecimal.compareTo(new BigDecimal(2)) < 0;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java
deleted file mode 100644
index 7075f88..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/BOLL.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.List;
-
-/**
- * BOLL (Bollinger Bands) 指标实现
- * 计算逻辑:
- * 1. 中轨(MB)= N日移动平均线
- * 2. 上轨(UP)= 中轨 + K倍标准差
- * 3. 下轨(DN)= 中轨 - K倍标准差
- *
- * 作用:
- * 1. 测量价格波动范围和市场宽度
- * 2. 价格突破上轨,提示超买或趋势加速
- * 3. 价格跌破下轨,提示超卖或趋势加速
- * 4. 轨道收窄,提示即将发生剧烈波动
- * 5. 价格回归轨道内,提示趋势可能反转
- *
- * 价格参数类型:
- * - 参数名称:prices
- * - 参数类型:List<BigDecimal>
- * - 参数说明:需要至少20个(默认周期)价格数据点用于计算
- *
- * 推荐时间粒度及优缺点:
- * 1. 1分钟(1m):
- * - 优点:反应迅速,适合超短线突破策略
- * - 缺点:布林带宽度窄,假突破多
- * 2. 5分钟(5m):
- * - 优点:布林带宽度适中,突破信号相对可靠
- * - 缺点:仍有一定假突破
- * 3. 15分钟(15m):
- * - 优点:适合日内交易,突破信号较为可靠
- * - 缺点:反应速度较慢
- * 4. 1小时(1h)及以上:
- * - 优点:布林带宽度稳定,突破信号可靠
- * - 缺点:反应滞后,不适合短线交易
- */
-@Slf4j
-@Getter
-@Setter
-public class BOLL extends IndicatorBase {
-
- private static final int DEFAULT_PERIOD = 20;
- private static final double DEFAULT_K = 2.0;
-
- private int period = DEFAULT_PERIOD;
- private BigDecimal k = new BigDecimal(DEFAULT_K);
- private BigDecimal mid = BigDecimal.ZERO;
- private BigDecimal upper = BigDecimal.ZERO;
- private BigDecimal lower = BigDecimal.ZERO;
-
- public BOLL() {}
-
- public BOLL(int period, double k) {
- this.period = period;
- this.k = new BigDecimal(k);
- }
-
- /**
- * 计算BOLL指标
- * @param prices 价格列表
- */
- public void calculate(List<BigDecimal> prices) {
- if (prices == null || prices.size() < period) {
- return;
- }
-
- // 计算中轨(MB)= N日移动平均线
- mid = calculateMA(prices, period);
-
- // 计算标准差
- BigDecimal stdDev = calculateStdDev(prices, period);
-
- // 计算上轨(UP)和下轨(DN)
- BigDecimal bandWidth = k.multiply(stdDev);
- upper = mid.add(bandWidth).setScale(8, RoundingMode.HALF_UP);
- lower = mid.subtract(bandWidth).setScale(8, RoundingMode.HALF_UP);
-
- log.info("BOLL计算结果 - 中轨: {}, 上轨: {}, 下轨: {}", mid, upper, lower);
- }
-
- /**
- * 判断价格是否突破上轨
- * @param price 当前价格
- * @return 是否突破上轨
- */
- public boolean isBreakUpper(BigDecimal price) {
- return price.compareTo(upper) > 0;
- }
-
- /**
- * 判断价格是否跌破下轨
- * @param price 当前价格
- * @return 是否跌破下轨
- */
- public boolean isBreakLower(BigDecimal price) {
- return price.compareTo(lower) < 0;
- }
-
- /**
- * 判断价格是否回归上轨下方
- * @param price 当前价格
- * @param prevPrice 前一期价格
- * @return 是否回归上轨下方
- */
- public boolean isReturnFromUpper(BigDecimal price, BigDecimal prevPrice) {
- return prevPrice.compareTo(upper) > 0 && price.compareTo(upper) <= 0;
- }
-
- /**
- * 判断价格是否回归下轨上方
- * @param price 当前价格
- * @param prevPrice 前一期价格
- * @return 是否回归下轨上方
- */
- public boolean isReturnFromLower(BigDecimal price, BigDecimal prevPrice) {
- return prevPrice.compareTo(lower) < 0 && price.compareTo(lower) >= 0;
- }
-
- /**
- * 判断价格是否在中轨上方
- * @param price 当前价格
- * @return 是否在中轨上方
- */
- public boolean isAboveMid(BigDecimal price) {
- return price.compareTo(mid) > 0;
- }
-
- /**
- * 判断价格是否在中轨下方
- * @param price 当前价格
- * @return 是否在中轨下方
- */
- public boolean isBelowMid(BigDecimal price) {
- return price.compareTo(mid) < 0;
- }
-
- /**
- * 计算布林带宽度
- * @return 布林带宽度
- */
- public BigDecimal calculateBandWidth() {
- if (mid.compareTo(BigDecimal.ZERO) == 0) {
- return BigDecimal.ZERO;
- }
- return upper.subtract(lower).divide(mid, 8, RoundingMode.HALF_UP);
- }
-
- /**
- * 计算价格相对于布林带的位置
- * @param price 当前价格
- * @return 价格位置指标 (-1: 下轨外, 0: 轨道内, 1: 上轨外)
- */
- public int getPricePosition(BigDecimal price) {
- if (price.compareTo(upper) > 0) {
- return 1;
- } else if (price.compareTo(lower) < 0) {
- return -1;
- } else {
- return 0;
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java
deleted file mode 100644
index ed1b226..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteStrategyExample.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 15分钟交易策略使用示例
- * 展示如何使用FifteenMinuteTradingStrategy处理100个15分钟价格数据点
- */
-public class FifteenMinuteStrategyExample {
-
- public static void main(String[] args) {
- // 1. 创建策略实例
- FifteenMinuteTradingStrategy strategy = new FifteenMinuteTradingStrategy();
-
- // 2. 准备100个15分钟价格数据(这里使用模拟数据,用户可以替换为真实数据)
- List<BigDecimal> prices = generateSampleFifteenMinuteData();
- System.out.println("已加载 " + prices.size() + " 个15分钟价格数据点");
-
- // 3. 获取当前价格
- BigDecimal currentPrice = prices.get(prices.size() - 1);
- System.out.println("当前价格: " + currentPrice);
-
- // 4. 示例1:获取多空方向
- System.out.println("\n=== 多空方向分析 ===");
- FifteenMinuteTradingStrategy.Direction direction = strategy.getDirection(prices);
- System.out.println("当前市场方向: " + direction);
-
- // 5. 示例2:获取开仓平仓信号(假设当前没有持仓)
- System.out.println("\n=== 开仓平仓信号分析(无持仓)===");
- FifteenMinuteTradingStrategy.PositionSignal signal1 =
- strategy.getPositionSignal(prices, false, false);
- System.out.println("无持仓时的信号: " + signal1);
-
- // 6. 示例3:获取开仓平仓信号(假设当前持有多仓)
- System.out.println("\n=== 开仓平仓信号分析(持有多仓)===");
- FifteenMinuteTradingStrategy.PositionSignal signal2 =
- strategy.getPositionSignal(prices, true, false);
- System.out.println("持有多仓时的信号: " + signal2);
-
- // 7. 示例4:获取开仓平仓信号(假设当前持有空仓)
- System.out.println("\n=== 开仓平仓信号分析(持有空仓)===");
- FifteenMinuteTradingStrategy.PositionSignal signal3 =
- strategy.getPositionSignal(prices, false, true);
- System.out.println("持有空仓时的信号: " + signal3);
-
- // 8. 示例5:获取完整交易结果
- System.out.println("\n=== 完整交易结果分析 ===");
- FifteenMinuteTradingStrategy.TradingResult result =
- strategy.getTradingResult(prices, false, false);
- System.out.println("市场方向: " + result.getDirection());
- System.out.println("交易信号: " + result.getSignal());
- System.out.println("\n指标状态详情:");
- System.out.println(result.getIndicatorStatus());
- }
-
- /**
- * 生成模拟的15分钟价格数据(100个数据点)
- * 用户可以替换为真实的价格数据
- * @return 15分钟价格数据列表
- */
- private static List<BigDecimal> generateSampleFifteenMinuteData() {
- List<BigDecimal> prices = new ArrayList<>();
-
- // 模拟ETH价格数据(从2400开始,有一定波动)
- BigDecimal basePrice = new BigDecimal(2400);
-
- for (int i = 0; i < 100; i++) {
- // 添加一些随机波动,但保持整体上升趋势
- double random = (Math.random() - 0.48) * 10; // -5 到 5 的随机波动,略微偏向上行
- BigDecimal price = basePrice.add(new BigDecimal(random));
- prices.add(price.setScale(2, BigDecimal.ROUND_HALF_UP));
-
- // 整体缓慢上升
- basePrice = basePrice.add(new BigDecimal(0.2));
- }
-
- return prices;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java
deleted file mode 100644
index d98eccf..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingExample.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * 15分钟交易策略示例
- * 演示如何使用交易策略与15分钟时间框架数据
- * 展示如何获取方向信号和交易信号
- */
-public class FifteenMinuteTradingExample {
-
- public static void main(String[] args) {
- // 创建交易策略
- TradingStrategy tradingStrategy = new TradingStrategy();
-
- // 生成100个15分钟价格数据点
- List<BigDecimal> prices = generateSampleFifteenMinuteData(100);
-
- // 生成对应的高、低、收盘价数据
- List<BigDecimal> high = generateHighPrices(prices);
- List<BigDecimal> low = generateLowPrices(prices);
- List<BigDecimal> close = new ArrayList<>(prices); // 使用价格作为收盘价
-
- // 生成成交量数据
- List<BigDecimal> volume = generateVolumeData(prices.size());
-
- // 获取最新价格
- BigDecimal currentPrice = prices.get(prices.size() - 1);
-
- // 生成多周期价格数据(5分钟、1小时、4小时)
- List<BigDecimal> fiveMinPrices = generateSampleFifteenMinuteData(100);
- List<BigDecimal> oneHourPrices = generateSampleFifteenMinuteData(100);
- List<BigDecimal> fourHourPrices = generateSampleFifteenMinuteData(100);
-
- // 其他参数
- BigDecimal fundingRate = new BigDecimal("0.001"); // 正常资金费率
- boolean hasLargeTransfer = false; // 无大额转账
- boolean hasUpcomingEvent = false; // 无即将到来的重大事件
-
- // 确定市场方向
- TradingStrategy.Direction direction = tradingStrategy.getDirection(prices, high, low, close, currentPrice);
- System.out.println("市场方向(15分钟): " + direction);
-
- // 检查当前持仓状态
- boolean hasLongPosition = false; // 示例:无当前做多持仓
- boolean hasShortPosition = false; // 示例:无当前做空持仓
-
- // 生成交易信号(开仓/平仓)
- TradingStrategy.SignalType signal = tradingStrategy.generateSignal(prices, high, low, close, volume, currentPrice,
- hasLongPosition, hasShortPosition,
- fiveMinPrices, oneHourPrices, fourHourPrices,
- fundingRate, hasLargeTransfer, hasUpcomingEvent);
- System.out.println("交易信号(15分钟): " + signal);
-
- // 显示指标状态用于分析
- System.out.println("\n指标状态:");
- System.out.println(tradingStrategy.getIndicatorStatus());
-
- // 计算动态杠杆
- BigDecimal dynamicLeverage = tradingStrategy.calculateDynamicLeverage(high, low, close);
- System.out.println("\n动态杠杆倍数: " + dynamicLeverage);
-
- // 基于信号模拟持仓变化
- if (signal == TradingStrategy.SignalType.BUY) {
- System.out.println("\n=== 执行开多操作 ===");
- hasLongPosition = true;
-
- // 演示三段式止盈策略
- BigDecimal entryPrice = currentPrice;
- BigDecimal positionSize = new BigDecimal(100);
- TradingStrategy.ProfitTakingResult profitTakingResult =
- tradingStrategy.calculateThreeStepProfitTaking(entryPrice, currentPrice, direction, positionSize);
- System.out.println("三段式止盈信号: " + profitTakingResult.getSignal());
- System.out.println("应平仓仓位大小: " + profitTakingResult.getClosePositionSize());
- } else if (signal == TradingStrategy.SignalType.SELL) {
- System.out.println("\n=== 执行开空操作 ===");
- hasShortPosition = true;
-
- // 演示三段式止盈策略
- BigDecimal entryPrice = currentPrice;
- BigDecimal positionSize = new BigDecimal(100);
- TradingStrategy.ProfitTakingResult profitTakingResult =
- tradingStrategy.calculateThreeStepProfitTaking(entryPrice, currentPrice, direction, positionSize);
- System.out.println("三段式止盈信号: " + profitTakingResult.getSignal());
- System.out.println("应平仓仓位大小: " + profitTakingResult.getClosePositionSize());
- } else if (signal == TradingStrategy.SignalType.CLOSE_BUY) {
- System.out.println("\n=== 执行平多操作 ===");
- hasLongPosition = false;
- } else if (signal == TradingStrategy.SignalType.CLOSE_SELL) {
- System.out.println("\n=== 执行平空操作 ===");
- hasShortPosition = false;
- } else {
- System.out.println("\n无需交易操作。");
- }
-
- // 现有做多持仓的模拟示例
- System.out.println("\n=== 现有做多持仓的模拟 ===");
- hasLongPosition = true;
- hasShortPosition = false;
- signal = tradingStrategy.generateSignal(prices, high, low, close, volume, currentPrice,
- hasLongPosition, hasShortPosition,
- fiveMinPrices, oneHourPrices, fourHourPrices,
- fundingRate, hasLargeTransfer, hasUpcomingEvent);
- System.out.println("有做多持仓时的交易信号: " + signal);
-
- // 现有做空持仓的模拟示例
- System.out.println("\n=== 现有做空持仓的模拟 ===");
- hasLongPosition = false;
- hasShortPosition = true;
- signal = tradingStrategy.generateSignal(prices, high, low, close, volume, currentPrice,
- hasLongPosition, hasShortPosition,
- fiveMinPrices, oneHourPrices, fourHourPrices,
- fundingRate, hasLargeTransfer, hasUpcomingEvent);
- System.out.println("有做空持仓时的交易信号: " + signal);
-
- // 模拟盈利场景演示三段式止盈
- System.out.println("\n=== 三段式止盈盈利场景演示 ===");
- BigDecimal entryPrice = new BigDecimal(2500.0);
- BigDecimal currentPriceProfit = new BigDecimal(2700.0); // 模拟盈利价格
- BigDecimal positionSize = new BigDecimal(100);
- TradingStrategy.ProfitTakingResult profitTakingResult =
- tradingStrategy.calculateThreeStepProfitTaking(entryPrice, currentPriceProfit, TradingStrategy.Direction.LONG, positionSize);
- System.out.println("入场价格: " + entryPrice);
- System.out.println("当前价格: " + currentPriceProfit);
- System.out.println("三段式止盈信号: " + profitTakingResult.getSignal());
- System.out.println("应平仓仓位大小: " + profitTakingResult.getClosePositionSize());
- }
-
- /**
- * 生成具有真实波动的15分钟价格数据
- * @param size 要生成的数据点数量
- * @return 价格数据列表
- */
- private static List<BigDecimal> generateSampleFifteenMinuteData(int size) {
- List<BigDecimal> prices = new ArrayList<>();
- Random random = new Random();
-
- // 以基础价格开始(ETH示例价格)
- BigDecimal basePrice = new BigDecimal(2500.0);
- prices.add(basePrice);
-
- // 生成具有真实波动的后续价格
- for (int i = 1; i < size; i++) {
- // 创建价格趋势(轻微上升偏向)
- BigDecimal trend = new BigDecimal(0.1).multiply(new BigDecimal(i));
- // 添加随机波动(每个周期±2%)
- BigDecimal volatility = new BigDecimal(random.nextDouble() * 0.04 - 0.02);
- // 计算新价格
- BigDecimal newPrice = basePrice.add(trend).multiply(BigDecimal.ONE.add(volatility));
- // 四舍五入到2位小数
- newPrice = newPrice.setScale(2, BigDecimal.ROUND_HALF_UP);
- prices.add(newPrice);
- }
-
- return prices;
- }
-
- /**
- * 生成最高价数据
- * @param prices 价格数据
- * @return 最高价数据列表
- */
- private static List<BigDecimal> generateHighPrices(List<BigDecimal> prices) {
- List<BigDecimal> high = new ArrayList<>();
- Random random = new Random();
-
- for (BigDecimal price : prices) {
- // 最高价比当前价格高0-2%
- BigDecimal highPrice = price.multiply(BigDecimal.ONE.add(new BigDecimal(random.nextDouble() * 0.02)));
- high.add(highPrice.setScale(2, BigDecimal.ROUND_HALF_UP));
- }
-
- return high;
- }
-
- /**
- * 生成最低价数据
- * @param prices 价格数据
- * @return 最低价数据列表
- */
- private static List<BigDecimal> generateLowPrices(List<BigDecimal> prices) {
- List<BigDecimal> low = new ArrayList<>();
- Random random = new Random();
-
- for (BigDecimal price : prices) {
- // 最低价比当前价格低0-2%
- BigDecimal lowPrice = price.multiply(BigDecimal.ONE.subtract(new BigDecimal(random.nextDouble() * 0.02)));
- low.add(lowPrice.setScale(2, BigDecimal.ROUND_HALF_UP));
- }
-
- return low;
- }
-
- /**
- * 生成成交量数据
- * @param size 数据点数量
- * @return 成交量数据列表
- */
- private static List<BigDecimal> generateVolumeData(int size) {
- List<BigDecimal> volume = new ArrayList<>();
- Random random = new Random();
-
- for (int i = 0; i < size; i++) {
- // 生成1000-10000之间的随机成交量
- BigDecimal vol = new BigDecimal(random.nextInt(9001) + 1000);
- volume.add(vol);
- }
-
- return volume;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java
deleted file mode 100644
index 6510022..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/FifteenMinuteTradingStrategy.java
+++ /dev/null
@@ -1,237 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-/**
- * 15分钟时间粒度的交易策略实现
- * 专门针对100个15分钟数据点设计的策略,包含明确的多空方向选择和开仓平仓方法
- */
-@Slf4j
-public class FifteenMinuteTradingStrategy {
-
- @Getter
- @Setter
- public static class TradingResult {
- private Direction direction; // 多空方向
- private PositionSignal signal; // 开仓平仓信号
- private String indicatorStatus; // 指标状态
-
- public TradingResult(Direction direction, PositionSignal signal, String indicatorStatus) {
- this.direction = direction;
- this.signal = signal;
- this.indicatorStatus = indicatorStatus;
- }
- }
-
- public enum Direction {
- LONG, // 多头方向
- SHORT, // 空头方向
- RANGING // 震荡行情
- }
-
- public enum PositionSignal {
- OPEN_LONG, // 开多仓
- OPEN_SHORT, // 开空仓
- CLOSE_LONG, // 平多仓
- CLOSE_SHORT, // 平空仓
- HOLD, // 持有
- STAY_OUT // 观望
- }
-
- private final MA ma;
- private final AdvancedMA advancedMA;
- private final BOLL boll;
- private final KDJ kdj;
- private final MACD macd;
- private final RSI rsi;
-
- public FifteenMinuteTradingStrategy() {
- // 15分钟数据优化的参数配置
- this.ma = new MA();
- this.advancedMA = new AdvancedMA();
- this.boll = new BOLL(20, 2.0); // BOLL使用默认20周期
- this.kdj = new KDJ(9); // KDJ使用默认9周期
- this.macd = new MACD(); // MACD使用默认12/26/9周期
- this.rsi = new RSI(14); // RSI使用默认14周期
- }
-
- /**
- * 计算所有指标
- * @param prices 15分钟价格数据(至少100个数据点)
- */
- private void calculateIndicators(List<BigDecimal> prices) {
- ma.calculate(prices);
- advancedMA.calculateTripleEMA(prices);
- boll.calculate(prices);
- kdj.calculate(prices);
- macd.calculate(prices);
- rsi.calculate(prices);
- }
-
- /**
- * 判断市场是否处于震荡行情
- * @return 是否为震荡行情
- */
- private boolean isRangeMarket() {
- // AdvancedMA三线粘合 + RSI(40-60) + BOLL带宽收窄
- boolean isMaConverged = advancedMA.calculatePercent().compareTo(new BigDecimal(2)) < 0;
- boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(40)) > 0 &&
- rsi.getRsi().compareTo(new BigDecimal(60)) < 0;
- boolean isBollNarrow = boll.calculateBandWidth().compareTo(new BigDecimal(0.05)) < 0;
-
- return isMaConverged && isRsiNeutral && isBollNarrow;
- }
-
- /**
- * 获取多空方向选择
- * @param prices 15分钟价格数据(100个数据点)
- * @return 多空方向
- */
- public Direction getDirection(List<BigDecimal> prices) {
- if (prices == null || prices.size() < 100) {
- throw new IllegalArgumentException("需要至少100个15分钟价格数据点");
- }
-
- calculateIndicators(prices);
-
- // 震荡过滤
- if (isRangeMarket()) {
- return Direction.RANGING;
- }
-
- BigDecimal currentPrice = prices.get(prices.size() - 1);
-
- // 多头信号判断:MA多头排列 + MACD金叉 + RSI(30-70) + BOLL价格在上轨与中轨之间
- boolean isLongSignal =
- ma.getEma5().compareTo(ma.getEma10()) > 0 && // MA5 > MA10
- ma.getEma10().compareTo(ma.getEma20()) > 0 && // MA10 > MA20
- macd.getDif().compareTo(macd.getDea()) > 0 && // MACD金叉
- rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI(30-70)
- currentPrice.compareTo(boll.getMid()) > 0 && currentPrice.compareTo(boll.getUpper()) < 0; // BOLL价格在上轨与中轨之间
-
- // 空头信号判断:MA空头排列 + MACD死叉 + RSI(30-70) + BOLL价格在下轨与中轨之间
- boolean isShortSignal =
- ma.getEma5().compareTo(ma.getEma10()) < 0 && // MA5 < MA10
- ma.getEma10().compareTo(ma.getEma20()) < 0 && // MA10 < MA20
- macd.getDif().compareTo(macd.getDea()) < 0 && // MACD死叉
- rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI(30-70)
- currentPrice.compareTo(boll.getMid()) < 0 && currentPrice.compareTo(boll.getLower()) > 0; // BOLL价格在下轨与中轨之间
-
- if (isLongSignal) {
- return Direction.LONG;
- } else if (isShortSignal) {
- return Direction.SHORT;
- } else {
- return Direction.RANGING;
- }
- }
-
- /**
- * 获取开仓平仓策略信号
- * @param prices 15分钟价格数据(100个数据点)
- * @param hasLongPosition 当前是否持有多仓
- * @param hasShortPosition 当前是否持有空仓
- * @return 开仓平仓信号
- */
- public PositionSignal getPositionSignal(List<BigDecimal> prices, boolean hasLongPosition, boolean hasShortPosition) {
- if (prices == null || prices.size() < 100) {
- throw new IllegalArgumentException("需要至少100个15分钟价格数据点");
- }
-
- calculateIndicators(prices);
-
- // 震荡过滤
- if (isRangeMarket()) {
- return PositionSignal.STAY_OUT;
- }
-
- BigDecimal currentPrice = prices.get(prices.size() - 1);
-
- // 开多信号:MA金叉 + MACD金叉 + KDJ金叉 + RSI中性 + 价格在BOLL中轨上方
- boolean shouldOpenLong =
- ma.getEma5().compareTo(ma.getEma20()) > 0 && // MA金叉(5日EMA上穿20日EMA)
- macd.getDif().compareTo(macd.getDea()) > 0 && macd.getMacdBar().compareTo(BigDecimal.ZERO) > 0 && // MACD金叉且柱状图为正
- kdj.isGoldenCross() && // KDJ金叉
- rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI中性
- currentPrice.compareTo(boll.getMid()) > 0; // 价格在BOLL中轨上方
-
- // 开空信号:MA死叉 + MACD死叉 + KDJ死叉 + RSI中性 + 价格在BOLL中轨下方
- boolean shouldOpenShort =
- ma.getEma5().compareTo(ma.getEma20()) < 0 && // MA死叉(5日EMA下穿20日EMA)
- macd.getDif().compareTo(macd.getDea()) < 0 && macd.getMacdBar().compareTo(BigDecimal.ZERO) < 0 && // MACD死叉且柱状图为负
- kdj.isDeathCross() && // KDJ死叉
- rsi.getRsi().compareTo(new BigDecimal(30)) > 0 && rsi.getRsi().compareTo(new BigDecimal(70)) < 0 && // RSI中性
- currentPrice.compareTo(boll.getMid()) < 0; // 价格在BOLL中轨下方
-
- // 平多信号:MA死叉 + MACD死叉 + RSI超买 + 价格跌破BOLL中轨
- boolean shouldCloseLong =
- (ma.getEma5().compareTo(ma.getEma20()) < 0 && // MA死叉
- macd.getDif().compareTo(macd.getDea()) < 0 && // MACD死叉
- (rsi.isOverbought() || rsi.isExtremelyOverbought())) || // RSI超买
- currentPrice.compareTo(boll.getMid()) < 0; // 价格跌破BOLL中轨
-
- // 平空信号:MA金叉 + MACD金叉 + RSI超卖 + 价格突破BOLL中轨
- boolean shouldCloseShort =
- (ma.getEma5().compareTo(ma.getEma20()) > 0 && // MA金叉
- macd.getDif().compareTo(macd.getDea()) > 0 && // MACD金叉
- (rsi.isOversold() || rsi.isExtremelyOversold())) || // RSI超卖
- currentPrice.compareTo(boll.getMid()) > 0; // 价格突破BOLL中轨
-
- // 确定开仓信号
- if (shouldOpenLong && !hasLongPosition && !hasShortPosition) {
- return PositionSignal.OPEN_LONG;
- } else if (shouldOpenShort && !hasLongPosition && !hasShortPosition) {
- return PositionSignal.OPEN_SHORT;
- }
-
- // 确定平仓信号
- if (shouldCloseLong && hasLongPosition) {
- return PositionSignal.CLOSE_LONG;
- } else if (shouldCloseShort && hasShortPosition) {
- return PositionSignal.CLOSE_SHORT;
- }
-
- // 无信号
- return hasLongPosition || hasShortPosition ? PositionSignal.HOLD : PositionSignal.STAY_OUT;
- }
-
- /**
- * 综合获取交易结果
- * @param prices 15分钟价格数据(100个数据点)
- * @param hasLongPosition 当前是否持有多仓
- * @param hasShortPosition 当前是否持有空仓
- * @return 包含多空方向和开仓平仓信号的完整交易结果
- */
- public TradingResult getTradingResult(List<BigDecimal> prices, boolean hasLongPosition, boolean hasShortPosition) {
- Direction direction = getDirection(prices);
- PositionSignal signal = getPositionSignal(prices, hasLongPosition, hasShortPosition);
- String indicatorStatus = getIndicatorStatus();
-
- return new TradingResult(direction, signal, indicatorStatus);
- }
-
- /**
- * 获取当前指标状态
- * @return 指标状态字符串
- */
- private String getIndicatorStatus() {
- return String.format("MA5: %s, MA20: %s, " +
- "MACD-DIF: %s, MACD-DEA: %s, MACD-BAR: %s, " +
- "KDJ-K: %s, KDJ-D: %s, KDJ-J: %s, " +
- "RSI: %s, " +
- "BOLL-UP: %s, BOLL-MID: %s, BOLL-DN: %s, " +
- "AdvancedMA-Bullish: %s, Bearish: %s, Percent: %s",
- ma.getEma5(), ma.getEma20(),
- macd.getDif(), macd.getDea(), macd.getMacdBar(),
- kdj.getK(), kdj.getD(), kdj.getJ(),
- rsi.getRsi(),
- boll.getUpper(), boll.getMid(), boll.getLower(),
- advancedMA.isBullish(), advancedMA.isBearish(), advancedMA.calculatePercent());
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java
deleted file mode 100644
index a3b80b7..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java
+++ /dev/null
@@ -1,161 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Technical indicators base class, provides common calculation methods
- *
- * Indicator combination strategy:
- * 1. Trend judgment (MA/AdvancedMA/MACD): Determine the overall trend direction of prices
- * 2. Momentum confirmation (RSI/KDJ): Confirm the strength and sustainability of the current trend
- * 3. Volatility reference (BOLL): Determine reasonable price volatility range and breakthrough timing
- *
- * Long/Short direction selection logic:
- * - Long signal: MA bullish arrangement + MACD golden cross + RSI(30-70) + BOLL price between upper and middle band
- * - Short signal: MA bearish arrangement + MACD death cross + RSI(30-70) + BOLL price between lower and middle band
- * - Consolidation signal: AdvancedMA three-line convergence + RSI(40-60) + BOLL bandwidth narrowing
- *
- * Open and close position strategies:
- * - Open long: MA golden cross + MACD golden cross + KDJ golden cross + RSI(30-70) + price breaks through BOLL middle band
- * - Open short: MA death cross + MACD death cross + KDJ death cross + RSI(30-70) + price breaks below BOLL middle band
- * - Close long: MA death cross + MACD death cross + RSI overbought(>70) + price breaks below BOLL middle band
- * - Close short: MA golden cross + MACD golden cross + RSI oversold(<30) + price breaks through BOLL middle band
- */
-public abstract class IndicatorBase {
-
- /**
- * Calculate moving average
- * @param prices Price list
- * @param period Period
- * @return Moving average value
- */
- protected BigDecimal calculateMA(List<BigDecimal> prices, int period) {
- if (prices == null || prices.size() < period) {
- return BigDecimal.ZERO;
- }
- BigDecimal sum = BigDecimal.ZERO;
- for (int i = prices.size() - period; i < prices.size(); i++) {
- sum = sum.add(prices.get(i));
- }
- return sum.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
- }
-
- /**
- * Calculate exponential moving average
- * @param prices Price list
- * @param period Period
- * @param prevEMA Previous EMA value
- * @return Exponential moving average value
- */
- protected BigDecimal calculateEMA(List<BigDecimal> prices, int period, BigDecimal prevEMA) {
- if (prices == null || prices.size() == 0) {
- return BigDecimal.ZERO;
- }
- if (prevEMA == null || prevEMA.compareTo(BigDecimal.ZERO) == 0) {
- return calculateMA(prices, Math.min(period, prices.size()));
- }
- BigDecimal k = new BigDecimal(2).divide(new BigDecimal(period + 1), 8, RoundingMode.HALF_UP);
- BigDecimal currentPrice = prices.get(prices.size() - 1);
- return currentPrice.multiply(k).add(prevEMA.multiply(BigDecimal.ONE.subtract(k)));
- }
-
- /**
- * Calculate standard deviation
- * @param prices Price list
- * @param period Period
- * @return Standard deviation
- */
- protected BigDecimal calculateStdDev(List<BigDecimal> prices, int period) {
- if (prices == null || prices.size() < period) {
- return BigDecimal.ZERO;
- }
- BigDecimal ma = calculateMA(prices, period);
- BigDecimal sumSquares = BigDecimal.ZERO;
- for (int i = prices.size() - period; i < prices.size(); i++) {
- BigDecimal diff = prices.get(i).subtract(ma);
- sumSquares = sumSquares.add(diff.multiply(diff));
- }
- BigDecimal variance = sumSquares.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
- return sqrt(variance);
- }
-
- /**
- * Calculate square root (simplified implementation)
- * @param value Input value
- * @return Square root
- */
- protected BigDecimal sqrt(BigDecimal value) {
- if (value.compareTo(BigDecimal.ZERO) < 0) {
- return BigDecimal.ZERO;
- }
- return new BigDecimal(Math.sqrt(value.doubleValue())).setScale(8, RoundingMode.HALF_UP);
- }
-
- /**
- * Get recent price data
- * @param prices All price data
- * @param period Period
- * @return Recent period price data
- */
- protected List<BigDecimal> getRecentPrices(List<BigDecimal> prices, int period) {
- if (prices == null || prices.size() == 0) {
- return new ArrayList<>();
- }
- int startIndex = Math.max(0, prices.size() - period);
- return prices.subList(startIndex, prices.size());
- }
-
- /**
- * Calculate ATR (Average True Range)
- * @param high High price list
- * @param low Low price list
- * @param close Close price list
- * @param period Period
- * @return ATR value
- */
- protected BigDecimal calculateATR(List<BigDecimal> high, List<BigDecimal> low, List<BigDecimal> close, int period) {
- if (high == null || low == null || close == null ||
- high.size() < period || low.size() < period || close.size() < period) {
- return BigDecimal.ZERO;
- }
-
- List<BigDecimal> trList = new ArrayList<>();
- for (int i = 1; i < high.size(); i++) {
- BigDecimal trueRange = calculateTrueRange(high.get(i), low.get(i), close.get(i - 1));
- trList.add(trueRange);
- }
-
- // Use simple moving average to calculate ATR
- return calculateMA(trList, Math.min(period, trList.size()));
- }
-
- /**
- * Calculate True Range
- * @param high Current high price
- * @param low Current low price
- * @param prevClose Previous close price
- * @return True range
- */
- protected BigDecimal calculateTrueRange(BigDecimal high, BigDecimal low, BigDecimal prevClose) {
- BigDecimal h1 = high.subtract(low);
- BigDecimal h2 = high.subtract(prevClose).abs();
- BigDecimal h3 = low.subtract(prevClose).abs();
- return h1.max(h2).max(h3);
- }
-
- /**
- * Calculate normalized volatility (based on ATR)
- * @param close Close price list
- * @param atr ATR value
- * @return Normalized volatility (percentage)
- */
- protected BigDecimal normalizeVolatility(List<BigDecimal> close, BigDecimal atr) {
- if (close == null || close.size() == 0 || atr.compareTo(BigDecimal.ZERO) == 0) {
- return BigDecimal.ZERO;
- }
- return atr.divide(close.get(close.size() - 1), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java
deleted file mode 100644
index 7148f4e..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/KDJ.java
+++ /dev/null
@@ -1,145 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.List;
-
-/**
- * KDJ (Stochastic Oscillator) 指标实现
- * 计算逻辑:
- * 1. RSV = (收盘价 - N日内最低价) / (N日内最高价 - N日内最低价) * 100
- * 2. K = 2/3 * 前一日K值 + 1/3 * 当日RSV
- * 3. D = 2/3 * 前一日D值 + 1/3 * 当日K值
- * 4. J = 3*K - 2*D
- *
- * 作用:
- * 1. 衡量价格的超买超卖状态(K值>80超买,K值<20超卖)
- * 2. K线上穿D线形成金叉,提示买入信号
- * 3. K线下穿D线形成死叉,提示卖出信号
- * 4. J值反映市场的极端状态,J值>100或J值<0为极端行情
- *
- * 价格参数类型:
- * - 参数名称:prices
- * - 参数类型:List<BigDecimal>
- * - 参数说明:需要至少9个(默认周期)价格数据点用于计算
- *
- * 推荐时间粒度及优缺点:
- * 1. 1分钟(1m):
- * - 优点:反应迅速,适合超短线交易
- * - 缺点:K值波动剧烈,信号频繁且可靠性低
- * 2. 5分钟(5m):
- * - 优点:K值波动相对稳定,适合短线交易
- * - 缺点:仍有一定虚假信号
- * 3. 15分钟(15m):
- * - 优点:信号较为可靠,适合日内交易
- * - 缺点:反应速度较慢
- * 4. 1小时(1h)及以上:
- * - 优点:超买超卖信号明确,适合中期交易
- * - 缺点:反应滞后,不适合短线交易
- */
-@Slf4j
-@Getter
-@Setter
-public class KDJ extends IndicatorBase {
-
- private static final int DEFAULT_PERIOD = 9;
- private static final int K_PERIOD = 3;
- private static final int D_PERIOD = 3;
-
- private int period = DEFAULT_PERIOD;
- private BigDecimal k = new BigDecimal(50);
- private BigDecimal d = new BigDecimal(50);
- private BigDecimal j = new BigDecimal(50);
- private BigDecimal prevK = new BigDecimal(50);
- private BigDecimal prevD = new BigDecimal(50);
-
- public KDJ() {}
-
- public KDJ(int period) {
- this.period = period;
- }
-
- /**
- * 计算KDJ指标
- * @param prices 价格列表
- */
- public void calculate(List<BigDecimal> prices) {
- if (prices == null || prices.size() < period) {
- return;
- }
-
- // 获取最近N天的价格
- List<BigDecimal> recentPrices = getRecentPrices(prices, period);
-
- // 计算最高价和最低价
- BigDecimal high = recentPrices.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
- BigDecimal low = recentPrices.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
- BigDecimal close = recentPrices.get(recentPrices.size() - 1);
-
- // 计算RSV
- BigDecimal rsv;
- if (high.compareTo(low) == 0) {
- rsv = new BigDecimal(50);
- } else {
- rsv = close.subtract(low)
- .divide(high.subtract(low), 8, RoundingMode.HALF_UP)
- .multiply(new BigDecimal(100))
- .setScale(8, RoundingMode.HALF_UP);
- }
-
- // 计算K值
- prevK = k;
- k = new BigDecimal(2).multiply(prevK)
- .add(rsv)
- .divide(new BigDecimal(3), 8, RoundingMode.HALF_UP);
-
- // 计算D值
- prevD = d;
- d = new BigDecimal(2).multiply(prevD)
- .add(k)
- .divide(new BigDecimal(3), 8, RoundingMode.HALF_UP);
-
- // 计算J值
- j = k.multiply(new BigDecimal(3))
- .subtract(d.multiply(new BigDecimal(2)))
- .setScale(8, RoundingMode.HALF_UP);
-
- log.info("KDJ计算结果 - K: {}, D: {}, J: {}", k, d, j);
- }
-
- /**
- * 判断超买(J > 85)
- * @return 是否超买
- */
- public boolean isOverbought() {
- return j.compareTo(new BigDecimal(85)) > 0;
- }
-
- /**
- * 判断超卖(J < 15)
- * @return 是否超卖
- */
- public boolean isOversold() {
- return j.compareTo(new BigDecimal(15)) < 0;
- }
-
- /**
- * 判断金叉信号(K线上穿D线)
- * @return 是否形成金叉
- */
- public boolean isGoldenCross() {
- return prevK.compareTo(prevD) < 0 && k.compareTo(d) > 0;
- }
-
- /**
- * 判断死叉信号(K线下穿D线)
- * @return 是否形成死叉
- */
- public boolean isDeathCross() {
- return prevK.compareTo(prevD) > 0 && k.compareTo(d) < 0;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java
deleted file mode 100644
index 0da46e6..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MA.java
+++ /dev/null
@@ -1,232 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-/**
- * MA (Moving Average) 指标实现
- * 支持不同周期的简单移动平均线(SMA)和指数移动平均线(EMA)
- *
- * 作用:
- * 1. 平滑价格波动,识别趋势方向
- * 2. 短周期MA上穿长周期MA形成金叉,提示买入信号
- * 3. 短周期MA下穿长周期MA形成死叉,提示卖出信号
- * 4. 价格上穿/下穿MA线,也可作为买卖参考
- *
- * 价格参数类型:
- * - 参数名称:prices
- * - 参数类型:List<BigDecimal>
- * - 参数说明:需要至少1个价格数据点用于计算,根据不同周期需求更多数据点
- *
- * 推荐时间粒度及优缺点:
- * 1. 1分钟(1m):
- * - 优点:反应迅速,适合超短线交易
- * - 缺点:噪音多,容易产生虚假信号
- * 2. 5分钟(5m):
- * - 优点:平衡了反应速度和噪音过滤
- * - 缺点:仍有一定噪音
- * 3. 15分钟(15m):
- * - 优点:适合日内交易,信号相对可靠
- * - 缺点:反应速度较慢
- * 4. 1小时(1h)及以上:
- * - 优点:趋势信号明确,虚假信号少
- * - 缺点:反应滞后,不适合短线交易
- */
-@Slf4j
-@Getter
-@Setter
-public class MA extends IndicatorBase {
-
- // 默认周期
- public static final int DEFAULT_MA5 = 5;
- public static final int DEFAULT_MA10 = 10;
- public static final int DEFAULT_MA20 = 20;
- public static final int DEFAULT_MA30 = 30;
- public static final int DEFAULT_MA60 = 60;
-
- // 动态周期参数
- private int ma5Period;
- private int ma10Period;
- private int ma20Period;
- private int ma30Period;
- private int ma60Period;
-
- private BigDecimal ma5 = BigDecimal.ZERO;
- private BigDecimal ma10 = BigDecimal.ZERO;
- private BigDecimal ma20 = BigDecimal.ZERO;
- private BigDecimal ma30 = BigDecimal.ZERO;
- private BigDecimal ma60 = BigDecimal.ZERO;
-
- private BigDecimal ema5 = BigDecimal.ZERO;
- private BigDecimal ema10 = BigDecimal.ZERO;
- private BigDecimal ema20 = BigDecimal.ZERO;
- private BigDecimal ema30 = BigDecimal.ZERO;
- private BigDecimal ema60 = BigDecimal.ZERO;
-
- private BigDecimal prevEma5 = null;
- private BigDecimal prevEma10 = null;
- private BigDecimal prevEma20 = null;
- private BigDecimal prevEma30 = null;
- private BigDecimal prevEma60 = null;
-
- // 构造函数使用默认周期
- public MA() {
- this.ma5Period = DEFAULT_MA5;
- this.ma10Period = DEFAULT_MA10;
- this.ma20Period = DEFAULT_MA20;
- this.ma30Period = DEFAULT_MA30;
- this.ma60Period = DEFAULT_MA60;
- }
-
- /**
- * 计算所有周期的MA指标(使用当前周期设置)
- * @param prices 价格列表
- */
- public void calculate(List<BigDecimal> prices) {
- calculate(prices, null);
- }
-
- /**
- * 计算所有周期的MA指标,并支持动态周期调整
- * @param prices 价格列表
- * @param volatility 标准化波动率(ATR百分比),用于动态调整周期
- */
- public void calculate(List<BigDecimal> prices, BigDecimal volatility) {
- if (prices == null || prices.size() < 1) {
- return;
- }
-
- // 如果提供了波动率,则动态调整周期
- if (volatility != null) {
- adjustPeriodsByVolatility(volatility);
- }
-
- // 计算SMA
- if (prices.size() >= ma5Period) {
- ma5 = calculateMA(prices, ma5Period);
- }
- if (prices.size() >= ma10Period) {
- ma10 = calculateMA(prices, ma10Period);
- }
- if (prices.size() >= ma20Period) {
- ma20 = calculateMA(prices, ma20Period);
- }
- if (prices.size() >= ma30Period) {
- ma30 = calculateMA(prices, ma30Period);
- }
- if (prices.size() >= ma60Period) {
- ma60 = calculateMA(prices, ma60Period);
- }
-
- // 计算EMA
- prevEma5 = calculateEMA(prices, ma5Period, prevEma5);
- ema5 = prevEma5;
-
- prevEma10 = calculateEMA(prices, ma10Period, prevEma10);
- ema10 = prevEma10;
-
- prevEma20 = calculateEMA(prices, ma20Period, prevEma20);
- ema20 = prevEma20;
-
- prevEma30 = calculateEMA(prices, ma30Period, prevEma30);
- ema30 = prevEma30;
-
- prevEma60 = calculateEMA(prices, ma60Period, prevEma60);
- ema60 = prevEma60;
-
- log.info("MA计算结果 - MA5({}): {}, MA10({}): {}, MA20({}): {}, MA30({}): {}, MA60({}): {}",
- ma5Period, ma5, ma10Period, ma10, ma20Period, ma20, ma30Period, ma30, ma60Period, ma60);
- log.info("EMA计算结果 - EMA5({}): {}, EMA10({}): {}, EMA20({}): {}, EMA30({}): {}, EMA60({}): {}",
- ma5Period, ema5, ma10Period, ema10, ma20Period, ema20, ma30Period, ema30, ma60Period, ema60);
- }
-
- /**
- * 根据波动率调整MA周期
- * @param volatility 标准化波动率(ATR百分比)
- */
- private void adjustPeriodsByVolatility(BigDecimal volatility) {
- // 根据波动率缩放均线周期
- // 3%、5%、8%作为ATR阈值
- BigDecimal lowVolatility = new BigDecimal(3);
- BigDecimal midVolatility = new BigDecimal(5);
- BigDecimal highVolatility = new BigDecimal(8);
-
- // 快速MA周期 (ma5)
- ma5Period = volatility.compareTo(lowVolatility) < 0 ? 10 : 6;
-
- // 中期MA周期 (ma10, ma20)
- ma10Period = volatility.compareTo(midVolatility) < 0 ? 10 : 8;
- ma20Period = volatility.compareTo(midVolatility) < 0 ? 21 : 13;
-
- // 长期MA周期 (ma30, ma60)
- ma30Period = volatility.compareTo(highVolatility) < 0 ? 30 : 24;
- ma60Period = volatility.compareTo(highVolatility) < 0 ? 50 : 34;
-
- log.info("根据波动率{}调整MA周期: ma5={}, ma10={}, ma20={}, ma30={}, ma60={}",
- volatility, ma5Period, ma10Period, ma20Period, ma30Period, ma60Period);
- }
-
- /**
- * 判断短期均线是否上穿长期均线(金叉)
- * @param shortMA 短期均线
- * @param longMA 长期均线
- * @param prevShortMA 前一期短期均线
- * @param prevLongMA 前一期长期均线
- * @return 是否形成金叉
- */
- public boolean isGoldenCross(BigDecimal shortMA, BigDecimal longMA,
- BigDecimal prevShortMA, BigDecimal prevLongMA) {
- return prevShortMA != null && prevLongMA != null &&
- prevShortMA.compareTo(prevLongMA) < 0 &&
- shortMA.compareTo(longMA) > 0;
- }
-
- /**
- * 判断短期均线是否下穿长期均线(死叉)
- * @param shortMA 短期均线
- * @param longMA 长期均线
- * @param prevShortMA 前一期短期均线
- * @param prevLongMA 前一期长期均线
- * @return 是否形成死叉
- */
- public boolean isDeathCross(BigDecimal shortMA, BigDecimal longMA,
- BigDecimal prevShortMA, BigDecimal prevLongMA) {
- return prevShortMA != null && prevLongMA != null &&
- prevShortMA.compareTo(prevLongMA) > 0 &&
- shortMA.compareTo(longMA) < 0;
- }
-
- /**
- * 判断价格是否上穿均线
- * @param price 当前价格
- * @param ma 均线值
- * @param prevPrice 前一期价格
- * @param prevMA 前一期均线值
- * @return 是否上穿
- */
- public boolean isPriceCrossUp(BigDecimal price, BigDecimal ma,
- BigDecimal prevPrice, BigDecimal prevMA) {
- return prevPrice != null && prevMA != null &&
- prevPrice.compareTo(prevMA) < 0 &&
- price.compareTo(ma) > 0;
- }
-
- /**
- * 判断价格是否下穿均线
- * @param price 当前价格
- * @param ma 均线值
- * @param prevPrice 前一期价格
- * @param prevMA 前一期均线值
- * @return 是否下穿
- */
- public boolean isPriceCrossDown(BigDecimal price, BigDecimal ma,
- BigDecimal prevPrice, BigDecimal prevMA) {
- return prevPrice != null && prevMA != null &&
- prevPrice.compareTo(prevMA) > 0 &&
- price.compareTo(ma) < 0;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java
deleted file mode 100644
index 9b63ee2..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java
+++ /dev/null
@@ -1,241 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import lombok.Getter;
-import lombok.Setter;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * MACD (Moving Average Convergence Divergence) 指标实现
- * 计算逻辑:
- * 1. 快线DIFF = EMA(Close, 12) - EMA(Close, 26)
- * 2. 慢线DEA = EMA(DIFF, 9)
- * 3. MACD柱状图 = (DIFF - DEA) * macdBarsMultiplier
- *
- * 核心概念:
- * 1. DIFF是EMA12与EMA26之间的距离,反映短期与长期趋势的差异
- * 2. 当DIFF > 0表示EMA12在EMA26上方,代表多头趋势
- * 3. 当DIFF < 0表示EMA12在EMA26下方,代表空头趋势
- * 4. DEA是DIFF的EMA平滑线,用于过滤DIFF的波动
- * 5. MACD柱状图通过放大倍数展示DIFF与DEA之间的关系,便于观察趋势变化
- *
- * 多空判断:
- * - 多头机会:DIFF在0轴上且MACD柱状图向上,股价同步上涨
- * - 空头机会:DIFF在0轴下且MACD柱状图向下,股价同步下跌
- *
- * 信号过滤:
- * - 可通过设置macdBarsSmoothingPeriod启用MACD柱状图平滑处理
- * - 平滑公式:MACD柱状图 = MA((DIFF - DEA) * macdBarsMultiplier, macdBarsSmoothingPeriod)
- * - 作用:消除柱状图杂讯,使信号更加清晰
- */
-@Getter
-@Setter
-public class MACD extends IndicatorBase {
-
- // 默认周期参数
- public static final int DEFAULT_FAST_PERIOD = 12;
- public static final int DEFAULT_SLOW_PERIOD = 26;
- public static final int DEFAULT_SIGNAL_PERIOD = 9;
-
- // 默认MACD柱状图放大倍数
- public static final int DEFAULT_MACDBARS_MULTIPLIER = 2;
- // 默认MACD柱状图平滑周期(0表示不平滑)
- public static final int DEFAULT_MACDBARS_SMOOTHING_PERIOD = 0;
-
- // 周期参数
- private int fastPeriod;
- private int slowPeriod;
- private int signalPeriod;
-
- // MACD柱状图参数
- private int macdBarsMultiplier; // MACD柱状图放大倍数
- private int macdBarsSmoothingPeriod; // MACD柱状图平滑周期(0表示不平滑)
-
- // MACD计算结果
- private BigDecimal dif = BigDecimal.ZERO;
- private BigDecimal dea = BigDecimal.ZERO;
- private BigDecimal macdBar = BigDecimal.ZERO;
-
- // 历史值缓存
- private BigDecimal prevFastEMA = null;
- private BigDecimal prevSlowEMA = null;
- private BigDecimal prevDea = null;
- private List<BigDecimal> difHistory = new ArrayList<>(); // 保存历史DIF值,用于计算DEA
- private List<BigDecimal> rawMacdBarHistory = new ArrayList<>(); // 保存原始MACD柱状图值,用于平滑处理
-
- // 最大保存的历史值数量
- private static final int MAX_HISTORY_SIZE = 50;
-
- // 构造函数使用默认参数
- public MACD() {
- this.fastPeriod = DEFAULT_FAST_PERIOD;
- this.slowPeriod = DEFAULT_SLOW_PERIOD;
- this.signalPeriod = DEFAULT_SIGNAL_PERIOD;
- this.macdBarsMultiplier = DEFAULT_MACDBARS_MULTIPLIER;
- this.macdBarsSmoothingPeriod = DEFAULT_MACDBARS_SMOOTHING_PERIOD;
- }
-
- /**
- * 计算MACD指标(使用当前周期设置)
- * @param prices 价格列表
- */
- public void calculate(List<BigDecimal> prices) {
- calculate(prices, null);
- }
-
- /**
- * 计算MACD指标,并支持动态周期调整
- * @param prices 价格列表
- * @param volatility 标准化波动率(百分比),用于动态调整周期
- */
- public void calculate(List<BigDecimal> prices, BigDecimal volatility) {
- if (prices == null || prices.isEmpty()) {
- return;
- }
-
- // 如果提供了波动率,则动态调整周期
- if (volatility != null) {
- adjustPeriodsByVolatility(volatility);
- }
-
-
-
- // 计算快速EMA (12日)
- prevFastEMA = calculateEMA(prices, fastPeriod, prevFastEMA);
-
- // 计算慢速EMA (26日)
- prevSlowEMA = calculateEMA(prices, slowPeriod, prevSlowEMA);
-
- // 计算DIF = EMA(12) - EMA(26)
- dif = prevFastEMA.subtract(prevSlowEMA).setScale(8, RoundingMode.HALF_UP);
-
- // 将新的DIF值添加到历史记录
- difHistory.add(dif);
- // 保持历史记录在合理范围内
- if (difHistory.size() > MAX_HISTORY_SIZE) {
- difHistory.remove(0);
- }
-
- // 计算DEA = EMA(DIFF, 9)
- calculateDEA();
-
- // 计算原始MACD柱状图值 = (DIF - DEA) * 放大倍数
- BigDecimal rawMacdBar = dif.subtract(dea).multiply(new BigDecimal(macdBarsMultiplier)).setScale(8, RoundingMode.HALF_UP);
-
- // 将原始MACD柱状图值添加到历史记录
- rawMacdBarHistory.add(rawMacdBar);
- // 保持历史记录在合理范围内
- if (rawMacdBarHistory.size() > MAX_HISTORY_SIZE) {
- rawMacdBarHistory.remove(0);
- }
-
- // 如果启用了平滑处理,则计算平滑后的MACD柱状图
- if (macdBarsSmoothingPeriod > 0) {
- macdBar = smoothMacdBars().setScale(8, RoundingMode.HALF_UP);
- } else {
- macdBar = rawMacdBar;
- }
-
-
- }
-
- /**
- * 计算DEA指标
- */
- private void calculateDEA() {
- int difCount = difHistory.size();
-
- // 如果没有足够的DIF历史值,无法计算有效的DEA
- if (difCount == 0) {
- dea = BigDecimal.ZERO;
- prevDea = null;
- return;
- }
-
- // 计算DEA = EMA(DIFF, signalPeriod)
- // 使用所有DIF历史值来计算初始EMA,然后使用最新值更新
- prevDea = calculateEMA(difHistory, signalPeriod, prevDea);
- dea = prevDea.setScale(8, RoundingMode.HALF_UP);
- }
-
- /**
- * 平滑MACD柱状图
- * @return 平滑后的MACD柱状图值
- */
- private BigDecimal smoothMacdBars() {
- int historyCount = rawMacdBarHistory.size();
-
- // 如果没有足够的历史数据,返回最新的原始值
- if (historyCount < macdBarsSmoothingPeriod) {
- return rawMacdBarHistory.get(historyCount - 1);
- }
-
- // 使用简单移动平均平滑MACD柱状图
- List<BigDecimal> recentMacdBars = rawMacdBarHistory.subList(historyCount - macdBarsSmoothingPeriod, historyCount);
- return calculateMA(recentMacdBars, macdBarsSmoothingPeriod);
- }
-
- /**
- * 根据波动率调整MACD周期参数
- * @param volatility 标准化波动率(百分比)
- */
- private void adjustPeriodsByVolatility(BigDecimal volatility) {
- // 波动率阈值
- BigDecimal volatilityThreshold = new BigDecimal(15);
-
- // 根据波动率调整MACD参数
- if (volatility.compareTo(volatilityThreshold) < 0) {
- // 低波动率环境,使用默认参数
- fastPeriod = DEFAULT_FAST_PERIOD;
- slowPeriod = DEFAULT_SLOW_PERIOD;
- signalPeriod = DEFAULT_SIGNAL_PERIOD;
- } else {
- // 高波动率环境,使用更灵敏的参数
- fastPeriod = 8;
- slowPeriod = 17;
- signalPeriod = 5;
- }
-
-
- }
-
- /**
- * 判断金叉信号(DIF上穿DEA)
- * @param previousDIF 上一个DIF值
- * @param previousDEA 上一个DEA值
- * @return 是否形成金叉
- */
- public boolean isGoldenCross(BigDecimal previousDIF, BigDecimal previousDEA) {
- return previousDIF != null && previousDEA != null &&
- previousDIF.compareTo(previousDEA) < 0 && dif.compareTo(dea) > 0;
- }
-
- /**
- * 判断死叉信号(DIF下穿DEA)
- * @param previousDIF 上一个DIF值
- * @param previousDEA 上一个DEA值
- * @return 是否形成死叉
- */
- public boolean isDeathCross(BigDecimal previousDIF, BigDecimal previousDEA) {
- return previousDIF != null && previousDEA != null &&
- previousDIF.compareTo(previousDEA) > 0 && dif.compareTo(dea) < 0;
- }
-
- /**
- * 重置MACD指标状态
- */
- public void reset() {
- dif = BigDecimal.ZERO;
- dea = BigDecimal.ZERO;
- macdBar = BigDecimal.ZERO;
- prevFastEMA = null;
- prevSlowEMA = null;
- prevDea = null;
- difHistory.clear();
- rawMacdBarHistory.clear();
-
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACDTest.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACDTest.java
deleted file mode 100644
index ec93614..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACDTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * MACD Indicator Test Class
- * Used to verify the correctness of MACD calculation logic
- */
-public class MACDTest {
-
- public static void main(String[] args) {
- // Create MACD instance
- MACD macd = new MACD();
-
- // Set MACD bars parameters (optional, using default values here)
- macd.setMacdBarsMultiplier(2); // Default multiplier
- macd.setMacdBarsSmoothingPeriod(0); // No smoothing
-
- // Generate test price data (simple upward trend)
- List<BigDecimal> prices = generateTestPrices(30);
-
- System.out.println("=== MACD Indicator Test Start ===");
- System.out.println("Price count: " + prices.size());
- System.out.println("MACD parameters: fast=" + macd.getFastPeriod() + ", slow=" + macd.getSlowPeriod() + ", signal=" + macd.getSignalPeriod());
- System.out.println("MACD bars parameters: multiplier=" + macd.getMacdBarsMultiplier() + ", smoothing=" + macd.getMacdBarsSmoothingPeriod());
- System.out.println();
-
- // Calculate MACD
- macd.calculate(prices);
-
- // Output results
- System.out.println("=== MACD Calculation Results ===");
- System.out.println("DIF: " + macd.getDif());
- System.out.println("DEA: " + macd.getDea());
- System.out.println("MACD Bars: " + macd.getMacdBar());
- System.out.println();
-
- // Trend judgment
- System.out.println("=== Trend Judgment ===");
- if (macd.getDif().compareTo(BigDecimal.ZERO) > 0) {
- System.out.println("DIFF > 0: Bullish trend");
- } else if (macd.getDif().compareTo(BigDecimal.ZERO) < 0) {
- System.out.println("DIFF < 0: Bearish trend");
- } else {
- System.out.println("DIFF = 0: No trend");
- }
-
- // Test smoothing function
- testSmoothingFunction();
-
- System.out.println("=== MACD Indicator Test End ===");
- }
-
- /**
- * Generate test price data
- * @param count Number of data points
- * @return Price list
- */
- private static List<BigDecimal> generateTestPrices(int count) {
- List<BigDecimal> prices = new ArrayList<>();
- // Start from 100, simple upward trend with some random fluctuations
- BigDecimal basePrice = new BigDecimal(100);
- for (int i = 0; i < count; i++) {
- // Add random fluctuation between 0.1 and 0.5
- BigDecimal price = basePrice.add(new BigDecimal(Math.random() * 0.4 + 0.1));
- prices.add(price.setScale(2, BigDecimal.ROUND_HALF_UP));
- basePrice = price;
- }
- return prices;
- }
-
- /**
- * Test MACD bars smoothing function
- */
- private static void testSmoothingFunction() {
- System.out.println("=== MACD Bars Smoothing Function Test ===");
-
- MACD macd = new MACD();
- macd.setMacdBarsMultiplier(2);
- macd.setMacdBarsSmoothingPeriod(3); // 3-day smoothing
-
- // Generate test price data with more fluctuations
- List<BigDecimal> prices = generateVolatileTestPrices(30);
-
- macd.calculate(prices);
-
- System.out.println("Price count: " + prices.size());
- System.out.println("MACD parameters: fast=" + macd.getFastPeriod() + ", slow=" + macd.getSlowPeriod() + ", signal=" + macd.getSignalPeriod());
- System.out.println("MACD bars parameters: multiplier=" + macd.getMacdBarsMultiplier() + ", smoothing=" + macd.getMacdBarsSmoothingPeriod());
- System.out.println();
-
- System.out.println("Smoothed MACD Results:");
- System.out.println("DIF: " + macd.getDif());
- System.out.println("DEA: " + macd.getDea());
- System.out.println("MACD Bars: " + macd.getMacdBar());
- System.out.println();
- }
-
- /**
- * Generate test price data with more fluctuations
- * @param count Number of data points
- * @return Price list
- */
- private static List<BigDecimal> generateVolatileTestPrices(int count) {
- List<BigDecimal> prices = new ArrayList<>();
- // Start from 100 with more random fluctuations
- BigDecimal basePrice = new BigDecimal(100);
- for (int i = 0; i < count; i++) {
- // Add random fluctuation between -1.0 and 1.0
- BigDecimal price = basePrice.add(new BigDecimal(Math.random() * 2 - 1));
- prices.add(price.setScale(2, BigDecimal.ROUND_HALF_UP));
- basePrice = price;
- }
- return prices;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java
deleted file mode 100644
index 3e05082..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/RSI.java
+++ /dev/null
@@ -1,145 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.List;
-
-/**
- * RSI (Relative Strength Index) 指标实现
- * 计算逻辑:
- * 1. 计算N天内的上涨幅度和下跌幅度
- * 2. 计算平均上涨幅度和平均下跌幅度
- * 3. RSI = 100 - (100 / (1 + (平均上涨幅度 / 平均下跌幅度)))
- *
- * 作用:
- * 1. 衡量市场的相对强弱程度(0-100)
- * 2. 超买信号:RSI>70表示市场超买,可能回调
- * 3. 超卖信号:RSI<30表示市场超卖,可能反弹
- * 4. 极端超买:RSI>80表示市场极度超买
- * 5. 极端超卖:RSI<20表示市场极度超卖
- *
- * 价格参数类型:
- * - 参数名称:prices
- * - 参数类型:List<BigDecimal>
- * - 参数说明:需要至少15个(默认周期+1)价格数据点用于计算
- *
- * 推荐时间粒度及优缺点:
- * 1. 1分钟(1m):
- * - 优点:反应迅速,适合超短线交易
- * - 缺点:RSI波动剧烈,频繁进入超买超卖区域
- * 2. 5分钟(5m):
- * - 优点:RSI波动相对稳定,适合短线交易
- * - 缺点:仍有一定虚假超买超卖信号
- * 3. 15分钟(15m):
- * - 优点:超买超卖信号较为可靠,适合日内交易
- * - 缺点:反应速度较慢
- * 4. 1小时(1h)及以上:
- * - 优点:超买超卖信号明确,适合中期交易
- * - 缺点:反应滞后,不适合短线交易
- */
-@Slf4j
-@Getter
-@Setter
-public class RSI extends IndicatorBase {
-
- private static final int DEFAULT_PERIOD = 14;
-
- private int period = DEFAULT_PERIOD;
- private BigDecimal rsi = BigDecimal.ZERO;
- private BigDecimal prevAvgGain = BigDecimal.ZERO;
- private BigDecimal prevAvgLoss = BigDecimal.ZERO;
-
- public RSI() {}
-
- public RSI(int period) {
- this.period = period;
- }
-
- /**
- * 计算RSI指标
- * @param prices 价格列表
- */
- public void calculate(List<BigDecimal> prices) {
- if (prices == null || prices.size() < period + 1) {
- return;
- }
-
- if (prevAvgGain.compareTo(BigDecimal.ZERO) == 0 && prevAvgLoss.compareTo(BigDecimal.ZERO) == 0) {
- // 首次计算
- BigDecimal totalGain = BigDecimal.ZERO;
- BigDecimal totalLoss = BigDecimal.ZERO;
-
- for (int i = prices.size() - period; i < prices.size(); i++) {
- BigDecimal change = prices.get(i).subtract(prices.get(i - 1));
- if (change.compareTo(BigDecimal.ZERO) > 0) {
- totalGain = totalGain.add(change);
- } else {
- totalLoss = totalLoss.add(change.abs());
- }
- }
-
- prevAvgGain = totalGain.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
- prevAvgLoss = totalLoss.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
- } else {
- // 后续计算
- BigDecimal change = prices.get(prices.size() - 1).subtract(prices.get(prices.size() - 2));
- BigDecimal gain = change.compareTo(BigDecimal.ZERO) > 0 ? change : BigDecimal.ZERO;
- BigDecimal loss = change.compareTo(BigDecimal.ZERO) < 0 ? change.abs() : BigDecimal.ZERO;
-
- prevAvgGain = prevAvgGain.multiply(new BigDecimal(period - 1))
- .add(gain)
- .divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
- prevAvgLoss = prevAvgLoss.multiply(new BigDecimal(period - 1))
- .add(loss)
- .divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
- }
-
- // 计算RSI
- if (prevAvgLoss.compareTo(BigDecimal.ZERO) == 0) {
- rsi = new BigDecimal(100);
- } else {
- BigDecimal rs = prevAvgGain.divide(prevAvgLoss, 8, RoundingMode.HALF_UP);
- rsi = new BigDecimal(100)
- .subtract(new BigDecimal(100).divide(BigDecimal.ONE.add(rs), 8, RoundingMode.HALF_UP))
- .setScale(8, RoundingMode.HALF_UP);
- }
-
- log.info("RSI计算结果 - RSI({}): {}", period, rsi);
- }
-
- /**
- * 判断超买(RSI > 70)
- * @return 是否超买
- */
- public boolean isOverbought() {
- return rsi.compareTo(new BigDecimal(70)) > 0;
- }
-
- /**
- * 判断超卖(RSI < 30)
- * @return 是否超卖
- */
- public boolean isOversold() {
- return rsi.compareTo(new BigDecimal(30)) < 0;
- }
-
- /**
- * 判断超买(RSI > 80)
- * @return 是否严重超买
- */
- public boolean isExtremelyOverbought() {
- return rsi.compareTo(new BigDecimal(80)) > 0;
- }
-
- /**
- * 判断超卖(RSI < 20)
- * @return 是否严重超卖
- */
- public boolean isExtremelyOversold() {
- return rsi.compareTo(new BigDecimal(20)) < 0;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java
deleted file mode 100644
index 558ad79..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/TradingStrategy.java
+++ /dev/null
@@ -1,1029 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 交易策略实现
- * 展示如何为ETH合约交易(开仓/平仓)组合所有指标
- */
-@Slf4j
-public class TradingStrategy extends IndicatorBase {
-
- @Getter
- @Setter
- @AllArgsConstructor
- @NoArgsConstructor
- public static class StrategyConfig {
- private int maShortPeriod = 5; // 短期移动平均周期
- private int maLongPeriod = 20; // 长期移动平均周期
- private int rsiPeriod = 14; // RSI指标周期
- private int kdjPeriod = 9; // KDJ指标周期
- private int bollPeriod = 20; // 布林带周期
- private double bollK = 2.0; // 布林带标准差倍数
- private int atrPeriod = 14; // ATR计算周期
- private boolean enableDynamicParams = true; // 是否启用动态参数优化
- private boolean enableMultiTimeframeConfirm = true; // 是否启用多周期确认
- private int volumeMaPeriod = 20; // 成交量移动平均周期
- private boolean enableVolumeConfirm = true; // 是否启用成交量验证
-
- // 风险控制参数
- private BigDecimal baseLeverage = new BigDecimal(3); // 基础杠杆倍数
- private int volatilityThresholdPeriod = 30; // 波动率阈值计算周期(用于动态杠杆)
- private boolean enableDynamicLeverage = true; // 是否启用动态杠杆
- private boolean enableThreeStepProfitTaking = true; // 是否启用三段式止盈
- private boolean enableBlackSwanFilter = true; // 是否启用黑天鹅事件过滤
- }
-
- public enum Direction {
- LONG, // 做多方向信号
- SHORT, // 做空方向信号
- RANGING // 震荡市场
- }
-
- public enum SignalType {
- NONE, // 无信号
- BUY, // 开多信号
- SELL, // 开空信号
- CLOSE_BUY, // 平多信号
- CLOSE_SELL // 平空信号
- }
-
- private final StrategyConfig config;
- private final MA ma;
- private final AdvancedMA advancedMA;
- private final BOLL boll;
- private final KDJ kdj;
- private final MACD macd;
- private final RSI rsi;
-
- public TradingStrategy() {
- this(new StrategyConfig());
- }
-
- public TradingStrategy(StrategyConfig config) {
- this.config = config;
- this.ma = new MA();
- this.advancedMA = new AdvancedMA();
- this.boll = new BOLL(config.getBollPeriod(), config.getBollK());
- this.kdj = new KDJ(config.getKdjPeriod());
- this.macd = new MACD();
- this.rsi = new RSI(config.getRsiPeriod());
- }
-
- /**
- * 计算所有指标并生成交易信号
- * @param prices 价格数据
- * @param high 最高价列表
- * @param low 最低价列表
- * @param close 收盘价列表
- * @param volume 成交量列表
- * @param currentPrice 当前价格
- * @param hasLongPosition 是否当前持有做多仓位
- * @param hasShortPosition 是否当前持有做空仓位
- * @param fiveMinPrices 5分钟价格数据(多周期确认)
- * @param oneHourPrices 1小时价格数据(多周期确认)
- * @param fourHourPrices 4小时价格数据(多周期确认)
- * @param fundingRate 当前资金费率(用于黑天鹅过滤)
- * @param hasLargeTransfer 是否有大额转账(用于黑天鹅过滤)
- * @param hasUpcomingEvent 是否有即将到来的重大事件(用于黑天鹅过滤)
- * @return 交易信号
- */
- public SignalType generateSignal(List<BigDecimal> prices, List<BigDecimal> high,
- List<BigDecimal> low, List<BigDecimal> close,
- List<BigDecimal> volume, BigDecimal currentPrice,
- boolean hasLongPosition, boolean hasShortPosition,
- List<BigDecimal> fiveMinPrices,
- List<BigDecimal> oneHourPrices,
- List<BigDecimal> fourHourPrices,
- BigDecimal fundingRate,
- boolean hasLargeTransfer,
- boolean hasUpcomingEvent) {
- // 计算所有指标
- calculateIndicators(prices, high, low, close);
-
- // 检查是否为震荡市场,如果是,则执行区间交易策略
- if (isRangeMarket(prices)) {
- log.info("当前市场为震荡行情,执行区间交易策略");
- return generateRangeTradingSignal(currentPrice, volume, hasLongPosition, hasShortPosition);
- }
-
- // 黑天鹅事件过滤
- if (blackSwanFilter(fundingRate, hasLargeTransfer, hasUpcomingEvent)) {
- log.info("黑天鹅事件过滤触发,不产生信号");
- return SignalType.NONE;
- }
-
- // 开多信号
- if (shouldOpenLong(currentPrice, prices, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && !hasLongPosition && !hasShortPosition) {
- log.info("生成买入信号");
- return SignalType.BUY;
- }
-
- // 开空信号
- if (shouldOpenShort(currentPrice, prices, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && !hasLongPosition && !hasShortPosition) {
- log.info("生成卖出信号");
- return SignalType.SELL;
- }
-
- // 平多信号
- if (shouldCloseLong(currentPrice, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && hasLongPosition) {
- log.info("生成平多信号");
- return SignalType.CLOSE_BUY;
- }
-
- // 平空信号
- if (shouldCloseShort(currentPrice, volume, fiveMinPrices, oneHourPrices, fourHourPrices) && hasShortPosition) {
- log.info("生成平空信号");
- return SignalType.CLOSE_SELL;
- }
-
- log.info("未生成信号");
- return SignalType.NONE;
- }
-
- /**
- * 多周期确认辅助方法(看涨)
- * @param fiveMinPrices 5分钟价格数据
- * @param oneHourPrices 1小时价格数据
- * @param fourHourPrices 4小时价格数据
- * @return 是否有足够的多周期确认
- */
- private boolean multiTimeframeConfirm(List<BigDecimal> fiveMinPrices,
- List<BigDecimal> oneHourPrices,
- List<BigDecimal> fourHourPrices) {
- if (!config.isEnableMultiTimeframeConfirm()) {
- return true; // 如果未启用多周期确认,则默认返回true
- }
-
- int confirmCount = 0;
-
- // 检查5分钟周期
- if (hasBullishTrend(fiveMinPrices)) {
- confirmCount++;
- }
-
- // 检查1小时周期
- if (hasBullishTrend(oneHourPrices)) {
- confirmCount++;
- }
-
- // 检查4小时周期
- if (hasBullishTrend(fourHourPrices)) {
- confirmCount++;
- }
-
- // 至少需要2个周期确认
- return confirmCount >= 2;
- }
-
- /**
- * 多周期确认辅助方法(看跌)
- * @param fiveMinPrices 5分钟价格数据
- * @param oneHourPrices 1小时价格数据
- * @param fourHourPrices 4小时价格数据
- * @return 是否有足够的多周期确认
- */
- private boolean multiTimeframeBearishConfirm(List<BigDecimal> fiveMinPrices,
- List<BigDecimal> oneHourPrices,
- List<BigDecimal> fourHourPrices) {
- if (!config.isEnableMultiTimeframeConfirm()) {
- return true; // 如果未启用多周期确认,则默认返回true
- }
-
- int confirmCount = 0;
-
- // 检查5分钟周期
- if (hasBearishTrend(fiveMinPrices)) {
- confirmCount++;
- }
-
- // 检查1小时周期
- if (hasBearishTrend(oneHourPrices)) {
- confirmCount++;
- }
-
- // 检查4小时周期
- if (hasBearishTrend(fourHourPrices)) {
- confirmCount++;
- }
-
- // 至少需要2个周期确认
- return confirmCount >= 2;
- }
-
- /**
- * 检查指定周期是否有看涨趋势
- * @param prices 价格数据
- * @return 是否有看涨趋势
- */
- private boolean hasBullishTrend(List<BigDecimal> prices) {
- if (prices == null || prices.size() < 20) {
- return false; // 数据不足
- }
-
- // 创建临时MA指标用于判断趋势
- MA tempMA = new MA();
- MACD tempMACD = new MACD();
-
- // 计算指标
- tempMA.calculate(prices);
- tempMACD.calculate(prices);
-
- // 优化后的多头趋势判断:
- // 1. MA多头排列:短期MA > 中期MA > 长期MA
- // 2. 所有均线向上发散
- // 3. MACD趋势:DIFF线在DEA线之上
- boolean isMaBullish = tempMA.getEma5().compareTo(tempMA.getEma10()) > 0 &&
- tempMA.getEma10().compareTo(tempMA.getEma20()) > 0;
-
- // 判断均线向上发散(简单方法:近期均线斜率为正)
- boolean isMaDivergingUp = calculateMaSlope(prices, 5) > 0 &&
- calculateMaSlope(prices, 10) > 0 &&
- calculateMaSlope(prices, 20) > 0;
-
- // MACD趋势判断
- boolean isMacdBullish = tempMACD.getDif().compareTo(tempMACD.getDea()) > 0;
-
- return isMaBullish && isMaDivergingUp && isMacdBullish;
- }
-
- /**
- * 检查指定周期是否有看跌趋势
- * @param prices 价格数据
- * @return 是否有看跌趋势
- */
- private boolean hasBearishTrend(List<BigDecimal> prices) {
- if (prices == null || prices.size() < 20) {
- return false; // 数据不足
- }
-
- // 创建临时MA指标用于判断趋势
- MA tempMA = new MA();
- MACD tempMACD = new MACD();
-
- // 计算指标
- tempMA.calculate(prices);
- tempMACD.calculate(prices);
-
- // 优化后的空头趋势判断:
- // 1. MA空头排列:短期MA < 中期MA < 长期MA
- // 2. 所有均线向下发散
- // 3. MACD趋势:DIFF线在DEA线之下
- boolean isMaBearish = tempMA.getEma5().compareTo(tempMA.getEma10()) < 0 &&
- tempMA.getEma10().compareTo(tempMA.getEma20()) < 0;
-
- // 判断均线向下发散(简单方法:近期均线斜率为负)
- boolean isMaDivergingDown = calculateMaSlope(prices, 5) < 0 &&
- calculateMaSlope(prices, 10) < 0 &&
- calculateMaSlope(prices, 20) < 0;
-
- // MACD趋势判断
- boolean isMacdBearish = tempMACD.getDif().compareTo(tempMACD.getDea()) < 0;
-
- return isMaBearish && isMaDivergingDown && isMacdBearish;
- }
-
- /**
- * 计算均线斜率
- * @param prices 价格数据
- * @param period 均线周期
- * @return 均线斜率(正数表示向上,负数表示向下)
- */
- private double calculateMaSlope(List<BigDecimal> prices, int period) {
- if (prices == null || prices.size() < period * 2) {
- return 0; // 数据不足
- }
-
- // 获取最近两个周期的均线值
- int endIndex = prices.size() - 1;
- int startIndex = endIndex - period + 1;
-
- // 计算当前周期的均线
- BigDecimal currentMa = calculateMA(prices.subList(startIndex, endIndex + 1), period);
-
- // 计算前一个周期的均线
- int prevStartIndex = startIndex - period;
- BigDecimal prevMa = calculateMA(prices.subList(prevStartIndex, startIndex), period);
-
- // 计算斜率(简单差值法)
- return currentMa.subtract(prevMa).doubleValue();
- }
-
- /**
- * 成交量验证辅助方法
- * 增强版:当前成交量需大于20周期均量的1.2倍,以过滤无量反弹/回调的假信号
- * @param volume 成交量列表
- * @return 是否通过成交量验证
- */
- private boolean volumeConfirm(List<BigDecimal> volume) {
- if (!config.isEnableVolumeConfirm() || volume == null || volume.size() < config.getVolumeMaPeriod()) {
- return true; // 如果未启用成交量验证或数据不足,则默认返回true
- }
-
- // 计算成交量移动平均
- BigDecimal volumeMA = calculateMA(volume, config.getVolumeMaPeriod());
- BigDecimal currentVolume = volume.get(volume.size() - 1);
-
- // 增强验证:成交量需要大于1.2倍均线
- return currentVolume.compareTo(volumeMA.multiply(new BigDecimal("1.2"))) > 0;
- }
-
- /**
- * 量价背离检测
- * 检测价格上涨/下跌但成交量萎缩的情况,或价格和成交量趋势不一致
- * @param prices 价格列表
- * @param volume 成交量列表
- * @return 是否存在量价背离
- */
- private boolean hasPriceVolumeDivergence(List<BigDecimal> prices, List<BigDecimal> volume) {
- if (!config.isEnableVolumeConfirm() || prices == null || volume == null || prices.size() < 3 || volume.size() < 3) {
- return false; // 如果未启用成交量验证或数据不足,则默认返回false
- }
-
- // 获取最近3个周期的价格和成交量
- BigDecimal currentPrice = prices.get(prices.size() - 1);
- BigDecimal prevPrice1 = prices.get(prices.size() - 2);
- BigDecimal prevPrice2 = prices.get(prices.size() - 3);
-
- BigDecimal currentVolume = volume.get(volume.size() - 1);
- BigDecimal prevVolume1 = volume.get(volume.size() - 2);
- BigDecimal prevVolume2 = volume.get(volume.size() - 3);
-
- // 计算价格趋势
- boolean priceTrendUp = currentPrice.compareTo(prevPrice1) > 0 && prevPrice1.compareTo(prevPrice2) > 0;
- boolean priceTrendDown = currentPrice.compareTo(prevPrice1) < 0 && prevPrice1.compareTo(prevPrice2) < 0;
-
- // 计算成交量趋势
- boolean volumeTrendUp = currentVolume.compareTo(prevVolume1) > 0 && prevVolume1.compareTo(prevVolume2) > 0;
- boolean volumeTrendDown = currentVolume.compareTo(prevVolume1) < 0 && prevVolume1.compareTo(prevVolume2) < 0;
-
- // 检测量价背离
- // 价格上涨但成交量萎缩
- boolean bullishDivergence = priceTrendUp && volumeTrendDown;
- // 价格下跌但成交量萎缩(通常是强势信号,不视为背离)
- // 价格下跌但成交量放大(可能是恐慌性抛售,视为背离)
- boolean bearishDivergence = priceTrendDown && volumeTrendUp;
-
- return bullishDivergence || bearishDivergence;
- }
-
- /**
- * 计算所有指标
- * @param prices 价格数据
- * @param high 最高价列表
- * @param low 最低价列表
- * @param close 收盘价列表
- */
- private void calculateIndicators(List<BigDecimal> prices, List<BigDecimal> high,
- List<BigDecimal> low, List<BigDecimal> close) {
- // 计算ATR和波动率
- BigDecimal atr = calculateATR(high, low, close, config.getAtrPeriod());
- BigDecimal volatility = normalizeVolatility(close, atr);
-
- // 使用动态参数计算指标
- if (config.isEnableDynamicParams()) {
- log.info("使用动态参数计算指标,波动率: {}", volatility);
- ma.calculate(prices, volatility);
- macd.calculate(prices, volatility);
- } else {
- ma.calculate(prices);
- macd.calculate(prices);
- }
-
- // 其他指标计算
- advancedMA.calculateTripleEMA(prices);
- boll.calculate(prices);
- kdj.calculate(prices);
- rsi.calculate(prices);
- }
-
- /**
- * 检查是否为震荡市场
- * @param prices 价格数据列表
- * @return 是否为震荡市场
- */
- private boolean isRangeMarket(List<BigDecimal> prices) {
- // 优化的震荡市场判断条件,放宽标准以减少RANGING信号频率
-
- // 1. 高级MA三线收敛(小于3%,放宽条件)
- boolean isMaConverged = advancedMA.calculatePercent().compareTo(new BigDecimal(3)) < 0;
-
- // 2. RSI中性区间(35-65,放宽条件)
- boolean isRsiNeutral = rsi.getRsi().compareTo(new BigDecimal(35)) > 0 &&
- rsi.getRsi().compareTo(new BigDecimal(65)) < 0;
-
- // 3. BOLL带宽收窄(小于0.06,放宽条件)
- boolean isBollNarrow = boll.calculateBandWidth().compareTo(new BigDecimal(0.06)) < 0;
-
- // 4. MACD柱状图趋近于0(多空力量平衡,放宽条件)
- boolean isMacdBalanced = macd.getMacdBar().abs().compareTo(new BigDecimal(0.02)) < 0;
-
- // 5. KDJ在中间区域波动(25-75,放宽条件)
- boolean isKdjNeutral = kdj.getK().compareTo(new BigDecimal(25)) > 0 &&
- kdj.getK().compareTo(new BigDecimal(75)) < 0 &&
- kdj.getD().compareTo(new BigDecimal(25)) > 0 &&
- kdj.getD().compareTo(new BigDecimal(75)) < 0 &&
- kdj.getJ().compareTo(new BigDecimal(20)) > 0 &&
- kdj.getJ().compareTo(new BigDecimal(80)) < 0;
-
- // 6. 价格波动范围较小(最近20根K线最高价与最低价的比值小于1.06,放宽条件)
- boolean isPriceVolatilityLow = calculatePriceVolatility(prices).compareTo(new BigDecimal(1.06)) < 0;
-
- // 综合判断:只需要满足部分条件即可,增加趋势信号的机会
- int rangeConditionsMet = 0;
- if (isMaConverged) {
- rangeConditionsMet++;
- }
- if (isRsiNeutral) {
- rangeConditionsMet++;
- }
- if (isBollNarrow) {
- rangeConditionsMet++;
- }
- if (isMacdBalanced) {
- rangeConditionsMet++;
- }
- if (isKdjNeutral) {
- rangeConditionsMet++;
- }
- if (isPriceVolatilityLow) {
- rangeConditionsMet++;
- }
-
- // 只有满足4个或以上条件才判定为震荡市场
- return rangeConditionsMet >= 4;
- }
-
- /**
- * 计算价格波动率(最近20根K线最高价与最低价的比值)
- * @param prices 价格数据列表
- * @return 价格波动率
- */
- private BigDecimal calculatePriceVolatility(List<BigDecimal> prices) {
- if (prices == null || prices.isEmpty()) {
- return BigDecimal.ONE;
- }
-
- List<BigDecimal> recentPrices = prices.subList(Math.max(0, prices.size() - 20), prices.size());
- if (recentPrices.isEmpty()) {
- return BigDecimal.ONE;
- }
-
- BigDecimal highest = recentPrices.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
- BigDecimal lowest = recentPrices.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ONE);
-
- return highest.divide(lowest, 10, RoundingMode.HALF_UP);
- }
-
-
-
- /**
- * 根据15分钟时间框架指标确定市场方向(做多/做空/震荡)
- * @param prices 价格数据列表
- * @param high 最高价列表
- * @param low 最低价列表
- * @param close 收盘价列表
- * @param currentPrice 当前价格
- * @return 市场方向
- */
- public Direction getDirection(List<BigDecimal> prices, List<BigDecimal> high,
- List<BigDecimal> low, List<BigDecimal> close,
- BigDecimal currentPrice) {
- // 计算所有指标
- calculateIndicators(prices, high, low, close);
-
- // 检查是否为震荡市场
- if (isRangeMarket(prices)) {
- return Direction.RANGING;
- }
-
- // 优化后的多头趋势条件:
- // 1. MA多头排列(短期MA > 中期MA > 长期MA)
- // 2. MACD趋势(DIFF在DEA之上,代表多头趋势)
- // 3. 价格在BOLL中轨上方
- // 4. RSI(50-65) 为健康多头区间
- boolean isMaBullish = ma.getEma5().compareTo(ma.getEma10()) > 0 &&
- ma.getEma10().compareTo(ma.getEma20()) > 0;
- boolean isMacdBullish = macd.getDif().compareTo(macd.getDea()) > 0;
- boolean isPriceAboveBollMid = currentPrice.compareTo(boll.getMid()) > 0;
- boolean isRsiBullish = rsi.getRsi().compareTo(new BigDecimal(50)) > 0 &&
- rsi.getRsi().compareTo(new BigDecimal(65)) < 0;
-
- // 检查多头信号(MA多头 + MACD金叉 + 价格在BOLL中轨上方 + RSI(50-65))
- if (isMaBullish && isMacdBullish && isPriceAboveBollMid && isRsiBullish) {
- return Direction.LONG;
- }
-
- // 优化后的空头趋势条件:
- // 1. MA空头排列(短期MA < 中期MA < 长期MA)
- // 2. MACD趋势(DIFF在DEA之下,代表空头趋势)
- // 3. 价格在BOLL中轨下方
- // 4. RSI(35-50) 为健康空头区间
- boolean isMaBearish = ma.getEma5().compareTo(ma.getEma10()) < 0 &&
- ma.getEma10().compareTo(ma.getEma20()) < 0;
- boolean isMacdBearish = macd.getDif().compareTo(macd.getDea()) < 0;
- boolean isPriceBelowBollMid = currentPrice.compareTo(boll.getMid()) < 0;
- boolean isRsiBearish = rsi.getRsi().compareTo(new BigDecimal(35)) > 0 &&
- rsi.getRsi().compareTo(new BigDecimal(50)) < 0;
-
- // 检查空头信号(MA空头 + MACD死叉 + 价格在BOLL中轨下方 + RSI(35-50))
- if (isMaBearish && isMacdBearish && isPriceBelowBollMid && isRsiBearish) {
- return Direction.SHORT;
- }
-
- // 如果没有明确方向,默认为震荡
- return Direction.RANGING;
- }
-
- /**
- * 根据优化建议检查是否应该开多仓位
- * 使用主次结合法:
- * - 主信号(趋势):MA多头排列 + MACD金叉
- * - 辅信号(入场点):价格回调至BOLL中轨附近获得支撑,且出现KDJ金叉或RSI从低位回升至50以上
- * @param currentPrice 当前价格
- * @param prices 价格数据
- * @param volume 成交量列表
- * @param fiveMinPrices 5分钟价格数据
- * @param oneHourPrices 1小时价格数据
- * @param fourHourPrices 4小时价格数据
- * @return 是否应该开多
- */
- private boolean shouldOpenLong(BigDecimal currentPrice, List<BigDecimal> prices, List<BigDecimal> volume,
- List<BigDecimal> fiveMinPrices,
- List<BigDecimal> oneHourPrices,
- List<BigDecimal> fourHourPrices) {
- // 主信号:趋势判断
- // MA多头排列(短期MA > 中期MA > 长期MA)
- boolean isMaBullish = ma.getEma5().compareTo(ma.getEma10()) > 0 &&
- ma.getEma10().compareTo(ma.getEma20()) > 0;
- // MACD趋势(DIFF在DEA之上)
- boolean isMacdBullish = macd.getDif().compareTo(macd.getDea()) > 0;
-
- // 如果主信号不满足,直接返回false
- if (!(isMaBullish && isMacdBullish)) {
- return false;
- }
-
- // 辅信号:入场点判断
- // 价格在BOLL中轨上方,且未触及上轨(避免追高)
- boolean isPriceInSafeZone = currentPrice.compareTo(boll.getMid()) > 0 &&
- currentPrice.compareTo(boll.getUpper()) < 0;
- // 价格靠近BOLL中轨
- boolean isPriceNearBollMid = currentPrice.compareTo(boll.getMid().multiply(new BigDecimal(0.98))) > 0 &&
- currentPrice.compareTo(boll.getMid().multiply(new BigDecimal(1.02))) < 0;
-
- // RSI:健康区间30-70,刚从50中线向上
- boolean isRsiHealthy = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 &&
- rsi.getRsi().compareTo(new BigDecimal(70)) < 0;
- boolean isRsiAboveMid = rsi.getRsi().compareTo(new BigDecimal(50)) > 0;
-
- // KDJ:金叉或超卖区域
- boolean isKdjGoldenCross = kdj.isGoldenCross();
- boolean isKdjOversold = kdj.getJ().compareTo(new BigDecimal(15)) < 0;
-
- // 入场点条件:价格在安全区域或靠近中轨,且动量健康
- boolean isEntryPointValid = (isPriceInSafeZone || isPriceNearBollMid) &&
- (isRsiHealthy && isRsiAboveMid) &&
- (isKdjGoldenCross || isKdjOversold);
-
- // 成交量验证
- boolean isVolumeConfirmed = volumeConfirm(volume);
- // 量价背离检测
- boolean isPriceVolumeDivergence = hasPriceVolumeDivergence(prices, volume);
- // 多周期确认
- boolean isMultiTimeframeConfirmed = multiTimeframeConfirm(fiveMinPrices, oneHourPrices, fourHourPrices);
-
- return isEntryPointValid && isVolumeConfirmed && !isPriceVolumeDivergence && isMultiTimeframeConfirmed;
- }
-
- /**
- * 根据优化建议检查是否应该开空仓位
- * 使用主次结合法:
- * - 主信号(趋势):MA空头排列 + MACD死叉
- * - 辅信号(入场点):价格反弹至BOLL中轨附近遇阻,且出现KDJ死叉或RSI从高位回落至50以下
- * @param currentPrice 当前价格
- * @param prices 价格数据
- * @param volume 成交量列表
- * @param fiveMinPrices 5分钟价格数据
- * @param oneHourPrices 1小时价格数据
- * @param fourHourPrices 4小时价格数据
- * @return 是否应该开空
- */
- private boolean shouldOpenShort(BigDecimal currentPrice, List<BigDecimal> prices, List<BigDecimal> volume,
- List<BigDecimal> fiveMinPrices,
- List<BigDecimal> oneHourPrices,
- List<BigDecimal> fourHourPrices) {
- // 主信号:趋势判断
- // MA空头排列(短期MA < 中期MA < 长期MA)
- boolean isMaBearish = ma.getEma5().compareTo(ma.getEma10()) < 0 &&
- ma.getEma10().compareTo(ma.getEma20()) < 0;
- // MACD趋势(DIFF在DEA之下)
- boolean isMacdBearish = macd.getDif().compareTo(macd.getDea()) < 0;
-
- // 如果主信号不满足,直接返回false
- if (!(isMaBearish && isMacdBearish)) {
- return false;
- }
-
- // 辅信号:入场点判断
- // 价格在BOLL中轨下方,且未触及下轨(避免追空)
- boolean isPriceInSafeZone = currentPrice.compareTo(boll.getMid()) < 0 &&
- currentPrice.compareTo(boll.getLower()) > 0;
- // 价格靠近BOLL中轨
- boolean isPriceNearBollMid = currentPrice.compareTo(boll.getMid().multiply(new BigDecimal(0.98))) > 0 &&
- currentPrice.compareTo(boll.getMid().multiply(new BigDecimal(1.02))) < 0;
-
- // RSI:健康区间30-70,刚从50中线向下
- boolean isRsiHealthy = rsi.getRsi().compareTo(new BigDecimal(30)) > 0 &&
- rsi.getRsi().compareTo(new BigDecimal(70)) < 0;
- boolean isRsiBelowMid = rsi.getRsi().compareTo(new BigDecimal(50)) < 0;
-
- // KDJ:死叉或超买区域
- boolean isKdjDeathCross = kdj.isDeathCross();
- boolean isKdjOverbought = kdj.getJ().compareTo(new BigDecimal(85)) > 0;
-
- // 入场点条件:价格在安全区域或靠近中轨,且动量健康
- boolean isEntryPointValid = (isPriceInSafeZone || isPriceNearBollMid) &&
- (isRsiHealthy && isRsiBelowMid) &&
- (isKdjDeathCross || isKdjOverbought);
-
- // 成交量验证
- boolean isVolumeConfirmed = volumeConfirm(volume);
- // 量价背离检测
- boolean isPriceVolumeDivergence = hasPriceVolumeDivergence(prices, volume);
- // 多周期确认(看跌)
- boolean isMultiTimeframeConfirmed = multiTimeframeBearishConfirm(fiveMinPrices, oneHourPrices, fourHourPrices);
-
- return isEntryPointValid && isVolumeConfirmed && !isPriceVolumeDivergence && isMultiTimeframeConfirmed;
- }
-
- /**
- * 根据优化建议检查是否应该平多仓位
- * 采用分层止盈止损策略:
- * - 保护止损:价格跌破BOLL中轨
- * - 跟踪止损:价格有效跌破移动平均线
- * - 最终平仓:趋势反转信号(MA空头排列 + MACD死叉)
- * @param currentPrice 当前价格
- * @param volume 成交量列表
- * @param fiveMinPrices 5分钟价格数据
- * @param oneHourPrices 1小时价格数据
- * @param fourHourPrices 4小时价格数据
- * @return 是否应该平多
- */
- private boolean shouldCloseLong(BigDecimal currentPrice, List<BigDecimal> volume,
- List<BigDecimal> fiveMinPrices,
- List<BigDecimal> oneHourPrices,
- List<BigDecimal> fourHourPrices) {
- // 保护止损:价格跌破BOLL中轨(关键支撑位)
- boolean isStopLossTriggered = currentPrice.compareTo(boll.getMid()) < 0;
-
- // 跟踪止损:价格有效跌破短期均线(5EMA)
- boolean isTrailingStopTriggered = currentPrice.compareTo(ma.getEma5()) < 0;
-
- // 趋势反转信号:MA空头排列 + MACD死叉
- boolean isMaBearish = ma.getEma5().compareTo(ma.getEma10()) < 0 &&
- ma.getEma10().compareTo(ma.getEma20()) < 0;
- boolean isMacdBearish = macd.getDif().compareTo(macd.getDea()) < 0;
- boolean isTrendReversed = isMaBearish && isMacdBearish;
-
- // 多周期确认(看跌)
- boolean isMultiTimeframeConfirmed = multiTimeframeBearishConfirm(fiveMinPrices, oneHourPrices, fourHourPrices);
-
- // 平多条件:保护止损触发 或 跟踪止损触发 或 (趋势反转且多周期确认)
- return isStopLossTriggered || isTrailingStopTriggered || (isTrendReversed && isMultiTimeframeConfirmed);
- }
-
- /**
- * 根据优化建议检查是否应该平空仓位
- * 采用分层止盈止损策略:
- * - 保护止损:价格突破BOLL中轨
- * - 跟踪止损:价格有效突破移动平均线
- * - 最终平仓:趋势反转信号(MA多头排列 + MACD金叉)
- * @param currentPrice 当前价格
- * @param volume 成交量列表
- * @param fiveMinPrices 5分钟价格数据
- * @param oneHourPrices 1小时价格数据
- * @param fourHourPrices 4小时价格数据
- * @return 是否应该平空
- */
- private boolean shouldCloseShort(BigDecimal currentPrice, List<BigDecimal> volume,
- List<BigDecimal> fiveMinPrices,
- List<BigDecimal> oneHourPrices,
- List<BigDecimal> fourHourPrices) {
- // 保护止损:价格突破BOLL中轨(关键阻力位)
- boolean isStopLossTriggered = currentPrice.compareTo(boll.getMid()) > 0;
-
- // 跟踪止损:价格有效突破短期均线(5EMA)
- boolean isTrailingStopTriggered = currentPrice.compareTo(ma.getEma5()) > 0;
-
- // 趋势反转信号:MA多头排列 + MACD金叉
- boolean isMaBullish = ma.getEma5().compareTo(ma.getEma10()) > 0 &&
- ma.getEma10().compareTo(ma.getEma20()) > 0;
- boolean isMacdBullish = macd.getDif().compareTo(macd.getDea()) > 0;
- boolean isTrendReversed = isMaBullish && isMacdBullish;
-
- // 多周期确认(看涨)
- boolean isMultiTimeframeConfirmed = multiTimeframeConfirm(fiveMinPrices, oneHourPrices, fourHourPrices);
-
- // 平空条件:保护止损触发 或 跟踪止损触发 或 (趋势反转且多周期确认)
- return isStopLossTriggered || isTrailingStopTriggered || (isTrendReversed && isMultiTimeframeConfirmed);
- }
-
- /**
- * 获取所有指标的当前状态
- * @return 指标状态字符串
- */
- public String getIndicatorStatus() {
- return String.format("MA5: %s, MA20: %s, ", ma.getEma5(), ma.getEma20()) +
- String.format("MACD-DIF: %s, MACD-DEA: %s, MACD-BAR: %s, ", macd.getDif(), macd.getDea(), macd.getMacdBar()) +
- String.format("KDJ-K: %s, KDJ-D: %s, KDJ-J: %s, ", kdj.getK(), kdj.getD(), kdj.getJ()) +
- String.format("RSI: %s, ", rsi.getRsi()) +
- String.format("BOLL-MID: %s, BOLL-UP: %s, BOLL-DN: %s, ", boll.getMid(), boll.getUpper(), boll.getLower()) +
- String.format("AdvancedMA-Bullish: %s, Bearish: %s, Percent: %s",
- advancedMA.isBullish(), advancedMA.isBearish(), advancedMA.calculatePercent());
- }
-
- /**
- * 生成区间交易信号
- * 在震荡行情下,使用BOLL通道作为区间边界,结合KDJ指标生成交易信号
- * @param currentPrice 当前价格
- * @param volume 成交量列表
- * @param hasLongPosition 是否当前持有做多仓位
- * @param hasShortPosition 是否当前持有做空仓位
- * @return 交易信号
- */
- private SignalType generateRangeTradingSignal(BigDecimal currentPrice, List<BigDecimal> volume,
- boolean hasLongPosition, boolean hasShortPosition) {
- // 区间交易策略逻辑:
- // 1. 价格触及BOLL下轨且KDJ超卖 → 买入信号
- // 2. 价格触及BOLL上轨且KDJ超买 → 卖出信号
- // 3. 价格回归BOLL中轨 → 平仓信号
-
- // 检查KDJ极端超买超卖情况
- boolean isKdjJExtremeOverbought = kdj.getJ().compareTo(new BigDecimal("100")) > 0;
- boolean isKdjJExtremeOversold = kdj.getJ().compareTo(new BigDecimal("10")) < 0;
-
- // 价格触及BOLL下轨(基础条件)
- boolean isPriceNearBollLower = currentPrice.compareTo(boll.getLower()) >= 0 &&
- currentPrice.compareTo(boll.getLower().multiply(new BigDecimal("1.01"))) <= 0;
-
- // 价格触及BOLL上轨(基础条件)
- boolean isPriceNearBollUpper = currentPrice.compareTo(boll.getUpper()) <= 0 &&
- currentPrice.compareTo(boll.getUpper().multiply(new BigDecimal("0.99"))) >= 0;
-
- // 当KDJ-J极度超买/超卖时,放宽BOLL边界要求
- if (isKdjJExtremeOverbought) {
- isPriceNearBollUpper = currentPrice.compareTo(boll.getUpper().multiply(new BigDecimal("0.985"))) >= 0;
- }
- if (isKdjJExtremeOversold) {
- isPriceNearBollLower = currentPrice.compareTo(boll.getLower().multiply(new BigDecimal("1.015"))) <= 0;
- }
-
- // 价格回归BOLL中轨附近
- boolean isPriceNearBollMid = currentPrice.compareTo(boll.getMid().multiply(new BigDecimal("0.998"))) >= 0 &&
- currentPrice.compareTo(boll.getMid().multiply(new BigDecimal("1.002"))) <= 0;
-
- // KDJ超卖(使用调整后的阈值)
- boolean isKdjOversold = kdj.getJ().compareTo(new BigDecimal(15)) < 0;
-
- // KDJ超买(使用调整后的阈值)
- boolean isKdjOverbought = kdj.getJ().compareTo(new BigDecimal(85)) > 0;
-
- // RSI超卖(<30)
- boolean isRsiOversold = rsi.getRsi().compareTo(new BigDecimal(30)) < 0;
-
- // RSI超买(>70)
- boolean isRsiOverbought = rsi.getRsi().compareTo(new BigDecimal(70)) > 0;
-
- // 成交量验证(当前成交量大于20周期均值的1.1倍)
- boolean isVolumeValid = volumeConfirm(volume) &&
- volume.get(volume.size() - 1).compareTo(calculateMA(volume, config.getVolumeMaPeriod()).multiply(new BigDecimal("1.1"))) > 0;
-
- // 开多逻辑:价格触及BOLL下轨且KDJ超卖且RSI超卖且有成交量支持
- if (isPriceNearBollLower && isKdjOversold && isRsiOversold && isVolumeValid && !hasLongPosition && !hasShortPosition) {
- log.info("区间交易:价格触及BOLL下轨({}), KDJ-J值({})超卖,RSI({})超卖,生成买入信号", boll.getLower(), kdj.getJ(), rsi.getRsi());
- return SignalType.BUY;
- }
-
- // 开空逻辑:价格触及BOLL上轨且KDJ超买且RSI超买且有成交量支持
- if (isPriceNearBollUpper && isKdjOverbought && isRsiOverbought && isVolumeValid && !hasLongPosition && !hasShortPosition) {
- log.info("区间交易:价格触及BOLL上轨({}), KDJ-J值({})超买,RSI({})超买,生成卖出信号", boll.getUpper(), kdj.getJ(), rsi.getRsi());
- return SignalType.SELL;
- }
-
- // 平多逻辑:价格回归BOLL中轨
- if (isPriceNearBollMid && hasLongPosition) {
- log.info("区间交易:价格回归BOLL中轨({}),生成平多信号", boll.getMid());
- return SignalType.CLOSE_BUY;
- }
-
- // 平空逻辑:价格回归BOLL中轨
- if (isPriceNearBollMid && hasShortPosition) {
- log.info("区间交易:价格回归BOLL中轨({}),生成平空信号", boll.getMid());
- return SignalType.CLOSE_SELL;
- }
-
- return SignalType.NONE;
- }
-
- /**
- * 计算动态杠杆倍数
- * 杠杆倍数 = 基础杠杆 * (波动率阈值/当前波动率)
- * @param high 最高价列表
- * @param low 最低价列表
- * @param close 收盘价列表
- * @return 动态杠杆倍数
- */
- public BigDecimal calculateDynamicLeverage(List<BigDecimal> high, List<BigDecimal> low, List<BigDecimal> close) {
- if (!config.isEnableDynamicLeverage()) {
- return config.getBaseLeverage();
- }
-
- // 计算当前ATR和波动率
- BigDecimal currentAtr = calculateATR(high, low, close, config.getAtrPeriod());
- BigDecimal currentVolatility = normalizeVolatility(close, currentAtr);
-
- // 计算30日ATR移动中位数作为波动率阈值
- BigDecimal volatilityThreshold = calculateVolatilityThreshold(high, low, close);
-
- // 动态计算杠杆倍数
- BigDecimal leverage = config.getBaseLeverage().multiply(volatilityThreshold).divide(currentVolatility, 2, BigDecimal.ROUND_HALF_UP);
-
- // 限制杠杆范围在1x-10x之间
- leverage = leverage.min(new BigDecimal(10)).max(BigDecimal.ONE);
-
- log.info("动态杠杆计算 - 基础杠杆: {}, 波动率阈值: {}, 当前波动率: {}, 计算杠杆: {}",
- config.getBaseLeverage(), volatilityThreshold, currentVolatility, leverage);
-
- return leverage;
- }
-
- /**
- * 计算波动率阈值(30日ATR移动中位数)
- * @param high 最高价列表
- * @param low 最低价列表
- * @param close 收盘价列表
- * @return 波动率阈值
- */
- private BigDecimal calculateVolatilityThreshold(List<BigDecimal> high, List<BigDecimal> low, List<BigDecimal> close) {
- if (high == null || low == null || close == null || close.size() < config.getVolatilityThresholdPeriod()) {
- return new BigDecimal(5); // 默认阈值
- }
-
- List<BigDecimal> volatilityList = new ArrayList<>();
- for (int i = close.size() - config.getVolatilityThresholdPeriod(); i < close.size(); i++) {
- List<BigDecimal> recentHigh = high.subList(Math.max(0, i - config.getAtrPeriod()), i + 1);
- List<BigDecimal> recentLow = low.subList(Math.max(0, i - config.getAtrPeriod()), i + 1);
- List<BigDecimal> recentClose = close.subList(Math.max(0, i - config.getAtrPeriod()), i + 1);
-
- BigDecimal atr = calculateATR(recentHigh, recentLow, recentClose, config.getAtrPeriod());
- BigDecimal volatility = normalizeVolatility(recentClose, atr);
- volatilityList.add(volatility);
- }
-
- // 计算中位数
- volatilityList.sort(BigDecimal::compareTo);
- int midIndex = volatilityList.size() / 2;
- return volatilityList.get(midIndex);
- }
-
- /**
- * 三段式止盈策略
- * 第一目标:BOLL上轨(30%仓位)
- * 第二目标:斐波那契161.8%(50%仓位)
- * 第三目标:趋势线破位(20%仓位)
- * @param entryPrice 入场价格
- * @param currentPrice 当前价格
- * @param direction 交易方向
- * @param positionSize 当前仓位大小
- * @return 止盈信号和应该平仓的仓位比例
- */
- public ProfitTakingResult calculateThreeStepProfitTaking(BigDecimal entryPrice, BigDecimal currentPrice,
- Direction direction, BigDecimal positionSize) {
- if (!config.isEnableThreeStepProfitTaking()) {
- return new ProfitTakingResult(SignalType.NONE, BigDecimal.ZERO);
- }
-
- // 计算三个止盈目标
- BigDecimal firstTarget = calculateFirstProfitTarget(entryPrice, currentPrice, direction);
- BigDecimal secondTarget = calculateSecondProfitTarget(entryPrice, direction);
- BigDecimal thirdTarget = calculateThirdProfitTarget(currentPrice, direction);
-
- // 判断当前价格是否达到目标
- if (direction == Direction.LONG) {
- if (currentPrice.compareTo(thirdTarget) >= 0) {
- // 达到第三目标,平全部仓位
- return new ProfitTakingResult(SignalType.CLOSE_BUY, positionSize);
- } else if (currentPrice.compareTo(secondTarget) >= 0) {
- // 达到第二目标,平50%仓位
- return new ProfitTakingResult(SignalType.CLOSE_BUY, positionSize.multiply(new BigDecimal("0.5")));
- } else if (currentPrice.compareTo(firstTarget) >= 0) {
- // 达到第一目标,平30%仓位
- return new ProfitTakingResult(SignalType.CLOSE_BUY, positionSize.multiply(new BigDecimal("0.3")));
- }
- } else if (direction == Direction.SHORT) {
- if (currentPrice.compareTo(thirdTarget) <= 0) {
- // 达到第三目标,平全部仓位
- return new ProfitTakingResult(SignalType.CLOSE_SELL, positionSize);
- } else if (currentPrice.compareTo(secondTarget) <= 0) {
- // 达到第二目标,平50%仓位
- return new ProfitTakingResult(SignalType.CLOSE_SELL, positionSize.multiply(new BigDecimal("0.5")));
- } else if (currentPrice.compareTo(firstTarget) <= 0) {
- // 达到第一目标,平30%仓位
- return new ProfitTakingResult(SignalType.CLOSE_SELL, positionSize.multiply(new BigDecimal("0.3")));
- }
- }
-
- return new ProfitTakingResult(SignalType.NONE, BigDecimal.ZERO);
- }
-
- /**
- * 计算第一止盈目标:BOLL上轨
- */
- private BigDecimal calculateFirstProfitTarget(BigDecimal entryPrice, BigDecimal currentPrice, Direction direction) {
- return direction == Direction.LONG ? boll.getUpper() : boll.getLower();
- }
-
- /**
- * 计算第二止盈目标:斐波那契161.8%
- */
- private BigDecimal calculateSecondProfitTarget(BigDecimal entryPrice, Direction direction) {
- BigDecimal fibonacciRatio = new BigDecimal("1.618");
- if (direction == Direction.LONG) {
- return entryPrice.multiply(BigDecimal.ONE.add(fibonacciRatio.divide(new BigDecimal(100))));
- } else {
- return entryPrice.multiply(BigDecimal.ONE.subtract(fibonacciRatio.divide(new BigDecimal(100))));
- }
- }
-
- /**
- * 计算第三止盈目标:简单趋势线破位(这里简化为MA5下穿MA20)
- */
- private BigDecimal calculateThirdProfitTarget(BigDecimal currentPrice, Direction direction) {
- // 这里使用简化的趋势线破位判断
- // 实际应用中可以使用更复杂的趋势线计算
- return direction == Direction.LONG ? ma.getEma20() : ma.getEma20();
- }
-
-
-
- /**
- * 黑天鹅事件过滤
- * 规避重大事件前后30分钟、链上大额转账、异常资金费率
- * @param fundingRate 当前资金费率
- * @param hasLargeTransfer 是否有大额转账
- * @param hasUpcomingEvent 是否有即将到来的重大事件
- * @return 是否应该规避交易
- */
- public boolean blackSwanFilter(BigDecimal fundingRate, boolean hasLargeTransfer, boolean hasUpcomingEvent) {
- if (!config.isEnableBlackSwanFilter()) {
- return false;
- }
-
- // 资金费率绝对值大于0.2%
- boolean isAbnormalFundingRate = fundingRate != null &&
- fundingRate.abs().compareTo(new BigDecimal("0.002")) > 0;
-
- // 大额转账监控(这里简化为外部传入)
- // 重大事件监控(这里简化为外部传入)
-
- boolean shouldAvoid = isAbnormalFundingRate || hasLargeTransfer || hasUpcomingEvent;
-
- if (shouldAvoid) {
- log.info("黑天鹅事件过滤触发 - 资金费率异常: {}, 大额转账: {}, 即将发生重大事件: {}",
- isAbnormalFundingRate, hasLargeTransfer, hasUpcomingEvent);
- }
-
- return shouldAvoid;
- }
-
- /**
- * 止盈结果类
- */
- public static class ProfitTakingResult {
- private SignalType signal;
- private BigDecimal closePositionSize;
-
- public ProfitTakingResult(SignalType signal, BigDecimal closePositionSize) {
- this.signal = signal;
- this.closePositionSize = closePositionSize;
- }
-
- public SignalType getSignal() {
- return signal;
- }
-
- public BigDecimal getClosePositionSize() {
- return closePositionSize;
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/EMACalculator.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/EMACalculator.java
deleted file mode 100644
index 019b1a1..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/EMACalculator.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * 指数移动平均线(EMA)计算器
- * <p>
- * EMA(Exponential Moving Average)是一种加权移动平均线,对近期价格赋予更高权重,
- * 对远期价格赋予较低权重,能够更敏感地反映价格变化趋势。
- * 本计算器提供了EMA的多种计算方式,支持使用SMA作为初始值或使用第一个价格作为初始值。
- */
-package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 指数移动平均线(EMA)计算器
- *
- * <p>计算公式:EMA(today) = Price(today) * k + EMA(yesterday) * (1 - k)</p>
- * <p>其中:k = 2 / (period + 1),period为EMA的周期</p>
- */
-public class EMACalculator {
- /**
- * 计算价格序列的指数移动平均线(EMA)
- *
- * @param prices 价格序列,使用BigDecimal确保计算精度
- * @param period EMA计算周期
- * @param initialSMA 是否使用SMA(简单移动平均线)作为初始值
- * @return 计算得到的EMA序列,与输入价格序列一一对应
- * @throws IllegalArgumentException 当输入参数无效时抛出异常
- */
- public static List<BigDecimal> calculateEMA(List<BigDecimal> prices, int period, boolean initialSMA) {
- if (prices == null || prices.isEmpty() || period <= 0) {
- throw new IllegalArgumentException("Invalid input parameters.");
- }
- if (initialSMA && prices.size() < period) {
- throw new IllegalArgumentException("Prices list too short for initial SMA.");
- }
-
- // 计算权重因子k = 2 / (period + 1)
- BigDecimal alpha = BigDecimal.valueOf(2.0).divide(BigDecimal.valueOf(period + 1), 10, RoundingMode.HALF_UP);
- List<BigDecimal> ema = new ArrayList<>();
-
- if (initialSMA) {
- // 使用SMA作为初始EMA值(前period个价格的平均值)
- BigDecimal sum = BigDecimal.ZERO;
- for (int i = 0; i < period; i++) {
- sum = sum.add(prices.get(i));
- }
- BigDecimal sma = sum.divide(BigDecimal.valueOf(period), 10, RoundingMode.HALF_UP);
- ema.add(sma);
-
- // 从第period+1个数据点开始计算后续EMA值
- for (int i = period; i < prices.size(); i++) {
- BigDecimal price = prices.get(i);
- BigDecimal prevEMA = ema.get(ema.size() - 1);
- // EMA计算公式:Price(today) * alpha + EMA(yesterday) * (1 - alpha)
- BigDecimal emaToday = price.multiply(alpha)
- .add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha)))
- .setScale(10, RoundingMode.HALF_UP);
- ema.add(emaToday);
- }
- } else {
- // 使用第一个价格作为初始EMA值,并从第二个数据点开始计算
- ema.add(prices.get(0));
- for (int i = 1; i < prices.size(); i++) {
- BigDecimal price = prices.get(i);
- BigDecimal prevEMA = ema.get(ema.size() - 1);
- // EMA计算公式:Price(today) * alpha + EMA(yesterday) * (1 - alpha)
- BigDecimal emaToday = price.multiply(alpha)
- .add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha)))
- .setScale(10, RoundingMode.HALF_UP);
- ema.add(emaToday);
- }
- }
-
- return ema;
- }
-
- /**
- * 计算价格序列的指数移动平均线(EMA),默认使用SMA作为初始值
- *
- * @param prices 价格序列
- * @param period EMA计算周期
- * @return 计算得到的EMA序列
- */
- public static List<BigDecimal> calculateEMA(List<BigDecimal> prices, int period) {
- return calculateEMA(prices, period, true);
- }
-
- /**
- * 计算单个EMA值(递归计算方式)
- *
- * @param currentPrice 当前价格
- * @param prevEMA 前一个EMA值
- * @param period EMA周期
- * @return 当前EMA值
- */
- public static BigDecimal calculateSingleEMA(BigDecimal currentPrice, BigDecimal prevEMA, int period) {
- // 计算权重因子alpha = 2 / (period + 1)
- BigDecimal alpha = BigDecimal.valueOf(2.0).divide(BigDecimal.valueOf(period + 1), 10, RoundingMode.HALF_UP);
-
- // EMA(today) = Price(today) * alpha + EMA(yesterday) * (1 - alpha)
- return currentPrice.multiply(alpha)
- .add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha)))
- .setScale(10, RoundingMode.HALF_UP);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/IndicatorUtils.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/IndicatorUtils.java
deleted file mode 100644
index a3cbec7..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/IndicatorUtils.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/**
- * 指标计算工具类
- * 封装MACD策略中常用的通用功能,如高低点查找等
- */
-package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-/**
- * 指标计算工具类,提供MACD策略中常用的通用功能
- */
-public class IndicatorUtils {
-
- /**
- * 找到最近的价格高点索引
- *
- * @param prices 价格列表
- * @param startIdx 起始索引
- * @return 最近的价格高点索引
- */
- public static int findRecentHighIndex(List<BigDecimal> prices, int startIdx) {
- if (prices == null || startIdx < 0 || startIdx >= prices.size()) {
- return -1;
- }
-
- int highIdx = startIdx;
- BigDecimal highPrice = prices.get(startIdx);
-
- for (int i = startIdx + 1; i < prices.size(); i++) {
- BigDecimal currentPrice = prices.get(i);
- if (currentPrice.compareTo(highPrice) > 0) {
- highPrice = currentPrice;
- highIdx = i;
- }
- }
-
- return highIdx;
- }
-
- /**
- * 找到最近的价格低点索引
- *
- * @param prices 价格列表
- * @param startIdx 起始索引
- * @return 最近的价格低点索引
- */
- public static int findRecentLowIndex(List<BigDecimal> prices, int startIdx) {
- if (prices == null || startIdx < 0 || startIdx >= prices.size()) {
- return -1;
- }
-
- int lowIdx = startIdx;
- BigDecimal lowPrice = prices.get(startIdx);
-
- for (int i = startIdx + 1; i < prices.size(); i++) {
- BigDecimal currentPrice = prices.get(i);
- if (currentPrice.compareTo(lowPrice) < 0) {
- lowPrice = currentPrice;
- lowIdx = i;
- }
- }
-
- return lowIdx;
- }
-
- /**
- * 找到最近价格高点之前的价格高点索引
- *
- * @param prices 价格列表
- * @param startIdx 起始索引
- * @param recentHighIdx 最近的价格高点索引
- * @return 之前的价格高点索引
- */
- public static int findPreviousHighIndex(List<BigDecimal> prices, int startIdx, int recentHighIdx) {
- if (prices == null || startIdx < 0 || recentHighIdx <= startIdx || recentHighIdx >= prices.size()) {
- return -1;
- }
-
- int highIdx = startIdx;
- BigDecimal highPrice = prices.get(startIdx);
-
- for (int i = startIdx + 1; i < recentHighIdx; i++) {
- BigDecimal currentPrice = prices.get(i);
- if (currentPrice.compareTo(highPrice) > 0) {
- highPrice = currentPrice;
- highIdx = i;
- }
- }
-
- return highIdx;
- }
-
- /**
- * 找到最近价格低点之前的价格低点索引
- *
- * @param prices 价格列表
- * @param startIdx 起始索引
- * @param recentLowIdx 最近的价格低点索引
- * @return 之前的价格低点索引
- */
- public static int findPreviousLowIndex(List<BigDecimal> prices, int startIdx, int recentLowIdx) {
- if (prices == null || startIdx < 0 || recentLowIdx <= startIdx || recentLowIdx >= prices.size()) {
- return -1;
- }
-
- int lowIdx = startIdx;
- BigDecimal lowPrice = prices.get(startIdx);
-
- for (int i = startIdx + 1; i < recentLowIdx; i++) {
- BigDecimal currentPrice = prices.get(i);
- if (currentPrice.compareTo(lowPrice) < 0) {
- lowPrice = currentPrice;
- lowIdx = i;
- }
- }
-
- return lowIdx;
- }
-
- /**
- * 寻找最近的价格高点(带有回调确认)
- *
- * @param prices 价格列表
- * @param startIndex 起始索引
- * @param endIndex 结束索引
- * @return 符合条件的价格高点索引,未找到则返回-1
- */
- public static int findRecentHighWithRetrace(List<BigDecimal> prices, int startIndex, int endIndex) {
- if (prices == null || startIndex < 0 || endIndex >= prices.size() || startIndex >= endIndex) {
- return -1;
- }
-
- int highIndex = -1;
- BigDecimal highPrice = BigDecimal.ZERO;
-
- // 从右向左搜索,找到第一个有效高点
- for (int i = endIndex; i >= startIndex; i--) {
- if (prices.get(i).compareTo(highPrice) > 0) {
- highPrice = prices.get(i);
- highIndex = i;
- }
-
- // 检查高点后是否有回调
- if (highIndex != -1 && i < endIndex) {
- if (prices.get(i + 1).compareTo(highPrice) < 0) {
- return highIndex; // 找到确认回调的高点
- }
- }
- }
-
- return highIndex;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDCalculator.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDCalculator.java
deleted file mode 100644
index 5538216..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDCalculator.java
+++ /dev/null
@@ -1,241 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * MACD(Moving Average Convergence Divergence)指标计算器
- * <p>
- * MACD指标由三部分组成:
- * 1. DIF(Difference):短期EMA与长期EMA的差值
- * 2. DEA(Signal Line):DIF的指数移动平均线,作为MACD的信号线
- * 3. MACD柱状图(Histogram):DIF与DEA的差值,反映市场动量
- * <p>
- * 默认参数:短期周期=12,长期周期=26,信号周期=9
- */
-public class MACDCalculator {
-
- /**
- * 计算MACD指标
- *
- * @param closePrices 收盘价列表(使用BigDecimal确保计算精度)
- * @param fastlen 短期EMA周期(通常为12)
- * @param slowlen 长期EMA周期(通常为26)
- * @param siglen DEA的周期(通常为9)
- * @return 包含MACD各部分数据的PriceData列表
- * @throws IllegalArgumentException 如果数据点不足或参数无效
- */
- public static MACDResult calculateMACD(List<BigDecimal> closePrices, int fastlen, int slowlen, int siglen) {
- // 参数校验:确保数据点足够
- if (closePrices == null || closePrices.isEmpty()) {
- throw new IllegalArgumentException("Close prices list cannot be null or empty.");
- }
- if (fastlen <= 0 || slowlen <= 0 || siglen <= 0) {
- throw new IllegalArgumentException("All periods must be positive integers.");
- }
- if (fastlen >= slowlen) {
- throw new IllegalArgumentException("Fast period must be less than slow period.");
- }
- if (closePrices.size() < Math.max(fastlen, slowlen)) {
- throw new IllegalArgumentException("Insufficient data points for the specified periods.");
- }
-
- // 反转数据,确保从旧到新处理(因为用户提供的数据是从新到旧)
- List<BigDecimal> prices = new ArrayList<>(closePrices);
- Collections.reverse(prices);
-
- // 1. 计算快速EMA和慢速EMA,使用SMA作为初始值
- // 当initialSMA=true时,EMA列表长度为prices.size() - period + 1
- List<BigDecimal> fastEma = EMACalculator.calculateEMA(prices, fastlen, true);
- List<BigDecimal> slowEma = EMACalculator.calculateEMA(prices, slowlen, true);
-
- // 2. 计算MACD线(快速EMA减去慢速EMA)
- List<BigDecimal> macdLine = new ArrayList<>();
- // EMA列表的起始索引与价格列表的对应关系
- int slowEmaStartIdx = slowlen - 1; // slowEma中第一个有效值对应的价格索引
- int fastEmaStartIdx = fastlen - 1; // fastEma中第一个有效值对应的价格索引
-
- for (int i = 0; i < prices.size(); i++) {
- if (i < slowEmaStartIdx) {
- // 在慢速EMA开始有效之前,MACD线值为0
- macdLine.add(BigDecimal.ZERO);
- } else {
- // MACD线 = 快速EMA - 慢速EMA
- // 需要将价格索引转换为EMA列表索引
- int slowEmaIdx = i - slowEmaStartIdx;
- int fastEmaIdx = i - fastEmaStartIdx;
- BigDecimal macdValue = fastEma.get(fastEmaIdx).subtract(slowEma.get(slowEmaIdx));
- macdLine.add(macdValue);
- }
- }
-
- // 3. 计算信号线(MACD线的siglen周期EMA),使用SMA作为初始值
- List<BigDecimal> signalLine = EMACalculator.calculateEMA(macdLine, siglen, true);
-
- // 4. 计算柱状图(MACD线与信号线的差值)
- List<BigDecimal> histogram = new ArrayList<>();
- int signalLineStartIdx = siglen - 1; // signalLine中第一个有效值对应的macdLine索引
-
- for (int i = 0; i < macdLine.size(); i++) {
- if (i < slowEmaStartIdx + signalLineStartIdx) {
- // 在信号线开始有效之前,柱状图值为0
- histogram.add(BigDecimal.ZERO);
- } else {
- // 将macdLine索引转换为signalLine索引
- int signalLineIdx = i - signalLineStartIdx;
- // 柱状图 = (MACD线 - 信号线) * 2(放大信号)
- BigDecimal histValue = macdLine.get(i).subtract(signalLine.get(signalLineIdx)).multiply(new BigDecimal("2"));
- histogram.add(histValue);
- }
- }
-
- // 5. 构建结果数据
- List<PriceData> result = new ArrayList<>();
- int startIndex = slowEmaStartIdx + signalLineStartIdx; // 从信号线开始有效的位置开始
-
- for (int i = startIndex; i < prices.size(); i++) {
- PriceData data = new PriceData(prices.get(i));
-
- // 设置EMA值(需要转换索引)
- int fastEmaIdx = i - fastEmaStartIdx;
- int slowEmaIdx = i - slowEmaStartIdx;
- data.setEmaShort(fastEma.get(fastEmaIdx));
- data.setEmaLong(slowEma.get(slowEmaIdx));
-
- // 设置MACD指标值
- data.setDif(macdLine.get(i));
- data.setDea(signalLine.get(i - signalLineStartIdx));
- data.setMacdHist(histogram.get(i));
-
- result.add(data);
- }
-
- // 反转结果列表,恢复为从新到旧的顺序
- Collections.reverse(result);
-
- return new MACDResult(result, startIndex);
- }
-
- /**
- * 使用默认参数计算MACD指标
- * <p>
- * 默认参数:短期周期=12,长期周期=26,信号周期=9
- *
- * @param closePrices 收盘价列表
- * @return 包含MACD各部分数据的PriceData列表
- */
- public static MACDResult calculateMACD(List<BigDecimal> closePrices) {
- // 默认参数:快速周期12,慢速周期26,信号周期9
- return calculateMACD(closePrices, 12, 26, 9);
- }
-
- /**
- * 判断是否出现顶背离
- * <p>
- * 顶背离:价格创新高,但DIF未创新高,且与价格走势背离
- * 增强空头信号可靠性
- *
- * @param closePrices 原始收盘价列表
- * @param macdResult MACD计算结果
- * @return 是否出现顶背离
- */
- public static boolean isTopDivergence(List<BigDecimal> closePrices, MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- int startIdx = macdResult.getStartIndex();
-
- // 确保有足够的数据点进行判断(至少需要2个高点)
- if (macdData.size() < 10) {
- return false;
- }
-
- // 反转原始价格列表,确保从旧到新处理
- List<BigDecimal> prices = new ArrayList<>(closePrices);
- Collections.reverse(prices);
-
- // 找到最近的价格高点和对应的DIF值
- int recentPriceHighIdx = IndicatorUtils.findRecentHighIndex(prices, startIdx);
- if (recentPriceHighIdx < startIdx + 2 || recentPriceHighIdx == -1) {
- return false;
- }
-
- // 找到之前的价格高点和对应的DIF值
- int previousPriceHighIdx = IndicatorUtils.findPreviousHighIndex(prices, startIdx, recentPriceHighIdx);
- if (previousPriceHighIdx < startIdx || previousPriceHighIdx == -1) {
- return false;
- }
-
- // 获取对应位置的DIF值
- int recentDifIdx = recentPriceHighIdx - startIdx;
- int previousDifIdx = previousPriceHighIdx - startIdx;
-
- // 边界检查
- if (recentDifIdx >= macdData.size() || previousDifIdx >= macdData.size()) {
- return false;
- }
-
- BigDecimal recentPrice = prices.get(recentPriceHighIdx);
- BigDecimal previousPrice = prices.get(previousPriceHighIdx);
- BigDecimal recentDif = macdData.get(recentDifIdx).getDif();
- BigDecimal previousDif = macdData.get(previousDifIdx).getDif();
-
- // 顶背离条件:价格创新高,但DIF未创新高
- return recentPrice.compareTo(previousPrice) > 0 &&
- recentDif.compareTo(previousDif) < 0;
- }
-
- /**
- * 判断是否出现底背离
- * <p>
- * 底背离:价格创新低,但DIF未创新低,且与价格走势背离
- * 增强多头信号可靠性
- *
- * @param closePrices 原始收盘价列表
- * @param macdResult MACD计算结果
- * @return 是否出现底背离
- */
- public static boolean isBottomDivergence(List<BigDecimal> closePrices, MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- int startIdx = macdResult.getStartIndex();
-
- // 确保有足够的数据点进行判断(至少需要2个低点)
- if (macdData.size() < 10) {
- return false;
- }
-
- // 反转原始价格列表,确保从旧到新处理
- List<BigDecimal> prices = new ArrayList<>(closePrices);
- Collections.reverse(prices);
-
- // 找到最近的价格低点和对应的DIF值
- int recentPriceLowIdx = IndicatorUtils.findRecentLowIndex(prices, startIdx);
- if (recentPriceLowIdx < startIdx + 2 || recentPriceLowIdx == -1) {
- return false;
- }
-
- // 找到之前的价格低点和对应的DIF值
- int previousPriceLowIdx = IndicatorUtils.findPreviousLowIndex(prices, startIdx, recentPriceLowIdx);
- if (previousPriceLowIdx < startIdx || previousPriceLowIdx == -1) {
- return false;
- }
-
- // 获取对应位置的DIF值
- int recentDifIdx = recentPriceLowIdx - startIdx;
- int previousDifIdx = previousPriceLowIdx - startIdx;
-
- // 边界检查
- if (recentDifIdx >= macdData.size() || previousDifIdx >= macdData.size()) {
- return false;
- }
-
- BigDecimal recentPrice = prices.get(recentPriceLowIdx);
- BigDecimal previousPrice = prices.get(previousPriceLowIdx);
- BigDecimal recentDif = macdData.get(recentDifIdx).getDif();
- BigDecimal previousDif = macdData.get(previousDifIdx).getDif();
-
- // 底背离条件:价格创新低,但DIF未创新低
- return recentPrice.compareTo(previousPrice) < 0 &&
- recentDif.compareTo(previousDif) > 0;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDResult.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDResult.java
deleted file mode 100644
index 0ab00ae..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDResult.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * MACD计算结果类
- * <p>
- * 用于封装MACD指标计算的结果数据,包括完整的MACD数据序列和数据的起始索引信息,
- * 方便策略模块获取和使用MACD计算结果。
- */
-package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
-
-import java.util.List;
-
-/**
- * MACD计算结果封装类
- */
-public class MACDResult {
- /** MACD完整数据序列,包含每个价格点对应的DIF、DEA和MACD柱状图值 */
- private List<PriceData> macdData;
-
- /** 在原始价格序列中的起始索引,表示MACD数据的计算起点 */
- private int startIndex;
-
- /**
- * 构造函数,创建MACD计算结果对象
- *
- * @param result 计算得到的MACD数据序列
- * @param startIdx 数据在原始价格序列中的起始索引
- */
- public MACDResult(List<PriceData> result, int startIdx) {
- this.macdData = result;
- this.startIndex = startIdx;
- }
-
- /**
- * 获取MACD数据序列
- * @return MACD数据序列
- */
- public List<PriceData> getMacdData() {
- return macdData;
- }
-
- /**
- * 获取起始索引
- * @return 起始索引值
- */
- public int getStartIndex() {
- return startIndex;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdEmaStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdEmaStrategy.java
deleted file mode 100644
index 8dab3cb..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdEmaStrategy.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/**
- * MACD和MA组合交易策略实现类
- * 基于多时间粒度K线数据生成交易信号并确定持仓方向
- *
- * 该策略综合考虑了EMA指标、MACD指标、价格突破信号和波动率因素,
- * 形成了一套完整的开仓、平仓和持仓管理机制。
- * 支持1分钟(K线)和1分钟(K线)级别的数据输入,数据顺序要求从新到旧排列。
- */
-package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
-
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * MACD和MA组合交易策略实现
- * <p>
- * 该策略利用EMA交叉、MACD指标、价格突破信号和波动率过滤,
- * 为多时间粒度K线级别交易提供综合决策支持。
- * <p>
- * 数据输入要求:
- * - historicalPrices1M:1分钟K线收盘价列表,顺序从新到旧
- * - historicalPrices1D:日线K线收盘价列表,顺序从新到旧
- */
-@Slf4j
-public class MacdEmaStrategy {
-
- /** 操作类型枚举 */
- public enum OperationType {
- /** 开仓 */
- open,
- /** 平仓 */
- close
- }
-
- /** 持仓状态枚举 */
- public enum PositionType {
- /** 多头开仓 */
- LONG_BUY,
- /** 多头平仓 */
- LONG_SELL,
- /** 空头开仓 */
- SHORT_SELL,
- /** 空头平仓 */
- SHORT_BUY,
- /** 空仓 */
- NONE
- }
-
- /** 交易指令类,封装side和posSide的组合 */
- public static class TradingOrder {
- private String side; // buy或sell
- private String posSide; // long或short
-
- public TradingOrder(String side, String posSide) {
- this.side = side;
- this.posSide = posSide;
- }
-
- public String getSide() {
- return side;
- }
-
- public String getPosSide() {
- return posSide;
- }
-
- @Override
- public String toString() {
- return String.format("TradingOrder{side='%s', posSide='%s'}", side, posSide);
- }
- }
-
- // 策略参数
- private int shortPeriod; // 短期EMA周期
- private int longPeriod; // 长期EMA周期
- private int signalPeriod; // MACD信号线周期
- private int volatilityPeriod; // 波动率计算周期
- private int trendPeriod = 200; // 趋势过滤EMA周期(200日)
- private BigDecimal stopLossRatio; // 止损比例
- private BigDecimal takeProfitRatio; // 止盈比例
-
- /**
- * 默认构造函数,使用标准MACD参数
- * 短期周期=12, 长期周期=26, 信号线周期=9, 波动率周期=20
- * 止损比例=1%, 止盈比例=2%
- */
- public MacdEmaStrategy() {
- this(12, 26, 9, 20, new BigDecimal("0.01"), new BigDecimal("0.02"));
- }
-
- /**
- * 自定义参数构造函数,使用默认趋势周期200
- *
- * @param shortPeriod 短期EMA周期
- * @param longPeriod 长期EMA周期
- * @param signalPeriod MACD信号线周期
- * @param volatilityPeriod 波动率计算周期
- * @param stopLossRatio 止损比例
- * @param takeProfitRatio 止盈比例
- */
- public MacdEmaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod,
- BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
- this(shortPeriod, longPeriod, signalPeriod, volatilityPeriod, 200, stopLossRatio, takeProfitRatio);
- }
-
- /**
- * 自定义参数构造函数
- *
- * @param shortPeriod 短期EMA周期
- * @param longPeriod 长期EMA周期
- * @param signalPeriod MACD信号线周期
- * @param volatilityPeriod 波动率计算周期
- * @param trendPeriod 趋势过滤EMA周期(200日)
- * @param stopLossRatio 止损比例
- * @param takeProfitRatio 止盈比例
- */
- public MacdEmaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod, int trendPeriod,
- BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
- this.shortPeriod = shortPeriod;
- this.longPeriod = longPeriod;
- this.signalPeriod = signalPeriod;
- this.volatilityPeriod = volatilityPeriod;
- this.trendPeriod = trendPeriod;
- this.stopLossRatio = stopLossRatio;
- this.takeProfitRatio = takeProfitRatio;
- }
-
- // 主流程方法
-
- /**
- * 分析历史价格数据并生成交易指令
- *
- * @param historicalPrices 历史价格序列(1分钟K线收盘价),顺序从新到旧
- * @param operation 操作类型(open/close)
- * @return 交易指令(包含side和posSide),如果没有交易信号则返回null
- */
- public TradingOrder generateTradingOrder(List<BigDecimal> historicalPrices, String operation) {
- PositionType signal = null;
-
- if (OperationType.open.name().equals(operation)) {
- signal = analyzeOpen(historicalPrices);
- } else if (OperationType.close.name().equals(operation)) {
- signal = analyzeClose(historicalPrices);
- }
-
- // 根据信号生成交易指令
- return convertSignalToTradingOrder(signal);
- }
-
- /**
- * 分析最新价格数据并生成开仓信号
- *
- * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
- * @return 生成的交易信号(LONG_BUY、SHORT_SELL或NONE)
- */
- public PositionType analyzeOpen(List<BigDecimal> closePrices) {
- // 数据检查:确保有足够的数据点进行计算(需要足够数据计算200日EMA)
- if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod)) {
- return PositionType.NONE; // 数据不足,无法生成信号
- }
-
- // 计算MACD指标
- MACDResult macdResult = MACDCalculator.calculateMACD(
- closePrices, shortPeriod, longPeriod, signalPeriod);
- log.info("MACD计算结果:{}", macdResult.getMacdData().get(0));
-
- // 多头开仓条件检查
- if (isLongEntryCondition(macdResult, closePrices)) {
- log.info("多头开仓信号,价格:{}", closePrices.get(0));
- return PositionType.LONG_BUY;
- }
-
- // 空头开仓条件检查
- if (isShortEntryCondition(macdResult, closePrices)) {
- log.info("空头开仓信号,价格:{}", closePrices.get(0));
- return PositionType.SHORT_SELL;
- }
-
- // 无信号
- return PositionType.NONE;
- }
-
- /**
- * 分析最新价格数据并生成平仓信号
- *
- * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
- * @return 生成的交易信号(LONG_SELL、SHORT_BUY或NONE)
- */
- public PositionType analyzeClose(List<BigDecimal> closePrices) {
- // 数据检查:确保有足够的数据点进行计算
- if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod)) {
- return PositionType.NONE; // 数据不足,无法生成信号
- }
-
- // 计算MACD指标
- MACDResult macdResult = MACDCalculator.calculateMACD(
- closePrices, shortPeriod, longPeriod, signalPeriod);
-
- // 最新收盘价
- BigDecimal latestPrice = closePrices.get(0);
-
- if (isLongExitCondition(macdResult, latestPrice)) {
- log.info("多头平仓信号,价格:{}", latestPrice);
- return PositionType.LONG_SELL;
- }
-
- if (isShortExitCondition(macdResult, latestPrice)) {
- log.info("空头平仓信号,价格:{}", latestPrice);
- return PositionType.SHORT_BUY;
- }
-
- // 无信号
- return PositionType.NONE;
- }
-
- // 信号转换方法
-
- /**
- * 将持仓信号转换为交易指令
- *
- * @param signal 持仓信号
- * @return 交易指令,无信号则返回null
- */
- private TradingOrder convertSignalToTradingOrder(PositionType signal) {
- if (signal == null) {
- return null;
- }
-
- switch (signal) {
- case LONG_BUY:
- // 开多:买入开多(side 填写 buy; posSide 填写 long )
- return new TradingOrder("buy", "long");
- case LONG_SELL:
- // 平多:卖出平多(side 填写 sell; posSide 填写 long )
- return new TradingOrder("sell", "long");
- case SHORT_SELL:
- // 开空:卖出开空(side 填写 sell; posSide 填写 short )
- return new TradingOrder("sell", "short");
- case SHORT_BUY:
- // 平空:买入平空(side 填写 buy; posSide 填写 short )
- return new TradingOrder("buy", "short");
- default:
- // 无信号
- return null;
- }
- }
-
- // 开仓条件检查方法
-
- /**
- * 多头开仓条件检查
- *
- * @param macdResult MACD计算结果
- * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
- * @return 是否满足多头开仓条件
- */
- private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices) {
- // 1. 计算200日EMA(趋势过滤)
- // 复制并反转日线数据,确保从旧到新计算EMA
- List<BigDecimal> reversed1DPrices = new ArrayList<>(closePrices);
- Collections.reverse(reversed1DPrices);
- List<BigDecimal> trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true);
- BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1);
- BigDecimal latestPrice = closePrices.get(0);
-
- // 2. 价格必须位于200日EMA上方(多头趋势确认)
- boolean isAboveTrend = latestPrice.compareTo(latestTrendEma) > 0;
-
- // 3. MACD金叉检查
- boolean isGoldenCross = isGoldenCross(macdResult);
-
- // 4. MACD柱状线由负转正(动量转变)
- boolean isMacdHistTurningPositive = isMacdHistTurningPositive(macdResult);
-
- // 5. 底背离检查(增强多头信号可靠性)
- boolean isBottomDivergence = MACDCalculator.isBottomDivergence(closePrices, macdResult);
-
- log.info("多头信号检查, 200日EMA价格{}位于上方: {}, 金叉: {}, MACD柱状线由负转正: {}, 底背离: {}",
- latestTrendEma,isAboveTrend, isGoldenCross, isMacdHistTurningPositive, isBottomDivergence);
-
- // 多头开仓条件:柱状线转强 + 金叉 + (趋势向上或底背离)
- return isMacdHistTurningPositive && isGoldenCross && ( isAboveTrend|| isBottomDivergence);
- }
-
- /**
- * 空头开仓条件检查
- *
- * @param macdResult MACD计算结果
- * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
- * @return 是否满足空头开仓条件
- */
- private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices) {
- // 1. 计算200日EMA(趋势过滤)
- // 复制并反转日线数据,确保从旧到新计算EMA
- List<BigDecimal> reversed1DPrices = new ArrayList<>(closePrices);
- Collections.reverse(reversed1DPrices);
- List<BigDecimal> trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true);
- BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1);
- BigDecimal latestPrice = closePrices.get(0);
-
- // 2. 价格必须位于200日EMA下方(空头趋势确认)
- boolean isBelowTrend = latestPrice.compareTo(latestTrendEma) < 0;
-
- // 3. MACD死叉检查
- boolean isDeathCross = isDeathCross(macdResult);
-
- // 4. MACD柱状线由正转负(动量转变)
- boolean isMacdHistTurningNegative = isMacdHistTurningNegative(macdResult);
-
- // 5. 顶背离检查(增强空头信号可靠性)
- boolean isTopDivergence = MACDCalculator.isTopDivergence(closePrices, macdResult);
-
- log.info("空头信号检查, 200日EMA价格{}位于下方: {}, 死叉: {}, MACD柱状线由正转负: {}, 顶背离: {}",
- latestTrendEma,isBelowTrend, isDeathCross, isMacdHistTurningNegative, isTopDivergence);
-
- // 空头开仓条件:柱状线转弱 + 死叉 + (趋势向下或顶背离)
- return isMacdHistTurningNegative && isDeathCross && ( isBelowTrend || isTopDivergence);
- }
-
- // 平仓条件检查方法
-
- /**
- * 多头平仓条件检查
- *
- * @param macdResult MACD计算结果
- * @param currentPrice 当前价格
- * @return 是否满足多头平仓条件
- */
- private boolean isLongExitCondition(MACDResult macdResult, BigDecimal currentPrice) {
- // 多头平仓条件:MACD柱状线动量减弱(由正转弱)
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() >= 2) {
- PriceData latest = macdData.get(0); // 最新数据
- PriceData previous = macdData.get(1); // 前一个数据
-
- // 柱状线由正转弱:前一根为正,当前绝对值减小
- boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 &&
- latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
-
- return momentumWeakening;
- }
- return false;
- }
-
- /**
- * 空头平仓条件检查
- *
- * @param macdResult MACD计算结果
- * @param currentPrice 当前价格
- * @return 是否满足空头平仓条件
- */
- private boolean isShortExitCondition(MACDResult macdResult, BigDecimal currentPrice) {
- // 空头平仓条件:MACD柱状线动量减弱(由负转弱)
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() >= 2) {
- PriceData latest = macdData.get(0); // 最新数据
- PriceData previous = macdData.get(1); // 前一个数据
-
- // 柱状线由负转弱:前一根为负,当前绝对值减小
- boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 &&
- latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
-
- return momentumWeakening;
- }
-
- return false;
- }
-
- // MACD信号辅助方法
-
- /**
- * 简单金叉判断
- * <p>
- * 条件:DIF线从下往上穿过DEA线
- *
- * @param macdResult MACD计算结果
- * @return 是否形成金叉
- */
- private boolean isGoldenCross(MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() < 2) {
- return false;
- }
-
- PriceData latest = macdData.get(0);
- PriceData previous = macdData.get(1);
-
- // 金叉判断:DIF从下往上穿过DEA
- return previous.getDif().compareTo(previous.getDea()) < 0 &&
- latest.getDif().compareTo(latest.getDea()) > 0;
- }
-
- /**
- * 简单死叉判断
- * <p>
- * 条件:DIF线从上往下穿过DEA线
- *
- * @param macdResult MACD计算结果
- * @return 是否形成死叉
- */
- private boolean isDeathCross(MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() < 2) {
- return false;
- }
-
- PriceData latest = macdData.get(0);
- PriceData previous = macdData.get(1);
-
- // 死叉判断:DIF从上往下穿过DEA
- return previous.getDif().compareTo(previous.getDea()) > 0 &&
- latest.getDif().compareTo(latest.getDea()) < 0;
- }
-
- /**
- * MACD柱状线由负转正判断
- * <p>
- * 条件:前一根柱状线为负,当前柱状线为正
- *
- * @param macdResult MACD计算结果
- * @return 是否由负转正
- */
- private boolean isMacdHistTurningPositive(MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() < 2) {
- return false;
- }
-
- PriceData latest = macdData.get(0); // 最新数据
- PriceData previous = macdData.get(1); // 前一个数据
-
- // 柱状线由负转正:前一根为负,当前为正
- return previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 &&
- latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0;
- }
-
- /**
- * MACD柱状线由正转负判断
- * <p>
- * 条件:前一根柱状线为正,当前柱状线为负
- *
- * @param macdResult MACD计算结果
- * @return 是否由正转负
- */
- private boolean isMacdHistTurningNegative(MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() < 2) {
- return false;
- }
-
- PriceData latest = macdData.get(0); // 最新数据
- PriceData previous = macdData.get(1); // 前一个数据
-
- // 柱状线由正转负:前一根为正,当前为负
- return previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 &&
- latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0;
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java
deleted file mode 100644
index f5ebaf3..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java
+++ /dev/null
@@ -1,473 +0,0 @@
-/**
- * MACD和MA组合交易策略实现类
- * 基于多时间粒度K线数据生成交易信号并确定持仓方向
- *
- * 该策略综合考虑了EMA指标、MACD指标、价格突破信号和波动率因素,
- * 形成了一套完整的开仓、平仓和持仓管理机制。
- * 支持1分钟(K线)和日线(K线)级别的数据输入,数据顺序要求从新到旧排列。
- */
-package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
-
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * MACD和MA组合交易策略实现
- * <p>
- * 该策略利用EMA交叉、MACD指标、价格突破信号和波动率过滤,
- * 为多时间粒度K线级别交易提供综合决策支持。
- * <p>
- * 数据输入要求:
- * - historicalPrices1M:1分钟K线收盘价列表,顺序从新到旧
- * - historicalPrices1D:日线K线收盘价列表,顺序从新到旧
- */
-@Slf4j
-public class MacdMaStrategy {
-
- /** 操作类型枚举 */
- public enum OperationType {
- /** 开仓 */
- open,
- /** 平仓 */
- close
- }
-
- /** 持仓状态枚举 */
- public enum PositionType {
- /** 多头开仓 */
- LONG_BUY,
- /** 多头平仓 */
- LONG_SELL,
- /** 空头开仓 */
- SHORT_SELL,
- /** 空头平仓 */
- SHORT_BUY,
- /** 空仓 */
- NONE
- }
-
- /** 交易指令类,封装side和posSide的组合 */
- public static class TradingOrder {
- private String side; // buy或sell
- private String posSide; // long或short
-
- public TradingOrder(String side, String posSide) {
- this.side = side;
- this.posSide = posSide;
- }
-
- public String getSide() {
- return side;
- }
-
- public String getPosSide() {
- return posSide;
- }
-
- @Override
- public String toString() {
- return String.format("TradingOrder{side='%s', posSide='%s'}", side, posSide);
- }
- }
-
- // 策略参数
- private int shortPeriod; // 短期EMA周期
- private int longPeriod; // 长期EMA周期
- private int signalPeriod; // MACD信号线周期
- private int volatilityPeriod; // 波动率计算周期
- private int trendPeriod = 200; // 趋势过滤EMA周期(200日)
- private BigDecimal stopLossRatio; // 止损比例
- private BigDecimal takeProfitRatio; // 止盈比例
-
- /**
- * 默认构造函数,使用标准MACD参数
- * 短期周期=12, 长期周期=26, 信号线周期=9, 波动率周期=20
- * 止损比例=1%, 止盈比例=2%
- */
- public MacdMaStrategy() {
- this(12, 26, 9, 20, new BigDecimal("0.01"), new BigDecimal("0.02"));
- }
-
- /**
- * 自定义参数构造函数,使用默认趋势周期200
- *
- * @param shortPeriod 短期EMA周期
- * @param longPeriod 长期EMA周期
- * @param signalPeriod MACD信号线周期
- * @param volatilityPeriod 波动率计算周期
- * @param stopLossRatio 止损比例
- * @param takeProfitRatio 止盈比例
- */
- public MacdMaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod,
- BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
- this(shortPeriod, longPeriod, signalPeriod, volatilityPeriod, 200, stopLossRatio, takeProfitRatio);
- }
-
- /**
- * 自定义参数构造函数
- *
- * @param shortPeriod 短期EMA周期
- * @param longPeriod 长期EMA周期
- * @param signalPeriod MACD信号线周期
- * @param volatilityPeriod 波动率计算周期
- * @param trendPeriod 趋势过滤EMA周期(200日)
- * @param stopLossRatio 止损比例
- * @param takeProfitRatio 止盈比例
- */
- public MacdMaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod, int trendPeriod,
- BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
- this.shortPeriod = shortPeriod;
- this.longPeriod = longPeriod;
- this.signalPeriod = signalPeriod;
- this.volatilityPeriod = volatilityPeriod;
- this.trendPeriod = trendPeriod;
- this.stopLossRatio = stopLossRatio;
- this.takeProfitRatio = takeProfitRatio;
- }
-
- // 主流程方法
-
- /**
- * 分析历史价格数据并生成交易指令
- *
- * @param historicalPrices 历史价格序列(1分钟K线收盘价),顺序从新到旧
- * @param historical1DayPrices 日线历史价格序列,顺序从新到旧
- * @param operation 操作类型(open/close)
- * @return 交易指令(包含side和posSide),如果没有交易信号则返回null
- */
- public TradingOrder generateTradingOrder(List<BigDecimal> historicalPrices, List<BigDecimal> historical1DayPrices, String operation) {
- PositionType signal = null;
-
- if (OperationType.open.name().equals(operation)) {
- signal = analyzeOpen(historicalPrices, historical1DayPrices);
- } else if (OperationType.close.name().equals(operation)) {
- signal = analyzeClose(historicalPrices, historical1DayPrices);
- }
-
- // 根据信号生成交易指令
- return convertSignalToTradingOrder(signal);
- }
-
- /**
- * 分析最新价格数据并生成开仓信号
- *
- * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
- * @param close1DPrices 日线K线收盘价序列,顺序从新到旧
- * @return 生成的交易信号(LONG_BUY、SHORT_SELL或NONE)
- */
- public PositionType analyzeOpen(List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
- // 数据检查:确保有足够的数据点进行计算(需要足够数据计算200日EMA)
- if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod) ||
- close1DPrices == null || close1DPrices.size() < Math.max(34, trendPeriod)) {
- return PositionType.NONE; // 数据不足,无法生成信号
- }
-
- // 计算MACD指标
- MACDResult macdResult = MACDCalculator.calculateMACD(
- closePrices, shortPeriod, longPeriod, signalPeriod);
- log.info("MACD计算结果:{}", macdResult.getMacdData().get(0));
-
- // 多头开仓条件检查
- if (isLongEntryCondition(macdResult, closePrices, close1DPrices)) {
- log.info("多头开仓信号,价格:{}", closePrices.get(0));
- return PositionType.LONG_BUY;
- }
-
- // 空头开仓条件检查
- if (isShortEntryCondition(macdResult, closePrices, close1DPrices)) {
- log.info("空头开仓信号,价格:{}", closePrices.get(0));
- return PositionType.SHORT_SELL;
- }
-
- // 无信号
- return PositionType.NONE;
- }
-
- /**
- * 分析最新价格数据并生成平仓信号
- *
- * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
- * @param close1DPrices 日线K线收盘价序列,顺序从新到旧
- * @return 生成的交易信号(LONG_SELL、SHORT_BUY或NONE)
- */
- public PositionType analyzeClose(List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
- // 数据检查:确保有足够的数据点进行计算
- if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod) ||
- close1DPrices == null || close1DPrices.size() < Math.max(34, trendPeriod)) {
- return PositionType.NONE; // 数据不足,无法生成信号
- }
-
- // 计算MACD指标
- MACDResult macdResult = MACDCalculator.calculateMACD(
- closePrices, shortPeriod, longPeriod, signalPeriod);
-
- // 最新收盘价
- BigDecimal latestPrice = closePrices.get(0);
-
- if (isLongExitCondition(macdResult, latestPrice)) {
- log.info("多头平仓信号,价格:{}", latestPrice);
- return PositionType.LONG_SELL;
- }
-
- if (isShortExitCondition(macdResult, latestPrice)) {
- log.info("空头平仓信号,价格:{}", latestPrice);
- return PositionType.SHORT_BUY;
- }
-
- // 无信号
- return PositionType.NONE;
- }
-
- // 信号转换方法
-
- /**
- * 将持仓信号转换为交易指令
- *
- * @param signal 持仓信号
- * @return 交易指令,无信号则返回null
- */
- private TradingOrder convertSignalToTradingOrder(PositionType signal) {
- if (signal == null) {
- return null;
- }
-
- switch (signal) {
- case LONG_BUY:
- // 开多:买入开多(side 填写 buy; posSide 填写 long )
- return new TradingOrder("buy", "long");
- case LONG_SELL:
- // 平多:卖出平多(side 填写 sell; posSide 填写 long )
- return new TradingOrder("sell", "long");
- case SHORT_SELL:
- // 开空:卖出开空(side 填写 sell; posSide 填写 short )
- return new TradingOrder("sell", "short");
- case SHORT_BUY:
- // 平空:买入平空(side 填写 buy; posSide 填写 short )
- return new TradingOrder("buy", "short");
- default:
- // 无信号
- return null;
- }
- }
-
- // 开仓条件检查方法
-
- /**
- * 多头开仓条件检查
- *
- * @param macdResult MACD计算结果
- * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
- * @param close1DPrices 日线K线收盘价序列,顺序从新到旧
- * @return 是否满足多头开仓条件
- */
- private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
- // 1. 计算200日EMA(趋势过滤)
- // 复制并反转日线数据,确保从旧到新计算EMA
- List<BigDecimal> reversed1DPrices = new ArrayList<>(close1DPrices);
- Collections.reverse(reversed1DPrices);
- List<BigDecimal> trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true);
- BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1);
- BigDecimal latestPrice = closePrices.get(0);
- log.info( "200日EMA:{}, 最新价格:{}", latestTrendEma, latestPrice);
-
- // 2. 价格必须位于200日EMA上方(多头趋势确认)
- boolean isAboveTrend = latestPrice.compareTo(latestTrendEma) > 0;
-
- // 3. MACD金叉检查
- boolean isGoldenCross = isGoldenCross(macdResult);
-
- // 4. MACD柱状线由负转正(动量转变)
- boolean isMacdHistTurningPositive = isMacdHistTurningPositive(macdResult);
-
- // 5. 底背离检查(增强多头信号可靠性)
- boolean isBottomDivergence = MACDCalculator.isBottomDivergence(closePrices, macdResult);
-
- log.info("多头信号检查, 价格位于200日EMA上方: {}, 金叉: {}, MACD柱状线由负转正: {}, 底背离: {}",
- isAboveTrend, isGoldenCross, isMacdHistTurningPositive, isBottomDivergence);
-
- // 多头开仓条件:趋势向上 + 金叉 + (柱状线转强或底背离)
- return isAboveTrend && isGoldenCross && (isMacdHistTurningPositive || isBottomDivergence);
- }
-
- /**
- * 空头开仓条件检查
- *
- * @param macdResult MACD计算结果
- * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
- * @param close1DPrices 日线K线收盘价序列,顺序从新到旧
- * @return 是否满足空头开仓条件
- */
- private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
- // 1. 计算200日EMA(趋势过滤)
- // 复制并反转日线数据,确保从旧到新计算EMA
- List<BigDecimal> reversed1DPrices = new ArrayList<>(close1DPrices);
- Collections.reverse(reversed1DPrices);
- List<BigDecimal> trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true);
- BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1);
- BigDecimal latestPrice = closePrices.get(0);
-
- log.info( "200日EMA:{}, 最新价格:{}", latestTrendEma, latestPrice);
-
- // 2. 价格必须位于200日EMA下方(空头趋势确认)
- boolean isBelowTrend = latestPrice.compareTo(latestTrendEma) < 0;
-
- // 3. MACD死叉检查
- boolean isDeathCross = isDeathCross(macdResult);
-
- // 4. MACD柱状线由正转负(动量转变)
- boolean isMacdHistTurningNegative = isMacdHistTurningNegative(macdResult);
-
- // 5. 顶背离检查(增强空头信号可靠性)
- boolean isTopDivergence = MACDCalculator.isTopDivergence(closePrices, macdResult);
-
- log.info("空头信号检查, 价格位于200日EMA下方: {}, 死叉: {}, MACD柱状线由正转负: {}, 顶背离: {}",
- isBelowTrend, isDeathCross, isMacdHistTurningNegative, isTopDivergence);
-
- // 空头开仓条件:趋势向下 + 死叉 + (柱状线转弱或顶背离)
- return isBelowTrend && isDeathCross && (isMacdHistTurningNegative || isTopDivergence);
- }
-
- // 平仓条件检查方法
-
- /**
- * 多头平仓条件检查
- *
- * @param macdResult MACD计算结果
- * @param currentPrice 当前价格
- * @return 是否满足多头平仓条件
- */
- private boolean isLongExitCondition(MACDResult macdResult, BigDecimal currentPrice) {
- // 多头平仓条件:MACD柱状线动量减弱(由正转弱)
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() >= 2) {
- PriceData latest = macdData.get(0); // 最新数据
- PriceData previous = macdData.get(1); // 前一个数据
-
- // 柱状线由正转弱:前一根为正,当前绝对值减小
- boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 &&
- latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
-
- return momentumWeakening;
- }
- return false;
- }
-
- /**
- * 空头平仓条件检查
- *
- * @param macdResult MACD计算结果
- * @param currentPrice 当前价格
- * @return 是否满足空头平仓条件
- */
- private boolean isShortExitCondition(MACDResult macdResult, BigDecimal currentPrice) {
- // 空头平仓条件:MACD柱状线动量减弱(由负转弱)
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() >= 2) {
- PriceData latest = macdData.get(0); // 最新数据
- PriceData previous = macdData.get(1); // 前一个数据
-
- // 柱状线由负转弱:前一根为负,当前绝对值减小
- boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 &&
- latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
-
- return momentumWeakening;
- }
-
- return false;
- }
-
- // MACD信号辅助方法
-
- /**
- * 简单金叉判断
- * <p>
- * 条件:DIF线从下往上穿过DEA线
- *
- * @param macdResult MACD计算结果
- * @return 是否形成金叉
- */
- private boolean isGoldenCross(MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() < 2) {
- return false;
- }
-
- PriceData latest = macdData.get(0);
- PriceData previous = macdData.get(1);
-
- // 金叉判断:DIF从下往上穿过DEA
- return previous.getDif().compareTo(previous.getDea()) < 0 &&
- latest.getDif().compareTo(latest.getDea()) > 0;
- }
-
- /**
- * 简单死叉判断
- * <p>
- * 条件:DIF线从上往下穿过DEA线
- *
- * @param macdResult MACD计算结果
- * @return 是否形成死叉
- */
- private boolean isDeathCross(MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() < 2) {
- return false;
- }
-
- PriceData latest = macdData.get(0);
- PriceData previous = macdData.get(1);
-
- // 死叉判断:DIF从上往下穿过DEA
- return previous.getDif().compareTo(previous.getDea()) > 0 &&
- latest.getDif().compareTo(latest.getDea()) < 0;
- }
-
- /**
- * MACD柱状线由负转正判断
- * <p>
- * 条件:前一根柱状线为负,当前柱状线为正
- *
- * @param macdResult MACD计算结果
- * @return 是否由负转正
- */
- private boolean isMacdHistTurningPositive(MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() < 2) {
- return false;
- }
-
- PriceData latest = macdData.get(0); // 最新数据
- PriceData previous = macdData.get(1); // 前一个数据
-
- // 柱状线由负转正:前一根为负,当前为正
- return previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 &&
- latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0;
- }
-
- /**
- * MACD柱状线由正转负判断
- * <p>
- * 条件:前一根柱状线为正,当前柱状线为负
- *
- * @param macdResult MACD计算结果
- * @return 是否由正转负
- */
- private boolean isMacdHistTurningNegative(MACDResult macdResult) {
- List<PriceData> macdData = macdResult.getMacdData();
- if (macdData.size() < 2) {
- return false;
- }
-
- PriceData latest = macdData.get(0); // 最新数据
- PriceData previous = macdData.get(1); // 前一个数据
-
- // 柱状线由正转负:前一根为正,当前为负
- return previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 &&
- latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0;
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/PriceData.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/PriceData.java
deleted file mode 100644
index 38f61e8..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/PriceData.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/**
- * 价格数据实体类
- * <p>
- * 用于存储K线的价格数据及其衍生指标值。作为MACD和MA策略计算过程中的数据载体,
- * 包含收盘价、指数移动平均线、MACD指标等关键价格和指标信息。
- */
-package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
-
-import java.math.BigDecimal;
-
-/**
- * 价格数据类,封装单条K线的价格信息及相关技术指标数据
- * 使用BigDecimal确保金融计算的精确性
- */
-public class PriceData {
- /** 收盘价 */
- private BigDecimal close;
-
- /** 短期指数移动平均线(通常为12周期) */
- private BigDecimal emaShort;
-
- /** 长期指数移动平均线(通常为26周期) */
- private BigDecimal emaLong;
-
- /** DIF值(短期EMA与长期EMA的差值) */
- private BigDecimal dif;
-
- /** DEA值(DIF的移动平均线,通常为9周期EMA) */
- private BigDecimal dea;
-
- /** MACD柱状图值(DIF与DEA的差值) */
- private BigDecimal macdHist;
-
- /**
- * 构造函数,创建价格数据对象
- *
- * @param close 收盘价
- */
- public PriceData(BigDecimal close) {
- this.close = close;
- }
-
- /**
- * 获取收盘价
- *
- * @return 收盘价
- */
- public BigDecimal getClose() {
- return close;
- }
-
- /**
- * 设置收盘价
- *
- * @param close 收盘价
- */
- public void setClose(BigDecimal close) {
- this.close = close;
- }
-
- /**
- * 获取短期EMA值
- *
- * @return 短期EMA值
- */
- public BigDecimal getEmaShort() {
- return emaShort;
- }
-
- /**
- * 设置短期EMA值
- *
- * @param emaShort 短期EMA值
- */
- public void setEmaShort(BigDecimal emaShort) {
- this.emaShort = emaShort;
- }
-
- /**
- * 获取长期EMA值
- *
- * @return 长期EMA值
- */
- public BigDecimal getEmaLong() {
- return emaLong;
- }
-
- /**
- * 设置长期EMA值
- *
- * @param emaLong 长期EMA值
- */
- public void setEmaLong(BigDecimal emaLong) {
- this.emaLong = emaLong;
- }
-
- /**
- * 获取DIF值
- *
- * @return DIF值
- */
- public BigDecimal getDif() {
- return dif;
- }
-
- /**
- * 设置DIF值
- *
- * @param dif DIF值
- */
- public void setDif(BigDecimal dif) {
- this.dif = dif;
- }
-
- /**
- * 获取DEA值
- *
- * @return DEA值
- */
- public BigDecimal getDea() {
- return dea;
- }
-
- /**
- * 设置DEA值
- *
- * @param dea DEA值
- */
- public void setDea(BigDecimal dea) {
- this.dea = dea;
- }
-
- /**
- * 获取MACD柱状图值
- *
- * @return MACD柱状图值
- */
- public BigDecimal getMacdHist() {
- return macdHist;
- }
-
- /**
- * 设置MACD柱状图值
- *
- * @param macdHist MACD柱状图值
- */
- public void setMacdHist(BigDecimal macdHist) {
- this.macdHist = macdHist;
- }
-
- /**
- * 转换为字符串表示
- *
- * @return 格式化的字符串表示
- */
- @Override
- public String toString() {
- return String.format("PriceData{close=%.2f, EMA_short=%.2f, EMA_long=%.2f, DIF=%.2f, DEA=%.2f, MACD_hist=%.2f}",
- close.doubleValue(), emaShort.doubleValue(), emaLong.doubleValue(),
- dif.doubleValue(), dea.doubleValue(), macdHist.doubleValue());
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/Volatility.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/Volatility.java
deleted file mode 100644
index f4d1543..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/Volatility.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/**
- * 波动率指标计算类
- * <p>
- * 波动率是衡量金融市场价格波动程度的指标,通常用价格的标准差与平均值的比率表示。
- * 本类实现了基于滚动窗口的波动率计算,通过标准差与平均值的比值计算出百分比形式的波动率。
- */
-package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * 波动率指标实现类
- * <p>波动率计算原理:使用标准差与平均值的比值,以百分比形式表示价格的波动程度。</p>
- * <p>计算公式:波动率 = (标准差 / 平均值) * 100%</p>
- *
- * <p>使用示例:</p>
- * <pre>
- * // 初始化20日波动率计算器
- * Volatility vol = new Volatility(20);
- *
- * // 动态添加每日价格
- * priceFeed.subscribe(price -> {
- * vol.addPrice(price);
- * vol.calculate();
- * });
- *
- * // 判断是否满足低波动条件(<1%)
- * if (vol.getValue().compareTo(new BigDecimal("1.00")) < 0) {
- * System.out.println("低波动市场,暂停交易");
- * }
- * </pre>
- */
-public class Volatility {
- /** 波动率计算的周期(如20日波动率) */
- private final int period;
-
- /** 当前计算出的波动率值(百分比形式) */
- private BigDecimal volatility = BigDecimal.ZERO;
-
- /** 使用LinkedList存储滚动窗口内的价格数据,便于添加和删除操作 */
- private LinkedList<BigDecimal> priceWindow = new LinkedList<>();
-
- /** 窗口内价格的总和,用于快速计算平均值 */
- private BigDecimal sum = BigDecimal.ZERO;
-
- /** 窗口内价格平方的总和,用于快速计算方差 */
- private BigDecimal sumSquares = BigDecimal.ZERO;
-
- /**
- * 构造函数,创建指定周期的波动率计算器
- *
- * @param period 波动率计算周期,如20表示计算20日波动率
- */
- public Volatility(int period) {
- this.period = period;
- }
-
- /**
- * 添加新价格到计算窗口,并维护窗口内的价格数据
- * 采用滑动窗口方式,当价格数量超过周期时,自动移除最早的价格
- *
- * @param price 新的价格数据,使用BigDecimal确保计算精度
- * @throws IllegalArgumentException 当价格为null时抛出异常
- */
- public void addPrice(BigDecimal price) {
- if (price == null) {
- throw new IllegalArgumentException("Price cannot be null");
- }
-
- // 当窗口大小达到周期时,移除最早的价格,并从总和中减去
- if (priceWindow.size() == period) {
- BigDecimal removed = priceWindow.removeFirst();
- sum = sum.subtract(removed);
- sumSquares = sumSquares.subtract(removed.pow(2));
- }
-
- // 添加新价格到窗口,并更新总和
- priceWindow.add(price);
- sum = sum.add(price);
- sumSquares = sumSquares.add(price.pow(2));
- }
-
- /**
- * 计算当前窗口内价格的波动率
- * 使用标准差与平均值的比值计算波动率百分比
- */
- public void calculate() {
- // 数据点不足,无法计算波动率
- if (priceWindow.size() < period) {
- return;
- }
-
- // 计算平均值:sum / period
- BigDecimal avg = sum.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
-
- // 防止除以零的情况
- if (avg.compareTo(BigDecimal.ZERO) == 0) {
- volatility = BigDecimal.ZERO;
- return;
- }
-
- // 计算方差:(sumSquares / period) - avg^2
- BigDecimal variance = sumSquares.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP)
- .subtract(avg.pow(2));
-
- // 确保方差非负(防止浮点数计算误差导致负数方差)
- variance = variance.max(BigDecimal.ZERO);
-
- // 计算标准差:sqrt(variance)
- BigDecimal stdDev = sqrt(variance, 8);
-
- // 计算波动率:(标准差 / 平均值) * 100%
- volatility = stdDev.divide(avg, 8, RoundingMode.HALF_UP)
- .multiply(new BigDecimal(100))
- .setScale(2, RoundingMode.HALF_UP);
- }
-
- /**
- * 计算BigDecimal的平方根(使用牛顿迭代法)
- *
- * @param value 要计算平方根的数值
- * @param scale 结果的精度(小数位数)
- * @return 平方根结果
- */
- private BigDecimal sqrt(BigDecimal value, int scale) {
- // 负数没有实数平方根,返回0
- if (value.compareTo(BigDecimal.ZERO) < 0) {
- return BigDecimal.ZERO;
- }
-
- // 使用牛顿迭代法计算平方根
- BigDecimal x = value.divide(new BigDecimal(2), scale, RoundingMode.HALF_UP); // 初始猜测值
- BigDecimal prev;
-
- do {
- prev = x;
- // 牛顿迭代公式:x(n+1) = (x(n) + value/x(n))/2
- // 添加零检查,防止除以零异常
- if (x.compareTo(BigDecimal.ZERO) == 0) {
- x = new BigDecimal(1); // 设置一个合理的初始值
- }
- x = x.add(value.divide(x, scale, RoundingMode.HALF_UP)).divide(new BigDecimal(2), scale, RoundingMode.HALF_UP);
- } while (x.subtract(prev).abs().compareTo(BigDecimal.ONE.movePointLeft(scale)) > 0); // 直到满足精度要求
-
- return x;
- }
-
- /**
- * 获取最新的波动率计算结果
- *
- * @return 波动率值,以百分比形式表示(例如:2.5表示2.5%)
- */
- public BigDecimal getValue() {
- return volatility;
- }
-}
\ No newline at end of file
diff --git "a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/\345\206\205\345\256\271" "b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/\345\206\205\345\256\271"
deleted file mode 100644
index 16e57d4..0000000
--- "a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/\345\206\205\345\256\271"
+++ /dev/null
@@ -1,107 +0,0 @@
-MACD 是什麼?三個核心組成介紹
-MACD 全名為 Moving Average Convergence Divergence,中文為平滑異同移動平均線,它主要是透過快線 DIF、慢線 DEA、能量柱 Histogram三個成分組成,用以幫助投資者捕捉股價或資產價格的趨勢變化、動能強弱以及潛在的買賣訊號。
-
-MACD
-
-MACD 指標主要由三個核心構成,每個核心皆有各自的涵義,讓投資者對於市場動態清楚明瞭:
-
-快線 DIF(DIFferential Line)-差離值
-
-DIF 是 MACD 的核心快線,是 MACD 中主要用來判斷市場長短期趨勢差異的工具,DIF 波動較快的特性也使得投資者可以透過其捕捉更敏銳的短期變化,當 DIF 上漲時表示短期動能增強,反之則減弱。
-
-快線 DIF(DIFferential Line)
-
-慢線 DEA(DIFference Exponential Average)-訊號線
-
-慢線 DEA 又稱為 Signal 訊號線,曲線通常較為平滑,能夠用以輔佐投資者過濾 DIF 的雜訊,提供更穩定的趨勢確認訊號。當 DIF 穿越 DEA 向上時,常被視作買入訊號(黃金交叉),反之則為賣出訊號(死亡交叉)。
-
-慢線 DEA(DIFference Exponential Average)-訊號線
-
-能量柱(Histogram)
-
-Histogram 是 MACD 的柱狀圖部分,由快線 DIF 減去慢線 DEA 得來,柱狀圖的高度反映快線與慢線的差距,當正柱狀圖(零軸之上)愈來愈高時表示多頭動能增強,負柱狀圖(零軸之下)愈來愈低時表示空頭動能增強,而柱狀圖逐漸收斂時則可能表示趨勢即將反轉。
-
-能量柱(Histogram)
-
-MACD公式計算詳細解析
-要深入理解 MACD 指標首先必須先掌握其數值的算法並得知運算結果,MACD 的計算主要基於指數移動平均線(EMA),並通過一系列簡單的數學公式計算得出 DIF、DEA 和柱狀圖。
-
-EMA(Exponential Moving Average)介紹
-
-EMA 中文為指數移動平均線,與簡單移動平均線(SMA)不同的是,EMA 對近期價格數據給予更高的權重,使得它對價格變化的反應更為靈敏。
-
-EMA 的運算公式為:【今日收盤價 × α】 + 【昨日 EMA × (1 − α)】
-
-其中,α(平滑因子)= 2/(N + 1),N 為選定的周期數。例如,12 期 EMA 的 α = 2/(12 + 1) ≈ 0.1538。初次計算 EMA 時,若無前一日的 EMA,可使用該周期的簡單移動平均(SMA)作為起點。EMA 的靈敏性使 MACD 更能快速反映市場動態。
-
-EMA
-
-快線 DIF 計算方式
-
-DIF 作為 MACD 核心之一,計算方式為:EMA(12)-EMA(26)。
-
-EMA 12 代表短期價格趨勢(較快反應),而 EMA 26 代表長期趨勢(較平滑)。當短期 EMA 高於長期 EMA 時,DIF 為正值,表示短期動能強於長期動能,暗示看漲;反之,DIF 為負值則暗示看跌。這之間的差值能幫助投資者快速判斷趨勢的強弱與方向。
-
-慢線 DEA 計算方式
-
-DEA 計算方式為:EMA(DIF,9)
-
-這樣的計算方式將 DIF 的曲線進一步平滑,減少短期價格噪音的干擾,提供更穩定的趨勢指標。DEA 線的作用在於與 DIF 形成交叉訊號,例如 DIF 上穿 DEA 線(黃金交叉)通常被視為買入訊號,而下穿(死亡交叉)則為賣出訊號。
-
-柱狀圖 Histogram 計算方式
-
-Histogram 作為 MACD 指標唯一的圖形,計算方式為:DIF − DEA
-
-柱狀圖直觀顯示快線與慢線的差距,當 DIF 大於 DEA 時,柱狀圖為正時,表示多頭動能增強,反之柱狀圖為負時,表示空頭力量占優。
-
-MACD 公式實盤範例解析
-為了更直觀的理解 MACD 在實盤上的計算過程,我們以幣安的 ETHUSDT 圖表進行 MACD 試算。
-
-第一步驟:新增 EMA 指標
-
-由於 MACD 中的快線、慢線計算過程中皆會使用到 EMA 指標,因此我們可以直接新增 EMA 指標並進行參數調整即可計算 MACD 相關參數。
-
-第二步驟:計算 MACD 中的快線 DIF
-
-快線 DIF 的計算方式為 EMA(12)-EMA(26),因此我們只要將兩條 EMA 指標分別設定為 12、26 並進行相減即可,例如下圖中 EMA 12 為 4271.55,EMA 26 為 3941.88,相減即得 329.67,這一數字與 MACD 中顯示的快線 DIF 相同。
-
-第三步驟:計算 MACD 中的慢線 DEA
-
-慢線的公式為:EMA(DIF,9),其中 9 代表周期數,EMA 的計算使用平滑因子 α = 2 ÷ (9 + 1) = 0.2,因此 MACD 中的慢線 DEA 計算公式為:【今日 DIF*0.2】+【昨日 DEA*0.8】。
-
-今日 DIF 為 329.67,而昨日 DEA 為 250.86,套入計算公式 329.67*0.2+250.86*0.8=266.62,這一數字同樣與 MACD 中顯示的慢線 DEA 相同。
-
-第四步驟:計算 MACD 中的柱狀圖 Histogram
-
-只要解出快線及慢線,要求出 MACD 中的柱狀圖就十分容易,僅需將快線減去慢線即可。上述計算出快線 DIF 數值為 329.67,而慢線 DEA 數值為 266.62,相減即可得出柱狀圖為 63.05,如此一來就得出 MACD 指標所需的所有數據。
-
-MACD公式
-
-MACD黃金/死亡交叉案例
-黃金交叉與死亡交叉是 MACD 指標中最為人所知的交易訊號,許多交易者會藉由黃金交叉與死亡交叉判斷行情趨勢。
-
-首先我們可以觀察到 ETHUSDT 在 4 月 13 號時快線 DIF 上穿慢線 DEA 形成有效金叉後,接下來不斷上漲,甚至直接開啟以太坊牛市。
-
-MACD金叉案例
-
-而再將時間往回推,可以看到在 2024 年 12 月 9 號時 MACD 快線下穿慢線形成死叉,並在接下來回撤超過 60%,當時若觀察到 MACD 為死叉的投資者即可避免該次下跌甚至進行空單布局。
-
-MACD死叉範例
-
-常見問題
-為什麼有的版本能量柱( Histogram) 會*2?
-
-有些版本的 MACD 會將柱狀圖放大一倍,目的是為了讓視覺效果更明顯,投資者即可更直接的觀察快線與慢線之間的差距變化。
-
-金叉、死叉一定會上漲、下跌嗎?
-
-不一定,金融市場中並沒有哪一個指標可以保證上漲、下跌,MACD 也不例外。且包括 MACD 在內的所有指標皆具有一定的延遲性,其中可能包含雜訊,因此建議搭配技術分析或其他指標一同使用。
-
-MACD 的參數可以修改嗎?
-
-MACD 的參數是可以隨個人喜好修改的,例如(5, 13, 5)適用於短期交易,(50, 200, 20) 則適合長期趨勢分析,不過修改 MACD 參數會影響指標的靈敏度,建議回測歷史數據以找到最適合的設定。
-
-小結
-本篇文章解析了 MACD 的核心,從 MACD 的計算基礎 EMA 平均線到快線、慢線和柱狀圖三大核心,以及結合 ETHUSDT 圖表展示了 EMA 推導 MACD 數值的計算過程,但指標真正的價值在於如何靈活應用並提升勝率。因此看完這篇文章的你如果對於 MACD 有濃厚興趣的話,趕快打開 MACD 指標並進行深入的覆盤研究吧!
-
-本報告僅供資訊分享之用,內容不構成任何形式的投資建議或決策依據。文中所引用的數據、分析與觀點均基於作者的研究與公開來源,可能存在不確定性或隨時變動的情況。讀者應根據自身情況及風險承受能力,審慎進行投資判斷。如需進一步指導,建議尋求專業顧問意見。
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/AbstractTechnicalIndicatorStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/AbstractTechnicalIndicatorStrategy.java
deleted file mode 100644
index a853473..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/AbstractTechnicalIndicatorStrategy.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator.strategy;
-
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 技术指标策略抽象基类,提供通用功能
- */
-@Slf4j
-@Getter
-@Setter
-public abstract class AbstractTechnicalIndicatorStrategy implements TechnicalIndicatorStrategy {
-
- protected static final int MAX_PRICE_HISTORY = 100; // 最大价格历史记录数量
- protected List<BigDecimal> priceHistory = new ArrayList<>();
- protected boolean initialized = false;
- protected String strategyName = ""; // 策略名称
-
- @Override
- public void initialize() {
- priceHistory.clear();
- initialized = true;
- log.info("策略初始化完成: {}", strategyName);
- }
-
- @Override
- public void updatePrices(List<BigDecimal> prices) {
- if (!initialized) {
- initialize();
- }
-
- if (prices == null || prices.isEmpty()) {
- return;
- }
-
- // 更新价格历史记录
- priceHistory.addAll(prices);
-
- // 限制价格历史记录数量
- if (priceHistory.size() > MAX_PRICE_HISTORY) {
- priceHistory = priceHistory.subList(priceHistory.size() - MAX_PRICE_HISTORY, priceHistory.size());
- }
-
- log.debug("价格历史记录更新完成,当前数量: {}", priceHistory.size());
- }
-
- @Override
- public boolean isValid() {
- return initialized;
- }
-
- /**
- * 创建交易请求参数
- * @param accountName 账户名称
- * @param markPx 当前标记价格
- * @param posSide 仓位方向
- * @param signal 交易信号
- * @return 交易请求参数
- */
- protected TradeRequestParam createTradeRequestParam(String accountName, String markPx, String posSide, TradeSignal signal) {
- TradeRequestParam param = new TradeRequestParam();
- param.setAccountName(accountName);
- param.setMarkPx(markPx);
- param.setPosSide(posSide);
-
- log.info("账户: {}, 价格: {}, 仓位方向: {}, 信号: {}",
- accountName, markPx, posSide, signal.getName());
-
- return param;
- }
-
- /**
- * 日志记录交易信号
- * @param accountName 账户名称
- * @param markPx 当前标记价格
- * @param signal 交易信号
- */
- protected void logSignal(String accountName, String markPx, TradeSignal signal) {
- log.info("策略: {}, 账户: {}, 价格: {}, 信号: {}",
- strategyName, accountName, markPx, signal.getName());
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/ComprehensiveTechnicalStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/ComprehensiveTechnicalStrategy.java
deleted file mode 100644
index a6c320c..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/ComprehensiveTechnicalStrategy.java
+++ /dev/null
@@ -1,351 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator.strategy;
-
-import com.xcong.excoin.modules.okxNewPrice.indicator.BOLL;
-import com.xcong.excoin.modules.okxNewPrice.indicator.KDJ;
-import com.xcong.excoin.modules.okxNewPrice.indicator.MACD;
-import com.xcong.excoin.modules.okxNewPrice.indicator.RSI;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-/**
- * 综合技术指标策略实现类,整合MACD、KDJ、RSI、BOLL等指标生成交易信号
- */
-@Slf4j
-public class ComprehensiveTechnicalStrategy extends AbstractTechnicalIndicatorStrategy {
-
- private final MACD macd;
- private final KDJ kdj;
- private final RSI rsi;
- private final BOLL boll;
-
- private BigDecimal prevDif;
- private BigDecimal prevDea;
- private BigDecimal prevK;
- private BigDecimal prevD;
-
- public ComprehensiveTechnicalStrategy() {
- super();
- this.strategyName = "综合技术指标策略";
- this.macd = new MACD();
- this.kdj = new KDJ();
- this.rsi = new RSI();
- this.boll = new BOLL();
- this.prevDif = BigDecimal.ZERO;
- this.prevDea = BigDecimal.ZERO;
- this.prevK = new BigDecimal(50);
- this.prevD = new BigDecimal(50);
- }
-
- @Override
- public void initialize() {
- super.initialize();
- macd.setDif(BigDecimal.ZERO);
- macd.setDea(BigDecimal.ZERO);
- macd.setMacdBar(BigDecimal.ZERO);
- macd.setPrevFastEMA(null);
- macd.setPrevSlowEMA(null);
- macd.setPrevDea(null);
-
- kdj.setK(new BigDecimal(50));
- kdj.setD(new BigDecimal(50));
- kdj.setJ(new BigDecimal(50));
- kdj.setPrevK(new BigDecimal(50));
- kdj.setPrevD(new BigDecimal(50));
-
- rsi.setRsi(BigDecimal.ZERO);
- rsi.setPrevAvgGain(BigDecimal.ZERO);
- rsi.setPrevAvgLoss(BigDecimal.ZERO);
-
- boll.setMid(BigDecimal.ZERO);
- boll.setUpper(BigDecimal.ZERO);
- boll.setLower(BigDecimal.ZERO);
-
- prevDif = BigDecimal.ZERO;
- prevDea = BigDecimal.ZERO;
- prevK = new BigDecimal(50);
- prevD = new BigDecimal(50);
-
- log.info("综合技术指标策略初始化完成");
- }
-
- @Override
- public TradeRequestParam getSignal(String accountName, String markPx, String posSide) {
- if (!initialized || priceHistory.isEmpty()) {
- log.warn("策略未初始化或价格历史为空");
- return createTradeRequestParam(accountName, markPx, posSide, TradeSignal.NO_SIGNAL);
- }
-
- try {
- BigDecimal currentPrice = new BigDecimal(markPx);
-
- // 计算所有技术指标
- macd.calculate(priceHistory);
- kdj.calculate(priceHistory);
- rsi.calculate(priceHistory);
- boll.calculate(priceHistory);
-
- // 生成交易信号
- TradeRequestParam param = analyzeSignal(accountName, markPx, posSide, currentPrice);
-
- // 更新历史指标值
- updateHistoricalIndicatorValues();
-
- return param;
- } catch (Exception e) {
- log.error("计算交易信号时发生异常", e);
- return createTradeRequestParam(accountName, markPx, posSide, TradeSignal.NO_SIGNAL);
- }
- }
-
- /**
- * 分析技术指标生成交易信号
- */
- private TradeRequestParam analyzeSignal(String accountName, String markPx, String posSide, BigDecimal currentPrice) {
- TradeRequestParam param = createTradeRequestParam(accountName, markPx, posSide, TradeSignal.NO_SIGNAL);
- param.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- param.setInstId(CoinEnums.HE_YUE.getCode());
- param.setTdMode(CoinEnums.CROSS.getCode());
- param.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
-
- // 检查冷静期
- String outStr = getAccountConfig(accountName, CoinEnums.OUT.name());
- if (OrderParamEnums.OUT_YES.getValue().equals(outStr)) {
- log.error("冷静中,不允许下单......");
- param.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return param;
- }
-
- TradeSignal signal = TradeSignal.NO_SIGNAL;
-
- // 根据多空方向分别分析信号
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)) {
- signal = analyzeLongSignal(currentPrice, posSide);
- } else if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)) {
- signal = analyzeShortSignal(currentPrice, posSide);
- } else {
- // 如果没有指定仓位方向,同时分析多头和空头信号
- TradeSignal longSignal = analyzeLongSignal(currentPrice, posSide);
- TradeSignal shortSignal = analyzeShortSignal(currentPrice, posSide);
-
- // 优先选择非NO_SIGNAL的信号
- if (longSignal != TradeSignal.NO_SIGNAL) {
- signal = longSignal;
- } else {
- signal = shortSignal;
- }
- }
-
- log.info("账户: {}, 价格: {}, 方向: {}, 生成信号: {}",
- accountName, markPx, posSide, signal.getName());
-
- // 设置信号参数
- setSignalParameters(param, signal);
-
- return param;
- }
-
- /**
- * 分析多头信号
- */
- private TradeSignal analyzeLongSignal(BigDecimal currentPrice, String posSide) {
- // 检查超卖条件
- if (rsi.isOversold() || rsi.isExtremelyOversold()) {
- // KDJ超卖且金叉
- if ((kdj.isOversold() && kdj.isGoldenCross()) ||
- (boll.isBreakLower(currentPrice) && macd.isGoldenCross(prevDif, prevDea))) {
- // 如果当前没有仓位,则开多
- if (posSide == null || posSide.isEmpty()) {
- return TradeSignal.OPEN_LONG;
- }
- // 如果当前是空头仓位,则平空
- if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)) {
- return TradeSignal.CLOSE_SHORT;
- }
- // 如果当前已经是多头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
- }
-
- // 检查超买条件
- if (rsi.isOverbought() || rsi.isExtremelyOverbought()) {
- // KDJ超买且死叉
- if ((kdj.isOverbought() && kdj.isDeathCross()) ||
- (boll.isBreakUpper(currentPrice) && macd.isDeathCross(prevDif, prevDea))) {
- // 如果当前是多头仓位,则平多
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)) {
- return TradeSignal.CLOSE_LONG;
- }
- // 如果当前没有仓位,则开空
- if (posSide == null || posSide.isEmpty()) {
- return TradeSignal.OPEN_SHORT;
- }
- // 如果当前已经是空头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
- }
-
- // 检查MACD金叉死叉
- if (macd.isGoldenCross(prevDif, prevDea)) {
- // 如果当前没有仓位,则开多
- if (posSide == null || posSide.isEmpty()) {
- return TradeSignal.OPEN_LONG;
- }
- // 如果当前是空头仓位,则平空
- if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)) {
- return TradeSignal.CLOSE_SHORT;
- }
- // 如果当前已经是多头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- } else if (macd.isDeathCross(prevDif, prevDea)) {
- // 如果当前是多头仓位,则平多
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)) {
- return TradeSignal.CLOSE_LONG;
- }
- // 如果当前没有仓位,则开空
- if (posSide == null || posSide.isEmpty()) {
- return TradeSignal.OPEN_SHORT;
- }
- // 如果当前已经是空头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
-
- return TradeSignal.NO_SIGNAL;
- }
-
- /**
- * 分析空头信号
- */
- private TradeSignal analyzeShortSignal(BigDecimal currentPrice, String posSide) {
- // 检查超买条件
- if (rsi.isOverbought() || rsi.isExtremelyOverbought()) {
- // KDJ超买且死叉
- if ((kdj.isOverbought() && kdj.isDeathCross()) ||
- (boll.isBreakUpper(currentPrice) && macd.isDeathCross(prevDif, prevDea))) {
- // 如果当前没有仓位,则开空
- if (posSide == null || posSide.isEmpty()) {
- return TradeSignal.OPEN_SHORT;
- }
- // 如果当前是多头仓位,则平多
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)) {
- return TradeSignal.CLOSE_LONG;
- }
- // 如果当前已经是空头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
- }
-
- // 检查超卖条件
- if (rsi.isOversold() || rsi.isExtremelyOversold()) {
- // KDJ超卖且金叉
- if ((kdj.isOversold() && kdj.isGoldenCross()) ||
- (boll.isBreakLower(currentPrice) && macd.isGoldenCross(prevDif, prevDea))) {
- // 如果当前是空头仓位,则平空
- if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)) {
- return TradeSignal.CLOSE_SHORT;
- }
- // 如果当前没有仓位,则开多
- if (posSide == null || posSide.isEmpty()) {
- return TradeSignal.OPEN_LONG;
- }
- // 如果当前已经是多头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
- }
-
- // 检查MACD金叉死叉
- if (macd.isGoldenCross(prevDif, prevDea)) {
- // 如果当前是空头仓位,则平空
- if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)) {
- return TradeSignal.CLOSE_SHORT;
- }
- // 如果当前没有仓位,则开多
- if (posSide == null || posSide.isEmpty()) {
- return TradeSignal.OPEN_LONG;
- }
- // 如果当前已经是多头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- } else if (macd.isDeathCross(prevDif, prevDea)) {
- // 如果当前没有仓位,则开空
- if (posSide == null || posSide.isEmpty()) {
- return TradeSignal.OPEN_SHORT;
- }
- // 如果当前是多头仓位,则平多
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)) {
- return TradeSignal.CLOSE_LONG;
- }
- // 如果当前已经是空头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
-
- return TradeSignal.NO_SIGNAL;
- }
-
- /**
- * 设置信号参数
- */
- private void setSignalParameters(TradeRequestParam param, TradeSignal signal) {
- String side = null;
-
- switch (signal) {
- case BUY:
- side = CoinEnums.SIDE_BUY.getCode();
- break;
- case SELL:
- side = CoinEnums.SIDE_SELL.getCode();
- break;
- case OPEN_LONG:
- side = CoinEnums.SIDE_BUY.getCode();
- break;
- case CLOSE_LONG:
- side = CoinEnums.SIDE_SELL.getCode();
- break;
- case OPEN_SHORT:
- side = CoinEnums.SIDE_SELL.getCode();
- break;
- case CLOSE_SHORT:
- side = CoinEnums.SIDE_BUY.getCode();
- break;
- case STOP_LOSS:
- // 止损操作,根据当前仓位方向决定买卖方向
- side = CoinEnums.POSSIDE_LONG.getCode().equals(param.getPosSide()) ?
- CoinEnums.SIDE_SELL.getCode() : CoinEnums.SIDE_BUY.getCode();
- break;
- default:
- param.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return;
- }
-
- param.setSide(side);
- log.info("设置交易方向: {}", side);
- }
-
- /**
- * 更新历史指标值
- */
- private void updateHistoricalIndicatorValues() {
- prevDif = macd.getDif();
- prevDea = macd.getDea();
- prevK = kdj.getK();
- prevD = kdj.getD();
- }
-
- /**
- * 获取账户配置信息
- */
- private String getAccountConfig(String accountName, String key) {
- try {
- // 这里需要根据实际情况获取账户配置信息
- // 暂时返回默认值,实际应该从InstrumentsWs或其他配置类中获取
- return "NO";
- } catch (Exception e) {
- log.error("获取账户配置信息失败", e);
- return "NO";
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/CoreTechnicalStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/CoreTechnicalStrategy.java
deleted file mode 100644
index 61469c5..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/CoreTechnicalStrategy.java
+++ /dev/null
@@ -1,589 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator.strategy;
-
-import com.xcong.excoin.modules.okxNewPrice.indicator.AdvancedMA;
-import com.xcong.excoin.modules.okxNewPrice.indicator.BOLL;
-import com.xcong.excoin.modules.okxNewPrice.indicator.KDJ;
-import com.xcong.excoin.modules.okxNewPrice.indicator.MACD;
-import com.xcong.excoin.modules.okxNewPrice.indicator.RSI;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 核心技术指标策略实现类
- * 整合三重EMA交叉系统、波动率自适应布林带、MACD能量柱分级策略等核心指标
- */
-@Slf4j
-public class CoreTechnicalStrategy extends AbstractTechnicalIndicatorStrategy {
-
- private static final int MACD_HISTOGRAM_PERIOD = 5; // MACD能量柱计算周期
- private static final BigDecimal VOLUME_MULTIPLIER = new BigDecimal(3); // 成交量放大倍数
- private static final BigDecimal GLUE_THRESHOLD = new BigDecimal(2); // 三线粘合度阈值(%)
-
- private final AdvancedMA advancedMA;
- private final BOLL boll;
- private final MACD macd;
- private final RSI rsi;
- private final KDJ kdj;
-
- private List<BigDecimal> macdHistogramHistory; // MACD能量柱历史
- private BigDecimal prevEma9;
- private BigDecimal prevEma21;
- private BigDecimal prevEma55;
- private BigDecimal prevDif;
- private BigDecimal prevDea;
- private BigDecimal prevK;
- private BigDecimal prevD;
- private BigDecimal prevJ;
-
- public CoreTechnicalStrategy() {
- super();
- this.strategyName = "核心技术指标策略";
- this.advancedMA = new AdvancedMA();
- this.boll = new BOLL();
- this.macd = new MACD();
- this.rsi = new RSI();
- this.kdj = new KDJ();
- this.macdHistogramHistory = new ArrayList<>();
- this.prevEma9 = BigDecimal.ZERO;
- this.prevEma21 = BigDecimal.ZERO;
- this.prevEma55 = BigDecimal.ZERO;
- this.prevDif = BigDecimal.ZERO;
- this.prevDea = BigDecimal.ZERO;
- this.prevK = new BigDecimal(50);
- this.prevD = new BigDecimal(50);
- this.prevJ = new BigDecimal(50);
- }
-
- @Override
- public void initialize() {
- super.initialize();
- macdHistogramHistory.clear();
-
- // 初始化技术指标
- advancedMA.setPrevEma9(null);
- advancedMA.setPrevEma21(null);
- advancedMA.setPrevEma55(null);
-
- macd.setDif(BigDecimal.ZERO);
- macd.setDea(BigDecimal.ZERO);
- macd.setMacdBar(BigDecimal.ZERO);
- macd.setPrevFastEMA(null);
- macd.setPrevSlowEMA(null);
- macd.setPrevDea(null);
-
- rsi.setRsi(BigDecimal.ZERO);
- rsi.setPrevAvgGain(BigDecimal.ZERO);
- rsi.setPrevAvgLoss(BigDecimal.ZERO);
-
- kdj.setK(new BigDecimal(50));
- kdj.setD(new BigDecimal(50));
- kdj.setJ(new BigDecimal(50));
- kdj.setPrevK(new BigDecimal(50));
- kdj.setPrevD(new BigDecimal(50));
-
- boll.setMid(BigDecimal.ZERO);
- boll.setUpper(BigDecimal.ZERO);
- boll.setLower(BigDecimal.ZERO);
-
- prevEma9 = BigDecimal.ZERO;
- prevEma21 = BigDecimal.ZERO;
- prevEma55 = BigDecimal.ZERO;
- prevDif = BigDecimal.ZERO;
- prevDea = BigDecimal.ZERO;
- prevK = new BigDecimal(50);
- prevD = new BigDecimal(50);
- prevJ = new BigDecimal(50);
-
- log.info("核心技术指标策略初始化完成");
- }
-
- @Override
- public TradeRequestParam getSignal(String accountName, String markPx, String posSide) {
- if (!initialized || priceHistory.isEmpty()) {
- log.warn("策略未初始化或价格历史为空");
- return createTradeRequestParam(accountName, markPx, posSide, TradeSignal.NO_SIGNAL);
- }
-
- try {
- BigDecimal currentPrice = new BigDecimal(markPx);
-
- // 计算所有技术指标
- calculateIndicators(currentPrice);
-
- // 生成交易信号
- TradeRequestParam param = analyzeSignal(accountName, markPx, posSide, currentPrice);
-
- // 更新历史指标值
- updateHistoricalIndicatorValues();
-
- return param;
- } catch (Exception e) {
- log.error("计算交易信号时发生异常", e);
- return createTradeRequestParam(accountName, markPx, posSide, TradeSignal.NO_SIGNAL);
- }
- }
-
- /**
- * 计算所有技术指标
- */
- private void calculateIndicators(BigDecimal currentPrice) {
- // 计算三重EMA交叉系统
- advancedMA.calculateTripleEMA(priceHistory);
-
- // 计算MACD
- macd.calculate(priceHistory);
-
- // 维护MACD能量柱历史
- updateMacdHistogramHistory();
-
- // 计算RSI
- rsi.calculate(priceHistory);
-
- // 计算KDJ
- kdj.calculate(priceHistory);
-
- // 计算波动率自适应布林带
- calculateAdaptiveBollingerBands();
- }
-
- /**
- * 计算波动率自适应布林带
- */
- private void calculateAdaptiveBollingerBands() {
- // 动态调整布林带的标准差倍数
- BigDecimal atr = calculateATR(priceHistory, 14);
-
- // 根据ATR动态计算标准差倍数
- BigDecimal stdDevMultiplier;
- if (atr.compareTo(new BigDecimal(0.5)) < 0) {
- stdDevMultiplier = new BigDecimal(2);
- } else if (atr.compareTo(new BigDecimal(1)) < 0) {
- stdDevMultiplier = new BigDecimal(2.5);
- } else {
- stdDevMultiplier = new BigDecimal(3);
- }
-
- boll.setK(stdDevMultiplier);
- boll.calculate(priceHistory);
-
- log.debug("ATR: {}, 布林带标准差倍数: {}", atr, stdDevMultiplier);
- }
-
- /**
- * 计算ATR (平均真实范围)
- */
- private BigDecimal calculateATR(List<BigDecimal> prices, int period) {
- if (prices == null || prices.size() < period + 1) {
- return new BigDecimal(0.5); // 默认值
- }
-
- List<BigDecimal> trList = new ArrayList<>();
-
- for (int i = 1; i < prices.size(); i++) {
- BigDecimal high = prices.get(i);
- BigDecimal low = prices.get(i);
- BigDecimal prevClose = prices.get(i - 1);
-
- BigDecimal tr1 = high.subtract(low);
- BigDecimal tr2 = high.subtract(prevClose).abs();
- BigDecimal tr3 = prevClose.subtract(low).abs();
-
- trList.add(tr1.max(tr2).max(tr3));
- }
-
- // 计算ATR
- BigDecimal sum = trList.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
- return sum.divide(new BigDecimal(trList.size()), 8, RoundingMode.HALF_UP);
- }
-
- /**
- * 更新MACD能量柱历史
- */
- private void updateMacdHistogramHistory() {
- macdHistogramHistory.add(macd.getMacdBar());
-
- // 限制历史记录数量
- if (macdHistogramHistory.size() > MACD_HISTOGRAM_PERIOD) {
- macdHistogramHistory = macdHistogramHistory.subList(
- macdHistogramHistory.size() - MACD_HISTOGRAM_PERIOD,
- macdHistogramHistory.size());
- }
- }
-
- /**
- * 计算MACD能量柱面积指标(累计过去5根柱体积分)
- */
- private BigDecimal calculateMacdHistogramArea() {
- return macdHistogramHistory.stream()
- .reduce(BigDecimal.ZERO, BigDecimal::add)
- .abs(); // 使用绝对值表示面积
- }
-
- /**
- * 分析技术指标生成交易信号
- */
- private TradeRequestParam analyzeSignal(String accountName, String markPx, String posSide, BigDecimal currentPrice) {
- TradeRequestParam param = createTradeRequestParam(accountName, markPx, posSide, TradeSignal.NO_SIGNAL);
- param.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- param.setInstId(CoinEnums.HE_YUE.getCode());
- param.setTdMode(CoinEnums.CROSS.getCode());
- param.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
-
- // 检查冷静期
- String outStr = getAccountConfig(accountName, CoinEnums.OUT.name());
- if (OrderParamEnums.OUT_YES.getValue().equals(outStr)) {
- log.error("冷静中,不允许下单......");
- param.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return param;
- }
-
- // 检查震荡行情
- if (advancedMA.isUpAndDown()) {
- log.info("处于震荡行情(三线粘合度<{}%),暂停交易", GLUE_THRESHOLD);
- param.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return param;
- }
-
- TradeSignal signal = TradeSignal.NO_SIGNAL;
-
- // 分析多空信号
- signal = analyzeCoreSignal(currentPrice, posSide);
-
- log.info("账户: {}, 价格: {}, 方向: {}, 生成信号: {}",
- accountName, markPx, posSide, signal.getName());
-
- // 设置信号参数
- setSignalParameters(param, signal);
-
- return param;
- }
-
- /**
- * 分析核心技术指标信号
- */
- private TradeSignal analyzeCoreSignal(BigDecimal currentPrice, String posSide) {
- // 计算MACD能量柱面积
- BigDecimal macdArea = calculateMacdHistogramArea();
- BigDecimal prevMacdArea = calculateMacdHistogramAreaPrevious();
-
- // 多头入场条件:当前柱体>0且面积增速>前周期20%
- if (isBullishEntry(macdArea, prevMacdArea)) {
- // 如果当前没有仓位或仓位为空头,则开多
- if (posSide == null || posSide.isEmpty() || CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)) {
- return posSide == null || posSide.isEmpty() ? TradeSignal.OPEN_LONG : TradeSignal.CLOSE_SHORT;
- }
- // 如果当前已经是多头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
-
- // 空头入场条件:当前柱体<0且面积增速>前周期20%
- if (isBearishEntry(macdArea, prevMacdArea)) {
- // 如果当前没有仓位或仓位为多头,则开空
- if (posSide == null || posSide.isEmpty() || CoinEnums.POSSIDE_LONG.getCode().equals(posSide)) {
- return posSide == null || posSide.isEmpty() ? TradeSignal.OPEN_SHORT : TradeSignal.CLOSE_LONG;
- }
- // 如果当前已经是空头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
-
- // 突破上轨+成交量放大3倍=做空信号
- if (isBollingerUpperBreak(currentPrice) && isVolumeIncreased()) {
- // 如果当前没有仓位或仓位为多头,则开空
- if (posSide == null || posSide.isEmpty() || CoinEnums.POSSIDE_LONG.getCode().equals(posSide)) {
- return posSide == null || posSide.isEmpty() ? TradeSignal.OPEN_SHORT : TradeSignal.CLOSE_LONG;
- }
- // 如果当前已经是空头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
-
- // 触及下轨+期货资金费率转正=做多信号
- if (isBollingerLowerTouch(currentPrice) && isFundingRatePositive()) {
- // 如果当前没有仓位或仓位为空头,则开多
- if (posSide == null || posSide.isEmpty() || CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)) {
- return posSide == null || posSide.isEmpty() ? TradeSignal.OPEN_LONG : TradeSignal.CLOSE_SHORT;
- }
- // 如果当前已经是多头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
-
- // 空头反转:柱体顶背离+RSI>70区域死叉
- if (isBearishReversal(currentPrice)) {
- // 如果当前没有仓位或仓位为多头,则开空或平多
- if (posSide == null || posSide.isEmpty() || CoinEnums.POSSIDE_LONG.getCode().equals(posSide)) {
- return posSide == null || posSide.isEmpty() ? TradeSignal.OPEN_SHORT : TradeSignal.CLOSE_LONG;
- }
- // 如果当前已经是空头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
-
- // 多头反转:柱体底背离+RSI<30区域金叉
- if (isBullishReversal(currentPrice)) {
- // 如果当前没有仓位或仓位为空头,则开多或平空
- if (posSide == null || posSide.isEmpty() || CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)) {
- return posSide == null || posSide.isEmpty() ? TradeSignal.OPEN_LONG : TradeSignal.CLOSE_SHORT;
- }
- // 如果当前已经是多头仓位,则保持观望
- return TradeSignal.NO_SIGNAL;
- }
-
- return TradeSignal.NO_SIGNAL;
- }
-
- /**
- * 多头入场条件判断
- */
- private boolean isBullishEntry(BigDecimal currentArea, BigDecimal prevArea) {
- // 当前柱体>0
- boolean currentBarPositive = macd.getMacdBar().compareTo(BigDecimal.ZERO) > 0;
-
- // 面积增速>前周期20%
- boolean areaGrowth = prevArea != null && prevArea.compareTo(BigDecimal.ZERO) > 0 &&
- currentArea.divide(prevArea, 8, RoundingMode.HALF_UP)
- .compareTo(new BigDecimal(1.2)) > 0;
-
- // 三重EMA多头排列
- boolean emaBullish = advancedMA.isBullish();
-
- return currentBarPositive && areaGrowth && emaBullish;
- }
-
- /**
- * 空头入场条件判断
- */
- private boolean isBearishEntry(BigDecimal currentArea, BigDecimal prevArea) {
- // 当前柱体<0
- boolean currentBarNegative = macd.getMacdBar().compareTo(BigDecimal.ZERO) < 0;
-
- // 面积增速>前周期20%
- boolean areaGrowth = prevArea != null && prevArea.compareTo(BigDecimal.ZERO) > 0 &&
- currentArea.divide(prevArea, 8, RoundingMode.HALF_UP)
- .compareTo(new BigDecimal(1.2)) > 0;
-
- // 三重EMA空头排列
- boolean emaBearish = advancedMA.isBearish();
-
- return currentBarNegative && areaGrowth && emaBearish;
- }
-
- /**
- * 突破上轨判断
- */
- private boolean isBollingerUpperBreak(BigDecimal currentPrice) {
- return currentPrice.compareTo(boll.getUpper()) > 0;
- }
-
- /**
- * 触及下轨判断
- */
- private boolean isBollingerLowerTouch(BigDecimal currentPrice) {
- return currentPrice.compareTo(boll.getLower()) < 0;
- }
-
- /**
- * 成交量放大判断(模拟)
- */
- private boolean isVolumeIncreased() {
- // 这里使用价格波动率模拟成交量
- return calculateVolatility(priceHistory, 5)
- .compareTo(calculateVolatility(priceHistory, 20).multiply(VOLUME_MULTIPLIER)) > 0;
- }
-
- /**
- * 期货资金费率转正判断(模拟)
- */
- private boolean isFundingRatePositive() {
- // 这里使用MACD柱状图模拟资金费率
- return macd.getMacdBar().compareTo(BigDecimal.ZERO) > 0;
- }
-
- /**
- * 计算价格波动率
- */
- private BigDecimal calculateVolatility(List<BigDecimal> prices, int period) {
- if (prices.size() < period) {
- return BigDecimal.ZERO;
- }
-
- List<BigDecimal> returns = new ArrayList<>();
- for (int i = 1; i < prices.size(); i++) {
- BigDecimal current = prices.get(i);
- BigDecimal previous = prices.get(i - 1);
- returns.add(current.divide(previous, 8, RoundingMode.HALF_UP).subtract(BigDecimal.ONE));
- }
-
- return calculateStandardDeviation(returns);
- }
-
- /**
- * 计算标准差
- */
- private BigDecimal calculateStandardDeviation(List<BigDecimal> values) {
- if (values.isEmpty()) {
- return BigDecimal.ZERO;
- }
-
- // 计算平均值
- BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal mean = sum.divide(new BigDecimal(values.size()), 8, RoundingMode.HALF_UP);
-
- // 计算方差
- BigDecimal variance = values.stream()
- .map(val -> val.subtract(mean).pow(2))
- .reduce(BigDecimal.ZERO, BigDecimal::add)
- .divide(new BigDecimal(values.size()), 8, RoundingMode.HALF_UP);
-
- // 计算标准差
- return sqrt(variance);
- }
-
- /**
- * 计算平方根
- */
- private BigDecimal sqrt(BigDecimal value) {
- return new BigDecimal(Math.sqrt(value.doubleValue()));
- }
-
- /**
- * 空头反转判断
- */
- private boolean isBearishReversal(BigDecimal currentPrice) {
- // 柱体顶背离
- boolean topDivergence = isMacdTopDivergence(currentPrice);
-
- // RSI>70区域死叉
- boolean rsiOverbought = rsi.getRsi().compareTo(new BigDecimal(70)) > 0;
- boolean kdjDeathCross = kdj.isDeathCross();
-
- return topDivergence && rsiOverbought && kdjDeathCross;
- }
-
- /**
- * 多头反转判断
- */
- private boolean isBullishReversal(BigDecimal currentPrice) {
- // 柱体底背离
- boolean bottomDivergence = isMacdBottomDivergence(currentPrice);
-
- // RSI<30区域金叉
- boolean rsiOversold = rsi.getRsi().compareTo(new BigDecimal(30)) < 0;
- boolean kdjGoldenCross = kdj.isGoldenCross();
-
- return bottomDivergence && rsiOversold && kdjGoldenCross;
- }
-
- /**
- * 判断MACD顶背离
- */
- private boolean isMacdTopDivergence(BigDecimal currentPrice) {
- // 简化的顶背离判断:价格创新高但MACD未创新高
- if (priceHistory.size() < 2) {
- return false;
- }
-
- BigDecimal previousPrice = priceHistory.get(priceHistory.size() - 2);
- return currentPrice.compareTo(previousPrice) > 0 &&
- macd.getMacdBar().compareTo(prevDea) < 0;
- }
-
- /**
- * 判断MACD底背离
- */
- private boolean isMacdBottomDivergence(BigDecimal currentPrice) {
- // 简化的底背离判断:价格创新低但MACD未创新低
- if (priceHistory.size() < 2) {
- return false;
- }
-
- BigDecimal previousPrice = priceHistory.get(priceHistory.size() - 2);
- return currentPrice.compareTo(previousPrice) < 0 &&
- macd.getMacdBar().compareTo(prevDea) > 0;
- }
-
- /**
- * 计算前一期MACD能量柱面积
- */
- private BigDecimal calculateMacdHistogramAreaPrevious() {
- if (macdHistogramHistory.size() < 2) {
- return BigDecimal.ZERO;
- }
-
- List<BigDecimal> prevHistory = macdHistogramHistory.subList(
- 0, macdHistogramHistory.size() - 1);
-
- return prevHistory.stream()
- .reduce(BigDecimal.ZERO, BigDecimal::add)
- .abs();
- }
-
- /**
- * 设置信号参数
- */
- private void setSignalParameters(TradeRequestParam param, TradeSignal signal) {
- String side = null;
-
- switch (signal) {
- case BUY:
- side = CoinEnums.SIDE_BUY.getCode();
- param.setPosSide(CoinEnums.POSSIDE_LONG.getCode());
- break;
- case SELL:
- side = CoinEnums.SIDE_SELL.getCode();
- param.setPosSide(CoinEnums.POSSIDE_SHORT.getCode());
- break;
- case OPEN_LONG:
- side = CoinEnums.SIDE_BUY.getCode();
- param.setPosSide(CoinEnums.POSSIDE_LONG.getCode());
- break;
- case CLOSE_LONG:
- side = CoinEnums.SIDE_SELL.getCode();
- break;
- case OPEN_SHORT:
- side = CoinEnums.SIDE_SELL.getCode();
- param.setPosSide(CoinEnums.POSSIDE_SHORT.getCode());
- break;
- case CLOSE_SHORT:
- side = CoinEnums.SIDE_BUY.getCode();
- break;
- case STOP_LOSS:
- // 止损操作
- side = CoinEnums.POSSIDE_LONG.getCode().equals(param.getPosSide()) ?
- CoinEnums.SIDE_SELL.getCode() : CoinEnums.SIDE_BUY.getCode();
- break;
- default:
- param.setTradeType(OrderParamEnums.TRADE_NO.getValue());
- return;
- }
-
- param.setSide(side);
- log.info("设置交易方向: {}, 仓位方向: {}", side, param.getPosSide());
- }
-
- /**
- * 更新历史指标值
- */
- private void updateHistoricalIndicatorValues() {
- prevEma9 = advancedMA.getEma9();
- prevEma21 = advancedMA.getEma21();
- prevEma55 = advancedMA.getEma55();
- prevDif = macd.getDif();
- prevDea = macd.getDea();
- prevK = kdj.getK();
- prevD = kdj.getD();
- prevJ = kdj.getJ();
- }
-
- /**
- * 获取账户配置
- */
- private String getAccountConfig(String accountName, String key) {
- // 模拟获取账户配置
- return OrderParamEnums.OUT_NO.getValue();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/CoreTechnicalStrategyUsageGuide.md b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/CoreTechnicalStrategyUsageGuide.md
deleted file mode 100644
index 0e56433..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/CoreTechnicalStrategyUsageGuide.md
+++ /dev/null
@@ -1,198 +0,0 @@
-# CoreTechnicalStrategy 使用指南
-
-## 1. 概述
-CoreTechnicalStrategy 是一个综合技术指标策略类,整合了以下核心技术指标:
-- 三重EMA交叉系统(9/21/55周期)
-- 波动率自适应布林带
-- MACD能量柱分级策略
-
-## 2. 类结构
-
-### 2.1 AdvancedMA 类
-用于计算三重EMA交叉系统的指标。
-
-**主要方法:**
-- `calculateTripleEMA(List<BigDecimal> prices)`: 计算9EMA、21EMA、55EMA
-- `isBullish排列()`: 判断是否多头排列(9EMA>21EMA>55EMA)
-- `isBearish排列()`: 判断是否空头排列(9EMA<21EMA<55EMA)
-- `calculate粘合度()`: 计算三线粘合度
-- `is震荡行情()`: 判断是否处于震荡行情(粘合度<2%)
-
-### 2.2 CoreTechnicalStrategy 类
-综合所有技术指标生成交易信号的策略类。
-
-**主要方法:**
-- `initialize()`: 初始化策略
-- `updatePrices(List<BigDecimal> prices)`: 更新价格数据
-- `getSignal(String accountName, String markPx, String posSide)`: 获取交易信号
-
-## 3. 使用步骤
-
-### 3.1 初始化策略
-```java
-// 创建策略实例
-CoreTechnicalStrategy strategy = new CoreTechnicalStrategy();
-
-// 初始化策略
-strategy.initialize();
-```
-
-### 3.2 更新价格数据
-```java
-// 准备价格数据列表
-List<BigDecimal> prices = new ArrayList<>();
-prices.add(new BigDecimal(30000));
-prices.add(new BigDecimal(30500));
-prices.add(new BigDecimal(31000));
-// 添加更多价格数据...
-
-// 更新价格数据
-strategy.updatePrices(prices);
-```
-
-### 3.3 获取交易信号
-```java
-// 调用getSignal方法获取交易信号
-TradeRequestParam param = strategy.getSignal(
- "account1", // 账户名称
- "31500", // 当前标记价格
- "long" // 当前仓位方向("long"或"short")
-);
-
-// 获取交易方向
-String side = param.getSide(); // "buy"或"sell"
-String tradeType = param.getTradeType(); // "yes"或"no"
-```
-
-## 4. 参数说明
-
-### 4.1 getSignal 方法参数
-
-| 参数名 | 类型 | 说明 | 示例 |
-|-------|------|------|------|
-| accountName | String | 账户名称,用于日志记录和配置获取 | "account1", "myAccount" |
-| markPx | String | 当前标记价格,作为交易决策的基础价格 | "31500", "32000.5" |
-| posSide | String | 当前仓位方向,决定多空信号的生成逻辑 | "long"(多头), "short"(空头) |
-
-### 4.2 返回值 TradeRequestParam 说明
-
-| 属性名 | 类型 | 说明 |
-|-------|------|------|
-| accountName | String | 账户名称 |
-| markPx | String | 当前标记价格 |
-| posSide | String | 仓位方向 |
-| side | String | 交易方向:"buy"(买入/做多)或"sell"(卖出/做空) |
-| tradeType | String | 交易类型:"yes"(执行交易)或"no"(不执行交易) |
-| instId | String | 交易对ID |
-| tdMode | String | 交易模式 |
-| ordType | String | 订单类型 |
-
-## 5. 核心指标逻辑
-
-### 5.1 三重EMA交叉系统
-- **多头条件**:9EMA > 21EMA > 55EMA
-- **空头条件**:9EMA < 21EMA < 55EMA
-- **震荡过滤**:三线粘合度 < 2% 时暂停交易
-
-### 5.2 波动率自适应布林带
-- **动态通道宽度**:标准差倍数根据ATR调整
-- **做空信号**:突破上轨 + 成交量放大3倍
-- **做多信号**:触及下轨 + 期货资金费率转正
-
-### 5.3 MACD能量柱分级策略
-- **能量柱面积**:累计过去5根柱体积分
-- **多头入场**:当前柱体 > 0 且面积增速 > 前周期20%
-- **空头反转**:柱体顶背离 + RSI > 70区域死叉
-
-## 6. 示例代码
-
-```java
-public class StrategyExample {
- public static void main(String[] args) {
- // 初始化策略
- CoreTechnicalStrategy strategy = new CoreTechnicalStrategy();
- strategy.initialize();
-
- // 准备历史价格数据
- List<BigDecimal> historicalPrices = new ArrayList<>();
- for (int i = 0; i < 100; i++) {
- // 模拟价格数据
- BigDecimal price = new BigDecimal(30000 + i * 100 + Math.random() * 500);
- historicalPrices.add(price);
- }
-
- // 更新价格数据
- strategy.updatePrices(historicalPrices);
-
- // 获取当前价格
- String currentPrice = "35000";
-
- // 获取多头信号
- TradeRequestParam longSignal = strategy.getSignal("myAccount", currentPrice, "long");
- System.out.println("多头信号: " + longSignal.getSide() + ", 交易类型: " + longSignal.getTradeType());
-
- // 获取空头信号
- TradeRequestParam shortSignal = strategy.getSignal("myAccount", currentPrice, "short");
- System.out.println("空头信号: " + shortSignal.getSide() + ", 交易类型: " + shortSignal.getTradeType());
- }
-}
-```
-
-## 7. 注意事项
-
-1. **价格数据长度**:建议至少提供55个以上的价格数据点,以确保所有指标都能正确计算
-2. **初始化顺序**:必须先调用`initialize()`方法,再调用`updatePrices()`和`getSignal()`
-3. **仓位方向**:传入的`posSide`参数会影响信号生成逻辑,请确保传入正确的当前仓位方向
-4. **日志记录**:策略会输出详细的日志信息,可通过日志级别控制输出内容
-5. **震荡过滤**:当处于震荡行情时,`tradeType`会返回"no",建议暂停交易
-
-## 8. 错误处理
-
-策略内部包含完善的错误处理机制:
-- 价格数据为空或无效时,会返回`NO_SIGNAL`
-- 计算过程中发生异常时,会捕获并记录日志,返回`NO_SIGNAL`
-- 冷静期内会返回`tradeType="no"`
-- 震荡行情会返回`tradeType="no"`
-
-## 9. 扩展与定制
-
-### 9.1 修改指标参数
-可以在`CoreTechnicalStrategy`类中修改以下常量来调整策略参数:
-
-```java
-// MACD能量柱计算周期
-private static final int MACD_HISTOGRAM_PERIOD = 5;
-
-// 成交量放大倍数
-private static final BigDecimal VOLUME_MULTIPLIER = new BigDecimal(3);
-
-// 三线粘合度阈值(%)
-private static final BigDecimal GLUE_THRESHOLD = new BigDecimal(2);
-```
-
-### 9.2 自定义信号逻辑
-可以重写`analyzeCoreSignal`方法来自定义信号生成逻辑:
-
-```java
-@Override
-protected TradeSignal analyzeCoreSignal(BigDecimal currentPrice) {
- // 自定义信号逻辑
- // ...
-}
-```
-
-## 10. 整合到现有系统
-
-要将CoreTechnicalStrategy整合到现有系统中,只需将其替换或补充现有的策略类即可:
-
-```java
-// 在OkxWebSocketClientManager或其他适当位置
-CoreTechnicalStrategy coreStrategy = new CoreTechnicalStrategy();
-coreStrategy.initialize();
-
-// 在价格更新时
-coreStrategy.updatePrices(priceList);
-
-// 在需要交易信号时
-TradeRequestParam signal = coreStrategy.getSignal(accountName, markPx, posSide);
-```
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/TechnicalIndicatorStrategy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/TechnicalIndicatorStrategy.java
deleted file mode 100644
index 65b4d95..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/TechnicalIndicatorStrategy.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator.strategy;
-
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-/**
- * 技术指标策略接口,定义技术指标策略的基本方法
- */
-public interface TechnicalIndicatorStrategy {
-
- /**
- * 初始化策略
- */
- void initialize();
-
- /**
- * 更新价格数据
- * @param prices 价格列表
- */
- void updatePrices(List<BigDecimal> prices);
-
- /**
- * 获取交易信号
- * @param accountName 账户名称
- * @param markPx 当前标记价格
- * @param posSide 仓位方向
- * @return 交易请求参数,包含交易信号和相关信息
- */
- TradeRequestParam getSignal(String accountName, String markPx, String posSide);
-
- /**
- * 获取策略名称
- * @return 策略名称
- */
- String getStrategyName();
-
- /**
- * 判断策略是否有效
- * @return 是否有效
- */
- boolean isValid();
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/TradeSignal.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/TradeSignal.java
deleted file mode 100644
index e1c05da..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/strategy/TradeSignal.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.indicator.strategy;
-
-import lombok.Getter;
-
-/**
- * 交易信号枚举
- */
-@Getter
-public enum TradeSignal {
-
- /** 无信号,保持观望 */
- NO_SIGNAL("NO_SIGNAL", "无信号"),
-
- /** 买入信号 */
- BUY("BUY", "买入"),
-
- /** 卖出信号 */
- SELL("SELL", "卖出"),
-
- /** 开多信号 */
- OPEN_LONG("OPEN_LONG", "开多"),
-
- /** 平多信号 */
- CLOSE_LONG("CLOSE_LONG", "平多"),
-
- /** 开空信号 */
- OPEN_SHORT("OPEN_SHORT", "开空"),
-
- /** 平空信号 */
- CLOSE_SHORT("CLOSE_SHORT", "平空"),
-
- /** 止损信号 */
- STOP_LOSS("STOP_LOSS", "止损"),
-
- /** 止盈信号 */
- TAKE_PROFIT("TAKE_PROFIT", "止盈");
-
- private final String value;
- private final String name;
-
- TradeSignal(String value, String name) {
- this.value = value;
- this.name = name;
- }
-
- public static TradeSignal fromValue(String value) {
- for (TradeSignal signal : values()) {
- if (signal.getValue().equals(value)) {
- return signal;
- }
- }
- return NO_SIGNAL;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/jiaoyi/IMQService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/jiaoyi/IMQService.java
deleted file mode 100644
index 411481c..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/jiaoyi/IMQService.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.jiaoyi;
-
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeRequestBuy;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeRequestSell;
-
-public interface IMQService {
-
-
-
- /**
- * 消费买入消息
- * @param returnVo
- */
- void operationBuyMsg(TradeRequestBuy returnVo);
-
-
- /**
- * 消费卖出消息
- * @param returnVo
- */
- void operationSellMsg(TradeRequestSell returnVo);
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/jiaoyi/IMQServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/jiaoyi/IMQServiceImpl.java
deleted file mode 100644
index 4ac6153..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/jiaoyi/IMQServiceImpl.java
+++ /dev/null
@@ -1,198 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.jiaoyi;
-
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.json.JSONUtil;
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.enumerates.TradeTypeEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeEventEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeEventRunner;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeRequestBuy;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeRequestSell;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.VerifyAccountFactory;
-import com.xcong.excoin.modules.okxNewPrice.zhanghu.IApiMessageService;
-import com.xcong.excoin.modules.okxNewPrice.zhanghu.ZhangHuEnum;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import java.math.BigDecimal;
-import java.util.LinkedHashMap;
-
-/**
- * @author Administrator
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class IMQServiceImpl implements IMQService{
-
- private final IApiMessageService apiMessageService;
-
- private final VerifyAccountFactory verifyAccountFactory;
- @Override
- public void operationBuyMsg(TradeRequestBuy returnVo) {
-
- log.info("买入入参:{}", JSON.toJSONString(returnVo));
- /**
- * 1、获取用户的策略信息
- * 2、quant_operate_recode 记录表新增一条记录,finish_status 为1.未完成
- * 3、发起OKX接口调用
- */
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(ZhangHuEnum.JIAOYISUO.getValue());
- if(ObjectUtil.isEmpty(quantApiMessage)){
- return;
- }
-
- OKXAccount okxAccount = (OKXAccount) verifyAccountFactory.getAccountMap()
- .get(quantApiMessage.getExchange())
- .initAccount(quantApiMessage);
- if(ObjectUtil.isEmpty(okxAccount)){
- return;
- }
-
- LinkedHashMap<String, Object> requestParam = new LinkedHashMap<>();
- requestParam.put("instId", returnVo.getInstId()+"-SWAP");
- /**
- * 订单方向
- * buy:买, sell:卖
- */
- requestParam.put("side", TradeTypeEnum.BUY.getValue());
- /**
- * 订单类型
- * market:市价单
- * limit:限价单
- * post_only:只做maker单
- * fok:全部成交或立即取消
- * ioc:立即成交并取消剩余
- * optimal_limit_ioc:市价委托立即成交并取消剩余(仅适用交割、永续)
- * mmp:做市商保护(仅适用于组合保证金账户模式下的期权订单)
- * mmp_and_post_only:做市商保护且只做maker单(仅适用于组合保证金账户模式下的期权订单)
- */
- String type = (BigDecimal.ZERO.compareTo(new BigDecimal(returnVo.getLimitPrice())) == 0) ? TradeTypeEnum.MARKET.getValue() : TradeTypeEnum.LIMIT.getValue();
- requestParam.put("ordType", type);
- if (TradeTypeEnum.LIMIT.getValue().equals(type)) {
- requestParam.put("px", returnVo.getLimitPrice());
- requestParam.put("ordType", TradeTypeEnum.LIMIT.getValue());
- }
- /**
- * 持仓方向
- * 在开平仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。
- */
- String positionSide = (TradeTypeEnum.LONG.getCode() == returnVo.getPositionSide()) ? TradeTypeEnum.LONG.getValue() : TradeTypeEnum.SHORT.getValue();
- requestParam.put("posSide", positionSide);
- /**
- * 交易模式
- * 保证金模式:isolated:逐仓 ;cross:全仓
- * 非保证金模式:cash:非保证金
- * spot_isolated:现货逐仓(仅适用于现货带单) ,现货带单时,tdMode 的值需要指定为spot_isolated
- */
- String tdMode = (TradeTypeEnum.ISOLATED.getCode() == returnVo.getTdMode()) ? TradeTypeEnum.ISOLATED.getValue() : TradeTypeEnum.CROSS.getValue();
- requestParam.put("tdMode", tdMode);
- /**
- * 委托数量
- */
- requestParam.put("sz", returnVo.getTradeCnt());
- /**
- * 客户自定义订单ID
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。
- */
- String clOrdId = MallUtils.getOrderNum();
- requestParam.put("clOrdId", clOrdId);
-
- String placeOrderRspOkxRestResponse = TradeEventRunner.runTradeEvent(TradeEventEnum.TRADE_BUY.getEventPoint(),requestParam,okxAccount);
- log.info("下买单结果成功:{}", placeOrderRspOkxRestResponse);
- log.info("消费成功:{}", JSONUtil.parseObj(returnVo));
- }
-
- @Override
- public void operationSellMsg(TradeRequestSell returnVo) {
- /**
- * 1、获取用户的策略信息
- * 2、quant_operate_recode 记录表新增一条记录,finish_status 为1.未完成
- * 3、发起OKX接口调用
- */
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(ZhangHuEnum.JIAOYISUO.getValue());
- if(ObjectUtil.isEmpty(quantApiMessage)){
- return;
- }
-
- OKXAccount okxAccount = (OKXAccount) verifyAccountFactory.getAccountMap()
- .get(quantApiMessage.getExchange())
- .initAccount(quantApiMessage);
- if(ObjectUtil.isEmpty(okxAccount)){
- return;
- }
- LinkedHashMap<String, Object> requestParam = new LinkedHashMap<>();
-
- requestParam.put("instId", returnVo.getInstId()+"-SWAP");
- /**
- * 订单方向
- * buy:买, sell:卖
- */
- requestParam.put("side", TradeTypeEnum.SELL.getValue());
- /**
- * 订单类型
- * market:市价单
- * limit:限价单
- * post_only:只做maker单
- * fok:全部成交或立即取消
- * ioc:立即成交并取消剩余
- * optimal_limit_ioc:市价委托立即成交并取消剩余(仅适用交割、永续)
- * mmp:做市商保护(仅适用于组合保证金账户模式下的期权订单)
- * mmp_and_post_only:做市商保护且只做maker单(仅适用于组合保证金账户模式下的期权订单)
- */
- String type = (BigDecimal.ZERO.compareTo(new BigDecimal(returnVo.getLimitPrice())) == 0) ? TradeTypeEnum.MARKET.getValue() : TradeTypeEnum.LIMIT.getValue();
- requestParam.put("ordType", type);
- if (TradeTypeEnum.LIMIT.getValue().equals(type)) {
- requestParam.put("px", returnVo.getLimitPrice());
- requestParam.put("ordType", TradeTypeEnum.LIMIT.getValue());
- }
- /**
- * 持仓方向
- * 在开平仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。
- */
- String positionSide = (TradeTypeEnum.LONG.getCode() == returnVo.getPositionSide()) ? TradeTypeEnum.LONG.getValue() : TradeTypeEnum.SHORT.getValue();
- requestParam.put("posSide", positionSide);
- /**
- * 交易模式
- * 保证金模式:isolated:逐仓 ;cross:全仓
- * 非保证金模式:cash:非保证金
- * spot_isolated:现货逐仓(仅适用于现货带单) ,现货带单时,tdMode 的值需要指定为spot_isolated
- */
- String tdMode = (TradeTypeEnum.ISOLATED.getCode() == returnVo.getTdMode()) ? TradeTypeEnum.ISOLATED.getValue() : TradeTypeEnum.CROSS.getValue();
- requestParam.put("tdMode", tdMode);
- /**
- * 委托数量
- */
- requestParam.put("sz", returnVo.getTradeCnt());
- /**
- * 客户自定义订单ID
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。
- */
- String clOrdId = MallUtils.getOrderNum();
- requestParam.put("clOrdId", clOrdId);
-
-
- String placeOrderRspOkxRestResponse = TradeEventRunner.runTradeEvent(TradeEventEnum.TRADE_SELL.getEventPoint(), requestParam, okxAccount);
-
- String code = JSON.parseObject(placeOrderRspOkxRestResponse).get("code").toString();
- if("1".equals(code)){
- String data = JSON.parseObject(placeOrderRspOkxRestResponse).get("data").toString();
-
- JSONArray jsonArray = JSON.parseArray(data);
- JSONObject jsonObject = jsonArray.getJSONObject(0);
-
- String sCode = jsonObject.getString("sCode");
- if("51169".equals(sCode)){
- log.warn("平仓下单返回结果-失败:{}", data);
- }
- }
- log.info("平仓下单返回结果:{}", placeOrderRspOkxRestResponse);
- log.info("消费成功:{}", JSONUtil.parseObj(returnVo));
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java
deleted file mode 100644
index 81188a8..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs;
-
-import cn.hutool.json.JSONUtil;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
-import io.micrometer.core.instrument.util.JsonUtils;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * 账户 WebSocket 处理类,用于订阅 OKX 的账户频道并处理账户信息推送。
- * 包含账户资金状态判断、计算下单保证金及保存到 Redis 的逻辑。
- *
- * @author Administrator
- */
-@Slf4j
-public class AccountWs {
-
- // 使用双层Map,第一层key为账号名称,第二层key为数据key
- private static final Map<String, Map<String, String>> ACCOUNTWSMAP = new ConcurrentHashMap<>();
-
- // 获取指定账号的Map,如果不存在则创建
- public static Map<String, String> getAccountMap(String accountName) {
- return ACCOUNTWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
- }
- /**
- * 账户频道名称常量
- */
- public static final String ACCOUNTWS_CHANNEL = "account";
-
- /**
- * 订阅账户频道
- *
- * @param webSocketClient WebSocket 客户端实例
- * @param option 请求选项(如 unsubscribe 或 subscribe)
- */
- public static void subscribeAccountChannel(WebSocketClient webSocketClient, String option) {
- try {
- JSONArray argsArray = new JSONArray();
- JSONObject args = new JSONObject();
- args.put("channel", ACCOUNTWS_CHANNEL);
- args.put("ccy", CoinEnums.USDT.getCode());
- argsArray.add(args);
-
- String connId = MallUtils.getOrderNum(ACCOUNTWS_CHANNEL);
- JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, option, argsArray);
- webSocketClient.send(jsonObject.toJSONString());
-// log.info("发送账户频道:{}", option);
- } catch (Exception e) {
- log.error("订阅账户频道构建失败", e);
- }
- }
-
- public static void initEvent(JSONObject response, String accountName) {
-// log.info("订阅成功: {}", response.getJSONObject("arg"));
- JSONObject arg = response.getJSONObject("arg");
- initParam(arg, accountName);
- }
-
- /**
- * 处理账户频道推送的数据
- *
- * @param response 推送的 JSON 数据对象
- * @param accountName 账号名称
- */
- public static void handleEvent(JSONObject response, String accountName) {
-
-
-// log.info("开始执行AccountWs......{}",ACCOUNTWS_CHANNEL);
- try {
- JSONArray dataArray = response.getJSONArray("data");
- if (dataArray == null || dataArray.isEmpty()) {
- log.warn("账户频道数据为空");
- return;
- }
-
- for (int i = 0; i < dataArray.size(); i++) {
- try {
- JSONObject accountData = dataArray.getJSONObject(i);
- JSONArray detailsArray = accountData.getJSONArray("details");
- if (detailsArray == null || detailsArray.isEmpty()) {
- log.warn("账户频道{}数据为空",CoinEnums.USDT.getCode());
- continue;
- }
-
- for (int j = 0; j < detailsArray.size(); j++) {
- JSONObject detail = detailsArray.getJSONObject(j);
- initParam(detail, accountName);
-
- Map<String, String> accountMap = getAccountMap(accountName);
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode());
- }
- } catch (Exception innerEx) {
- log.warn("处理账户频道数据失败", innerEx);
- }
- }
- } catch (Exception e) {
- log.error("处理账户频道推送数据失败", e);
- }
- }
-
-
- public static final String ccyKey = "ccy";
- public static final String availBalKey = "availBal";
- public static final String cashBalKey = "cashBal";
- public static final String eqKey = "eq";
- public static final String uplKey = "upl";
- public static final String imrKey = "imr";
- private static void initParam(JSONObject detail, String accountName) {
- Map<String, String> accountMap = getAccountMap(accountName);
-
- String ccy = WsMapBuild.parseStringSafe( detail.getString(ccyKey));
- WsMapBuild.saveStringToMap(accountMap, ccyKey, ccy);
-
- String availBal = WsMapBuild.parseStringSafe(detail.getString(availBalKey));
- WsMapBuild.saveStringToMap(accountMap, availBalKey, availBal);
-
- String cashBal = WsMapBuild.parseStringSafe(detail.getString(cashBalKey));
- WsMapBuild.saveStringToMap(accountMap, cashBalKey, cashBal);
-
- String eq = WsMapBuild.parseStringSafe(detail.getString(eqKey));
- WsMapBuild.saveStringToMap(accountMap, eqKey, eq);
-
- String upl = WsMapBuild.parseStringSafe(detail.getString(uplKey));
- WsMapBuild.saveStringToMap(accountMap, uplKey, upl);
-
- String imr = WsMapBuild.parseStringSafe(detail.getString(imrKey));
- WsMapBuild.saveStringToMap(accountMap, imrKey, imr);
- BigDecimal cashBalDecimal = WsMapBuild.parseBigDecimalSafe(cashBal);
- // 根据可用余额计算下单总保证金
- String total_order_usdtpecent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDTPECENT.name());
- BigDecimal total_order_usdt_factor = WsMapBuild.parseBigDecimalSafe(total_order_usdtpecent);
- BigDecimal totalOrderUsdt = cashBalDecimal.multiply(total_order_usdt_factor).setScale(2, RoundingMode.DOWN);
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.TOTAL_ORDER_USDT.name(), String.valueOf(totalOrderUsdt));
-
- /**
- * 当前账户未满仓,并且账户余额不为0,才更新为已就绪
- */
- BigDecimal imrDecimal = WsMapBuild.parseBigDecimalSafe(imr);
- if (BigDecimal.ZERO.compareTo(cashBalDecimal) < 0 && imrDecimal.compareTo(totalOrderUsdt) < 0){
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode());
- }
-
-// log.info(
-// "{}: 账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {},已使用保证金:{}",
-// accountName, ccy, availBal, cashBal, eq, upl, totalOrderUsdt,imr
-// );
- }
-}
-
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/BalanceAndPositionWs.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/BalanceAndPositionWs.java
deleted file mode 100644
index 9667283..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/BalanceAndPositionWs.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-/**
- * @author Administrator
- */
-@Slf4j
-public class BalanceAndPositionWs {
-
- public static final String CHANNEL_NAME = "balance_and_position";
- private static final String LOG_PREFIX = "账户余额和持仓频道";
-
- public static void subscribeBalanceAndPositionChannel(WebSocketClient webSocketClient, String option) {
- try {
- JSONArray argsArray = new JSONArray();
- JSONObject args = new JSONObject();
- args.put("channel", CHANNEL_NAME);
- argsArray.add(args);
-
- String connId = MallUtils.getOrderNum("bap");
- JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, option, argsArray);
- webSocketClient.send(jsonObject.toJSONString());
- log.info("账户余额和持仓频道订阅成功: {}", LOG_PREFIX, option);
- } catch (Exception e) {
- log.error("账户余额和持仓频道订阅构建失败", LOG_PREFIX, e);
- }
- }
-
- public static void handleEvent(JSONObject response) {
-
- log.info("开始执行BalanceAndPositionWs......");
- try {
- JSONArray dataArray = response.getJSONArray("data");
- if (dataArray == null || dataArray.isEmpty()) {
- log.warn("{}数据为空", LOG_PREFIX);
- return;
- }
-
- JSONObject firstData = dataArray.getJSONObject(0);
- processBalData(firstData);
- processPosData(firstData);
- } catch (Exception e) {
- log.error("{}推送数据处理失败", LOG_PREFIX, e);
- }
- }
-
- private static void processBalData(JSONObject dataObject) {
- JSONArray balDataArray = dataObject.getJSONArray("balData");
- if (balDataArray == null || balDataArray.isEmpty()) {
- return;
- }
-
- for (int i = 0; i < balDataArray.size(); i++) {
- JSONObject balData = balDataArray.getJSONObject(i);
- if (!balData.containsKey("ccy") || !balData.containsKey("cashBal") || !balData.containsKey("uTime")) {
- continue;
- }
-
- String ccy = balData.getString("ccy");
- String cashBal = balData.getString("cashBal");
- String uTime = balData.getString("uTime");
-
- log.info("币种: {}, 余额: {}, 更新时间: {}", ccy, cashBal, uTime);
- }
- }
-
- private static void processPosData(JSONObject dataObject) {
- JSONArray posDataArray = dataObject.getJSONArray("posData");
- if (posDataArray == null || posDataArray.isEmpty()) {
- return;
- }
-
- for (int i = 0; i < posDataArray.size(); i++) {
- JSONObject posData = posDataArray.getJSONObject(i);
- if (!posData.containsKey("posId") || !posData.containsKey("instId")
- || !posData.containsKey("instType") || !posData.containsKey("posSide")
- || !posData.containsKey("pos") || !posData.containsKey("avgPx")
- || !posData.containsKey("ccy")) {
- continue;
- }
-
- String posId = posData.getString("posId");
- String instId = posData.getString("instId");
- String instType = posData.getString("instType");
- String posSide = posData.getString("posSide");
- String pos = posData.getString("pos");
- String avgPx = posData.getString("avgPx");
- String ccy = posData.getString("ccy");
-
- log.info("持仓ID: {}, 产品ID: {}, 类型: {}, 方向: {}, 数量: {}, 平均价: {}, 币种: {}",
- posId, instId, instType, posSide, pos, avgPx, ccy);
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/InstrumentsWs.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/InstrumentsWs.java
deleted file mode 100644
index 334443e..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/InstrumentsWs.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs;
-
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * @author Administrator
- */
-@Slf4j
-public class InstrumentsWs {
-
- public static final Map<String, Map<String, String>> INSTRUMENTSWSMAP = new ConcurrentHashMap<>();
-
- public static final String INSTRUMENTSWS_CHANNEL = "instruments";
-
- public static Map<String, String> getAccountMap(String accountName) {
- return INSTRUMENTSWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
- }
-
- public static void handleEvent(String accountName) {
-// log.info("开始执行InstrumentsWs......");
-
- Map<String, String> accountMap = getAccountMap(accountName);
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.HE_YUE.name(), CoinEnums.HE_YUE.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.CTVAL.name(), CoinEnums.CTVAL.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.TICKSZ.name(), CoinEnums.TICKSZ.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.MINSZ.name(), CoinEnums.MINSZ.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.OUT.name(), OrderParamEnums.OUT_NO.getValue());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.LEVERAGE.name(), CoinEnums.LEVERAGE.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.BUY_CNT.name(), CoinEnums.BUY_CNT.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.BUY_CNT_INIT.name(), CoinEnums.BUY_CNT_INIT.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.BUY_CNT_TIME.name(), CoinEnums.BUY_CNT_TIME.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.ZHI_SUN.name(), CoinEnums.ZHI_SUN.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.KANG_CANG.name(), CoinEnums.KANG_CANG.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.PING_CANG_SHOUYI.name(), CoinEnums.PING_CANG_SHOUYI.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.TOTAL_ORDER_USDTPECENT.name(), CoinEnums.TOTAL_ORDER_USDTPECENT.getCode());
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.CONTRACTMULTIPLIER.name(), CoinEnums.CONTRACTMULTIPLIER.getCode());
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/LoginWs.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/LoginWs.java
deleted file mode 100644
index eb3e416..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/LoginWs.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.ExchangeInfoEnum;
-import com.xcong.excoin.modules.okxNewPrice.utils.SignUtils;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-/**
- * @author Administrator
- */
-@Slf4j
-public class LoginWs {
- public static void websocketLogin(WebSocketClient webSocketClient, ExchangeInfoEnum account) {
-
-// log.info("开始执行LoginWs......");
- try {
-
- JSONArray argsArray = new JSONArray();
- JSONObject loginArgs = new JSONObject();
- // 获取登录凭证信息(需要从配置或Redis中获取)
- String apiKey = account.getApiKey();
- String passphrase = account.getPassphrase();
- String timestamp = String.valueOf(System.currentTimeMillis() /1000);
- String sign = SignUtils.signWebsocket(timestamp, account.getSecretKey());
-
- loginArgs.put("apiKey", apiKey);
- loginArgs.put("passphrase", passphrase);
- loginArgs.put("timestamp", timestamp);
- loginArgs.put("sign", sign);
- argsArray.add(loginArgs);
-
- String option = "login";
- JSONObject login = WsParamBuild.buildJsonObject(null,option, argsArray);
- webSocketClient.send(login.toJSONString());
- log.info("发送登录:{}",option);
- } catch (Exception e) {
- log.error("WebSocket登录请求构建失败", e);
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java
deleted file mode 100644
index 883a2ca..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs;
-
-import cn.hutool.core.util.StrUtil;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList.WangGeListEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
-import com.xcong.excoin.utils.RedisUtils;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * @author Administrator
- */
-@Slf4j
-public class OrderInfoWs {
-
- // 使用双层Map,第一层key为账号名称,第二层key为数据key
- public static final Map<String, Map<String, String>> ORDERINFOWSMAP = new ConcurrentHashMap<>();
-
- // 获取指定账号的Map,如果不存在则创建
- public static Map<String, String> getAccountMap(String accountName) {
- return ORDERINFOWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
- }
-
- public static final String ORDERINFOWS_CHANNEL = "orders";
-
- public static void subscribeOrderInfoChannel(WebSocketClient webSocketClient, String option) {
- try {
- JSONArray argsArray = new JSONArray();
- JSONObject args = new JSONObject();
- args.put("channel", ORDERINFOWS_CHANNEL);
- args.put("instType", CoinEnums.INSTTYPE_SWAP.getCode());
- args.put("instId", CoinEnums.HE_YUE.getCode());
- argsArray.add(args);
-
- String connId = MallUtils.getOrderNum(ORDERINFOWS_CHANNEL);
- JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, option, argsArray);
- webSocketClient.send(jsonObject.toJSONString());
-// log.info("发送订单频道频道:{}", option);
- } catch (Exception e) {
- log.error("订阅订单频道构建失败", e);
- }
- }
-
- public static void initEvent(JSONObject response, String accountName) {
-// log.info("订阅成功: {}", response.getJSONObject("arg"));
- }
-
-
- private static final String DATA_KEY = "data";
- private static final String INSTID_KEY = "instId";
- private static final String ORDID_KEY = "ordId";
- private static final String CLORDID_KEY = "clOrdId";
- private static final String SIDE_KEY = "side";
- private static final String TDMODE_KEY = "tdMode";
- private static final String ACCFILLSZ_KEY = "accFillSz";
- private static final String AVGPX_KEY = "avgPx";
- private static final String STATE_KEY = "state";
- private static final String FILLFEE_KEY = "fillFee";
- private static final String POSSIDE_KEY = "posSide";
- public static List<TradeRequestParam> handleEvent(JSONObject response, RedisUtils redisUtils, String accountName) {
-
- log.info("开始执行OrderInfoWs......");
- try {
- JSONArray dataArray = response.getJSONArray(DATA_KEY);
- if (dataArray == null || dataArray.isEmpty()) {
- log.warn("订单频道数据为空");
- return null;
- }
-
- for (int i = 0; i < dataArray.size(); i++) {
- JSONObject detail = dataArray.getJSONObject(i);
-
- String instId = detail.getString(INSTID_KEY);
- if (!CoinEnums.HE_YUE.getCode().equals(instId)){
- log.info( "订单详情-币种: {} 没有成交订单", CoinEnums.HE_YUE.getCode() );
- continue;
- }
- String ordId = detail.getString(ORDID_KEY);
- String clOrdId = detail.getString(CLORDID_KEY);
- String side = detail.getString(SIDE_KEY);
- String tdMode = detail.getString(TDMODE_KEY);
- String accFillSz = detail.getString(ACCFILLSZ_KEY);
- String avgPx = detail.getString(AVGPX_KEY);
- String state = detail.getString(STATE_KEY);
- String fillFee = detail.getString(FILLFEE_KEY);
- String posSide = detail.getString(POSSIDE_KEY);
-
- log.info(
- "{}: 订单详情-币种: {}, 系统编号: {}, 自定义编号: {}, 订单方向: {}, 交易模式: {}," +
- " 累计成交数量: {}, 成交均价: {}, 订单状态: {}, 手续费: {}, 持仓方向: {}",
- accountName, instId, ordId, clOrdId, side, tdMode,
- accFillSz, avgPx,state, fillFee,posSide
- );
-
- String stateStr = TradeOrderWs.getAccountMap(accountName).get("state");
- if (StrUtil.isNotBlank(stateStr) && state.equals(stateStr)){
- // 使用账号特定的Map
- String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- Map<String, BigDecimal> positionsMap = PositionsWs.getAccountMap(positionAccountName);
- WsMapBuild.saveBigDecimalToMap(positionsMap, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode()));
-
- Map<String, String> accountWsMap = AccountWs.getAccountMap(accountName);
- WsMapBuild.saveStringToMap(accountWsMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode());
-
- log.info("{}: 订单详情已完成: {}, 自定义编号: {}", accountName, CoinEnums.HE_YUE.getCode(), clOrdId);
-
- ArrayList<TradeRequestParam> tradeRequestParams = new ArrayList<>();
- TradeRequestParam tradeRequestParam = new TradeRequestParam();
- tradeRequestParam.setAccountName(accountName);
- BigDecimal zhiYingPx = getZhiYingPx(
- accountName,
- posSide,
- fillFee,
- WsMapBuild.parseBigDecimalSafe(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.CTVAL.name())),
- WsMapBuild.parseBigDecimalSafe(accFillSz),
- WsMapBuild.parseBigDecimalSafe(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.CONTRACTMULTIPLIER.name())),
- WsMapBuild.parseBigDecimalSafe(avgPx),
- WsMapBuild.parseBigDecimalSafe(CoinEnums.LEVERAGE.getCode())
- );
- tradeRequestParam.setMarkPx(String.valueOf(zhiYingPx));
- tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode());
- tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode());
- tradeRequestParam.setPosSide(posSide);
- tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_LIMIT.getCode());
- tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- tradeRequestParam.setSide(CoinEnums.POSSIDE_LONG.getCode().equals(posSide) ? CoinEnums.SIDE_SELL.getCode() : CoinEnums.SIDE_BUY.getCode());
- tradeRequestParam.setClOrdId(WsParamBuild.getOrderNum(tradeRequestParam.getSide()));
- tradeRequestParam.setSz(accFillSz);
- tradeRequestParams.add(tradeRequestParam);
-
- TradeRequestParam tradeRequestParamZhiSun = new TradeRequestParam();
- tradeRequestParamZhiSun.setAccountName(accountName);
- BigDecimal zhiSunPx = getZhiSunPx(
- accountName,
- posSide,
- fillFee,
- WsMapBuild.parseBigDecimalSafe(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.CTVAL.name())),
- WsMapBuild.parseBigDecimalSafe(accFillSz),
- WsMapBuild.parseBigDecimalSafe(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.CONTRACTMULTIPLIER.name())),
- WsMapBuild.parseBigDecimalSafe(avgPx),
- WsMapBuild.parseBigDecimalSafe(CoinEnums.LEVERAGE.getCode())
- );
- tradeRequestParamZhiSun.setMarkPx(String.valueOf(zhiSunPx));
- tradeRequestParamZhiSun.setInstId(CoinEnums.HE_YUE.getCode());
- tradeRequestParamZhiSun.setTdMode(CoinEnums.CROSS.getCode());
- tradeRequestParamZhiSun.setPosSide(posSide);
- tradeRequestParamZhiSun.setOrdType(CoinEnums.ORDTYPE_LIMIT.getCode());
- tradeRequestParamZhiSun.setTradeType(OrderParamEnums.TRADE_YES.getValue());
- tradeRequestParamZhiSun.setSide(CoinEnums.POSSIDE_LONG.getCode().equals(posSide) ? CoinEnums.SIDE_SELL.getCode() : CoinEnums.SIDE_BUY.getCode());
- tradeRequestParamZhiSun.setClOrdId(WsParamBuild.getOrderNum(tradeRequestParamZhiSun.getSide()));
- tradeRequestParamZhiSun.setSz(accFillSz);
- tradeRequestParams.add(tradeRequestParamZhiSun);
- return tradeRequestParams;
-
- }
- return null;
- }
- } catch (Exception e) {
- log.error("处理订单频道推送数据失败", e);
- }
- return null;
- }
-
- public static void main(String[] args) {
- System.out.println(
- getZhiYingPx(
- "eth",
- CoinEnums.POSSIDE_LONG.getCode(),
- "0.0001",
- new BigDecimal("0.1"),
- new BigDecimal("0.05"),
- new BigDecimal("1"),
- new BigDecimal("2950"),
- new BigDecimal("100"))
- );
- }
- /**
- * 计算预期收益
- */
- public static BigDecimal getZhiSunPx(
- String accountName, String posSide, String fillFee, BigDecimal coinValue, BigDecimal coinNum,
- BigDecimal contractMultiplier, BigDecimal avgPx, BigDecimal leverage
- ) {
- BigDecimal initMargin = getInitMargin(coinValue, coinNum, contractMultiplier, avgPx, leverage);
- String pingCangImr = StrUtil.isEmpty(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name())) ? "0.2" : InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name());
- BigDecimal expectProfit = (initMargin)
- .multiply(new BigDecimal(pingCangImr))
- .add(new BigDecimal(fillFee).abs())
- .multiply(new BigDecimal("-1"))
- .setScale(4, RoundingMode.DOWN);
- log.info("{}: 订单详情-预期收益: {}", accountName, expectProfit);
- return getMarkPrice(expectProfit,posSide, coinValue, coinNum, contractMultiplier, avgPx, leverage);
- }
- /**
- * 计算预期收益
- */
- public static BigDecimal getZhiYingPx(
- String accountName, String posSide, String fillFee, BigDecimal coinValue, BigDecimal coinNum,
- BigDecimal contractMultiplier, BigDecimal avgPx, BigDecimal leverage
- ) {
- BigDecimal initMargin = getInitMargin(coinValue, coinNum, contractMultiplier, avgPx, leverage);
- String pingCangImr = StrUtil.isEmpty(InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name())) ? "0.2" : InstrumentsWs.getAccountMap(accountName).get(CoinEnums.PING_CANG_SHOUYI.name());
- BigDecimal expectProfit = (initMargin).multiply(new BigDecimal(pingCangImr)).add(new BigDecimal(fillFee).abs()).setScale(4, RoundingMode.DOWN);
- log.info("{}: 订单详情-预期收益: {}", accountName, expectProfit);
- return getMarkPrice(expectProfit,posSide, coinValue, coinNum, contractMultiplier, avgPx, leverage);
- }
-
- /**
- * 计算初始保证金
- * 面值 * 张数 * 合约乘数 * 标记价格 / 杠杆倍数
- */
- public static BigDecimal getInitMargin(BigDecimal coinValue, BigDecimal coinNum, BigDecimal contractMultiplier, BigDecimal avgPx, BigDecimal leverage) {
- BigDecimal initMargin = coinValue.multiply(coinNum).multiply(contractMultiplier).multiply(avgPx).divide(leverage, 4, RoundingMode.DOWN);
- log.info("订单详情-初始保证金: {}", initMargin);
- return initMargin;
- }
- /**
- * USDT保证金合约
- * 多仓收益 = 面值 * |张数| * 合约乘数 *(标记价格 - 开仓均价)
- * (标记价格 - 开仓均价) = 多仓收益 /(面值 * |张数| * 合约乘数)
- * 标记价格 = 多仓收益 /(面值 * |张数| * 合约乘数) + 开仓均价
- * 空仓收益 = 面值 * |张数| * 合约乘数 *(开仓均价 - 标记价格)
- * (开仓均价 - 标记价格) = 空仓收益 /(面值 * |张数| * 合约乘数)
- * 标记价格 = 开仓均价 - 空仓收益 /(面值 * |张数| * 合约乘数)
- */
- public static BigDecimal getMarkPrice(BigDecimal expectProfit,String posSide,BigDecimal coinValue, BigDecimal sz, BigDecimal contractMultiplier, BigDecimal openPrice, BigDecimal leverage) {
- BigDecimal markPrice = BigDecimal.ZERO;
- BigDecimal multiply = coinValue.multiply(sz.abs()).multiply(contractMultiplier);
- BigDecimal bigDecimal = expectProfit.divide(multiply, 2, RoundingMode.DOWN);
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)){
- markPrice = openPrice.add(bigDecimal).setScale(2, RoundingMode.DOWN);
- }
- if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)){
- markPrice = openPrice.subtract(bigDecimal).setScale(2, RoundingMode.DOWN);
- }
- log.info("订单详情-止盈标记价格: {}", markPrice);
- return markPrice;
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java
deleted file mode 100644
index 4bb3ca3..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * @author Administrator
- */
-@Slf4j
-public class PositionsWs {
-
- // 使用双层Map,第一层key为账号名称,第二层key为数据key
- public static final Map<String, Map<String, BigDecimal>> POSITIONSWSMAP = new ConcurrentHashMap<>();
-
- // 获取指定账号的Map,如果不存在则创建
- public static Map<String, BigDecimal> getAccountMap(String accountName) {
- return POSITIONSWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
- }
-
- public static final String POSITIONSWS_CHANNEL = "positions";
-
- public static void subscribePositionChannel(WebSocketClient webSocketClient, String option) {
- try {
- JSONArray argsArray = new JSONArray();
- JSONObject args = new JSONObject();
- args.put("channel", POSITIONSWS_CHANNEL);
- args.put("instType", CoinEnums.INSTTYPE_SWAP.getCode());
- args.put("instId", CoinEnums.HE_YUE.getCode());
- argsArray.add(args);
-
- String connId = MallUtils.getOrderNum(POSITIONSWS_CHANNEL);
- JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, option, argsArray);
- webSocketClient.send(jsonObject.toJSONString());
- log.info("发送持仓频道频道:{}", option);
- } catch (Exception e) {
- log.error("订阅持仓频道频道构建失败", e);
- }
- }
-
- public static String initAccountName(String accountName, String posSide) {
- return accountName+"_"+ posSide;
- }
-
- public static void initEvent(JSONObject response, String accountName) {
- log.info("订阅成功,数据初始化: {}", response.getJSONObject("arg"));
- JSONObject arg = response.getJSONObject("arg");
- initParam(arg, accountName,CoinEnums.POSSIDE_LONG.getCode());
- initParam(arg, accountName,CoinEnums.POSSIDE_SHORT.getCode());
- }
-
- public static void handleEvent(JSONObject response, String accountName) {
-
-
-// log.info("开始执行PositionsWs......");
- try {
- JSONArray dataArray = response.getJSONArray("data");
- if (dataArray == null || dataArray.isEmpty()) {
- log.info("账户持仓频道数据为空,等待更新");
- return;
- }
-
- for (int i = 0; i < dataArray.size(); i++) {
- JSONObject posData = dataArray.getJSONObject(i);
- String instId = posData.getString("instId");
- if (CoinEnums.HE_YUE.getCode().equals(instId)) {
-// log.info("查询到账户{}持仓数据",CoinEnums.HE_YUE.getCode());
- String mgnMode = posData.getString("mgnMode");
- String posSide = posData.getString("posSide");
- String pos = posData.getString("pos");
- String avgPx = posData.getString("avgPx");
- String upl = posData.getString("upl");
- String uplRatio = posData.getString("uplRatio");
- String lever = posData.getString("lever");
- String liqPx = posData.getString("liqPx");
- String markPx = posData.getString("markPx");
- String imr = posData.getString("imr");
- String mgnRatio = posData.getString("mgnRatio");
- String mmr = posData.getString("mmr");
- String notionalUsd = posData.getString("notionalUsd");
- String ccy = posData.getString("ccy");
- String last = posData.getString("last");
- String idxPx = posData.getString("idxPx");
- String bePx = posData.getString("bePx");
- String realizedPnl = posData.getString("realizedPnl");
- String settledPnl = posData.getString("settledPnl");
- String fee = posData.getString("fee");
- String fundingFee = posData.getString("fundingFee");
-// log.info(
-// "{}: 账户持仓频道-产品类型: {}, 保证金模式: {}, 持仓方向: {}, 持仓数量: {}, 开仓平均价: {}, "
-// + "未实现收益: {}, 未实现收益率: {}, 杠杆倍数: {}, 预估强平价: {}, 初始保证金: {}, "
-// + "维持保证金率: {}, 维持保证金: {}, 以美金价值为单位的持仓数量: {}, 占用保证金的币种: {}, "
-// + "最新成交价: {}, 最新指数价格: {}, 盈亏平衡价: {}, 已实现收益: {}, 累计已结算收益: {}"
-// + "最新标记价格: {},累计手续费: {},累计持仓费: {},",
-// initAccountName(accountName, posSide), instId, mgnMode, posSide, pos, avgPx,
-// upl, uplRatio, lever, liqPx, imr,
-// mgnRatio, mmr, notionalUsd, ccy,
-// last, idxPx, bePx, realizedPnl, settledPnl,
-// markPx,fee,fundingFee
-// );
- initParam(posData, accountName,posSide);
- }
- }
- } catch (Exception e) {
- log.error("处理持仓频道推送数据失败", e);
- }
- }
-
- private static void initParam(JSONObject posData, String accountName,String posSide) {
- String accountNamePositons = initAccountName(accountName, posSide);
- Map<String, BigDecimal> accountMap = getAccountMap(accountNamePositons);
- WsMapBuild.saveBigDecimalToMap(accountMap, "avgPx", WsMapBuild.parseBigDecimalSafe(posData.getString("avgPx")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "pos", WsMapBuild.parseBigDecimalSafe(posData.getString("pos")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "upl", WsMapBuild.parseBigDecimalSafe(posData.getString("upl")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "imr", WsMapBuild.parseBigDecimalSafe(posData.getString("imr")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "mgnRatio", WsMapBuild.parseBigDecimalSafe(posData.getString("mgnRatio")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "markPx", WsMapBuild.parseBigDecimalSafe(posData.getString("markPx")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "bePx", WsMapBuild.parseBigDecimalSafe(posData.getString("bePx")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "realizedPnl", WsMapBuild.parseBigDecimalSafe(posData.getString("realizedPnl")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "fee", WsMapBuild.parseBigDecimalSafe(posData.getString("fee")));
- WsMapBuild.saveBigDecimalToMap(accountMap, "fundingFee", WsMapBuild.parseBigDecimalSafe(posData.getString("fundingFee")));
-
- BigDecimal ordFrozImr = PositionsWs.getAccountMap(accountNamePositons).get("imr");
- BigDecimal totalOrderUsdt = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDT.name()))
- .divide(new BigDecimal("2"), RoundingMode.DOWN);
- if (ordFrozImr.compareTo(totalOrderUsdt) <= 0){
- WsMapBuild.saveBigDecimalToMap(accountMap, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode()));
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
deleted file mode 100644
index bea34fd..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
+++ /dev/null
@@ -1,247 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs;
-
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONUtil;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
-import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
-import lombok.extern.slf4j.Slf4j;
-import org.java_websocket.client.WebSocketClient;
-
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * 交易订单处理类,负责构建和发送订单请求到OKX WebSocket
- *
- * @author Administrator
- */
-@Slf4j
-public class TradeOrderWs {
-
- // 使用双层Map,第一层key为账号名称,第二层key为数据key
- public static final Map<String, Map<String,String>> TRADEORDERWSMAP = new ConcurrentHashMap<>();
-
- // 获取指定账号的Map,如果不存在则创建
- public static Map<String, String> getAccountMap(String accountName) {
- return TRADEORDERWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
- }
-
- public static final String ORDERWS_CHANNEL = "order";
- public static final String BATCH_ORDERSWS_CHANNEL = "batch-orders";
-
- public static void orderEvent(WebSocketClient webSocketClient, TradeRequestParam tradeRequestParam) {
-
-
- log.info("开始执行TradeOrderWs{}......", JSONUtil.parse(tradeRequestParam));
- String accountName = tradeRequestParam.getAccountName();
- String markPx = tradeRequestParam.getMarkPx();
- String instId = tradeRequestParam.getInstId();
- String tdMode = tradeRequestParam.getTdMode();
- String posSide = tradeRequestParam.getPosSide();
- String ordType = tradeRequestParam.getOrdType();
-
- String tradeType = tradeRequestParam.getTradeType();
-
- String clOrdId = tradeRequestParam.getClOrdId();
- String side = tradeRequestParam.getSide();
- String sz = tradeRequestParam.getSz();
- log.info("账户:{},类型:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},",
- accountName,ordType, markPx, instId, posSide,side, sz, tradeType, clOrdId);
- //验证是否允许下单
- if (StrUtil.isNotEmpty(tradeType) && OrderParamEnums.TRADE_NO.getValue().equals(tradeType)) {
- log.warn("账户{}不允许下单,取消发送", accountName);
- return;
- }
- /**
- * 校验必要参数
- * 验证下单参数是否存在空值
- */
- if (
- StrUtil.isBlank(accountName)
- || StrUtil.isBlank(instId)
- || StrUtil.isBlank(tdMode)
- || StrUtil.isBlank(posSide)
- || StrUtil.isBlank(ordType)
- || StrUtil.isBlank(clOrdId)
- || StrUtil.isBlank(side)
- || StrUtil.isBlank(sz)
-
- ){
- log.warn("下单参数缺失,取消发送");
- return;
- }
- if (BigDecimal.ZERO.compareTo(new BigDecimal(sz)) >= 0) {
- log.warn("下单数量{}不允许下单,取消发送", sz);
- return;
- }
-
- /**
- * 检验账户和仓位是否准备就绪
- * 开多:买入开多(side 填写 buy; posSide 填写 long )
- * 开空:卖出开空(side 填写 sell; posSide 填写 short ) 需要检验账户通道是否准备就绪
- * 平多:卖出平多(side 填写 sell;posSide 填写 long )
- * 平空:买入平空(side 填写 buy; posSide 填写 short ) 需要检验仓位通道是否准备就绪
- */
- //买入开多、卖出开空则验证仓位通道是否准备就绪
-
- String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- boolean b = posSide.equals(CoinEnums.POSSIDE_LONG.getCode()) && side.equals(CoinEnums.SIDE_BUY.getCode());
- boolean c = posSide.equals(CoinEnums.POSSIDE_SHORT.getCode()) && side.equals(CoinEnums.SIDE_SELL.getCode());
- if ( b || c ){
- BigDecimal positionsReadyState = PositionsWs.getAccountMap(positionAccountName).get(CoinEnums.READY_STATE.name()) == null
- ? BigDecimal.ZERO : PositionsWs.getAccountMap(positionAccountName).get(CoinEnums.READY_STATE.name());
- if (WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode()).compareTo(positionsReadyState) != 0) {
- log.info("仓位{}通道未就绪,取消发送",positionAccountName);
- return;
- }
- String accountReadyState = AccountWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name());
- if (!CoinEnums.READY_STATE_YES.getCode().equals(accountReadyState)) {
- log.info("账户通道未就绪,取消发送");
- return;
- }
- }
-
- try {
- JSONArray argsArray = new JSONArray();
- JSONObject args = new JSONObject();
- args.put("instId", instId);
- args.put("tdMode", tdMode);
- args.put("clOrdId", clOrdId);
- args.put("side", side);
-
- args.put("posSide", posSide);
- args.put("ordType", ordType);
- args.put("sz", sz);
- argsArray.add(args);
-
- String connId = WsParamBuild.getOrderNum(ORDERWS_CHANNEL);
- JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, ORDERWS_CHANNEL, argsArray);
- webSocketClient.send(jsonObject.toJSONString());
- log.info("发送下单频道:{},数量:{}", side, sz);
-
- WsMapBuild.saveStringToMap(getAccountMap(accountName), "state", CoinEnums.ORDER_FILLED.getCode());
- /**
- * 将状态更新为未准备就绪
- */
- WsMapBuild.saveBigDecimalToMap(PositionsWs.getAccountMap(positionAccountName), CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode()));
- WsMapBuild.saveStringToMap(AccountWs.getAccountMap(accountName), CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode());
-
- } catch (Exception e) {
- log.error("下单构建失败", e);
- }
- }
-
- public static void orderZhiYingZhiSunEventNoState(WebSocketClient webSocketClient, List<TradeRequestParam> tradeRequestParams) {
-
-
- log.info("开始执行TradeOrderWs......");
- if (tradeRequestParams == null){
-
- log.warn("下单{}参数缺失,取消发送",tradeRequestParams);
- return;
- }
-
- JSONArray argsArray = new JSONArray();
- for (TradeRequestParam tradeRequestParam : tradeRequestParams){
- String accountName = tradeRequestParam.getAccountName();
- String markPx = tradeRequestParam.getMarkPx();
- String instId = tradeRequestParam.getInstId();
- String tdMode = tradeRequestParam.getTdMode();
- String posSide = tradeRequestParam.getPosSide();
- String ordType = tradeRequestParam.getOrdType();
-
- String tradeType = tradeRequestParam.getTradeType();
-
- String clOrdId = tradeRequestParam.getClOrdId();
- String side = tradeRequestParam.getSide();
- String sz = tradeRequestParam.getSz();
- /**
- * 校验必要参数
- * 验证下单参数是否存在空值
- */
- if (
- StrUtil.isBlank(accountName)
- || StrUtil.isBlank(instId)
- || StrUtil.isBlank(tdMode)
- || StrUtil.isBlank(posSide)
- || StrUtil.isBlank(ordType)
- || StrUtil.isBlank(clOrdId)
- || StrUtil.isBlank(side)
- || StrUtil.isBlank(sz)
- || StrUtil.isBlank(markPx)
-
- ){
- log.warn("下单参数缺失,取消发送");
- continue;
- }
- log.info("账户:{},类型:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},",
- accountName,ordType, markPx, instId, posSide,side, sz, tradeType, clOrdId);
- //验证是否允许下单
- if (StrUtil.isNotEmpty(tradeType) && OrderParamEnums.TRADE_NO.getValue().equals(tradeType)) {
- log.warn("账户{}不允许下单,取消发送", accountName);
- continue;
- }
-
- /**
- * 检验账户和仓位是否准备就绪
- * 开多:买入开多(side 填写 buy; posSide 填写 long )
- * 开空:卖出开空(side 填写 sell; posSide 填写 short ) 需要检验账户通道是否准备就绪
- * 平多:卖出平多(side 填写 sell;posSide 填写 long )
- * 平空:买入平空(side 填写 buy; posSide 填写 short ) 需要检验仓位通道是否准备就绪
- */
-
- JSONObject args = new JSONObject();
- args.put("instId", instId);
- args.put("tdMode", tdMode);
- args.put("clOrdId", clOrdId);
- args.put("side", side);
-
- args.put("posSide", posSide);
- args.put("ordType", ordType);
- args.put("sz", sz);
- args.put("px", markPx);
- argsArray.add(args);
- }
-
- String connId = WsParamBuild.getOrderNum(null);
- JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, BATCH_ORDERSWS_CHANNEL, argsArray);
- webSocketClient.send(jsonObject.toJSONString());
- log.info("发送止盈止损批量下单频道:{}",argsArray);
- }
-
-
-
- /**
- * 计算盈亏金额。
- *
- * @param faceValue 面值
- * @param position 持仓数量
- * @param markPrice 标记价格
- * @param openPrice 开仓价格
- * @param isLong 是否为多头仓位
- * @param minTickSz 最小变动单位精度
- * @return 盈亏金额,保留指定精度的小数位
- */
- public BigDecimal profit(BigDecimal faceValue, BigDecimal position,
- BigDecimal markPrice, BigDecimal openPrice, boolean isLong, int minTickSz) {
- BigDecimal profit = BigDecimal.ZERO;
- if (isLong) {
- profit = markPrice.subtract(openPrice)
- .multiply(faceValue)
- .multiply(position);
- } else {
- profit = openPrice.subtract(markPrice)
- .multiply(faceValue)
- .multiply(position);
- }
- return profit.setScale(minTickSz, BigDecimal.ROUND_DOWN);
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java
deleted file mode 100644
index 8429c7d..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.enums;
-
-import lombok.Getter;
-
-/**
- * @author Administrator
- */
-@Getter
-public enum CoinEnums {
-
- /**
- * 0: 仅根据持仓事件推送数据
- * 2000, 3000, 4000: 根据持仓事件推送,且根据设置的时间间隔定时推送(ms)
- *
- * 若不添加该字段或将其设置为上述合法值以外的其他值,数据将根据事件推送并大约每 5 秒定期推送一次
- */
- UPDATEINTERVAL("2000","2000"),
-
- ORDER_FILLED("filled","filled"),
- ORDER_LIVE("live","live"),
-
- INSTTYPE_SWAP("SWAP","SWAP"),
-
- ORDTYPE_MARKET("market","market"),
- ORDTYPE_LIMIT("limit","limit"),
-
- POSSIDE_SHORT("short","short"),
-
- POSSIDE_LONG("long","long"),
-
- SIDE_SELL("sell","sell"),
-
- SIDE_BUY("buy","buy"),
-
- CROSS("cross","cross"),
-
- USDT("USDT","USDT"),
-
-
- WANG_GE_OLD("上一个网格wang_ge_old", "0"),
-
- READY_STATE("是否准备就绪ready_state", "1"),
- READY_STATE_YES("准备就绪ready_state", "1"),
- READY_STATE_NO("未准备就绪ready_state", "0"),
-
- PING_CANG_SHOUYI("平仓收益比例", "1"),
- //下单的总保障金为账户总金额cashBal * TOTAL_ORDER_USDT用来做保证金
- TOTAL_ORDER_USDTPECENT("总保证金比例total_order_usdtpecent","0.06"),
- TOTAL_ORDER_USDT("总保证金totalOrderUsdt","0"),
- KANG_CANG("抗压比例KANG_CANG","0.9"),
- ZHI_SUN("止损比例ZHI_SUN","0.8"),
- //每次下单的张数
- BUY_CNT("每次开仓的张数buyCnt","0.5"),
- BUY_CNT_INIT("每次初始化开仓张数的基础值buyCntInit","0.5"),
- BUY_CNT_TIME("每次开仓张数的倍数基础值buyCntTime","20"),
- OUT("是否允许下单out","操作中"),
- CTVAL("合约面值ctVal","0.1"),
- CONTRACTMULTIPLIER("合约乘积ctVal","1"),
- TICKSZ("下单价格精度tickSz","2"),
- MINSZ("最小下单数小数位minSz","2"),
- LEVERAGE("合约杠杆leverage","100"),
-// HE_YUE("合约instId","ETH-USDT-SWAP"),
- HE_YUE("合约instId","BTC-USDT-SWAP"),
- POSSIDE("持仓方向posSide","short");
-
- private String name;
-
- private String code;
-
- /**
- * 构造方法
- *
- * @param name
- * @param code
- */
- CoinEnums(String name, String code) {
- this.name = name;
- this.code = code;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/ExchangeInfoEnum.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/ExchangeInfoEnum.java
deleted file mode 100644
index 42a1d83..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/ExchangeInfoEnum.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.enums;
-
-import lombok.Getter;
-
-/**
- * 交易信息枚举类
- * 用于存储不同交易账户的密钥信息,包括实盘账户和模拟账户
- */
-@Getter
-public enum ExchangeInfoEnum {
-
- /**
- * 模拟盘账户1信息
- * 存储了模拟盘交易所需的API密钥、秘钥和通过码
- */
-// OKX_PRD_xiao("f512673b-2685-4fcb-9bb1-2ae8db745d62",
-// "B0C1CC8F39625B41140D93DC25039E33",
-// "Aa12345678@",
-// true);
- OKX_UAT_ceshi("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61",
- "AA06C5ED1D7C7F5AFE6484052E231C55",
- "Aa12345678@",
- false);
-//
-// /**
-// * 模拟盘账户2信息
-// * 存储了模拟盘交易所需的API密钥、秘钥和通过码
-// */
-// OKX_PRD_wang("72e380a6-4133-451b-8b10-8b1905b30717",
-// "2A5BD55BF0771F1ADF08AE0A2FB4D561",
-// "Aa12345678@",
-// true);
-// OKX_UAT2("7a023eb2-06c0-4255-9969-b86ea1cef0d7",
-// "D0106A4D63BD22BEAB9CBA8F41219661",
-// "Aa12345678@",
-// false);
-
- /**
- * 模拟盘账户3信息
- * 存储了模拟盘交易所需的API密钥、秘钥和通过码
- */
-// OKX_UAT3("0769b50c-2c36-4310-8bd9-cad6bc6c9d8f",
-// "7AF4A574BC44907CE76BBFF91F53852D",
-// "Aa123456@",
-// false);
-
- // API公钥,用于识别用户身份
- private String apiKey;
-
- // API秘钥,用于签名和验证请求
- private String secretKey;
-
- // API通过码,用于额外的身份验证
- private String passphrase;
-
- // 账户类型,true表示实盘账户,false表示模拟账户
- private boolean accountType;
-
- /**
- * 构造方法
- *
- * @param apiKey API公钥,用于识别用户身份
- * @param secretKey API秘钥,用于签名和验证请求
- * @param passphrase API通过码,用于额外的身份验证
- * @param accountType 账户类型,true表示实盘账户,false表示模拟账户
- */
- ExchangeInfoEnum(String apiKey, String secretKey, String passphrase, boolean accountType) {
- this.apiKey = apiKey;
- this.secretKey = secretKey;
- this.passphrase = passphrase;
- this.accountType = accountType;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java
deleted file mode 100644
index 981203e..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.enums;
-
-import com.xcong.excoin.common.enumerates.SymbolEnum;
-import lombok.Getter;
-
-/**
- * @author Administrator
- */
-
-@Getter
-public enum OrderParamEnums {
-
- TRADE_YES("允许下单", "TRADE_YES"),
- TRADE_NO("拒绝下单", "TRADE_NO"),
-
- OUT_NO("操作中", "操作中"),
- OUT_YES("冷静中", "冷静中"),
-
- ORDERING("操作下单中", "ORDERING"),
- LIMIT("限价止损", "limit"),
- OUT("止损", "out"),
- INIT("初始化", "init"),
- HOLDING("持仓", "holding"),
- BUY("买", "buy"),
- SELL("卖", "sell")
- ;
-
- private String name;
-
- private String value;
-
- private OrderParamEnums(String name, String value) {
- this.name = name;
- this.value = value;
- }
-
- public static String getNameByValue(String value) {
- String name = "";
- for (OrderParamEnums orderParamEnum : values()) {
- if (value.equals(orderParamEnum.getValue())){
- name = orderParamEnum.getName();
- break;
- }
- }
- return name;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java
deleted file mode 100644
index 5adafea..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/Kline.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.param;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-
-@Data
-public class Kline{
- private String ts;
- private BigDecimal o;
- private BigDecimal h;
- private BigDecimal l;
- private BigDecimal c;
- private BigDecimal vol;
- private String confirm;
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java
deleted file mode 100644
index 5dcd018..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/StrategyParam.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.param;
-
-import lombok.Data;
-
-/**
- * @author Administrator
- */
-@Data
-public class StrategyParam {
- private String accountName;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/TradeRequestParam.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/TradeRequestParam.java
deleted file mode 100644
index 14260d7..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/TradeRequestParam.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.param;
-
-import lombok.Data;
-
-/**
- * @author Administrator
- */
-@Data
-public class TradeRequestParam {
- /**
- * 这些参数由 caoZuoHandler方法提供
- */
- private String accountName;
- private String markPx;
-
- private String instId;
- private String tdMode;
- private String posSide;
- private String ordType;
-
- /**
- * 决定是否进行下单操作
- */
- private String tradeType;
- /**
- * 这些参数由 caoZuoLong 或者 caoZuoShort 提供
- */
- private String side;
- private String clOrdId;
- private String sz;
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListEnum.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListEnum.java
deleted file mode 100644
index 7ad9442..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListEnum.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList;
-
-import lombok.Getter;
-
-import java.math.BigDecimal;
-
-/**
- * @author Administrator
- * 网格数据枚举 数据
- * todo 后期考虑优化为可配置项
- */
-@Getter
-public enum WangGeListEnum {
- UP("上层做空", "2", "3200", "3000", "4", "short", "3100"),
- CENTER("中间做空", "2", "3000", "2700", "4", "short", "2700"),
- DOWN("下层做多", "2", "2700", "2500", "4", "long", "2500"),
- DOWN_ONE("下层做空", "2", "2500", "2200", "4", "short", "2500");
-
- private String name;
- private String xiaoshu_weishu;
- private String jiage_shangxian;
- private String jiage_xiaxian;
- private String jian_ju;
- private String fang_xiang;
- private String zhi_sun_dian;
-
- WangGeListEnum(String name, String xiaoshu_weishu, String jiage_shangxian, String jiage_xiaxian, String jian_ju, String fang_xiang, String zhi_sun_dian) {
- this.name = name;
- this.xiaoshu_weishu = xiaoshu_weishu;
- this.jiage_shangxian = jiage_shangxian;
- this.jiage_xiaxian = jiage_xiaxian;
- this.jian_ju = jian_ju;
- this.fang_xiang = fang_xiang;
- this.zhi_sun_dian = zhi_sun_dian;
- }
-
- /**
- * 根据价格获取匹配的网格信息
- * @param price 待比较的价格
- * @return 匹配的网格枚举信息,如果没有匹配项则返回null
- */
- public static WangGeListEnum getGridByPrice(BigDecimal price) {
- for (WangGeListEnum grid : WangGeListEnum.values()) {
- BigDecimal upperLimit = new BigDecimal(grid.jiage_shangxian);
- BigDecimal lowerLimit = new BigDecimal(grid.jiage_xiaxian);
-
- // 确保上限大于下限
- if (upperLimit.compareTo(lowerLimit) > 0) {
- // 检查价格是否在区间内
- if (price.compareTo(lowerLimit) > 0 && price.compareTo(upperLimit) <= 0) {
- return grid;
- }
- }
- }
- return null;
- }
-
- /**
- * 根据枚举名称获取枚举
- */
- public static WangGeListEnum getByName(String name) {
- for (WangGeListEnum grid : WangGeListEnum.values()) {
- if (grid.name.equals(name)) {
- return grid;
- }
- }
- return null;
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListQueue.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListQueue.java
deleted file mode 100644
index 70ae5a0..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListQueue.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList;
-
-import com.xcong.excoin.rabbit.pricequeue.AscBigDecimal;
-import com.xcong.excoin.rabbit.pricequeue.DescBigDecimal;
-
-import java.util.concurrent.PriorityBlockingQueue;
-
-/**
- * 网格交易队列管理类
- *
- * 用于管理系统中各种网格交易相关的优先级阻塞队列,
- * 包括完整的网格队列、平仓队列和开仓队列。
- *
- * @author Administrator
- */
-public class WangGeListQueue {
-
- //------------------------------------------------------------------------------------------------------------------
- //------------------------------------------------------------------------------------------------------------------
- // todo 系统启动后,初始化网格队列
- /**
- * 完整的网格 头元素最小
- */
- public static PriorityBlockingQueue<AscBigDecimal> QUEUE_ASC = null;
-
-
- //------------------------------------------------------------------------------------------------------------------
- //------------------------------------------------------------------------------------------------------------------
- // todo 当用户下了第一单后,根据开仓价格初始化网格平仓队列和开仓队列
- /**
- * 网格平仓队列 头元素最小
- */
- public static PriorityBlockingQueue<AscBigDecimal> QUEUE_PINGCANG_ASC = null;
-
- /**
- * 网格开仓队列 头元素最大
- */
- public static PriorityBlockingQueue<DescBigDecimal> QUEUE_KAICANG_DESC = null;
-
- /**
- * 获取完整的网格队列(升序)
- * 如果队列未初始化则创建新的优先级阻塞队列
- *
- * @return 返回升序排列的PriorityBlockingQueue队列,队列头部元素最小
- */
- public static PriorityBlockingQueue<AscBigDecimal> getQueueAsc() {
- if (QUEUE_ASC == null) {
- QUEUE_ASC = new PriorityBlockingQueue<AscBigDecimal>();
- }
- return QUEUE_ASC;
- }
-
- /**
- * 获取网格平仓队列(升序)
- * 如果队列未初始化则创建新的优先级阻塞队列
- *
- * @return 返回升序排列的PriorityBlockingQueue队列,队列头部元素最小
- */
- public static PriorityBlockingQueue<AscBigDecimal> getPingCang() {
- if (QUEUE_PINGCANG_ASC == null) {
- QUEUE_PINGCANG_ASC = new PriorityBlockingQueue<AscBigDecimal>();
- }
- return QUEUE_PINGCANG_ASC;
- }
-
- /**
- * 获取网格开仓队列(降序)
- * 如果队列未初始化则创建新的优先级阻塞队列
- *
- * @return 返回降序排列的PriorityBlockingQueue队列,队列头部元素最大
- */
- public static PriorityBlockingQueue<DescBigDecimal> getKaiCang() {
- if (QUEUE_KAICANG_DESC == null) {
- QUEUE_KAICANG_DESC = new PriorityBlockingQueue<DescBigDecimal>();
- }
- return QUEUE_KAICANG_DESC;
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListService.java
deleted file mode 100644
index b8b304a..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListService.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList;
-
-import com.xcong.excoin.rabbit.pricequeue.AscBigDecimal;
-import com.xcong.excoin.rabbit.pricequeue.DescBigDecimal;
-
-import java.math.BigDecimal;
-import java.util.concurrent.PriorityBlockingQueue;
-
-/**
- * 网格交易服务接口
- * 定义了网格交易的核心操作方法,包括初始化网格、开仓和平仓等操作
- * @author Administrator
- */
-public interface WangGeListService {
-
- /**
- * 初始化网格交易
- * 创建并初始化用于网格交易的价格队列,按照价格升序排列
- * @return 初始化结果信息,返回按价格升序排列的阻塞队列
- */
- PriorityBlockingQueue<AscBigDecimal> initWangGe(String markPx);
-
- /**
- * 初始化开仓操作
- * 根据指定价格初始化开仓队列,将开仓价格点加入到价格队列中
- * @param jiaGe 开仓价格
- * @param queueAsc 价格队列,用于存储按升序排列的价格点
- */
- PriorityBlockingQueue<DescBigDecimal> initKaiCang(BigDecimal jiaGe, PriorityBlockingQueue<AscBigDecimal> queueAsc);
-
- /**
- * 初始化平仓操作
- * 根据指定价格初始化平仓队列,将平仓价格点加入到价格队列中
- * @param jiaGe 开仓价格
- * @param queueAsc 价格队列,用于存储按升序排列的价格点
- */
- PriorityBlockingQueue<AscBigDecimal> initPingCang(BigDecimal jiaGe, PriorityBlockingQueue<AscBigDecimal> queueAsc);
-
-
-}
-
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListServiceImpl.java
deleted file mode 100644
index 6b3b1d6..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/wanggeList/WangGeListServiceImpl.java
+++ /dev/null
@@ -1,175 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxWs.wanggeList;
-
-import com.xcong.excoin.rabbit.pricequeue.AscBigDecimal;
-import com.xcong.excoin.rabbit.pricequeue.DescBigDecimal;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.concurrent.PriorityBlockingQueue;
-
-/**
- * 网格交易服务实现类,用于初始化价格网格、开仓和平仓操作。
- *
- * @author Administrator
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class WangGeListServiceImpl implements WangGeListService {
-
- /**
- * 初始化价格网格队列。根据配置的价格上限、下限和间隔生成一系列价格点,
- * 并将这些价格点存入升序优先阻塞队列中。
- *
- * @return 返回初始化完成的升序价格队列;若初始化失败则返回null
- */
- @Override
- public PriorityBlockingQueue<AscBigDecimal> initWangGe(String markPx) {
- log.info("网格初始化中");
- PriorityBlockingQueue<AscBigDecimal> queueAsc = WangGeListQueue.getQueueAsc();
- queueAsc.clear();
-
- //获取WangGeListEnum全部网格参数
- WangGeListEnum gridByPrice = WangGeListEnum.getGridByPrice(new BigDecimal(markPx));
- log.info("获取的网格参数: {}", gridByPrice);
- if (gridByPrice == null){
- log.error("没有获取到网格参数......");
- return null;
- }
-
- String shangxianValue = gridByPrice.getJiage_shangxian();
- String xiaxianValue = gridByPrice.getJiage_xiaxian();
- String jianjuValue = gridByPrice.getJian_ju();
- String weishuValueStr = gridByPrice.getXiaoshu_weishu();
-
- try {
- BigDecimal shangxian = new BigDecimal(shangxianValue);
- BigDecimal xiaxian = new BigDecimal(xiaxianValue);
- BigDecimal jianju = new BigDecimal(jianjuValue);
-
- if (jianju.compareTo(BigDecimal.ZERO) == 0) {
- log.error("价格间隔不能为0");
- return null;
- }
-
- int weishu = Integer.parseInt(weishuValueStr);
- BigDecimal diff = shangxian.subtract(xiaxian);
- int count = diff.divide(jianju, 0, RoundingMode.DOWN).intValue();
-
- BigDecimal currentStep = BigDecimal.ZERO;
- for (int i = 0; i <= count; i++) {
- BigDecimal stepMultiplier = currentStep.multiply(jianju);
- BigDecimal wangGeJiaGe = xiaxian.add(stepMultiplier).setScale(weishu, RoundingMode.DOWN);
- AscBigDecimal ascBigDecimal = new AscBigDecimal(wangGeJiaGe.toString());
- queueAsc.add(ascBigDecimal);
- currentStep = currentStep.add(BigDecimal.ONE);
- }
-
- if (queueAsc.isEmpty()) {
- log.info("网格初始化失败");
- return null;
- }
-
- return queueAsc;
- } catch (NumberFormatException e) {
- log.error("解析价格参数失败", e);
- return null;
- } catch (Exception e) {
- log.error("初始化网格发生未知异常", e);
- return null;
- }
- }
-
- /**
- * 根据当前价格初始化开仓队列。遍历已有的升序价格队列,
- * 将小于当前价格的所有价格点加入降序的开仓队列中。
- *
- * @param jiaGe 当前价格
- * @param queueAsc 已初始化的价格升序队列
- */
- @Override
- public PriorityBlockingQueue<DescBigDecimal> initKaiCang(BigDecimal jiaGe, PriorityBlockingQueue<AscBigDecimal> queueAsc) {
- PriorityBlockingQueue<DescBigDecimal> queueKaiCang = WangGeListQueue.getKaiCang();
- queueKaiCang.clear();
-
- AscBigDecimal now = new AscBigDecimal(jiaGe.toString());
-
- for (AscBigDecimal ascBigDecimal : queueAsc) {
- if (ascBigDecimal.compareTo(now) < 0) {
- DescBigDecimal kaiCangJia = new DescBigDecimal(ascBigDecimal.getValue().toString());
- queueKaiCang.add(kaiCangJia);
- }
- }
- StringBuilder kaiCangStr = new StringBuilder();
- kaiCangStr.append("初始化下限队列: [");
- boolean first = true;
- for (DescBigDecimal item : queueKaiCang) {
- if (!first) {
- kaiCangStr.append(", ");
- }
- kaiCangStr.append(item.getValue());
- first = false;
- }
- kaiCangStr.append("]");
- log.info(kaiCangStr.toString());
-
- return queueKaiCang;
- }
-
- /**
- * 根据当前价格初始化平仓队列。遍历已有的升序价格队列,
- * 将大于当前价格的所有价格点加入升序的平仓队列中。
- *
- * @param jiaGe 当前价格
- * @param queueAsc 已初始化的价格升序队列
- */
- @Override
- public PriorityBlockingQueue<AscBigDecimal> initPingCang(BigDecimal jiaGe, PriorityBlockingQueue<AscBigDecimal> queueAsc) {
- PriorityBlockingQueue<AscBigDecimal> queuePingCang = WangGeListQueue.getPingCang();
- queuePingCang.clear();
-
- AscBigDecimal now = new AscBigDecimal(jiaGe.toString());
-
- for (AscBigDecimal ascBigDecimal : queueAsc) {
- if (ascBigDecimal.compareTo(now) > 0) {
- queuePingCang.add(ascBigDecimal);
- }
- }
-
- StringBuilder pingCangStr = new StringBuilder();
- pingCangStr.append("初始化上限队列: [");
- boolean first = true;
- for (AscBigDecimal item : queuePingCang) {
- if (!first) {
- pingCangStr.append(", ");
- }
- pingCangStr.append(item.getValue());
- first = false;
- }
- pingCangStr.append("]");
- log.info(pingCangStr.toString());
-
- return queuePingCang;
- }
-
- /**
- * 主方法,用于测试网格初始化及开仓/平仓逻辑。
- * 示例使用固定价格"0.355"进行模拟调用。
- *
- * @param args 启动参数(未使用)
- */
- public static void main(String[] args) {
- WangGeListServiceImpl wangGeService = new WangGeListServiceImpl();
- String openPx = "2875";
- String markPx = "2905";
- String orderPx = "2895";
- PriorityBlockingQueue<AscBigDecimal> queueAsc = wangGeService.initWangGe(openPx);
- if (queueAsc != null) {
- wangGeService.initKaiCang(new BigDecimal(orderPx), queueAsc);
- wangGeService.initPingCang(new BigDecimal(orderPx), queueAsc);
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/DataUtil.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/DataUtil.java
deleted file mode 100644
index 4992a3b..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/DataUtil.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi;
-
-import java.math.BigDecimal;
-
-/**
- * 时间工具类
- *
- * @author MrBird
- */
-public class DataUtil {
-
- //输入两个整数a、b,a/b取模,输出模,如果有余数,输出模+1
- public static int mod(int a, int b) {
- int mod = a / b;
- if (mod != 0) {
- return mod + 1;
- }
- return mod;
- }
-
- public static void main(String[] args) {
- System.out.println(getDecimalDigits8("3422.66666666666666"));
- }
-
- //输入一个字符串类的小数,输出小数位数
- public static int getDecimalDigits(String num) {
- if( num.indexOf(".") == -1){
- return Integer.valueOf(num);
- } else {
- return String.valueOf(num).split("\\.")[1].length(); //split() 方法用于把一个字符串分割成字符串数组。
- }
- }
-
- //输入一个BigDecimal类的小数,输出小数位数,
- public static int getDecimalDigitsNew(BigDecimal num) {
- //除去小数点后多余的0
- num = num.stripTrailingZeros();
- if (num.scale() == 0) {
- return 0;
- } else {
- return num.scale(); //scale() 方法返回小数点后的位数。
- }
- }
-
- //输入一个包含有小数的字符串,输出原字符串,如果小数位数超过8位,则保留8位小数
- public static String getDecimalDigits8(String num) {
- if (getDecimalDigits(num) > 8) {
- return String.format("%.8f", Double.valueOf(num));
- } else {
- return num;
- }
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/MallUtils.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/MallUtils.java
deleted file mode 100644
index 9f378bf..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/MallUtils.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi;
-
-import cn.hutool.core.util.StrUtil;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Random;
-
-/**
- * @author wzy
- * @date 2021-09-22
- **/
-public class MallUtils {
-
- public static String getRandomNum(int length) {
- String str = "0123456789";
- Random random = new Random();
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < length; ++i) {
- int number = random.nextInt(str.length());
- sb.append(str.charAt(number));
- }
-
- return sb.toString();
- }
-
- public static String getOrderNum(String prefix) {
- SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
- String dd=df.format(new Date());
- if (StrUtil.isNotBlank(prefix)) {
- return prefix+dd+getRandomNum(5);
- }
- return dd+getRandomNum(5);
- }
-
- public static String getOrderNum() {
- return getOrderNum(null);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Account.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Account.java
deleted file mode 100644
index 3bf3669..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Account.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config;
-
-import lombok.Data;
-
-@Data
-public abstract class Account {
- // 交易所的基URL
- public String baseUrl;
- // 处理HTTP请求的处理器
- public RequestHandler requestHandler;
- // 表示是否显示限制使用情况
- public boolean isSimluate;
-
- public Account(){}
-
- public Account(String baseUrl, String apiKey, String secretKey, String passPhrase,boolean isSimluate) {
- this.baseUrl = baseUrl;
- this.requestHandler = new RequestHandler(apiKey, secretKey,passPhrase);
- this.isSimluate = isSimluate;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/QuantApiMessage.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/QuantApiMessage.java
deleted file mode 100644
index 48be097..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/QuantApiMessage.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto;
-
-import lombok.Data;
-
-/**
- * 交易所信息绑定
- */
-@Data
-public class QuantApiMessage{
- private Long memberId;//用户ID
- private String exchange;//交易所名称
- private String aSecretkey;//A秘钥(access_key)
- private String bSecretkey;//s秘钥(secret_key)
- private String passPhrass;//passphrass
- private String accountType;//账户类型 true:正式 false:测试
- private int state;//是否成功连通账户 0-失败 1-成功
- private int isTrade;//是否可交易 1.是 2-否
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/SubmitOrderReqDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/SubmitOrderReqDto.java
deleted file mode 100644
index 9a1cd32..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/SubmitOrderReqDto.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto;
-
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-public class SubmitOrderReqDto {
- /**
- * 是 当前价
- */
- private String price;
- /**
- * 是 产品ID,如 BTC-USDT
- */
- private String instId;
- /**
- * 是 交易模式
- * 保证金模式:isolated:逐仓 ;cross:全仓
- * 非保证金模式:cash:非保证金
- * spot_isolated:现货逐仓(仅适用于现货带单) ,现货带单时,tdMode 的值需要指定为spot_isolated
- */
- private String tdMode;
- /**
- * 否 保证金币种,仅适用于现货和合约模式下的全仓杠杆订单
- */
- private String ccy;
- /**
- * 否 客户自定义订单ID
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。
- */
- private String clOrdId;
- /**
- * 否 订单标签
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字,且长度在1-16位之间。
- */
- private String tag;
- /**
- * 是 订单方向
- * buy:买, sell:卖
- */
- private String side;
- /**
- * 可选 持仓方向
- * 在开平仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。
- */
- private String posSide;
- /**
- * 是 订单类型
- * market:市价单
- * limit:限价单
- * post_only:只做maker单
- * fok:全部成交或立即取消
- * ioc:立即成交并取消剩余
- * optimal_limit_ioc:市价委托立即成交并取消剩余(仅适用交割、永续)
- * mmp:做市商保护(仅适用于组合保证金账户模式下的期权订单)
- * mmp_and_post_only:做市商保护且只做maker单(仅适用于组合保证金账户模式下的期权订单)
- */
- private String ordType;
- /**
- * 是 委托数量
- */
- private String sz;
- /**
- * 可选 委托价格,仅适用于limit、post_only、fok、ioc、mmp、mmp_and_post_only类型的订单
- * 期权下单时,px/pxUsd/pxVol 只能填一个
- */
- private String px;
- /**
- * 可选 以USD价格进行期权下单
- * 仅适用于期权
- * 期权下单时 px/pxUsd/pxVol 必填一个,且只能填一个
- */
- private String pxUsd;
- /**
- * 可选 以隐含波动率进行期权下单,例如 1 代表 100%
- * 仅适用于期权
- * 期权下单时 px/pxUsd/pxVol 必填一个,且只能填一个
- */
- private String pxVol;
- /**
- * 否 是否只减仓,true 或 false,默认false
- * 仅适用于币币杠杆,以及买卖模式下的交割/永续
- * 仅适用于现货和合约模式和跨币种保证金模式
- */
- private Boolean reduceOnly;
- /**
- * 否 市价单委托数量sz的单位,仅适用于币币市价订单
- * base_ccy: 交易货币 ;quote_ccy:计价货币
- * 买单默认quote_ccy, 卖单默认base_ccy
- */
- private String tgtCcy;
- /**
- * 否 是否禁止币币市价改单,true 或 false,默认false
- * 为true时,余额不足时,系统不会改单,下单会失败,仅适用于币币市价单
- */
- private Boolean banAmend;
- /**
- * 否 自成交保护模式
- * 默认为 cancel maker
- * cancel_maker,cancel_taker, cancel_both
- * Cancel both不支持FOK
- */
- private String stpMode;
- /**
- * 否 下单附带止盈止损信息
- */
- private List<Object> attachAlgoOrds;
- /**
- * 否 下单附带止盈止损时,客户自定义的策略订单ID
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。
- * 订单完全成交,下止盈止损委托单时,该值会传给algoClOrdId
- */
- private String attachAlgoClOrdId;
- /**
- * 可选 止盈触发价
- * 对于条件止盈单,如果填写此参数,必须填写 止盈委托价
- */
- private String tpTriggerPx;
- /**
- * 可选 止盈委托价
- * 对于条件止盈单,如果填写此参数,必须填写 止盈触发价
- * 对于限价止盈单,需填写此参数,不需要填写止盈触发价
- * 委托价格为-1时,执行市价止盈
- */
- private String tpOrdPx;
- /**
- * 否 止盈订单类型
- * condition: 条件单
- * limit: 限价单
- * 默认为condition
- */
- private String tpOrdKind;
- /**
- * 可选 止损触发价,如果填写此参数,必须填写 止损委托价
- */
- private String slTriggerPx;
- /**
- * String 可选 止损委托价,如果填写此参数,必须填写 止损触发价
- * 委托价格为-1时,执行市价止损
- */
- private String slOrdPx;
- /**
- * 否 止盈触发价类型
- * last:最新价格
- * index:指数价格
- * mark:标记价格
- * 默认为last
- */
- private String tpTriggerPxType;
- /**
- * 否 止损触发价类型
- * last:最新价格
- * index:指数价格
- * mark:标记价格
- * 默认为last
- */
- private String slTriggerPxType;
- /**
- * 否 是否启用开仓价止损,仅适用于分批止盈的止损订单,第一笔止盈触发时,止损触发价格是否移动到开仓均价止损
- * 0:不开启,默认值
- * 1:开启,且止损触发价不能为空
- */
- private String amendPxOnTriggerType;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/TradeOrderDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/TradeOrderDto.java
deleted file mode 100644
index b11b8ba..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/Dto/TradeOrderDto.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto;
-
-import io.swagger.annotations.ApiModel;
-import lombok.Data;
-
-/**
- * @author wzy
- * @date 2021-09-16
- **/
-@Data
-@ApiModel(value = "TradeOrderDto", description = "交易订单参数接收类")
-public class TradeOrderDto {
- private String instrumentId;
- private String side;
- private String type;
- private String size;
- private String price;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeInfoEnum.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeInfoEnum.java
deleted file mode 100644
index 8aab481..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeInfoEnum.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config;
-
-import lombok.Getter;
-
-/**
- * 交易信息枚举类
- * 用于存储不同交易账户的密钥信息,包括实盘账户和模拟账户
- */
-@Getter
-public enum ExchangeInfoEnum {
-
- /**
- * 模拟盘账户信息
- * 存储了模拟盘交易所需的API密钥、秘钥和通过码
- */
-// OKX_UAT("f512673b-2685-4fcb-9bb1-2ae8db745d62",
-// "B0C1CC8F39625B41140D93DC25039E33",
-// "Aa12345678@",
-// true);
- OKX_UAT("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61",
- "AA06C5ED1D7C7F5AFE6484052E231C55",
- "Aa12345678@",
- false);
-
-// /**
-// * 模拟盘账户信息
-// * 存储了模拟盘交易所需的API密钥、秘钥和通过码
-// */
-// OKX_UAT("0769b50c-2c36-4310-8bd9-cad6bc6c9d8f",
-// "7AF4A574BC44907CE76BBFF91F53852D",
-// "Aa123456@",
-// false);
-
- // API公钥,用于识别用户身份
- private String apiKey;
-
- // API秘钥,用于签名和验证请求
- private String secretKey;
-
- // API通过码,用于额外的身份验证
- private String passphrase;
-
- // 账户类型,true表示实盘账户,false表示模拟账户
- private boolean accountType;
-
- /**
- * 构造方法
- *
- * @param apiKey API公钥,用于识别用户身份
- * @param secretKey API秘钥,用于签名和验证请求
- * @param passphrase API通过码,用于额外的身份验证
- * @param accountType 账户类型,true表示实盘账户,false表示模拟账户
- */
- ExchangeInfoEnum(String apiKey, String secretKey, String passphrase, boolean accountType) {
- this.apiKey = apiKey;
- this.secretKey = secretKey;
- this.passphrase = passphrase;
- this.accountType = accountType;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeLoginEventService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeLoginEventService.java
deleted file mode 100644
index b4d7b33..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeLoginEventService.java
+++ /dev/null
@@ -1,199 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config;
-
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.SubmitOrderReqDto;
-
-import java.util.LinkedHashMap;
-
-public interface ExchangeLoginEventService {
- /**
- * 获取交易产品基础信息
- * 获取所有可交易产品的信息列表。
- * <br><br>
- * GET /api/v5/public/instruments /api/v5/account/instruments
- * <br>
- *
- * @param parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instType -- String 是 产品类型 SPOT:币币 MARGIN:币币杠杆 SWAP:永续合约 FUTURES:交割合约 OPTION:期权 <br>
- * uly -- String 可选 标的指数,仅适用于交割/永续/期权,期权必填 <br>
- * instFamily -- String 否 交易品种,仅适用于交割/永续/期权 <br>
- * instId -- String 否 产品ID <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-public-data-get-instruments">
- * https://www.okx.com/docs-v5/zh/#rest-api-public-data-get-instruments</a>
- */
- String exchangeInfo(LinkedHashMap<String, Object> parameters);
-
- String lineHistory(LinkedHashMap<String, Object> parameters);
- /**
- * 查看账户余额
- * 获取交易账户中资金余额信息。
- * <br><br>
- * GET /api/v5/account/balance
- * <br>
- * @param
- * parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * ccy -- String 否 币种,如 BTC 支持多币种查询(不超过20个),币种之间半角逗号分隔 <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-balance">
- * https://www.okx.com/docs-v5/zh/#rest-api-account-get-balance</a>
- */
- String balance(LinkedHashMap<String, Object> parameters);
- /**
- * 查看持仓信息
- * 获取该账户下拥有实际持仓的信息。账户为单向持仓模式会显示净持仓(net),账户为双向持仓模式下会分别返回多头(long)或空头(short)的仓位。按照仓位创建时间倒序排列。
- * <br><br>
- * GET /api/v5/account/positions
- * <br>
- * @param
- * parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instType -- String 否 产品类型
- * MARGIN:币币杠杆
- * SWAP:永续合约
- * FUTURES:交割合约
- * OPTION:期权
- * instType和instId同时传入的时候会校验instId与instType是否一致。<br>
- * instId -- String 否 交易产品ID,如:BTC-USD-190927-5000-C
- * 支持多个instId查询(不超过10个),半角逗号分隔<br>
- * posId -- String 否 持仓ID
- * 支持多个posId查询(不超过20个),半角逗号分割<br>
- * @return String <br>
- * note: 如果该 instId 拥有过仓位且当前持仓量为0,传 instId 时,会返回仓位信息;不传 instId 时,仓位信息不返回。
- * 逐仓交易设置中,如果设置为自主划转模式,逐仓转入保证金后,会生成一个持仓量为0的仓位 <br>
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions">
- * https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions</a>
- */
- String positions(LinkedHashMap<String, Object> parameters);
- /**
- * 查看历史持仓信息
- * 获取最近3个月有更新的仓位信息,按照仓位更新时间倒序排列。
- * <br><br>
- * GET /api/v5/account/positions-history
- * <br>
- * @param
- * parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instType -- String 否 产品类型
- * MARGIN:币币杠杆
- * SWAP:永续合约
- * FUTURES:交割合约
- * OPTION:期权 <br>
- * instId -- String 否 交易产品ID,如:BTC-USD-SWAP <br>
- * mgnMode -- String 否 保证金模式
- * cross:全仓,isolated:逐仓
- * type -- String 否 平仓类型
- * 1:部分平仓;2:完全平仓;3:强平;4:强减; 5:ADL自动减仓;
- * 状态叠加时,以最新的平仓类型为准状态为准。 <br>
- * posId -- String 否 持仓ID <br>
- * after -- String 否 查询仓位更新 (uTime) 之前的内容,值为时间戳,Unix 时间戳为毫秒数格式,如 1597026383085 <br>
- * before -- String 否 查询仓位更新 (uTime) 之后的内容,值为时间戳,Unix 时间戳为毫秒数格式,如 1597026383085 <br>
- * limit -- String 否 分页返回结果的数量,最大为100,默认100条 <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions-history">
- * https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions-history</a>
- */
- String positionsHistory(LinkedHashMap<String, Object> parameters);
- /**
- * 撤单
- * 撤销之前下的未完成订单。
- *
- * <br><br>
- * GET /api/v5/trade/cancel-order
- * <br>
- *
- * @param originOrderId 用户自定义ID
- * LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instId -- String 是 产品ID,如 BTC-USD-190927 <br>
- * ordId -- String 可选 订单ID, ordId和clOrdId必须传一个,若传两个,以ordId为主 <br>
- * clOrdId -- String 可选 用户自定义ID <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-trade-cancel-order">
- * https://www.okx.com/docs-v5/zh/#rest-api-trade-cancel-order</a>
- */
- boolean cancelOrder(String originOrderId,String instId);
- /**
- * 下单
- * 只有当您的账户有足够的资金才能下单。
- *
- * <br><br>
- * GET /api/v5/trade/order
- * <br>
- *
- * @param submitOrderReq
- * LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instId -- String 是 产品ID,如 BTC-USD-190927-5000-C <br>
- * tdMode -- String 是 交易模式
- * 保证金模式:isolated:逐仓 ;cross:全仓
- * 非保证金模式:cash:非保证金 <br>
- * ccy -- String 否 保证金币种,仅适用于单币种保证金模式下的全仓杠杆订单 <br>
- * clOrdId -- String 否 客户自定义订单ID
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。<br>
- * tag -- String 否 订单标签
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字,且长度在1-16位之间。 <br>
- * side -- String 是 订单方向
- * buy:买, sell:卖 <br>
- * posSide -- String 可选 持仓方向
- * 在双向持仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。 <br>
- * ordType -- String 是 订单类型
- * market:市价单
- * limit:限价单
- * post_only:只做maker单
- * fok:全部成交或立即取消
- * ioc:立即成交并取消剩余
- * optimal_limit_ioc:市价委托立即成交并取消剩余(仅适用交割、永续) <br>
- * sz -- String 是 委托数量 <br>
- * px -- String 可选 委托价格,仅适用于limit、post_only、fok、ioc类型的订单 <br>
- * reduceOnly -- Boolean 否 是否只减仓,true 或 false,默认false
- * 仅适用于币币杠杆,以及买卖模式下的交割/永续
- * 仅适用于单币种保证金模式和跨币种保证金模式 <br>
- * tgtCcy -- String 否 市价单委托数量sz的单位,仅适用于币币市价订单
- * base_ccy: 交易货币 ;quote_ccy:计价货币
- * 买单默认quote_ccy, 卖单默认base_ccy <br>
- * banAmend -- Boolean 否 是否禁止币币市价改单,true 或 false,默认false
- * 为true时,余额不足时,系统不会改单,下单会失败,仅适用于币币市价单 <br>
- * tpTriggerPx -- String 否 止盈触发价,如果填写此参数,必须填写 止盈委托价 <br>
- * tpOrdPx -- String 否 止盈委托价,如果填写此参数,必须填写 止盈触发价
- * 委托价格为-1时,执行市价止盈 <br>
- * slTriggerPx -- String 否 止损触发价,如果填写此参数,必须填写 止损委托价 <br>
- * slOrdPx -- String 否 止损委托价,如果填写此参数,必须填写 止损触发价
- * 委托价格为-1时,执行市价止损 <br>
- * tpTriggerPxType -- String 否 止盈触发价类型
- * last:最新价格
- * index:指数价格
- * mark:标记价格
- * 默认为last <br>
- * slTriggerPxType -- String 否 止损触发价类型
- * last:最新价格
- * index:指数价格
- * mark:标记价格
- * 默认为last <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-trade-place-order">
- * https://www.okx.com/docs-v5/zh/#rest-api-trade-place-order</a>
- */
- String submitOrder(SubmitOrderReqDto submitOrderReq);
-
- /**
- * 获取单个币种价格信息
- * @param parameters
- * @return
- */
- String tickerMess(LinkedHashMap<String, Object> parameters);
-
- /**
- * 获取单个订单信息
- * @param parameters
- * @return
- */
- public String getOrderMessage(LinkedHashMap<String, Object> parameters);
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeLoginService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeLoginService.java
deleted file mode 100644
index 20ff07a..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeLoginService.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config;
-
-
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsException;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.impl.ExchangeLoginEventServiceImpl;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 交易所登录服务 singleton模式
- * 负责根据交易所类型提供对应的交易所登录事件服务
- */
-public class ExchangeLoginService {
- // 存储交易所类型与登录服务实例的映射
- private final static Map<String, ExchangeLoginEventService> eventMap = new HashMap<>();
-
- // 静态代码块,用于初始化eventMap
- static {
- for (ExchangeInfoEnum infoEnum : ExchangeInfoEnum.values()) {
- eventMap.put(infoEnum.name(), new ExchangeLoginEventServiceImpl(
- infoEnum.getApiKey(),
- infoEnum.getSecretKey(),
- infoEnum.getPassphrase(),
- infoEnum.isAccountType()));
- }
- }
-
- // 私有构造方法,防止外部实例化
- private ExchangeLoginService() {
- }
-
- // Singleton实例
- public final static ExchangeLoginService INSTANCE = new ExchangeLoginService();
-
- /**
- * 根据交易所类型获取对应的交易所登录事件服务
- * @param exchangeType 交易所类型
- * @return 对应的交易所登录事件服务实例
- * @throws FebsException 如果提供的交易所类型无效,则抛出异常
- */
- public static ExchangeLoginEventService getInstance(String exchangeType) {
- ExchangeLoginEventService exchange = eventMap.get(exchangeType);
- if (exchange == null) {
- throw new FebsException("参数错误");
- }
-
- return exchange;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/OKXAccount.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/OKXAccount.java
deleted file mode 100644
index 0277c5c..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/OKXAccount.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config;
-
-import lombok.Data;
-
-/**
- * OKX交易所账户配置类
- * 用于存储和管理OKX交易所的账户信息和请求处理
- */
-@Data
-public class OKXAccount extends Account{
-
- // 用于在OKX交易所进行身份验证的密码短语
- String passPhrase;
- // 表示是否显示限制使用情况
- public boolean showLimitUsage;
-
- /**
- * OKXAccount的构造方法
- *
- * @param baseUrl OKX交易所的基URL
- * @param apiKey API的密钥
- * @param secretKey 私钥
- * @param passPhrase 用于身份验证的密码短语
- * @param isSimluate 表示是否为模拟交易
- */
- public OKXAccount(String baseUrl, String apiKey, String secretKey, String passPhrase,boolean isSimluate) {
- super(baseUrl, apiKey, secretKey,passPhrase, isSimluate);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/RequestHandler.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/RequestHandler.java
deleted file mode 100644
index fbf5f94..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/RequestHandler.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config;
-
-import com.alibaba.fastjson.JSON;
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsException;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.HttpMethod;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.RequestType;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.DateUtils;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.RequestBuilder;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.SignUtils;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.UrlBuilder;
-import lombok.extern.slf4j.Slf4j;
-import okhttp3.Request;
-
-import java.util.Date;
-import java.util.LinkedHashMap;
-
-@Slf4j
-public class RequestHandler {
- private final String apiKey;
- private final String secretKey;
- private final String passphrase;
-
- public RequestHandler(String apiKey) {
- this.apiKey = apiKey;
- this.secretKey = null;
- this.passphrase = null;
- }
-
- public RequestHandler(String apiKey, String secretKey, String passphrase) {
- this.apiKey = apiKey;
- this.secretKey = secretKey;
- this.passphrase = passphrase;
- }
-
- public static void main(String[] args) {
- LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
- String queryString = UrlBuilder.joinQueryParameters(new StringBuilder("/api/v5/account/balance"), balanceParameters).toString();
- String balanceParameters1 = UrlBuilder.buildFullUrl("/api/v5/account/balance","" , balanceParameters, null);
- System.out.println(queryString);
- System.out.println(balanceParameters1);
- }
-
- /**
- * Build request based on request type and send the requests to server.
- *
- * @param baseUrl
- * @param urlPath
- * @param parameters
- * @param httpMethod
- * @param requestType
- * @return String - response from server
- */
- private String sendApiRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
- HttpMethod httpMethod, RequestType requestType, boolean isSimluate) {
- String fullUrl = UrlBuilder.buildFullUrl(baseUrl, urlPath, parameters, null);
- log.debug("{} {}", httpMethod, fullUrl);
- //System.out.println("sendApiRequest:fullUrl"+fullUrl);
- Request request;
- switch (requestType) {
- case PUBLIC:
- request = RequestBuilder.buildPublicRequest(fullUrl, httpMethod, isSimluate).build();
- break;
- case WITH_API_KEY:
- case SIGNED:
- // 获取签名
- String timestamp = DateUtils.format(DateUtils.FORMAT_UTC_ISO8601, new Date(), 0);
- String queryString = UrlBuilder.joinQueryParameters(new StringBuilder(urlPath), parameters).toString();
- // String timestamp = System.currentTimeMillis()+"";
-// System.out.println("timestamp:"+timestamp);
-// System.out.println("timestamp:"+timestamp);
-// System.out.println("secretKey:"+secretKey);
-// System.out.println("httpMethod.toString():"+httpMethod.toString());
-// System.out.println("queryString:"+queryString);
-// System.out.println("passphrase:"+passphrase);
- // 组装body
- String body = "";
- if (HttpMethod.POST.equals(httpMethod)) {
- body = JSON.toJSONString(parameters);
- queryString = UrlBuilder.joinQueryParameters(new StringBuilder(urlPath), null).toString();
- fullUrl = UrlBuilder.buildFullUrl(baseUrl, urlPath, null, null);
- }
- if (HttpMethod.GET.equals(httpMethod)) {
- queryString = UrlBuilder.buildFullUrl(urlPath,"" , parameters, null);
-// queryString = UrlBuilder.buildFullUrl(null, urlPath, parameters, null);
- }
- String sign = SignUtils.signRest(secretKey,
- timestamp,
- httpMethod.toString(),
- queryString, body);
-
-
- request = RequestBuilder.buildApiKeyRequest(fullUrl, body, passphrase, sign, timestamp, httpMethod, apiKey,isSimluate);
-
-
- break;
- default:
- throw new FebsException("[RequestHandler] Invalid request type: " + requestType);
- }
- return ResponseHandler.handleResponse(request, isSimluate);
- }
-
- public String sendPublicRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
- HttpMethod httpMethod, boolean isSimluate) {
- return sendApiRequest(baseUrl, urlPath, parameters, httpMethod, RequestType.PUBLIC, isSimluate);
- }
-
- public String sendWithApiKeyRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
- HttpMethod httpMethod, boolean isSimluate) {
- if (null == apiKey || apiKey.isEmpty()) {
- throw new FebsException("[RequestHandler] API key cannot be null or empty!");
- }
- return sendApiRequest(baseUrl, urlPath, parameters, httpMethod, RequestType.WITH_API_KEY, isSimluate);
- }
-
- public String sendSignedRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
- HttpMethod httpMethod, boolean isSimluate) {
- if (null == secretKey || secretKey.isEmpty() || null == apiKey || apiKey.isEmpty()) {
- throw new FebsException("[RequestHandler] Secret key/API key cannot be null or empty!");
- }
- //parameters.put("timestamp", UrlBuilder.buildTimestamp());
- //String queryString = UrlBuilder.joinQueryParameters(parameters);
- //String signature = SignatureGenerator.getSignature(queryString, secretKey);
- return sendApiRequest(baseUrl, urlPath, parameters, httpMethod, RequestType.SIGNED, isSimluate);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ResponseHandler.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ResponseHandler.java
deleted file mode 100644
index 3ef2c50..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ResponseHandler.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config;
-
-
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsException;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.JSONParser;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.OkHttpUtils;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-import org.json.JSONException;
-
-import java.io.IOException;
-
-public final class ResponseHandler {
-
- private static final int HTTP_STATUS_CODE_400 = 400;
- private static final int HTTP_STATUS_CODE_499 = 499;
- private static final int HTTP_STATUS_CODE_500 = 500;
-
- private ResponseHandler() {
- }
-
- public static String handleResponse(Request request, boolean showLimitUsage) {
- try (Response response = OkHttpUtils.okHttpClient.newCall(request).execute()) {//OkHttpUtils.builder().okHttpClient
- String responseAsString = getResponseBodyAsString(response.body());
-
- if (response.code() >= HTTP_STATUS_CODE_400 && response.code() <= HTTP_STATUS_CODE_499) {
- throw handleErrorResponse(responseAsString, response.code());
- } else if (response.code() >= HTTP_STATUS_CODE_500) {
- System.out.println("handleResponse:"+response.code());
- throw new FebsException("responseAsString-"+responseAsString+";handleResponse-"+response.code());
- }
- return responseAsString;
-// if (showLimitUsage) {
-// return getlimitUsage(response, responseAsString);
-// } else {
-// return responseAsString;
-// }
- } catch (IOException | IllegalStateException e) {
- e.printStackTrace();
- throw new FebsException("[ResponseHandler] OKHTTP Error: " + e.getMessage());
- }
- }
-
-
- private static FebsException handleErrorResponse(String responseBody, int responseCode) {
- try {
- String errorMsg = JSONParser.getJSONStringValue(responseBody, "msg");
- int errorCode = JSONParser.getJSONIntValue(responseBody, "code");
- return new FebsException("responseBody-"+responseBody+";errorMsg-"+errorMsg+";responseCode-"+responseCode+";errorCode-"+errorCode);
- } catch (JSONException e) {
- throw new FebsException("responseBody-"+responseBody+";responseCode-"+responseCode);
- }
- }
-
- private static String getResponseBodyAsString(ResponseBody body) throws IOException {
- if (null != body) {
- return body.string();
- } else {
- return "";
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/DefaultUrls.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/DefaultUrls.java
deleted file mode 100644
index a666b21..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/DefaultUrls.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums;
-
-public final class DefaultUrls {
- public static final String USDM_UAT_URL = "https://www.okx.com";
- public static final String USDM_UAT_WSS_URL = "wss://wspap.okx.com:8443";
- //public static final String USDM_UAT_WSS_URL = "wss://ws.okx.com:8443";
- //USD-M Futures
- public static final String USDM_PROD_URL = "https://www.okx.com";
- public static final String USDM_PROD_WS_URL = "wss://ws.okx.com:8443";
- //比特币买入数量
- public static final String BTC_BUYNUMBER = "0.001";
- //以太坊买入数量
- public static final String ETH_BUYNUMBER = "0.01";
- //狗狗币买入数量
- public static final String DOGE_BUYNUMBER = "100";
- /**
- * 全部卖出
- */
- public static final String OPERATION_ALLSOLD = "allsold";
-
- /**
- * 卖出
- */
- public static final String OPERATION_SOLD = "sell";
-
- /**
- * 买入
- */
- public static final String OPERATION_BUY = "buy";
-
- /**
- * 不买入
- */
- public static final String OPERATION_NOBUY = "nobuy";
-
- private DefaultUrls() {
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/HttpMethod.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/HttpMethod.java
deleted file mode 100644
index 44f0a21..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/HttpMethod.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums;
-
-public enum HttpMethod {
- POST,
- GET,
- PUT,
- DELETE,
- INVALID
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/RequestType.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/RequestType.java
deleted file mode 100644
index e50bc1b..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/RequestType.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums;
-
-public enum RequestType {
- PUBLIC,
- WITH_API_KEY,
- SIGNED
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/impl/ExchangeLoginEventServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/impl/ExchangeLoginEventServiceImpl.java
deleted file mode 100644
index 3d781ed..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/impl/ExchangeLoginEventServiceImpl.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.impl;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.SubmitOrderReqDto;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeInfoEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeLoginEventService;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeLoginService;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.DefaultUrls;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.HttpMethod;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.OKXContants;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.util.*;
-
-@Slf4j
-public class ExchangeLoginEventServiceImpl implements ExchangeLoginEventService {
-
-
- private OKXAccount OKXAccount;
- private String apiKey;
- private String secretKey;
- private String passphrase;
- private boolean accountType;
-
- public ExchangeLoginEventServiceImpl(String apiKey, String secretKey, String passphrase, boolean accountType) {
- this.apiKey = apiKey;
- this.secretKey = secretKey;
- this.passphrase = passphrase;
- this.accountType = accountType;
- OKXAccount = new OKXAccount(
- accountType ? DefaultUrls.USDM_PROD_URL : DefaultUrls.USDM_UAT_URL,
- apiKey,
- secretKey,
- passphrase,
- !accountType);
- }
-
- @Override
- public String exchangeInfo(LinkedHashMap<String, Object> parameters) {
- return OKXAccount.requestHandler.sendPublicRequest(OKXAccount.baseUrl, OKXContants.INSTRUMENTS,parameters, HttpMethod.GET, OKXAccount.isSimluate());
- }
-
- @Override
- public String lineHistory(LinkedHashMap<String, Object> parameters) {
- return OKXAccount.requestHandler.sendPublicRequest(OKXAccount.baseUrl, OKXContants.K_LINE_HISTORY,parameters, HttpMethod.GET, OKXAccount.isSimluate());
- }
-
- @Override
- public String balance(LinkedHashMap<String, Object> parameters) {
- return OKXAccount.requestHandler.sendSignedRequest(OKXAccount.baseUrl, OKXContants.BALANCE, parameters, HttpMethod.GET, OKXAccount.isSimluate());
- }
-
- @Override
- public String positions(LinkedHashMap<String, Object> parameters) {
- return OKXAccount.requestHandler.sendSignedRequest(OKXAccount.baseUrl, OKXContants.POSITIONS, parameters, HttpMethod.GET, OKXAccount.isSimluate());
- }
-
- @Override
- public String positionsHistory(LinkedHashMap<String, Object> parameters) {
- return OKXAccount.requestHandler.sendSignedRequest(OKXAccount.baseUrl, OKXContants.POSITIONS_HISTORY, parameters, HttpMethod.GET, OKXAccount.isSimluate());
- }
-
- @Override
- public boolean cancelOrder(String originOrderId,String instId) {
- LinkedHashMap<String, Object> parameters = new LinkedHashMap<>();
- parameters.put("instId", instId);
- parameters.put("clOrdId", originOrderId);
-
- String s = OKXAccount.requestHandler.sendSignedRequest(OKXAccount.baseUrl, OKXContants.CANCEL_ORDER, parameters, HttpMethod.POST, OKXAccount.isSimluate());
- log.info("[{}] 收到撤单请求,返回", s);
- return true;
- }
-
- @Override
- public String submitOrder(SubmitOrderReqDto submitOrderReq) {
- log.info("收到下单请求,参数:[{}]", submitOrderReq);
- LinkedHashMap<String, Object> parameters = new LinkedHashMap<>();
-
- String side = submitOrderReq.getSide();
- String type = submitOrderReq.getOrdType();
- String positionSides = submitOrderReq.getPosSide();
- // 开仓
- //开多:买多BUY、LONG
- //开空:卖空SELL、SHORT
- if ("buy".equals(side)) {
- if ("limit".equals(type)) {
- parameters.put("px", submitOrderReq.getPx());
- parameters.put("ordType", "limit");
- }
- // 持仓方向
- parameters.put("posSide", positionSides);
- //placeOrderReq.setPosSide(positionSide);
- // slTriggerPx 止损触发价,如果填写此参数,必须填写 止损委托价
- // slOrdPx 止损委托价,如果填写此参数,必须填写 止损触发价
-
- if (new BigDecimal(submitOrderReq.getSlTriggerPx()).compareTo(BigDecimal.ZERO) > 0) {
- Map<String, Object> attachAlgoOrder = new HashMap<>();
- // 如果是开多 止损价小于传来的价格
-
- List<Map<String, Object>> attachAlgoOrds = new ArrayList<>();
- // 如果是开空 止损价小于传来的价格
- attachAlgoOrder.put("slTriggerPx", submitOrderReq.getSlTriggerPx());
- attachAlgoOrder.put("slOrdPx", submitOrderReq.getSlOrdPx());
- attachAlgoOrds.add(attachAlgoOrder);
- parameters.put("attachAlgoOrds", attachAlgoOrds);
- } else {
- BigDecimal price = new BigDecimal(submitOrderReq.getPrice());
- BigDecimal stopPrice = BigDecimal.ZERO;
- if ("buy".equalsIgnoreCase(side)) {
- stopPrice = price.multiply(new BigDecimal("0.99")).setScale(2,BigDecimal.ROUND_DOWN);
- } else {
- stopPrice = price.multiply(new BigDecimal("1.01")).setScale(2,BigDecimal.ROUND_DOWN);
- }
- Map<String, Object> attachAlgoOrder = new HashMap<>();
- // 如果是开多 止损价小于传来的价格
- List<Map<String, Object>> attachAlgoOrds = new ArrayList<>();
- // 如果是开空 止损价小于传来的价格
- attachAlgoOrder.put("slTriggerPx", stopPrice);
- attachAlgoOrder.put("slOrdPx", stopPrice);
- attachAlgoOrds.add(attachAlgoOrder);
- parameters.put("attachAlgoOrds", attachAlgoOrds);
- }
- } else {
- // 平仓
- //平空:卖空BUY、SHORT
- //平多:卖多SELL、LONG
- //side = (CoreEnum.DirectionEnum.D_Buy.getNumber() == direction.getNumber()) ? "BUY" : "SELL";
- // 平仓方向
- //positionSide = (CoreEnum.DirectionEnum.D_Buy.getNumber() == direction.getNumber()) ? "SHORT" : "LONG";
- if ("limit".equals(type)) {
- //placeOrderReq.setPx(new BigDecimal(submitOrderReq.price()));
- parameters.put("px", submitOrderReq.getPrice());
- //parameters.put("timeInForce", timeInForce);
- }
- //placeOrderReq.setPosSide(positionSide);
- parameters.put("posSide", positionSides);
- }
-
- //订单种类,市价单不传价格
- parameters.put("instId", submitOrderReq.getInstId());
- parameters.put("side", side);
- //placeOrderReq.setSide(side);
- parameters.put("ordType", type);
- //placeOrderReq.setSz(new BigDecimal(quantity));
- parameters.put("sz", submitOrderReq.getSz());
- //placeOrderReq.setClOrdId(submitOrderReq.originOrderId());
- parameters.put("clOrdId", submitOrderReq.getClOrdId());
- parameters.put("tdMode", submitOrderReq.getTdMode());
- log.info("下单参数:[{}]",JSON.toJSONString(parameters));
- String placeOrderRspOkxRestResponse = OKXAccount.requestHandler.sendSignedRequest(OKXAccount.baseUrl, OKXContants.ORDER, parameters, HttpMethod.POST, OKXAccount.isSimluate());
- log.info("收到下单返回,响应:[{}]", JSON.parseObject(placeOrderRspOkxRestResponse).get("data"));
- return submitOrderReq.getClOrdId();
- }
-
- @Override
- public String tickerMess(LinkedHashMap<String, Object> parameters) {
- return OKXAccount.requestHandler.sendSignedRequest(OKXAccount.baseUrl, OKXContants.TICKER, parameters, HttpMethod.GET, OKXAccount.isSimluate());
- }
-
- @Override
- public String getOrderMessage(LinkedHashMap<String, Object> parameters) {
- return OKXAccount.requestHandler.sendSignedRequest(OKXAccount.baseUrl, OKXContants.TICKER, parameters, HttpMethod.GET, OKXAccount.isSimluate());
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/DateUtils.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/DateUtils.java
deleted file mode 100644
index 4d5199e..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/DateUtils.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.time.ZoneId;
-import java.util.Date;
-import java.util.TimeZone;
-
-public class DateUtils {
-
- public static final String FORMAT_Y = "yyyy";
- public static final String FORMAT_D_1 = "yyyy/MM/dd";
- public static final String FORMAT_D_2 = "yyyy-MM-dd";
- public static final String FORMAT_D_3 = "yyyyMMdd";
- public static final String FORMAT_D_4 = "yyyy.MM.dd";
- public static final String FORMAT_D = "dd";
- public static final String FORMAT_DT_1 = "yyyy/MM/dd HH:mm:ss";
- public static final String FORMAT_DT_2 = "yyyy-MM-dd HH:mm:ss";
- public static final String FORMAT_DT_3 = "yyyyMMdd HH:mm:ss";
- public static final String FORMAT_DT_4 = "yyyy-MM-dd HH:mm";
- public static final String FORMAT_DT_5 = "yyyy.MM.dd HH:mm:ss";
- public static final String FORMAT_DT_6 = "yyyyMMddHHmmss";
- public static final String FORMAT_DT_7 = "yyyyMMddHH";
- public static final String FORMAT_M_1 = "yyyy/MM";
- public static final String FORMAT_M_2 = "yyyy-MM";
- public static final String FORMAT_M_3 = "yyyyMM";
- public static final String FORMAT_M = "MM";
- public static final String FORMAT_MD_1 = "MM/dd";
- public static final String FORMAT_MD_2 = "MM-dd";
- public static final String FORMAT_MD_3 = "MMdd";
- public static final String FORMAT_T_1 = "HH:mm:ss";
- public static final String FORMAT_T_2 = "HH:mm";
- public static final String FORMAT_TH = "HH";
- public static final String FORMAT_TM = "mm";
- public static final String FORMAT_TS = "ss";
- public static final String FORMAT_UTC_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss'Z'";
-
- public static String format(String format, Date date) {
- SimpleDateFormat sdf = new SimpleDateFormat(format);
- return sdf.format(date);
- }
-
- /**
- * @param format format
- * @param date date
- * @param timeZone 时区数字 -8, 0, 8 等
- * @return date string
- */
- public static String format(String format, Date date, int timeZone) {
- timeZone = timeZone % 13;
- SimpleDateFormat sdf = new SimpleDateFormat(format);
- ZoneId zoneId = ZoneId.of("GMT" + (timeZone >= 0 ? "+" : "") + timeZone);
- TimeZone tz = TimeZone.getTimeZone(zoneId);
- sdf.setTimeZone(tz);
- return sdf.format(date);
- }
-
- public static Date parse(String dateString, String format) {
- SimpleDateFormat sdf = new SimpleDateFormat(format);
-
- try {
- return sdf.parse(dateString);
- } catch (ParseException var4) {
- return null;
- }
- }
-
- public static Date parse(String dateString, String format, int timeZone) {
- SimpleDateFormat sdf = new SimpleDateFormat(format);
- ZoneId zoneId = ZoneId.of("GMT" + (timeZone >= 0 ? "+" : "") + timeZone);
- TimeZone tz = TimeZone.getTimeZone(zoneId);
- sdf.setTimeZone(tz);
- try {
- return sdf.parse(dateString);
- } catch (ParseException var4) {
- return null;
- }
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/JSONParser.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/JSONParser.java
deleted file mode 100644
index cf41cbc..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/JSONParser.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-
-public final class JSONParser {
-
- private JSONParser() {
- }
-
- public static String getJSONStringValue(String json, String key) {
- try {
- JSONObject obj = new JSONObject(json);
- return obj.getString(key);
- } catch (JSONException e) {
- throw new JSONException(String.format("[JSONParser] Failed to get \"%s\" from JSON object", key));
- }
- }
-
- public static int getJSONIntValue(String json, String key) {
- try {
- JSONObject obj = new JSONObject(json);
- return obj.getInt(key);
- } catch (JSONException e) {
- throw new JSONException(String.format("[JSONParser] Failed to get \"%s\" from JSON object", key));
- }
- }
-
- public static String getJSONArray(ArrayList<?> symbols, String key) {
- try {
- JSONArray arr = new JSONArray(symbols);
- return arr.toString();
- } catch (JSONException e) {
- throw new JSONException(String.format("[JSONParser] Failed to convert \"%s\" to JSON array", key));
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/OKXContants.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/OKXContants.java
deleted file mode 100644
index ac69758..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/OKXContants.java
+++ /dev/null
@@ -1,224 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils;
-
-public class OKXContants {
- /**
- * 获取交易产品基础信息
- * 获取所有可交易产品的信息列表。
- * <br><br>
- * GET /api/v5/public/instruments
- * <br>
- *
- * @param parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instType -- String 是 产品类型 SPOT:币币 MARGIN:币币杠杆 SWAP:永续合约 FUTURES:交割合约 OPTION:期权 <br>
- * uly -- String 可选 标的指数,仅适用于交割/永续/期权,期权必填 <br>
- * instFamily -- String 否 交易品种,仅适用于交割/永续/期权 <br>
- * instId -- String 否 产品ID <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-public-data-get-instruments">
- * https://www.okx.com/docs-v5/zh/#rest-api-public-data-get-instruments</a>
- */
- public static final String INSTRUMENTS = "/api/v5/public/instruments";
- public static final String K_LINE_HISTORY_MARK_PRICE = "/api/v5/market/history-mark-price-candles";
- public static final String K_LINE_HISTORY = "/api/v5/market/history-candles";
- /**
- * 查看账户余额
- * 获取交易账户中资金余额信息。
- * <br><br>
- * GET /api/v5/account/balance
- * <br>
- * @param
- * parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * ccy -- String 否 币种,如 BTC 支持多币种查询(不超过20个),币种之间半角逗号分隔 <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-balance">
- * https://www.okx.com/docs-v5/zh/#rest-api-account-get-balance</a>
- */
- public static final String BALANCE = "/api/v5/account/balance";
- /**
- * /api/v5/asset/balances
- * 获取资金账户余额
- * 获取资金账户所有资产列表,查询各币种的余额、冻结和可用等信息。
- * 币种,如 BTC
- * 支持多币种查询(不超过20个),币种之间半角逗号分隔
- */
- public static final String ACCOUNT_BALANCE = "/api/v5/asset/balances";
- /**
- * 查看持仓信息
- * 获取该账户下拥有实际持仓的信息。账户为单向持仓模式会显示净持仓(net),账户为双向持仓模式下会分别返回多头(long)或空头(short)的仓位。按照仓位创建时间倒序排列。
- * <br><br>
- * GET /api/v5/account/positions
- * <br>
- * @param
- * parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instType -- String 否 产品类型
- * MARGIN:币币杠杆
- * SWAP:永续合约
- * FUTURES:交割合约
- * OPTION:期权
- * instType和instId同时传入的时候会校验instId与instType是否一致。<br>
- * instId -- String 否 交易产品ID,如:BTC-USD-190927-5000-C
- * 支持多个instId查询(不超过10个),半角逗号分隔<br>
- * posId -- String 否 持仓ID
- * 支持多个posId查询(不超过20个),半角逗号分割<br>
- * @return String <br>
- * note: 如果该 instId 拥有过仓位且当前持仓量为0,传 instId 时,会返回仓位信息;不传 instId 时,仓位信息不返回。
- * 逐仓交易设置中,如果设置为自主划转模式,逐仓转入保证金后,会生成一个持仓量为0的仓位 <br>
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions">
- * https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions</a>
- */
- public static final String POSITIONS = "/api/v5/account/positions";
- /**
- * 查看历史持仓信息
- * 获取最近3个月有更新的仓位信息,按照仓位更新时间倒序排列。
- * <br><br>
- * GET /api/v5/account/positions-history
- * <br>
- * @param
- * parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instType -- String 否 产品类型
- * MARGIN:币币杠杆
- * SWAP:永续合约
- * FUTURES:交割合约
- * OPTION:期权 <br>
- * instId -- String 否 交易产品ID,如:BTC-USD-SWAP <br>
- * mgnMode -- String 否 保证金模式
- * cross:全仓,isolated:逐仓
- * type -- String 否 平仓类型
- * 1:部分平仓;2:完全平仓;3:强平;4:强减; 5:ADL自动减仓;
- * 状态叠加时,以最新的平仓类型为准状态为准。 <br>
- * posId -- String 否 持仓ID <br>
- * after -- String 否 查询仓位更新 (uTime) 之前的内容,值为时间戳,Unix 时间戳为毫秒数格式,如 1597026383085 <br>
- * before -- String 否 查询仓位更新 (uTime) 之后的内容,值为时间戳,Unix 时间戳为毫秒数格式,如 1597026383085 <br>
- * limit -- String 否 分页返回结果的数量,最大为100,默认100条 <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions-history">
- * https://www.okx.com/docs-v5/zh/#rest-api-account-get-positions-history</a>
- */
- public static final String POSITIONS_HISTORY = "/api/v5/account/positions-history";
- /**
- * 撤单
- * 撤销之前下的未完成订单。
- *
- * <br><br>
- * GET /api/v5/trade/cancel-order
- * <br>
- *
- * @param parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instId -- String 是 产品ID,如 BTC-USD-190927 <br>
- * ordId -- String 可选 订单ID, ordId和clOrdId必须传一个,若传两个,以ordId为主 <br>
- * clOrdId -- String 可选 用户自定义ID <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-trade-cancel-order">
- * https://www.okx.com/docs-v5/zh/#rest-api-trade-cancel-order</a>
- */
- public static final String CANCEL_ORDER = "/api/v5/trade/cancel-order";
- /**
- * 下单
- * 只有当您的账户有足够的资金才能下单。
- *
- * <br><br>
- * GET /api/v5/trade/order
- * <br>
- *
- * @param parameters LinkedHashedMap of String,Object pair
- * where String is the name of the parameter and Object is the value of the parameter
- * <br><br>
- * instId -- String 是 产品ID,如 BTC-USD-190927-5000-C <br>
- * tdMode -- String 是 交易模式
- * 保证金模式:isolated:逐仓 ;cross:全仓
- * 非保证金模式:cash:非保证金 <br>
- * ccy -- String 否 保证金币种,仅适用于单币种保证金模式下的全仓杠杆订单 <br>
- * clOrdId -- String 否 客户自定义订单ID
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。<br>
- * tag -- String 否 订单标签
- * 字母(区分大小写)与数字的组合,可以是纯字母、纯数字,且长度在1-16位之间。 <br>
- * side -- String 是 订单方向
- * buy:买, sell:卖 <br>
- * posSide -- String 可选 持仓方向
- * 在双向持仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。 <br>
- * ordType -- String 是 订单类型
- * market:市价单
- * limit:限价单
- * post_only:只做maker单
- * fok:全部成交或立即取消
- * ioc:立即成交并取消剩余
- * optimal_limit_ioc:市价委托立即成交并取消剩余(仅适用交割、永续) <br>
- * sz -- String 是 委托数量 <br>
- * px -- String 可选 委托价格,仅适用于limit、post_only、fok、ioc类型的订单 <br>
- * reduceOnly -- Boolean 否 是否只减仓,true 或 false,默认false
- * 仅适用于币币杠杆,以及买卖模式下的交割/永续
- * 仅适用于单币种保证金模式和跨币种保证金模式 <br>
- * tgtCcy -- String 否 市价单委托数量sz的单位,仅适用于币币市价订单
- * base_ccy: 交易货币 ;quote_ccy:计价货币
- * 买单默认quote_ccy, 卖单默认base_ccy <br>
- * banAmend -- Boolean 否 是否禁止币币市价改单,true 或 false,默认false
- * 为true时,余额不足时,系统不会改单,下单会失败,仅适用于币币市价单 <br>
- * tpTriggerPx -- String 否 止盈触发价,如果填写此参数,必须填写 止盈委托价 <br>
- * tpOrdPx -- String 否 止盈委托价,如果填写此参数,必须填写 止盈触发价
- * 委托价格为-1时,执行市价止盈 <br>
- * slTriggerPx -- String 否 止损触发价,如果填写此参数,必须填写 止损委托价 <br>
- * slOrdPx -- String 否 止损委托价,如果填写此参数,必须填写 止损触发价
- * 委托价格为-1时,执行市价止损 <br>
- * tpTriggerPxType -- String 否 止盈触发价类型
- * last:最新价格
- * index:指数价格
- * mark:标记价格
- * 默认为last <br>
- * slTriggerPxType -- String 否 止损触发价类型
- * last:最新价格
- * index:指数价格
- * mark:标记价格
- * 默认为last <br>
- * @return String
- * @see <a href="https://www.okx.com/docs-v5/zh/#rest-api-trade-place-order">
- * https://www.okx.com/docs-v5/zh/#rest-api-trade-place-order</a>
- */
- public static final String ORDER = "/api/v5/trade/order";
-
- /**
- * 获取币种价格信息
- */
- public static final String TICKER = "/api/v5/market/ticker";
-
- /**
- * 获取杠杆倍数
- */
- public static final String LEVERAGE = "/api/v5/account/leverage-info";
-
- /**
- * 设置杠杆倍数
- */
- public static final String SETLEVERAGE = "/api/v5/account/set-leverage";
-
-
- /**
- * 获取支持大数据的币种列表
- */
- public static final String TRADEDATA = "/api/v5/rubik/stat/trading-data/support-coin";
-
- /**
- * 获取合约主动买入/卖出情况
- */
- public static final String BUYSELLSITUATION = "/api/v5/rubik/stat/taker-volume-contract";
-
- /**
- * 获取合约多空持仓人数比
- */
- public static final String POSITIONRATIO = "/api/v5/rubik/stat/contracts/long-short-account-ratio-contract";
-
- /**
- * 获取合约持仓量及交易量
- */
- public static final String POSITIONVOLUME = "/api/v5/rubik/stat/contracts/open-interest-volume";
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/OkHttpUtils.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/OkHttpUtils.java
deleted file mode 100644
index 1c349bb..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/OkHttpUtils.java
+++ /dev/null
@@ -1,330 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils;
-
-import lombok.extern.slf4j.Slf4j;
-import okhttp3.*;
-import org.json.JSONObject;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * OkHttp请求工具封装
- * 参考:https://blog.csdn.net/DwZ735660836/article/details/119976068
- */
-@Slf4j
-public class OkHttpUtils {
- public static volatile OkHttpClient okHttpClient = null;
- private static volatile Semaphore semaphore = null;
- private Map<String, String> headerMap;
- private Map<String, String> paramMap;
- private String url;
- private Request.Builder request;
- // 开发环境用的 ShadowsocksR-dotnet4.0 免费版本 正式环境得使用外网服务器
- // 安易代理 http://127.0.0.1:10809/ http://127.0.0.1:10808/
-
- /**
- * 初始化okHttpClient,并且允许https访问
- */
- private OkHttpUtils() {
- if (okHttpClient == null) {
- synchronized (OkHttpUtils.class) {
- if (okHttpClient == null) {
- TrustManager[] trustManagers = buildTrustManagers();
- okHttpClient = new OkHttpClient.Builder()
- .connectTimeout(30, TimeUnit.SECONDS)
- .writeTimeout(20, TimeUnit.SECONDS)
- .readTimeout(60, TimeUnit.SECONDS)
- .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
- //.hostnameVerifier((hostName, session) -> true)
- //配置自定义连接池参数
- .connectionPool(new ConnectionPool(5, 60, TimeUnit.SECONDS))
- .retryOnConnectionFailure(true)
- .build();
- addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
- addHeader("Connection", "close");
- addHeader("Accept-Encoding", "identity");
- }
- }
- }
- }
-
- /**
- * 用于异步请求时,控制访问线程数,返回结果
- *
- * @return
- */
- private static Semaphore getSemaphoreInstance() {
- //只能1个线程同时访问
- synchronized (OkHttpUtils.class) {
- if (semaphore == null) {
- semaphore = new Semaphore(0);
- }
- }
- return semaphore;
- }
-
- /**
- * 创建OkHttpUtils
- *
- * @return
- */
- public static OkHttpUtils builder() {
- return new OkHttpUtils();
- }
-
- /**
- * 添加url
- *
- * @param url
- * @return
- */
- public OkHttpUtils url(String url) {
- this.url = url;
- return this;
- }
-
- /**
- * 添加参数
- *
- * @param key 参数名
- * @param value 参数值
- * @return
- */
- public OkHttpUtils addParam(String key, String value) {
- if (paramMap == null) {
- paramMap = new LinkedHashMap<>(16);
- }
- paramMap.put(key, value);
- return this;
- }
-
- /**
- * 添加请求头
- *
- * @param key 参数名
- * @param value 参数值
- * @return
- */
- public OkHttpUtils addHeader(String key, String value) {
- if (headerMap == null) {
- headerMap = new LinkedHashMap<>(16);
- }
- headerMap.put(key, value);
- return this;
- }
-
- /**
- * 初始化get方法
- *
- * @return
- */
- public OkHttpUtils get() {
- request = new Request.Builder().get();
- StringBuilder urlBuilder = new StringBuilder(url);
- if (paramMap != null) {
- urlBuilder.append("?");
- try {
- for (Map.Entry<String, String> entry : paramMap.entrySet()) {
- urlBuilder.append(URLEncoder.encode(entry.getKey(), "utf-8")).
- append("=").
- append(URLEncoder.encode(entry.getValue(), "utf-8")).
- append("&");
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- urlBuilder.deleteCharAt(urlBuilder.length() - 1);
- }
- request.url(urlBuilder.toString());
- return this;
- }
-
- /**
- * 初始化post方法
- *
- * @param isJsonPost true等于json的方式提交数据,类似postman里post方法的raw
- * false等于普通的表单提交
- * @return
- */
- public OkHttpUtils post(boolean isJsonPost) {
- RequestBody requestBody;
- if (isJsonPost) {
- String json = "";
- if (paramMap != null) {
- json = JSONObject.valueToString(paramMap);
- }
- requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
- } else {
- FormBody.Builder formBody = new FormBody.Builder();
- if (paramMap != null) {
- paramMap.forEach(formBody::add);
- }
- requestBody = formBody.build();
- }
- request = new Request.Builder().post(requestBody).url(url);
- return this;
- }
-
- /**
- * 同步请求
- *
- * @return
- */
- public Request.Builder sync() {
- return setHeader(request);
- }
-
- /**
- * 同步请求
- *
- * @return
- */
- public String syncStr() {
- setHeader(request);
- try {
- Response response = okHttpClient.newCall(request.build()).execute();
- assert response.body() != null;
- return response.body().string();
- } catch (IOException e) {
- e.printStackTrace();
- return "请求失败:" + e.getMessage();
- }
- }
-
-
- /**
- * 异步请求,有返回值
- */
- public String async() {
- StringBuilder buffer = new StringBuilder("");
- setHeader(request);
- okHttpClient.newCall(request.build()).enqueue(new Callback() {
- @Override
- public void onFailure(Call call, IOException e) {
- buffer.append("请求出错:").append(e.getMessage());
- }
-
- @Override
- public void onResponse(Call call, Response response) throws IOException {
- assert response.body() != null;
- buffer.append(response.body().string());
- getSemaphoreInstance().release();
- }
- });
- try {
- getSemaphoreInstance().acquire();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return buffer.toString();
- }
-
- /**
- * 异步请求,带有接口回调
- *
- * @param callBack
- */
- public void async(ICallBack callBack) {
- setHeader(request);
- okHttpClient.newCall(request.build()).enqueue(new Callback() {
- @Override
- public void onFailure(Call call, IOException e) {
- callBack.onFailure(call, e.getMessage());
- }
-
- @Override
- public void onResponse(Call call, Response response) throws IOException {
- assert response.body() != null;
- callBack.onSuccessful(call, response.body().string());
- }
- });
- }
-
- /**
- * 为request添加请求头
- *
- * @param request
- */
- private Request.Builder setHeader(Request.Builder request) {
- if (headerMap != null) {
- try {
- for (Map.Entry<String, String> entry : headerMap.entrySet()) {
- request.addHeader(entry.getKey(), entry.getValue());
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- return request;
- }
-
-
- /**
- * 生成安全套接字工厂,用于https请求的证书跳过
- *
- * @return
- */
- private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
- SSLSocketFactory ssfFactory = null;
- try {
- SSLContext sc = SSLContext.getInstance("SSL");
- sc.init(null, trustAllCerts, new SecureRandom());
- ssfFactory = sc.getSocketFactory();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return ssfFactory;
- }
-
- private static TrustManager[] buildTrustManagers() {
- return new TrustManager[]{
- new X509TrustManager() {
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String authType) {
- }
-
- @Override
- public void checkServerTrusted(X509Certificate[] chain, String authType) {
- }
-
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[]{};
- }
- }
- };
- }
-
- /**
- * 自定义一个接口回调
- */
- public interface ICallBack {
-
- void onSuccessful(Call call, String data);
-
- void onFailure(Call call, String errorMsg);
-
- }
-
- public static void main(String[] args) {
- String url = "https://api2.binance.com/api/v3/ticker/24hr?symbol=BNBUSDT&type=MINI";
- String result = OkHttpUtils.builder()
- .url(url)
- .addHeader("Content-Type", "application/x-www-form-urlencoded")
- .get()
- .syncStr();
- System.out.println(result);
- }
-}
-
-
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/ParameterChecker.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/ParameterChecker.java
deleted file mode 100644
index 1eec770..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/ParameterChecker.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils;
-
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsException;
-
-import java.util.LinkedHashMap;
-
-public final class ParameterChecker {
-
- private ParameterChecker() {
- }
-
- public static void checkParameter(LinkedHashMap<String, Object> parameters, String parameter, Class t) {
- checkRequiredParameter(parameters, parameter);
- checkParameterType(parameters.get(parameter), t, parameter);
- }
-
- public static void checkOrParameters(LinkedHashMap<String, Object> parameters, String parameter, String parameter2) {
- if (!parameters.containsKey(parameter) && (!parameters.containsKey(parameter2))) {
- throw new FebsException(String.format("Either \"%s\" or \"%s\" is required!", parameter, parameter2));
- }
- }
-
- public static void checkRequiredParameter(LinkedHashMap<String, Object> parameters, String parameter) {
- if (!parameters.containsKey(parameter)) {
- throw new FebsException(String.format("\"%s\" is a mandatory parameter!", parameter));
- }
- }
-
- public static void checkParameterType(Object parameter, Class t, String name) {
- if (!t.isInstance(parameter)) {
- throw new FebsException(String.format("\"%s\" must be of %s type.", name, t));
- } else if (t == String.class && parameter.toString().trim().equals("")) {
- throw new FebsException(String.format("\"%s\" must not be empty.", name));
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java
deleted file mode 100644
index e887de5..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/RequestBuilder.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils;
-
-
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsException;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.HttpMethod;
-import okhttp3.MediaType;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-
-public final class RequestBuilder {
- private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
-
- private RequestBuilder() {
- }
-
- public static Request.Builder buildPublicRequest(String fullUrl, HttpMethod httpMethod, boolean issimulated) {
- try {
- final Request.Builder result;
- switch (httpMethod) {
- case POST:
- OkHttpUtils builder = OkHttpUtils.builder();
- if(issimulated){
- builder.addHeader("x-simulated-trading","1");
- }
- result = builder
- .url(fullUrl)
- .post(true)
- .sync();
- break;
- case GET:
- OkHttpUtils builder1 = OkHttpUtils.builder();
- if(issimulated){
- builder1.addHeader("x-simulated-trading","1");
- }
- result = builder1
- .url(fullUrl)
- .addHeader("Content-Type", "application/x-www-form-urlencoded")
- .get()
- .sync();
- break;
- case PUT:
- OkHttpUtils builder2 = OkHttpUtils.builder();
- if(issimulated){
- builder2.addHeader("x-simulated-trading","1");
- }
- result = builder2
- .url(fullUrl)
- .addHeader("Content-Type", "application/x-www-form-urlencoded")
- .post(false)
- .sync();
- break;
- case DELETE:
- OkHttpUtils builder3 = OkHttpUtils.builder();
- if(issimulated){
- builder3.addHeader("x-simulated-trading","1");
- }
- result = builder3
- .url(fullUrl)
- .post(false)
- .sync();
- break;
- default:
- throw new FebsException("Invalid HTTP method: " + httpMethod);
- }
- return result;
- } catch (IllegalArgumentException e) {
- throw new FebsException("Invalid URL: " + e.getMessage());
- }
- }
-
- public static Request buildApiKeyRequest(String fullUrl,String body,String passphrase,String sign,String timeStamp, HttpMethod httpMethod, String apiKey,boolean issimulate) {
- try {
- final Request request;
- switch (httpMethod) {
- case POST:
- Request.Builder builder = new Request.Builder();
- if(issimulate){
- builder.addHeader("x-simulated-trading","1");
- }
- request = builder
- .url(fullUrl)
- .post(RequestBody.create(JSON_TYPE, body))
- .addHeader("OK-ACCESS-KEY", apiKey)
- .addHeader("OK-ACCESS-SIGN", sign)
- .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
- .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
- .build();
- break;
- case GET:
- Request.Builder builder1 = new Request.Builder();
- if(issimulate){
- builder1.addHeader("x-simulated-trading","1");
- }
- request = builder1
- .url(fullUrl)
- .get()
- .addHeader("Content-Type", "application/x-www-form-urlencoded")
- .addHeader("OK-ACCESS-KEY", apiKey)
- .addHeader("OK-ACCESS-KEY", apiKey)
- .addHeader("OK-ACCESS-SIGN", sign)
- .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
- .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
- .build();
- break;
- case PUT:
- Request.Builder builder2 = new Request.Builder();
- if(issimulate){
- builder2.addHeader("x-simulated-trading","1");
- }
- request = builder2
- .url(fullUrl)
- .put(RequestBody.create(JSON_TYPE, ""))
- .addHeader("Content-Type", "application/x-www-form-urlencoded")
- .addHeader("OK-ACCESS-KEY", apiKey)
- .addHeader("OK-ACCESS-KEY", apiKey)
- .addHeader("OK-ACCESS-SIGN", sign)
- .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
- .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
- .build();
- break;
- case DELETE:
- Request.Builder builder3 = new Request.Builder();
- if(issimulate){
- builder3.addHeader("x-simulated-trading","1");
- }
- request = builder3
- .url(fullUrl)
- .delete()
- .addHeader("Content-Type", "application/x-www-form-urlencoded")
- .addHeader("OK-ACCESS-KEY", apiKey)
- .addHeader("OK-ACCESS-KEY", apiKey)
- .addHeader("OK-ACCESS-SIGN", sign)
- .addHeader("OK-ACCESS-TIMESTAMP", timeStamp)
- .addHeader("OK-ACCESS-PASSPHRASE", passphrase)
- .build();
- break;
- default:
- throw new FebsException("Invalid HTTP method: " + httpMethod);
- }
- return request;
- } catch (IllegalArgumentException e) {
- throw new FebsException("Invalid URL: " + e.getMessage());
- }
- }
-
- public static Request buildWebsocketRequest(String fullUrl) {
- return new Request.Builder().url(fullUrl).build();
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/SignUtils.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/SignUtils.java
deleted file mode 100644
index fdd5a45..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/SignUtils.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils;
-
-import lombok.extern.slf4j.Slf4j;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.util.Base64;
-
-
-@Slf4j
-public class SignUtils {
-
- public static String signRest(String secretKey, String timestamp, String method, String path, String body) {
- String str = String.format("%s%s%s%s",
- timestamp, // timestamp
- method, // method GET/POST
- path, // requestPath
- body // body
- );
- try {
- return Base64.getEncoder().encodeToString(hmacSHA256(secretKey.getBytes(), str.getBytes()));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
-
-
- /**
- * HmacSHA256算法,返回的结果始终是32位
- *
- * @param key 加密的键,可以是任何数据
- * @param content 待加密的内容
- * @return 加密后的内容
- * @throws Exception ex
- */
- public static byte[] hmacSHA256(byte[] key, byte[] content) throws Exception {
- Mac hmacSha256 = Mac.getInstance("HmacSHA256");
- hmacSha256.init(new SecretKeySpec(key, 0, key.length, "HmacSHA256"));
- return hmacSha256.doFinal(content);
- }
-
- public static String signWebsocket(String timestamp, String secretKey) {
- String str = String.format("%s%s%s",
- timestamp,
- "GET",
- "/users/self/verify");
- try {
- return Base64.getEncoder().encodeToString(hmacSHA256(secretKey.getBytes(), str.getBytes()));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/UrlBuilder.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/UrlBuilder.java
deleted file mode 100644
index c26cd8d..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/utils/UrlBuilder.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-
-public final class UrlBuilder {
- private static final int MAX_DECIMAL_DIGITS = 30;
- private static DecimalFormat df;
-
-
- private UrlBuilder() {
- }
-
- public static String buildFullUrl(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters, String signature) {
- if (parameters != null && !parameters.isEmpty()) {
- StringBuilder sb = new StringBuilder(baseUrl);
- sb.append(urlPath).append('?');
- joinQueryParameters(sb, parameters);
- if (null != signature) {
- sb.append("&signature=").append(signature);
- }
- return sb.toString();
- } else {
- return baseUrl + urlPath;
- }
- }
-
- public static String buildStreamUrl(String baseUrl, ArrayList<String> streams) {
- StringBuilder sb = new StringBuilder(baseUrl);
- sb.append("?streams=");
- return joinStreamUrls(sb, streams);
- }
-
- //concatenate query parameters
- public static String joinQueryParameters(LinkedHashMap<String, Object> parameters) {
- return joinQueryParameters(new StringBuilder(), parameters).toString();
- }
-
- public static StringBuilder joinQueryParameters(StringBuilder urlPath, LinkedHashMap<String, Object> parameters) {
- if (parameters == null || parameters.isEmpty()) {
- return urlPath;
- }
-
- boolean isFirst = true;
- for (Map.Entry<String, Object> mapElement : parameters.entrySet()) {
-
- if (mapElement.getValue() instanceof Double) {
- parameters.replace(mapElement.getKey(), getFormatter().format(mapElement.getValue()));
- } else if (mapElement.getValue() instanceof ArrayList) {
- if (((ArrayList<?>) mapElement.getValue()).isEmpty()) {
- continue;
- }
- String key = mapElement.getKey();
- joinArrayListParameters(key, urlPath, (ArrayList<?>) mapElement.getValue(), isFirst);
- isFirst = false;
- continue;
- }
-
- if (isFirst) {
- isFirst = false;
- } else {
- urlPath.append('&');
- }
-
- urlPath.append(mapElement.getKey())
- .append('=')
- .append(urlEncode(mapElement.getValue().toString()));
- }
- return urlPath;
- }
-
- private static void joinArrayListParameters(String key, StringBuilder urlPath, ArrayList<?> values, boolean isFirst) {
- for (Object value: values) {
- if (isFirst) {
- isFirst = false;
- } else {
- urlPath.append('&');
- }
-
- urlPath.append(key)
- .append('=')
- .append(urlEncode(value.toString()));
- }
- }
-
- private static String joinStreamUrls(StringBuilder urlPath, ArrayList<String> streams) {
- boolean isFirst = true;
- for (String stream: streams) {
- if (isFirst) {
- isFirst = false;
- } else {
- urlPath.append('/');
- }
- urlPath.append(stream);
- }
- return urlPath.toString();
- }
-
-
- public static String urlEncode(String s) {
- try {
- return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
- } catch (UnsupportedEncodingException e) {
- // UTF-8 being unsuppored is unlikely
- // Replace with a unchecked exception to tidy up exception handling
- throw new RuntimeException(StandardCharsets.UTF_8.name() + " is unsupported", e);
- }
- }
-
- private static DecimalFormat getFormatter() {
- if (null == df) {
- df = new DecimalFormat();
- df.setMaximumFractionDigits(MAX_DECIMAL_DIGITS);
- df.setGroupingUsed(false);
- }
- return df;
- }
-
- public static String buildTimestamp() {
- return String.valueOf(System.currentTimeMillis());
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/BalanceVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/BalanceVo.java
deleted file mode 100644
index b81bebb..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/BalanceVo.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.vo;
-
-import lombok.Data;
-
-@Data
-public class BalanceVo {
- //币种 如BTC
- private String ccy;
- // 可用余额
- private String availBal;
- // 币种余额
- private String cashBal;
- // 可用保证金
- private String availEq;
- // 未实现盈亏总额
- private String unrealizedProfit;
- // 维持保证金
- private String maintMargin;
- //合约率
- private String mgnRatio;
- //占用保证金
- private String frozenBal;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/InstrumentsVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/InstrumentsVo.java
deleted file mode 100644
index 9cd7358..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/InstrumentsVo.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.vo;
-
-import lombok.Data;
-
-@Data
-public class InstrumentsVo {
- /**
- * 产品id, 如 BTC-USDT
- */
- private String instId;
- /**
- * 产品状态
- * live:交易中
- * suspend:暂停中
- * preopen:预上线,如:交割和期权的新合约在 live 之前,会有 preopen 状态
- * test:测试中(测试产品,不可交易)
- */
- private String state;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/PositionsVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/PositionsVo.java
deleted file mode 100644
index f331032..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/vo/PositionsVo.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.config.vo;
-
-import com.alibaba.fastjson.JSONObject;
-import lombok.Data;
-
-import java.util.List;
-import java.util.Map;
-
-@Data
-public class PositionsVo {
- private List<JSONObject> positionList;
- private Map<String, JSONObject> newPositionMap;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/enumerates/TradeTypeEnum.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/enumerates/TradeTypeEnum.java
deleted file mode 100644
index 07fddf7..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/enumerates/TradeTypeEnum.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.enumerates;
-
-import lombok.Getter;
-
-@Getter
-public enum TradeTypeEnum {
- /**
- * 1 - 开仓 2 - 平仓
- */
- OPEN_ORDER(1,"open","开仓"),
- CLOSE_ORDER(2,"close","平仓"),
- /**
- * 交易模式
- * 保证金模式:isolated:逐仓 ;cross:全仓
- * 非保证金模式:cash:非保证金
- * spot_isolated:现货逐仓(仅适用于现货带单) ,现货带单时,tdMode 的值需要指定为spot_isolated
- */
- ISOLATED(1,"isolated","逐仓"),
- CROSS(2,"cross","全仓"),
- /**
- * 持仓方向
- * long:开平仓模式开多,pos为正
- * short:开平仓模式开空,pos为正
- * net:买卖模式(交割/永续/期权:pos为正代表开多,pos为负代表开空。币币杠杆时,pos均为正,posCcy为交易货币时,代表开多;posCcy为计价货币时,代表开空。)
- */
- LONG(1,"long","持仓方向-long"),
- SHORT(2,"short","持仓方向-short"),
- /**
- * 订单类型
- * market:市价单
- * limit:限价单
- * post_only:只做maker单
- * fok:全部成交或立即取消
- * ioc:立即成交并取消剩余
- * optimal_limit_ioc:市价委托立即成交并取消剩余(仅适用交割、永续)
- * mmp:做市商保护(仅适用于组合保证金账户模式下的期权订单)
- * mmp_and_post_only:做市商保护且只做maker单(仅适用于组合保证金账户模式下的期权订单)
- */
- MARKET(1,"market","市价单"),
- LIMIT(2,"limit","限价单"),
- /**
- * 订单方向
- * buy:买, sell:卖
- */
- BUY(1,"buy","买"),
- SELL(2,"sell","卖")
- ;
-
- private int code;
- private String value;
- private String description;
-
- TradeTypeEnum(int code, String value, String description) {
- this.code = code;
- this.value = value;
- this.description = description;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/ITradeOrderService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/ITradeOrderService.java
deleted file mode 100644
index f18ddb8..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/ITradeOrderService.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.order;
-
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsResponse;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.TradeOrderDto;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeRequestBuy;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeRequestSell;
-
-//订单交易接口 欧易调用的接口是 撮合交易/交易/ POST 下单
-public interface ITradeOrderService {
- FebsResponse QuantExchangeReturnVo(TradeOrderDto tradeOrderDto, QuantApiMessage quantApiMessage);
-
- /**
- * 消费买入消息
- * @param returnVo
- */
- void operationBuyMsg(TradeRequestBuy returnVo);
-
- /**
- * 消费卖出消息
- * @param returnVo
- */
- void operationSellMsg(TradeRequestSell returnVo);
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/TradeOrderFactory.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/TradeOrderFactory.java
deleted file mode 100644
index 9736802..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/TradeOrderFactory.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.order;
-
-import lombok.Data;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Component;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 交易订单工厂类
- * 该类用于根据不同的交易平台(如OKX、BINANCE)获取相应的交易订单服务实例
- */
-@Component
-@Data
-public class TradeOrderFactory {
- /**
- * OKX交易平台的交易订单服务实例
- */
- @Qualifier("oKXTradeOrderServiceImpl")
- private final ITradeOrderService oKXTradeOrderServiceImpl;
-
- /**
- * 存储不同交易平台对应的交易订单服务实例的映射
- */
- private Map<String, ITradeOrderService> accountMap = new HashMap<>();
-
- /**
- * 构造方法,初始化交易平台与交易订单服务实例的映射
- *
- * @param oKXTradeOrderServiceImpl OKX交易平台的交易订单服务实例
- */
- public TradeOrderFactory(ITradeOrderService oKXTradeOrderServiceImpl) {
- this.oKXTradeOrderServiceImpl = oKXTradeOrderServiceImpl;
- accountMap.put("OKX", oKXTradeOrderServiceImpl);
- }
-
- /**
- * 根据平台关键字获取交易订单服务实例
- *
- * @param key 平台关键字,如"OKX"、"BINANCE"
- * @return 对应平台的交易订单服务实例
- */
- public ITradeOrderService get(String key) {
- return accountMap.get(key);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/impl/OKXTradeOrderServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/impl/OKXTradeOrderServiceImpl.java
deleted file mode 100644
index 7832be2..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/impl/OKXTradeOrderServiceImpl.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.order.impl;
-
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsResponse;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.TradeOrderDto;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.order.ITradeOrderService;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.order.vo.QuantExchangeReturnVo;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeRequestBuy;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeRequestSell;
-import com.xcong.excoin.modules.okxNewPrice.jiaoyi.IMQService;
-import lombok.SneakyThrows;
-import org.json.JSONObject;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.Base64;
-
-@Service("oKXTradeOrderServiceImpl")
-public class OKXTradeOrderServiceImpl implements ITradeOrderService {
- @Value("${spring.OKEX.baseurl}")
- private String baseurl;
-
- @Resource
- private IMQService imqService;
-
- @SneakyThrows
- @Override
- public FebsResponse QuantExchangeReturnVo(TradeOrderDto tradeOrderDto, QuantApiMessage quantApiMessage) {
- try {
- // 构建订单的JSON对象
- JSONObject jsonBody = new JSONObject();
- jsonBody.put("instId", "BTC-USDT");
- jsonBody.put("tdMode", "cash");
- jsonBody.put("side", "buy");
- jsonBody.put("ordType", "limit");
- jsonBody.put("sz", "0.01");
- jsonBody.put("px", "30000");
-
- // 发起下单请求
- String result = postRequest("/api/v5/trade/order", jsonBody.toString(),quantApiMessage);
- System.out.println("Result: " + result);
-
- //解析返回数据
- JSONObject jsonResponse = new JSONObject(result);
- if (jsonResponse.has("code") && "0".equals(jsonResponse.getString("code"))) {
- // 订单提交成功
- JSONObject jSONObject = jsonResponse.getJSONObject("data");
- String clOrdId = jSONObject.getString("clOrdId");
- String ordId = jSONObject.getString("ordId");
- QuantExchangeReturnVo quantExchangeReturnVo = new QuantExchangeReturnVo();
- quantExchangeReturnVo.setCode("0");
- quantExchangeReturnVo.setOrdId(ordId);
- quantExchangeReturnVo.setClOrdId(clOrdId);
- return new FebsResponse().success().data(quantExchangeReturnVo);
- } else {
- String code = jsonResponse.getString("code");
- String msg = jsonResponse.getString("msg");
- QuantExchangeReturnVo quantExchangeReturnVo = new QuantExchangeReturnVo();
- quantExchangeReturnVo.setCode(code);
- quantExchangeReturnVo.setMessage(msg);
- return new FebsResponse().fail().data(quantExchangeReturnVo);
- }
-
- } catch (Exception e) {
- return new FebsResponse().fail().message("下单失败");
- }
- }
-
- @Override
- public void operationBuyMsg(TradeRequestBuy returnVo) {
-
- imqService.operationBuyMsg(returnVo);
- }
-
- @Override
- public void operationSellMsg(TradeRequestSell returnVo) {
-
- imqService.operationSellMsg(returnVo);
- }
-
- private String postRequest(String endpoint, String body, QuantApiMessage quantApiMessage) throws Exception {
- URL url = new URL(baseurl + endpoint);
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- connection.setDoOutput(true);
- connection.setRequestMethod("POST");
- connection.setRequestProperty("Content-Type", "application/json");
- connection.setRequestProperty("OK-ACCESS-KEY", quantApiMessage.getASecretkey());
- connection.setRequestProperty("OK-ACCESS-SIGN", generateSignature(body,quantApiMessage));
- connection.setRequestProperty("OK-ACCESS-TIMESTAMP", getTimestamp());
- connection.setRequestProperty("OK-ACCESS-PASSPHRASE", quantApiMessage.getPassPhrass());
-
- try (OutputStream os = connection.getOutputStream()) {
- os.write(body.getBytes());
- os.flush();
- }
-
- if (connection.getResponseCode() != 200) {
- throw new RuntimeException("Failed : HTTP Error code : " + connection.getResponseCode());
- }
-
- BufferedReader br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
- StringBuilder output = new StringBuilder();
- String line;
- while ((line = br.readLine()) != null) {
- output.append(line);
- }
- connection.disconnect();
- return output.toString();
- }
-
- private static String generateSignature(String body, QuantApiMessage quantApiMessage) throws Exception {
- String preHash = getTimestamp() + "POST" + "/api/v5/order" + body;
- SecretKeySpec secretKey = new SecretKeySpec(quantApiMessage.getBSecretkey().getBytes(), "HmacSHA256");
- Mac mac = Mac.getInstance("HmacSHA256");
- mac.init(secretKey);
- return Base64.getEncoder().encodeToString(mac.doFinal(preHash.getBytes()));
- }
-
- private static String getTimestamp() {
- return String.valueOf(System.currentTimeMillis() / 1000);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/vo/QuantExchangeReturnVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/vo/QuantExchangeReturnVo.java
deleted file mode 100644
index 280fe02..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/order/vo/QuantExchangeReturnVo.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.order.vo;
-
-import lombok.Data;
-
-/**
- * 订单返回信息
- */
-@Data
-public class QuantExchangeReturnVo{
-
- private String code;//交易状态 0代表成功
- private String message;//错误信息
- private String ordId;//交易所订单号
- private String clOrdId;//客户订单id
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/IQueryOrderService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/IQueryOrderService.java
deleted file mode 100644
index 225d64f..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/IQueryOrderService.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.query;
-
-
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsResponse;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.query.dto.QuantOperateRecode;
-
-//订单查询接口 欧易调用的接口是 撮合交易/交易/ GET 获取订单信息
-public interface IQueryOrderService {
- FebsResponse QueryOrder(QuantApiMessage quantApiMessage, String instId, String clOrdId);
-
- //检查订单在交易所交易情况 exChangeState返回状态说明 1.filled代表交易成功 2.canceled代表交易失败或者取消 3.live代表交易中
- FebsResponse checkOrder(QuantOperateRecode operateRecode);
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/QueryOrderFactory.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/QueryOrderFactory.java
deleted file mode 100644
index 0bd1ac6..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/QueryOrderFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.query;
-
-import lombok.Data;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Component;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 查询委托工厂类
- * 该类用于根据不同的交易平台(如OKX、BINANCE)获取相应的查询委托服务实例
- */
-@Component
-@Data
-public class QueryOrderFactory {
- // OKX的查询委托服务实现
- @Qualifier("oKXQueryOrderServiceImpl")
- private final IQueryOrderService oKXQueryOrderServiceImpl;
-
- // 存储不同交易平台查询委托服务的映射
- private Map<String, IQueryOrderService> accountMap = new HashMap<>();
-
- /**
- * 构造方法,初始化查询委托服务映射
- * @param oKXQueryOrderServiceImpl OKX的查询委托服务实现
- */
- public QueryOrderFactory(IQueryOrderService oKXQueryOrderServiceImpl) {
- this.oKXQueryOrderServiceImpl = oKXQueryOrderServiceImpl;
- accountMap.put("OKX", oKXQueryOrderServiceImpl);
- }
-
- /**
- * 根据平台名称获取查询委托服务实例
- * @param key 平台名称,如"OKX"、"BINANCE"
- * @return 对应平台的查询委托服务实例
- */
- public IQueryOrderService get(String key) {
- return accountMap.get(key);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/dto/QuantOperateRecode.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/dto/QuantOperateRecode.java
deleted file mode 100644
index 27d525e..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/dto/QuantOperateRecode.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.query.dto;
-
-import com.baomidou.mybatisplus.annotation.TableField;
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-@Data
-public class QuantOperateRecode{
-
- private String tradeNumber;//交易编号
- private String operationCode;//操作编号
- private String sellTradeNumber;//卖出的订单号
- private Long currencyId;//操作币种ID
- private String exchange;//交易所名称
- private Long apiMessageId;//交易所ID
- private Long memberId;//用户ID
- private String coinPair;//货币对
- private Integer coinType;//计价货币 1-USDTU本位 2-USDT币本位
- private Integer coinDirect;//方向 1-买多 2-买空
- private BigDecimal coinLevel;//杠杆倍数
- private BigDecimal pageNum;//张数
- private BigDecimal price;//均价
- private BigDecimal amount;//总价
- private BigDecimal profit;//盈亏
- private BigDecimal fee;//费用与返佣
- private BigDecimal singleOrder;//单序
- private BigDecimal quantity;//数量
- private Integer directStatus;//策略 1-顺势 2-逆势
- private Integer type;//方式 1.网络 2.其他
- private Integer status;//所属状态 1-开仓 2-平仓
- private Integer finishStatus;//所属状态 1-未完成 2-已完成 3-撤单 4-已卖出(针对买单由此状态)5.-交易失败
- private String ordId;//交易所订单ID
- private BigDecimal sellPrice;//计划平仓价格
- private BigDecimal incomePrice;//平仓传入价格
- private BigDecimal rangeRatio;//比率区域
- private Integer lockStatus;//卖出状态 0-未锁定 1-已锁定
- private String buyRediskey;//买单的rediskey
- private Integer tickSz;//下单精度
-
- @TableField(exist = false)
- private String startTime;//开始时间
- @TableField(exist = false)
- private String endTime;//结束时间
- @TableField(exist = false)
- private Integer excludeFinishStatus;//查询时排除状态
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/impl/OKXQueryOrderServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/impl/OKXQueryOrderServiceImpl.java
deleted file mode 100644
index 8913a48..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/impl/OKXQueryOrderServiceImpl.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.query.impl;
-
-import cn.hutool.core.util.ObjectUtil;
-import com.alibaba.fastjson.JSON;
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsException;
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsResponse;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.HttpMethod;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.OKXContants;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.vo.BalanceVo;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.query.IQueryOrderService;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.query.dto.QuantOperateRecode;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.query.vo.QuantCheckOrderVo;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.query.vo.QuantOperateRecodeVo;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.VerifyAccountFactory;
-import com.xcong.excoin.modules.okxNewPrice.zhanghu.IApiMessageService;
-import com.xcong.excoin.modules.okxNewPrice.zhanghu.ZhangHuEnum;
-import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.json.JSONObject;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.math.BigDecimal;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.LinkedHashMap;
-
-@Service("oKXQueryOrderServiceImpl")
-@RequiredArgsConstructor
-@Slf4j
-public class OKXQueryOrderServiceImpl implements IQueryOrderService {
- @Value("spring.OKEX.baseurl")
- private String baseurl;
-
- private final IApiMessageService apiMessageService;
-
- private final VerifyAccountFactory verifyAccountFactory;
-
- private static String hmacSha256(String data, String key) throws Exception {
- Mac mac = Mac.getInstance("HmacSHA256");
- SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "HmacSHA256");
- mac.init(secretKeySpec);
- return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes()));
- }
-
- @SneakyThrows
- @Override
- public FebsResponse QueryOrder(QuantApiMessage quantApiMessage, String instId, String clOrdId) {
- String url = baseurl + "/api/v5/trade/order?instId=" + instId + "&clOrdId=" + clOrdId; // 构建请求URL
- try {
- HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
- connection.setRequestMethod("GET");
- connection.setRequestProperty("Content-Type", "application/json");
- connection.setRequestProperty("OK-ACCESS-KEY", quantApiMessage.getASecretkey());
- connection.setRequestProperty("OK-ACCESS-SIGN", hmacSha256(quantApiMessage.getBSecretkey(), clOrdId)); // 根据需要生成签名
- connection.setRequestProperty("OK-ACCESS-TIMESTAMP", String.valueOf(System.currentTimeMillis() / 1000));
- connection.setRequestProperty("OK-ACCESS-PASSPHRASE", "your_passphrase"); // 替换为您的Passphrase
-
- int responseCode = connection.getResponseCode();
-
- if (responseCode == HttpURLConnection.HTTP_OK) {
- BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
- String inputLine;
- StringBuilder response = new StringBuilder();
-
- while ((inputLine = in.readLine()) != null) {
- response.append(inputLine);
- }
- in.close();
-
- JSONObject jsonResponse = new JSONObject(response.toString());
- QuantCheckOrderVo quantCheckOrderVo = new QuantCheckOrderVo();
- if (jsonResponse.has("clOrdId") && StringUtils.isNotBlank(jsonResponse.getString("clOrdId"))) {
- quantCheckOrderVo.setTradeNumber(jsonResponse.getString("clOrdId"));
- }
- if (jsonResponse.has("ordId") && StringUtils.isNotBlank(jsonResponse.getString("ordId"))) {
- quantCheckOrderVo.setOrdId(jsonResponse.getString("ordId"));
- }
- if (jsonResponse.has("pnl") && StringUtils.isNotBlank(jsonResponse.getString("pnl"))) {
- quantCheckOrderVo.setProfit(new BigDecimal(jsonResponse.getString("pnl")));
- }
- if (jsonResponse.has("sz") && StringUtils.isNotBlank(jsonResponse.getString("sz"))) {
- quantCheckOrderVo.setPageNum(new Integer(jsonResponse.getString("sz")));
- }
- if (jsonResponse.has("fillPx") && StringUtils.isNotBlank(jsonResponse.getString("fillPx"))) {
- quantCheckOrderVo.setPrice(new BigDecimal(jsonResponse.getString("fillPx")));
- BigDecimal total = new BigDecimal(new Integer(jsonResponse.getString("sz"))).multiply(new BigDecimal(jsonResponse.getString("fillPx")));
- quantCheckOrderVo.setAmount(total);
- }
- return new FebsResponse().success().data(quantCheckOrderVo);
- } else {
- return new FebsResponse().fail().message("交易所订单查询异常");
- }
- } catch (Exception e) {
- return new FebsResponse().fail().message("订单查询异常");
- }
- }
-
- @Override
- public FebsResponse checkOrder(QuantOperateRecode operateRecode) {
- log.info("查询OKX订单信息:{}",operateRecode);
-
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(ZhangHuEnum.JIAOYISUO.getValue());
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("参数错误");
- }
-
- OKXAccount okxAccount = (OKXAccount) verifyAccountFactory.getAccountMap().get(quantApiMessage.getExchange())
- .initAccount(quantApiMessage);
- ArrayList<BalanceVo> balanceVos = new ArrayList<>();
-
- LinkedHashMap<String, Object> orderParameters = new LinkedHashMap<>();
- orderParameters.put("clOrdId", operateRecode.getTradeNumber());
- //将字符串BTC-USDTU-空 截取为 BTC-USDT
- String coinPair = operateRecode.getCoinPair().substring(0, operateRecode.getCoinPair().indexOf("-USDT")+5);
- orderParameters.put("instId", coinPair+"-SWAP");
- String orderStr = okxAccount.requestHandler.sendSignedRequest(okxAccount.baseUrl, OKXContants.ORDER, orderParameters, HttpMethod.GET, okxAccount.isSimluate());
- log.info("查询OKX订单返回信息:{}",orderStr);
- QuantOperateRecodeVo quantOperateRecodeVo = new QuantOperateRecodeVo();
- com.alibaba.fastjson.JSONObject orderStrJson = JSON.parseObject(orderStr);
- String code = orderStrJson.getString("code");
- if("0".equals(code)){
- com.alibaba.fastjson.JSONObject data = orderStrJson.getJSONArray("data").getJSONObject(0);
- quantOperateRecodeVo.setQuantity(ObjectUtil.isEmpty(data.getString("accFillSz"))?new BigDecimal("0"): new BigDecimal(data.getString("accFillSz")));
- quantOperateRecodeVo.setPrice(ObjectUtil.isEmpty(data.getString("avgPx"))?new BigDecimal("0"): new BigDecimal(data.getString("avgPx")));
- quantOperateRecodeVo.setProfit(ObjectUtil.isEmpty(data.getString("pnl"))?new BigDecimal("0"): new BigDecimal(data.getString("pnl")));
- quantOperateRecodeVo.setFee(ObjectUtil.isEmpty(data.getString("fee"))?new BigDecimal("0"): new BigDecimal(data.getString("fee")));
- quantOperateRecodeVo.setExchangeNumber(data.getString("ordId"));
- quantOperateRecodeVo.setCoinLevel(ObjectUtil.isEmpty(data.getString("lever"))?new BigDecimal("0"): new BigDecimal(data.getString("lever")));
- quantOperateRecodeVo.setOrdType(data.getString("ordType"));
- quantOperateRecodeVo.setExChangeState(data.getString("state"));
- } else if("51603".equals(code)){
- quantOperateRecodeVo.setQuantity(new BigDecimal("0"));
- quantOperateRecodeVo.setPrice(new BigDecimal("0"));
- quantOperateRecodeVo.setProfit(new BigDecimal("0"));
- quantOperateRecodeVo.setCoinLevel(new BigDecimal("0"));
- quantOperateRecodeVo.setExchangeNumber("");
- quantOperateRecodeVo.setOrdType("");
- quantOperateRecodeVo.setExChangeState("canceled");
- } else {
- return null;
- }
-
- return new FebsResponse().success().data(quantOperateRecodeVo);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/vo/QuantCheckOrderVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/vo/QuantCheckOrderVo.java
deleted file mode 100644
index a462272..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/vo/QuantCheckOrderVo.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.query.vo;
-
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-/**
- * 订单返回信息
- */
-@Data
-public class QuantCheckOrderVo{
-
- private String tradeNumber;//交易编号
- private Integer pageNum;//张数
- private BigDecimal price;//均价
- private BigDecimal amount;//总价
- private BigDecimal profit;//盈亏
- private String ordId;//交易所订单ID
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/vo/QuantOperateRecodeVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/vo/QuantOperateRecodeVo.java
deleted file mode 100644
index df9d931..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/query/vo/QuantOperateRecodeVo.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.query.vo;
-
-
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-/**
- * 合约操作记录
- */
-@Data
-public class QuantOperateRecodeVo{
-
- private String tradeNumber;//交易编号
- private Long apiMessageId;//交易所ID
- private String exchange;//交易所名称
- private String exchangeNumber;//交易所交易编号
- private String operationCode;//操作编号
- private Long currencyId;//操作币种ID
- private Long memberId;//用户ID
- private String coinPair;//货币对
- private Integer coinDirect;//方向 1-买多 2-买空
- private BigDecimal coinLevel;//杠杆倍数
- private BigDecimal pageNum;//张数
- private BigDecimal quantity;//数量
- private BigDecimal price;//均价
- private BigDecimal amount;//成交金额
- private BigDecimal profit;//盈亏
- private String sellTradeNumber;//卖出的订单号
- private BigDecimal fee;//费用与返佣
- private BigDecimal singleOrder;//单序
- private BigDecimal sellPrice;//计划平仓价格
- private Integer coinType;//计价货币 1-USDTU本位 2-USDT币本位
- private Integer directStatus;//策略 1-顺势 2-逆势
- private Integer type;//方式 1.网络 2.其他
- private Integer status;//所属状态 1-开仓 2-平仓
- private Integer tradeStatus;//交易状态 1-未完成 2-成功 3-失败
- private Integer finishStatus;//所属状态 1-未完成 2-已完成 3-撤单 4-已卖出(针对买单由此状态)5.-交易失败
- private String exChangeState;//交易所状态
- private Integer lockStatus;//卖出状态 0-未锁定 1-已锁定
- private Integer tickSz;//下单精度 1-0.1 2-0.01 3-0.001 4-0.0001 5-0.00001
- private String ordType;//订单类型 订单类型 market:市价单 limit:限价单 post_only:只做maker单 fok:全部成交或立即取消 ioc:立即成交并取消剩余 optimal_limit_ioc:市价委托立即成交并取消剩余(仅适用交割、永续)
- //mmp:做市商保护(仅适用于组合保证金账户模式下的期权订单) mmp_and_post_only:做市商保护且只做maker单(仅适用于组合保证金账户模式下的期权订单) op_fok:期权简选(全部成交或立即取消)
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeEventEnum.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeEventEnum.java
deleted file mode 100644
index 17e05bf..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeEventEnum.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade;
-
-import lombok.Getter;
-
-@Getter
-public enum TradeEventEnum {
-
-
- TRADE_CLOSE_POSITION("CLOSE_POSITION",
- "编码:{},用户:{}, 触发价:{},实际成交价格:{},执行全平操作"),
-
- TRADE_SELL("SELL",
- "编码:{},用户:{}, 触发价:{},实际成交价格:{},执行平仓操作"),
-
- TRADE_BUY("BUY",
- "编码:{},用户:{}, 触发价:{},实际成交价格:{},执行开仓操作");
-
- private String eventPoint;
-
- private String eventDec;
-
-
- TradeEventEnum(String eventPoint, String eventDec) {
- this.eventPoint = eventPoint;
- this.eventDec = eventDec;
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeEventRunner.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeEventRunner.java
deleted file mode 100644
index c14b24e..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeEventRunner.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade;
-
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsException;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.impl.TradeServiceBuy;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.impl.TradeServiceClosePosition;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.impl.TradeServiceSell;
-
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * 交易事件运行类,用于根据不同的事件点选择相应的交易策略并执行
- */
-public class TradeEventRunner {
-
- // 使用线程安全的ConcurrentHashMap存储交易事件和对应的策略服务
- private static final Map<String, TradeService> TRADE_EVENT_MAP = new ConcurrentHashMap<>();
-
- static {
- // 初始化策略
- List<TradeService> tradeServices = Arrays.asList(
- new TradeServiceBuy(), // 买入策略
- new TradeServiceSell(), // 卖出策略
- new TradeServiceClosePosition() // 平仓策略
- );
-
- // 将策略放入ConcurrentHashMap中,确保线程安全
- for (TradeService service : tradeServices) {
- TRADE_EVENT_MAP.put(service.tradeEvent(), service);
- }
- }
-
- /**
- * 根据事件点选择并执行相应的交易策略
- *
- * @param eventPoint 事件点,用于匹配相应的交易策略
- * @param tradeRequest 包含交易请求信息的LinkedHashMap
- * @param okxAccount OKX账户信息,用于执行交易
- * @return 交易执行的结果
- * @throws FebsException 如果找不到对应的交易策略,则抛出异常
- */
- public static String runTradeEvent(String eventPoint, LinkedHashMap<String, Object> tradeRequest, OKXAccount okxAccount) {
- TradeService tradeService = TRADE_EVENT_MAP.get(eventPoint);
- if (tradeService == null) {
- throw new FebsException("交易EVENT异常");
- }
-
- return tradeService.doTrade(tradeRequest,okxAccount);
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequest.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequest.java
deleted file mode 100644
index 3aec237..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequest.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade;
-
-import lombok.Data;
-
-@Data
-public class TradeRequest {
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequestBuy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequestBuy.java
deleted file mode 100644
index 5189e06..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequestBuy.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade;
-
-import lombok.Data;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-
-@Data
-public class TradeRequestBuy implements Serializable {
- /**
- * 实现了 Serializable 接口,rabbitmq可以直接发送它,但需要确保 SimpleMessageConverter 能够处理它(对象简单,不是复杂的对象)
- */
- private static final long serialVersionUID = 1L;
-
- private Long strategiesId;//策略ID ReturnBuyVo.strategyId
-
- private String tradeCode;//自定义订单编号
-
- private Double tradeCnt;//交易数量 ReturnBuyVo.quantity
- /**
- * 限定价格
- * 限定价格为0的时候,表示市价操作当前订单
- */
- private String limitPrice = "0";
- /**
- * 产品ID,如 BTC-USDT
- */
- private String instId; //ReturnBuyVo.instId
- /**
- * 持仓方向
- * 在开平仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。
- * 1-long 2-short
- */
- private Integer positionSide = 1;
- /**
- * 1 - isolated:逐仓 ;2 - cross:全仓
- * 交易模式
- * 保证金模式:isolated:逐仓 ;cross:全仓
- * 非保证金模式:cash:非保证金
- * spot_isolated:现货逐仓(仅适用于现货带单) ,现货带单时,tdMode 的值需要指定为spot_isolated
- */
- private Integer tdMode = 2;
-
- private BigDecimal priceRange; //买入的价格段位
-
- private Long operationCurrencyId; //操作货币id
-
- private String redisKey; //redisKey 用于标识是否已经处理过
-
- private String exchange; //交易所
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequestSell.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequestSell.java
deleted file mode 100644
index d167070..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeRequestSell.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade;
-
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.vo.SellOrderVo;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.util.List;
-
-@Data
-public class TradeRequestSell implements Serializable {
- /**
- * 实现了 Serializable 接口,rabbitmq可以直接发送它,但需要确保 SimpleMessageConverter 能够处理它(对象简单,不是复杂的对象)
- */
- private static final long serialVersionUID = 1L;
-
- private Long strategiesId;//策略ID ReturnVo.strategyId
-
- private String tradeCode;//自定义订单编号
-
- private BigDecimal rangeRatio;//区域比率
-
- private BigDecimal incomePrice;//平仓传入价格
-
- private Double tradeCnt;//交易数量 ReturnVo.quantity
- /**
- * 限定价格
- * 限定价格为0的时候,表示市价操作当前订单
- */
- private String limitPrice = "0";
- /**
- * 产品ID,如 BTC-USDT
- */
- private String instId; //ReturnVo.instId
- /**
- * 持仓方向
- * 在开平仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。
- * 1-long 2-short
- */
- private Integer positionSide = 1;
- /**
- * 1 - isolated:逐仓 ;2 - cross:全仓
- * 交易模式
- * 保证金模式:isolated:逐仓 ;cross:全仓
- * 非保证金模式:cash:非保证金
- * spot_isolated:现货逐仓(仅适用于现货带单) ,现货带单时,tdMode 的值需要指定为spot_isolated
- */
- private Integer tdMode = 2;
-
- private List<SellOrderVo> sellOrderList; //卖出订单列表,当operations为buy时,该字段为空
-
- private Long operationCurrencyId; //操作货币id
-
- private String redisKey; //redisKey 用于标识是否已经处理过
-
- private String exchange; //交易所
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeService.java
deleted file mode 100644
index f19809a..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/TradeService.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade;
-
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-
-import java.util.LinkedHashMap;
-
-public interface TradeService {
- /**
- * 采用策略
- * @return
- */
- String tradeEvent();
-
- /**
- * 执行
- * @param tradeDto
- * @param okxAccount
- */
- String doTrade(LinkedHashMap<String, Object> tradeDto, OKXAccount okxAccount);
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceBuy.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceBuy.java
deleted file mode 100644
index b303fda..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceBuy.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade.impl;
-
-import com.alibaba.fastjson.JSON;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.HttpMethod;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.OKXContants;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.ParameterChecker;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeEventEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.LinkedHashMap;
-
-@Slf4j
-@RequiredArgsConstructor
-public class TradeServiceBuy implements TradeService {
-
- @Override
- public String tradeEvent() {
- return TradeEventEnum.TRADE_BUY.getEventPoint();
- }
-
- @Override
- public String doTrade(LinkedHashMap<String, Object> tradeDto, OKXAccount okxAccount) {
- ParameterChecker.checkParameter(tradeDto, "instId", String.class);
- ParameterChecker.checkParameter(tradeDto, "tdMode", String.class);
- ParameterChecker.checkParameter(tradeDto, "side", String.class);
- ParameterChecker.checkParameter(tradeDto, "ordType", String.class);
- ParameterChecker.checkParameter(tradeDto, "sz", Double.class);
- String placeOrderRspOkxRestResponse = okxAccount.requestHandler.sendSignedRequest(okxAccount.baseUrl, OKXContants.ORDER, tradeDto, HttpMethod.POST, okxAccount.isSimluate());
- log.info("开仓响应:{}",JSON.parseObject(placeOrderRspOkxRestResponse).get("data"));
- /**
- * todo 下单之后的日志处理
- */
- return placeOrderRspOkxRestResponse;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceClosePosition.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceClosePosition.java
deleted file mode 100644
index c06bcdb..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceClosePosition.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade.impl;
-
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeEventEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.LinkedHashMap;
-
-@Slf4j
-@RequiredArgsConstructor
-public class TradeServiceClosePosition implements TradeService {
- @Override
- public String tradeEvent() {
- return TradeEventEnum.TRADE_CLOSE_POSITION.getEventPoint();
- }
-
- @Override
- public String doTrade(LinkedHashMap<String, Object> tradeDto, OKXAccount okxAccount) {
- System.out.println(this.tradeEvent());
- return null;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceSell.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceSell.java
deleted file mode 100644
index 6a36a4c..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/impl/TradeServiceSell.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade.impl;
-
-import com.alibaba.fastjson.JSON;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.HttpMethod;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.OKXContants;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.ParameterChecker;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeEventEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.trade.TradeService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.LinkedHashMap;
-
-@Slf4j
-@RequiredArgsConstructor
-public class TradeServiceSell implements TradeService {
- @Override
- public String tradeEvent() {
- return TradeEventEnum.TRADE_SELL.getEventPoint();
- }
-
- @Override
- public String doTrade(LinkedHashMap<String, Object> tradeDto, OKXAccount okxAccount) {
- ParameterChecker.checkParameter(tradeDto, "instId", String.class);
- ParameterChecker.checkParameter(tradeDto, "tdMode", String.class);
- ParameterChecker.checkParameter(tradeDto, "side", String.class);
- ParameterChecker.checkParameter(tradeDto, "ordType", String.class);
- ParameterChecker.checkParameter(tradeDto, "sz", Double.class);
- String placeOrderRspOkxRestResponse = okxAccount.requestHandler.sendSignedRequest(okxAccount.baseUrl, OKXContants.ORDER, tradeDto, HttpMethod.POST, okxAccount.isSimluate());
- log.info("平仓响应:{}", JSON.parseObject(placeOrderRspOkxRestResponse).get("data"));
- /**
- * todo 下单之后的日志处理
- */
-
- return placeOrderRspOkxRestResponse;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/vo/SellOrderVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/vo/SellOrderVo.java
deleted file mode 100644
index 2d14a67..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/trade/vo/SellOrderVo.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.trade.vo;
-
-import lombok.Data;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-
-/**
- * @author wzy
- * @date 2021-09-16
- **/
-@Data
-public class SellOrderVo implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- private String ordId; //交易所订单号
-
- private String tradeNumber; //本地订单号
-
- private BigDecimal quantity;//数量
-
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/IVerifyAccountService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/IVerifyAccountService.java
deleted file mode 100644
index fdb0269..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/IVerifyAccountService.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify;
-
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsResponse;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Account;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto.*;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo.ApiAccountHoldVo;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo.ApiPositionsInfoVo;
-
-//检查账户信息是否合格 欧易调用的接口是交易账户/REST API/查看账户余额
-public interface IVerifyAccountService {
-
- /**
- * 初始化api信息
- * @return
- */
- OKXAccount initAccount(QuantApiMessage quantApiMessage);
- /**
- * 初始化api信息
- * @param apiKey
- * @param secretKey
- * @param accountType
- * @return
- */
- Account initAccountV2(String apiKey, String secretKey, String passPhrass, boolean accountType);
-
- FebsResponse verifyAccount(ApiMessageDto apiMessageDto);
-
- /**
- * 获取产品信息
- * @param apiValidApiMessageDto
- */
- FebsResponse getProductMess(ApiValidApiMessageDto apiValidApiMessageDto);
-
- /**
- * 获取当前价格
- * @param operateCurrencyDto
- */
- FebsResponse getCurrenPrice(OperateCurrencyDto operateCurrencyDto);
-
- /**
- * 获取杠杆列表
- * @param operateCurrencyLeverDto
- */
- FebsResponse getlever(OperateCurrencyLeverDto operateCurrencyLeverDto);
-
- /**
- * 设置币种杠杆
- * @param operateCurrencyLeverDto
- */
- FebsResponse setLever(OperateCurrencyLeverDto operateCurrencyLeverDto);
-
- /**
- * 获取持仓信息
- * @param quantApiMessage
- */
- FebsResponse testApiLink(QuantApiMessage quantApiMessage);
-
- /**
- * 获取账户总权益
- * @param apiAccountBalanceDto
- */
- void getAccountBalance(ApiAccountBalanceDto apiAccountBalanceDto);
-
- /**
- * 获取持仓概况
- * @param apiAccountBalanceInfoDto
- */
- ApiAccountHoldVo getAccountBalanceInfo(ApiAccountBalanceInfoDto apiAccountBalanceInfoDto);
-
- ApiPositionsInfoVo getAccountPositionsInfo(ApiPositionsInfoDto apiPositionsInfoDto);
-
- /**
- * 获取交易大数据
- * @return
- */
- FebsResponse getTradeData(ApiValidApiMessageDto apiValidApiMessageDto);
-
- /**
- * 获取买入卖出情况
- * @return
- */
- FebsResponse getBuySellSituation(ApiValidApiMessageDto apiValidApiMessageDto);
-
- /**
- * 获取持仓人数比
- * @return
- */
- FebsResponse getPositionRatio(ApiValidApiMessageDto apiValidApiMessageDto);
-
- /**
- * 获取合约持仓量及交易量
- * @return
- */
- FebsResponse getPositionTradingvolume(ApiValidApiMessageDto apiValidApiMessageDto);
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/VerifyAccountFactory.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/VerifyAccountFactory.java
deleted file mode 100644
index 1ae8b06..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/VerifyAccountFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify;
-
-import lombok.Data;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Component;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 账号验证工厂类
- * 该类用于管理不同交易所的账号验证服务通过传入的键获取相应的验证服务
- */
-@Component
-@Data
-public class VerifyAccountFactory {
-
- /**
- * OKX交易所的账号验证服务
- */
- @Qualifier("oKXVerifyAccount")
- private final IVerifyAccountService oKXVerifyAccount;
-
- /**
- * 存储不同交易所的账号验证服务的映射
- */
- private Map<String, IVerifyAccountService> accountMap = new HashMap<>();
-
- /**
- * 构造方法,初始化账号验证工厂
- * @param oKXVerifyAccount OKX交易所的账号验证服务
- */
- public VerifyAccountFactory(IVerifyAccountService oKXVerifyAccount) {
- this.oKXVerifyAccount = oKXVerifyAccount;
- accountMap.put("OKX", oKXVerifyAccount);
- }
-
- /**
- * 根据传入的键获取相应的账号验证服务
- * @param key 交易所的键,如"OKX"或"BINANCE"
- * @return 对应的账号验证服务
- */
- public IVerifyAccountService get(String key) {
- return accountMap.get(key);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalDto.java
deleted file mode 100644
index 00c9cc3..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalDto.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-
-@Data
-@ApiModel(value = "ApiAccountBalDto", description = "参数接收类")
-public class ApiAccountBalDto {
-
- @NotBlank(message = "请选择时间范围")
- @ApiModelProperty(value = "开始时间", example = "")
- private String startTime;//开始时间
-
- @NotBlank(message = "请选择时间范围")
- @ApiModelProperty(value = "结束时间", example = "")
- private String endTime;//结束时间
-
- @ApiModelProperty(value = "交易所类型", example = "1")
- private String exchange;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalanceDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalanceDto.java
deleted file mode 100644
index 9c5dc63..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalanceDto.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotNull;
-
-@Data
-@ApiModel(value = "ApiAccountBalanceDto", description = "参数接收类")
-public class ApiAccountBalanceDto {
-
- @ApiModelProperty(value = "apiMessageId", example = "11")
- private Long id;
-
- @NotNull(message = "账户类型不能为空")
- @ApiModelProperty(value = "账户类型:实盘账户-true, 模拟账户-false", example = "false")
- private boolean accountType;
-
- @ApiModelProperty(value = "币种名称", example = "BTC")
- private String coinName;
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalanceInfoDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalanceInfoDto.java
deleted file mode 100644
index 7774264..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountBalanceInfoDto.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-@Data
-@ApiModel(value = "ApiAccountBalanceInfoDto", description = "参数接收类")
-public class ApiAccountBalanceInfoDto {
-
- @ApiModelProperty(value = "apiMessageId", example = "11")
- private Long id;
-
- @ApiModelProperty(value = "币种名称", example = "BTC")
- private String coinName;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountProfitDailyDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountProfitDailyDto.java
deleted file mode 100644
index 229f292..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiAccountProfitDailyDto.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-@Data
-@ApiModel(value = "ApiAccountBalanceInfoDto", description = "参数接收类")
-public class ApiAccountProfitDailyDto {
-
- @ApiModelProperty(value = "交易所类型", example = "1")
- private String exchange;
-
- @ApiModelProperty(value = "开始时间", example = "")
- private String startTime;//开始时间
-
- @ApiModelProperty(hidden = true)
- private String endTime;//结束时间
-
- @ApiModelProperty(hidden = true)
- private Long memberId;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiMessageDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiMessageDto.java
deleted file mode 100644
index 2b9f2f2..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiMessageDto.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-
-/**
- * @author lyq
- * @date 2024-09-16
- **/
-@Data
-public class ApiMessageDto {
-
- @ApiModelProperty(value = "id")
- private Long id;
-
- @ApiModelProperty(value = "会员id", example = "11")
- private Long memberId;
-
- @NotBlank(message = "交易所不能为空")
- @ApiModelProperty(value = "交易所", example = "jys")
- private String exchange;
-
- @NotBlank(message = "a秘钥不能为空")
- @ApiModelProperty(value = "a秘钥", example = "123456")
- private String aSecretkey;
-
- @NotBlank(message = "b秘钥不能为空")
- @ApiModelProperty(value = "b秘钥", example = "123456")
- private String bSecretkey;
-
- @NotBlank(message = "passPhrass不能为空")
- @ApiModelProperty(value = "passPhrass", example = "123456")
- private String passPhrass;
-
- @NotNull(message = "账户类型不能为空")
- @ApiModelProperty(value = "账户类型:实盘账户-true, 模拟账户-false", example = "false")
- private String accountType;
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiPositionsInfoDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiPositionsInfoDto.java
deleted file mode 100644
index 3222f37..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiPositionsInfoDto.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-@Data
-@ApiModel(value = "ApiAccountBalanceInfoDto", description = "参数接收类")
-public class ApiPositionsInfoDto {
-
- @ApiModelProperty(value = "apiMessageId", example = "11")
- private Long id;
-
- @ApiModelProperty(value = "币种名称", example = "BTC")
- private String coinName;
-
- @ApiModelProperty(value = "交易所名称", example = "OKX")
- private String exchange;
-
- @ApiModelProperty(value = "持仓ID", example = "")
- private String posId;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiValidApiMessageDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiValidApiMessageDto.java
deleted file mode 100644
index 3a57022..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/ApiValidApiMessageDto.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotNull;
-
-@Data
-@ApiModel(value = "ApiValidApiMessageDto", description = "API信息参数接收类")
-public class ApiValidApiMessageDto {
-
- @NotNull(message = "apiMessageId")
- @ApiModelProperty(value = "apiMessageId", example = "11")
- private Long id;
-
- @NotNull(message = "账户类型不能为空")
- @ApiModelProperty(value = "账户类型:实盘账户-true, 模拟账户-false", example = "false")
- private boolean accountType;
-
- @ApiModelProperty(value = "币种名称", example = "BTC")
- private String coinName;
-
- @ApiModelProperty(value = "交易所", example = "OKX")
- private String exchange;
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/OperateCurrencyDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/OperateCurrencyDto.java
deleted file mode 100644
index 4aac95a..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/OperateCurrencyDto.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-
-/**
- * @author wzy
- * @date 2024-08-10
- **/
-@Data
-@ApiModel(value = "OperateCurrencyDto", description = "登录接口参数接收类")
-public class OperateCurrencyDto {
-
- @ApiModelProperty(value = "id")
- private Long id;
-
- @ApiModelProperty(value = "交易所ID", example = "")
- private Long apiMessageId;//交易所ID
-
- @ApiModelProperty(value = "交易所名称", example = "")
- private String exchange;//交易所名称
-
-// @NotBlank(message = "币种标识不能为空")
-// @ApiModelProperty(value = "币种标识", example = "DOGE")
-// private String coinSymbol;//货币对(交易货币(币种标识))
-
- @NotBlank(message = "币种标识不能为空")
- @ApiModelProperty(value = "币种标识", example = "DOGE")
- private String coinName;//货币货币名称(交易货币(币种标识))
-
- @NotBlank(message = "计价货币不能为空")
- @ApiModelProperty(value = "计价货币", example = "USDTU")
- private Integer coinType;//计价货币 1-USDTU本位 2-USDT币本位
-
- @NotBlank(message = "方向不能为空")
- @ApiModelProperty(value = "方向", example = "1")
- private Integer coinDirect;//方向 1-多 2-空
-
- @ApiModelProperty(value = "策略方向", example = "1")
- private Integer directStatus;//策略方向 1-顺势 2-逆势
-
- @ApiModelProperty(value = "货币对", example = "-SWAP")
- private String coinSymbol;//货币对(交易货币(币种标识))
-
- @ApiModelProperty(value = "用户id", example = "1")
- private Long MemberId;//用户ID
-
- @ApiModelProperty(value = "账户类型BTC-USD:实盘账户-true, 模拟账户-false", example = "false")
- private boolean accountType;
-
- @ApiModelProperty(value = "是否开启监控 0-否 1-是", example = "0")
- private Integer monitorStatus;//是否开启监控 0-未开启 1-已开启
-
- @ApiModelProperty(value = "杠杆倍率", example = "20")
- private String lever;//杠杆倍率
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/OperateCurrencyLeverDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/OperateCurrencyLeverDto.java
deleted file mode 100644
index 3c42ab9..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/OperateCurrencyLeverDto.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-/**
- * @author wzy
- * @date 2024-08-10
- **/
-@Data
-@ApiModel(value = "OperateCurrencyLeverDto", description = "倍率查询接收类")
-public class OperateCurrencyLeverDto {
-
- @ApiModelProperty(value = "apiMessageId", example = "1")
- private Long apiMessageId;//交易所ID
-
- @ApiModelProperty(value = "产品ID", example = "BTC-USDT")
- private String instId;//交易所名称
-
- @ApiModelProperty(value = "杠杆倍率", example = "20")
- private String lever;//杠杆倍率
-
- @ApiModelProperty(value = "交易所名称", example = "OKX")
- private String exchange;//交易所名称
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/TradeBigdataDto.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/TradeBigdataDto.java
deleted file mode 100644
index bb16168..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/dto/TradeBigdataDto.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-@ApiModel(value = "TradeBigdataDto", description = "交易大数据数据接收类")
-public class TradeBigdataDto implements Serializable {
- private static final long serialVersionUID = 1L;
- @ApiModelProperty(value = "数据产生时间", example = "2024-09-18 14:38:01")
- private String ts;//数据产生时间
-
- @ApiModelProperty(value = "卖出量", example = "1000")
- private String sellVol;//卖出量
-
- @ApiModelProperty(value = "买入量", example = "1000")
- private String buyVol;//买入量
-
- @ApiModelProperty(value = "多空人数比", example = "0.8")
- private String longShortAcctRatio;//多空人数比
-
- @ApiModelProperty(value = "持仓总量", example = "1000")
- private String oi;//持仓总量
-
- @ApiModelProperty(value = "交易总量", example = "1000")
- private String vol;//交易总量
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/enums/PublicStatusEnum.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/enums/PublicStatusEnum.java
deleted file mode 100644
index a267e11..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/enums/PublicStatusEnum.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.enums;
-
-import java.math.BigDecimal;
-
-public class PublicStatusEnum {
-
- /**
- * API验证返回状态
- */
- public static final int API_MESSAGE_STATUS_SUCCESS = 1;
- public static final int API_MESSAGE_STATUS_FAIL = 0;
-
- /**
- * 货币对监控状态
- */
- //货币对监控状态开启
- public static final int CURRENCY_MONITOR_OPEN = 1;
-
- //货币对监控状态开启
- public static final int CURRENCY_ONLYSELL_OPEN = 1;
- //货币对监控状态关闭
-
- public static final int CURRENCY_MONITOR_CLOSE = 0;
- //是否开启过监听
- public static final int CURRENCY_MONITORED_OPEN = 1;
- //订单未完成
- public static final int ORDER_FINISH_STATUS_NO = 1;
- //订单已完成
- public static final int ORDER_FINISH_STATUS_YES = 2;
- //订单取消
- public static final int ORDER_FINISH_STATUS_CANEL = 3;
- //订单已卖出(针对买单)
- public static final int ORDER_FINISH_STATUS_SELL = 4;
- //订单下单失败
- public static final int ORDER_FINISH_STATUS_FAIL = 5;
-
- //其他方式卖出
- public static final int ORDER_FINISH_STATUS_OTHERSELL = 6;
-
- //买单
- public static final int ORDER_STATUS_BUY = 1;
-
- public static final BigDecimal ORDER_DOWN_RATIO= new BigDecimal("0.01");
- //买单
-
- public static final int ORDER_STATUS_SELL = 2;
-
-// public static final boolean AccountType = false;
-
- public static final int ORDER_FINISH_STATUS = 2;
-
- //订单卖出未锁定
- public static final int ORDER_SELL_LOCK_STATUS_NO = 0;
- //订单卖出已锁定
- public static final int ORDER_SELL_LOCK_STATUS_YES = 1;
-
- //定义超过开仓均价时需向下调整卖价的比率
- public static final BigDecimal HIGH_SELL_RATIO= new BigDecimal("1.1");
-
- //定义需要调整后的比率
- public static final BigDecimal HIGH_ADJUST_RATIO= new BigDecimal("1.03");
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/impl/OKXVerifyAccountServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/impl/OKXVerifyAccountServiceImpl.java
deleted file mode 100644
index fe5de75..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/impl/OKXVerifyAccountServiceImpl.java
+++ /dev/null
@@ -1,680 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.impl;
-
-import cn.hutool.core.util.ObjectUtil;
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsException;
-import com.xcong.excoin.modules.okxNewPrice.utils.FebsResponse;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.DataUtil;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.OKXAccount;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.DefaultUrls;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.enums.HttpMethod;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.utils.OKXContants;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.IVerifyAccountService;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.dto.*;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.enums.PublicStatusEnum;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo.ApiAccountHoldVo;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo.ApiPositionsInfoVo;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo.ProductMessVo;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo.SinglemarketVo;
-import com.xcong.excoin.modules.okxNewPrice.zhanghu.IApiMessageService;
-import com.xcong.excoin.modules.okxNewPrice.zhanghu.ZhangHuEnum;
-import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.math.BigDecimal;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.*;
-
-@Slf4j
-@Service("oKXVerifyAccount")
-@RequiredArgsConstructor
-public class OKXVerifyAccountServiceImpl implements IVerifyAccountService {
-
- private final IApiMessageService apiMessageService;
- @Value("${spring.OKEX.baseurl}")
- private String baseurl;
-
- @Override
- public OKXAccount initAccount(QuantApiMessage quantApiMessage) {
- return new OKXAccount(
- quantApiMessage.getAccountType().equalsIgnoreCase("true") ? DefaultUrls.USDM_PROD_URL : DefaultUrls.USDM_UAT_URL,
- quantApiMessage.getASecretkey(),
- quantApiMessage.getBSecretkey(),
- quantApiMessage.getPassPhrass(),
- !quantApiMessage.getAccountType().equalsIgnoreCase("true"));
-
- }
-
- @Override
- public OKXAccount initAccountV2(String apiKey, String secretKey, String passPhrass, boolean accountType) {
- return new OKXAccount(
- accountType ? DefaultUrls.USDM_PROD_URL : DefaultUrls.USDM_UAT_URL,
- apiKey,
- secretKey,
- passPhrass,
- !accountType);
- }
-
- @SneakyThrows
- @Override
- public FebsResponse verifyAccount(ApiMessageDto apiMessageDto) {
- if(validateAccount(apiMessageDto)){
- return new FebsResponse().success();
- }
- return new FebsResponse().fail();
- }
-
- @Override
- public FebsResponse getProductMess(ApiValidApiMessageDto apiValidApiMessageDto) {
- Long id = apiValidApiMessageDto.getId();
-
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(ZhangHuEnum.JIAOYISUO.getValue());
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("参数错误");
- }
-
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
- LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
- balanceParameters.put("instType", "SWAP");
- if(StringUtils.isNotBlank(apiValidApiMessageDto.getCoinName())){
- balanceParameters.put("instId", apiValidApiMessageDto.getCoinName()+"-USD-SWAP");
- balanceParameters.put("instFamily", apiValidApiMessageDto.getCoinName()+"-USD");
- }
-
- String productStr = okxAccount.requestHandler.sendSignedRequest(okxAccount.baseUrl, OKXContants.INSTRUMENTS, balanceParameters, HttpMethod.GET, okxAccount.isSimluate());
- log.info("productStr:{}", productStr);
- JSONObject balanceJson = JSON.parseObject(productStr);
- JSONObject data = balanceJson.getJSONArray("data").getJSONObject(0);
-
- ProductMessVo productMessVo = new ProductMessVo();
- productMessVo.setLotSz(data.getString("lotSz"));
- productMessVo.setMinSz(data.getString("minSz"));
- productMessVo.setTickSz(data.getString("tickSz"));
- productMessVo.setCtVal(data.getString("ctVal"));
-
- return new FebsResponse().success().data(productMessVo);
- }
-
- @Override
- public FebsResponse getCurrenPrice(OperateCurrencyDto operateCurrencyDto) {
-
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(ZhangHuEnum.JIAOYISUO.getValue());
- if(null == quantApiMessage){
- return new FebsResponse().fail().message("请先配置交易所");
- }
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
- if(StringUtils.isNotBlank(operateCurrencyDto.getCoinSymbol())){
- balanceParameters.put("instId", operateCurrencyDto.getCoinSymbol()+"-SWAP");
- }
- String ticker = okxAccount.requestHandler.sendSignedRequest(okxAccount.baseUrl, OKXContants.TICKER, balanceParameters, HttpMethod.GET, okxAccount.isSimluate());
-
- log.info("ticker================:{}", ticker);
-
- JSONObject jsonObject = JSON.parseObject(ticker);
- JSONObject data = jsonObject.getJSONArray("data").getJSONObject(0);
-
- SinglemarketVo singlemarketVo = new SinglemarketVo();
- singlemarketVo.setInstId(data.getString("instId"));
- singlemarketVo.setLast(data.getString("last"));
- singlemarketVo.setInstType(data.getString("instType"));
-
- return new FebsResponse().success().data(singlemarketVo);
- }
-
- @Override
- public FebsResponse getlever(OperateCurrencyLeverDto operateCurrencyLeverDto) {
-
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(ZhangHuEnum.JIAOYISUO.getValue());
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("参数错误");
- }
-
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
- LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
- balanceParameters.put("instId", operateCurrencyLeverDto.getInstId());
- balanceParameters.put("mgnMode", "cross");
- String leverStr = okxAccount.requestHandler.sendSignedRequest(okxAccount.baseUrl, OKXContants.LEVERAGE, balanceParameters, HttpMethod.GET, okxAccount.isSimluate());
- log.info("leverStr:{}", leverStr);
- JSONObject leverStrJson = JSON.parseObject(leverStr);
-
- Set<String> leverage = new HashSet<>();
-
- JSONArray details = leverStrJson.getJSONArray("data");
- int size = details.size();
- for (int i = 0; i < size; i++) {
- leverage.add(details.getJSONObject(i).getString("lever"));
- }
-
-
- log.info("leverage:{}", leverage);
-
- return new FebsResponse().success().data(leverage);
- }
-
- @Override
- public FebsResponse setLever(OperateCurrencyLeverDto operateCurrencyLeverDto) {
-
- log.info("OKX_SET_LEVEL");
- log.info("operateCurrencyLeverDto:{}", operateCurrencyLeverDto);
-
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(ZhangHuEnum.JIAOYISUO.getValue());
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("参数错误");
- }
-
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
- LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
- balanceParameters.put("instId", operateCurrencyLeverDto.getInstId()+"-SWAP");
- balanceParameters.put("mgnMode", "cross");
- balanceParameters.put("lever", operateCurrencyLeverDto.getLever());
- String setleverStr = okxAccount.requestHandler.sendSignedRequest(okxAccount.baseUrl, OKXContants.SETLEVERAGE, balanceParameters, HttpMethod.POST, okxAccount.isSimluate());
- log.info("setleverStr:{}", setleverStr);
- JSONObject leverStrJson = JSON.parseObject(setleverStr);
-
- String code = leverStrJson.getString("code");
- if("0".equals(code)||"59000".equals(code)){
- if("59000".equals(code)){
- return new FebsResponse().success().message("59000");
- }
- return new FebsResponse().success().message("设置杠杆成功");
- } else {
- return new FebsResponse().fail().message("设置杠杆失败");
- }
-
- }
-
- /**
- * 测试API链接
- *
- * @param quantApiMessage 包含API链接信息的DTO对象
- * @return 返回一个FebsResponse对象,包含验证结果和消息
- */
- @Override
- public FebsResponse testApiLink(QuantApiMessage quantApiMessage) {
- log.info("apiMessageDto:{}",quantApiMessage);
- try{
- // 初始化账户对象
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- // 创建用于存储余额查询参数的容器
- LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
- log.info("okxAccount:{}",okxAccount);
-
- // 发送签名请求以获取账户余额
- String balance = okxAccount.requestHandler.sendSignedRequest(okxAccount.baseUrl, OKXContants.BALANCE, balanceParameters, HttpMethod.GET, okxAccount.isSimluate());
- log.info("balance:{}",balance);
-
- // 解析余额响应JSON
- JSONObject balanceJson = JSON.parseObject(balance);
-
- // 检查余额响应代码,如果为0则表示成功
- if("0".equals(balanceJson.getString("code").toString())){
- // 更新QuantApiMessage状态为成功
- HashMap<String, Integer> objectObjectHashMap = new HashMap<>();
- objectObjectHashMap.put("state", PublicStatusEnum.API_MESSAGE_STATUS_SUCCESS);
- return new FebsResponse().success().data(objectObjectHashMap).message("账号验证成功");
- }
-
- // 返回验证失败的消息
- return new FebsResponse().fail().message("账号验证失败");
- } catch (Exception e){
- // 异常情况下返回验证失败的消息
- return new FebsResponse().fail().message("账号验证失败");
- }
- }
-
- /**
- * 获取账户余额的方法
- *
- * @param apiAccountBalanceDto 包含账户余额查询信息的数据传输对象
- * @throws FebsException 当API消息信息未设置时抛出异常
- */
- @Override
- public void getAccountBalance(ApiAccountBalanceDto apiAccountBalanceDto) {
- // 提取API消息ID、账户类型和币种名称
- String coinName = apiAccountBalanceDto.getCoinName();
-
- // 根据ID查询API消息信息
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(ZhangHuEnum.JIAOYISUO.getValue());
- // 检查API消息信息是否存在
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("API_MESSAGE信息未设置");
- }
-
- // 初始化账户对象
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- // 构建查询余额的参数
- LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
- balanceParameters.put("ccy", coinName);
- // 发送签名请求获取余额信息
- String balance = okxAccount.requestHandler.sendSignedRequest(
- okxAccount.baseUrl,
- OKXContants.BALANCE,
- balanceParameters,
- HttpMethod.GET,
- okxAccount.isSimluate());
- // 记录余额信息日志
- log.info("ACCOUNT_BALANCE:{}", balance);
- JSONObject balanceJson = JSON.parseObject(balance);
- JSONObject data = balanceJson.getJSONArray("data").getJSONObject(0);
- JSONArray details = data.getJSONArray("details");
- for (int i = 0; i < details.size(); i++) {
- JSONObject jsonObject = details.getJSONObject(i);
- // 总权益
- String bal = jsonObject.getString("eq");
-// String upl = jsonObject.getString("upl");
- }
- }
-
- @Override
- public ApiAccountHoldVo getAccountBalanceInfo(ApiAccountBalanceInfoDto apiAccountBalanceInfoDto) {
- ApiAccountHoldVo apiAccountHoldVo = new ApiAccountHoldVo();
-
- // 提取API消息ID、账户类型和币种名称
- Long apiMessageId = apiAccountBalanceInfoDto.getId();
- String coinName = apiAccountBalanceInfoDto.getCoinName();
-
- // 根据ID查询API消息信息
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(
- ZhangHuEnum.JIAOYISUO.getValue()
- );
- // 检查API消息信息是否存在
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("API_MESSAGE信息未设置");
- }
-
- // 初始化账户对象
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- // 构建查询余额的参数
- LinkedHashMap<String, Object> balanceParameters = new LinkedHashMap<>();
- balanceParameters.put("ccy", coinName);
- // 发送签名请求获取余额信息
- String balance = okxAccount.requestHandler.sendSignedRequest(
- okxAccount.baseUrl,
- OKXContants.BALANCE,
- balanceParameters,
- HttpMethod.GET,
- okxAccount.isSimluate());
- // 记录余额信息日志
- log.info("ACCOUNT_BALANCE:{}", balance);
- JSONObject balanceJson = JSON.parseObject(balance);
- JSONObject data = balanceJson.getJSONArray("data").getJSONObject(0);
- JSONArray details = data.getJSONArray("details");
- for (int i = 0; i < details.size(); i++) {
- JSONObject jsonObject = details.getJSONObject(i);
- log.info("OKX-HOLD:",jsonObject);
- // 总权益
- String bal = jsonObject.getString("eq");//币种总权益
- String cashBal = jsonObject.getString("cashBal");//币种余额
- String availEq = jsonObject.getString("availEq");//可用保证金
- String availBal = jsonObject.getString("availBal");//可用余额
- String frozenBal = jsonObject.getString("frozenBal");//币种占用金额
- String imr = jsonObject.getString("imr");//币种维度全仓占用保证金,适用于现货和合约模式且有全仓仓位时
- String mgnRatio = jsonObject.getString("mgnRatio");//币种全仓保证金率,衡量账户内某项资产风险的指标
- String mmr = jsonObject.getString("mmr");//币种维度全仓维持保证金
- String upl = jsonObject.getString("upl");//未实现盈亏
- apiAccountHoldVo.setTotalBalance(new BigDecimal(bal));
- apiAccountHoldVo.setTotalPercent(new BigDecimal(upl));
- apiAccountHoldVo.setAvailableBalance(new BigDecimal(availBal));
-// apiAccountHoldVo.setHoldBalance(new BigDecimal(mmr));
- apiAccountHoldVo.setHoldBalance(new BigDecimal(mmr));
- apiAccountHoldVo.setUseBalance(new BigDecimal(mmr));
- apiAccountHoldVo.setCashBal(new BigDecimal(cashBal));
- apiAccountHoldVo.setFrozenBal(new BigDecimal(frozenBal));
- apiAccountHoldVo.setAvailEq(new BigDecimal(availEq));
- apiAccountHoldVo.setMgnRatio(mgnRatio);
- }
- return apiAccountHoldVo;
- }
-
- @Override
- public ApiPositionsInfoVo getAccountPositionsInfo(ApiPositionsInfoDto apiPositionsInfoDto) {
-
- log.info("getAccountPositionsInfo:{}",apiPositionsInfoDto);
- ApiPositionsInfoVo apiPositionsInfoVo = new ApiPositionsInfoVo();
- // 提取API消息ID、账户类型和币种名称
- Long apiMessageId = apiPositionsInfoDto.getId();
- String coinName = apiPositionsInfoDto.getCoinName();
-
- // 根据ID查询API消息信息
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(
- ZhangHuEnum.JIAOYISUO.getValue()
- );
- // 检查API消息信息是否存在
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("API_MESSAGE信息未设置");
- }
-
- // 初始化账户对象
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- // 构建查询余额的参数
- LinkedHashMap<String, Object> positionsParameters = new LinkedHashMap<>();
- positionsParameters.put("instType", "SWAP");
- positionsParameters.put("instId", coinName+"-USDT-SWAP");
- try{
- // 发送签名请求获取余额信息
- String positions = okxAccount.requestHandler.sendSignedRequest(
- okxAccount.baseUrl,
- OKXContants.POSITIONS,
- positionsParameters,
- HttpMethod.GET,
- okxAccount.isSimluate());
- // 记录余额信息日志
- log.info("POSITIONS:{}", positions);
- JSONObject balanceJson = JSON.parseObject(positions);
- String code = balanceJson.getString("code");
- if(!"0".equals(code)){
- return null;
- }
- JSONObject data = balanceJson.getJSONArray("data").getJSONObject(0);
- String avgPx = data.getString("avgPx");
- String upl = data.getString("upl");
- String markPx = data.getString("markPx");
- String bePx = data.getString("bePx");
- String pos = data.getString("pos");
- apiPositionsInfoVo.setUpl(new BigDecimal(ObjectUtil.isNotEmpty(upl)? DataUtil.getDecimalDigits8(upl):"0"));
- apiPositionsInfoVo.setAvgPx(new BigDecimal(ObjectUtil.isNotEmpty(avgPx)?DataUtil.getDecimalDigits8(avgPx):"0"));
- apiPositionsInfoVo.setMarkPx(new BigDecimal(ObjectUtil.isNotEmpty(markPx)?DataUtil.getDecimalDigits8(markPx):"0"));
- apiPositionsInfoVo.setBePx(new BigDecimal(ObjectUtil.isNotEmpty(bePx)?DataUtil.getDecimalDigits8(bePx):"0"));
- apiPositionsInfoVo.setPos(new BigDecimal(ObjectUtil.isNotEmpty(pos)?DataUtil.getDecimalDigits8(pos):"0"));
- apiPositionsInfoVo.setCoinName(coinName);
- return apiPositionsInfoVo;
- }catch (Exception e){
- return null;
- }
-
- }
-
- @Override
- public FebsResponse getTradeData(ApiValidApiMessageDto apiValidApiMessageDto) {
-
- log.info("getTradeData:{}", apiValidApiMessageDto);
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(
- ZhangHuEnum.JIAOYISUO.getValue()
- );
- // 检查API消息信息是否存在
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("API_MESSAGE信息未设置");
- }
-
- // 初始化账户对象
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- // 构建查询余额的参数
- LinkedHashMap<String, Object> positionsParameters = new LinkedHashMap<>();
- try{
- // 发送签名请求获取余额信息
- String tradedata = okxAccount.requestHandler.sendSignedRequest(
- okxAccount.baseUrl,
- OKXContants.TRADEDATA,
- positionsParameters,
- HttpMethod.GET,
- okxAccount.isSimluate());
- // 记录余额信息日志
- log.info("tradedata:{}", tradedata);
- JSONObject balanceJson = JSON.parseObject(tradedata);
- String code = balanceJson.getString("code");
- if(!"0".equals(code)){
- return null;
- }
- JSONObject dataObject = balanceJson.getJSONObject("data");
- JSONArray contractArray = dataObject.getJSONArray("contract");
- List<String> contractList = new ArrayList<>();
- if (contractArray != null) {
- for (int i = 0; i < contractArray.size(); i++) {
- contractList.add(contractArray.getString(i));
- }
- }
- return new FebsResponse().data(contractList);
- }catch (Exception e){
- return null;
- }
- }
-
- @Override
- public FebsResponse getBuySellSituation(ApiValidApiMessageDto apiValidApiMessageDto) {
- log.info("getBuySellSituation:{}", apiValidApiMessageDto);
-
- String coinName = apiValidApiMessageDto.getCoinName();// String coinName = "BTC";
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(
- ZhangHuEnum.JIAOYISUO.getValue()
- );
- // 检查API消息信息是否存在
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("API_MESSAGE信息未设置");
- }
-
- // 初始化账户对象
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- // 构建查询余额的参数
- LinkedHashMap<String, Object> positionsParameters = new LinkedHashMap<>();
- positionsParameters.put("period", "4H");
- positionsParameters.put("instId", coinName+"-USDT-SWAP");
- positionsParameters.put("limit", "1");
- try{
- // 发送签名请求获取余额信息
- String buySellSituation = okxAccount.requestHandler.sendSignedRequest(
- okxAccount.baseUrl,
- OKXContants.BUYSELLSITUATION,
- positionsParameters,
- HttpMethod.GET,
- okxAccount.isSimluate());
- // 记录余额信息日志
- log.info("buySellSituation:{}", buySellSituation);
- JSONObject balanceJson = JSON.parseObject(buySellSituation);
- String code = balanceJson.getString("code");
- if(!"0".equals(code)){
- return null;
- }
- JSONArray dataArray = balanceJson.getJSONArray("data");
- TradeBigdataDto tradeBigdataDto = new TradeBigdataDto();
- if (dataArray != null) {
- JSONArray innerArray = dataArray.getJSONArray(0);
- String sellVol = innerArray.getString(1);
- String buyVol = innerArray.getString(2);
- String ts = innerArray.getString(0);
- tradeBigdataDto.setBuyVol(buyVol);
- tradeBigdataDto.setSellVol(sellVol);
- tradeBigdataDto.setTs(ts);
- }
- return new FebsResponse().data(tradeBigdataDto);
- }catch (Exception e){
- return null;
- }
- }
-
- @Override
- public FebsResponse getPositionRatio(ApiValidApiMessageDto apiValidApiMessageDto) {
- log.info("getPositionRatio:{}", apiValidApiMessageDto);
-
- String coinName = apiValidApiMessageDto.getCoinName();// String coinName = "BTC";
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(
- ZhangHuEnum.JIAOYISUO.getValue()
- );
- // 检查API消息信息是否存在
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("API_MESSAGE信息未设置");
- }
-
- // 初始化账户对象
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- // 构建查询余额的参数
- LinkedHashMap<String, Object> positionsParameters = new LinkedHashMap<>();
- positionsParameters.put("period", "4H");
- positionsParameters.put("instId", coinName+"-USDT-SWAP");
- positionsParameters.put("limit", "1");
- try{
- // 发送签名请求获取余额信息
- String positionRatio = okxAccount.requestHandler.sendSignedRequest(
- okxAccount.baseUrl,
- OKXContants.POSITIONRATIO,
- positionsParameters,
- HttpMethod.GET,
- okxAccount.isSimluate());
- // 记录余额信息日志
- log.info("positionRatio:{}", positionRatio);
- JSONObject balanceJson = JSON.parseObject(positionRatio);
- String code = balanceJson.getString("code");
- if(!"0".equals(code)){
- return null;
- }
- JSONArray dataArray = balanceJson.getJSONArray("data");
- TradeBigdataDto tradeBigdataDto = new TradeBigdataDto();
- if (dataArray != null) {
- JSONArray innerArray = dataArray.getJSONArray(0);
- String longShortAcctRatio = innerArray.getString(1);
- String ts = innerArray.getString(0);
- tradeBigdataDto.setLongShortAcctRatio(longShortAcctRatio);
- tradeBigdataDto.setTs(ts);
- }
- return new FebsResponse().data(tradeBigdataDto);
- }catch (Exception e){
- return null;
- }
- }
-
- @Override
- public FebsResponse getPositionTradingvolume(ApiValidApiMessageDto apiValidApiMessageDto) {
- log.info("getPositionTradingvolume:{}", apiValidApiMessageDto);
-
- String coinName = apiValidApiMessageDto.getCoinName();// String coinName = "BTC";
- QuantApiMessage quantApiMessage = apiMessageService.getApiMessage(
- ZhangHuEnum.JIAOYISUO.getValue()
- );
- // 检查API消息信息是否存在
- if(ObjectUtil.isEmpty(quantApiMessage)){
- throw new FebsException("API_MESSAGE信息未设置");
- }
-
- // 初始化账户对象
- OKXAccount okxAccount = this.initAccount(quantApiMessage);
-
- // 构建查询余额的参数
- LinkedHashMap<String, Object> positionsParameters = new LinkedHashMap<>();
- positionsParameters.put("period", "1D");
- positionsParameters.put("ccy", coinName);
- positionsParameters.put("limit", "1");
- try{
- // 发送签名请求获取余额信息
- String positionTradingvolume = okxAccount.requestHandler.sendSignedRequest(
- okxAccount.baseUrl,
- OKXContants.POSITIONVOLUME,
- positionsParameters,
- HttpMethod.GET,
- okxAccount.isSimluate());
- // 记录余额信息日志
- log.info("positionTradingvolume:{}", positionTradingvolume);
- JSONObject balanceJson = JSON.parseObject(positionTradingvolume);
- String code = balanceJson.getString("code");
- if(!"0".equals(code)){
- return null;
- }
- JSONArray dataArray = balanceJson.getJSONArray("data");
- TradeBigdataDto tradeBigdataDto = new TradeBigdataDto();
- if (dataArray != null) {
- JSONArray innerArray = dataArray.getJSONArray(0);
- String oi = innerArray.getString(1);
- String vol = innerArray.getString(2);
- String ts = innerArray.getString(0);
- tradeBigdataDto.setOi(oi);
- tradeBigdataDto.setVol(vol);
- tradeBigdataDto.setTs(ts);
- }
- return new FebsResponse().data(tradeBigdataDto);
- }catch (Exception e){
- return null;
- }
- }
-
- //此方法用于验证账号
- private boolean validateAccount(ApiMessageDto apiMessageDto) throws Exception {
- boolean flag = false;
-// try {
- //设置请求
- URL url = new URL(baseurl+"/api/v5/account/balance");
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
- //设置请求方法
- connection.setRequestMethod("GET");
-
- //设置必需的头信息
- long now = System.currentTimeMillis() / 1000;
- String timestamp = String.valueOf(now);
- String preHash = timestamp + "GET" + "/api/v5/account/balance"; //URL路径
- String signature = generateSignature(preHash, apiMessageDto.getBSecretkey());
-
- connection.setRequestProperty("OK-ACCESS-KEY", apiMessageDto.getASecretkey());
- connection.setRequestProperty("OK-ACCESS-SIGN", signature);
- connection.setRequestProperty("OK-ACCESS-TIMESTAMP", timestamp);
- connection.setRequestProperty("OK-ACCESS-PASSPHRASE", apiMessageDto.getPassPhrass());
-
- connection.setRequestProperty("Content-Type", "application/json");
-
- //执行请求
- int responseCode = connection.getResponseCode();
- BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
- String inputLine;
- StringBuffer response = new StringBuffer();
-
- while ((inputLine = in.readLine()) != null) {
- response.append(inputLine);
- }
- in.close();
-
- //打印响应
- if(responseCode == 200){
- flag = true;
- } else {
- flag = false;
- }
-// } catch (Exception e) {
-// //在此打印异常信息
-// log.error("Exception: " + e.getMessage());
-// flag = false;
-// }
- return flag;
- }
-
- //此方法用于生成签名
- private static String generateSignature(String preHash, String secretKey) throws Exception{
- javax.crypto.Mac sha256_HMAC = javax.crypto.Mac.getInstance("HmacSHA256");
- javax.crypto.spec.SecretKeySpec secret_key = new javax.crypto.spec.SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
- sha256_HMAC.init(secret_key);
- return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(preHash.getBytes()));
- }
-
- public static void main(String[] args) throws Exception {
- String buySellSituation = "{\"code\":\"0\",\"data\":[[\"1718164800000\",\"52000.2999999999975\",\"60671.0000000000019\"]],\"msg\":\"\"}";
- log.info("buySellSituation:{}", buySellSituation);
- JSONObject balanceJson = JSON.parseObject(buySellSituation);
- JSONArray dataArray = balanceJson.getJSONArray("data");
- TradeBigdataDto tradeBigdataDto = new TradeBigdataDto();
- if (dataArray != null) {
- JSONArray innerArray = dataArray.getJSONArray(0);
- String sellVol = innerArray.getString(1);
- String buyVol = innerArray.getString(2);
- String ts = innerArray.getString(0);
- tradeBigdataDto.setBuyVol(buyVol);
- tradeBigdataDto.setSellVol(sellVol);
- tradeBigdataDto.setTs(ts);
- }
- System.out.println(tradeBigdataDto.toString());
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ApiAccountHoldVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ApiAccountHoldVo.java
deleted file mode 100644
index 88c5367..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ApiAccountHoldVo.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-@Data
-@ApiModel(value = "ApiAccountHoldVo", description = "列表")
-public class ApiAccountHoldVo {
-
- @ApiModelProperty(value = "总金额")
- private BigDecimal totalBalance = BigDecimal.ZERO;
- @ApiModelProperty(value = "盈亏比")
- private BigDecimal totalPercent = BigDecimal.ZERO;
- @ApiModelProperty(value = "可用金额")
- private BigDecimal availableBalance = BigDecimal.ZERO;
- @ApiModelProperty(value = "持仓金额")
- private BigDecimal holdBalance = BigDecimal.ZERO;
- @ApiModelProperty(value = "占用保证金")
- private BigDecimal useBalance = BigDecimal.ZERO;
-
- @ApiModelProperty(value = "币种余额")
- private BigDecimal cashBal = BigDecimal.ZERO;
-
- @ApiModelProperty(value = "可用保证金")
- private BigDecimal availEq = BigDecimal.ZERO;
-
- @ApiModelProperty(value = "币种占用金额")
- private BigDecimal frozenBal = BigDecimal.ZERO;
-
- @ApiModelProperty(value = "币种全仓保证金率")
- private String mgnRatio;
-
- @ApiModelProperty(value = "当前所需起始保证金")
- private BigDecimal initialMargin;
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ApiPositionsInfoVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ApiPositionsInfoVo.java
deleted file mode 100644
index 671c1c8..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ApiPositionsInfoVo.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-
-@Data
-@ApiModel(value = "ApiPositionsInfoVo", description = "持仓信息")
-public class ApiPositionsInfoVo implements Serializable {
-
- @ApiModelProperty(value = "开仓均价")
- private BigDecimal avgPx = BigDecimal.ZERO;
- @ApiModelProperty(value = "未实现盈亏")
- private BigDecimal upl = BigDecimal.ZERO;
- @ApiModelProperty(value = "盈亏平衡价")
- private BigDecimal bePx = BigDecimal.ZERO;
- @ApiModelProperty(value = "标记价格")
- private BigDecimal markPx = BigDecimal.ZERO;
- @ApiModelProperty(value = "持仓数量")
- private BigDecimal pos = BigDecimal.ZERO;
- @ApiModelProperty(value = "币种")
- private String coinName;
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ProductMessVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ProductMessVo.java
deleted file mode 100644
index 6c42884..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/ProductMessVo.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo;
-
-import lombok.Data;
-
-@Data
-public class ProductMessVo {
- // 币种
- private String symbol;
-
- // 下单价格精度
- private String tickSz;
-
- // 下单数量精度
- private String lotSz;
-
- // 最小下单数量
- private String minSz;
-
- // 合约面值
- private String ctVal;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/SinglemarketVo.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/SinglemarketVo.java
deleted file mode 100644
index b169a1c..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/verify/vo/SinglemarketVo.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.okxpi.verify.vo;
-
-import lombok.Data;
-
-@Data
-public class SinglemarketVo {
- // 产品类型
- private String instType;
-
- // 产品ID
- private String instId;
-
- // 最新成交价
- private String last;
-
- // 最新成交的数量,0 代表没有成交量
- private String lastSz;
-
- // 卖一价
- private String askPx;
-
- // 卖一价对应的数量
- private String askSz;
-
- // 买一价
- private String bidPx;
-
- // 买一价对应的数量
- private String bidSz;
-
- // 24小时开盘价
- private String open24h;
-
- // 24小时最高价
- private String high24h;
-
- // 24小时最低价
- private String low24h;
-
- // 24小时成交量,以币为单位
- // 如果是衍生品合约,数值为交易货币的数量。
- // 如果是币币/币币杠杆,数值为计价货币的数量。
- private String volCcy24h;
-
- // 24小时成交量,以张为单位
- // 如果是衍生品合约,数值为合约的张数。
- // 如果是币币/币币杠杆,数值为交易货币的数量。
- private String vol24h;
-
- // UTC+0 时开盘价
- private String sodUtc0;
-
- // UTC+8 时开盘价
- private String sodUtc8;
-
- // ticker数据产生时间,Unix时间戳的毫秒数格式,如 1597026383085
- private String ts;
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/FebsException.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/FebsException.java
deleted file mode 100644
index 98219f7..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/FebsException.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.utils;
-
-/**
- * FEBS系统内部异常
- *
- * @author MrBird
- */
-public class FebsException extends RuntimeException {
-
- private static final long serialVersionUID = -994962710559017255L;
-
- public FebsException(String message) {
- super(message);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/FebsResponse.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/FebsResponse.java
deleted file mode 100644
index c0e8d12..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/FebsResponse.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.utils;
-
-import org.springframework.http.HttpStatus;
-
-import java.util.HashMap;
-
-/**
- * @author MrBird
- */
-public class FebsResponse extends HashMap<String, Object> {
-
- private static final long serialVersionUID = -8713837118340960775L;
-
- public FebsResponse code(HttpStatus status) {
- this.put("code", status.value());
- return this;
- }
-
- public FebsResponse message(String message) {
- this.put("message", message);
- return this;
- }
-
- public FebsResponse data(Object data) {
- this.put("data", data);
- return this;
- }
-
- public FebsResponse success() {
- this.code(HttpStatus.OK);
- return this;
- }
-
- public FebsResponse fail() {
- this.code(HttpStatus.INTERNAL_SERVER_ERROR);
- return this;
- }
-
- @Override
- public FebsResponse put(String key, Object value) {
- super.put(key, value);
- return this;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/SSLConfig.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/SSLConfig.java
deleted file mode 100644
index 2187807..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/SSLConfig.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.utils;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import java.security.SecureRandom;
-
-/**
- * @author Administrator
- */
-public class SSLConfig {
- public static void configureSSL() {
- try {
- // 配置SSL上下文
- SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
- sslContext.init(null, null, new SecureRandom());
-
- // 设置默认SSL套接字工厂
- HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/SignUtils.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/SignUtils.java
deleted file mode 100644
index 87dfb2c..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/SignUtils.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.utils;
-
-import lombok.extern.slf4j.Slf4j;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.util.Base64;
-
-
-@Slf4j
-public class SignUtils {
-
- public static String signRest(String secretKey, String timestamp, String method, String path, String body) {
- String str = String.format("%s%s%s%s",
- timestamp, // timestamp
- method, // method GET/POST
- path, // requestPath
- body // body
- );
- try {
- return Base64.getEncoder().encodeToString(hmacSHA256(secretKey.getBytes(), str.getBytes()));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
-
-
- /**
- * HmacSHA256算法,返回的结果始终是32位
- *
- * @param key 加密的键,可以是任何数据
- * @param content 待加密的内容
- * @return 加密后的内容
- * @throws Exception ex
- */
- public static byte[] hmacSHA256(byte[] key, byte[] content) throws Exception {
- Mac hmacSha256 = Mac.getInstance("HmacSHA256");
- hmacSha256.init(new SecretKeySpec(key, 0, key.length, "HmacSHA256"));
- return hmacSha256.doFinal(content);
- }
-
- public static String signWebsocket(String timestamp, String secretKey) {
- String str = String.format("%s%s%s",
- timestamp,
- "GET",
- "/users/self/verify");
- try {
- return Base64.getEncoder().encodeToString(hmacSHA256(secretKey.getBytes(), str.getBytes()));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsMapBuild.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsMapBuild.java
deleted file mode 100644
index bb31a95..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsMapBuild.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.utils;
-
-import cn.hutool.core.util.StrUtil;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
-import lombok.extern.slf4j.Slf4j;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Map;
-
-/**
- * @author Administrator
- */
-@Slf4j
-public class WsMapBuild {
-
- public static boolean saveBigDecimalToMap(Map<String,BigDecimal> accountMap, String key, BigDecimal value) {
- try {
- accountMap.put(key, value);
- return true;
- } catch (Exception e) {
- log.error("保存账户数据到MAP 失败", e);
- return false;
- }
- }
-
- public static boolean saveStringToMap(Map<String,String> accountMap, String key, String value) {
- try {
- accountMap.put(key, value);
- return true;
- } catch (Exception e) {
- log.error("保存账户数据到MAP 失败", e);
- return false;
- }
- }
-
-
- /**
- * 安全地将字符串解析为 BigDecimal 类型
- *
- * @param value 字符串数值
- * @return 解析后的 BigDecimal 对象,若解析失败则返回 null
- */
- public static BigDecimal parseBigDecimalSafe(String value) {
- if (value == null || value.isEmpty()) {
- return new BigDecimal(0);
- }
- return new BigDecimal(value).setScale(Integer.parseInt(CoinEnums.TICKSZ.getCode()), RoundingMode.DOWN);
- }
-
- /**
- * 安全地将字符串解析为 BigDecimal 类型
- *
- * @param value 字符串数值
- * @return 解析后的 BigDecimal 对象,若解析失败则返回 null
- */
- public static String parseStringSafe(String value) {
- if (value == null || value.isEmpty()) {
- return "0";
- }
- return value;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsParamBuild.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsParamBuild.java
deleted file mode 100644
index 082e530..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsParamBuild.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.utils;
-
-import cn.hutool.core.util.StrUtil;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-
-import java.math.BigDecimal;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Random;
-
-/**
- * @author Administrator
- */
-public class WsParamBuild {
-
- public static String getOrderNum(String prefix) {
- SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
- String dd=df.format(new Date());
- if (StrUtil.isNotBlank(prefix)) {
- return prefix+dd+getRandomNum(5);
- }
- return dd+getRandomNum(5);
- }
-
- public static String getRandomNum(int length) {
- String str = "0123456789";
- Random random = new Random();
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < length; ++i) {
- int number = random.nextInt(str.length());
- sb.append(str.charAt(number));
- }
-
- return sb.toString();
- }
-
- public static JSONObject buildJsonObject(String connId, String option, JSONArray argsArray) {
- JSONObject jsonObject = new JSONObject();
- if (StrUtil.isNotEmpty(connId)){
- jsonObject.put("id", connId);
- }
- jsonObject.put("op", option);
- jsonObject.put("args", argsArray);
- return jsonObject;
- }
-
-
- /**
- * 计算购买合约的数量
- *
- * USDT 币本位合约
- * 公式:张数 = 保证金 / (面值 * 标记价格 / 杠杆倍数)
- *
- * @param margin 用户的保证金金额
- * @param leverage 杠杆倍数
- * @param faceValue 合约面值
- * @param markPrice 标记价格
- * @param minLotSz 最小下单精度
- * @return 返回用户可以购买的合约数量
- */
- public static BigDecimal buyCnt(BigDecimal margin, BigDecimal leverage, BigDecimal faceValue, BigDecimal markPrice, int minLotSz) {
- if (margin.compareTo(BigDecimal.ZERO) <= 0 ||
- leverage.compareTo(BigDecimal.ZERO) <= 0 ||
- faceValue.compareTo(BigDecimal.ZERO) <= 0 ||
- markPrice.compareTo(BigDecimal.ZERO) <= 0) {
- return BigDecimal.ZERO;
- }
-
- BigDecimal divisor = markPrice.divide(leverage, 10, BigDecimal.ROUND_DOWN);
- BigDecimal denominator = faceValue.multiply(divisor);
- return margin.divide(denominator, minLotSz, BigDecimal.ROUND_DOWN);
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeEnum.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeEnum.java
deleted file mode 100644
index 24f6f87..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeEnum.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.wangge;
-
-/**
- * @author Administrator
- * 网格数据枚举 数据
- * todo 后期考虑优化为可配置项
- */
-
-public enum WangGeEnum {
-
- XIAOSHU_WEISHU("网格价格小数位数", "2"),
- JIAGE_SHANGXIAN("网格上限", "3500"),
- JIAGE_XIAXIAN("网格下限", "2500"),
- JIAN_JU("网格间距", "5")
- ;
-
- private String name;
-
- private String value;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
-
- private WangGeEnum(String name, String value) {
- this.name = name;
- this.value = value;
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeQueue.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeQueue.java
deleted file mode 100644
index 85dddea..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeQueue.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.wangge;
-
-import com.xcong.excoin.rabbit.pricequeue.AscBigDecimal;
-import com.xcong.excoin.rabbit.pricequeue.DescBigDecimal;
-
-import java.util.concurrent.PriorityBlockingQueue;
-
-/**
- * 网格交易队列管理类
- *
- * 用于管理系统中各种网格交易相关的优先级阻塞队列,
- * 包括完整的网格队列、平仓队列和开仓队列。
- *
- * @author Administrator
- */
-public class WangGeQueue {
-
- //------------------------------------------------------------------------------------------------------------------
- //------------------------------------------------------------------------------------------------------------------
- // todo 系统启动后,初始化网格队列
- /**
- * 完整的网格 头元素最小
- */
- public static PriorityBlockingQueue<AscBigDecimal> QUEUE_ASC = null;
-
-
- //------------------------------------------------------------------------------------------------------------------
- //------------------------------------------------------------------------------------------------------------------
- // todo 当用户下了第一单后,根据开仓价格初始化网格平仓队列和开仓队列
- /**
- * 网格平仓队列 头元素最小
- */
- public static PriorityBlockingQueue<AscBigDecimal> QUEUE_PINGCANG_ASC = null;
-
- /**
- * 网格开仓队列 头元素最大
- */
- public static PriorityBlockingQueue<DescBigDecimal> QUEUE_KAICANG_DESC = null;
-
- /**
- * 获取完整的网格队列(升序)
- * 如果队列未初始化则创建新的优先级阻塞队列
- *
- * @return 返回升序排列的PriorityBlockingQueue队列,队列头部元素最小
- */
- public static PriorityBlockingQueue<AscBigDecimal> getQueueAsc() {
- if (QUEUE_ASC == null) {
- QUEUE_ASC = new PriorityBlockingQueue<AscBigDecimal>();
- }
- return QUEUE_ASC;
- }
-
- /**
- * 获取网格平仓队列(升序)
- * 如果队列未初始化则创建新的优先级阻塞队列
- *
- * @return 返回升序排列的PriorityBlockingQueue队列,队列头部元素最小
- */
- public static PriorityBlockingQueue<AscBigDecimal> getPingCang() {
- if (QUEUE_PINGCANG_ASC == null) {
- QUEUE_PINGCANG_ASC = new PriorityBlockingQueue<AscBigDecimal>();
- }
- return QUEUE_PINGCANG_ASC;
- }
-
- /**
- * 获取网格开仓队列(降序)
- * 如果队列未初始化则创建新的优先级阻塞队列
- *
- * @return 返回降序排列的PriorityBlockingQueue队列,队列头部元素最大
- */
- public static PriorityBlockingQueue<DescBigDecimal> getKaiCang() {
- if (QUEUE_KAICANG_DESC == null) {
- QUEUE_KAICANG_DESC = new PriorityBlockingQueue<DescBigDecimal>();
- }
- return QUEUE_KAICANG_DESC;
- }
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeService.java
deleted file mode 100644
index 9b240bb..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeService.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.wangge;
-
-import com.xcong.excoin.rabbit.pricequeue.AscBigDecimal;
-import com.xcong.excoin.rabbit.pricequeue.DescBigDecimal;
-
-import java.math.BigDecimal;
-import java.util.concurrent.PriorityBlockingQueue;
-
-/**
- * 网格交易服务接口
- * 定义了网格交易的核心操作方法,包括初始化网格、开仓和平仓等操作
- * @author Administrator
- */
-public interface WangGeService {
-
- /**
- * 初始化网格交易
- * 创建并初始化用于网格交易的价格队列,按照价格升序排列
- * @return 初始化结果信息,返回按价格升序排列的阻塞队列
- */
- PriorityBlockingQueue<AscBigDecimal> initWangGe();
-
- /**
- * 初始化开仓操作
- * 根据指定价格初始化开仓队列,将开仓价格点加入到价格队列中
- * @param jiaGe 开仓价格
- * @param queueAsc 价格队列,用于存储按升序排列的价格点
- */
- PriorityBlockingQueue<DescBigDecimal> initKaiCang(BigDecimal jiaGe, PriorityBlockingQueue<AscBigDecimal> queueAsc);
-
- /**
- * 初始化平仓操作
- * 根据指定价格初始化平仓队列,将平仓价格点加入到价格队列中
- * @param jiaGe 开仓价格
- * @param queueAsc 价格队列,用于存储按升序排列的价格点
- */
- PriorityBlockingQueue<AscBigDecimal> initPingCang(BigDecimal jiaGe, PriorityBlockingQueue<AscBigDecimal> queueAsc);
-
-
-}
-
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeServiceImpl.java
deleted file mode 100644
index 02371a7..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/wangge/WangGeServiceImpl.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.wangge;
-
-import com.xcong.excoin.rabbit.pricequeue.AscBigDecimal;
-import com.xcong.excoin.rabbit.pricequeue.DescBigDecimal;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.concurrent.PriorityBlockingQueue;
-
-/**
- * 网格交易服务实现类,用于初始化价格网格、开仓和平仓操作。
- *
- * @author Administrator
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class WangGeServiceImpl implements WangGeService {
-
- /**
- * 初始化价格网格队列。根据配置的价格上限、下限和间隔生成一系列价格点,
- * 并将这些价格点存入升序优先阻塞队列中。
- *
- * @return 返回初始化完成的升序价格队列;若初始化失败则返回null
- */
- @Override
- public PriorityBlockingQueue<AscBigDecimal> initWangGe() {
- log.info("网格初始化中");
- PriorityBlockingQueue<AscBigDecimal> queueAsc = WangGeQueue.getQueueAsc();
- queueAsc.clear();
-
- String shangxianValue = WangGeEnum.JIAGE_SHANGXIAN.getValue();
- String xiaxianValue = WangGeEnum.JIAGE_XIAXIAN.getValue();
- String jianjuValue = WangGeEnum.JIAN_JU.getValue();
- String weishuValueStr = WangGeEnum.XIAOSHU_WEISHU.getValue();
-
- try {
- BigDecimal shangxian = new BigDecimal(shangxianValue);
- BigDecimal xiaxian = new BigDecimal(xiaxianValue);
- BigDecimal jianju = new BigDecimal(jianjuValue);
-
- if (jianju.compareTo(BigDecimal.ZERO) == 0) {
- log.error("价格间隔不能为0");
- return null;
- }
-
- int weishu = Integer.parseInt(weishuValueStr);
- BigDecimal diff = shangxian.subtract(xiaxian);
- int count = diff.divide(jianju, 0, RoundingMode.DOWN).intValue();
-
- BigDecimal currentStep = BigDecimal.ZERO;
- for (int i = 0; i <= count; i++) {
- BigDecimal stepMultiplier = currentStep.multiply(jianju);
- BigDecimal wangGeJiaGe = xiaxian.add(stepMultiplier).setScale(weishu, RoundingMode.DOWN);
- AscBigDecimal ascBigDecimal = new AscBigDecimal(wangGeJiaGe.toString());
- queueAsc.add(ascBigDecimal);
- currentStep = currentStep.add(BigDecimal.ONE);
- }
-
- if (queueAsc.isEmpty()) {
- log.info("网格初始化失败");
- return null;
- }
-
- log.info("网格初始化成功");
- return queueAsc;
- } catch (NumberFormatException e) {
- log.error("解析价格参数失败", e);
- return null;
- } catch (Exception e) {
- log.error("初始化网格发生未知异常", e);
- return null;
- }
- }
-
- /**
- * 根据当前价格初始化开仓队列。遍历已有的升序价格队列,
- * 将小于当前价格的所有价格点加入降序的开仓队列中。
- *
- * @param jiaGe 当前价格
- * @param queueAsc 已初始化的价格升序队列
- */
- @Override
- public PriorityBlockingQueue<DescBigDecimal> initKaiCang(BigDecimal jiaGe, PriorityBlockingQueue<AscBigDecimal> queueAsc) {
- PriorityBlockingQueue<DescBigDecimal> queueKaiCang = WangGeQueue.getKaiCang();
- queueKaiCang.clear();
-
- AscBigDecimal now = new AscBigDecimal(jiaGe.toString());
-
- for (AscBigDecimal ascBigDecimal : queueAsc) {
- if (ascBigDecimal.compareTo(now) < 0) {
- DescBigDecimal kaiCangJia = new DescBigDecimal(ascBigDecimal.getValue().toString());
- queueKaiCang.add(kaiCangJia);
- }
- }
-
- return queueKaiCang;
- }
-
- /**
- * 根据当前价格初始化平仓队列。遍历已有的升序价格队列,
- * 将大于当前价格的所有价格点加入升序的平仓队列中。
- *
- * @param jiaGe 当前价格
- * @param queueAsc 已初始化的价格升序队列
- */
- @Override
- public PriorityBlockingQueue<AscBigDecimal> initPingCang(BigDecimal jiaGe, PriorityBlockingQueue<AscBigDecimal> queueAsc) {
- PriorityBlockingQueue<AscBigDecimal> queuePingCang = WangGeQueue.getPingCang();
- queuePingCang.clear();
-
- AscBigDecimal now = new AscBigDecimal(jiaGe.toString());
-
- for (AscBigDecimal ascBigDecimal : queueAsc) {
- if (ascBigDecimal.compareTo(now) > 0) {
- queuePingCang.add(ascBigDecimal);
- }
- }
-
- return queuePingCang;
- }
-
- /**
- * 主方法,用于测试网格初始化及开仓/平仓逻辑。
- * 示例使用固定价格"0.355"进行模拟调用。
- *
- * @param args 启动参数(未使用)
- */
- public static void main(String[] args) {
- for (int i = 0; i < 10; i++) {
- WangGeServiceImpl wangGeService = new WangGeServiceImpl();
- PriorityBlockingQueue<AscBigDecimal> queueAsc = wangGeService.initWangGe();
- if (queueAsc != null) {
- wangGeService.initKaiCang(new BigDecimal("91000"), queueAsc);
- wangGeService.initPingCang(new BigDecimal("91000"), queueAsc);
- }
- }
- }
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/ApiMessageServiceImpl.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/ApiMessageServiceImpl.java
deleted file mode 100644
index c8b8f0e..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/ApiMessageServiceImpl.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.zhanghu;
-
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.ExchangeInfoEnum;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class ApiMessageServiceImpl implements IApiMessageService {
-
- @Override
- public QuantApiMessage getApiMessage(String okx) {
-
- QuantApiMessage quantApiMessage = new QuantApiMessage();
- quantApiMessage.setExchange(okx);
- quantApiMessage.setMemberId(1L);
-
- // 根据传入的账号名称获取对应的账号信息
- ExchangeInfoEnum account = ExchangeInfoEnum.valueOf(okx);
- quantApiMessage.setAccountType(account.isAccountType()? "true":"false");
- quantApiMessage.setState(1);
- quantApiMessage.setIsTrade(1);
- quantApiMessage.setASecretkey(account.getApiKey());
- quantApiMessage.setBSecretkey(account.getSecretKey());
- quantApiMessage.setPassPhrass(account.getPassphrase());
- return quantApiMessage;
- }
-
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/IApiMessageService.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/IApiMessageService.java
deleted file mode 100644
index 73ec98d..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/IApiMessageService.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.zhanghu;
-
-import com.xcong.excoin.modules.okxNewPrice.okxpi.config.Dto.QuantApiMessage;
-
-
-public interface IApiMessageService{
-
- QuantApiMessage getApiMessage(String okx);
-
-}
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/ZhangHuEnum.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/ZhangHuEnum.java
deleted file mode 100644
index 3647e8b..0000000
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/ZhangHuEnum.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.xcong.excoin.modules.okxNewPrice.zhanghu;
-
-/**
- * @author Administrator
- */
-
-public enum ZhangHuEnum {
- JIAOYISUO("交易所名字", "OKX"),
- zhanghu_zongjine("账户总资金", "1000"),
- baozhengjine("账户下单总保证金", "300"),
- meicixiadanjine("每次下单金额", "30"),
- ;
-
- private String name;
-
- private String value;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
-
- private ZhangHuEnum(String name, String value) {
- this.name = name;
- this.value = value;
- }
-}
--
Gitblit v1.9.1