在实践中,我发现即使对于数百万个样本,这也是有效的方法。它计算了一个运行的移动平均值,并且比我尝试过的任何其他方法都要快。
public class Sma
{
decimal mult = 0;
private decimal[] samples;
private readonly int max;
private decimal average;
public Sma(int period)
{
mult = 1m / period;
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
{
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>();
foreach (var dataPoint in dataPoints)
{
foreach (var kvp in Smas)
{
var sma = kvp.Value;
var period = sma.Period;
var average = sma.Average;
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];
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;
}
decimal
具有 96 位精度,对于这种累积求和计算而言比double
或float
要表现出更好的性能。 - nneonneo