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; }