package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * MACD(Moving Average Convergence Divergence)指标计算器 *

* MACD指标由三部分组成: * 1. DIF(Difference):短期EMA与长期EMA的差值 * 2. DEA(Signal Line):DIF的指数移动平均线,作为MACD的信号线 * 3. MACD柱状图(Histogram):DIF与DEA的差值,反映市场动量 *

* 默认参数:短期周期=12,长期周期=26,信号周期=9 */ public class MACDCalculator { /** * 计算MACD指标 * * @param closePrices 收盘价列表(使用BigDecimal确保计算精度) * @param fastlen 短期EMA周期(通常为12) * @param slowlen 长期EMA周期(通常为26) * @param siglen DEA的周期(通常为9) * @return 包含MACD各部分数据的PriceData列表 * @throws IllegalArgumentException 如果数据点不足或参数无效 */ public static MACDResult calculateMACD(List closePrices, int fastlen, int slowlen, int siglen) { // 参数校验:确保数据点足够 if (closePrices == null || closePrices.isEmpty()) { throw new IllegalArgumentException("Close prices list cannot be null or empty."); } if (fastlen <= 0 || slowlen <= 0 || siglen <= 0) { throw new IllegalArgumentException("All periods must be positive integers."); } if (fastlen >= slowlen) { throw new IllegalArgumentException("Fast period must be less than slow period."); } if (closePrices.size() < Math.max(fastlen, slowlen)) { throw new IllegalArgumentException("Insufficient data points for the specified periods."); } // 反转数据,确保从旧到新处理(因为用户提供的数据是从新到旧) List prices = new ArrayList<>(closePrices); Collections.reverse(prices); // 1. 计算快速EMA和慢速EMA,使用SMA作为初始值 // 当initialSMA=true时,EMA列表长度为prices.size() - period + 1 List fastEma = EMACalculator.calculateEMA(prices, fastlen, true); List slowEma = EMACalculator.calculateEMA(prices, slowlen, true); // 2. 计算MACD线(快速EMA减去慢速EMA) List 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); } } // 3. 计算信号线(MACD线的siglen周期EMA),使用SMA作为初始值 List signalLine = EMACalculator.calculateEMA(macdLine, siglen, true); // 4. 计算柱状图(MACD线与信号线的差值) List 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); } } // 5. 构建结果数据 List 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, startIndex); } /** * 使用默认参数计算MACD指标 *

* 默认参数:短期周期=12,长期周期=26,信号周期=9 * * @param closePrices 收盘价列表 * @return 包含MACD各部分数据的PriceData列表 */ public static MACDResult calculateMACD(List closePrices) { // 默认参数:快速周期12,慢速周期26,信号周期9 return calculateMACD(closePrices, 12, 26, 9); } /** * 判断是否出现顶背离 *

* 顶背离:价格创新高,但DIF未创新高,且与价格走势背离 * 增强空头信号可靠性 * * @param closePrices 原始收盘价列表 * @param macdResult MACD计算结果 * @return 是否出现顶背离 */ public static boolean isTopDivergence(List closePrices, MACDResult macdResult) { List macdData = macdResult.getMacdData(); int startIdx = macdResult.getStartIndex(); // 确保有足够的数据点进行判断(至少需要2个高点) if (macdData.size() < 10) { return false; } // 反转原始价格列表,确保从旧到新处理 List prices = new ArrayList<>(closePrices); Collections.reverse(prices); // 找到最近的价格高点和对应的DIF值 int recentPriceHighIdx = IndicatorUtils.findRecentHighIndex(prices, startIdx); if (recentPriceHighIdx < startIdx + 2 || recentPriceHighIdx == -1) { return false; } // 找到之前的价格高点和对应的DIF值 int previousPriceHighIdx = IndicatorUtils.findPreviousHighIndex(prices, startIdx, recentPriceHighIdx); if (previousPriceHighIdx < startIdx || previousPriceHighIdx == -1) { return false; } // 获取对应位置的DIF值 int recentDifIdx = recentPriceHighIdx - startIdx; int previousDifIdx = previousPriceHighIdx - startIdx; // 边界检查 if (recentDifIdx >= macdData.size() || previousDifIdx >= macdData.size()) { return false; } BigDecimal recentPrice = prices.get(recentPriceHighIdx); BigDecimal previousPrice = prices.get(previousPriceHighIdx); BigDecimal recentDif = macdData.get(recentDifIdx).getDif(); BigDecimal previousDif = macdData.get(previousDifIdx).getDif(); // 顶背离条件:价格创新高,但DIF未创新高 return recentPrice.compareTo(previousPrice) > 0 && recentDif.compareTo(previousDif) < 0; } /** * 判断是否出现底背离 *

* 底背离:价格创新低,但DIF未创新低,且与价格走势背离 * 增强多头信号可靠性 * * @param closePrices 原始收盘价列表 * @param macdResult MACD计算结果 * @return 是否出现底背离 */ public static boolean isBottomDivergence(List closePrices, MACDResult macdResult) { List macdData = macdResult.getMacdData(); int startIdx = macdResult.getStartIndex(); // 确保有足够的数据点进行判断(至少需要2个低点) if (macdData.size() < 10) { return false; } // 反转原始价格列表,确保从旧到新处理 List prices = new ArrayList<>(closePrices); Collections.reverse(prices); // 找到最近的价格低点和对应的DIF值 int recentPriceLowIdx = IndicatorUtils.findRecentLowIndex(prices, startIdx); if (recentPriceLowIdx < startIdx + 2 || recentPriceLowIdx == -1) { return false; } // 找到之前的价格低点和对应的DIF值 int previousPriceLowIdx = IndicatorUtils.findPreviousLowIndex(prices, startIdx, recentPriceLowIdx); if (previousPriceLowIdx < startIdx || previousPriceLowIdx == -1) { return false; } // 获取对应位置的DIF值 int recentDifIdx = recentPriceLowIdx - startIdx; int previousDifIdx = previousPriceLowIdx - startIdx; // 边界检查 if (recentDifIdx >= macdData.size() || previousDifIdx >= macdData.size()) { return false; } BigDecimal recentPrice = prices.get(recentPriceLowIdx); BigDecimal previousPrice = prices.get(previousPriceLowIdx); BigDecimal recentDif = macdData.get(recentDifIdx).getDif(); BigDecimal previousDif = macdData.get(previousDifIdx).getDif(); // 底背离条件:价格创新低,但DIF未创新低 return recentPrice.compareTo(previousPrice) < 0 && recentDif.compareTo(previousDif) > 0; } }