2968d8cf5bf5437728467e010e6f3292f495a9ed..920927331afecb67e85268eecb567a91b53f1e61
2026-01-05 Administrator
refactor(okxNewPrice): 移除调试日志
920927 diff | tree
2026-01-05 Administrator
fix(strategy): 修复MACD策略EMA计算和交易信号处理问题
7d135a diff | tree
2026-01-05 Administrator
refactor(trading): 优化MACD和MA组合交易策略支持多时间粒度
084bb8 diff | tree
2026-01-05 Administrator
refactor(indicator): 优化MACD计算逻辑并修复数据处理顺序问题
bfb3c8 diff | tree
2026-01-05 Administrator
fix(okxNewPrice): 解决K线数据处理中的数组越界问题
de3815 diff | tree
2026-01-05 Administrator
feat(okxNewPrice): 添加K线时间戳转换和日志记录功能
f83e54 diff | tree
2026-01-05 Administrator
fix(config): 更新OKX交易所配置信息
2c7114 diff | tree
2026-01-05 Administrator
fix(okxNewPrice): 修复日志输出变量错误
b0c4c7 diff | tree
2026-01-05 Administrator
feat(okxNewPrice): 添加价格数据日志记录功能
0dac65 diff | tree
2026-01-05 Administrator
fix(indicator): 修复MACD计算中的索引映射问题
2f5192 diff | tree
2026-01-05 Administrator
feat(indicator): 添加MACD指标计算功能并优化策略参数
b70f32 diff | tree
2026-01-05 Administrator
refactor(indicator): 调整MACD策略默认参数
ff340a diff | tree
2026-01-05 Administrator
feat(trading): 优化MACD策略算法适配欧意平台
9ff43c diff | tree
2026-01-05 Administrator
fix(okxWs): 修复下单日志参数缺失问题
8a9f1f diff | tree
2026-01-05 Administrator
fix(okxNewPrice): 修复订单事件处理中的数据冲突问题
27ebde diff | tree
2026-01-05 Administrator
fix(okxWs): 修正订单信息中仓位方向参数传递错误
b04ced diff | tree
9 files modified
899 ■■■■■ changed files
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java 119 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/EMACalculator.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDCalculator.java 148 ●●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java 584 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeInfoEnum.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/DefaultUrls.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
@@ -3,9 +3,11 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xcong.excoin.modules.blackchain.service.DateUtil;
import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService;
import com.xcong.excoin.modules.okxNewPrice.indicator.TradingStrategy;
import com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy.MacdMaStrategy;
@@ -332,6 +334,8 @@
                BigDecimal lowPx = new BigDecimal(data.getString(3));
                BigDecimal closePx = new BigDecimal(data.getString(4));
                BigDecimal vol = new BigDecimal(data.getString(5));
                //ts    String    开始时间,Unix时间戳的毫秒数格式,如 1597026383085 转日期:2020-08-07 15:13:03.085
                String time = DateUtil.TimeStampToDateTime(Long.parseLong(data.getString(0)));
                /**
                 * K线状态
                 * 0:K线未完结
@@ -339,37 +343,28 @@
                 */
                String confirm = data.getString(8);
                if ("1".equals(confirm)){
                    log.info("{}开仓{}:{}",time,closePx,instId);
                    //调用策略
                    // 创建策略实例
                    MacdMaStrategy strategy = new MacdMaStrategy();
                    // 生成100个15分钟价格数据点
                    // 生成200个1m价格数据点
                    List<Kline> kline1MinuteData = getKlineDataByInstIdAndBar(instId, "1m");
                    List<BigDecimal> historicalPrices1M = kline1MinuteData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    // 生成200个1D价格数据点
                    List<Kline> kline1DayData = getKlineDataByInstIdAndBar(instId, "1D");
                    List<BigDecimal> historicalPrices1D = kline1DayData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    log.info("1D:{}", JSONUtil.parse( historicalPrices1D));
                    // 使用策略分析最新价格数据
                    MacdMaStrategy.TradingOrder tradingOrderOpen1M = strategy.generateTradingOrder(historicalPrices1M,MacdMaStrategy.OperationType.open.name());
                    if (tradingOrderOpen1M == null ){
                    MacdMaStrategy.TradingOrder tradingOrderOpenOpen = strategy.generateTradingOrder(historicalPrices1M,historicalPrices1D,MacdMaStrategy.OperationType.open.name());
                    if (tradingOrderOpenOpen == null){
                        return;
                    }
//                    List<Kline> kline15MinuteData = getKlineDataByInstIdAndBar(instId, "15m");
//                    List<BigDecimal> historicalPrices15M = kline15MinuteData.stream()
//                            .map(Kline::getC)
//                            .collect(Collectors.toList());
//                    // 使用策略分析最新价格数据
//                    MacdMaStrategy.TradingOrder tradingOrderOpen15M = strategy.generateTradingOrder(historicalPrices15M,MacdMaStrategy.OperationType.open.name());
//                    if (tradingOrderOpen15M == null ){
//                        return;
//                    }
//
//                    if (!tradingOrderOpen1M.getPosSide().equals(tradingOrderOpen15M.getPosSide())){
//                        return;
//                    }
//                    log.info("1分钟和15分钟K线方向一致,开始执行交易操作!");
                    Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients();
                    //如果为空,则直接返回
@@ -380,16 +375,16 @@
                    for (OkxQuantWebSocketClient client : clientManager.getAllClients()) {
                        String accountName = client.getAccountName();
                        if (accountName != null) {
                            if (ObjectUtil.isNotEmpty(tradingOrderOpen1M)){
                            if (ObjectUtil.isNotEmpty(tradingOrderOpenOpen)){
                                // 根据信号执行交易操作
                                TradeRequestParam tradeRequestParam = new TradeRequestParam();
                                String posSide = tradingOrderOpen1M.getPosSide();
                                String posSide = tradingOrderOpenOpen.getPosSide();
                                tradeRequestParam.setPosSide(posSide);
                                String currentPrice = String.valueOf(closePx);
                                tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, currentPrice, posSide);
                                String side = tradingOrderOpen1M.getSide();
                                String side = tradingOrderOpenOpen.getSide();
                                tradeRequestParam.setSide(side);
                                String clOrdId = WsParamBuild.getOrderNum(side);
@@ -398,6 +393,63 @@
                                String sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name());
                                tradeRequestParam.setSz(sz);
                                TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParam);
                            }
                        }
                    }
                }else{
                    log.info("{}平仓{}:{}",time,closePx,instId);
                    //调用策略
                    // 创建策略实例
                    MacdMaStrategy strategy = new MacdMaStrategy();
                    // 生成200个1m价格数据点
                    List<Kline> kline1MinuteData = getKlineDataByInstIdAndBar(instId, "1m");
                    List<BigDecimal> historicalPrices1M = kline1MinuteData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    // 生成200个1D价格数据点
                    List<Kline> kline1DayData = getKlineDataByInstIdAndBar(instId, "1D");
                    List<BigDecimal> historicalPrices1D = kline1DayData.stream()
                            .map(Kline::getC)
                            .collect(Collectors.toList());
                    // 使用策略分析最新价格数据
                    MacdMaStrategy.TradingOrder tradingOrderOpenClose = strategy.generateTradingOrder(historicalPrices1M,historicalPrices1D,MacdMaStrategy.OperationType.close.name());
                    if (tradingOrderOpenClose == null){
                        return;
                    }
                    Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients();
                    //如果为空,则直接返回
                    if (allClients.isEmpty()) {
                        return;
                    }
                    // 获取所有OkxQuantWebSocketClient实例
                    for (OkxQuantWebSocketClient client : clientManager.getAllClients()) {
                        String accountName = client.getAccountName();
                        if (accountName != null) {
                            if (ObjectUtil.isNotEmpty(tradingOrderOpenClose)){
                                // 根据信号执行交易操作
                                TradeRequestParam tradeRequestParam = new TradeRequestParam();
                                tradeRequestParam.setAccountName(accountName);
                                tradeRequestParam.setMarkPx(String.valueOf(closePx));
                                tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode());
                                tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode());
                                tradeRequestParam.setOrdType(CoinEnums.ORDTYPE_MARKET.getCode());
                                String posSide = tradingOrderOpenClose.getPosSide();
                                tradeRequestParam.setPosSide(posSide);
                                String side = tradingOrderOpenClose.getSide();
                                tradeRequestParam.setSide(side);
                                String clOrdId = WsParamBuild.getOrderNum(side);
                                tradeRequestParam.setClOrdId(clOrdId);
                                String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
                                BigDecimal pos = PositionsWs.getAccountMap(positionAccountName).get("pos");
                                tradeRequestParam.setSz(String.valueOf(pos));
                                TradeOrderWs.orderZhiYingZhiSunEventNoState(client.getWebSocketClient(), tradeRequestParam);
                            }
                        }
                    }
