【问题标题】:Implementing exponential moving average in Java在 Java 中实现指数移动平均线
【发布时间】:2012-02-08 20:27:09
【问题描述】:

我基本上有一个这样的值数组:

0.25, 0.24, 0.27, 0.26, 0.29, 0.34, 0.32, 0.36, 0.32, 0.28, 0.25, 0.24, 0.25

上面的数组过于简单,我在真实代码中每毫秒收集 1 个值,我需要处理我编写的算法的输出,以找到某个时间点之前的最近峰值。我的逻辑失败了,因为在上面的示例中,0.36 是真正的峰值,但我的算法会向后看,将最后一个数字 0.25 视为峰值,因为它之前的 0.24 有所减少。

我们的目标是获取这些值并对它们应用一种算法,这将使它们“平滑”一点,这样我就有更多的线性值。 (即:我希望我的结果是弯曲的,而不是锯齿状的)

有人告诉我对我的值应用指数移动平均过滤器。我怎样才能做到这一点?阅读数学方程式对我来说真的很难,我处理代码要好得多。

如何处理我的数组中的值,应用指数移动平均计算来平衡它们?

float[] mydata = ...
mySmoothedData = exponentialMovingAverage(mydata, 0.5);

float[] exponentialMovingAverage(float[] input, float alpha) {
    // what do I do here?
    return result;
}

