Administrator
2025-12-12 abc803a37c3eb804a8c85164c41554eb9d1d44b8
refactor(okx): 重构账户与合约 WebSocket 数据处理逻辑

- 移除 RedisUtils 依赖,改用内存 Map 存储数据
- 新增 WsMapBuild 工具类统一处理数据解析与存储
- 调整账户、合约、订单等模块的数据结构与处理方式
- 优化止盈止损逻辑,使用 BigDecimal 进行精确计算
- 简化操作服务中的状态判断与交易决策流程
- 更新枚举类,增加更多配置项支持灵活策略调整
- 修复数据处理过程中的空指针异常风险
- 提升代码可读性与维护性,减少外部依赖耦合度
10 files modified
1 files added
855 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java 232 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java 139 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/InstrumentsWs.java 39 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java 29 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java 123 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java 101 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java 75 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsMapBuild.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsParamBuild.java 53 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java
@@ -145,7 +145,7 @@
        }
        
        try {
            InstrumentsWs.handleEvent(redisUtils);
            InstrumentsWs.handleEvent();
            wangGeService.initWangGe();
            SSLConfig.configureSSL();
            System.setProperty("https.protocols", "TLSv1.2,TLSv1.3");
@@ -310,11 +310,11 @@
        if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) {
            OrderInfoWs.handleEvent(response, redisUtils);
        }else if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) {
            AccountWs.handleEvent(response, redisUtils);
            AccountWs.handleEvent(response);
            String side = caoZuoService.caoZuo();
            TradeOrderWs.orderEvent(webSocketClient, redisUtils, side);
            TradeOrderWs.orderEvent(webSocketClient, side);
        } else if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) {
            PositionsWs.handleEvent(response, redisUtils);
            PositionsWs.handleEvent(response);
        } else if (BalanceAndPositionWs.CHANNEL_NAME.equals(channel)) {
            BalanceAndPositionWs.handleEvent(response);
        }
