| | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | /** |
| | |
| | | * 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<BigDecimal> |
| | | * - 参数说明:需要至少2个价格数据点用于计算,数据越多计算越准确 |
| | | * |
| | | * 推荐时间粒度及优缺点: |
| | | * 1. 1分钟(1m): |
| | | * - 优点:反应迅速,适合超短线交易 |
| | | * - 缺点:MACD柱状图波动剧烈,信号频繁 |
| | | * 2. 5分钟(5m): |
| | | * - 优点:平衡了反应速度和信号可靠性 |
| | | * - 缺点:仍有一定噪音 |
| | | * 3. 15分钟(15m): |
| | | * - 优点:适合日内交易,信号较为可靠 |
| | | * - 缺点:反应速度较慢 |
| | | * 4. 1小时(1h)及以上: |
| | | * - 优点:趋势信号明确,MACD柱状图变化稳定 |
| | | * - 缺点:反应滞后,不适合短线交易 |
| | | */ |
| | | @Slf4j |
| | | @Getter |
| | | @Setter |
| | | public class MACD extends IndicatorBase { |
| | | |
| | | private static final int FAST_PERIOD = 12; |
| | | private static final int SLOW_PERIOD = 26; |
| | | private static final int SIGNAL_PERIOD = 9; |
| | | // 默认周期参数 |
| | | 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 prevFastEMA = null; |
| | | private BigDecimal prevSlowEMA = null; |
| | | private BigDecimal prevDea = null; |
| | | // 用于保存历史DIF值,用于计算DEA |
| | | private List<BigDecimal> 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指标 |
| | | * 计算MACD指标(使用当前周期设置) |
| | | * @param prices 价格列表 |
| | | */ |
| | | public void calculate(List<BigDecimal> prices) { |
| | | calculate(prices, null); |
| | | } |
| | | |
| | | /** |
| | | * 计算MACD指标,并支持动态周期调整 |
| | | * @param prices 价格列表 |
| | | * @param volatility 标准化波动率(百分比),用于动态调整周期 |
| | | */ |
| | | public void calculate(List<BigDecimal> prices, BigDecimal volatility) { |
| | | if (prices == null || prices.size() < 2) { |
| | | return; |
| | | } |
| | | |
| | | // 如果提供了波动率,则动态调整周期 |
| | | if (volatility != null) { |
| | | adjustPeriodsByVolatility(volatility); |
| | | } |
| | | |
| | | // 计算快速EMA |
| | | prevFastEMA = calculateEMA(prices, FAST_PERIOD, prevFastEMA); |
| | | prevFastEMA = calculateEMA(prices, fastPeriod, prevFastEMA); |
| | | |
| | | // 计算慢速EMA |
| | | prevSlowEMA = calculateEMA(prices, SLOW_PERIOD, prevSlowEMA); |
| | | prevSlowEMA = calculateEMA(prices, slowPeriod, prevSlowEMA); |
| | | |
| | | // 计算DIF |
| | | dif = prevFastEMA.subtract(prevSlowEMA).setScale(8, RoundingMode.HALF_UP); |
| | | |
| | | // 计算DEA |
| | | List<BigDecimal> difList = new ArrayList<>(); |
| | | difList.add(dif); |
| | | prevDea = calculateEMA(difList, SIGNAL_PERIOD, prevDea); |
| | | // 将新的DIF值添加到历史记录 |
| | | difHistory.add(dif); |
| | | // 保持历史记录在合理范围内 |
| | | if (difHistory.size() > MAX_DIF_HISTORY) { |
| | | difHistory.remove(0); |
| | | } |
| | | |
| | | // 计算DEA:使用足够数量的历史DIF值 |
| | | // 如果历史DIF值不足,使用当前可用的所有值 |
| | | int difCount = difHistory.size(); |
| | | List<BigDecimal> 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.debug("MACD计算结果 - DIF: {}, DEA: {}, MACD柱状图: {}", dif, dea, macdBar); |
| | | 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); |
| | | } |
| | | |
| | | /** |