Administrator
2025-12-29 b22f1797312ac54028f24c50ef9277f75c8ef9fc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
 
import java.math.BigDecimal;
import java.util.List;
 
/**
 * 示例用法
 * // 计算MACD结果(包含起始索引)
 * MACDCalculator.MACDResult macdResult = MACDCalculator.calculateMACD(closePrices, 12, 26, 9);
 *
 * // 检测做多信号
 * boolean bullishSignal = BullishSignalDetector.isBullishSignalFormed(macdResult, closePrices);
 */
public class BullishSignalDetector {
    public static boolean isBullishSignalFormed(MACDResult macdResult, List<BigDecimal> closePrices) {
        List<PriceData> macdData = macdResult.getMacdData();
        int startIdx = macdResult.getStartIndex();
 
        // 校验MACD数据和收盘价长度合法性
        if (closePrices.size() < 34 || macdData.size() < 2 || startIdx + macdData.size() > closePrices.size()) {
            return false;
        }
 
        int currentMacdIndex = macdData.size() - 1;
        int currentCloseIndex = startIdx + currentMacdIndex;
 
        // 1. 金叉判断
        boolean isGoldenCross = isGoldenCross(macdData, currentMacdIndex);
 
        // 2. MACD柱动量增强
        boolean isMacdHistExpanding = isMacdHistExpanding(macdData, currentMacdIndex);
 
        // 3. 价格突破前15周期高点
        boolean isPriceBreakout = isPriceBreakout(closePrices, currentCloseIndex, 15);
 
        return isGoldenCross && isMacdHistExpanding && isPriceBreakout;
    }
 
    private static boolean isGoldenCross(List<PriceData> macdData, int currentMacdIndex) {
        if (currentMacdIndex < 1 || currentMacdIndex >= macdData.size()) {
            return false;
        }
 
        PriceData current = macdData.get(currentMacdIndex);
        PriceData previous = macdData.get(currentMacdIndex - 1);
 
        return previous.getDif().compareTo(previous.getDea()) <= 0 &&
                current.getDif().compareTo(current.getDea()) > 0;
    }
 
    private static boolean isMacdHistExpanding(List<PriceData> macdData, int currentMacdIndex) {
        if (currentMacdIndex < 2 || currentMacdIndex >= macdData.size()) {
            return false;
        }
 
        BigDecimal currentHist = macdData.get(currentMacdIndex).getMacdHist();
        BigDecimal prevHist = macdData.get(currentMacdIndex - 1).getMacdHist();
        BigDecimal prevPrevHist = macdData.get(currentMacdIndex - 2).getMacdHist();
 
        boolean isPositive = currentHist.compareTo(BigDecimal.ZERO) > 0;
        boolean isExpanding = prevPrevHist.compareTo(prevHist) <= 0 &&
                prevHist.compareTo(currentHist) < 0;
 
        return isPositive && isExpanding;
    }
 
    private static boolean isPriceBreakout(List<BigDecimal> closePrices, int currentCloseIndex, int period) {
        if (currentCloseIndex < period || period <= 0) {
            return false;
        }
 
        int startSearchIdx = Math.max(currentCloseIndex - period, 0);
        BigDecimal maxPreviousPrice = closePrices.get(startSearchIdx);
 
        for (int i = startSearchIdx + 1; i < currentCloseIndex; i++) {
            if (i >= closePrices.size()) {
                break;
            }
            BigDecimal price = closePrices.get(i);
            if (price.compareTo(maxPreviousPrice) > 0) {
                maxPreviousPrice = price;
            }
        }
 
        return closePrices.get(currentCloseIndex).compareTo(maxPreviousPrice) > 0;
    }
}