【问题标题】:How to calculate simple moving average faster in C#?如何在 C# 中更快地计算简单移动平均线?
【发布时间】:2023-04-07 21:51:01
【问题描述】:

计算简单移动平均线最快的库/算法是什么?我自己写了,但是在 330 000 个十进制数据集上花费的时间太长了。

  • 周期/时间(毫秒)
  • 20 / 300;
  • 60 / 1500;
  • 120 / 3500。

这是我的方法的代码:

public decimal MA_Simple(int period, int ii) {
    if (period != 0 && ii > period) {
        //stp.Start();
        decimal summ = 0;
        for (int i = ii; i > ii - period; i--) {
            summ = summ + Data.Close[i];
        }
        summ = summ / period;
        //stp.Stop();
        //if (ii == 1500) System.Windows.Forms.MessageBox.Show((stp.ElapsedTicks * 1000.0) / Stopwatch.Frequency + " ms");
        return summ;
    } else return -1;
}

Data.Close[] 是一个固定大小 (1 000 000) 的十进制数组。

【问题讨论】:

  • 你用移动平均线做什么?如果您在滑动窗口上进行平均,那么您可以逐步更新平均值,从而使其更快。如果您正在计算随机窗口,您可以将数组预处理为累积和数组以使移动平均速度更快。优化取决于您的用例,在这里。
  • 一个大数组的累积和会导致精度损失,除非使用具有任意精度的数值库。
  • decimal 的精度为 96 位,对于此类累积和计算,其性能将比 doublefloat 好很多。

标签: c# algorithm financial moving-average


【解决方案1】:
    public class MovingAverage  
    {
        private Queue<Decimal> samples = new Queue<Decimal>();
        private int windowSize = 16;
        private Decimal sampleAccumulator;
        public Decimal Average { get; private set; }

        /// <summary>
        /// Computes a new windowed average each time a new sample arrives
        /// </summary>
        /// <param name="newSample"></param>
        public void ComputeAverage(Decimal newSample)
        {
            sampleAccumulator += newSample;
            samples.Enqueue(newSample);

            if (samples.Count > windowSize)
            {
                sampleAccumulator -= samples.Dequeue();
            }

            Average = sampleAccumulator / samples.Count;
        }
    }

【讨论】:

【解决方案2】:

您的主要问题是每次迭代都丢弃了太多信息。 如果你想跑得这么快,你需要保持一个与帧长度相同大小的缓冲区。

此代码将为您的整个数据集运行移动平均线:

