Administrator
2025-12-15 2e6cbd3ee1c2d7e1aa73f4392c79bfbfacbcb67d
feat(okx): 支持多账号WebSocket连接管理

- 重构AccountWs、InstrumentsWs、OrderInfoWs和PositionsWs类,使用双层Map结构支持多账号数据存储
- 修改CaoZuoService接口和实现,增加accountName参数以区分不同账号的操作
- 更新OkxQuantWebSocketClient类,支持注入账号信息并传递给各个处理方法
- 新增OkxWebSocketClientManager类,用于管理多个账号的WebSocket客户端实例
- 调整ApiMessageServiceImpl类,根据账号名称动态获取对应的API密钥信息
- 修改ExchangeInfoEnum枚举,支持多个模拟盘账号配置
- 更新LoginWs类,支持根据不同账号信息进行WebSocket登录
- 在OkxWebSocketClientMain中引入Spring容器管理,通过OkxWebSocketClientManager统一管理客户端实例
12 files modified
1 files added
486 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java 70 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientMain.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java 101 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java 60 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/InstrumentsWs.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/LoginWs.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/ExchangeInfoEnum.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/ApiMessageServiceImpl.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java
@@ -30,15 +30,11 @@
 * @author Administrator
 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "app", name = "quant", havingValue = "true")
public class OkxQuantWebSocketClient {
    @Autowired
    private WangGeService wangGeService;
    @Autowired
    private CaoZuoService caoZuoService;
    @Autowired
    private RedisUtils redisUtils;
    private final WangGeService wangGeService;
    private final CaoZuoService caoZuoService;
    private final RedisUtils redisUtils;
    private final ExchangeInfoEnum account;
    private WebSocketClient webSocketClient;
    private ScheduledExecutorService heartbeatExecutor;
@@ -48,6 +44,14 @@
    // 连接状态标志
    private final AtomicBoolean isConnected = new AtomicBoolean(false);
    private final AtomicBoolean isConnecting = new AtomicBoolean(false);
    public OkxQuantWebSocketClient(ExchangeInfoEnum account, WangGeService wangGeService,
                                   CaoZuoService caoZuoService, RedisUtils redisUtils) {
        this.account = account;
        this.wangGeService = wangGeService;
        this.caoZuoService = caoZuoService;
        this.redisUtils = redisUtils;
    }
    private static final String WS_URL_MONIPAN = "wss://wspap.okx.com:8443/ws/v5/private";
    private static final String WS_URL_SHIPAN = "wss://ws.okx.com:8443/ws/v5/private";
@@ -169,12 +173,12 @@
        }
        
        try {
            InstrumentsWs.handleEvent();
            InstrumentsWs.handleEvent(account.name());
            wangGeService.initWangGe();
            SSLConfig.configureSSL();
            System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
            String WS_URL = WS_URL_MONIPAN;
            if (ExchangeInfoEnum.OKX_UAT.isAccountType()){
            if (account.isAccountType()){
                WS_URL = WS_URL_SHIPAN;
            }
            URI uri = new URI(WS_URL);
@@ -199,7 +203,7 @@
                    // 棜查应用是否正在关闭
                    if (!sharedExecutor.isShutdown()) {
                        resetHeartbeatTimer();
                        websocketLogin();
                        websocketLogin(account);
                    } else {
                        log.warn("应用正在关闭,忽略WebSocket连接成功回调");
                    }
@@ -249,8 +253,8 @@
        }
    }
    private void websocketLogin() {
        LoginWs.websocketLogin(webSocketClient);
    private void websocketLogin(ExchangeInfoEnum account) {
        LoginWs.websocketLogin(webSocketClient, account);
    }
    private void subscribeBalanceAndPositionChannel(String option) {
@@ -278,7 +282,7 @@
    private void handleWebSocketMessage(String message) {
        try {
            if ("pong".equals(message)) {
                log.debug("收到心跳响应");
                log.debug("{}: 收到心跳响应", account.name());
                cancelPongTimeout();
                return;
            }
@@ -289,26 +293,26 @@
                String code = response.getString("code");
                if ("0".equals(code)) {
                    String connId = response.getString("connId");
                    log.info("WebSocket登录成功, connId: {}", connId);
                    log.info("{}: WebSocket登录成功, connId: {}", account.name(), connId);
                    subscribeAccountChannel(SUBSCRIBE);
                    subscribeOrderInfoChannel(SUBSCRIBE);
                    subscribePositionChannel(SUBSCRIBE);
                } else {
                    log.error("WebSocket登录失败, code: {}, msg: {}", code, response.getString("msg"));
                    log.error("{}: WebSocket登录失败, code: {}, msg: {}", account.name(), code, response.getString("msg"));
                }
            } else if ("subscribe".equals(event)) {
                subscribeEvent(response);
            } else if ("error".equals(event)) {
                log.error("订阅错误: code={}, msg={}",
                         response.getString("code"), response.getString("msg"));
                log.error("{}: 订阅错误: code={}, msg={}",
                         account.name(), response.getString("code"), response.getString("msg"));
            } else if ("channel-conn-count".equals(event)) {
                log.info("连接限制更新: channel={}, connCount={}",
                         response.getString("channel"), response.getString("connCount"));
                log.info("{}: 连接限制更新: channel={}, connCount={}",
                         account.name(), response.getString("channel"), response.getString("connCount"));
            } else {
                processPushData(response);
            }
        } catch (Exception e) {
            log.error("处理WebSocket消息失败: {}", message, e);
            log.error("{}: 处理WebSocket消息失败: {}", account.name(), message, e);
        }
    }
@@ -325,13 +329,13 @@
            return;
        }
        if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) {
            OrderInfoWs.initEvent(response);
            OrderInfoWs.initEvent(response, account.name());
        }
        if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) {
            AccountWs.initEvent(response);
            AccountWs.initEvent(response, account.name());
        }
        if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) {
            PositionsWs.initEvent(response);
            PositionsWs.initEvent(response, account.name());
        }
    }