@@ -425,16 +477,19 @@
                    for (String[] s : klinesList) {
                        // 确保数组有足够的元素
                        if (s != null && s.length >= 9) {
                            String s1 = s[8];
                            try {
                                Kline kline = new Kline();
                                kline.setTs(s[0]);
                                kline.setO(new BigDecimal(s[1]));
                                kline.setH(new BigDecimal(s[2]));
                                kline.setL(new BigDecimal(s[3]));
                                kline.setC(new BigDecimal(s[4]));
                                kline.setVol(new BigDecimal(s[5]));
                                kline.setConfirm(s[8]);
                                klineList.add(kline);
                                if ("1".equals(s1)){
                                    Kline kline = new Kline();
                                    kline.setTs(s[0]);
                                    kline.setO(new BigDecimal(s[1]));
                                    kline.setH(new BigDecimal(s[2]));
                                    kline.setL(new BigDecimal(s[3]));
                                    kline.setC(new BigDecimal(s[4]));
                                    kline.setVol(new BigDecimal(s[5]));
                                    kline.setConfirm(s[8]);
                                    klineList.add(kline);
                                }
                            } catch (NumberFormatException e) {
                                log.error("K线数据转换为BigDecimal失败: {}", Arrays.toString(s), e);
                            }
src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxQuantWebSocketClient.java
@@ -383,8 +383,9 @@
        // 注意:当前实现中,OrderInfoWs等类使用静态Map存储数据
        // 这会导致多账号之间的数据冲突。需要进一步修改这些类的设计,让数据存储与特定账号关联
        if (OrderInfoWs.ORDERINFOWS_CHANNEL.equals(channel)) {
            TradeRequestParam tradeRequestParam = OrderInfoWs.handleEvent(response, redisUtils, account.name());
            TradeOrderWs.orderEvent(webSocketClient, tradeRequestParam);
            OrderInfoWs.handleEvent(response, redisUtils, account.name());
//            TradeRequestParam tradeRequestParam = OrderInfoWs.handleEvent(response, redisUtils, account.name());
//            TradeOrderWs.orderZhiYingZhiSunEventNoState(webSocketClient, tradeRequestParam);
        }else if (AccountWs.ACCOUNTWS_CHANNEL.equals(channel)) {
            AccountWs.handleEvent(response, account.name());
        } else if (PositionsWs.POSITIONSWS_CHANNEL.equals(channel)) {
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/EMACalculator.java
@@ -86,4 +86,22 @@
    public static List<BigDecimal> calculateEMA(List<BigDecimal> prices, int period) {
        return calculateEMA(prices, period, true);
    }
    /**
     * 计算单个EMA值(递归计算方式)
     *
     * @param currentPrice 当前价格
     * @param prevEMA 前一个EMA值
     * @param period EMA周期
     * @return 当前EMA值
     */
    public static BigDecimal calculateSingleEMA(BigDecimal currentPrice, BigDecimal prevEMA, int period) {
        // 计算权重因子alpha = 2 / (period + 1)
        BigDecimal alpha = BigDecimal.valueOf(2.0).divide(BigDecimal.valueOf(period + 1), 10, RoundingMode.HALF_UP);
        // EMA(today) = Price(today) * alpha + EMA(yesterday) * (1 - alpha)
        return currentPrice.multiply(alpha)
                .add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha)))
                .setScale(10, RoundingMode.HALF_UP);
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MACDCalculator.java
@@ -2,6 +2,7 @@
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -20,74 +21,101 @@
     * 计算MACD指标
     *
     * @param closePrices 收盘价列表(使用BigDecimal确保计算精度)
     * @param shortPeriod 短期EMA周期(通常为12)
     * @param longPeriod  长期EMA周期(通常为26)
     * @param signalPeriod DEA的周期(通常为9)
     * @param fastlen 短期EMA周期(通常为12)
     * @param slowlen 长期EMA周期(通常为26)
     * @param siglen DEA的周期(通常为9)
     * @return 包含MACD各部分数据的PriceData列表
     * @throws IllegalArgumentException 如果数据点不足或参数无效
     */
    public static MACDResult calculateMACD(List<BigDecimal> closePrices, int shortPeriod, int longPeriod, int signalPeriod) {
    public static MACDResult calculateMACD(List<BigDecimal> closePrices, int fastlen, int slowlen, int siglen) {
        // 参数校验:确保数据点足够
        if (closePrices == null || closePrices.isEmpty()) {
            throw new IllegalArgumentException("Close prices list cannot be null or empty.");
        }
        if (shortPeriod <= 0 || longPeriod <= 0 || signalPeriod <= 0) {
        if (fastlen <= 0 || slowlen <= 0 || siglen <= 0) {
            throw new IllegalArgumentException("All periods must be positive integers.");
        }
        if (shortPeriod >= longPeriod) {
            throw new IllegalArgumentException("Short period must be less than long period.");
        if (fastlen >= slowlen) {
            throw new IllegalArgumentException("Fast period must be less than slow period.");
        }
        if (closePrices.size() < Math.max(shortPeriod, longPeriod)) {
        if (closePrices.size() < Math.max(fastlen, slowlen)) {
            throw new IllegalArgumentException("Insufficient data points for the specified periods.");
        }
        // 1. 计算短期和长期EMA(使用SMA作为初始值,提高计算准确性)
        List<BigDecimal> emaShort = EMACalculator.calculateEMA(closePrices, shortPeriod, true);
        List<BigDecimal> emaLong = EMACalculator.calculateEMA(closePrices, longPeriod, true);
        // 反转数据,确保从旧到新处理(因为用户提供的数据是从新到旧)
        List<BigDecimal> prices = new ArrayList<>(closePrices);
        Collections.reverse(prices);
        // 2. 确定公共有效起始点(从较长周期的EMA开始计算)
        int startIdx = Math.max(shortPeriod, longPeriod) - 1; // 因为EMA从第period个数据点开始有效
        int validLength = closePrices.size() - startIdx;      // 有效数据点数量
        // 1. 计算快速EMA和慢速EMA,使用SMA作为初始值
        // 当initialSMA=true时,EMA列表长度为prices.size() - period + 1
        List<BigDecimal> fastEma = EMACalculator.calculateEMA(prices, fastlen, true);
        List<BigDecimal> slowEma = EMACalculator.calculateEMA(prices, slowlen, true);
        // 3. 计算DIF(仅在有效区间内计算)
        List<BigDecimal> difValues = new ArrayList<>(validLength);
        for (int i = 0; i < validLength; i++) {
            // 计算EMA的有效索引
            int idxEmaShort = startIdx - shortPeriod + 1 + i; // 短期EMA的当前索引
            int idxEmaLong = startIdx - longPeriod + 1 + i;   // 长期EMA的当前索引
            // DIF = 短期EMA - 长期EMA
            BigDecimal dif = emaShort.get(idxEmaShort).subtract(emaLong.get(idxEmaLong));
            difValues.add(dif);
        // 2. 计算MACD线(快速EMA减去慢速EMA)
        List<BigDecimal> macdLine = new ArrayList<>();
        // EMA列表的起始索引与价格列表的对应关系
        int slowEmaStartIdx = slowlen - 1; // slowEma中第一个有效值对应的价格索引
        int fastEmaStartIdx = fastlen - 1; // fastEma中第一个有效值对应的价格索引
        for (int i = 0; i < prices.size(); i++) {
            if (i < slowEmaStartIdx) {
                // 在慢速EMA开始有效之前,MACD线值为0
                macdLine.add(BigDecimal.ZERO);
            } else {
                // MACD线 = 快速EMA - 慢速EMA
                // 需要将价格索引转换为EMA列表索引
                int slowEmaIdx = i - slowEmaStartIdx;
                int fastEmaIdx = i - fastEmaStartIdx;
                BigDecimal macdValue = fastEma.get(fastEmaIdx).subtract(slowEma.get(slowEmaIdx));
                macdLine.add(macdValue);
            }
        }
        // 4. 计算DEA(基于有效DIF数据的EMA)
        List<BigDecimal> deaValues = EMACalculator.calculateEMA(difValues, signalPeriod, false);
        // 3. 计算信号线(MACD线的siglen周期EMA),使用SMA作为初始值
        List<BigDecimal> signalLine = EMACalculator.calculateEMA(macdLine, siglen, true);
        // 5. 构建并填充结果(包含所有MACD数据)
        List<PriceData> result = new ArrayList<>(deaValues.size());
        // 4. 计算柱状图(MACD线与信号线的差值)
        List<BigDecimal> histogram = new ArrayList<>();
        int signalLineStartIdx = siglen - 1; // signalLine中第一个有效值对应的macdLine索引
        for (int i = 0; i < macdLine.size(); i++) {
            if (i < slowEmaStartIdx + signalLineStartIdx) {
                // 在信号线开始有效之前,柱状图值为0
                histogram.add(BigDecimal.ZERO);
            } else {
                // 将macdLine索引转换为signalLine索引
                int signalLineIdx = i - signalLineStartIdx;
                // 柱状图 = (MACD线 - 信号线) * 2(放大信号)
                BigDecimal histValue = macdLine.get(i).subtract(signalLine.get(signalLineIdx)).multiply(new BigDecimal("2"));
                histogram.add(histValue);
            }
        }
        // 从第一个DEA值开始构建结果
        for (int i = 0; i < deaValues.size(); i++) {
            int closeIdx = startIdx + i; // 对应原收盘价列表的索引
            // 创建价格数据对象
            PriceData data = new PriceData(closePrices.get(closeIdx));
            // 设置EMA(使用正确的偏移位置)
            data.setEmaShort(emaShort.get(closeIdx - shortPeriod + 1));
            data.setEmaLong(emaLong.get(closeIdx - longPeriod + 1));
            // 设置DIF、DEA和MACD柱状图
            data.setDif(difValues.get(i));
            data.setDea(deaValues.get(i)); // DEA索引直接对应
            data.setMacdHist(data.getDif().subtract(data.getDea())); // MACD柱状图 = DIF - DEA
        // 5. 构建结果数据
        List<PriceData> result = new ArrayList<>();
        int startIndex = slowEmaStartIdx + signalLineStartIdx; // 从信号线开始有效的位置开始
        for (int i = startIndex; i < prices.size(); i++) {
            PriceData data = new PriceData(prices.get(i));
            // 设置EMA值(需要转换索引)
            int fastEmaIdx = i - fastEmaStartIdx;
            int slowEmaIdx = i - slowEmaStartIdx;
            data.setEmaShort(fastEma.get(fastEmaIdx));
            data.setEmaLong(slowEma.get(slowEmaIdx));
            // 设置MACD指标值
            data.setDif(macdLine.get(i));
            data.setDea(signalLine.get(i - signalLineStartIdx));
            data.setMacdHist(histogram.get(i));
            result.add(data);
        }
        // 反转结果列表,恢复为从新到旧的顺序
        Collections.reverse(result);
        return new MACDResult(result, startIdx);
        return new MACDResult(result, startIndex);
    }
    /**
@@ -99,7 +127,7 @@
     * @return 包含MACD各部分数据的PriceData列表
     */
    public static MACDResult calculateMACD(List<BigDecimal> closePrices) {
        // 默认参数:短期周期12,长期周期26,信号周期9
        // 默认参数:快速周期12,慢速周期26,信号周期9
        return calculateMACD(closePrices, 12, 26, 9);
    }
@@ -122,14 +150,18 @@
            return false;
        }
        
        // 反转原始价格列表,确保从旧到新处理
        List<BigDecimal> prices = new ArrayList<>(closePrices);
        Collections.reverse(prices);
        // 找到最近的价格高点和对应的DIF值
        int recentPriceHighIdx = IndicatorUtils.findRecentHighIndex(closePrices, startIdx);
        int recentPriceHighIdx = IndicatorUtils.findRecentHighIndex(prices, startIdx);
        if (recentPriceHighIdx < startIdx + 2 || recentPriceHighIdx == -1) {
            return false;
        }
        
        // 找到之前的价格高点和对应的DIF值
        int previousPriceHighIdx = IndicatorUtils.findPreviousHighIndex(closePrices, startIdx, recentPriceHighIdx);
        int previousPriceHighIdx = IndicatorUtils.findPreviousHighIndex(prices, startIdx, recentPriceHighIdx);
        if (previousPriceHighIdx < startIdx || previousPriceHighIdx == -1) {
            return false;
        }
@@ -143,8 +175,8 @@
            return false;
        }
        
        BigDecimal recentPrice = closePrices.get(recentPriceHighIdx);
        BigDecimal previousPrice = closePrices.get(previousPriceHighIdx);
        BigDecimal recentPrice = prices.get(recentPriceHighIdx);
        BigDecimal previousPrice = prices.get(previousPriceHighIdx);
        BigDecimal recentDif = macdData.get(recentDifIdx).getDif();
        BigDecimal previousDif = macdData.get(previousDifIdx).getDif();
        
@@ -172,14 +204,18 @@
            return false;
        }
        
        // 反转原始价格列表,确保从旧到新处理
        List<BigDecimal> prices = new ArrayList<>(closePrices);
        Collections.reverse(prices);
        // 找到最近的价格低点和对应的DIF值
        int recentPriceLowIdx = IndicatorUtils.findRecentLowIndex(closePrices, startIdx);
        int recentPriceLowIdx = IndicatorUtils.findRecentLowIndex(prices, startIdx);
        if (recentPriceLowIdx < startIdx + 2 || recentPriceLowIdx == -1) {
            return false;
        }
        
        // 找到之前的价格低点和对应的DIF值
        int previousPriceLowIdx = IndicatorUtils.findPreviousLowIndex(closePrices, startIdx, recentPriceLowIdx);
        int previousPriceLowIdx = IndicatorUtils.findPreviousLowIndex(prices, startIdx, recentPriceLowIdx);
        if (previousPriceLowIdx < startIdx || previousPriceLowIdx == -1) {
            return false;
        }
@@ -193,8 +229,8 @@
            return false;
        }
        
        BigDecimal recentPrice = closePrices.get(recentPriceLowIdx);
        BigDecimal previousPrice = closePrices.get(previousPriceLowIdx);
        BigDecimal recentPrice = prices.get(recentPriceLowIdx);
        BigDecimal previousPrice = prices.get(previousPriceLowIdx);
        BigDecimal recentDif = macdData.get(recentDifIdx).getDif();
        BigDecimal previousDif = macdData.get(previousDifIdx).getDif();
        
@@ -202,4 +238,4 @@
        return recentPrice.compareTo(previousPrice) < 0 && 
               recentDif.compareTo(previousDif) > 0;
    }
}
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java
@@ -1,161 +1,56 @@
/**
 * MACD和MA组合交易策略实现类
 * 基于15分钟K线数据生成交易信号并确定持仓方向
 * 基于多时间粒度K线数据生成交易信号并确定持仓方向
 *
 * 该策略综合考虑了EMA指标、MACD指标、价格突破信号和波动率因素,
 * 形成了一套完整的开仓、平仓和持仓管理机制。
 * 支持1分钟(K线)和日线(K线)级别的数据输入,数据顺序要求从新到旧排列。
 */
package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
 * MACD和MA组合交易策略实现
 * <p>
 * 该策略利用EMA交叉、MACD指标、价格突破信号和波动率过滤,
 * 为15分钟K线级别交易提供综合决策支持。
 * 为多时间粒度K线级别交易提供综合决策支持。
 * <p>
 * 数据输入要求:
 * - historicalPrices1M:1分钟K线收盘价列表,顺序从新到旧
 * - historicalPrices1D:日线K线收盘价列表,顺序从新到旧
 */
@Slf4j
public class MacdMaStrategy {
    /** 持仓状态枚举 */
    /** 操作类型枚举 */
    public enum OperationType {
        /** 开仓平仓 */
        /** 开仓 */
        open,
        /** 平仓 */
        close
    }
    /** 持仓状态枚举 */
    public enum PositionType {
        /** 多头持仓 */
        /** 多头开仓 */
        LONG_BUY,
        /** 多头平仓 */
        LONG_SELL,
        /** 空头持仓 */
        /** 空头开仓 */
        SHORT_SELL,
        /** 空头平仓 */
        SHORT_BUY,
        /** 空仓 */
        NONE
    }
    // 策略参数
    private int shortPeriod;      // 短期EMA周期
    private int longPeriod;       // 长期EMA周期
    private int signalPeriod;     // MACD信号线周期
    private int volatilityPeriod; // 波动率计算周期
    private BigDecimal stopLossRatio;   // 止损比例
    private BigDecimal takeProfitRatio; // 止盈比例
    /**
     * 默认构造函数,使用标准MACD参数
     * 短期周期=12, 长期周期=26, 信号线周期=9, 波动率周期=20
     * 止损比例=1%, 止盈比例=2%
     */
    public MacdMaStrategy() {
        this(12, 26, 9, 20, new BigDecimal("0.01"), new BigDecimal("0.02"));
    }
    /**
     * 自定义参数构造函数
     *
     * @param shortPeriod 短期EMA周期
     * @param longPeriod 长期EMA周期
     * @param signalPeriod MACD信号线周期
     * @param volatilityPeriod 波动率计算周期
     * @param stopLossRatio 止损比例
     * @param takeProfitRatio 止盈比例
     */
    public MacdMaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod,
                          BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
        this.shortPeriod = shortPeriod;
        this.longPeriod = longPeriod;
        this.signalPeriod = signalPeriod;
        this.volatilityPeriod = volatilityPeriod;
        this.stopLossRatio = stopLossRatio;
        this.takeProfitRatio = takeProfitRatio;
    }
    /**
     * 分析最新价格数据并生成交易信号
     *
     * @param closePrices 收盘价序列
     * @return 生成的交易信号(LONG、SHORT或NONE)
     */
    public PositionType analyzeOpen(List<BigDecimal> closePrices) {
        // 数据检查:确保有足够的数据点进行计算
        if (closePrices == null || closePrices.size() < 34) {
            return PositionType.NONE; // 数据不足,无法生成信号
        }
        // 1. 计算MACD指标
        MACDResult macdResult = MACDCalculator.calculateMACD(
                closePrices, shortPeriod, longPeriod, signalPeriod);
         log.info( "MACD计算结果:{}", macdResult.getMacdData().get( macdResult.getMacdData().size() -1));
        // 最新收盘价
        BigDecimal latestPrice = closePrices.get(closePrices.size() - 1);
        // 3. 检查开平仓条件
        // 多头开仓条件检查
        if (isLongEntryCondition(macdResult, closePrices)) {
            // 执行开多
            log.info( "多头开仓信号,价格:{}", latestPrice);
            return PositionType.LONG_BUY;
        }
        // 空头开仓条件检查
        if (isShortEntryCondition(macdResult, closePrices)) {
            // 执行开空
             log.info( "空头开仓信号,价格:{}", latestPrice);
            return PositionType.SHORT_SELL;
        }
        // 无信号
        return PositionType.NONE;
    }
    /**
     * 分析最新价格数据并生成交易信号
     *
     * @param closePrices 收盘价序列
     * @return 生成的交易信号(LONG、SHORT或NONE)
     */
    public PositionType analyzeClose(List<BigDecimal> closePrices) {
        // 数据检查:确保有足够的数据点进行计算
        if (closePrices == null || closePrices.size() < 34) {
            return PositionType.NONE; // 数据不足,无法生成信号
        }
        // 1. 计算MACD指标
        MACDResult macdResult = MACDCalculator.calculateMACD(
                closePrices, shortPeriod, longPeriod, signalPeriod);
        // 最新收盘价
        BigDecimal latestPrice = closePrices.get(closePrices.size() - 1);
        if (isLongExitCondition(macdResult, latestPrice)) {
            // 执行平多
            log.info( "多头平仓信号,价格:{}", latestPrice);
            return PositionType.LONG_SELL;
        }
        if (isShortExitCondition(macdResult, latestPrice)) {
            // 执行平空
            log.info( "空头平仓信号,价格:{}", latestPrice);
            return PositionType.SHORT_BUY;
        }
        // 无信号
        return PositionType.NONE;
    }
    /**
     * 交易指令类,封装side和posSide的组合
     */
    /** 交易指令类,封装side和posSide的组合 */
    public static class TradingOrder {
        private String side;    // buy或sell
        private String posSide; // long或short
@@ -179,93 +74,264 @@
        }
    }
    // 策略参数
    private int shortPeriod;      // 短期EMA周期
    private int longPeriod;       // 长期EMA周期
    private int signalPeriod;     // MACD信号线周期
    private int volatilityPeriod; // 波动率计算周期
    private int trendPeriod = 200; // 趋势过滤EMA周期(200日)
    private BigDecimal stopLossRatio;   // 止损比例
    private BigDecimal takeProfitRatio; // 止盈比例
    /**
     * 默认构造函数,使用标准MACD参数
     * 短期周期=12, 长期周期=26, 信号线周期=9, 波动率周期=20
     * 止损比例=1%, 止盈比例=2%
     */
    public MacdMaStrategy() {
        this(12, 26, 9, 20, new BigDecimal("0.01"), new BigDecimal("0.02"));
    }
    /**
     * 自定义参数构造函数,使用默认趋势周期200
     *
     * @param shortPeriod 短期EMA周期
     * @param longPeriod 长期EMA周期
     * @param signalPeriod MACD信号线周期
     * @param volatilityPeriod 波动率计算周期
     * @param stopLossRatio 止损比例
     * @param takeProfitRatio 止盈比例
     */
    public MacdMaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod,
                          BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
        this(shortPeriod, longPeriod, signalPeriod, volatilityPeriod, 200, stopLossRatio, takeProfitRatio);
    }
    /**
     * 自定义参数构造函数
     *
     * @param shortPeriod 短期EMA周期
     * @param longPeriod 长期EMA周期
     * @param signalPeriod MACD信号线周期
     * @param volatilityPeriod 波动率计算周期
     * @param trendPeriod 趋势过滤EMA周期(200日)
     * @param stopLossRatio 止损比例
     * @param takeProfitRatio 止盈比例
     */
    public MacdMaStrategy(int shortPeriod, int longPeriod, int signalPeriod, int volatilityPeriod, int trendPeriod,
                          BigDecimal stopLossRatio, BigDecimal takeProfitRatio) {
        this.shortPeriod = shortPeriod;
        this.longPeriod = longPeriod;
        this.signalPeriod = signalPeriod;
        this.volatilityPeriod = volatilityPeriod;
        this.trendPeriod = trendPeriod;
        this.stopLossRatio = stopLossRatio;
        this.takeProfitRatio = takeProfitRatio;
    }
    // 主流程方法
    /**
     * 分析历史价格数据并生成交易指令
     *
     * @param historicalPrices 历史价格序列
     * @param historicalPrices 历史价格序列(1分钟K线收盘价),顺序从新到旧
     * @param historical1DayPrices 日线历史价格序列,顺序从新到旧
     * @param operation 操作类型(open/close)
     * @return 交易指令(包含side和posSide),如果没有交易信号则返回null
     */
    public TradingOrder generateTradingOrder(List<BigDecimal> historicalPrices,String operation) {
        PositionType signal =  null;
    public TradingOrder generateTradingOrder(List<BigDecimal> historicalPrices, List<BigDecimal> historical1DayPrices, String operation) {
        PositionType signal = null;
        if ( operation == OperationType.open.name()){
            signal = analyzeOpen(historicalPrices);
        }else  if ( operation == OperationType.close.name()){
            signal = analyzeClose(historicalPrices);
        if (OperationType.open.name().equals(operation)) {
            signal = analyzeOpen(historicalPrices, historical1DayPrices);
        } else if (OperationType.close.name().equals(operation)) {
            signal = analyzeClose(historicalPrices, historical1DayPrices);
        }
        // 根据信号和当前持仓状态生成交易指令
        if (signal == PositionType.LONG_BUY) {
            // 开多:买入开多(side 填写 buy; posSide 填写 long )
            return new TradingOrder("buy", "long");
        } else if (signal == PositionType.LONG_SELL) {
            // 开空:卖出开空(side 填写 sell; posSide 填写 short )
            return new TradingOrder("sell", "long");
        } else if (signal == PositionType.SHORT_SELL) {
            // 开空:卖出开空(side 填写 sell; posSide 填写 short )
            return new TradingOrder("sell", "short");
        } else if (signal == PositionType.SHORT_BUY) {
            // 开空:卖出开空(side 填写 sell; posSide 填写 short )
            return new TradingOrder("buy", "short");
        }
        // 没有交易信号
        return null;
        // 根据信号生成交易指令
        return convertSignalToTradingOrder(signal);
    }
    /**
     * 分析最新价格数据并生成开仓信号
     *
     * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
     * @param close1DPrices 日线K线收盘价序列,顺序从新到旧
     * @return 生成的交易信号(LONG_BUY、SHORT_SELL或NONE)
     */
    public PositionType analyzeOpen(List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
        // 数据检查:确保有足够的数据点进行计算(需要足够数据计算200日EMA)
        if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod) ||
            close1DPrices == null || close1DPrices.size() < Math.max(34, trendPeriod)) {
            return PositionType.NONE; // 数据不足,无法生成信号
        }
        // 计算MACD指标
        MACDResult macdResult = MACDCalculator.calculateMACD(
                closePrices, shortPeriod, longPeriod, signalPeriod);
         log.info("MACD计算结果:{}", macdResult.getMacdData().get(0));
        // 多头开仓条件检查
        if (isLongEntryCondition(macdResult, closePrices, close1DPrices)) {
            log.info("多头开仓信号,价格:{}", closePrices.get(0));
            return PositionType.LONG_BUY;
        }
        // 空头开仓条件检查
        if (isShortEntryCondition(macdResult, closePrices, close1DPrices)) {
            log.info("空头开仓信号,价格:{}", closePrices.get(0));
            return PositionType.SHORT_SELL;
        }
        // 无信号
        return PositionType.NONE;
    }
    /**
     * 分析最新价格数据并生成平仓信号
     *
     * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
     * @param close1DPrices 日线K线收盘价序列,顺序从新到旧
     * @return 生成的交易信号(LONG_SELL、SHORT_BUY或NONE)
     */
    public PositionType analyzeClose(List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
        // 数据检查:确保有足够的数据点进行计算
        if (closePrices == null || closePrices.size() < Math.max(34, trendPeriod) ||
            close1DPrices == null || close1DPrices.size() < Math.max(34, trendPeriod)) {
            return PositionType.NONE; // 数据不足,无法生成信号
        }
        // 计算MACD指标
        MACDResult macdResult = MACDCalculator.calculateMACD(
                closePrices, shortPeriod, longPeriod, signalPeriod);
        // 最新收盘价
        BigDecimal latestPrice = closePrices.get(0);
        if (isLongExitCondition(macdResult, latestPrice)) {
            log.info("多头平仓信号,价格:{}", latestPrice);
            return PositionType.LONG_SELL;
        }
        if (isShortExitCondition(macdResult, latestPrice)) {
            log.info("空头平仓信号,价格:{}", latestPrice);
            return PositionType.SHORT_BUY;
        }
        // 无信号
        return PositionType.NONE;
    }
    // 信号转换方法
    /**
     * 将持仓信号转换为交易指令
     *
     * @param signal 持仓信号
     * @return 交易指令,无信号则返回null
     */
    private TradingOrder convertSignalToTradingOrder(PositionType signal) {
        if (signal == null) {
            return null;
        }
        switch (signal) {
            case LONG_BUY:
                // 开多:买入开多(side 填写 buy; posSide 填写 long )
                return new TradingOrder("buy", "long");
            case LONG_SELL:
                // 平多:卖出平多(side 填写 sell; posSide 填写 long )
                return new TradingOrder("sell", "long");
            case SHORT_SELL:
                // 开空:卖出开空(side 填写 sell; posSide 填写 short )
                return new TradingOrder("sell", "short");
            case SHORT_BUY:
                // 平空:买入平空(side 填写 buy; posSide 填写 short )
                return new TradingOrder("buy", "short");
            default:
                // 无信号
                return null;
        }
    }
    // 开仓条件检查方法
    /**
     * 多头开仓条件检查
     *
     * @param macdResult MACD计算结果
     * @param closePrices 收盘价序列
     * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
     * @param close1DPrices 日线K线收盘价序列,顺序从新到旧
     * @return 是否满足多头开仓条件
     */
    private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices) {
        // 2. MACD金叉且柱状线扩张检查
        boolean isMacdFavorable = isMacdGoldenCrossAndExpanding(macdResult);
    private boolean isLongEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
        // 1. 计算200日EMA(趋势过滤)
        // 复制并反转日线数据,确保从旧到新计算EMA
        List<BigDecimal> reversed1DPrices = new ArrayList<>(close1DPrices);
        Collections.reverse(reversed1DPrices);
        List<BigDecimal> trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true);
        BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1);
        BigDecimal latestPrice = closePrices.get(0);
        log.info( "200日EMA:{}, 最新价格:{}", latestTrendEma,  latestPrice);
        
        // 3. MACD柱状线必须为正
        boolean macdPositive = isMacdPositive(macdResult);
        // 2. 价格必须位于200日EMA上方(多头趋势确认)
        boolean isAboveTrend = latestPrice.compareTo(latestTrendEma) > 0;
        // 3. MACD金叉检查
        boolean isGoldenCross = isGoldenCross(macdResult);
        // 4. MACD柱状线由负转正(动量转变)
        boolean isMacdHistTurningPositive = isMacdHistTurningPositive(macdResult);
        
        // 5. 底背离检查(增强多头信号可靠性)
        boolean isBottomDivergence = MACDCalculator.isBottomDivergence(closePrices, macdResult);
        log.info("多头信号形成, MACD有利状态: {}, 柱状线为正: {}, 底背离: {}",
                isMacdFavorable, macdPositive, isBottomDivergence);
        log.info("多头信号检查, 价格位于200日EMA上方: {}, 金叉: {}, MACD柱状线由负转正: {}, 底背离: {}",
                isAboveTrend, isGoldenCross, isMacdHistTurningPositive, isBottomDivergence);
        
        // 所有条件必须同时满足
        boolean result = macdPositive && (isMacdFavorable || isBottomDivergence);
        return result;
        // 多头开仓条件:趋势向上 + 金叉 + (柱状线转强或底背离)
        return isAboveTrend && isGoldenCross && (isMacdHistTurningPositive || isBottomDivergence);
    }
    /**
     * 空头开仓条件检查
     * 
     * @param macdResult MACD计算结果
     * @param closePrices 收盘价序列
     * @param closePrices 1分钟K线收盘价序列,顺序从新到旧
     * @param close1DPrices 日线K线收盘价序列,顺序从新到旧
     * @return 是否满足空头开仓条件
     */
    private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices) {
        // 2. MACD死叉且柱状线收缩检查
        boolean isMacdFavorable = isMacdDeathCrossAndContracting(macdResult);
    private boolean isShortEntryCondition(MACDResult macdResult, List<BigDecimal> closePrices, List<BigDecimal> close1DPrices) {
        // 1. 计算200日EMA(趋势过滤)
        // 复制并反转日线数据,确保从旧到新计算EMA
        List<BigDecimal> reversed1DPrices = new ArrayList<>(close1DPrices);
        Collections.reverse(reversed1DPrices);
        List<BigDecimal> trendEma = EMACalculator.calculateEMA(reversed1DPrices, trendPeriod, true);
        BigDecimal latestTrendEma = trendEma.get(trendEma.size() - 1);
        BigDecimal latestPrice = closePrices.get(0);
        log.info( "200日EMA:{}, 最新价格:{}", latestTrendEma,  latestPrice);
        
        // 3. MACD柱状线必须为负
        boolean macdNegative = isMacdNegative(macdResult);
        // 2. 价格必须位于200日EMA下方(空头趋势确认)
        boolean isBelowTrend = latestPrice.compareTo(latestTrendEma) < 0;
        // 3. MACD死叉检查
        boolean isDeathCross = isDeathCross(macdResult);
        // 4. MACD柱状线由正转负(动量转变)
        boolean isMacdHistTurningNegative = isMacdHistTurningNegative(macdResult);
        
        // 5. 顶背离检查(增强空头信号可靠性)
        boolean isTopDivergence = MACDCalculator.isTopDivergence(closePrices, macdResult);
        log.info("空头信号形成, MACD有利状态: {}, 柱状线为负: {}, 顶背离: {}",
                isMacdFavorable, macdNegative, isTopDivergence);
        // 所有条件必须同时满足
        boolean result = macdNegative && (isMacdFavorable || isTopDivergence);
        log.info("空头信号检查, 价格位于200日EMA下方: {}, 死叉: {}, MACD柱状线由正转负: {}, 顶背离: {}",
                isBelowTrend, isDeathCross, isMacdHistTurningNegative, isTopDivergence);
        
        return result;
        // 空头开仓条件:趋势向下 + 死叉 + (柱状线转弱或顶背离)
        return isBelowTrend && isDeathCross && (isMacdHistTurningNegative || isTopDivergence);
    }
    // 平仓条件检查方法
    
    /**
     * 多头平仓条件检查
@@ -275,17 +341,17 @@
     * @return 是否满足多头平仓条件
     */
    private boolean isLongExitCondition(MACDResult macdResult, BigDecimal currentPrice) {
        // 1. MACD柱状线由正转负(动量转变)
        // 多头平仓条件:MACD柱状线动量减弱(由正转弱)
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() >= 2) {
            PriceData latest = macdData.get(macdData.size() - 1);
            PriceData previous = macdData.get(macdData.size() - 2);
            PriceData latest = macdData.get(0); // 最新数据
            PriceData previous = macdData.get(1); // 前一个数据
            boolean momentumShift = previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 &&
                                   latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
            if (momentumShift) {
                return true;
            }
            // 柱状线由正转弱:前一根为正,当前绝对值减小
            boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 &&
                                      latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
            return momentumWeakening;
        }
        return false;
    }
