From 86a73a6274f9c5938c39c03198f11df9b1735320 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 18 Dec 2025 17:35:01 +0800
Subject: [PATCH] feat(okx): 实现新的交易策略与账户保证金逻辑
---
src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md | 285 +++++++++++
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java | 5
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java | 6
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java | 608 +++++++++++++-----------
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java | 4
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java | 6
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java | 28 +
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java | 10
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java | 153 ++---
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java | 15
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxNewPriceWebSocketClient.java | 57 +
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/TradeRequestParam.java | 32 +
src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX_QUANT_DOCUMENTATION.md | 254 ++++++++++
13 files changed, 1,070 insertions(+), 393 deletions(-)
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
new file mode 100644
index 0000000..f4897c8
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX_QUANT_DOCUMENTATION.md
@@ -0,0 +1,254 @@
+# OKX量化交易模块技术文档
+
+## 1. 包结构
+
+```
+com.xcong.excoin.modules.okxNewPrice
+├── OkxWebSocketClientManager.java # WebSocket客户端管理器
+├── OkxNewPriceWebSocketClient.java # 公共价格WebSocket客户端
+├── OkxQuantWebSocketClient.java # 多账号量化交易WebSocket客户端
+├── README.md # 模块说明文档
+├── celue/ # 策略执行层
+│ ├── CaoZuoService.java # 策略执行接口
+│ └── CaoZuoServiceImpl.java # 策略执行实现
+├── okxWs/ # OKX WebSocket通信层
+│ ├── AccountWs.java # 账户信息处理
+│ ├── BalanceAndPositionWs.java # 余额和仓位处理
+│ ├── LoginWs.java # 登录处理
+│ ├── OrderInfoWs.java # 订单信息处理
+│ ├── PositionsWs.java # 仓位信息处理
+│ ├── TradeOrderWs.java # 交易订单处理
+│ ├── enums/ # 枚举定义
+│ └── wanggeList/ # 网格策略配置
+│ ├── WangGeListEnum.java # 网格参数枚举
+│ └── WangGeListServiceImpl.java # 网格策略服务
+├── okxpi/ # OKX API工具
+└── utils/ # 工具类
+ ├── SSLConfig.java # SSL配置
+ ├── WsMapBuild.java # Map构建工具
+ └── WsParamBuild.java # WebSocket参数构建工具
+```
+
+## 2. 核心组件说明
+
+### 2.1 WebSocket客户端管理
+
+#### OkxWebSocketClientManager
+- **功能**:管理所有OKX WebSocket客户端实例,包括价格客户端和多账号量化客户端
+- **核心特性**:
+ - 统一初始化、连接和销毁管理
+ - 多账号客户端映射(accountName → OkxQuantWebSocketClient)
+ - 单一价格客户端管理
+- **关键方法**:
+ - `init()`:初始化所有客户端
+ - `destroy()`:销毁所有客户端
+ - `getAllClients()`:获取所有账号客户端
+
+### 2.2 WebSocket客户端
+
+#### OkxNewPriceWebSocketClient
+- **功能**:连接OKX公共WebSocket接口,获取实时标记价格
+- **核心特性**:
+ - 心跳检测和自动重连
+ - 价格数据解析和Redis存储
+ - 价格变化触发策略执行
+- **关键流程**:
+ - `init()` → `connect()` → `subscribeChannels()` → 接收价格数据 → `processPushData()` → `triggerQuantOperations()`
+
+#### OkxQuantWebSocketClient
+- **功能**:连接OKX私有WebSocket接口,处理账号相关数据
+- **核心特性**:
+ - 账号登录认证
+ - 订阅账号、订单、仓位等私有频道
+ - 处理私有数据推送
+- **关键流程**:
+ - `init()` → `connect()` → `websocketLogin()` → 订阅私有频道 → 接收数据
+
+### 2.3 策略执行层
+
+#### CaoZuoService
+- **功能**:执行量化交易策略,决定买卖操作方向
+- **核心方法**:
+ - `caoZuo(String accountName)`:根据账号执行策略
+ - `caoZuoLong(String accountName, String markPx)`:多头策略执行
+ - `caoZuoShort(String accountName, String markPx)`:空头策略执行
+- **策略逻辑**:
+ - 根据当前价格确定所在网格位置
+ - 结合网格方向参数决定操作方向
+ - 考虑仓位状态和风险控制
+
+### 2.4 数据管理
+
+#### PositionsWs
+- **功能**:管理账户仓位数据,支持多空方向分离
+- **核心特性**:
+ - 双层Map存储:accountName → dataKey → value
+ - 支持多空方向分离(accountName_posSide)
+ - 数据就绪状态标记
+- **关键方法**:
+ - `initAccountName(String accountName, String posSide)`:初始化带多空方向的账号名
+ - `handleEvent(JSONObject response, String accountName)`:处理仓位数据推送
+
+### 2.5 网格策略
+
+#### WangGeListEnum
+- **功能**:定义网格策略参数
+- **核心参数**:
+ - `jiage_shangxian`:价格上限
+ - `jiage_xiaxian`:价格下限
+ - `fang_xiang`:操作方向(long/short)
+ - `jian_ju`:网格间距
+- **关键方法**:
+ - `getGridByPrice(BigDecimal price)`:根据当前价格获取匹配的网格
+
+## 3. 业务流程
+
+### 3.1 系统初始化流程
+
+```
+1. Spring容器启动,OkxWebSocketClientManager初始化
+2. 创建OkxNewPriceWebSocketClient实例并初始化
+3. 遍历ExchangeInfoEnum,为每个账号创建OkxQuantWebSocketClient实例
+4. 所有客户端建立WebSocket连接并进行认证
+5. 订阅相应的WebSocket频道
+```
+
+### 3.2 价格触发交易流程
+
+```
+1. OkxNewPriceWebSocketClient接收价格数据
+2. 调用`processPushData()`处理价格数据
+3. 调用`triggerQuantOperations()`触发策略执行
+4. 遍历所有账号客户端:
+ a. 获取账号名称
+ b. 调用`CaoZuoService.caoZuo(accountName)`确定操作方向
+ c. 调用`TradeOrderWs.orderEvent()`执行订单操作
+```
+
+### 3.3 网格策略执行流程
+
+```
+1. 获取当前价格
+2. 调用`WangGeListEnum.getGridByPrice(price)`确定网格位置
+3. 根据网格的`fang_xiang`参数确定操作方向
+4. 结合当前仓位状态和市场情况,决定最终操作(买/卖/止损/初始化)
+5. 返回操作方向给调用者
+```
+
+## 4. 技术架构
+
+### 4.1 客户端架构
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ 应用层 │
+├─────────────────────────────────────────────────────────┤
+│ OkxWebSocketClientManager │
+├─────────────────┬───────────────────────────────────────┤
+│ │ │
+│ OkxNewPrice │ OkxQuantWebSocketClient (多实例) │
+│ WebSocketClient │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
+│ │ │ Account1│ │ Account2│ │ AccountN│ │
+│ │ └─────────┘ └─────────┘ └─────────┘ │
+├─────────────────┴───────────────────────────────────────┤
+│ WebSocket通信层 │
+├─────────────────────────────────────────────────────────┤
+│ OKX WebSocket API │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 4.2 数据流向
+
+```
+1. OKX公共WebSocket → OkxNewPriceWebSocketClient → Redis存储价格
+2. OKX私有WebSocket → OkxQuantWebSocketClient → 私有数据处理
+3. 价格变化 → CaoZuoService策略执行 → TradeOrderWs订单执行
+4. 订单结果 → OrderInfoWs处理 → 更新订单状态
+5. 仓位变化 → PositionsWs处理 → 更新仓位数据
+```
+
+## 5. 关键特性
+
+### 5.1 WebSocket连接管理
+- **心跳检测**:定期发送ping请求,检测连接有效性
+- **自动重连**:连接断开时,使用指数退避算法自动重连
+- **连接状态监控**:实时监控连接状态(connected/connecting/initialized)
+
+### 5.2 多账号支持
+- **独立客户端**:每个账号拥有独立的WebSocket客户端实例
+- **账号隔离**:账号数据隔离存储,避免数据冲突
+- **统一管理**:通过OkxWebSocketClientManager统一管理所有账号客户端
+
+### 5.3 网格策略
+- **多网格支持**:支持设置多个网格区域
+- **方向控制**:每个网格可配置独立的操作方向(long/short)
+- **动态匹配**:根据当前价格自动匹配对应的网格策略
+
+### 5.4 多空支持
+- **仓位分离**:多头和空头仓位数据分离存储
+- **独立操作**:多空方向可独立进行策略执行
+- **风险控制**:针对多空方向独立设置风险控制参数
+
+## 6. 配置与扩展
+
+### 6.1 网格策略配置
+当前网格策略通过`WangGeListEnum`硬编码配置:
+
+```java
+UP("上层做空", "2", "3100", "3000", "2", "short", "3100"),
+CENTER("中间指定一个方向", "2", "3000", "2950", "2", "long", "2950"),
+DOWN("下层做空", "2", "2950", "2920", "2", "short", "2950"),
+DOWN_ONE("下层做多", "2", "2920", "2900", "2", "long", "2900");
+```
+
+**扩展建议**:
+- 将网格参数迁移到数据库或配置文件
+- 实现动态加载和更新网格策略
+- 支持网格策略的增删改查操作
+
+### 6.2 账号配置
+账号信息通过`ExchangeInfoEnum`配置,每个枚举值对应一个交易账号。
+
+### 6.3 性能优化
+- **线程池管理**:合理配置线程池大小,避免资源浪费
+- **连接复用**:考虑连接复用策略,减少连接数量
+- **数据缓存**:合理使用缓存,减少重复计算
+
+## 7. 开发与维护
+
+### 7.1 开发流程
+1. 理解现有策略逻辑和数据流
+2. 根据需求修改或扩展策略
+3. 更新相关配置(如网格参数)
+4. 测试策略有效性
+5. 部署到生产环境
+
+### 7.2 常见问题
+
+**问题1**:WebSocket连接频繁断开
+**解决**:检查网络环境,调整心跳间隔和重连策略
+
+**问题2**:策略执行不符合预期
+**解决**:检查网格参数配置,分析策略执行日志,调整策略逻辑
+
+**问题3**:订单执行失败
+**解决**:检查账号权限,查看订单执行日志,分析失败原因
+
+### 7.3 日志与监控
+- 关键操作日志:记录WebSocket连接、策略执行、订单操作等关键事件
+- 错误日志:记录异常情况,便于问题排查
+- 性能监控:监控系统性能指标,及时发现性能瓶颈
+
+## 8. 未来规划
+
+1. **动态策略配置**:支持通过后台管理系统动态配置网格策略
+2. **策略回测**:实现策略回测功能,提高策略有效性
+3. **风险控制增强**:增加更完善的风险控制机制
+4. **多交易所支持**:扩展支持其他交易所
+5. **数据可视化**:实现交易数据可视化展示
+
+---
+
+**版本**:1.0
+**更新日期**:2025-12-17
+**维护人**:开发团队
\ 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
index f079744..181ecd0 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxNewPriceWebSocketClient.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxNewPriceWebSocketClient.java
@@ -4,19 +4,21 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
-import com.xcong.excoin.modules.okxNewPrice.OkxWebSocketClientManager;
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.rabbit.pricequeue.WebsocketPriceService;
-import com.xcong.excoin.utils.CoinTypeConvert;
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;
@@ -32,6 +34,7 @@
private final RedisUtils redisUtils;
private final CaoZuoService caoZuoService;
private final OkxWebSocketClientManager clientManager;
+ private final WangGeListService wangGeListService;
private WebSocketClient webSocketClient;
private ScheduledExecutorService heartbeatExecutor;
@@ -56,10 +59,12 @@
});
public OkxNewPriceWebSocketClient(RedisUtils redisUtils,
- CaoZuoService caoZuoService, OkxWebSocketClientManager clientManager) {
+ CaoZuoService caoZuoService, OkxWebSocketClientManager clientManager,
+ WangGeListService wangGeListService) {
this.redisUtils = redisUtils;
this.caoZuoService = caoZuoService;
this.clientManager = clientManager;
+ this.wangGeListService = wangGeListService;
}
/**
@@ -287,18 +292,46 @@
*/
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()) {
- // 由于OkxQuantWebSocketClient没有直接暴露账号名称的方法,我们需要从clientManager中获取
- // 这里可以通过遍历clientMap的方式获取账号名称
- // 或者修改OkxQuantWebSocketClient,添加getAccountName方法
- // 暂时使用这种方式获取账号名称
String accountName = getAccountNameFromClient(client);
if (accountName != null) {
- // 调用CaoZuoService的caoZuo方法,触发量化操作
- String side = caoZuoService.caoZuo(accountName);
- TradeOrderWs.orderEvent(client.getWebSocketClient(), side, accountName);
- log.info("价格变化触发量化操作: 账号={}, 价格={}, 操作方向={}", accountName, markPx, side);
+ /**
+ * 处理历史网格的订单
+ * 根据历史网格的开单方向,是否需要止损处理
+ * 如果方向一致就不需要处理
+ * 如果不一致则需要处理
+ */
+ String fangXiang = gridByPriceNew.getFang_xiang();
+ String fangXiangOld = CoinEnums.POSSIDE_LONG.equals(fangXiang) ? CoinEnums.POSSIDE_SHORT.getCode() : CoinEnums.POSSIDE_LONG.getCode();
+ log.info("历史网格方向为:{}", 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) {
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
index 21dc07e..be64554 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
@@ -2,6 +2,7 @@
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.modules.okxNewPrice.wangge.WangGeService;
import com.xcong.excoin.rabbit.pricequeue.WebsocketPriceService;
import com.xcong.excoin.utils.RedisUtils;
@@ -26,6 +27,8 @@
private CaoZuoService caoZuoService;
@Autowired
private RedisUtils redisUtils;
+ @Autowired
+ private WangGeListService wangGeListService;
// 存储所有OkxQuantWebSocketClient实例,key为账号类型名称
private final Map<String, OkxQuantWebSocketClient> quantClientMap = new ConcurrentHashMap<>();
@@ -43,7 +46,7 @@
// 初始化价格WebSocket客户端
try {
- newPriceClient = new OkxNewPriceWebSocketClient(redisUtils, caoZuoService, this);
+ newPriceClient = new OkxNewPriceWebSocketClient(redisUtils, caoZuoService, this, wangGeListService);
newPriceClient.init();
log.info("已初始化OkxNewPriceWebSocketClient");
} catch (Exception e) {
diff --git a/src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md b/src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md
new file mode 100644
index 0000000..cb6e715
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md
@@ -0,0 +1,285 @@
+# 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
index 0d700c3..e53aa38 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java
@@ -1,13 +1,35 @@
package com.xcong.excoin.modules.okxNewPrice.celue;
+import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam;
+
/**
* @author Administrator
*/
public interface CaoZuoService {
- String caoZuo(String accountName);
+ TradeRequestParam caoZuoHandler(String accountName, String markPx, String posSide);
- String caoZuoLong(String accountName,String markPx);
+ /**
+ * 止损 事件
+ * @param accountName
+ * @param markPx
+ * @param posSide
+ * @return
+ */
+ TradeRequestParam caoZuoZhiSunEvent(String accountName, String markPx, String posSide);
- String caoZuoShort(String accountName,String markPx);
+ /**
+ * 初始化 事件
+ * @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
index 9328774..31191b4 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
@@ -5,10 +5,12 @@
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;
@@ -42,91 +44,33 @@
* @return 返回操作类型字符串(如买入BUY、卖出SELL等),如果无有效操作则返回null
*/
@Override
- public String caoZuo(String accountName) {
- String accountReadyState = AccountWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name());
- if (!CoinEnums.READY_STATE_YES.getCode().equals(accountReadyState)) {
- log.info("账户通道未就绪,取消发送");
- return null;
- }
- String markPx = ObjectUtil.isEmpty(redisUtils.getString(CoinEnums.HE_YUE.getCode())) ? "0" : redisUtils.getString(CoinEnums.HE_YUE.getCode());
+ 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("当前价格: {}", markPx);
- WangGeListEnum gridByPrice = WangGeListEnum.getGridByPrice(new BigDecimal(markPx));
- if (gridByPrice == null){
- log.error("没有获取到网格参数......");
- return null;
- }
- log.info("当前网格: {}", gridByPrice.name());
-
- PriorityBlockingQueue<AscBigDecimal> ascBigDecimals = wangGeListService.initWangGe(markPx);
- if (ascBigDecimals == null){
- log.error("没有获取到网格队列......");
- return null;
- }
+ log.info("操作账户:{},当前价格: {},仓位方向: {}", accountName,markPx,posSide);
/**
- * 如果下单的网格不属于同一个网格,则先止损掉老的网格的仓位
+ * 准备工作
+ * 1、准备好下单的基本信息
*/
- Map<String, String> accountMap = InstrumentsWs.getAccountMap(accountName);
- String wanggeName = accountMap.get(CoinEnums.WANG_GE_OLD.name());
- if (StrUtil.isNotEmpty(wanggeName) && !wanggeName.equals(gridByPrice.name())){
- log.error("正在止损老的网格仓位......");
- WangGeListEnum oldWangge = WangGeListEnum.getByName(wanggeName);
- if (oldWangge != null){
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.POSSIDE.name(), oldWangge.getFang_xiang());
- return OrderParamEnums.OUT.getValue();
- }
- }
- String posSide = gridByPrice.getFang_xiang();
- log.info("仓位方向: {}", posSide);
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.POSSIDE.name(), posSide);
- String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- 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);
- // 判断是否保证金超标
- if (PositionsWs.getAccountMap(positionAccountName).get("imr") == null){
- log.error("没有获取到持仓信息,等待初始化......");
- return null;
- }
- BigDecimal ordFrozImr = PositionsWs.getAccountMap(positionAccountName).get("imr");
- BigDecimal totalOrderUsdt = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDT.name()));
- if (ordFrozImr.compareTo(totalOrderUsdt) >= 0){
- log.error("已满仓......");
- return OrderParamEnums.HOLDING.getValue();
- }
- if (PositionsWs.getAccountMap(positionAccountName).get("pos") == null){
- log.error("没有获取到持仓信息,等待初始化......");
- return null;
- }
- BigDecimal pos = PositionsWs.getAccountMap(positionAccountName).get("pos");
- if (BigDecimal.ZERO.compareTo( pos) >= 0) {
- log.error("持仓数量为零,进行初始化订单");
- return OrderParamEnums.INIT.getValue();
- }else{
- log.error("仓位有持仓,等待持仓更新");
- return null;
- }
- }
// 系统设置的开关,等于冷静中,则代表不开仓
String outStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.OUT.name());
if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){
log.error("冷静中,不允许下单......");
- return null;
+ tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
+ return chooseEvent(tradeRequestParam);
}
BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("cashBal"));
-
- // 判断账户余额是否充足
- if (cashBal.compareTo(BigDecimal.ZERO) <= 0){
- log.error("账户没有钱,请充值......");
- return null;
- }
/**
* 判断止损抗压
*/
- // 实际亏损金额
BigDecimal realKuiSunAmount = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("upl"));
- log.info("未实现盈亏: {}", realKuiSunAmount);
+ log.info("实际盈亏金额: {}", realKuiSunAmount);
String zhiSunPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.ZHI_SUN.name());
BigDecimal zhiSunAmount = cashBal.multiply(new BigDecimal(zhiSunPercent));
log.info("预期亏损金额: {}", zhiSunAmount);
@@ -140,56 +84,268 @@
if (realKuiSunAmount.compareTo(zhiSunAmount) > 0){
log.error("账户冷静止损......");
WsMapBuild.saveStringToMap(InstrumentsWs.getAccountMap(accountName), CoinEnums.OUT.name(), OrderParamEnums.OUT_YES.getValue());
- return OrderParamEnums.OUT.getValue();
+ tradeRequestParam.setTradeType(OrderParamEnums.TRADE_YES.getValue());
+ return caoZuoZhiSunEvent(accountName, markPx, posSide);
}
// 判断抗压
if (realKuiSunAmount.compareTo(kangYaAmount) > 0 && realKuiSunAmount.compareTo(zhiSunAmount) <= 0){
log.error("账户紧张扛仓......");
- return OrderParamEnums.HOLDING.getValue();
+ tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
+ return chooseEvent(tradeRequestParam);
}
}
+
+ String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
// 判断是否保证金超标
if (PositionsWs.getAccountMap(positionAccountName).get("imr") == null){
log.error("没有获取到持仓信息,等待初始化......");
- return null;
+ 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()));
+ 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("已满仓......");
- return OrderParamEnums.HOLDING.getValue();
+ tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue());
+ return chooseEvent(tradeRequestParam);
}
if (PositionsWs.getAccountMap(positionAccountName).get("pos") == null){
log.error("没有获取到持仓信息,等待初始化......");
- return null;
+ 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 OrderParamEnums.INIT.getValue();
+ 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)){
- return caoZuoLong(accountName,markPx);
- }else if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)){
- return caoZuoShort(accountName,markPx);
- }else{
- log.error("账户未设置持仓方向......");
- return null;
+ 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);
+ 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");
+ //未实现收益
+ 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 String caoZuoLong(String accountName,String markPxStr) {
- log.info("开始看涨执行操作CaoZuoServiceImpl......");
- try {
+ public TradeRequestParam caoZuoShort(TradeRequestParam tradeRequestParam) {
+ log.info("开始做{}执行操作CaoZuoServiceImpl......",tradeRequestParam.getPosSide());
- String positionAccountName = PositionsWs.initAccountName(accountName, CoinEnums.POSSIDE_LONG.getCode());
+ String accountName = tradeRequestParam.getAccountName();
+ String markPxStr = tradeRequestParam.getMarkPx();
+ String posSide = tradeRequestParam.getPosSide();
+
+ try {
+ String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
// 获取标记价格和平均持仓价格
-// BigDecimal markPx = PositionsWs.getAccountMap(positionAccountName).get("markPx");
BigDecimal markPx = new BigDecimal(markPxStr);
BigDecimal avgPx = PositionsWs.getAccountMap(positionAccountName).get("avgPx");
- log.info("开仓价格: {}, 当前价格:{},匹配队列中......", avgPx, markPx);
+ log.info("持仓价格: {}, 当前价格:{},匹配队列中......", avgPx, markPx);
// 初始化网格队列
PriorityBlockingQueue<AscBigDecimal> queueAsc = WangGeListQueue.getQueueAsc();
@@ -198,196 +354,99 @@
// 处理订单价格在队列中的情况
String orderPrice = OrderInfoWs.getAccountMap(accountName).get("orderPrice");
- log.info("订单价格: {}", orderPrice);
+ log.info("上一次网格触发价格:{}", orderPrice);
handleOrderPriceInQueues(orderPrice, queueKaiCang, queuePingCang);
// 判断是加仓还是减仓
if (avgPx.compareTo(markPx) > 0) {
- log.info("开始加仓...");
- if (queueKaiCang.isEmpty()) {
- // 队列为空
- log.info("开始加仓,但是超出了网格设置...");
- return OrderParamEnums.HOLDING.getValue();
- }
- 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));
- boolean buyCntTimeFlag = buyCntTimeLongEvent(accountName, avgPx, markPx);
- if (buyCntTimeFlag){
- log.info("加仓参数准备成功......");
- return OrderParamEnums.BUY.getValue();
- }else{
- log.error("加仓参数准备失败......");
- return null;
+ 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");
+ //未实现收益
+ 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("未触发加仓......,等待");
- return OrderParamEnums.HOLDING.getValue();
+ }else{
+ log.info("开始减仓,但是超出了网格设置...");
}
} else if (avgPx.compareTo(markPx) < 0) {
- log.info("开始减仓...");
- if (queuePingCang.isEmpty()) {
- // 队列为空
- log.info("开始减仓,但是超出了网格设置...");
- return OrderParamEnums.HOLDING.getValue();
- }
- 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");
- //未实现收益
- 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));
- return OrderParamEnums.SELL.getValue();
- }else{
- log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(realizedPnlValueZheng));
- return OrderParamEnums.HOLDING.getValue();
- }
- }else {
- if (uplValue.compareTo(imrValue.add(feeValue)) >= 0) {
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(feeValue));
- return OrderParamEnums.SELL.getValue();
- }else{
- log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(feeValue));
- return OrderParamEnums.HOLDING.getValue();
- }
+ 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_BUY.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("超出了网格设置...");
}
} else {
log.info("价格波动较小......,等待");
}
- return null;
+ return tradeRequestParam;
} catch (NumberFormatException e) {
- log.error("解析价格失败,请检查Redis中的值是否合法", e);
- return null;
+ log.error("开空方向异常", e);
+ return tradeRequestParam;
}
}
- @Override
- public String caoZuoShort(String accountName,String markPxStr) {
- log.info("开始看空执行操作CaoZuoServiceImpl......");
- try {
-
-
- String positionAccountName = PositionsWs.initAccountName(accountName, CoinEnums.POSSIDE_SHORT.getCode());
- // 获取标记价格和平均持仓价格
-// BigDecimal markPx = PositionsWs.getAccountMap(positionAccountName).get("markPx");
- 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()) {
- // 队列为空
- log.info("开始减仓,但是超出了网格设置...");
- return OrderParamEnums.HOLDING.getValue();
- }
- 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");
- //未实现收益
- 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));
- return OrderParamEnums.BUY.getValue();
- }else{
- log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(realizedPnlValueZheng));
- return OrderParamEnums.HOLDING.getValue();
- }
- }else {
- if (uplValue.compareTo(imrValue.add(feeValue)) >= 0) {
- WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
- log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(feeValue));
- return OrderParamEnums.BUY.getValue();
- }else{
- log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(feeValue));
- return OrderParamEnums.HOLDING.getValue();
- }
- }
- } else {
- log.info("未触发减仓......,等待");
- return OrderParamEnums.HOLDING.getValue();
- }
- } else if (avgPx.compareTo(markPx) < 0) {
- log.info("开始加仓...");
- if (queuePingCang.isEmpty()) {
- // 队列为空
- log.info("开始加仓,但是超出了网格设置...");
- return OrderParamEnums.HOLDING.getValue();
- }
- 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));
- boolean buyCntTimeFlag = buyCntTimeShortEvent(accountName, avgPx, markPx);
- if (buyCntTimeFlag){
- log.info("加仓参数准备成功......");
- return OrderParamEnums.SELL.getValue();
- }else{
- log.error("加仓参数准备失败......");
- return null;
- }
- } else {
- log.info("未触发加仓......,等待");
- }
- } else {
- log.info("价格波动较小......,等待");
- }
- return null;
- } catch (NumberFormatException e) {
- log.error("解析价格失败,请检查Redis中的值是否合法", e);
- return null;
- }
- }
-
- private boolean buyCntTimeLongEvent(String accountName, BigDecimal avgPx, BigDecimal markPx){
+ 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);
@@ -395,26 +454,21 @@
log.info("倍数价格差距{}", subtract);
BigDecimal divide = subtract.divide(new BigDecimal(buyCntTime), 0, RoundingMode.DOWN).add(BigDecimal.ONE);
log.info("倍数次数{}", divide);
- if (divide.compareTo(BigDecimal.ZERO) <= 0){
- log.warn("加仓次数间隔时间小于0,不加仓");
- return false;
- }
- return WsMapBuild.saveStringToMap(TradeOrderWs.getAccountMap(accountName), "buyCntTime",String.valueOf(divide));
+ String buyCntInit = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
+ return String.valueOf(divide.multiply(new BigDecimal(buyCntInit)));
}
- private boolean buyCntTimeShortEvent(String accountName, BigDecimal avgPx, BigDecimal markPx){
+ 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);
- if (divide.compareTo(BigDecimal.ZERO) <= 0){
- log.warn("加仓次数间隔时间小于0,不加仓");
- return false;
- }
- return WsMapBuild.saveStringToMap(TradeOrderWs.getAccountMap(accountName), "buyCntTime",String.valueOf(divide));
+ String buyCntInit = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
+ return String.valueOf(divide.multiply(new BigDecimal(buyCntInit)));
}
/**
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
index 1e60135..3d5fe77 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java
@@ -115,6 +115,7 @@
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);
@@ -133,6 +134,8 @@
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());
@@ -140,11 +143,17 @@
BigDecimal totalOrderUsdt = cashBalDecimal.multiply(total_order_usdt_factor).setScale(2, RoundingMode.DOWN);
WsMapBuild.saveStringToMap(accountMap, CoinEnums.TOTAL_ORDER_USDT.name(), String.valueOf(totalOrderUsdt));
- WsMapBuild.saveStringToMap(accountMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode());
+ /**
+ * 当前账户未满仓,并且账户余额不为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
+ "{}: 账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {},已使用保证金:{}",
+ accountName, ccy, availBal, cashBal, eq, upl, totalOrderUsdt,imr
);
}
}
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
index 6c68e71..92d76f8 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java
@@ -67,7 +67,7 @@
private static final String STATE_KEY = "state";
public static void handleEvent(JSONObject response, RedisUtils redisUtils, String accountName) {
-// log.info("开始执行OrderInfoWs......");
+ log.info("开始执行OrderInfoWs......");
try {
JSONArray dataArray = response.getJSONArray(DATA_KEY);
if (dataArray == null || dataArray.isEmpty()) {
@@ -113,14 +113,6 @@
WsMapBuild.saveStringToMap(accountMap, "orderPrice",avgPx);
}
WsMapBuild.saveStringToMap(TradeOrderWs.getAccountMap(accountName), "state", CoinEnums.ORDER_LIVE.getCode());
-
- //保存上一个网格信息
- WangGeListEnum gridByPrice = WangGeListEnum.getGridByPrice(new BigDecimal(avgPx));
- if (gridByPrice != null){
- log.info("保存上一个网格: {}", gridByPrice.name());
- Map<String, String> instrumentsMap = InstrumentsWs.getAccountMap(accountName);
- WsMapBuild.saveStringToMap(instrumentsMap, CoinEnums.WANG_GE_OLD.name(), gridByPrice.name());
- }
// 使用账号特定的Map
String positionAccountName = PositionsWs.initAccountName(accountName, side);
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
index a27dad2..73953c5 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java
@@ -108,10 +108,6 @@
markPx,fee,fundingFee
);
initParam(posData, accountName,posSide);
-
- String accountNamePositons = initAccountName(accountName, posSide);
- Map<String, BigDecimal> accountMap = getAccountMap(accountNamePositons);
- WsMapBuild.saveBigDecimalToMap(accountMap, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode()));
}
}
} catch (Exception e) {
@@ -132,5 +128,7 @@
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")));
+
+ 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
index be34982..6a7a700 100644
--- a/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
@@ -5,6 +5,7 @@
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;
@@ -32,113 +33,103 @@
public static final String ORDERWS_CHANNEL = "order";
- public static void orderEvent(WebSocketClient webSocketClient, String side, String accountName) {
+ public static void orderEvent(WebSocketClient webSocketClient, TradeRequestParam tradeRequestParam) {
-// log.info("开始执行TradeOrderWs......");
- String accountReadyState = AccountWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name());
- if (!CoinEnums.READY_STATE_YES.getCode().equals(accountReadyState)) {
- log.info("账户通道未就绪,取消发送");
+
+ log.info("开始执行TradeOrderWs......");
+ 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, markPx, instId, posSide,side, sz, tradeType, clOrdId);
+ //验证是否允许下单
+ if (StrUtil.isNotEmpty(tradeType) && OrderParamEnums.TRADE_NO.getValue().equals(tradeType)) {
+ log.warn("账户{}不允许下单,取消发送", accountName);
return;
}
- String posSide = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.POSSIDE.name());
+ /**
+ * 校验必要参数
+ * 验证下单参数是否存在空值
+ */
+ 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;
+ }
+
+ /**
+ * 检验账户和仓位是否准备就绪
+ * 开多:买入开多(side 填写 buy; posSide 填写 long )
+ * 开空:卖出开空(side 填写 sell; posSide 填写 short ) 需要检验账户通道是否准备就绪
+ * 平多:卖出平多(side 填写 sell;posSide 填写 long )
+ * 平空:买入平空(side 填写 buy; posSide 填写 short ) 需要检验仓位通道是否准备就绪
+ */
+
String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
- 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;
- }
- // 校验必要参数
- if (StrUtil.isBlank(side)) {
- log.warn("下单参数 side 为空,取消发送");
- return;
- }
- // 校验必要参数
- if (StrUtil.isBlank(posSide)) {
- log.warn("下单参数 posSide 为空,取消发送");
- return;
- }
-
- String buyCnt = "";
- if (CoinEnums.POSSIDE_LONG.getCode().equals(posSide)){
- if (OrderParamEnums.HOLDING.getValue().equals(side)){
- log.info("当前状态为持仓中,取消发送");
- return;
- }else if (OrderParamEnums.OUT.getValue().equals(side)){
- log.info("当前状态为止损");
- side = OrderParamEnums.SELL.getValue();
- buyCnt = String.valueOf(PositionsWs.getAccountMap(positionAccountName).get("pos"));
- }else if (OrderParamEnums.INIT.getValue().equals(side)){
- log.info("当前状态为初始化");
- side = OrderParamEnums.BUY.getValue();
- buyCnt = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
- }else if (OrderParamEnums.BUY.getValue().equals(side)){
- log.info("当前状态为加仓");
- String buyCntTime = getAccountMap(accountName).get("buyCntTime");
- String buyCntStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT.name());
- buyCnt = String.valueOf(new BigDecimal(buyCntTime).multiply(new BigDecimal(buyCntStr)));
- }else if (OrderParamEnums.SELL.getValue().equals(side)){
- log.info("当前状态为减仓");
- buyCnt = String.valueOf(PositionsWs.getAccountMap(positionAccountName).get("pos"));
- }else{
- log.warn("交易状态异常,取消发送");
+ if (
+ (posSide.equals(CoinEnums.POSSIDE_LONG.getCode()) && side.equals(CoinEnums.SIDE_BUY.getCode()))
+ || (posSide.equals(CoinEnums.POSSIDE_SHORT.getCode()) && side.equals(CoinEnums.SIDE_SELL.getCode()))
+ ){
+ String accountReadyState = AccountWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name());
+ if (!CoinEnums.READY_STATE_YES.getCode().equals(accountReadyState)) {
+ log.info("账户通道未就绪,取消发送");
return;
}
- }else if (CoinEnums.POSSIDE_SHORT.getCode().equals(posSide)){
- if (OrderParamEnums.HOLDING.getValue().equals(side)){
- log.info("当前状态为持仓中,取消发送");
- return;
- }else if (OrderParamEnums.OUT.getValue().equals(side)){
- log.info("当前状态为止损");
- side = OrderParamEnums.BUY.getValue();
- buyCnt = String.valueOf(PositionsWs.getAccountMap(positionAccountName).get("pos"));
- }else if (OrderParamEnums.INIT.getValue().equals(side)){
- log.info("当前状态为初始化");
- side = OrderParamEnums.SELL.getValue();
- buyCnt = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
- }else if (OrderParamEnums.BUY.getValue().equals(side)){
- log.info("当前状态为减仓");
- buyCnt = String.valueOf(PositionsWs.getAccountMap(positionAccountName).get("pos"));
- }else if (OrderParamEnums.SELL.getValue().equals(side)){
- log.info("当前状态为加仓");
- String buyCntTime = getAccountMap(accountName).get("buyCntTime");
- String buyCntStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT.name());
- buyCnt = String.valueOf(new BigDecimal(buyCntTime).multiply(new BigDecimal(buyCntStr)));
- }else{
- log.warn("交易状态异常,取消发送");
+ }else if (
+ (posSide.equals(CoinEnums.POSSIDE_LONG.getCode()) && side.equals(CoinEnums.SIDE_SELL.getCode()))
+ || (posSide.equals(CoinEnums.POSSIDE_SHORT.getCode()) && side.equals(CoinEnums.SIDE_BUY.getCode()))
+ ) {
+ 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;
}
- }
-
-
- if (StrUtil.isBlank(buyCnt)) {
- log.warn("下单数量 buyCnt 为空,取消发送");
+ }else{
+ log.info("下单构建失败{},{},取消发送",posSide, side);
return;
}
try {
- String clOrdId = WsParamBuild.getOrderNum(side);
JSONArray argsArray = new JSONArray();
JSONObject args = new JSONObject();
- args.put("instId", CoinEnums.HE_YUE.getCode());
- args.put("tdMode", CoinEnums.CROSS.getCode());
+ args.put("instId", instId);
+ args.put("tdMode", tdMode);
args.put("clOrdId", clOrdId);
args.put("side", side);
args.put("posSide", posSide);
- args.put("ordType", CoinEnums.ORDTYPE_MARKET.getCode());
- args.put("sz", buyCnt);
+ 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, buyCnt);
+ log.info("发送下单频道:{},数量:{}", side, sz);
- WsMapBuild.saveStringToMap(getAccountMap(accountName), "buyCntTime",String.valueOf(BigDecimal.ONE));
WsMapBuild.saveStringToMap(getAccountMap(accountName), "clOrdId", clOrdId);
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());
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
index 3e15849..061c17b 100644
--- 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
@@ -44,10 +44,10 @@
PING_CANG_SHOUYI("平仓收益比例", "0.1"),
//下单的总保障金为账户总金额cashBal * TOTAL_ORDER_USDT用来做保证金
- TOTAL_ORDER_USDTPECENT("总保证金比例total_order_usdtpecent","0.05"),
+ TOTAL_ORDER_USDTPECENT("总保证金比例total_order_usdtpecent","0.1"),
TOTAL_ORDER_USDT("总保证金totalOrderUsdt","0"),
- KANG_CANG("抗压比例KANG_CANG","0.8"),
- ZHI_SUN("止损比例ZHI_SUN","0.6"),
+ KANG_CANG("抗压比例KANG_CANG","0.9"),
+ ZHI_SUN("止损比例ZHI_SUN","0.8"),
//每次下单的张数
BUY_CNT("每次开仓的张数buyCnt","0.1"),
BUY_CNT_INIT("每次初始化开仓张数的基础值buyCntInit","0.2"),
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
index aa1256d..981203e 100644
--- 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
@@ -9,6 +9,10 @@
@Getter
public enum OrderParamEnums {
+
+ TRADE_YES("允许下单", "TRADE_YES"),
+ TRADE_NO("拒绝下单", "TRADE_NO"),
+
OUT_NO("操作中", "操作中"),
OUT_YES("冷静中", "冷静中"),
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
new file mode 100644
index 0000000..14260d7
--- /dev/null
+++ b/src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/TradeRequestParam.java
@@ -0,0 +1,32 @@
+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;
+
+}
--
Gitblit v1.9.1