@@ -347,30 +351,32 @@
            if (TradeOrderWs.ORDERWS_CHANNEL.equals(op)) {
                // 直接使用Object类型接收,避免强制类型转换
                Object data = response.get("data");
                log.info("收到下单推送结果: {}", JSON.toJSONString(data));
                log.info("{}: 收到下单推送结果: {}", account.name(), JSON.toJSONString(data));
                return;
            }
        }
        JSONObject arg = response.getJSONObject("arg");
        if (arg == null) {
            log.warn("无效的推送数据,缺少 'arg' 字段 :{}",response);
            log.warn("{}: 无效的推送数据,缺少 'arg' 字段 :{}", account.name(), response);
            return;
        }
        String channel = arg.getString("channel");
        if (channel == null) {
            log.warn("无效的推送数据,缺少 'channel' 字段{}",response);
            log.warn("{}: 无效的推送数据,缺少 'channel' 字段{}", account.name(), response);
            return;
        }
        // 注意:当前实现中,OrderInfoWs等类使用静态Map存储数据
        // 这会导致多账号之间的数据冲突。需要进一步修改这些类的设计,让数据存储与特定账号关联
        if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) {
            OrderInfoWs.handleEvent(response, redisUtils);
            OrderInfoWs.handleEvent(response, redisUtils, account.name());
        }else if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) {
            AccountWs.handleEvent(response);
            String side = caoZuoService.caoZuo();
            TradeOrderWs.orderEvent(webSocketClient, side);
            AccountWs.handleEvent(response, account.name());
            String side = caoZuoService.caoZuo(account.name());
            TradeOrderWs.orderEvent(webSocketClient, side, account.name());
        } else if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) {
            PositionsWs.handleEvent(response);
            PositionsWs.handleEvent(response, account.name());
        } else if (BalanceAndPositionWs.CHANNEL_NAME.equals(channel)) {
            BalanceAndPositionWs.handleEvent(response);
        }
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientMain.java
@@ -1,16 +1,21 @@
package com.xcong.excoin.modules.okxNewPrice;
import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoServiceImpl;
import com.xcong.excoin.modules.okxNewPrice.wangge.WangGeServiceImpl;
import com.xcong.excoin.utils.RedisUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class OkxWebSocketClientMain {
    public static void main(String[] args) throws InterruptedException {
        OkxQuantWebSocketClient clientV2 = new OkxQuantWebSocketClient();
        // 手动调用初始化方法
        clientV2.init();
        // 使用Spring上下文初始化管理器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        OkxWebSocketClientManager manager = context.getBean(OkxWebSocketClientManager.class);
        // 运行一段时间以观察结果
        Thread.sleep(1200000000L); // 运行一小时
        
        // 关闭连接
        clientV2.destroy();
        manager.destroy();
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxWebSocketClientManager.java
New file
@@ -0,0 +1,101 @@
package com.xcong.excoin.modules.okxNewPrice;
import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.ExchangeInfoEnum;
import com.xcong.excoin.modules.okxNewPrice.wangge.WangGeService;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 管理多个OKX WebSocket客户端实例,每个实例对应一个账号
 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "app", name = "quant", havingValue = "true")
public class OkxWebSocketClientManager {
    @Autowired
    private WangGeService wangGeService;
    @Autowired
    private CaoZuoService caoZuoService;
    @Autowired
    private RedisUtils redisUtils;
    // 存储所有WebSocket客户端实例,key为账号类型名称
    private final Map<String, OkxQuantWebSocketClient> clientMap = new ConcurrentHashMap<>();
    /**
     * 初始化方法,在Spring Bean构造完成后执行
     * 创建并初始化所有账号的WebSocket客户端实例
     */
    @PostConstruct
    public void init() {
        log.info("开始初始化OkxWebSocketClientManager");
        // 获取所有ExchangeInfoEnum枚举值
        ExchangeInfoEnum[] accounts = ExchangeInfoEnum.values();
        // 为每个账号创建一个WebSocket客户端实例
        for (ExchangeInfoEnum account : accounts) {
            try {
                OkxQuantWebSocketClient client = new OkxQuantWebSocketClient(account, wangGeService, caoZuoService, redisUtils);
                clientMap.put(account.name(), client);
                client.init();
                log.info("已初始化账号 {} 的WebSocket客户端", account.name());
            } catch (Exception e) {
                log.error("初始化账号 {} 的WebSocket客户端失败", account.name(), e);
            }
        }
        log.info("OkxWebSocketClientManager初始化完成");
    }
    /**
     * 销毁方法,在Spring Bean销毁前执行
     * 关闭所有WebSocket客户端连接和相关资源
     */
    @PreDestroy
    public void destroy() {
        log.info("开始销毁OkxWebSocketClientManager");
        // 关闭所有客户端实例
        for (Map.Entry<String, OkxQuantWebSocketClient> entry : clientMap.entrySet()) {
            try {
                OkxQuantWebSocketClient client = entry.getValue();
                client.destroy();
                log.info("已销毁账号 {} 的WebSocket客户端", entry.getKey());
            } catch (Exception e) {
                log.error("销毁账号 {} 的WebSocket客户端失败", entry.getKey(), e);
            }
        }
        // 清空客户端映射
        clientMap.clear();
        log.info("OkxWebSocketClientManager销毁完成");
    }
    /**
     * 获取指定账号的WebSocket客户端实例
     * @param accountName 账号类型名称
     * @return WebSocket客户端实例
     */
    public OkxQuantWebSocketClient getClient(String accountName) {
        return clientMap.get(accountName);
    }
    /**
     * 获取所有WebSocket客户端实例
     * @return 所有客户端实例的集合
     */
    public Collection<OkxQuantWebSocketClient> getAllClients() {
        return clientMap.values();
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoService.java
@@ -5,5 +5,5 @@
 */
public interface CaoZuoService {
    String caoZuo();
    String caoZuo(String accountName);
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
@@ -39,26 +39,26 @@
     * @return 返回操作类型字符串(如买入BUY、卖出SELL等),如果无有效操作则返回null
     */
    @Override
    public String caoZuo() {
    public String caoZuo(String accountName) {
        log.info("开始执行操作CaoZuoServiceImpl......");
        String accountReadyState = AccountWs.ACCOUNTWSMAP.get(CoinEnums.READY_STATE.name());
        String accountReadyState = AccountWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name());
        if (!CoinEnums.READY_STATE_YES.getCode().equals(accountReadyState)) {
            log.info("账户通道未就绪,取消发送");
            return null;
        }
        BigDecimal positionsReadyState = PositionsWs.POSITIONSWSMAP.get(CoinEnums.READY_STATE.name()) == null
                ? BigDecimal.ZERO : PositionsWs.POSITIONSWSMAP.get(CoinEnums.READY_STATE.name());
        BigDecimal positionsReadyState = PositionsWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name()) == null
                ? BigDecimal.ZERO : PositionsWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name());
        if (WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode()).compareTo(positionsReadyState) != 0) {
            log.info("仓位通道未就绪,取消发送");
            return null;
        }
        // 系统设置的开关,等于冷静中,则代表不开仓
        String outStr = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.OUT.name());
        String outStr = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.OUT.name());
        if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){
            log.error("冷静中,不允许下单......");
            return null;
        }
        BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.ACCOUNTWSMAP.get("cashBal"));
        BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("cashBal"));
        // 判断账户余额是否充足
        if (cashBal.compareTo(BigDecimal.ZERO) <= 0){
@@ -69,12 +69,12 @@
         * 判断止损抗压
         */
        // 实际亏损金额
        BigDecimal realKuiSunAmount = WsMapBuild.parseBigDecimalSafe(AccountWs.ACCOUNTWSMAP.get("upl"));
        BigDecimal realKuiSunAmount = WsMapBuild.parseBigDecimalSafe(AccountWs.getAccountMap(accountName).get("upl"));
        log.info("未实现盈亏: {}", realKuiSunAmount);
        String zhiSunPercent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.ZHI_SUN.name());
        String zhiSunPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.ZHI_SUN.name());
        BigDecimal zhiSunAmount = cashBal.multiply(new BigDecimal(zhiSunPercent));
        log.info("预期亏损金额: {}", zhiSunAmount);
        String kangYaPercent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.KANG_CANG.name());
        String kangYaPercent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.KANG_CANG.name());
        BigDecimal  kangYaAmount = cashBal.multiply(new BigDecimal(kangYaPercent));
        log.info("预期抗仓金额: {}", kangYaAmount);
