Java中計算移動平均線

banq發表於2024-04-27

移動平均線是分析資料趨勢和模式的基本工具,廣泛應用於金融、經濟和工程領域。

它們有助於消除短期波動並揭示潛在趨勢,使資料更易於解釋。

在本教程中,我們將探索計算移動平均值的各種方法和技術,從傳統方法到庫和 Stream API。

計算移動平均線的常用方法
在本節中,我們將探討計算移動平均線的三種常用方法。

1.使用 Apache Commons 數學庫
Apache Commons Math是一個功能強大的 Java 庫,提供廣泛的數學和統計函式,包括計算移動平均值的工具。

透過利用Apache Commons Math 庫中的DescriptiveStatistics類,我們可以簡化移動平均計算的過程,並利用最佳化的演算法進行高效的資料處理。它將資料點新增到統計物件並檢索平均值(表示移動平均值)。

讓我們使用DescriptiveStatistics類來計算帶有windowSize的移動平均值:

public class MovingAverageWithApacheCommonsMath {
    private final DescriptiveStatistics stats;
    public MovingAverageWithApacheCommonsMath(int windowSize) {
        this.stats = new DescriptiveStatistics(windowSize);
    }
    public void add(double value) {
        stats.addValue(value);
    }
    public double getMovingAverage() {
        return stats.getMean();
    }
}

讓我們測試一下我們的實現:

@Test
public void whenValuesAreAdded_shouldUpdateAverageCorrectly() {
    MovingAverageWithApacheCommonsMath movingAverageCalculator = new MovingAverageWithApacheCommonsMath(3);
    movingAverageCalculator.add(10);
    assertEquals(10.0, movingAverageCalculator.getMovingAverage(), 0.001);
    movingAverageCalculator.add(20);
    assertEquals(15.0, movingAverageCalculator.getMovingAverage(), 0.001);
    movingAverageCalculator.add(30);
    assertEquals(20.0, movingAverageCalculator.getMovingAverage(), 0.001);
}

首先,我們建立一個視窗大小為 3 的MovingAverageWithApacheCommonsMath類的例項。然後,將三個值(10、20 和 30)分別新增到計算器中,並驗證其平均值。

2.使用迴圈緩衝區方法
迴圈緩衝區方法是計算移動平均值的經典方法,並以其高效的記憶體使用而聞名。這種方法很簡單,在某些情況下可能會提供更好的效能,特別是當我們擔心外部依賴項的開銷時。

在這種方法中,新的資料點會覆蓋最舊的資料點,並且平均值是根據緩衝區中的當前元素計算的。

透過迴圈迴圈緩衝區,我們可以為每次更新實現恆定的時間複雜度,使其適合實時資料處理應用。

讓我們使用迴圈緩衝區計算移動平均值:

public class MovingAverageByCircularBuffer {
    private final double[] buffer;
    private int head;
    private int count;
    public MovingAverageByCircularBuffer(int windowSize) {
        this.buffer = new double[windowSize];
    }
    public void add(double value) {
        buffer[head] = value;
        head = (head + 1) % buffer.length;
        if (count < buffer.length) {
            count++;
        }
    }
    public double getMovingAverage() {
        if (count == 0) {
            return Double.NaN;
        }
        double sum = 0;
        for (int i = 0; i < count; i++) {
            sum += buffer[i];
        }
        return sum / count;
    }
}

我們來寫一個測試用例來驗證該方法:

@Test
public void whenValuesAreAdded_shouldUpdateAverageCorrectly() {
    MovingAverageByCircularBuffer ma = new MovingAverageByCircularBuffer(3);
    ma.add(10);
    assertEquals(10.0, ma.getMovingAverage(), 0.001);
    ma.add(20);
    assertEquals(15.0, ma.getMovingAverage(), 0.001);
    ma.add(30);
    assertEquals(20.0, ma.getMovingAverage(), 0.001);
}

我們建立一個視窗大小為 3 的MovingAverageByCircularBuffer類的例項。新增每個值後,測試斷言計算出的移動平均值與預期值匹配,容差為 0.001。

3.使用指數移動平均線
另一種方法是使用指數平滑來計算移動平均值。

指數平滑為較舊的觀測值分配指數遞減的權重,這對於捕獲趨勢和對資料變化快速做出反應非常有用:

