Administrator
6 days ago 97b7f914d060b2a052299416384fc80e061f4b1d
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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)指标计算器
 * <p>
 * MACD指标由三部分组成:
 * 1. DIF(Difference):短期EMA与长期EMA的差值
 * 2. DEA(Signal Line):DIF的指数移动平均线,作为MACD的信号线
 * 3. MACD柱状图(Histogram):DIF与DEA的差值,反映市场动量
 * <p>
 * 默认参数:短期周期=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<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 (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<BigDecimal> prices = new ArrayList<>(closePrices);
        Collections.reverse(prices);
 
        // 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);
 
        // 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);
            }
        }
 
        // 3. 计算信号线(MACD线的siglen周期EMA),使用SMA作为初始值
        List<BigDecimal> signalLine = EMACalculator.calculateEMA(macdLine, siglen, true);
 
        // 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);
            }
        }
 
        // 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, startIndex);
    }
 
    /**
     * 使用默认参数计算MACD指标
     * <p>
     * 默认参数:短期周期=12,长期周期=26,信号周期=9
     *
     * @param closePrices 收盘价列表
     * @return 包含MACD各部分数据的PriceData列表
     */
    public static MACDResult calculateMACD(List<BigDecimal> closePrices) {
        // 默认参数:快速周期12,慢速周期26,信号周期9
        return calculateMACD(closePrices, 12, 26, 9);
    }
 
    /**
     * 判断是否出现顶背离
     * <p>
     * 顶背离:价格创新高,但DIF未创新高,且与价格走势背离
     * 增强空头信号可靠性
     *
     * @param closePrices 原始收盘价列表
     * @param macdResult MACD计算结果
     * @return 是否出现顶背离
     */
    public static boolean isTopDivergence(List<BigDecimal> closePrices, MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        int startIdx = macdResult.getStartIndex();
        
        // 确保有足够的数据点进行判断(至少需要2个高点)
        if (macdData.size() < 10) {
            return false;
        }
        
        // 反转原始价格列表,确保从旧到新处理
        List<BigDecimal> 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;
    }
 
    /**
     * 判断是否出现底背离
     * <p>
     * 底背离:价格创新低,但DIF未创新低,且与价格走势背离
     * 增强多头信号可靠性
     *
     * @param closePrices 原始收盘价列表
     * @param macdResult MACD计算结果
     * @return 是否出现底背离
     */
    public static boolean isBottomDivergence(List<BigDecimal> closePrices, MACDResult macdResult) {
        List<PriceData> macdData = macdResult.getMacdData();
        int startIdx = macdResult.getStartIndex();
        
        // 确保有足够的数据点进行判断(至少需要2个低点)
        if (macdData.size() < 10) {
            return false;
        }
        
        // 反转原始价格列表,确保从旧到新处理
        List<BigDecimal> 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;
    }
}