@@ -83,7 +83,7 @@
            // 账户预期亏损金额比这个还小时,立即止损
            if (realKuiSunAmount.compareTo(zhiSunAmount) > 0){
                log.error("账户冷静止损......");
                WsMapBuild.saveStringToMap(InstrumentsWs.INSTRUMENTSWSMAP, CoinEnums.OUT.name(),  OrderParamEnums.OUT_YES.getValue());
                WsMapBuild.saveStringToMap(InstrumentsWs.getAccountMap(accountName), CoinEnums.OUT.name(),  OrderParamEnums.OUT_YES.getValue());
                return OrderParamEnums.OUT.getValue();
            }
            // 判断抗压
@@ -93,22 +93,22 @@
            }
        }
        if (PositionsWs.POSITIONSWSMAP.get("pos") == null){
        if (PositionsWs.getAccountMap(accountName).get("pos") == null){
            log.error("没有获取到持仓信息,等待初始化......");
            return null;
        }
        BigDecimal pos = PositionsWs.POSITIONSWSMAP.get("pos");
        BigDecimal pos = PositionsWs.getAccountMap(accountName).get("pos");
        if (BigDecimal.ZERO.compareTo( pos) >= 0) {
            log.error("持仓数量为零,进行初始化订单");
            return OrderParamEnums.INIT.getValue();
        }
        // 判断是否保证金超标
        if (PositionsWs.POSITIONSWSMAP.get("imr") == null){
        if (PositionsWs.getAccountMap(accountName).get("imr") == null){
            log.error("没有获取到持仓信息,等待初始化......");
            return null;
        }
        BigDecimal ordFrozImr = PositionsWs.POSITIONSWSMAP.get("imr");
        BigDecimal totalOrderUsdt = WsMapBuild.parseBigDecimalSafe(AccountWs.ACCOUNTWSMAP.get(CoinEnums.TOTAL_ORDER_USDT.name()));
        BigDecimal ordFrozImr = PositionsWs.getAccountMap(accountName).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();
@@ -116,8 +116,8 @@
        try {
            // 获取标记价格和平均持仓价格
            BigDecimal markPx = PositionsWs.POSITIONSWSMAP.get("markPx");
            BigDecimal avgPx = PositionsWs.POSITIONSWSMAP.get("avgPx");
            BigDecimal markPx = PositionsWs.getAccountMap(accountName).get("markPx");
            BigDecimal avgPx = PositionsWs.getAccountMap(accountName).get("avgPx");
            log.info("开仓价格: {}, 当前价格:{},匹配队列中......", avgPx, markPx);
            // 初始化网格队列
@@ -126,7 +126,7 @@
            PriorityBlockingQueue<AscBigDecimal> queuePingCang = wangGeService.initPingCang(avgPx, queueAsc);
            // 处理订单价格在队列中的情况
            String orderPrice = OrderInfoWs.ORDERINFOWSMAP.get("orderPrice");
            String orderPrice = OrderInfoWs.getAccountMap(accountName).get("orderPrice");
            handleOrderPriceInQueues(orderPrice, queueKaiCang, queuePingCang);
            // 判断是加仓还是减仓
            if (avgPx.compareTo(markPx) > 0) {
@@ -139,8 +139,8 @@
                DescBigDecimal kaiCang = queueKaiCang.peek();
                if (kaiCang != null && markPx.compareTo(kaiCang.getValue()) <= 0 && avgPx.compareTo(kaiCang.getValue()) >= 0) {
                    log.info("开始加仓...开仓队列价格大于当前价格{}>{}", kaiCang.getValue(), markPx);
                    WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice", String.valueOf(markPx));
                    boolean buyCntTimeFlag = buyCntTimeEvent(avgPx, markPx);
                    WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
                    boolean buyCntTimeFlag = buyCntTimeEvent(accountName, avgPx, markPx);
                    if (buyCntTimeFlag){
                        log.info("加仓参数准备成功......");
                        return OrderParamEnums.BUY.getValue();
@@ -163,23 +163,23 @@
                if (pingCang != null && markPx.compareTo(pingCang.getValue()) >= 0 && avgPx.compareTo(pingCang.getValue()) < 0) {
                    log.info("开始减仓...平仓队列价格小于当前价格{}<={}", pingCang.getValue(), markPx);
                    // 手续费
                    BigDecimal feeValue = PositionsWs.POSITIONSWSMAP.get("fee").multiply(new BigDecimal("2"));
                    BigDecimal feeValue = PositionsWs.getAccountMap(accountName).get("fee").multiply(new BigDecimal("2"));
                    //未实现收益
                    BigDecimal uplValue = PositionsWs.POSITIONSWSMAP.get("upl");
                    BigDecimal uplValue = PositionsWs.getAccountMap(accountName).get("upl");
                    //已实现收益
                    BigDecimal realizedPnlValue = PositionsWs.POSITIONSWSMAP.get("realizedPnl");
                    BigDecimal realizedPnlValue = PositionsWs.getAccountMap(accountName).get("realizedPnl");
                    realizedPnlValue = realizedPnlValue.add(feeValue);
                    //持仓保证金
                    BigDecimal imr = PositionsWs.POSITIONSWSMAP.get("imr");
                    String pingCangImr = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.PING_CANG_SHOUYI.name());
                    BigDecimal imr = PositionsWs.getAccountMap(accountName).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.ORDERINFOWSMAP, "orderPrice", String.valueOf(markPx));
                            WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
                            return OrderParamEnums.SELL.getValue();
                        }else{
                            log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue.add(realizedPnlValueZheng));
@@ -187,7 +187,7 @@
                        }
                    }else {
                        if (uplValue.compareTo(imrValue.add(feeValue))  >= 0) {
                            WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice", String.valueOf(markPx));
                            WsMapBuild.saveStringToMap(OrderInfoWs.getAccountMap(accountName), "orderPrice", String.valueOf(markPx));
                            log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue.add(feeValue));
                            return OrderParamEnums.SELL.getValue();
                        }else{
@@ -208,16 +208,16 @@
        }
    }
    private boolean buyCntTimeEvent(BigDecimal avgPx, BigDecimal markPx){
    private boolean buyCntTimeEvent(String accountName, BigDecimal avgPx, BigDecimal markPx){
        //判断当前价格和开仓价格直接间隔除以间距,取整,获取的数量是否大于等于0,如果大于0,则下单基础张数*倍数
        String buyCntTime = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.BUY_CNT_TIME.name());
        String buyCntTime = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_TIME.name());
        BigDecimal subtract = avgPx.subtract(markPx);
        BigDecimal divide = subtract.divide(new BigDecimal(buyCntTime), 0, RoundingMode.DOWN);
        if (divide.compareTo(BigDecimal.ZERO) <= 0){
            log.warn("加仓次数间隔时间小于0,不加仓");
            return false;
        }
        return WsMapBuild.saveStringToMap(TradeOrderWs.TRADEORDERWSMAP, "buyCntTime",String.valueOf(divide));
        return WsMapBuild.saveStringToMap(TradeOrderWs.getAccountMap(accountName), "buyCntTime",String.valueOf(divide));
    }
    /**
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java
@@ -26,7 +26,13 @@
@Slf4j
public class AccountWs {
    public static  final Map<String,String> ACCOUNTWSMAP = new ConcurrentHashMap<>();
    // 使用双层Map,第一层key为账号名称,第二层key为数据key
    private static final Map<String, Map<String, String>> ACCOUNTWSMAP = new ConcurrentHashMap<>();
    // 获取指定账号的Map,如果不存在则创建
    public static Map<String, String> getAccountMap(String accountName) {
        return ACCOUNTWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
    }
    /**
     * 账户频道名称常量
     */
