package com.xcong.excoin.modules.okxNewPrice.indicator; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * MACD (Moving Average Convergence Divergence) 指标实现 * 计算逻辑: * 1. DIF = EMA(12) - EMA(26) * 2. DEA = EMA(DIF, 9) * 3. MACD柱状图 = (DIF - DEA) * 2 * * 作用: * 1. 识别趋势方向和动能变化 * 2. DIF上穿DEA形成金叉,提示买入信号 * 3. DIF下穿DEA形成死叉,提示卖出信号 * 4. MACD柱状图由负转正,提示多头力量增强 * 5. MACD柱状图由正转负,提示空头力量增强 * * 价格参数类型: * - 参数名称:prices * - 参数类型:List * - 参数说明:需要至少2个价格数据点用于计算,数据越多计算越准确 * * 推荐时间粒度及优缺点: * 1. 1分钟(1m): * - 优点:反应迅速,适合超短线交易 * - 缺点:MACD柱状图波动剧烈,信号频繁 * 2. 5分钟(5m): * - 优点:平衡了反应速度和信号可靠性 * - 缺点:仍有一定噪音 * 3. 15分钟(15m): * - 优点:适合日内交易,信号较为可靠 * - 缺点:反应速度较慢 * 4. 1小时(1h)及以上: * - 优点:趋势信号明确,MACD柱状图变化稳定 * - 缺点:反应滞后,不适合短线交易 */ @Slf4j @Getter @Setter public class MACD extends IndicatorBase { // 默认周期参数 public static final int DEFAULT_FAST_PERIOD = 12; public static final int DEFAULT_SLOW_PERIOD = 26; public static final int DEFAULT_SIGNAL_PERIOD = 9; // 动态周期参数 private int fastPeriod; private int slowPeriod; private int signalPeriod; private BigDecimal dif = BigDecimal.ZERO; private BigDecimal dea = BigDecimal.ZERO; private BigDecimal macdBar = BigDecimal.ZERO; private BigDecimal prevFastEMA = null; private BigDecimal prevSlowEMA = null; private BigDecimal prevDea = null; // 用于保存历史DIF值,用于计算DEA private List difHistory = new ArrayList<>(); // 最大保存的DIF历史值数量,至少为signalPeriod private static final int MAX_DIF_HISTORY = 30; // 构造函数使用默认周期 public MACD() { this.fastPeriod = DEFAULT_FAST_PERIOD; this.slowPeriod = DEFAULT_SLOW_PERIOD; this.signalPeriod = DEFAULT_SIGNAL_PERIOD; } /** * 计算MACD指标(使用当前周期设置) * @param prices 价格列表 */ public void calculate(List prices) { calculate(prices, null); } /** * 计算MACD指标,并支持动态周期调整 * @param prices 价格列表 * @param volatility 标准化波动率(百分比),用于动态调整周期 */ public void calculate(List prices, BigDecimal volatility) { if (prices == null || prices.size() < 2) { return; } // 如果提供了波动率,则动态调整周期 if (volatility != null) { adjustPeriodsByVolatility(volatility); } // 计算快速EMA prevFastEMA = calculateEMA(prices, fastPeriod, prevFastEMA); // 计算慢速EMA prevSlowEMA = calculateEMA(prices, slowPeriod, prevSlowEMA); // 计算DIF dif = prevFastEMA.subtract(prevSlowEMA).setScale(8, RoundingMode.HALF_UP); // 将新的DIF值添加到历史记录 difHistory.add(dif); // 保持历史记录在合理范围内 if (difHistory.size() > MAX_DIF_HISTORY) { difHistory.remove(0); } // 计算DEA:使用足够数量的历史DIF值 // 如果历史DIF值不足,使用当前可用的所有值 int difCount = difHistory.size(); List deaCalculationList; if (difCount >= signalPeriod) { // 使用最近的signalPeriod个DIF值 deaCalculationList = difHistory.subList(difCount - signalPeriod, difCount); } else if (difCount > 0) { // 使用所有可用的DIF值 deaCalculationList = new ArrayList<>(difHistory); } else { // 没有DIF历史值,使用当前DIF deaCalculationList = Collections.singletonList(dif); } prevDea = calculateEMA(deaCalculationList, signalPeriod, prevDea); dea = prevDea.setScale(8, RoundingMode.HALF_UP); // 计算MACD柱状图 macdBar = dif.subtract(dea).multiply(new BigDecimal(2)).setScale(8, RoundingMode.HALF_UP); log.info("MACD计算结果 - DIF: {}, DEA: {}, MACD柱状图: {}, 参数: fast={}, slow={}, signal={}", dif, dea, macdBar, fastPeriod, slowPeriod, signalPeriod); } /** * 根据波动率调整MACD周期参数 * @param volatility 标准化波动率(百分比) */ private void adjustPeriodsByVolatility(BigDecimal volatility) { // 波动率阈值 BigDecimal volatilityThreshold = new BigDecimal(15); // 根据波动率调整MACD参数 if (volatility.compareTo(volatilityThreshold) < 0) { // 低波动率环境,使用默认参数 fastPeriod = DEFAULT_FAST_PERIOD; slowPeriod = DEFAULT_SLOW_PERIOD; signalPeriod = DEFAULT_SIGNAL_PERIOD; } else { // 高波动率环境,使用更灵敏的参数 fastPeriod = 8; slowPeriod = 17; signalPeriod = 5; } log.info("根据波动率{}调整MACD周期: fast={}, slow={}, signal={}", volatility, fastPeriod, slowPeriod, signalPeriod); } /** * 判断金叉信号(DIF上穿DEA) * @return 是否形成金叉 */ public boolean isGoldenCross(BigDecimal previousDIF, BigDecimal previousDEA) { return previousDIF.compareTo(previousDEA) < 0 && dif.compareTo(dea) > 0; } /** * 判断死叉信号(DIF下穿DEA) * @return 是否形成死叉 */ public boolean isDeathCross(BigDecimal previousDIF, BigDecimal previousDEA) { return previousDIF.compareTo(previousDEA) > 0 && dif.compareTo(dea) < 0; } }