public class ExponentialMovingAverage {
    private double alpha;
    private Double previousEMA;
    public ExponentialMovingAverage(double alpha) {
        if (alpha <= 0 || alpha > 1) {
            throw new IllegalArgumentException(<font>"Alpha must be in the range (0, 1]");
        }
        this.alpha = alpha;
        this.previousEMA = null;
    }
    public double calculateEMA(double newValue) {
        if (previousEMA == null) {
            previousEMA = newValue;
        } else {
            previousEMA = alpha * newValue + (1 - alpha) * previousEMA;
        }
        return previousEMA;
    }
}

在這裡,alpha引數控制衰減率,較小的值賦予最近的觀察結果更大的權重。

當我們想要對資料變化快速做出反應,同時仍然捕捉長期趨勢時,指數移動平均線特別有用。

我們用一個測試用例來驗證一下:

@Test
public void whenValuesAreAdded_shouldUpdateExponentialMovingAverageCorrectly() {
    ExponentialMovingAverage ema = new ExponentialMovingAverage(0.4);
    assertEquals(10.0, ema.calculateEMA(10.0), 0.001);
    assertEquals(14.0, ema.calculateEMA(20.0), 0.001);
    assertEquals(20.4, ema.calculateEMA(30.0), 0.001);
}

我們首先建立平滑因子 ( alpha ) 為 0.4 的ExponentialMovingAverage (EMA)例項。

然後,當新增每個值時,測試斷言計算出的 EMA 與預期值相匹配,誤差範圍為 0.001。

4.基於流的方法
我們可以利用Stream API 以更具功能性和宣告性的方式計算移動平均線。如果我們想要處理資料流或集合,這種方法特別有用。

以下是我們如何使用基於流的方法來計算移動平均值的簡化示例:

public class MovingAverageWithStreamBasedApproach {
    private int windowSize;
    public MovingAverageWithStreamBasedApproach(int windowSize) {
        this.windowSize = windowSize;
    }
    public double calculateAverage(double[] data) {
        return DoubleStream.of(data)
                .skip(Math.max(0, data.length - windowSize))
                .limit(Math.min(data.length, windowSize))
                .summaryStatistics()
                .getAverage();
    }
}

在這裡,我們從輸入資料陣列建立一個流,跳過指定視窗大小之外的元素,將流限制在 windowSize 內,然後使用summaryStatistics()計算平均值。

這種方法利用Java Streams API的函數語言程式設計功能以簡潔高效的方式執行計算。

現在,讓我們編寫一些 JUnit 測試來確保我們的程式碼按預期工作:

@Test
public void whenValidDataIsPassed_shouldReturnCorrectAverage() {
    double[] data = {10, 20, 30, 40, 50};
    int windowSize = 3;
    double expectedAverage = 40;
    MovingAverageWithStreamBasedApproach calculator = new MovingAverageWithStreamBasedApproach(windowSize);
    double actualAverage = calculator.calculateAverage(data);
    assertEquals(expectedAverage, actualAverage);
}

在這些測試中,我們檢查calculateAverage()方法是否返回給定場景(例如有效資料和windowSize)的正確平均值。

附加方法
雖然上述方法是在 Java 中計算移動平均線的一些更方便、更有效的方法,但我們可以根據我們的具體要求和約束考慮其他方法。在這裡,我們將介紹兩種這樣的方法。

1.並行處理
如果效能是我們的首要任務,並且我們可以使用多個 CPU 核心,那麼我們可以利用並行處理技術更有效地計算移動平均值。

Java提供了對並行流的支援,它可以自動將計算分佈到多個執行緒上。

2.加權移動平均線
加權移動平均 (WMA) 是一種計算移動平均的方法,它為視窗內的每個資料點分配不同的權重。

權重通常是根據預定義的標準來確定的,例如重要性、相關性或與視窗中心的接近度。

3.累積移動平均線
累積移動平均線 (CMA) 計算截至特定時間點的所有資料點的平均值。與其他移動平均方法不同,CMA 不使用固定大小的視窗,而是包含所有可用資料。

結論
計算移動平均線是時間序列分析的一個基本方面,其應用涵蓋金融、經濟和工程等各個領域。

使用 Apache Commons Math、迴圈緩衝區和指數移動平均技術,分析師可以深入瞭解資料的潛在趨勢和模式。

此外,探索加權和累積移動平均線可以擴充套件分析師的工具包,從而能夠對時間序列資料進行更復雜的分析和解釋。

同樣,選擇完全取決於具體的專案要求和偏好。

相關文章