Administrator
2025-12-18 86a73a6274f9c5938c39c03198f11df9b1735320
feat(okx): 实现新的交易策略与账户保证金逻辑

- 新增 imrKey 字段用于跟踪初始保证金要求
- 更新账户就绪状态逻辑,基于保证金与余额比较
- 引入 TradeRequestParam 类统一交易请求参数传递
- 重构 CaoZuoService 接口,拆分不同交易场景处理方法
- 修改网格交易策略,调整保证金计算方式及下单逻辑
- 增加对冲策略支持,区分做多与做空方向的操作流程
- 完善止损和平仓机制,细化交易信号判断条件
- 提高代码可读性与维护性,增强异常处理能力
- 更新总保证金比例配置从 0.05 至 0.1
- 添加 OKX 量化交易模块技术文档说明整体架构设计
10 files modified
3 files added
1463 ■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX_QUANT_DOCUMENTATION.md 254 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxNewPriceWebSocketClient.java 57 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md 285 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java 28 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java 608 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java 153 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/TradeRequestParam.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OKX_QUANT_DOCUMENTATION.md
New file
@@ -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
**维护人**:开发团队
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) {
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) {
src/main/java/com/xcong/excoin/modules/okxNewPrice/README.md
New file
@@ -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. **高可靠性**:支持心跳检测、自动重连和异常处理
该系统实现了从价格获取、策略决策到订单执行的完整流程,为量化交易提供了稳定可靠的基础架构。
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);
}
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)));
    }
    /**
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
        );
    }
}
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);
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()));
    }
}
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());
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"),
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("冷静中", "冷静中"),
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/param/TradeRequestParam.java
New file
@@ -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;
}