【问题讨论】:

    标签: java math lowpass-filter


    【解决方案1】:

    要计算exponential moving average,您需要保持一些状态,并且需要一个调整参数。这需要一个小类(假设您使用的是 Java 5 或更高版本):

    class ExponentialMovingAverage {
        private double alpha;
        private Double oldValue;
        public ExponentialMovingAverage(double alpha) {
            this.alpha = alpha;
        }
    
        public double average(double value) {
            if (oldValue == null) {
                oldValue = value;
                return value;
            }
            double newValue = oldValue + alpha * (value - oldValue);
            oldValue = newValue;
            return newValue;
        }
    }
    

    用你想要的衰减参数实例化(可能需要调整;应该在 0 和 1 之间)然后使用average(…) 进行过滤。


    在阅读有关某些数学递归的页面时,将其转换为代码时您真正需要知道的是数学家喜欢将索引写入数组和带下标的序列。 (它们还有一些其他的符号,这没有帮助。)但是,EMA 非常简单,因为您只需要记住一个旧值;不需要复杂的状态数组。

    【讨论】:

    • 事实上,EMA 是代码中最简单的移动平均线(前提是你有像 Java 对象一样存储状态状态的地方),因为你不需要做复杂的事情状态管理。
    • 所以我基本上只是for (float dude : input) { output[index] = ema.average(dude); }?
    • @TKKocheran:差不多。当事情变得简单时不是很好吗? (如果从一个新序列开始,获得一个新的平均值。)请注意,由于边界效应,平均序列中的前几项会稍微跳跃,但你也会得到其他移动平均线。然而,一个好的优点是您可以将移动平均逻辑封装到平均器中并进行实验,而不会过多地干扰程序的其余部分。
    【解决方案2】:

    我很难理解你的问题,但无论如何我都会尝试回答。

    1) 如果你的算法找到的是 0.25 而不是 0.36,那就错了。这是错误的,因为它假设单调增加或减少(即“总是上升”或“总是下降”)。除非您对所有数据进行平均,否则您的数据点——当你呈现它们时——是非线性的。如果您真的想找到两个时间点之间的最大值,请将您的数组从 t_min 切片到 t_max 并找到该子数组的最大值。

    2) 现在,“移动平均线”的概念非常简单:假设我有以下列表:[1.4, 1.5, 1.4, 1.5, 1.5]。我可以通过取两个数字的平均值来“平滑它”:[1.45, 1.45, 1.45, 1.5]。请注意,第一个数字是 1.5 和 1.4(第二个和第一个数字)的平均值;第二个(新列表)是 1.4 和 1.5(第三和第二个旧列表)的平均值;第三(新榜)1.5和1.4(第四和第三)的平均值,以此类推。我本可以将其设为“第三期”或“第四期”或“n”。请注意数据如何平滑得多。 “查看工作中的移动平均线”的一个好方法是去谷歌财经,选择一只股票(试试特斯拉汽车;相当波动(TSLA)),然后点击图表底部的“技术”。选择具有给定周期的“移动平均线”和“指数移动平均线”来比较它们的差异。

    指数移动平均线只是对此的另一种阐述,但对“旧”数据的权重低于“新”数据;这是一种将平滑“偏置”到后面的方法。请阅读维基百科条目。

    所以,这更像是一个评论而不是一个答案,但是这个小评论框太小了。祝你好运。

    【讨论】:

    • +1:找到最小值和最大值相对容易,但要弄清楚它们的重要性要困难得多,因为您需要查看与长期模式的偏差。假设这种模式完全存在。
    【解决方案3】:

    看看this。 如果您的噪声平均值为零,请考虑使用Kalman filter

    【讨论】:

      【解决方案4】:

      滚动方式....我也使用 commons.apache 数学库

        public LinkedList EMA(int dperiods, double alpha)
                      throws IOException {
                  String line;
                  int i = 0;
                  DescriptiveStatistics stats = new SynchronizedDescriptiveStatistics();
                  stats.setWindowSize(dperiods);
                  File f = new File("");
                  BufferedReader in = new BufferedReader(new FileReader(f));
                  LinkedList<Double> ema1 = new LinkedList<Double>();
                  // Compute some statistics
                  while ((line = in.readLine()) != null) {
                      double sum = 0;
                      double den = 0;
                      System.out.println("line: " + " " + line);
                      stats.addValue(Double.parseDouble(line.trim()));
                      i++;
                      if (i > dperiods)
                          for (int j = 0; j < dperiods; j++) {
                              double var = Math.pow((1 - alpha), j);
                              den += var;
                              sum += stats.getElement(j) * var;
                              System.out.println("elements:"+stats.getElement(j));
                              System.out.println("sum:"+sum);
                          }
                      else
                          for (int j = 0; j < i; j++) {
                              double var = Math.pow((1 - alpha), j);
                              den += var;
                              sum += stats.getElement(j) * var;
                          }
                      ema1.add(sum / den);
                      System.out.println("EMA: " + sum / den);
                  }
                  return ema1;
              }
      

      【讨论】:

      • 请先使用 commons.apache 数学库,然后再点赞:(
      【解决方案5】:
      public class MovingAvarage {
      
      public static void main(String[] args) {
          double[] array = {1.2, 3.4, 4.5, 4.5, 4.5};
      
          double St = 0D;
          for(int i=0; i<array.length; i++) {
              St = movingAvarage(St, array[i]);
          }
          System.out.println(St);
      
      }
      
      private static double movingAvarage(double St, double Yt) {
          double alpha = 0.01, oneMinusAlpha = 0.99;
          if(St <= 0D) {
              St = Yt;
          } else {
              St = alpha*Yt + oneMinusAlpha*St;
          }
          return St;
         }
      
      }
      

      【讨论】:

      • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
      【解决方案6】:

      如果您在数学上遇到问题,可以使用简单的移动平均线而不是指数。所以你得到的输出将是最后的 x 项除以 x。未经测试的伪代码:

      int data[] = getFilled();
      int outdata[] = initializeme()
      for (int y = 0; y < data.length; y++)
          int sum = 0;
          for (int x = y; x < y-5; x++)
              sum+=data[x];
          outdata[y] = sum / 5;
      

      请注意,您需要处理数据的开始和结束部分,因为当您处于第二个数据点时,显然无法平均最后 5 个术语。此外,还有更有效的方法来计算这个移动平均值(总和 = 总和 - 最旧 + 最新),但这是为了了解正在发生的事情的概念。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-11-11
        • 2022-01-26
        • 1970-01-01
        • 1970-01-01
        • 2017-01-19
        • 1970-01-01
        • 1970-01-01
        • 2011-07-28
        相关资源
        最近更新 更多