@@ -298,170 +364,110 @@
     * @return 是否满足空头平仓条件
     */
    private boolean isShortExitCondition(MACDResult macdResult, BigDecimal currentPrice) {
        // 1. MACD柱状线由负转正(动量转变)
        // 空头平仓条件:MACD柱状线动量减弱(由负转弱)
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() >= 2) {
            PriceData latest = macdData.get(macdData.size() - 1);
            PriceData previous = macdData.get(macdData.size() - 2);
            PriceData latest = macdData.get(0); // 最新数据
            PriceData previous = macdData.get(1); // 前一个数据
            
            boolean momentumShift = previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 &&
                                latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
            if (momentumShift) {
                return true;
            }
            // 柱状线由负转弱:前一根为负,当前绝对值减小
            boolean momentumWeakening = previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 &&
                                      latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) < 0;
            return momentumWeakening;
        }
        
        return false;
    }
    // MACD信号辅助方法
    
    /**
     * MACD金叉且柱状线扩张检查
     * 简单金叉判断
     * <p>
     * 条件:
     * 1. DIF线从下往上穿过DEA线(金叉)
     * 2. MACD柱状线绝对值增大且为正值(动量增强)
     * 条件:DIF线从下往上穿过DEA线
     * 
     * @param macdResult MACD计算结果
     * @return 是否形成MACD金叉或柱状线扩张
     * @return 是否形成金叉
     */
    private boolean isMacdGoldenCrossAndExpanding(MACDResult macdResult) {
    private boolean isGoldenCross(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 3) {
        if (macdData.size() < 2) {
            return false;
        }
        PriceData latest = macdData.get(macdData.size() - 1);
        PriceData previous = macdData.get(macdData.size() - 2);
        PriceData prevPrev = macdData.get(macdData.size() - 3);
        PriceData latest = macdData.get(0);
        PriceData previous = macdData.get(1);
        
        // 金叉判断:DIF从下往上穿过DEA
        boolean isGoldenCross = prevPrev.getDif().compareTo(prevPrev.getDea()) <= 0 &&
                              previous.getDif().compareTo(previous.getDea()) > 0;
        // 柱状线扩张判断:连续正值且绝对值增大
        boolean isExpanding = latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0 &&
                            previous.getMacdHist().compareTo(BigDecimal.ZERO) > 0 &&
                            previous.getMacdHist().abs().compareTo(latest.getMacdHist().abs()) < 0;
        // 金叉柱状线扩张满足
        return isGoldenCross && isExpanding;
        return previous.getDif().compareTo(previous.getDea()) < 0 &&
               latest.getDif().compareTo(latest.getDea()) > 0;
    }
    
    /**
     * MACD柱状线扩张检查
     * 简单死叉判断
     * <p>
     * 条件:当前MACD柱状线绝对值大于前一根
     * 条件:DIF线从上往下穿过DEA线
     * 
     * @param macdResult MACD计算结果
     * @return MACD柱状线是否扩张
     * @return 是否形成死叉
     */
    private boolean isExpanding(MACDResult macdResult) {
    private boolean isDeathCross(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
        PriceData latest = macdData.get(macdData.size() - 1);
        PriceData previous = macdData.get(macdData.size() - 2);
        // MACD柱状线扩张:当前绝对值大于前一根
        return latest.getMacdHist().abs().compareTo(previous.getMacdHist().abs()) > 0;
    }
    /**
     * MACD死叉且柱状线扩张检查
     * <p>
     * 条件:
     * 1. DIF线从上往下穿过DEA线(死叉)
     * 2. MACD柱状线绝对值增大且为负值(动量增强)
     *
     * @param macdResult MACD计算结果
     * @return 是否形成MACD死叉且柱状线扩张
     */
    private boolean isMacdDeathCrossAndContracting(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 3) {
            return false;
        }
        PriceData latest = macdData.get(macdData.size() - 1);
        PriceData previous = macdData.get(macdData.size() - 2);
        PriceData prevPrev = macdData.get(macdData.size() - 3);
        PriceData latest = macdData.get(0);
        PriceData previous = macdData.get(1);
        
        // 死叉判断:DIF从上往下穿过DEA
        boolean isDeathCross = prevPrev.getDif().compareTo(prevPrev.getDea()) >= 0 &&
                             previous.getDif().compareTo(previous.getDea()) < 0;
        // 优化后的死叉柱状线条件:空头趋势中,死叉应伴随柱状线扩张(绝对值增大)
        boolean isExpanding = latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0 &&
                previous.getMacdHist().compareTo(BigDecimal.ZERO) < 0 &&
                previous.getMacdHist().abs().compareTo(latest.getMacdHist().abs()) < 0;
        // 死叉且柱状线扩张
        return isDeathCross && isExpanding;
        return previous.getDif().compareTo(previous.getDea()) > 0 &&
               latest.getDif().compareTo(latest.getDea()) < 0;
    }
    
    /**
     * MACD柱状线收缩检查
     * MACD柱状线由负转正判断
     * <p>
     * 条件:前一根MACD柱状线绝对值大于当前
     * 条件:前一根柱状线为负,当前柱状线为正
     * 
     * @param macdResult MACD计算结果
     * @return MACD柱状线是否收缩
     * @return 是否由负转正
     */
    private boolean isContracting(MACDResult macdResult) {
    private boolean isMacdHistTurningPositive(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.size() < 2) {
            return false;
        }
        PriceData latest = macdData.get(macdData.size() - 1);
        PriceData previous = macdData.get(macdData.size() - 2);
        PriceData latest = macdData.get(0); // 最新数据
        PriceData previous = macdData.get(1); // 前一个数据
        
        // MACD柱状线收缩:前一根绝对值大于当前
        return previous.getMacdHist().abs().compareTo(latest.getMacdHist().abs()) > 0;
        // 柱状线由负转正:前一根为负,当前为正
        return previous.getMacdHist().compareTo(BigDecimal.ZERO) <= 0 &&
               latest.getMacdHist().compareTo(BigDecimal.ZERO) > 0;
    }
    
    /**
     * 检查MACD柱状线是否为正值
     * MACD柱状线由正转负判断
     * <p>
     * 条件:前一根柱状线为正,当前柱状线为负
     * 
     * @param macdResult MACD计算结果
     * @return MACD柱状线是否为正值
     * @return 是否由正转负
     */
    private boolean isMacdPositive(MACDResult macdResult) {
    private boolean isMacdHistTurningNegative(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.isEmpty()) {
        if (macdData.size() < 2) {
            return false;
        }
        return macdData.get(macdData.size() - 1).getMacdHist().compareTo(BigDecimal.ZERO) > 0;
    }
    /**
     * 检查MACD柱状线是否为负值
     *
     * @param macdResult MACD计算结果
     * @return MACD柱状线是否为负值
     */
    private boolean isMacdNegative(MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        if (macdData.isEmpty()) {
            return false;
        }
        return macdData.get(macdData.size() - 1).getMacdHist().compareTo(BigDecimal.ZERO) < 0;
    }
    /**
     * 波动率过滤检查
     *
     * @param volatility 当前波动率
     * @return 波动率是否在0.5%~5%范围内
     */
    private boolean isVolatilityInRange(BigDecimal volatility) {
        BigDecimal minVolatility = new BigDecimal("0.1"); // 降低最小波动率阈值
        BigDecimal maxVolatility = new BigDecimal("5.0");
        return volatility.compareTo(minVolatility) >= 0 &&
                volatility.compareTo(maxVolatility) <= 0;
        PriceData latest = macdData.get(0); // 最新数据
        PriceData previous = macdData.get(1); // 前一个数据
        // 柱状线由正转负:前一根为正,当前为负
        return previous.getMacdHist().compareTo(BigDecimal.ZERO) >= 0 &&
               latest.getMacdHist().compareTo(BigDecimal.ZERO) < 0;
    }
}
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/OrderInfoWs.java
@@ -107,7 +107,7 @@
                String stateStr = TradeOrderWs.getAccountMap(accountName).get("state");
                if (StrUtil.isNotBlank(stateStr) && state.equals(stateStr)){
                    // 使用账号特定的Map
                    String positionAccountName = PositionsWs.initAccountName(accountName, side);
                    String positionAccountName = PositionsWs.initAccountName(accountName, posSide);
                    Map<String, BigDecimal> positionsMap = PositionsWs.getAccountMap(positionAccountName);
                    WsMapBuild.saveBigDecimalToMap(positionsMap, CoinEnums.READY_STATE.name(), WsMapBuild.parseBigDecimalSafe(CoinEnums.READY_STATE_NO.getCode()));
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxWs/TradeOrderWs.java
@@ -1,6 +1,7 @@
package com.xcong.excoin.modules.okxNewPrice.okxWs;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums;
@@ -36,7 +37,7 @@
    public static void orderEvent(WebSocketClient webSocketClient, TradeRequestParam tradeRequestParam) {
        log.info("开始执行TradeOrderWs......");
        log.info("开始执行TradeOrderWs{}......", JSONUtil.parse(tradeRequestParam));
        String accountName = tradeRequestParam.getAccountName();
        String markPx = tradeRequestParam.getMarkPx();
        String instId = tradeRequestParam.getInstId();
@@ -49,8 +50,8 @@
        String clOrdId = tradeRequestParam.getClOrdId();
        String side = tradeRequestParam.getSide();
        String sz = tradeRequestParam.getSz();
        log.info("账户:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},",
                accountName, markPx, instId, posSide,side,  sz, tradeType, clOrdId);
        log.info("账户:{},类型:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},",
                accountName,ordType, markPx, instId, posSide,side,  sz, tradeType, clOrdId);
        //验证是否允许下单
        if (StrUtil.isNotEmpty(tradeType) && OrderParamEnums.TRADE_NO.getValue().equals(tradeType)) {
            log.warn("账户{}不允许下单,取消发送", accountName);
@@ -175,8 +176,8 @@
            log.warn("下单参数缺失,取消发送");
            return;
        }
        log.info("账户:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},",
                accountName, markPx, instId, posSide,side,  sz, tradeType, clOrdId);
        log.info("账户:{},类型:{},触发价格:{},币种:{},方向:{},买卖:{},数量:{},是否允许下单:{},编号:{},",
                accountName,ordType, markPx, instId, posSide,side,  sz, tradeType, clOrdId);
        //验证是否允许下单
        if (StrUtil.isNotEmpty(tradeType) && OrderParamEnums.TRADE_NO.getValue().equals(tradeType)) {
            log.warn("账户{}不允许下单,取消发送", accountName);
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/ExchangeInfoEnum.java
@@ -13,10 +13,14 @@
     * 模拟盘账户信息
     * 存储了模拟盘交易所需的API密钥、秘钥和通过码
     */
    OKX_UAT("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61",
            "AA06C5ED1D7C7F5AFE6484052E231C55",
    OKX_UAT("f512673b-2685-4fcb-9bb1-2ae8db745d62",
            "B0C1CC8F39625B41140D93DC25039E33",
            "Aa12345678@",
            false);
            true);
//    OKX_UAT("ffb4e79f-fcf5-4afb-82c5-2fbb64123f61",
//            "AA06C5ED1D7C7F5AFE6484052E231C55",
//            "Aa12345678@",
//            false);
//    /**
//     * 模拟盘账户信息
src/main/java/com/xcong/excoin/modules/okxNewPrice/okxpi/config/enums/DefaultUrls.java
@@ -5,7 +5,7 @@
    public static final String USDM_UAT_WSS_URL = "wss://wspap.okx.com:8443";
    //public static final String USDM_UAT_WSS_URL = "wss://ws.okx.com:8443";
    //USD-M Futures
    public static final String USDM_PROD_URL = "https://aws.okx.com";
    public static final String USDM_PROD_URL = "https://www.okx.com";
    public static final String USDM_PROD_WS_URL = "wss://ws.okx.com:8443";
    //比特币买入数量
    public static final String BTC_BUYNUMBER = "0.001";