(不是真正的 C#,但你应该明白)

decimal buffer[] = new decimal[period];
decimal output[] = new decimal[data.Length];
current_index = 0;
for (int i=0; i<data.Length; i++)
    {
        buffer[current_index] = data[i]/period;
        decimal ma = 0.0;
        for (int j=0;j<period;j++)
            {
                ma += buffer[j];
            }
        output[i] = ma;
        current_index = (current_index + 1) % period;
    }
return output;

请注意,保留一个运行的 cumsum 可能很诱人,而不是保留整个缓冲区并计算每次迭代的值,但这不适用于非常长的数据长度,因为您的累积和会增长得如此之大以至于添加小的额外的值将导致舍入错误。

【讨论】:

  • 注意:这种性质的“舍入误差”只是浮点计算的问题,而不是定点(十进制)的问题。
  • 但 C# 中的十进制是浮点数(128 位)。 28-29 位有效数字。但错误可能足够小。我想这取决于计算的内容。如果这是关于货币的,我会使用定点库。
  • decimal 有一个 96 位尾数,但至关重要的是浮点基数是 10 而不是 2。因此,如果您所做的只是在小数点后使用有限位数(10小数位对于大多数财务计算来说已经足够了),decimal 没有错误。
  • 嗯,我承认我不知道 C# 十进制是浮点数。很高兴知道...
  • 小改进:十进制数组应定义为decimal[] buffer 而不是decimal buffer[]
【解决方案3】:

如今,Math DotNet 库有一个名为 RunningStatistics 的类,它可以为您完成这项工作。如果您只想对最后的“X”项执行此操作,请改用MovingStatistics

两者都将计算运行平均值、方差和标准偏差,仅一次通过,无需存储额外的数据副本。

【讨论】:

    【解决方案4】:

    如果数据是静态的,可以对数组进行预处理,使移动平均查询非常快:

    decimal[] GetCSum(decimal[] data) {
        decimal csum[] = new decimal[data.Length];
        decimal cursum = 0;
        for(int i=0; i<data.Length; i++) {
            cursum += data[i];
            csum[i] = cursum;
        }
        return csum;
    }
    

    现在移动平均线的计算既简单又快捷:

    decimal CSumMovingAverage(decimal[] csum, int period, int ii) {
        if(period == 0 || ii <= period)
            return -1;
        return csum[ii] - csum[ii - period];
    }
    

    【讨论】:

      【解决方案5】:

      当前(接受的)解决方案包含一个内部循环。删除它也会更有效。你可以在这里看到这是如何实现的:

      How to efficiently calculate a moving Standard Deviation

      【讨论】:

        【解决方案6】:

        您不需要保留运行队列。只需选择窗口的最新条目并丢弃旧条目。请注意,这只使用了一个循环,除了 sum 之外没有额外的存储空间。

          // n is the window for your Simple Moving Average
          public List<double> GetMovingAverages(List<Price> prices, int n)
          {
            var movingAverages = new double[prices.Count];
            var runningTotal = 0.0d;       
        
            for (int i = 0; i < prices.Count; ++i)
            {
              runningTotal += prices[i].Value;
              if( i - n >= 0) {
                var lost = prices[i - n].Value;
                runningTotal -= lost;
                movingAverages[i] = runningTotal / n;
              }
            }
            return movingAverages.ToList();
          }
        

        【讨论】:

          【解决方案7】:

          我发现提供的答案有点记忆饥渴,而且慢,你要求快。 添加 2 个字段,一个用于保持运行总计,一个用于值更改为的时间 平均值是值列表的总和/计数。我添加了一个 Add 方法,但是您也可以只在方法中使用变量……。

          public class Sample
          {
              private decimal sum = 0;
              private uint count = 0;
          
              public void Add(decimal value)
              {
                  sum += value;
                  count++;
              }
          
              public decimal AverageMove => count > 0 ? sum / count : 0;
          }
          

          使其线程安全:

          public class ThreadSafeSample
          {
          private decimal sum = 0;
          private uint count = 0;
          
          private static object locker = new object();
          public void Add(decimal value)
          {
              lock (locker)
              {
                  sum += value;
                  count++;
              }
          }
          
          public decimal AverageMove => count > 0 ? sum / count : 0;
          

          }

          【讨论】:

          • 请注意,这个答案只是一个简单的平均计算。移动平均线的表现不同。
          【解决方案8】:
          // simple moving average
          int moving_average(double *values, double *&averages, int size, int periods)
          {
              double sum = 0;
              for (int i = 0; i < size; i ++)
                  if (i < periods) {
                      sum += values[i];
                      averages[i] = (i == periods - 1) ? sum / (double)periods : 0;
                  } else {
                      sum = sum - values[i - periods] + values[i];
                      averages[i] = sum / (double)periods;
                  }
              return (size - periods + 1 > 0) ? size - periods + 1 : 0;
          }
          

          一个 C 函数,13 行代码,简单的移动平均线。 用法示例:

          double *values = new double[10]; // the input
          double *averages = new double[10]; // the output
          values[0] = 55;
          values[1] = 113;
          values[2] = 92.6;
          ...
          values[9] = 23;
          moving_average(values, averages, 10, 5); // 5-day moving average
          

          【讨论】:

          • 这看起来类似于 TA-Lib 正在做的事情。似乎是最佳选择。
          【解决方案9】:

          这是我在我的应用中使用的 MA。

          double[] MovingAverage(int period, double[] source)
          {
              var ma = new double[source.Length];
          
              double sum = 0;
              for (int bar = 0; bar < period; bar++)
                  sum += source[bar];
          
              ma[period - 1] = sum/period;
          
              for (int bar = period; bar < source.Length; bar++)
                  ma[bar] = ma[bar - 1] + source[bar]/period
                                        - source[bar - period]/period;
          
              return ma;
          }
          

          一旦你为整个数据系列计算了它,你就可以立即获取一个特定的值。

          【讨论】:

            【解决方案10】:

            这是我尝试的方法。但警告我是一个完全的业余爱好者,所以这可能是完全错误的。

            List<decimal> MovingAverage(int period, decimal[] Data)
            {
                 decimal[] interval = new decimal[period];
                 List<decimal> MAs = new List<decimal>();
            
                 for (int i=0, i < Data.length, i++)
                 {
                      interval[i % period] = Data[i];
                      if (i > period - 1)
                      {
                           MAs.Add(interval.Average());
                      }
                 }
                 return MAs;
            }
            

            应返回包含数据移动平均值的小数列表。

            【讨论】:

              【解决方案11】:

              Queue 怎么样?

              using System.Collections.Generic;
              using System.Linq;
              
              public class MovingAverage
              {
                  private readonly Queue<decimal> _queue;
                  private readonly int _period;
              
                  public MovingAverage(int period)
                  {
                      _period = period;
                      _queue = new Queue<decimal>(period);
                  }
              
                  public decimal Compute(decimal x)
                  {
                      if (_queue.Count >= _period)
                      {
                          _queue.Dequeue();
                      }
              
                      _queue.Enqueue(x);
              
                      return _queue.Average();
                  }
              }
              

              用法:

              MovingAverage ma = new MovingAverage(3);
              
              foreach(var val in new decimal[] { 1,2,3,4,5,6,7,8,9 })
              {
                 Console.WriteLine(ma.Compute(val));
              }
              

              【讨论】:

                【解决方案12】:
                /// <summary>
                /// Fast low CPU usage moving average based on floating point math
                /// Note: This algorithm algorithm compensates for floating point error by re-summing the buffer for every 1000 values
                /// </summary>
                public class FastMovingAverageDouble
                {
                    /// <summary>
                    /// Adjust this as you see fit to suit the scenario
                    /// </summary>
                    const int MaximumWindowSize = 100;
                
                    /// <summary>
                    /// Adjust this as you see fit
                    /// </summary>
                    const int RecalculateEveryXValues = 1000;
                
                    /// <summary>
                    /// Initializes moving average for specified window size
                    /// </summary>
                    /// <param name="_WindowSize">Size of moving average window between 2 and MaximumWindowSize 
                    /// Note: this value should not be too large and also bear in mind the possibility of overflow and floating point error as this class internally keeps a sum of the values within the window</param>
                    public FastMovingAverageDouble(int _WindowSize)
                    {
                        if (_WindowSize < 2)
                        {
                            _WindowSize = 2;
                        }
                        else if (_WindowSize > MaximumWindowSize)
                        {
                            _WindowSize = MaximumWindowSize;
                        }
                        m_WindowSize = _WindowSize;
                    }
                    private object SyncRoot = new object();
                    private Queue<double> Buffer = new Queue<double>();
                    private int m_WindowSize;
                    private double m_MovingAverage = 0d;
                    private double MovingSum = 0d;
                    private bool BufferFull;
                    private int Counter = 0;
                
                    /// <summary>
                    /// Calculated moving average
                    /// </summary>
                    public double MovingAverage
                    {
                        get
                        {
                            lock (SyncRoot)
                            {
                                return m_MovingAverage;
                            }
                        }
                    }
                
                    /// <summary>
                    /// Size of moving average window set by constructor during intialization
                    /// </summary>
                    public int WindowSize
                    {
                        get
                        {
                            return m_WindowSize;
                        }
                    }
                
                    /// <summary>
                    /// Add new value to sequence and recalculate moving average seee <see cref="MovingAverage"/>
                    /// </summary>
                    /// <param name="NewValue">New value to be added</param>
                    public void AddValue(int NewValue)
                    {
                        lock (SyncRoot)
                        {
                            Buffer.Enqueue(NewValue);
                            MovingSum += NewValue;
                            if (!BufferFull)
                            {
                                int BufferSize = Buffer.Count;
                                BufferFull = BufferSize == WindowSize;
                                m_MovingAverage = MovingSum / BufferSize;
                            }
                            else
                            {
                                Counter += 1;
                                if (Counter > RecalculateEveryXValues)
                                {
                                    MovingSum = 0;
                                    foreach (double BufferValue in Buffer)
                                    {
                                        MovingSum += BufferValue;
                                    }
                                    Counter = 0;
                                }
                                MovingSum -= Buffer.Dequeue();
                                m_MovingAverage = MovingSum / WindowSize;
                            }
                        }
                    }
                }
                

                【讨论】:

                  【解决方案13】:

                  使用 Dotnet Core 3 和 Linq 测试:

                  int period = 20;
                  for(int k=0;data.Count()-period;k++){
                     decimal summe = data.Skip(k).Take(period).Sum();
                     summe /= (decimal)period;
                  }
                  

                  它确实依赖于 Linq 及其内部优化,没有计时。
                  使用 Skip() 和 Take() 作为 moving average 的“rangeBetween”解决方案,然后将总和除以期间数量。
                  *for 循环 设置上限以避免不完整的求和运算。
                  参考(C#微软):Skip()Take()Sum()

                  【讨论】:

                    【解决方案14】:

                    我的MovingAverage 类实现是:

                    • 线程安全
                    • 无锁
                    • 仅限于windowSize,即二的幂

                    这是课程:

                    using System;
                    using System.Linq;
                    using System.Threading;
                    
                    public class MovingAverage
                    {
                        private readonly int _mask;
                        private readonly double?[] _values;
                        private int _nextIndex = -1;
                    
                        public MovingAverage(int windowSize)
                        {
                            _mask = windowSize - 1;
                            if (windowSize == 0 || (windowSize & _mask) != 0)
                            {
                                throw new ArgumentException("Must be power of two", nameof(windowSize));
                            }
                            _values = new double?[windowSize];
                        }
                    
                        public void Add(double newValue)
                        {
                            var index = Interlocked.Increment(ref _nextIndex) & _mask;
                            _values[index] = newValue;
                        }
                    
                        public double ComputeAverage()
                        {
                            return _values.TakeWhile(x => x.HasValue)
                                .Select(x => x ?? 0)
                                .DefaultIfEmpty(0)
                                .Average();
                        }
                    }
                    

                    这里是 NUnit 测试

                    using NUnit.Framework;
                    
                    public class MovingAverageTest
                    {
                        [Test]
                        public void Should_compute_average()
                        {
                            var sut = new MovingAverage(4);
                    
                            Assert.That(sut.ComputeAverage(), Is.EqualTo(0));
                            sut.Add(2);
                            Assert.That(sut.ComputeAverage(), Is.EqualTo(2));
                            sut.Add(4);
                            Assert.That(sut.ComputeAverage(), Is.EqualTo(3));
                            sut.Add(0);
                            Assert.That(sut.ComputeAverage(), Is.EqualTo(2));
                            sut.Add(6);
                            Assert.That(sut.ComputeAverage(), Is.EqualTo(3));
                            sut.Add(6);
                            Assert.That(sut.ComputeAverage(), Is.EqualTo(4));
                            sut.Add(0);
                            sut.Add(0);
                            sut.Add(0);
                            sut.Add(0);
                            Assert.That(sut.ComputeAverage(), Is.EqualTo(0));
                            sut.Add(10);
                            sut.Add(10);
                            sut.Add(10);
                            sut.Add(10);
                            Assert.That(sut.ComputeAverage(), Is.EqualTo(10));
                        }
                    
                        [Test]
                        public void Should_check_windowsize_param()
                        {
                            Assert.That(() => new MovingAverage(3), Throws.ArgumentException);
                        }
                    }
                    

                    【讨论】:

                      【解决方案15】:

                      在实践中,这就是我发现即使对数百万个样本也有效的方法。它计算移动平均线,比我尝试过的任何其他方法都快。

                      public class Sma
                        {
                          decimal mult = 0;
                          private decimal[] samples;
                          private readonly int max;
                      
                          private decimal average;
                          public Sma(int period)
                          {
                              mult = 1m / period; //cache to avoid expensive division on each step.
                              samples = new decimal[period];
                              max = period - 1;
                          }
                          public decimal ComputeAverage(decimal value)
                          {
                              average -= samples[max];
                              var sample = value * mult;
                              average += sample;
                              Array.Copy(samples, 0, samples, 1, max);
                              samples[0] = sample;
                              return average = average - samples[0];
                          }
                      }
                      

                      我发现我经常需要访问历史记录。我通过跟踪平均值来实现这一点:

                      public class Sma
                      {
                          private readonly int max;
                          private decimal[] history;
                          public readonly int Period;
                          public int Counter = -1;
                          public SimpleSma RunningSma { get; }
                      
                          public Sma(int period, int maxSamples)
                          {
                              this.Period = period;
                              this.RunningSma = new SimpleSma(period);
                              max = maxSamples - 1;
                              history = new decimal[maxSamples];
                          }
                      
                      
                          public decimal ComputeAverage(decimal value)
                          {
                              Counter++;
                              Array.Copy(history, 0, history, 1, max);
                              return history[0] = RunningSma.ComputeAverage(value);
                          }
                      
                          public decimal Average => history[0];
                          public decimal this[int index] => history[index];
                          public int Length => history.Length;
                      
                      }
                      

                      现在在实践中,您的用例听起来像我的,您需要跟踪多个时间范围:

                      public class MtfSma // MultiTimeFrame Sma
                      {
                          public Dictionary<int, Sma> Smas { get; private set; }
                          public MtfSma(int[] periods, int maxHistorySize = 100)
                          {
                              Smas = periods.ToDictionary(x=> x, x=> new Sma(x, maxHistorySize));
                          }
                      }
                      
                      A dictionary is no necessary, but is helpful to map an Sma to its period.
                      

                      可以这样使用:

                      IEnumerable<decimal> dataPoints = new List<Decimal>(); //330 000 data points.
                      foreach (var dataPoint in dataPoints)
                      {
                          foreach (var kvp in Smas)
                          {
                              var sma = kvp.Value;
                              var period = sma.Period;
                              var average = sma.Average; // or sma[0];
                              var lastAverage = sma[1];
                              Console.WriteLine($"Sma{period} [{sma.Counter}]: Current {average.ToString("n2")}, Previous {lastAverage.ToString("n2")}");
                          }
                      }
                      

                      另外一点是你可以看到它被强类型化为十进制,这意味着对其他数据类型的完全重写。

                      为了处理这个问题,可以将类设为泛型并使用接口来提供类型转换和所需的算术运算提供程序。

                      我在Github here 上有一个我使用的实际代码的完整工作示例,同样适用于数百万个数据点,以及交叉检测的实现等。本问答相关的代码:

                      public interface INumericOperationsProvider<TNumeric>
                          where TNumeric : IConvertible
                      {
                          TNumeric Divide(TNumeric dividend, TNumeric divisor);
                          TNumeric Multiply(TNumeric multiplicand, TNumeric multiplier);
                          TNumeric Add(TNumeric operandA, TNumeric operandB);
                          TNumeric Subtract(TNumeric operandA, TNumeric operandB);
                      
                          bool IsLessThan(TNumeric operandA, TNumeric operandB);
                          bool IsLessThanOrEqual(TNumeric operandA, TNumeric operandB);
                          bool IsEqual(TNumeric operandA, TNumeric operandB);
                          bool IsGreaterThanOrEqual(TNumeric operandA, TNumeric operandB);
                          bool IsGreaterThan(TNumeric operandA, TNumeric operandB);
                      
                          TNumeric ToNumeric(sbyte value);
                          TNumeric ToNumeric(short value);
                          TNumeric ToNumeric(int value);
                          TNumeric ToNumeric(long value);
                          TNumeric ToNumeric(byte value);
                          TNumeric ToNumeric(ushort value);
                          TNumeric ToNumeric(uint value);
                          TNumeric ToNumeric(ulong value);
                          TNumeric ToNumeric(float value);
                          TNumeric ToNumeric(double value);
                          TNumeric ToNumeric(decimal value);
                          TNumeric ToNumeric(IConvertible value);
                      }
                      
                      
                      
                      public abstract class OperationsProviderBase<TNumeric>
                          : INumericOperationsProvider<TNumeric>
                          where TNumeric : IConvertible
                      {
                      
                          private static Type Type = typeof(TNumeric);
                          public abstract TNumeric Divide(TNumeric dividend, TNumeric divisor);
                          public abstract TNumeric Multiply(TNumeric multiplicand, TNumeric multiplier);
                          public abstract TNumeric Add(TNumeric operandA, TNumeric operandB);
                          public abstract TNumeric Subtract(TNumeric operandA, TNumeric operandB);
                      
                      
                      
                          public TNumeric ToNumeric(sbyte value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(short value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(int value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(long value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(byte value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(ushort value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(uint value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(ulong value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(float value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(double value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(decimal value) => (TNumeric)Convert.ChangeType(value, Type);
                          public TNumeric ToNumeric(IConvertible value) => (TNumeric)Convert.ChangeType(value, Type);
                      
                      
                          public bool IsLessThan(TNumeric operandA, TNumeric operandB)
                              => ((IComparable<TNumeric>)operandA).CompareTo(operandB) < 0;
                      
                          public bool IsLessThanOrEqual(TNumeric operandA, TNumeric operandB)
                              => ((IComparable<TNumeric>)operandA).CompareTo(operandB) <= 0;
                      
                          public bool IsEqual(TNumeric operandA, TNumeric operandB)
                              => ((IComparable<TNumeric>)operandA).CompareTo(operandB) == 0;
                      
                          public bool IsGreaterThanOrEqual(TNumeric operandA, TNumeric operandB)
                              => ((IComparable<TNumeric>)operandA).CompareTo(operandB) >= 0;
                      
                          public bool IsGreaterThan(TNumeric operandA, TNumeric operandB)
                              => ((IComparable<TNumeric>)operandA).CompareTo(operandB) > 0;
                      }
                      
                      public class OperationsProviderFactory
                      {
                          public static OperationsProviderBase<TNumeric> GetProvider<TNumeric>()
                              where TNumeric : IConvertible
                          {
                              var name = typeof(TNumeric).Name;
                              switch (name)
                              {
                                  case nameof(Decimal):
                                      return new DecimalOperationsProvider() as OperationsProviderBase<TNumeric>;
                                  case nameof(Single):
                                      return new FloatOperationsProvider() as OperationsProviderBase<TNumeric>;
                                  case nameof(Double):
                                      return new DoubleOperationsProvider() as OperationsProviderBase<TNumeric>;
                                  default:
                                      throw new NotImplementedException();
                              }
                          }
                      }
                      public class DecimalOperationsProvider : OperationsProviderBase<decimal>
                      {
                          public override decimal Add(decimal a, decimal b)
                              => a + b;
                      
                          public override decimal Divide(decimal dividend, decimal divisor)
                              => dividend / divisor;
                      
                      
                          public override decimal Multiply(decimal multiplicand, decimal multiplier)
                              => multiplicand * multiplier;
                      
                          public override decimal Subtract(decimal a, decimal b)
                             => a - b;
                      }
                      
                      public class FloatOperationsProvider : OperationsProviderBase<float>
                      {
                          public override float Add(float a, float b)
                              => a + b;
                      
                          public override float Divide(float dividend, float divisor)
                              => dividend / divisor;
                      
                      
                          public override float Multiply(float multiplicand, float multiplier)
                              => multiplicand * multiplier;
                      
                          public override float Subtract(float a, float b)
                             => a - b;
                      }
                      
                      public class DoubleOperationsProvider : OperationsProviderBase<double>
                      {
                          public override double Add(double a, double b)
                              => a + b;
                      
                          public override double Divide(double dividend, double divisor)
                              => dividend / divisor;
                      
                      
                          public override double Multiply(double multiplicand, double multiplier)
                              => multiplicand * multiplier;
                      
                          public override double Subtract(double a, double b)
                             => a - b;
                      }
                      
                      public interface ISma<TNumeric>
                      {
                          int Count { get; }
                          void AddSample(TNumeric sample);
                          void AddSample(IConvertible sample);
                          TNumeric Average { get; }
                          TNumeric[] History { get; }
                      }
                      
                      public class SmaBase<T> : ISma<T>
                          where T : IConvertible
                      {
                          public int Count { get; private set; }
                          private int maxLen;
                          public T[] History { get; private set; }
                          public T Average { get; private set; } = default(T);
                          public INumericOperationsProvider<T> OperationsProvider { get; private set; }
                          public T SampleRatio { get; private set; }
                          public SmaBase(int count, INumericOperationsProvider<T> operationsProvider = null)
                          {
                              if (operationsProvider == null)
                                  operationsProvider = OperationsProviderFactory.GetProvider<T>();
                              this.Count = count;
                              this.maxLen = Count - 1;
                              History = new T[count];
                              this.OperationsProvider = operationsProvider;
                              SampleRatio = OperationsProvider.Divide(OperationsProvider.ToNumeric(1), OperationsProvider.ToNumeric(count));
                          }
                      
                          public void AddSample(T sample)
                          {
                              T sampleValue = OperationsProvider.Multiply(SampleRatio, sample);
                      
                              if (maxLen==0)
                              {
                                  History[0] = sample;
                                  Average = sample;
                              }
                              else
                              {
                                  var remValue = OperationsProvider.Multiply(SampleRatio, History[0]);
                                  Average = OperationsProvider.Subtract(Average, remValue);
                                  Average = OperationsProvider.Add(Average, sampleValue);
                                  Array.Copy(History, 1, History, 0, Count - 1);
                                  History[maxLen]= sample;
                              }
                          }
                      
                      
                          public void AddSample(IConvertible sample)
                              => AddSample(OperationsProvider.ToNumeric(sample));
                      
                      }
                      public class SmaOfDecimal : SmaBase<decimal>
                      {
                      
                          public SmaOfDecimal(int count) : base(count)
                          {
                      
                          }
                      }
                      
                      public class MultiTimeFrameSma<TNumeric>
                          where TNumeric : IConvertible
                      {
                          public Dictionary<int, SmaBase<TNumeric>> SimpleMovingAverages;
                          public Dictionary<int, int> SimpleMovingAverageIndexes;
                          public int[] SimpleMovingAverageKeys;
                          private List<Action<TNumeric>> SampleActions;
                          public TNumeric[] Averages;
                          public int TotalSamples = 0;
                          public TNumeric LastSample;
                      
                          public TNumeric[] History { get; private set; }
                          public int MaxSampleLength { get; private set; }
                          private int maxLen;
                          public MultiTimeFrameSma(int maximumMovingAverage) : this(Enumerable.Range(1, maximumMovingAverage))
                          {
                      
                          }
                      
                          public MultiTimeFrameSma(IEnumerable<int> movingAverageSizes)
                          {
                              SimpleMovingAverages = new Dictionary<int, SmaBase<TNumeric>>();
                              SimpleMovingAverageIndexes = new Dictionary<int, int>();
                              SimpleMovingAverageKeys = movingAverageSizes.ToArray();
                      
                              MaxSampleLength = SimpleMovingAverageKeys.Max(x => x);
                              maxLen = MaxSampleLength - 1;
                              History = new TNumeric[MaxSampleLength];//new List<TNumeric>();
                              this.SampleActions = new List<Action<TNumeric>>();
                              var averages = new List<TNumeric>();
                              int i = 0;
                              foreach (var smaSize in movingAverageSizes.OrderBy(x => x))
                              {
                                  var sma = new SmaBase<TNumeric>(smaSize);
                                  SampleActions.Add((x) => { sma.AddSample(x); Averages[SimpleMovingAverageIndexes[sma.Count]] = sma.Average; });
                                  SimpleMovingAverages.Add(smaSize, sma);
                                  SimpleMovingAverageIndexes.Add(smaSize, i++);
                                  averages.Add(sma.Average);
                              }
                              this.Averages = averages.ToArray();
                          }
                          public void AddSample(TNumeric value)
                          {
                              if (maxLen > 0)
                              {
                                  Array.Copy(History, 1, History, 0, maxLen);
                                  History[maxLen] = value;
                              }
                              else
                              {
                                  History[0] = value;
                              }
                              LastSample = value;
                              SampleActions.ForEach(action => action(value));
                              TotalSamples++;
                          }
                      
                      }
                      
                      public class MultiTimeFrameCrossOver<TNumeric>
                          where TNumeric : IConvertible
                      {
                          public MultiTimeFrameSma<TNumeric> SimpleMovingAverages { get; }
                          public TNumeric[] History => SimpleMovingAverages.History;
                          public TNumeric[] Averages => SimpleMovingAverages.Averages;
                          public int TotalSamples => SimpleMovingAverages.TotalSamples;
                          public TNumeric LastSample => SimpleMovingAverages.LastSample;
                          private bool[][] matrix;
                          public MultiTimeFrameCrossOver(MultiTimeFrameSma<TNumeric> simpleMovingAverages)
                          {
                              this.SimpleMovingAverages = simpleMovingAverages;
                              int length = this.SimpleMovingAverages.Averages.Length;
                              this.matrix = SimpleMovingAverages.Averages.Select(avg => SimpleMovingAverages.Averages.Select(x => true).ToArray()).ToArray();
                      
                          }
                          public void AddSample(TNumeric value)
                          {
                              SimpleMovingAverages.AddSample(value);
                              int max = SimpleMovingAverages.Averages.Length;
                      
                              for (var maIndex = 0; maIndex < max; maIndex++)
                              {
                                  IComparable<TNumeric> ma = (IComparable<TNumeric>)SimpleMovingAverages.Averages[maIndex];
                                  var row = matrix[maIndex];
                                  for (var otherIndex = 0; otherIndex < max; otherIndex++)
                                  {
                                      row[otherIndex] = ma.CompareTo(SimpleMovingAverages.Averages[otherIndex]) >= 0;
                                  }
                              }
                          }
                      
                          public bool[][] GetMatrix() => matrix;
                      
                      }
                      

                      【讨论】:

                        【解决方案16】:

                        由于没有显示我的方法会建议它。 我认为 Linq 在大多数情况下会执行得足够快,而不需要创建缓冲区或代码复杂性。考虑到金融 _originalDataserie OHLC 开盘高低收盘,我想要 sma 收盘价是Ilist&lt;double&gt;

                          double[] smaSerie = new double[_originalDataSeries.Count];
                              for (int i = 0; i < _originalDataSeries.Count;i++)
                                    {
                                        double sma = double.NaN;
                                        int period = 50;
                                      //  var rangeOfinterest = _originalDataSeries.CloseValues.AsParallel().Skip(i - period).Take(period).ToList();
                                        var rangeOfinterest = _originalDataSeries.CloseValues.Skip(i - period).Take(period).ToList();
                                        if (rangeOfinterest.Any())
                                        {
                                            sma = rangeOfinterest.Average();                   
                                        }              
                                         smaSerie[i] = sma;
                        
                                    }
                        

                        Sma 计算 720 点:00:00:00.0075765

                        我无法判断评论中的并行版本是否表现更好,因为它需要将平均值实现为并行并用于 _originalSerie 并处理空范围,但如果你有百万分来显示一个镜头,它可以通过这种方式改进。但是在这种情况下,我会进行 GPU 计算,因为 sma 符合此 gpu 任务的条件

                        【讨论】:

                          猜你喜欢
                          • 2012-10-04
                          • 2021-07-18
                          • 2012-01-21
                          • 2012-06-14
                          • 1970-01-01
                          • 2017-12-19
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多