在实践中,这就是我发现即使对数百万个样本也有效的方法。它计算运行移动平均线,比我尝试过的任何其他方法都快。
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上的 CrossOver 检测等的实现。与此问答相关的代码:
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;
}