src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
@@ -4,6 +4,7 @@
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.utils.WsMapBuild;
import com.xcong.excoin.modules.okxNewPrice.wangge.WangGeQueue;
import com.xcong.excoin.modules.okxNewPrice.wangge.WangGeService;
import com.xcong.excoin.rabbit.pricequeue.AscBigDecimal;
@@ -27,98 +28,94 @@
@RequiredArgsConstructor
public class CaoZuoServiceImpl implements CaoZuoService {
    private final RedisUtils redisUtils;
    private final WangGeService wangGeService;
    // 构造Redis键名
    final String coinCode = CoinEnums.HE_YUE.getCode();
    final String instrumentsStateKey = InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + coinCode + ":state";
    final String instrumentsOutKey = InstrumentsWs.INSTRUMENTSWS_CHANNEL+":" + coinCode+":out";
    final String positionsMarkPxKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":markPx";
    final String positionsAvgPxKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":avgPx";
    final String positionsOrderPriceKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":orderPrice";
    final String positionsUplKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":upl";
    final String positionsRealizedPnlKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":realizedPnl";
    final String positionsImrKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":imr";
    final String positionsPosKey = PositionsWs.POSITIONSWS_CHANNEL + ":" + coinCode + ":pos";
    /**
     * 执行主要的操作逻辑,包括读取合约状态、获取市场价格信息,
     * 并根据当前持仓均价和标记价格决定是否执行买卖操作。
     *
     * @return 返回操作类型字符串(如买入BUY、卖出SELL等)
     * @return 返回操作类型字符串(如买入BUY、卖出SELL等),如果无有效操作则返回null
     */
    @Override
    public String caoZuo() {
        String state = (String) redisUtils.get(instrumentsStateKey);
        log.info("开始执行操作CaoZuoServiceImpl......{}",state);
        log.info("开始执行操作CaoZuoServiceImpl......");
        BigDecimal cashBal = AccountWs.ACCOUNTWSMAP.get("cashBal");
        BigDecimal availBal = AccountWs.ACCOUNTWSMAP.get("availBal");
        String live = (String) redisUtils.getWithDelay(TradeOrderWs.ORDERWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state");
        if (!CoinEnums.ORDER_LIVE.getCode().equals( live)){
            log.warn("正在下单中,等待下单结束...");
        }
        // 获取合约执行操作状态
        String outStr = (String) redisUtils.get(instrumentsOutKey);
        if (OrderParamEnums.OUT_YES.getValue().equals(outStr) && OrderParamEnums.STATE_3.getValue().equals(state)){
            log.error("止损过了......冷静一下,等待下次入场......");
        // 判断账户余额是否充足
        if (cashBal.compareTo(BigDecimal.ZERO) <= 0){
            log.error("账户没有钱,请充值......");
            return null;
        }
        if (OrderParamEnums.STATE_4.getValue().equals(state)) {
            log.error("操作下单中,等待......");
            return OrderParamEnums.ORDERING.getValue();
        // 系统设置的开关,等于冷静中,则代表不开仓
        String outStr = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.OUT.name());
        if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){
            log.error("冷静中,不允许下单......");
            return null;
        }
        // 判断当前是否有正在进行的订单操作
        String state = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.STATE.name());
        log.info(OrderParamEnums.getNameByValue(state));
        if (OrderParamEnums.STATE_4.getValue().equals(state)){
            log.warn("正在下单中,等待下单结束...");
            return null;
        }
        if (OrderParamEnums.STATE_3.getValue().equals(state)){
            log.error("持仓盈亏超过下单总保证金,冷静止损......");
            redisUtils.set(instrumentsOutKey, OrderParamEnums.OUT_YES.getValue(), 0);
            return OrderParamEnums.OUT.getValue();
            log.error("冷静中,不允许下单......");
            return null;
        }
        if (OrderParamEnums.STATE_2.getValue().equals(state)){
            log.error("持仓盈亏抗压......");
            return OrderParamEnums.HOLDING.getValue();
            log.error("账户紧张扛仓......");
            return null;
        }
        if (OrderParamEnums.STATE_0.getValue().equals(state)){
            log.error("请检查系统参数,不允许开仓......");
            log.error("参数异常,不允许开仓......");
            return null;
        }
        /**
         * 判断止损抗压
         */
        // 实际亏损金额
        BigDecimal realKuiSunAmount = cashBal.subtract(availBal);
        log.info("实际亏损金额: {}", realKuiSunAmount);
        String zhiSunPercent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.ZHI_SUN.name());
        BigDecimal zhiSunAmount = cashBal.multiply(new BigDecimal(zhiSunPercent));
        log.info("预期亏损金额: {}", realKuiSunAmount);
        String kangYaPercent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.KANG_CANG.name());
        log.info("预期抗仓金额: {}", kangYaPercent);
        BigDecimal  kangYaAmount = cashBal.multiply(new BigDecimal(kangYaPercent));
        // 账户预期亏损金额比这个还小时,立即止损
        if (realKuiSunAmount.compareTo(zhiSunAmount) > 0){
            log.error("账户冷静止损......");
            WsMapBuild.saveStringToMap(InstrumentsWs.INSTRUMENTSWSMAP, CoinEnums.OUT.name(),  OrderParamEnums.OUT_YES.getValue());
            return OrderParamEnums.OUT.getValue();
        }
        // 判断抗压
        if (realKuiSunAmount.compareTo(kangYaAmount) > 0 && realKuiSunAmount.compareTo(zhiSunAmount) <= 0){
            log.error("账户紧张扛仓......");
            return OrderParamEnums.HOLDING.getValue();
        }
        String pos = (String) redisUtils.get(positionsPosKey);
        if (StrUtil.isBlank(pos) || BigDecimal.ZERO.compareTo( new BigDecimal(pos)) >= 0) {
            log.error("未获取到持仓数量");
        BigDecimal ordFroz = AccountWs.ACCOUNTWSMAP.get("ordFroz");
        if (BigDecimal.ZERO.compareTo( ordFroz) >= 0) {
            log.error("占用保证金为零,进行初始化订单");
            return OrderParamEnums.INIT.getValue();
        }
        String uplStr = (String) redisUtils.get(positionsUplKey);
        //可使用的总保证金
        String cashBalStrKey = AccountWs.ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":cashBal";
        String cashBalStr = (String) redisUtils.get(cashBalStrKey);
        if (StrUtil.isBlank(cashBalStr) || StrUtil.isBlank(uplStr)){
            return OrderParamEnums.INIT.getValue();
        }
        BigDecimal upl = new BigDecimal(uplStr);
        if (BigDecimal.ZERO.compareTo(upl) >= 0){
            upl = upl.multiply(new BigDecimal("-1"));
            BigDecimal bigDecimal = new BigDecimal(cashBalStr).multiply(new BigDecimal(OrderParamEnums.ZHI_SUN.getValue()));
            if (upl.compareTo(bigDecimal) >= 0) {
                log.error("持仓盈亏超过下单总保证金,止损冷静一天......");
                return OrderParamEnums.OUT.getValue();
            }
        }
        log.info(OrderParamEnums.getNameByValue(state));
        // 获取标记价格和平均持仓价格
        String markPxObj = (String) redisUtils.get(positionsMarkPxKey);
        String avgPxObj = (String) redisUtils.get(positionsAvgPxKey);
        if (StrUtil.isBlank(markPxObj)  || StrUtil.isBlank(avgPxObj)) {
            return OrderParamEnums.INIT.getValue();
        // 判断是否保证金超标
        BigDecimal totalOrderUsdt = AccountWs.ACCOUNTWSMAP.get(CoinEnums.TOTAL_ORDER_USDT.name());
        if (ordFroz.compareTo(totalOrderUsdt) >= 0){
            log.error("已满仓......");
            return OrderParamEnums.HOLDING.getValue();
        }
        try {
            BigDecimal markPx = new BigDecimal( markPxObj);
            BigDecimal avgPx = new BigDecimal( avgPxObj);
            // 获取标记价格和平均持仓价格
            BigDecimal markPx = PositionsWs.POSITIONSWSMAP.get("markPx");
            BigDecimal avgPx = PositionsWs.POSITIONSWSMAP.get("avgPx");
            log.info("开仓价格: {}, 当前价格:{},匹配队列中......", avgPx, markPx);
            // 初始化网格队列
@@ -127,10 +124,8 @@
            PriorityBlockingQueue<AscBigDecimal> queuePingCang = wangGeService.initPingCang(avgPx, queueAsc);
            // 处理订单价格在队列中的情况
            String orderPrice = (String) redisUtils.get(positionsOrderPriceKey);
            String orderPrice = OrderInfoWs.ORDERINFOWSMAP.get("orderPrice");
            handleOrderPriceInQueues(orderPrice, queueKaiCang, queuePingCang);
            String side = OrderParamEnums.HOLDING.getValue();
            // 判断是加仓还是减仓
            if (avgPx.compareTo(markPx) > 0) {
@@ -138,80 +133,53 @@
                if (queueKaiCang.isEmpty()) {
                    // 队列为空
                    log.info("开始加仓,但是超出了网格设置...");
                    return side;
                    return OrderParamEnums.HOLDING.getValue();
                }
                DescBigDecimal kaiCang = queueKaiCang.peek();
                if (kaiCang != null && markPx.compareTo(kaiCang.getValue()) <= 0 && avgPx.compareTo(kaiCang.getValue()) >= 0) {
                    log.info("开始加仓...开仓队列价格大于当前价格{}>{}", kaiCang.getValue(), markPx);
                    side = OrderParamEnums.BUY.getValue();
                    redisUtils.set(positionsOrderPriceKey, String.valueOf(kaiCang.getValue()), 0);
                    WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice",String.valueOf(kaiCang.getValue()));
                    return OrderParamEnums.BUY.getValue();
                } else {
                    //判断是否加仓(当前持仓过小,可以加仓)
                    boolean isAddCang = doAddCang();
                    log.info("加仓过程中发现持仓过小 :{}",isAddCang);
                    if (isAddCang){
                        log.info("触发加仓......,持仓过小");
                        return OrderParamEnums.BUY.getValue();
                    }
                    log.info("未触发加仓......,等待");
                    return OrderParamEnums.HOLDING.getValue();
                }
            } else if (avgPx.compareTo(markPx) < 0) {
                log.info("开始减仓...");
                if (queuePingCang.isEmpty()) {
                    // 队列为空
                    log.info("开始减仓,但是超出了网格设置...");
                    return side;
                    return OrderParamEnums.HOLDING.getValue();
                }
                AscBigDecimal pingCang = queuePingCang.peek();
                if (pingCang != null && markPx.compareTo(pingCang.getValue()) >= 0 && avgPx.compareTo(pingCang.getValue()) < 0) {
                    log.info("开始减仓...平仓队列价格小于当前价格{}<={}", pingCang.getValue(), markPx);
                    //判断当前是否盈利
                    String uplstr = (String) redisUtils.get(positionsUplKey);
                    String realizedPnl = (String) redisUtils.get(positionsRealizedPnlKey);
                    String imr = (String) redisUtils.get(positionsImrKey);
                    if (uplstr != null && realizedPnl != null && imr != null) {
                        BigDecimal uplValue = new BigDecimal(uplstr);
                        BigDecimal realizedPnlValue = new BigDecimal(realizedPnl);
                        BigDecimal imrValue = new BigDecimal(imr).multiply(new BigDecimal(OrderParamEnums.PING_CANG_SHOUYI.getValue()));
                        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);
                                redisUtils.set(positionsOrderPriceKey, String.valueOf(pingCang.getValue()), 0);
                                return OrderParamEnums.SELL.getValue();
                            }else{
                                //判断是否加仓(当前持仓过小,可以加仓)
                                boolean isAddCang = doAddCang();
                                log.info("减仓过程中发现持仓过小 :{}",isAddCang);
                                if (isAddCang){
                                    log.info("触发加仓......,持仓过小");
                                    return OrderParamEnums.BUY.getValue();
                                }
                                log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue);
                                redisUtils.set(positionsOrderPriceKey, String.valueOf(pingCang.getValue()), 0);
                                return OrderParamEnums.HOLDING.getValue();
                            }
                    WsMapBuild.saveStringToMap(OrderInfoWs.ORDERINFOWSMAP, "orderPrice",String.valueOf(pingCang.getValue()));
                        }else {
                            if (uplValue.compareTo(imrValue)  >= 0) {
                                log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue);
                                redisUtils.set(positionsOrderPriceKey, String.valueOf(pingCang.getValue()), 0);
                                return OrderParamEnums.SELL.getValue();
                            }else{
                                //判断是否加仓(当前持仓过小,可以加仓)
                                boolean isAddCang = doAddCang();
                                log.info("减仓过程中发现持仓过小 :{}",isAddCang);
                                if (isAddCang){
                                    log.info("触发加仓......,持仓过小");
                                    return OrderParamEnums.BUY.getValue();
                                }
                                log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue);
                                redisUtils.set(positionsOrderPriceKey, String.valueOf(pingCang.getValue()), 0);
                                return OrderParamEnums.HOLDING.getValue();
                            }
                         }
                    // 判断当前是否盈利
                    BigDecimal uplValue = PositionsWs.POSITIONSWSMAP.get("upl");
                    BigDecimal imr = PositionsWs.POSITIONSWSMAP.get("imr");
                    BigDecimal realizedPnlValue = PositionsWs.POSITIONSWSMAP.get("realizedPnl");
                    String pingCangImr = InstrumentsWs.INSTRUMENTSWSMAP.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);
                            return OrderParamEnums.SELL.getValue();
                        }else{
                            log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue);
                            return OrderParamEnums.HOLDING.getValue();
                        }
                    }else {
                        return OrderParamEnums.HOLDING.getValue();
                        if (uplValue.compareTo(imrValue)  >= 0) {
                            log.info("当前未实现盈亏:{}大于预计收益>{},赚钱咯", uplValue, imrValue);
                            return OrderParamEnums.SELL.getValue();
                        }else{
                            log.info("当前未实现盈亏:{}没有大于预计收益>{},钱在路上了", uplValue, imrValue);
                            return OrderParamEnums.HOLDING.getValue();
                        }
                    }
                } else {
                    log.info("未触发减仓......,等待");
@@ -219,20 +187,11 @@
            } else {
                log.info("价格波动较小......,等待");
            }
            return side;
            return OrderParamEnums.HOLDING.getValue();
        } catch (NumberFormatException e) {
            log.error("解析价格失败,请检查Redis中的值是否合法", e);
            return OrderParamEnums.HOLDING.getValue();
        }
    }
    private boolean doAddCang() {
        String imr = (String) redisUtils.get(positionsImrKey);
        BigDecimal imrValue = new BigDecimal(StrUtil.isBlank(imr) ? "0" : imr);
        String everyTimeUsdt = (String) redisUtils.get(AccountWs.ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":everyTimeUsdt");
        BigDecimal everyTimeUsdtValue = new BigDecimal(everyTimeUsdt);
        return everyTimeUsdtValue.compareTo(imrValue) >= 0;
    }
    /**
@@ -258,8 +217,10 @@
            log.warn("无效的价格格式: {}", orderPrice);
            return;
        }
        // 删除比该价格大的数据
        queueKaiCang.removeIf(item -> item.getValue().compareTo(priceDecimal) >= 0);
        // 打印开仓队列
        StringBuilder kaiCangStr = new StringBuilder();
        kaiCangStr.append("开仓队列: [");
@@ -292,4 +253,3 @@
        log.info(pingCangStr.toString());
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/AccountWs.java
@@ -1,18 +1,19 @@
package com.xcong.excoin.modules.okxNewPrice.okxWs;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 账户 WebSocket 处理类,用于订阅 OKX 的账户频道并处理账户信息推送。
@@ -23,23 +24,11 @@
@Slf4j
public class AccountWs {
    public static  final Map<String,BigDecimal> ACCOUNTWSMAP = new ConcurrentHashMap<>();
    /**
     * 账户频道名称常量
     */
    public static final String ACCOUNTWS_CHANNEL = "account";
    private static final String CCY_KEY = "ccy";
    private static final String AVAIL_BAL_KEY = "availBal";
    private static final String CASH_BAL_KEY = "cashBal";
    private static final String EQ_KEY = "eq";
    private static final String DETAILS_KEY = "details";
    private static final String DATA_KEY = "data";
    // 缓存常用 BigDecimal 常量
    private static final BigDecimal KANG_CANG_THRESHOLD = new BigDecimal(OrderParamEnums.KANG_CANG.getValue());
    private static final BigDecimal ZHI_SUN_THRESHOLD = new BigDecimal(OrderParamEnums.ZHI_SUN.getValue());
    private static final BigDecimal TOTAL_ORDER_USDT_FACTOR = new BigDecimal(OrderParamEnums.TOTAL_ORDER_USDT.getValue());
    private static final BigDecimal EVERY_TIME_USDT_FACTOR = new BigDecimal(OrderParamEnums.EVERY_TIME_USDT.getValue());
    /**
     * 订阅账户频道
@@ -52,7 +41,7 @@
            JSONArray argsArray = new JSONArray();
            JSONObject args = new JSONObject();
            args.put("channel", ACCOUNTWS_CHANNEL);
            args.put(CCY_KEY, CoinEnums.USDT.getCode());
            args.put("ccy", CoinEnums.USDT.getCode());
            argsArray.add(args);
            String connId = MallUtils.getOrderNum(ACCOUNTWS_CHANNEL);
@@ -68,16 +57,13 @@
     * 处理账户频道推送的数据
     *
     * @param response   推送的 JSON 数据对象
     * @param redisUtils Redis 工具类实例,用于存储账户相关信息
     */
    public static void handleEvent(JSONObject response, RedisUtils redisUtils) {
    public static void handleEvent(JSONObject response) {
        String state = (String) redisUtils.get(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state");
        log.info("开始执行AccountWs......{}",state);
        log.info("开始执行AccountWs......{}",ACCOUNTWS_CHANNEL);
        try {
            JSONArray dataArray = response.getJSONArray(DATA_KEY);
            JSONArray dataArray = response.getJSONArray("data");
            if (dataArray == null || dataArray.isEmpty()) {
                log.warn("账户频道数据为空");
                return;
@@ -86,7 +72,7 @@
            for (int i = 0; i < dataArray.size(); i++) {
                try {
                    JSONObject accountData = dataArray.getJSONObject(i);
                    JSONArray detailsArray = accountData.getJSONArray(DETAILS_KEY);
                    JSONArray detailsArray = accountData.getJSONArray("details");
                    if (detailsArray == null || detailsArray.isEmpty()) {
                        log.warn("账户频道{}数据为空",CoinEnums.USDT.getCode());
                        continue;
@@ -94,56 +80,43 @@
                    for (int j = 0; j < detailsArray.size(); j++) {
                        JSONObject detail = detailsArray.getJSONObject(j);
                        //需要获取的参数
                        String ccyKey = "ccy";
                        String availBalKey = "availBal";
                        String cashBalKey = "cashBal";
                        String eqKey = "eq";
                        String ordFrozKey = "ordFroz";
                        String ccy = detail.getString(CCY_KEY);
                        String availBalStr = detail.getString(AVAIL_BAL_KEY);
                        String cashBalStr = detail.getString(CASH_BAL_KEY);
                        String eq = detail.getString(EQ_KEY);
                        BigDecimal ccy = WsMapBuild.parseBigDecimalSafe(detail.getString(ccyKey));
                        WsMapBuild.saveBigDecimalToMap(ACCOUNTWSMAP, ccyKey, ccy);
                        if (StrUtil.isBlank(ccy) || StrUtil.isBlank(availBalStr) || StrUtil.isBlank(cashBalStr)) {
                            log.warn("账户频道缺失必要字段,跳过处理");
                            continue;
                        }
                        BigDecimal availBal = WsMapBuild.parseBigDecimalSafe(detail.getString(availBalKey));
                        WsMapBuild.saveBigDecimalToMap(ACCOUNTWSMAP, availBalKey, availBal);
                        BigDecimal availBal = parseBigDecimalSafe(availBalStr);
                        BigDecimal cashBal = parseBigDecimalSafe(cashBalStr);
                        BigDecimal cashBal = WsMapBuild.parseBigDecimalSafe(detail.getString(cashBalKey));
                        WsMapBuild.saveBigDecimalToMap(ACCOUNTWSMAP, cashBalKey, cashBal);
                        if (availBal == null || cashBal == null || cashBal.compareTo(BigDecimal.ZERO) == 0) {
                        BigDecimal eq = WsMapBuild.parseBigDecimalSafe(detail.getString(eqKey));
                        WsMapBuild.saveBigDecimalToMap(ACCOUNTWSMAP, eqKey, eq);
                        BigDecimal ordFroz = WsMapBuild.parseBigDecimalSafe(detail.getString(ordFrozKey));
                        WsMapBuild.saveBigDecimalToMap(ACCOUNTWSMAP, ordFrozKey, ordFroz);
                        if (cashBal.compareTo(BigDecimal.ZERO) == 0) {
                            log.warn("账户频道无效的账户余额数据,跳过处理");
                            continue;
                        }
                        // 可用余额 / 现金余额 比例判断是否允许开仓
                        BigDecimal divide = availBal.divide(cashBal, 4, RoundingMode.DOWN);
                        if (OrderParamEnums.STATE_4.getValue().equals(state)){
                            log.info(OrderParamEnums.STATE_4.getName());
                            state = OrderParamEnums.STATE_4.getValue();
                        }else if(OrderParamEnums.STATE_3.getValue().equals(state)){
                            log.info(OrderParamEnums.STATE_3.getName());
                            state = OrderParamEnums.STATE_3.getValue();
                        }else{
                            if (divide.compareTo(KANG_CANG_THRESHOLD) > 0) {
                                log.info(OrderParamEnums.STATE_1.getName());
                                state = OrderParamEnums.STATE_1.getValue();
                            } else if (divide.compareTo(ZHI_SUN_THRESHOLD) > 0) {
                                log.warn(OrderParamEnums.STATE_2.getName());
                                state = OrderParamEnums.STATE_2.getValue();
                            } else {
                                log.error(OrderParamEnums.STATE_3.getName());
                                state = OrderParamEnums.STATE_3.getValue();
                            }
                        }
                        // 根据可用余额计算下单总保证金与每次下单金额
                        BigDecimal totalOrderUsdt = availBal.multiply(TOTAL_ORDER_USDT_FACTOR).setScale(4, RoundingMode.DOWN);
                        BigDecimal everyTimeUsdt = totalOrderUsdt.divide(EVERY_TIME_USDT_FACTOR, 4, RoundingMode.DOWN);
                        // 根据可用余额计算下单总保证金
                        String total_order_usdtpecent = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.TOTAL_ORDER_USDTPECENT.name());
                        BigDecimal total_order_usdt_factor = WsMapBuild.parseBigDecimalSafe(total_order_usdtpecent);
                        BigDecimal totalOrderUsdt = availBal.divide(total_order_usdt_factor, 4, RoundingMode.DOWN);
                        WsMapBuild.saveBigDecimalToMap(ACCOUNTWSMAP, CoinEnums.TOTAL_ORDER_USDT.name(), totalOrderUsdt);
                        log.info(
                                "账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 下单总保证金: {}, 每次下单保证金: {}, 是否允许开仓: {}",
                                ccy, availBalStr, cashBalStr, eq, totalOrderUsdt, everyTimeUsdt, state
                                "账户详情-币种: {}, 可用余额: {}, 现金余额: {}, 余额: {}, 占用保证金: {}, 下单总保证金: {}",
                                ccy, availBal, cashBal, eq, ordFroz, totalOrderUsdt
                        );
                        saveToRedis(redisUtils, availBalStr, cashBalStr, totalOrderUsdt.toString(), everyTimeUsdt.toString(), state);
                    }
                } catch (Exception innerEx) {
                    log.warn("处理账户频道数据失败", innerEx);
@@ -154,47 +127,5 @@
        }
    }
    /**
     * 安全地将字符串解析为 BigDecimal 类型
     *
     * @param value 字符串数值
     * @return 解析后的 BigDecimal 对象,若解析失败则返回 null
     */
    private static BigDecimal parseBigDecimalSafe(String value) {
        try {
            return new BigDecimal(value);
        } catch (NumberFormatException e) {
            log.warn("无法转换为 BigDecimal: {}", value);
            return null;
        }
    }
    /**
     * 将账户相关数据保存至 Redis 中
     *
     * @param redisUtils       Redis 工具类实例
     * @param availBal         可用余额
     * @param cashBal          现金余额
     * @param totalOrderUsdt   总下单保证金
     * @param everyTimeUsdt    每次下单保证金
     * @param state            当前账户状态(是否可开仓)
     */
    private static void saveToRedis(RedisUtils redisUtils, String availBal, String cashBal,
                                   String totalOrderUsdt, String everyTimeUsdt, String state) {
        try {
            boolean setResult =
                    redisUtils.set(ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":availBal", availBal, 0)
                            && redisUtils.set(ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":cashBal", cashBal, 0)
                            && redisUtils.set(ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":totalOrderUsdt", totalOrderUsdt, 0)
                            && redisUtils.set(ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":everyTimeUsdt", everyTimeUsdt, 0)
                            && redisUtils.set(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state", state, 0);
            if (!setResult) {
                log.warn("Redis set operation failed for key: account:{}", CoinEnums.USDT.getCode());
            }
        } catch (Exception e) {
            log.error("Redis操作异常,key: account:{}, error: {}", CoinEnums.USDT.getCode(), e.getMessage(), e);
        }
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/InstrumentsWs.java
@@ -2,8 +2,11 @@
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
import com.xcong.excoin.utils.RedisUtils;
import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * @author Administrator
@@ -11,26 +14,24 @@
@Slf4j
public class InstrumentsWs {
    public static  final Map<String, String> INSTRUMENTSWSMAP = new ConcurrentHashMap<>();
    public static final String INSTRUMENTSWS_CHANNEL = "instruments";
    public static void handleEvent(RedisUtils redisUtils) {
    public static void handleEvent() {
        log.info("开始执行InstrumentsWs......");
        // 将账户数据保存到Redis中,设置过期时间为30分钟
        try {
            boolean setResult =
                    redisUtils.set(INSTRUMENTSWS_CHANNEL+":" + CoinEnums.HE_YUE.getCode()+":instId", CoinEnums.HE_YUE.getCode(), 0)
                            && redisUtils.set(INSTRUMENTSWS_CHANNEL+":" + CoinEnums.HE_YUE.getCode()+":ctVal", "0.01", 0)
                            && redisUtils.set(INSTRUMENTSWS_CHANNEL+":" + CoinEnums.HE_YUE.getCode()+":tickSz", "2", 0)
                            && redisUtils.set(INSTRUMENTSWS_CHANNEL+":" + CoinEnums.HE_YUE.getCode()+":minSz", "2", 0)
                            && redisUtils.set(INSTRUMENTSWS_CHANNEL+":" + CoinEnums.HE_YUE.getCode()+":instIdCode", CoinEnums.HE_YUE.getCode(), 0)
                            && redisUtils.set(INSTRUMENTSWS_CHANNEL+":" + CoinEnums.HE_YUE.getCode()+":state", OrderParamEnums.STATE_0.getValue(), 0)
                            && redisUtils.set(INSTRUMENTSWS_CHANNEL+":" + CoinEnums.HE_YUE.getCode()+":out", OrderParamEnums.OUT_NO.getValue(), 0)
                    ;
            if (!setResult) {
                log.warn("Redis set operation failed for key: account:{}", CoinEnums.HE_YUE.getCode());
            }
        } catch (Exception e) {
            log.error("Redis操作异常,key: account:{}, error: {}", CoinEnums.HE_YUE.getCode(), e.getMessage(), e);
        }
        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.STATE.name(), OrderParamEnums.STATE_1.getValue());
        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.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());
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java
@@ -6,16 +6,22 @@
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * @author Administrator
 */
@Slf4j
public class OrderInfoWs {
    public static  final Map<String,String> ORDERINFOWSMAP = new ConcurrentHashMap<>();
    public static final String ORDERINFOWS_CHANNEL = "orders";
@@ -80,8 +86,8 @@
                        accFillSz, avgPx,state
                );
                String clOrdIdStr = (String) redisUtils.get(TradeOrderWs.ORDERWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":clOrdId");
                String stateStr = (String) redisUtils.get(TradeOrderWs.ORDERWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state");
                String clOrdIdStr = TradeOrderWs.TRADEORDERWSMAP.get("clOrdId");
                String stateStr = TradeOrderWs.TRADEORDERWSMAP.get("state");
                if (
                        StrUtil.isNotBlank(clOrdIdStr)
                                && clOrdId.equals(clOrdIdStr)
@@ -89,17 +95,20 @@
                                && state.equals(stateStr)
                                && !CoinEnums.ORDER_LIVE.getCode().equals(state)
                ){
                    boolean setResult = false;
                    String outStr = (String) redisUtils.get(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":out");
                    //记录成交均价
                    if (StrUtil.isBlank(ORDERINFOWSMAP.get("orderPrice"))){
                        WsMapBuild.saveStringToMap(ORDERINFOWSMAP, "orderPrice",avgPx);
                    }
                    WsMapBuild.saveStringToMap(TradeOrderWs.TRADEORDERWSMAP, "state", CoinEnums.ORDER_LIVE.getCode());
                    String outStr = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.OUT.name());
                    if (OrderParamEnums.OUT_YES.getValue().equals(outStr)){
                        setResult = redisUtils.set(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state", OrderParamEnums.STATE_3.getValue(), 0);
                        WsMapBuild.saveStringToMap(InstrumentsWs.INSTRUMENTSWSMAP, CoinEnums.STATE.name(), OrderParamEnums.STATE_3.getValue());
                    }else{
                        setResult = redisUtils.set(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state", OrderParamEnums.STATE_1.getValue(), 0);
                        WsMapBuild.saveStringToMap(InstrumentsWs.INSTRUMENTSWSMAP, CoinEnums.STATE.name(), OrderParamEnums.STATE_1.getValue());
                    }
                    if (setResult){
                        redisUtils.set(TradeOrderWs.ORDERWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state", CoinEnums.ORDER_LIVE.getCode(), 0);
                        log.info("订单详情-币种: {}, 自定义编号: {}, 订单状态: {}", CoinEnums.HE_YUE.getCode(), clOrdId, OrderParamEnums.STATE_1.getValue());
                    }
                    log.info("订单详情已完成: {}, 自定义编号: {}", CoinEnums.HE_YUE.getCode(), clOrdId);
                }
            }
        } catch (Exception e) {
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/PositionsWs.java
@@ -1,20 +1,17 @@
package com.xcong.excoin.modules.okxNewPrice.okxWs;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Optional;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * @author Administrator
@@ -22,8 +19,9 @@
@Slf4j
public class PositionsWs {
    public static  final Map<String,BigDecimal> POSITIONSWSMAP = new ConcurrentHashMap<>();
    public static final String POSITIONSWS_CHANNEL = "positions";
    private static final String REDIS_KEY_PREFIX = POSITIONSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode();
    public static void subscribePositionChannel(WebSocketClient webSocketClient, String option) {
        try {
@@ -43,7 +41,7 @@
        }
    }
    public static void handleEvent(JSONObject response, RedisUtils redisUtils) {
    public static void handleEvent(JSONObject response) {
        log.info("开始执行PositionsWs......");
@@ -52,7 +50,7 @@
            if (dataArray == null || dataArray.isEmpty()) {
                log.info("账户持仓频道数据为空,已当前价买入,并且初始化网格");
                JSONObject posData = new JSONObject();
                processPositionData(posData, redisUtils);
                processPositionData(posData);
                return;
            }
@@ -92,7 +90,8 @@
                            last, idxPx, bePx, realizedPnl, settledPnl,
                            markPx
                    );
                    processPositionData(posData, redisUtils);
                    processPositionData(posData);
                }
            }
        } catch (Exception e) {
@@ -100,101 +99,15 @@
        }
    }
    private static void processPositionData(JSONObject posData, RedisUtils redisUtils) {
        try {
            String avgPx = safeGetString(posData, "avgPx");
            String pos = safeGetString(posData, "pos");
            String upl = safeGetString(posData, "upl");
            String imr = safeGetString(posData, "imr");
            String mgnRatio = safeGetString(posData, "mgnRatio");
            String markPx = safeGetString(posData, "markPx");
            String bePx = safeGetString(posData, "bePx");
            String realizedPnl = safeGetString(posData, "realizedPnl");
    private static void processPositionData(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")));
            boolean setResult = saveToRedis(redisUtils, avgPx, pos, upl, imr, mgnRatio, markPx, bePx,realizedPnl);
            if (setResult) {
                calculateAndSaveBuyCount(redisUtils);
            } else {
                log.warn("Redis操作失败");
            }
        } catch (Exception e) {
            log.error("Redis操作异常", e);
        }
    }
    private static boolean saveToRedis(RedisUtils redisUtils,
                                       String avgPx, String pos, String upl,
                                       String imr, String mgnRatio, String markPx, String bePx, String realizedPnl) {
        return redisUtils.set(REDIS_KEY_PREFIX + ":avgPx", avgPx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":pos", pos, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":upl", upl, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":imr", imr, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":mgnRatio", mgnRatio, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":markPx", markPx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":bePx", bePx, 0)
                && redisUtils.set(REDIS_KEY_PREFIX + ":realizedPnl", realizedPnl, 0);
    }
    private static void calculateAndSaveBuyCount(RedisUtils redisUtils) {
        try {
            String ctValStr = (String) redisUtils.get(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":ctVal");
            String markPxStr = (String) redisUtils.get(REDIS_KEY_PREFIX + ":markPx");
            String minSzStr = (String) redisUtils.get(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":minSz");
            String everyTimeUsdt = (String) redisUtils.get(AccountWs.ACCOUNTWS_CHANNEL + ":" + CoinEnums.USDT.getCode() + ":everyTimeUsdt");
            BigDecimal margin = parseBigDecimal(everyTimeUsdt, "0");
            BigDecimal leverage = parseBigDecimal(OrderParamEnums.LEVERAGE.getValue(), "1");
            BigDecimal faceValue = parseBigDecimal(ctValStr, "0");
            BigDecimal markPrice = parseBigDecimal(markPxStr, "0"); // 默认值需谨慎对待
            int minLotSz = parseInt(minSzStr, 0);
            BigDecimal buyCnt = buyCnt(margin, leverage, faceValue, markPrice, minLotSz);
            redisUtils.set(REDIS_KEY_PREFIX + ":buyCnt", buyCnt.toString(), 0);
        } catch (NumberFormatException | ArithmeticException e) {
            log.error("计算购买数量时发生数字转换错误", e);
        }
    }
    private static String safeGetString(JSONObject obj, String key) {
        return Optional.ofNullable(obj.getString(key)).orElse("0");
    }
    private static BigDecimal parseBigDecimal(String value, String defaultValue) {
        return new BigDecimal(Optional.ofNullable(value).filter(s -> !s.isEmpty()).orElse(defaultValue));
    }
    private static int parseInt(String value, int defaultValue) {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }
    /**
     * 计算购买合约的数量
     *
     * USDT 币本位合约
     * 公式:张数 = 保证金 / (面值 * 标记价格 / 杠杆倍数)
     *
     * @param margin 用户的保证金金额
     * @param leverage 杠杆倍数
     * @param faceValue 合约面值
     * @param markPrice 标记价格
     * @param minLotSz 最小下单精度
     * @return 返回用户可以购买的合约数量
     */
    public static BigDecimal buyCnt(BigDecimal margin, BigDecimal leverage, BigDecimal faceValue, BigDecimal markPrice, int minLotSz) {
        if (margin.compareTo(BigDecimal.ZERO) <= 0 ||
            leverage.compareTo(BigDecimal.ZERO) <= 0 ||
            faceValue.compareTo(BigDecimal.ZERO) <= 0 ||
            markPrice.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal divisor = markPrice.divide(leverage, 10, BigDecimal.ROUND_DOWN);
        BigDecimal denominator = faceValue.multiply(divisor);
        return margin.divide(denominator, minLotSz, BigDecimal.ROUND_DOWN);
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
@@ -5,13 +5,14 @@
import com.alibaba.fastjson.JSONObject;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums;
import com.xcong.excoin.modules.okxNewPrice.okxpi.MallUtils;
import com.xcong.excoin.modules.okxNewPrice.utils.WsMapBuild;
import com.xcong.excoin.modules.okxNewPrice.utils.WsParamBuild;
import com.xcong.excoin.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 交易订单处理类,负责构建和发送订单请求到OKX WebSocket
@@ -21,60 +22,34 @@
@Slf4j
public class TradeOrderWs {
    public static  final Map<String,String> TRADEORDERWSMAP = new ConcurrentHashMap<>();
    public static final String ORDERWS_CHANNEL = "order";
    public static void orderEvent(WebSocketClient webSocketClient, RedisUtils redisUtils, String side) {
    public static void orderEvent(WebSocketClient webSocketClient, String side) {
        log.info("开始执行TradeOrderWs......");
        if (StrUtil.isBlank( side)){
            log.warn("止损了,下次再战...");
            return;
        }
        String live = (String) redisUtils.getWithDelay(ORDERWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state");
        if (!CoinEnums.ORDER_LIVE.getCode().equals( live)){
            log.warn("正在下单中,等待下单结束...");
            return;
        }
        String buyCnt = null;
        String ctval = getRedisValue(redisUtils, InstrumentsWs.INSTRUMENTSWS_CHANNEL, ":ctVal");
        String buyCntNormal = getRedisValue(redisUtils, PositionsWs.POSITIONSWS_CHANNEL, ":buyCnt");
        String pos = getRedisValue(redisUtils, PositionsWs.POSITIONSWS_CHANNEL, ":pos");
        if (OrderParamEnums.ORDERING.getValue().equals(side)) {
            log.warn("正在下单中,等待下单结束...");
            return;
        } else if (OrderParamEnums.HOLDING.getValue().equals(side)) {
            return;
        } else if (OrderParamEnums.INIT.getValue().equals(side)) {
            side = OrderParamEnums.BUY.getValue();
            if (StrUtil.isNotBlank(buyCntNormal) && new BigDecimal(buyCntNormal).compareTo(BigDecimal.ZERO) > 0) {
                buyCnt = buyCntNormal;
            }else{
                buyCnt = ctval;
            }
        } else if (OrderParamEnums.OUT.getValue().equals(side)) {
            log.info(OrderParamEnums.getNameByValue(OrderParamEnums.OUT.getValue()));
            side = OrderParamEnums.SELL.getValue();
            buyCnt = pos;
        } else if (OrderParamEnums.BUY.getValue().equals(side)){
            side = OrderParamEnums.BUY.getValue();
            if (StrUtil.isNotBlank(buyCntNormal) && new BigDecimal(buyCntNormal).compareTo(BigDecimal.ZERO) > 0) {
                buyCnt = buyCntNormal;
            }else{
                buyCnt = ctval;
            }
        }else if (OrderParamEnums.SELL.getValue().equals(side)){
            side = OrderParamEnums.SELL.getValue();
            buyCnt = getRedisValue(redisUtils, PositionsWs.POSITIONSWS_CHANNEL, ":pos");
        }else{
            log.warn("操作信号异常,请检查下单操作...");
            return;
        }
        // 校验必要参数
        if (StrUtil.isBlank(side)) {
            log.warn("下单参数 side 为空,取消发送");
            return;
        }
        String buyCnt = "";
        if (OrderParamEnums.HOLDING.getValue().equals(side)){
            log.info("当前状态为持仓中,取消发送");
            return;
        }else if (OrderParamEnums.OUT.getValue().equals(side)){
            log.info("当前状态为止损");
            buyCnt = String.valueOf(PositionsWs.POSITIONSWSMAP.get("pos"));
        }else if (OrderParamEnums.INIT.getValue().equals(side)){
            log.info("当前状态为初始化");
            buyCnt = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.BUY_CNT.name());
        }else if (OrderParamEnums.BUY.getValue().equals(side)){
            log.info("当前状态为加仓");
            buyCnt = InstrumentsWs.INSTRUMENTSWSMAP.get(CoinEnums.BUY_CNT.name());
        }else if (OrderParamEnums.SELL.getValue().equals(side)){
            log.info("当前状态为减仓");
            buyCnt = String.valueOf(PositionsWs.POSITIONSWSMAP.get("pos"));
        }
        if (StrUtil.isBlank(buyCnt)) {
@@ -83,7 +58,7 @@
        }
        try {
            String clOrdId = MallUtils.getOrderNum(side);
            String clOrdId = WsParamBuild.getOrderNum(side);
            JSONArray argsArray = new JSONArray();
            JSONObject args = new JSONObject();
            args.put("instId", CoinEnums.HE_YUE.getCode());
@@ -95,17 +70,16 @@
            args.put("sz", buyCnt);
            argsArray.add(args);
            String connId = MallUtils.getOrderNum(ORDERWS_CHANNEL);
            String connId = WsParamBuild.getOrderNum(ORDERWS_CHANNEL);
            JSONObject jsonObject = WsParamBuild.buildJsonObject(connId, ORDERWS_CHANNEL, argsArray);
            webSocketClient.send(jsonObject.toJSONString());
            log.info("发送下单频道:{},数量:{}", side, buyCnt);
            boolean setResult =
                    redisUtils.set(ORDERWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":clOrdId", clOrdId, 0)
                    && redisUtils.set(ORDERWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state", CoinEnums.ORDER_FILLED.getCode(), 0)
                    && redisUtils.set(InstrumentsWs.INSTRUMENTSWS_CHANNEL + ":" + CoinEnums.HE_YUE.getCode() + ":state", OrderParamEnums.STATE_4.getValue(), 0);
            if (!setResult) {
                log.warn("Redis set operation failed for key: order:{}", CoinEnums.HE_YUE.getCode());
            }
            WsMapBuild.saveStringToMap(TRADEORDERWSMAP, "clOrdId", connId);
            WsMapBuild.saveStringToMap(TRADEORDERWSMAP, "state", CoinEnums.ORDER_FILLED.getCode());
            WsMapBuild.saveStringToMap(InstrumentsWs.INSTRUMENTSWSMAP, CoinEnums.STATE.name(), OrderParamEnums.STATE_4.getValue());
        } catch (Exception e) {
            log.error("下单构建失败", e);
        }
@@ -139,17 +113,4 @@
        return profit.setScale(minTickSz, BigDecimal.ROUND_DOWN);
    }
    /**
     * 统一封装 Redis Key 构建逻辑
     *
     * @param redisUtils Redis 工具类实例
     * @param prefix     渠道前缀
     * @param suffix     字段后缀
     * @return Redis 中存储的值
     */
    private static String getRedisValue(RedisUtils redisUtils, String prefix, String suffix) {
        String key = prefix + ":" + CoinEnums.HE_YUE.getCode() + suffix;
        Object valueObj = redisUtils.get(key);
        return valueObj == null ? null : String.valueOf(valueObj);
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/CoinEnums.java
@@ -14,48 +14,43 @@
     *
     * 若不添加该字段或将其设置为上述合法值以外的其他值,数据将根据事件推送并大约每 5 秒定期推送一次
     */
    UPDATEINTERVAL("2000",
            "2000"),
    UPDATEINTERVAL("2000","2000"),
    ORDER_FILLED("filled","filled"),
    ORDER_LIVE("live","live"),
    INSTTYPE_SWAP("SWAP","SWAP"),
    ORDTYPE_MARKET("market","market"),
    POSSIDE_SHORT("short","short"),
    POSSIDE_LONG("long","long"),
    SIDE_SELL("sell","sell"),
    SIDE_BUY("buy","buy"),
    CROSS("cross","cross"),
    USDT("USDT","USDT"),
    ORDER_FILLED("filled",
            "filled"),
    ORDER_LIVE("live",
            "live"),
    INSTTYPE_SWAP("SWAP",
            "SWAP"),
    ORDTYPE_MARKET("market",
            "market"),
    POSSIDE_SHORT("short",
            "short"),
    POSSIDE_LONG("long",
            "long"),
    SIDE_SELL("sell",
            "sell"),
    SIDE_BUY("buy",
            "buy"),
    CROSS("cross",
            "cross"),
    USDT("USDT",
            "USDT"),
    HE_YUE("BTC-USDT-SWAP",
            "BTC-USDT-SWAP");
    PING_CANG_SHOUYI("平仓收益比例", "0.2"),
    //下单的总保障金为账户总金额cashBal * TOTAL_ORDER_USDT用来做保证金
    TOTAL_ORDER_USDTPECENT("总保证金比例total_order_usdtpecent","0.1"),
    TOTAL_ORDER_USDT("总保证金totalOrderUsdt","0"),
    KANG_CANG("抗压比例KANG_CANG","0.8"),
    ZHI_SUN("止损比例ZHI_SUN","0.5"),
    //每次下单的张数
    BUY_CNT("每次开仓的张数buyCnt","0.2"),
    OUT("是否允许下单out","操作中"),
    STATE("初始下单状态不允许下单state","0"),
    CTVAL("合约面值ctVal","0.01"),
    TICKSZ("下单价格精度tickSz","2"),
    MINSZ("最小下单数小数位minSz","2"),
    LEVERAGE("合约杠杆leverage","100"),
    HE_YUE("合约instId","BTC-USDT-SWAP");
    private String name;
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/enums/OrderParamEnums.java
@@ -26,17 +26,6 @@
    STATE_1("允许开仓", "1"),
    STATE_0("不允许开仓", "0"),
    ZHI_SUN("止损......", "0.6"),
    KANG_CANG("抗压......", "0.8"),
    LEVERAGE("杠杆倍数", "100"),
    PING_CANG_SHOUYI("平仓收益比例", "0.2"),
    EVERY_TIME_USDT("总下单次数", "50"),
    //下单的总保障金为账户总金额cashBal * TOTAL_ORDER_USDT用来做保证金
    TOTAL_ORDER_USDT("下单的总金额比例", "0.1"),
    ;
    private String name;
src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsMapBuild.java
New file
@@ -0,0 +1,45 @@
package com.xcong.excoin.modules.okxNewPrice.utils;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.Map;
/**
 * @author Administrator
 */
@Slf4j
public class WsMapBuild {
    public static void saveBigDecimalToMap(Map<String,BigDecimal> accountMap, String key, BigDecimal value) {
        try {
            accountMap.put(key, value);
        } catch (Exception e) {
            log.error("保存账户数据到MAP 失败", e);
        }
    }
    public static void saveStringToMap(Map<String,String> accountMap, String key, String value) {
        try {
            accountMap.put(key, value);
        } catch (Exception e) {
            log.error("保存账户数据到MAP 失败", e);
        }
    }
    /**
     * 安全地将字符串解析为 BigDecimal 类型
     *
     * @param value 字符串数值
     * @return 解析后的 BigDecimal 对象,若解析失败则返回 null
     */
    public static BigDecimal parseBigDecimalSafe(String value) {
        if (value == null || value.isEmpty()) {
            return new BigDecimal(0);
        }
        return new BigDecimal(value);
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/utils/WsParamBuild.java
@@ -4,10 +4,36 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
/**
 * @author Administrator
 */
public class WsParamBuild {
    public static String getOrderNum(String prefix) {
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
        String dd=df.format(new Date());
        if (StrUtil.isNotBlank(prefix)) {
            return prefix+dd+getRandomNum(5);
        }
        return dd+getRandomNum(5);
    }
    public static String getRandomNum(int length) {
        String str = "0123456789";
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; ++i) {
            int number = random.nextInt(str.length());
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }
    public static JSONObject buildJsonObject(String connId, String option, JSONArray argsArray) {
        JSONObject jsonObject = new JSONObject();
@@ -18,4 +44,31 @@
        jsonObject.put("args", argsArray);
        return jsonObject;
    }
    /**
     * 计算购买合约的数量
     *
     * USDT 币本位合约
     * 公式:张数 = 保证金 / (面值 * 标记价格 / 杠杆倍数)
     *
     * @param margin 用户的保证金金额
     * @param leverage 杠杆倍数
     * @param faceValue 合约面值
     * @param markPrice 标记价格
     * @param minLotSz 最小下单精度
     * @return 返回用户可以购买的合约数量
     */
    public static BigDecimal buyCnt(BigDecimal margin, BigDecimal leverage, BigDecimal faceValue, BigDecimal markPrice, int minLotSz) {
        if (margin.compareTo(BigDecimal.ZERO) <= 0 ||
                leverage.compareTo(BigDecimal.ZERO) <= 0 ||
                faceValue.compareTo(BigDecimal.ZERO) <= 0 ||
                markPrice.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal divisor = markPrice.divide(leverage, 10, BigDecimal.ROUND_DOWN);
        BigDecimal denominator = faceValue.multiply(divisor);
        return margin.divide(denominator, minLotSz, BigDecimal.ROUND_DOWN);
    }
}