@@ -55,18 +61,19 @@
        }
    }
    public static void initEvent(JSONObject response) {
    public static void initEvent(JSONObject response, String accountName) {
//        log.info("订阅成功: {}", response.getJSONObject("arg"));
        JSONObject arg = response.getJSONObject("arg");
        initParam(arg);
        initParam(arg, accountName);
    }
    /**
     * 处理账户频道推送的数据
     *
     * @param response   推送的 JSON 数据对象
     * @param accountName 账号名称
     */
    public static void handleEvent(JSONObject response) {
    public static void handleEvent(JSONObject response, String accountName) {
//        log.info("开始执行AccountWs......{}",ACCOUNTWS_CHANNEL);
@@ -88,7 +95,7 @@
                    for (int j = 0; j < detailsArray.size(); j++) {
                        JSONObject detail = detailsArray.getJSONObject(j);
                        initParam(detail);
                        initParam(detail, accountName);
                    }
                } catch (Exception innerEx) {
                    log.warn("处理账户频道数据失败", innerEx);
@@ -105,35 +112,36 @@
    public static final String cashBalKey = "cashBal";
    public static final String eqKey = "eq";
    public static final String uplKey = "upl";
    private static void initParam(JSONObject detail) {
    private static void initParam(JSONObject detail, String accountName) {
        Map<String, String> accountMap = getAccountMap(accountName);
        String ccy = WsMapBuild.parseStringSafe( detail.getString(ccyKey));
        WsMapBuild.saveStringToMap(ACCOUNTWSMAP, ccyKey, ccy);
        WsMapBuild.saveStringToMap(accountMap, ccyKey, ccy);
        String availBal = WsMapBuild.parseStringSafe(detail.getString(availBalKey));
        WsMapBuild.saveStringToMap(ACCOUNTWSMAP, availBalKey, availBal);
        WsMapBuild.saveStringToMap(accountMap, availBalKey, availBal);
        String cashBal = WsMapBuild.parseStringSafe(detail.getString(cashBalKey));
        WsMapBuild.saveStringToMap(ACCOUNTWSMAP, cashBalKey, cashBal);
        WsMapBuild.saveStringToMap(accountMap, cashBalKey, cashBal);
        String eq = WsMapBuild.parseStringSafe(detail.getString(eqKey));
        WsMapBuild.saveStringToMap(ACCOUNTWSMAP, eqKey, eq);
        WsMapBuild.saveStringToMap(accountMap, eqKey, eq);
        String upl = WsMapBuild.parseStringSafe(detail.getString(uplKey));
        WsMapBuild.saveStringToMap(ACCOUNTWSMAP, uplKey, upl);
        WsMapBuild.saveStringToMap(accountMap, uplKey, upl);
        BigDecimal cashBalDecimal = WsMapBuild.parseBigDecimalSafe(cashBal);
        // 根据可用余额计算下单总保证金
        String total_order_usdtpecent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.TOTAL_ORDER_USDTPECENT.name());
        String total_order_usdtpecent = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.TOTAL_ORDER_USDTPECENT.name());
        BigDecimal total_order_usdt_factor = WsMapBuild.parseBigDecimalSafe(total_order_usdtpecent);
        BigDecimal totalOrderUsdt = cashBalDecimal.multiply(total_order_usdt_factor).setScale(2, RoundingMode.DOWN);
        WsMapBuild.saveStringToMap(ACCOUNTWSMAP, CoinEnums.TOTAL_ORDER_USDT.name(), String.valueOf(totalOrderUsdt));
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.TOTAL_ORDER_USDT.name(), String.valueOf(totalOrderUsdt));
        WsMapBuild.saveStringToMap(ACCOUNTWSMAP, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_YES.getCode());
        log.info(
                "账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {}",
                ccy, availBal, cashBal, eq, upl, totalOrderUsdt
                "{}: 账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 全仓未实现盈亏: {}, 下单总保证金: {}",
                accountName, ccy, availBal, cashBal, eq, upl, totalOrderUsdt
        );
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/InstrumentsWs.java
@@ -14,24 +14,29 @@
@Slf4j
public class InstrumentsWs {
    public static  final Map<String, String> INSTRUMENTSWSMAP = new ConcurrentHashMap<>();
    public static final Map<String, Map<String, String>> INSTRUMENTSWSMAP = new ConcurrentHashMap<>();
    public static final String INSTRUMENTSWS_CHANNEL = "instruments";
    public static void handleEvent() {
    public static Map<String, String> getAccountMap(String accountName) {
        return INSTRUMENTSWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
    }
    public static void handleEvent(String accountName) {
//        log.info("开始执行InstrumentsWs......");
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.HE_YUE.name(), CoinEnums.HE_YUE.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.CTVAL.name(), CoinEnums.CTVAL.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.TICKSZ.name(), CoinEnums.TICKSZ.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.MINSZ.name(), CoinEnums.MINSZ.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.OUT.name(),  OrderParamEnums.OUT_NO.getValue());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.LEVERAGE.name(), CoinEnums.LEVERAGE.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.BUY_CNT.name(), CoinEnums.BUY_CNT.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.BUY_CNT_TIME.name(), CoinEnums.BUY_CNT_TIME.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.ZHI_SUN.name(), CoinEnums.ZHI_SUN.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.KANG_CANG.name(), CoinEnums.KANG_CANG.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.PING_CANG_SHOUYI.name(), CoinEnums.PING_CANG_SHOUYI.getCode());
        WsMapBuild.saveStringToMap(INSTRUMENTSWSMAP, CoinEnums.TOTAL_ORDER_USDTPECENT.name(), CoinEnums.TOTAL_ORDER_USDTPECENT.getCode());
        Map<String, String> accountMap = getAccountMap(accountName);
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.HE_YUE.name(), CoinEnums.HE_YUE.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.CTVAL.name(), CoinEnums.CTVAL.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.TICKSZ.name(), CoinEnums.TICKSZ.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.MINSZ.name(), CoinEnums.MINSZ.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.OUT.name(),  OrderParamEnums.OUT_NO.getValue());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.LEVERAGE.name(), CoinEnums.LEVERAGE.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.BUY_CNT.name(), CoinEnums.BUY_CNT.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.BUY_CNT_TIME.name(), CoinEnums.BUY_CNT_TIME.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.ZHI_SUN.name(), CoinEnums.ZHI_SUN.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.KANG_CANG.name(), CoinEnums.KANG_CANG.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.PING_CANG_SHOUYI.name(), CoinEnums.PING_CANG_SHOUYI.getCode());
        WsMapBuild.saveStringToMap(accountMap, CoinEnums.TOTAL_ORDER_USDTPECENT.name(), CoinEnums.TOTAL_ORDER_USDTPECENT.getCode());
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/LoginWs.java
@@ -13,7 +13,7 @@
 */
@Slf4j
public class LoginWs {
    public static void websocketLogin(WebSocketClient webSocketClient) {
    public static void websocketLogin(WebSocketClient webSocketClient, ExchangeInfoEnum account) {
//        log.info("开始执行LoginWs......");
        try {
@@ -21,10 +21,10 @@
            JSONArray argsArray = new JSONArray();
            JSONObject loginArgs = new JSONObject();
            // 获取登录凭证信息(需要从配置或Redis中获取)
            String apiKey = ExchangeInfoEnum.OKX_UAT.getApiKey();
            String passphrase = ExchangeInfoEnum.OKX_UAT.getPassphrase();
            String apiKey = account.getApiKey();
            String passphrase = account.getPassphrase();
            String timestamp = String.valueOf(System.currentTimeMillis() /1000);
            String sign = SignUtils.signWebsocket(timestamp, ExchangeInfoEnum.OKX_UAT.getSecretKey());
            String sign = SignUtils.signWebsocket(timestamp, account.getSecretKey());
            loginArgs.put("apiKey", apiKey);
            loginArgs.put("passphrase", passphrase);
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java
@@ -22,7 +22,13 @@
@Slf4j
public class OrderInfoWs {
    public static  final Map<String,String> ORDERINFOWSMAP = new ConcurrentHashMap<>();
    // 使用双层Map,第一层key为账号名称,第二层key为数据key
    public static final Map<String, Map<String, String>> ORDERINFOWSMAP = new ConcurrentHashMap<>();
    // 获取指定账号的Map,如果不存在则创建
    public static Map<String, String> getAccountMap(String accountName) {
        return ORDERINFOWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
    }
    public static final String ORDERINFOWS_CHANNEL = "orders";
@@ -44,7 +50,7 @@
        }
    }
    public static void initEvent(JSONObject response) {
    public static void initEvent(JSONObject response, String accountName) {
//        log.info("订阅成功: {}", response.getJSONObject("arg"));
    }
@@ -58,7 +64,7 @@
    private static final String ACCFILLSZ_KEY = "accFillSz";
    private static final String AVGPX_KEY = "avgPx";
    private static final String STATE_KEY = "state";
    public static void handleEvent(JSONObject response, RedisUtils redisUtils) {
    public static void handleEvent(JSONObject response, RedisUtils redisUtils, String accountName) {
//        log.info("开始执行OrderInfoWs......");
        try {
@@ -85,30 +91,35 @@
                String state = detail.getString(STATE_KEY);
                log.info(
                        "订单详情-币种: {}, 系统编号: {}, 自定义编号: {}, 订单方向: {}, 交易模式: {}," +
                        "{}: 订单详情-币种: {}, 系统编号: {}, 自定义编号: {}, 订单方向: {}, 交易模式: {}," +
                                " 累计成交数量: {}, 成交均价: {}, 订单状态: {}",
                        instId, ordId, clOrdId, side, tdMode,
                        accountName, instId, ordId, clOrdId, side, tdMode,
                        accFillSz, avgPx,state
                );
                String clOrdIdStr = TradeOrderWs.TRADEORDERWSMAP.get("clOrdId");
                String stateStr = TradeOrderWs.TRADEORDERWSMAP.get("state");
                String clOrdIdStr = TradeOrderWs.getAccountMap(accountName).get("clOrdId");
                String stateStr = TradeOrderWs.getAccountMap(accountName).get("state");
                if (
                        StrUtil.isNotBlank(clOrdIdStr)
                                && clOrdId.equals(clOrdIdStr)
                                && StrUtil.isNotBlank(stateStr)
                                && state.equals(stateStr)
                ){
                    Map<String, String> accountMap = getAccountMap(accountName);
                    //记录成交均价
                    if (ORDERINFOWSMAP.get("orderPrice") == null){
                        WsMapBuild.saveStringToMap(ORDERINFOWSMAP, "orderPrice",avgPx);
                    if (accountMap.get("orderPrice") == null){
                        WsMapBuild.saveStringToMap(accountMap, "orderPrice",avgPx);
                    }
                    WsMapBuild.saveStringToMap(TradeOrderWs.TRADEORDERWSMAP, "state", CoinEnums.ORDER_LIVE.getCode());
                    WsMapBuild.saveStringToMap(TradeOrderWs.getAccountMap(accountName), "state", CoinEnums.ORDER_LIVE.getCode());
                    WsMapBuild.saveBigDecimalToMap(PositionsWs.POSITIONSWSMAP, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode()));
                    WsMapBuild.saveStringToMap(AccountWs.ACCOUNTWSMAP, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode());
                    // 使用账号特定的Map
                    Map<String, BigDecimal> positionsMap = PositionsWs.getAccountMap(accountName);
                    WsMapBuild.saveBigDecimalToMap(positionsMap, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode()));
                    Map<String, String> accountWsMap = AccountWs.getAccountMap(accountName);
                    WsMapBuild.saveStringToMap(accountWsMap, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode());
                    log.info("订单详情已完成: {}, 自定义编号: {}", CoinEnums.HE_YUE.getCode(), clOrdId);
                    log.info("{}: 订单详情已完成: {}, 自定义编号: {}", accountName, CoinEnums.HE_YUE.getCode(), clOrdId);
                }
            }
        } catch (Exception e) {
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java
@@ -19,7 +19,13 @@
@Slf4j
public class PositionsWs {
    public static  final Map<String,BigDecimal> POSITIONSWSMAP = new ConcurrentHashMap<>();
    // 使用双层Map,第一层key为账号名称,第二层key为数据key
    public static final Map<String, Map<String, BigDecimal>> POSITIONSWSMAP = new ConcurrentHashMap<>();
    // 获取指定账号的Map,如果不存在则创建
    public static Map<String, BigDecimal> getAccountMap(String accountName) {
        return POSITIONSWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
    }
    public static final String POSITIONSWS_CHANNEL = "positions";
@@ -41,13 +47,13 @@
        }
    }
    public static void initEvent(JSONObject response) {
    public static void initEvent(JSONObject response, String accountName) {
//        log.info("订阅成功,数据初始化: {}", response.getJSONObject("arg"));
        JSONObject arg = response.getJSONObject("arg");
        initParam(arg);
        initParam(arg, accountName);
    }
    public static void handleEvent(JSONObject response) {
    public static void handleEvent(JSONObject response, String accountName) {
//        log.info("开始执行PositionsWs......");
@@ -56,7 +62,7 @@
            if (dataArray == null || dataArray.isEmpty()) {
//                log.info("账户持仓频道数据为空,已当前价买入,并且初始化网格");
                JSONObject posData = new JSONObject();
                initParam(posData);
                initParam(posData, accountName);
                return;
            }
@@ -87,19 +93,19 @@
                    String fee = posData.getString("fee");
                    String fundingFee = posData.getString("fundingFee");
                    log.info(
                            "账户持仓频道-产品类型: {}, 保证金模式: {}, 持仓方向: {}, 持仓数量: {}, 开仓平均价: {}, "
                            "{}: 账户持仓频道-产品类型: {}, 保证金模式: {}, 持仓方向: {}, 持仓数量: {}, 开仓平均价: {}, "
                                    + "未实现收益: {}, 未实现收益率: {}, 杠杆倍数: {}, 预估强平价: {}, 初始保证金: {}, "
                                    + "维持保证金率: {}, 维持保证金: {}, 以美金价值为单位的持仓数量: {}, 占用保证金的币种: {}, "
                                    + "最新成交价: {}, 最新指数价格: {}, 盈亏平衡价: {}, 已实现收益: {}, 累计已结算收益: {}"
                                    + "最新标记价格: {},累计手续费: {},累计持仓费: {},",
                            instId, mgnMode, posSide, pos, avgPx,
                            accountName, instId, mgnMode, posSide, pos, avgPx,
                            upl, uplRatio, lever, liqPx, imr,
                            mgnRatio, mmr, notionalUsd, ccy,
                            last, idxPx, bePx, realizedPnl, settledPnl,
                            markPx,fee,fundingFee
                    );
                    initParam(posData);
                    initParam(posData, accountName);
                }
            }
        } catch (Exception e) {
@@ -107,18 +113,19 @@
        }
    }
    private static void initParam(JSONObject posData) {
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "avgPx", WsMapBuild.parseBigDecimalSafe(posData.getString("avgPx")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "pos", WsMapBuild.parseBigDecimalSafe(posData.getString("pos")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "upl", WsMapBuild.parseBigDecimalSafe(posData.getString("upl")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "imr", WsMapBuild.parseBigDecimalSafe(posData.getString("imr")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "mgnRatio", WsMapBuild.parseBigDecimalSafe(posData.getString("mgnRatio")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "markPx", WsMapBuild.parseBigDecimalSafe(posData.getString("markPx")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "bePx", WsMapBuild.parseBigDecimalSafe(posData.getString("bePx")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "realizedPnl", WsMapBuild.parseBigDecimalSafe(posData.getString("realizedPnl")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "fee", WsMapBuild.parseBigDecimalSafe(posData.getString("fee")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, "fundingFee", WsMapBuild.parseBigDecimalSafe(posData.getString("fundingFee")));
    private static void initParam(JSONObject posData, String accountName) {
        Map<String, BigDecimal> accountMap = getAccountMap(accountName);
        WsMapBuild.saveBigDecimalToMap(accountMap, "avgPx", WsMapBuild.parseBigDecimalSafe(posData.getString("avgPx")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "pos", WsMapBuild.parseBigDecimalSafe(posData.getString("pos")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "upl", WsMapBuild.parseBigDecimalSafe(posData.getString("upl")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "imr", WsMapBuild.parseBigDecimalSafe(posData.getString("imr")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "mgnRatio", WsMapBuild.parseBigDecimalSafe(posData.getString("mgnRatio")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "markPx", WsMapBuild.parseBigDecimalSafe(posData.getString("markPx")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "bePx", WsMapBuild.parseBigDecimalSafe(posData.getString("bePx")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "realizedPnl", WsMapBuild.parseBigDecimalSafe(posData.getString("realizedPnl")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "fee", WsMapBuild.parseBigDecimalSafe(posData.getString("fee")));
        WsMapBuild.saveBigDecimalToMap(accountMap, "fundingFee", WsMapBuild.parseBigDecimalSafe(posData.getString("fundingFee")));
        WsMapBuild.saveBigDecimalToMap(POSITIONSWSMAP, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode()));
        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
@@ -22,20 +22,26 @@
@Slf4j
public class TradeOrderWs {
    public static  final Map<String,String> TRADEORDERWSMAP = new ConcurrentHashMap<>();
    // 使用双层Map,第一层key为账号名称,第二层key为数据key
    public static  final Map<String, Map<String,String>> TRADEORDERWSMAP = new ConcurrentHashMap<>();
    // 获取指定账号的Map,如果不存在则创建
    public static Map<String, String> getAccountMap(String accountName) {
        return TRADEORDERWSMAP.computeIfAbsent(accountName, k -> new ConcurrentHashMap<>());
    }
    public static final String ORDERWS_CHANNEL = "order";
    public static void orderEvent(WebSocketClient webSocketClient, String side) {
    public static void orderEvent(WebSocketClient webSocketClient, String side, String accountName) {
//        log.info("开始执行TradeOrderWs......");
        String accountReadyState = AccountWs.ACCOUNTWSMAP.get(CoinEnums.READY_STATE.name());
        String accountReadyState = AccountWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name());
        if (!CoinEnums.READY_STATE_YES.getCode().equals(accountReadyState)) {
            log.info("账户通道未就绪,取消发送");
            return;
        }
        BigDecimal positionsReadyState = PositionsWs.POSITIONSWSMAP.get(CoinEnums.READY_STATE.name()) == null
                ? BigDecimal.ZERO : PositionsWs.POSITIONSWSMAP.get(CoinEnums.READY_STATE.name());
        BigDecimal positionsReadyState = PositionsWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name()) == null
                ? BigDecimal.ZERO : PositionsWs.getAccountMap(accountName).get(CoinEnums.READY_STATE.name());
        if (WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_YES.getCode()).compareTo(positionsReadyState) != 0) {
            log.info("仓位通道未就绪,取消发送");
            return;
@@ -52,19 +58,19 @@
        }else if (OrderParamEnums.OUT.getValue().equals(side)){
            log.info("当前状态为止损");
            side = OrderParamEnums.SELL.getValue();
            buyCnt = String.valueOf(PositionsWs.POSITIONSWSMAP.get("pos"));
            buyCnt = String.valueOf(PositionsWs.getAccountMap(accountName).get("pos"));
        }else if (OrderParamEnums.INIT.getValue().equals(side)){
            log.info("当前状态为初始化");
            side = OrderParamEnums.BUY.getValue();
            buyCnt = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.BUY_CNT.name());
            buyCnt = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT.name());
        }else if (OrderParamEnums.BUY.getValue().equals(side)){
            log.info("当前状态为加仓");
            String buyCntTime = TRADEORDERWSMAP.get("buyCntTime");
            String buyCntStr = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.BUY_CNT.name());
            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.POSITIONSWSMAP.get("pos"));
            buyCnt = String.valueOf(PositionsWs.getAccountMap(accountName).get("pos"));
        }else{
            log.warn("交易状态异常,取消发送");
            return;
@@ -93,12 +99,12 @@
            webSocketClient.send(jsonObject.toJSONString());
            log.info("发送下单频道:{},数量:{}", side, buyCnt);
            WsMapBuild.saveStringToMap(TRADEORDERWSMAP, "buyCntTime",String.valueOf(BigDecimal.ONE));
            WsMapBuild.saveStringToMap(TRADEORDERWSMAP, "clOrdId", clOrdId);
            WsMapBuild.saveStringToMap(TRADEORDERWSMAP, "state", CoinEnums.ORDER_FILLED.getCode());
            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.POSITIONSWSMAP, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode()));
            WsMapBuild.saveStringToMap(AccountWs.ACCOUNTWSMAP, CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode());
            WsMapBuild.saveBigDecimalToMap(PositionsWs.getAccountMap(accountName), CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode()));
        WsMapBuild.saveStringToMap(AccountWs.getAccountMap(accountName), CoinEnums.READY_STATE.name(), CoinEnums.READY_STATE_NO.getCode());
        } catch (Exception e) {
            log.error("下单构建失败", e);
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/ExchangeInfoEnum.java
@@ -10,28 +10,28 @@
public enum ExchangeInfoEnum {
    /**
     * 模拟盘账户信息
     * 模拟盘账户1信息
     * 存储了模拟盘交易所需的API密钥、秘钥和通过码
     */
    OKX_UAT("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61",
    OKX_UAT1("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61",
            "AA06C5ED1D7C7F5AFE6484052E231C55",
            "Aa12345678@",
            false),
    /**
     * 模拟盘账户2信息
     * 存储了模拟盘交易所需的API密钥、秘钥和通过码
     */
    OKX_UAT2("7a023eb2-06c0-4255-9969-b86ea1cef0d7",
            "D0106A4D63BD22BEAB9CBA8F41219661",
            "Aa12345678@",
            false);
    /**
     * 模拟盘账户信息
     * 模拟盘账户3信息
     * 存储了模拟盘交易所需的API密钥、秘钥和通过码
     */
//    OKX_UAT("7a023eb2-06c0-4255-9969-b86ea1cef0d7",
//            "D0106A4D63BD22BEAB9CBA8F41219661",
//            "Aa12345678@",
//            false);
//    /**
//     * 模拟盘账户信息
//     * 存储了模拟盘交易所需的API密钥、秘钥和通过码
//     */
//    OKX_UAT("0769b50c-2c36-4310-8bd9-cad6bc6c9d8f",
//    OKX_UAT3("0769b50c-2c36-4310-8bd9-cad6bc6c9d8f",
//            "7AF4A574BC44907CE76BBFF91F53852D",
//            "Aa123456@",
//            false);
src/main/java/com/xcong/excoin/modules/okxNewPrice/zhanghu/ApiMessageServiceImpl.java
@@ -19,13 +19,14 @@
        quantApiMessage.setExchange(okx);
        quantApiMessage.setMemberId(1L);
        quantApiMessage.setAccountType(ExchangeInfoEnum.OKX_UAT.isAccountType()? "true":"false");
        // 根据传入的账号名称获取对应的账号信息
        ExchangeInfoEnum account = ExchangeInfoEnum.valueOf(okx);
        quantApiMessage.setAccountType(account.isAccountType()? "true":"false");
        quantApiMessage.setState(1);
        quantApiMessage.setIsTrade(1);
        quantApiMessage.setASecretkey(ExchangeInfoEnum.OKX_UAT.getApiKey());
        quantApiMessage.setBSecretkey(ExchangeInfoEnum.OKX_UAT.getSecretKey());
        quantApiMessage.setPassPhrass(ExchangeInfoEnum.OKX_UAT.getPassphrase());
        quantApiMessage.setASecretkey(account.getApiKey());
        quantApiMessage.setBSecretkey(account.getSecretKey());
        quantApiMessage.setPassPhrass(account.getPassphrase());
        return quantApiMessage;
    }