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
88
89
/**
 * 指数移动平均线(EMA)计算器
 * <p>
 * EMA(Exponential Moving Average)是一种加权移动平均线,对近期价格赋予更高权重,
 * 对远期价格赋予较低权重,能够更敏感地反映价格变化趋势。
 * 本计算器提供了EMA的多种计算方式,支持使用SMA作为初始值或使用第一个价格作为初始值。
 */
package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
 
/**
 * 指数移动平均线(EMA)计算器
 *
 * <p>计算公式:EMA(today) = Price(today) * k + EMA(yesterday) * (1 - k)</p>
 * <p>其中:k = 2 / (period + 1),period为EMA的周期</p>
 */
public class EMACalculator {
    /**
     * 计算价格序列的指数移动平均线(EMA)
     *
     * @param prices      价格序列,使用BigDecimal确保计算精度
     * @param period      EMA计算周期
     * @param initialSMA  是否使用SMA(简单移动平均线)作为初始值
     * @return 计算得到的EMA序列,与输入价格序列一一对应
     * @throws IllegalArgumentException 当输入参数无效时抛出异常
     */
    public static List<BigDecimal> calculateEMA(List<BigDecimal> prices, int period, boolean initialSMA) {
        if (prices == null || prices.isEmpty() || period <= 0) {
            throw new IllegalArgumentException("Invalid input parameters.");
        }
        if (initialSMA && prices.size() < period) {
            throw new IllegalArgumentException("Prices list too short for initial SMA.");
        }
 
        // 计算权重因子k = 2 / (period + 1)
        BigDecimal alpha = BigDecimal.valueOf(2.0).divide(BigDecimal.valueOf(period + 1), 10, RoundingMode.HALF_UP);
        List<BigDecimal> ema = new ArrayList<>();
 
        if (initialSMA) {
            // 使用SMA作为初始EMA值(前period个价格的平均值)
            BigDecimal sum = BigDecimal.ZERO;
            for (int i = 0; i < period; i++) {
                sum = sum.add(prices.get(i));
            }
            BigDecimal sma = sum.divide(BigDecimal.valueOf(period), 10, RoundingMode.HALF_UP);
            ema.add(sma);
 
            // 从第period+1个数据点开始计算后续EMA值
            for (int i = period; i < prices.size(); i++) {
                BigDecimal price = prices.get(i);
                BigDecimal prevEMA = ema.get(ema.size() - 1);
                // EMA计算公式:Price(today) * alpha + EMA(yesterday) * (1 - alpha)
                BigDecimal emaToday = price.multiply(alpha)
                        .add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha)))
                        .setScale(10, RoundingMode.HALF_UP);
                ema.add(emaToday);
            }
        } else {
            // 使用第一个价格作为初始EMA值,并从第二个数据点开始计算
            ema.add(prices.get(0));
            for (int i = 1; i < prices.size(); i++) {
                BigDecimal price = prices.get(i);
                BigDecimal prevEMA = ema.get(ema.size() - 1);
                // EMA计算公式:Price(today) * alpha + EMA(yesterday) * (1 - alpha)
                BigDecimal emaToday = price.multiply(alpha)
                        .add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha)))
                        .setScale(10, RoundingMode.HALF_UP);
                ema.add(emaToday);
            }
        }
 
        return ema;
    }
 
    /**
     * 计算价格序列的指数移动平均线(EMA),默认使用SMA作为初始值
     *
     * @param prices 价格序列
     * @param period EMA计算周期
     * @return 计算得到的EMA序列
     */
    public static List<BigDecimal> calculateEMA(List<BigDecimal> prices, int period) {
        return calculateEMA(prices, period, true);
    }
}