/**
|
* 指数移动平均线(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);
|
}
|
|
/**
|
* 计算单个EMA值(递归计算方式)
|
*
|
* @param currentPrice 当前价格
|
* @param prevEMA 前一个EMA值
|
* @param period EMA周期
|
* @return 当前EMA值
|
*/
|
public static BigDecimal calculateSingleEMA(BigDecimal currentPrice, BigDecimal prevEMA, int period) {
|
// 计算权重因子alpha = 2 / (period + 1)
|
BigDecimal alpha = BigDecimal.valueOf(2.0).divide(BigDecimal.valueOf(period + 1), 10, RoundingMode.HALF_UP);
|
|
// EMA(today) = Price(today) * alpha + EMA(yesterday) * (1 - alpha)
|
return currentPrice.multiply(alpha)
|
.add(prevEMA.multiply(BigDecimal.ONE.subtract(alpha)))
|
.setScale(10, RoundingMode.HALF_UP);
|
}
|
}
|