mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-29 18:17:43 +00:00
920 lines
26 KiB
Plaintext
920 lines
26 KiB
Plaintext
#!meta
|
|
|
|
{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}}
|
|
|
|
#!csharp
|
|
|
|
using System.Collections;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Numerics;
|
|
|
|
public class CircularBuffer : IEnumerable<double>
|
|
{
|
|
private readonly double[] _buffer;
|
|
private int _start = 0;
|
|
private int _size = 0;
|
|
|
|
public int Capacity { get; }
|
|
public int Count => _size;
|
|
|
|
public CircularBuffer(int capacity)
|
|
{
|
|
Capacity = capacity;
|
|
_buffer = GC.AllocateArray<double>(capacity, pinned: true);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Add(double item, bool isNew = true)
|
|
{
|
|
if (_size == 0 || isNew)
|
|
{
|
|
if (_size < Capacity)
|
|
{
|
|
_buffer[(_start + _size) % Capacity] = item;
|
|
_size++;
|
|
}
|
|
else
|
|
{
|
|
_buffer[_start] = item;
|
|
_start = (_start + 1) % Capacity;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_buffer[(_start + _size - 1) % Capacity] = item;
|
|
}
|
|
}
|
|
|
|
public double this[int index]
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get
|
|
{
|
|
index = index < 0 ? 0 : (index >= _size ? _size - 1 : index);
|
|
return _buffer[(_start + index) % Capacity];
|
|
}
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
set
|
|
{
|
|
index = index < 0 ? 0 : (index >= _size ? _size - 1 : index);
|
|
_buffer[(_start + index) % Capacity] = value;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private static void ThrowArgumentOutOfRangeException()
|
|
{
|
|
throw new ArgumentOutOfRangeException("index", "Index is out of range.");
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public double Newest()
|
|
{
|
|
if (_size == 0)
|
|
return 0;
|
|
return _buffer[(_start + _size - 1) % Capacity];
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public double Oldest()
|
|
{
|
|
if (_size == 0)
|
|
ThrowInvalidOperationException();
|
|
return _buffer[_start];
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private static void ThrowInvalidOperationException()
|
|
{
|
|
throw new InvalidOperationException("Buffer is empty.");
|
|
}
|
|
|
|
public Enumerator GetEnumerator() => new(this);
|
|
IEnumerator<double> IEnumerable<double>.GetEnumerator() => GetEnumerator();
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
public struct Enumerator : IEnumerator<double>
|
|
{
|
|
private readonly CircularBuffer _buffer;
|
|
private int _index;
|
|
private double _current;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal Enumerator(CircularBuffer buffer)
|
|
{
|
|
_buffer = buffer;
|
|
_index = -1;
|
|
_current = default;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool MoveNext()
|
|
{
|
|
if (_index + 1 >= _buffer._size)
|
|
return false;
|
|
|
|
_index++;
|
|
_current = _buffer[_index];
|
|
return true;
|
|
}
|
|
|
|
public double Current => _current;
|
|
object IEnumerator.Current => Current;
|
|
|
|
public void Reset()
|
|
{
|
|
_index = -1;
|
|
_current = default;
|
|
}
|
|
|
|
public void Dispose() { }
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void CopyTo(double[] destination, int destinationIndex)
|
|
{
|
|
if (_size == 0)
|
|
return;
|
|
|
|
if (_start + _size <= Capacity)
|
|
{
|
|
Array.Copy(_buffer, _start, destination, destinationIndex, _size);
|
|
}
|
|
else
|
|
{
|
|
int firstPartLength = Capacity - _start;
|
|
Array.Copy(_buffer, _start, destination, destinationIndex, firstPartLength);
|
|
Array.Copy(_buffer, 0, destination, destinationIndex + firstPartLength, _size - firstPartLength);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public ReadOnlySpan<double> GetSpan()
|
|
{
|
|
if (_size == 0)
|
|
return ReadOnlySpan<double>.Empty;
|
|
|
|
if (_start + _size <= Capacity)
|
|
{
|
|
return new ReadOnlySpan<double>(_buffer, _start, _size);
|
|
}
|
|
else
|
|
{
|
|
return new ReadOnlySpan<double>(ToArray());
|
|
}
|
|
}
|
|
|
|
public double[] InternalBuffer => _buffer;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public ReadOnlySpan<double> GetInternalSpan() => _buffer.AsSpan();
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Clear()
|
|
{
|
|
Array.Clear(_buffer, 0, _buffer.Length);
|
|
_start = 0;
|
|
_size = 0;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public double Max()
|
|
{
|
|
if (_size == 0)
|
|
ThrowInvalidOperationException();
|
|
|
|
return MaxSimd();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public double Min()
|
|
{
|
|
if (_size == 0)
|
|
ThrowInvalidOperationException();
|
|
|
|
return MinSimd();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public double Sum()
|
|
{
|
|
return SumSimd();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public double Average()
|
|
{
|
|
if (_size == 0)
|
|
ThrowInvalidOperationException();
|
|
|
|
return SumSimd() / _size;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private double MaxSimd()
|
|
{
|
|
var span = GetSpan();
|
|
var vectorSize = Vector<double>.Count;
|
|
var maxVector = new Vector<double>(double.MinValue);
|
|
|
|
int i = 0;
|
|
for (; i <= span.Length - vectorSize; i += vectorSize)
|
|
{
|
|
maxVector = Vector.Max(maxVector, new Vector<double>(span.Slice(i, vectorSize)));
|
|
}
|
|
|
|
double max = double.MinValue;
|
|
for (int j = 0; j < vectorSize; j++)
|
|
{
|
|
max = Math.Max(max, maxVector[j]);
|
|
}
|
|
|
|
for (; i < span.Length; i++)
|
|
{
|
|
max = Math.Max(max, span[i]);
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private double MinSimd()
|
|
{
|
|
var span = GetSpan();
|
|
var vectorSize = Vector<double>.Count;
|
|
var minVector = new Vector<double>(double.MaxValue);
|
|
|
|
int i = 0;
|
|
for (; i <= span.Length - vectorSize; i += vectorSize)
|
|
{
|
|
minVector = Vector.Min(minVector, new Vector<double>(span.Slice(i, vectorSize)));
|
|
}
|
|
|
|
double min = double.MaxValue;
|
|
for (int j = 0; j < vectorSize; j++)
|
|
{
|
|
min = Math.Min(min, minVector[j]);
|
|
}
|
|
|
|
for (; i < span.Length; i++)
|
|
{
|
|
min = Math.Min(min, span[i]);
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private double SumSimd()
|
|
{
|
|
var span = GetSpan();
|
|
var vectorSize = Vector<double>.Count;
|
|
var sumVector = Vector<double>.Zero;
|
|
|
|
int i = 0;
|
|
for (; i <= span.Length - vectorSize; i += vectorSize)
|
|
{
|
|
sumVector += new Vector<double>(span.Slice(i, vectorSize));
|
|
}
|
|
|
|
double sum = 0;
|
|
for (int j = 0; j < vectorSize; j++)
|
|
{
|
|
sum += sumVector[j];
|
|
}
|
|
|
|
for (; i < span.Length; i++)
|
|
{
|
|
sum += span[i];
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
public double[] ToArray()
|
|
{
|
|
double[] array = new double[_size];
|
|
CopyTo(array, 0);
|
|
return array;
|
|
}
|
|
|
|
public void ParallelOperation(Func<double[], int, int, double> operation)
|
|
{
|
|
const int MinimumPartitionSize = 1024;
|
|
|
|
if (_size < MinimumPartitionSize)
|
|
{
|
|
var span = GetSpan();
|
|
var array = span.ToArray();
|
|
operation(array, 0, array.Length);
|
|
return;
|
|
}
|
|
|
|
int partitionCount = Environment.ProcessorCount;
|
|
int partitionSize = _size / partitionCount;
|
|
|
|
if (partitionSize < MinimumPartitionSize)
|
|
{
|
|
partitionCount = Math.Max(1, _size / MinimumPartitionSize);
|
|
partitionSize = _size / partitionCount;
|
|
}
|
|
|
|
var buffer = ToArray();
|
|
var results = new double[partitionCount];
|
|
|
|
Parallel.For(0, partitionCount, i =>
|
|
{
|
|
int start = i * partitionSize;
|
|
int length = (i == partitionCount - 1) ? _size - start : partitionSize;
|
|
results[i] = operation(buffer, start, length);
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#!csharp
|
|
|
|
public interface iTValue {
|
|
DateTime Time { get; }
|
|
double Value { get; }
|
|
bool IsNew { get; }
|
|
bool IsHot { get; }
|
|
}
|
|
|
|
public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) : iTValue {
|
|
public DateTime Time { get; init; } = Time;
|
|
public double Value { get; init; } = Value;
|
|
public bool IsNew { get; init; } = IsNew;
|
|
public bool IsHot { get; init; } = IsHot;
|
|
public DateTime t => Time;
|
|
public double v => Value;
|
|
|
|
public TValue() : this(DateTime.UtcNow, 0) { }
|
|
public TValue(double value, bool isNew=true, bool isHot=true) : this(DateTime.UtcNow, value, IsNew:isNew, IsHot:isHot) { }
|
|
public static implicit operator double(TValue tv) => tv.Value;
|
|
public static implicit operator DateTime(TValue tv) => tv.Time;
|
|
public static implicit operator TValue(double value) => new TValue(DateTime.UtcNow, value);
|
|
|
|
public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}, {Value:F2}, IsNew: {IsNew}, IsHot: {IsHot}]";
|
|
}
|
|
|
|
public delegate void ValueSignal(object source, in ValueEventArgs args);
|
|
|
|
public class ValueEventArgs : EventArgs {
|
|
public TValue Tick { get; }
|
|
public ValueEventArgs(TValue value) { Tick = value; }
|
|
}
|
|
|
|
public class TSeries : List<TValue> {
|
|
private readonly TValue Default = new(DateTime.MinValue, double.NaN);
|
|
public IEnumerable<DateTime> t => this.Select(item => item.t);
|
|
public IEnumerable<double> v => this.Select(item => item.v);
|
|
public TValue Last => Count > 0 ? this[^1] : Default;
|
|
public TValue First => Count > 0 ? this[0] : Default;
|
|
public int Length => Count;
|
|
public string Name { get; set; }
|
|
public event ValueSignal Pub = delegate { };
|
|
|
|
public TSeries() { this.Name = "Data"; }
|
|
|
|
public TSeries (object source) : this() {
|
|
var pubEvent = source.GetType().GetEvent("Pub");
|
|
if (pubEvent != null) {
|
|
/*
|
|
var nameProperty = source.GetType().GetProperty("Name");
|
|
if (nameProperty != null) {
|
|
Name = nameProperty.GetValue(nameProperty)?.ToString()!;
|
|
}
|
|
*/
|
|
pubEvent.AddEventHandler(source, new ValueSignal(Sub));
|
|
}
|
|
}
|
|
public static explicit operator List<double>(TSeries series) => series.Select(item => item.Value).ToList();
|
|
public static explicit operator double[](TSeries series) => series.Select(item => item.Value).ToArray();
|
|
|
|
public new virtual void Add(TValue tick) {
|
|
if (tick.IsNew) { base.Add(tick); }
|
|
else { this[^1] = tick; }
|
|
Pub?.Invoke(this, new ValueEventArgs(tick));
|
|
}
|
|
public virtual void Add(DateTime Time, double Value, bool IsNew=true, bool IsHot=true) => this.Add(new TValue(Time, Value, IsNew, IsHot));
|
|
public virtual void Add(double Value, bool IsNew=true, bool IsHot=true) => this.Add(new TValue(DateTime.UtcNow, Value, IsNew, IsHot));
|
|
|
|
public void Add(IEnumerable<double> values) {
|
|
var valueList = values.ToList();
|
|
int count = valueList.Count;
|
|
DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count);
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
this.Add(startTime, valueList[i]);
|
|
startTime = startTime.AddHours(1);
|
|
}
|
|
}
|
|
public void Add(TSeries series) {
|
|
if (series == this) {
|
|
// If adding itself, create a copy to avoid modification during enumeration
|
|
var copy = new TSeries { Name = this.Name };
|
|
copy.AddRange(this);
|
|
AddRange(copy);
|
|
} else {
|
|
AddRange(series);
|
|
}
|
|
}
|
|
public new virtual void AddRange(IEnumerable<TValue> collection) {
|
|
foreach (var item in collection) {
|
|
Add(item);
|
|
}
|
|
}
|
|
public void Sub(object source, in ValueEventArgs args) { Add(args.Tick); }
|
|
}
|
|
|
|
#!csharp
|
|
|
|
TValue a = new(10.0);
|
|
TSeries ll = new();
|
|
ll.Add(a);
|
|
ll.Add(10);
|
|
TSeries ll1 = new();
|
|
ll.Add(new double[]{1, 2, 3, 4});
|
|
ll.Add(new List<double>{1, 2, 3, 4});
|
|
ll.Add(ll);
|
|
|
|
display((double[])ll);
|
|
|
|
#!csharp
|
|
|
|
public interface iTBar {
|
|
DateTime Time { get; }
|
|
double Open { get; }
|
|
double High { get; }
|
|
double Low { get; }
|
|
double Close { get; }
|
|
double Volume { get; }
|
|
bool IsNew { get; }
|
|
}
|
|
|
|
public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) :iTBar {
|
|
public DateTime Time { get; init; } = Time;
|
|
public double Open { get; init; } = Open;
|
|
public double High { get; init; } = High;
|
|
public double Low { get; init; } = Low;
|
|
public double Close { get; init; } = Close;
|
|
public double Volume { get; init; } = Volume;
|
|
public bool IsNew { get; init; } = IsNew;
|
|
|
|
public double HL2 => (High + Low) * 0.5;
|
|
public double OC2 => (Open + Close) * 0.5;
|
|
public double OHL3 => (Open + High + Low) /3;
|
|
public double HLC3 => (High + Low + Close) /3;
|
|
public double OHLC4 => (Open + High + Low + Close) * 0.25;
|
|
public double HLCC4 => (High + Low + Close + Close) * 0.25;
|
|
|
|
public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { }
|
|
public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { }
|
|
|
|
// when TBar casts to double, it returns its Close
|
|
public static implicit operator double(TBar bar) => bar.Close;
|
|
public static implicit operator DateTime(TBar tv) => tv.Time;
|
|
|
|
// castings for sloppy people - a single double injected into a TBar, and a single TValue injected into a TBar
|
|
public TBar (double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) {}
|
|
public TBar (TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) {}
|
|
|
|
public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]";
|
|
}
|
|
|
|
public delegate void BarSignal(object source, in TBarEventArgs args);
|
|
|
|
public class TBarEventArgs : EventArgs {
|
|
public TBar Bar { get; }
|
|
public TBarEventArgs(TBar bar) { Bar = bar; }
|
|
}
|
|
|
|
public class TBarSeries : List<TBar> {
|
|
private readonly TBar Default = new(DateTime.MinValue, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN);
|
|
|
|
public TSeries Open;
|
|
public TSeries High;
|
|
public TSeries Low;
|
|
public TSeries Close;
|
|
public TSeries Volume;
|
|
|
|
|
|
public TBar Last => Count > 0 ? this[^1] : Default;
|
|
public TBar First => Count > 0 ? this[0] : Default;
|
|
public int Length => Count;
|
|
public string Name { get; set; }
|
|
public event BarSignal Pub = delegate { };
|
|
|
|
public TBarSeries() {
|
|
this.Name = "Bar";
|
|
Open = new();
|
|
High = new();
|
|
Low = new();
|
|
Close = new();
|
|
Volume = new();
|
|
}
|
|
public TBarSeries (object source) : this() {
|
|
var pubEvent = source.GetType().GetEvent("Pub");
|
|
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
|
}
|
|
|
|
public new virtual void Add(TBar bar) {
|
|
if (bar.IsNew) { base.Add(bar); }else { this[^1] = bar; }
|
|
Pub?.Invoke(this, new TBarEventArgs(bar));
|
|
|
|
Open.Add(bar.Time, bar.Open, IsNew: bar.IsNew, IsHot: true);
|
|
High.Add(bar.Time, bar.High, IsNew: bar.IsNew, IsHot: true);
|
|
Low.Add(bar.Time, bar.Low, IsNew: bar.IsNew, IsHot: true);
|
|
Close.Add(bar.Time, bar.Close, IsNew: bar.IsNew, IsHot: true);
|
|
Volume.Add(bar.Time, bar.Volume, IsNew: bar.IsNew, IsHot: true);
|
|
}
|
|
public void Add(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) =>
|
|
this.Add(new TBar(Time, Open, High, Low, Close, Volume, IsNew));
|
|
|
|
public void Add(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) =>
|
|
this.Add(new TBar(DateTime.Now, Open, High, Low, Close, Volume, IsNew));
|
|
|
|
public void Add(TBarSeries series) {
|
|
if (series == this) {
|
|
// If adding itself, create a copy to avoid modification during enumeration
|
|
var copy = new TBarSeries { Name = this.Name };
|
|
copy.AddRange(this);
|
|
AddRange(copy);
|
|
} else {
|
|
AddRange(series);
|
|
}
|
|
}
|
|
public new virtual void AddRange(IEnumerable<TBar> collection) {
|
|
foreach (var item in collection) {
|
|
Add(item);
|
|
}
|
|
}
|
|
|
|
public void Sub(object source, in TBarEventArgs args) {
|
|
Add(args.Bar);
|
|
}
|
|
}
|
|
|
|
#!csharp
|
|
|
|
TBarSeries ll = new();
|
|
ll.Add(1,2,3,4,5);
|
|
ll.Add(1,2,3,4,5);
|
|
ll.Add(1,2,3,4,5);
|
|
ll.Add(ll);
|
|
//ll.Add(a);
|
|
//ll.Add(10);
|
|
//TSeries ll1 = new();
|
|
//ll.Add(new double[]{1, 2, 3, 4});
|
|
//ll.Add(new List<double>{1, 2, 3, 4});
|
|
//ll.Add(ll);
|
|
|
|
display(ll.Open.Last.Value);
|
|
|
|
#!csharp
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.CommandLine.Invocation;
|
|
|
|
public abstract class AbstractBase : iTValue
|
|
{
|
|
public DateTime Time { get; set; }
|
|
public double Value { get; set; }
|
|
public bool IsNew { get; set; }
|
|
public bool IsHot { get; set; }
|
|
|
|
public TValue Input { get; set; }
|
|
|
|
public TValue Tick => new(Time, Value, IsNew, IsHot); // Stores the current value of indicator
|
|
public event ValueSignal Pub = delegate { }; // Publisher of generated values
|
|
|
|
protected int _index; //tracking the position of output
|
|
protected double _lastValidValue;
|
|
// other _internal vars defined here
|
|
|
|
protected AbstractBase()
|
|
{ //add parameters into constructor
|
|
}
|
|
|
|
public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick);
|
|
|
|
public virtual void Init()
|
|
{
|
|
_index = 0;
|
|
_lastValidValue = 0;
|
|
}
|
|
|
|
public virtual TValue Calc(TValue input)
|
|
{
|
|
Input = input;
|
|
if (double.IsNaN(input.Value) || double.IsInfinity(input.Value))
|
|
{
|
|
return Process(new TValue(input.Time, GetLastValid(), input.IsNew, input.IsHot));
|
|
}
|
|
this.Value = Calculation();
|
|
return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot));
|
|
}
|
|
|
|
protected virtual double GetLastValid()
|
|
{
|
|
// should return last valid value
|
|
return _lastValidValue;
|
|
}
|
|
protected abstract void ManageState(bool isNew);
|
|
protected abstract double Calculation();
|
|
protected virtual TValue Process(TValue value)
|
|
{
|
|
this.Time = value.Time;
|
|
this.Value = value.Value;
|
|
this.IsNew = value.IsNew;
|
|
this.IsHot = value.IsHot;
|
|
Pub?.Invoke(this, new ValueEventArgs(value));
|
|
return value;
|
|
}
|
|
}
|
|
|
|
#!csharp
|
|
|
|
using System;
|
|
|
|
public class EmaCalc : AbstractBase
|
|
{
|
|
private readonly int _period;
|
|
private CircularBuffer _sma;
|
|
private double _lastEma, _p_lastEma;
|
|
private double _k, _e, _p_e;
|
|
private bool _isInitialized, _useSma;
|
|
|
|
public EmaCalc(int period, bool useSma = true) : base()
|
|
{
|
|
if (period < 1) {
|
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
|
}
|
|
_period = period;
|
|
_useSma = useSma;
|
|
_sma = new(period);
|
|
|
|
Init();
|
|
}
|
|
|
|
public EmaCalc(object source, int period, bool useSma = true) : this(period, useSma)
|
|
{
|
|
var pubEvent = source.GetType().GetEvent("Pub");
|
|
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
|
}
|
|
|
|
public override void Init()
|
|
{
|
|
base.Init();
|
|
_k = 2.0 / (_period + 1);
|
|
_e = 1.0;
|
|
_lastEma = 0;
|
|
_isInitialized = false;
|
|
_sma = new(_period);
|
|
}
|
|
|
|
protected override void ManageState(bool isNew) {
|
|
if (isNew) {
|
|
_p_lastEma = _lastEma;
|
|
_p_e = _e;
|
|
_index++;
|
|
} else {
|
|
_lastEma = _p_lastEma;
|
|
_e = _p_e;
|
|
}
|
|
}
|
|
|
|
protected override double GetLastValid() {
|
|
return _lastEma;
|
|
}
|
|
|
|
protected override double Calculation() {
|
|
double result, _ema;
|
|
ManageState(Input.IsNew);
|
|
|
|
// when _UseSma == true, use SMA calculation until we have enough data points
|
|
if (!_isInitialized && _useSma) {
|
|
_sma.Add(Input.Value, Input.IsNew);
|
|
_ema = _sma.Average();
|
|
result = _ema;
|
|
if (_index >= _period) {
|
|
_isInitialized = true;
|
|
}
|
|
} else {
|
|
// dunamic k when within period; (index is zero-based, therefore +2)
|
|
double _dk = (_index +1 >= _period) ? _k : 2.0 / (_index + 2);
|
|
|
|
// compensator for early ema values
|
|
_e = (_e > 1e-10) ? (1 - _dk) * _e : 0;
|
|
|
|
_ema = _dk * (Input.Value - _lastEma) + _lastEma;
|
|
|
|
// _useSma decides if we use compensator or not
|
|
result = (_useSma || _e == 0)? _ema : _ema / (1 - _e);
|
|
}
|
|
|
|
_lastEma = _ema;
|
|
IsHot = _index >= _period;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
#!csharp
|
|
|
|
double[] input = new[]{1.0, 2,3,4,5};
|
|
|
|
TSeries mm = new();
|
|
mm.Add(input);
|
|
mm.Display();
|
|
|
|
#!csharp
|
|
|
|
public class Convolution : AbstractBase
|
|
{
|
|
private readonly double[] _kernel;
|
|
private readonly int _kernelSize;
|
|
private CircularBuffer _buffer;
|
|
private double[] _normalizedKernel;
|
|
|
|
public Convolution(double[] kernel)
|
|
{
|
|
if (kernel == null || kernel.Length == 0)
|
|
{
|
|
throw new ArgumentException("Kernel must not be null or empty.", nameof(kernel));
|
|
}
|
|
_kernel = kernel;
|
|
_kernelSize = kernel.Length;
|
|
_buffer = new CircularBuffer(_kernelSize);
|
|
_normalizedKernel = new double[_kernelSize];
|
|
Init();
|
|
}
|
|
|
|
public Convolution(object source, double[] kernel) : this(kernel)
|
|
{
|
|
var pubEvent = source.GetType().GetEvent("Pub");
|
|
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
|
}
|
|
|
|
private void Init()
|
|
{
|
|
_index = 0;
|
|
_lastValidValue = 0;
|
|
Array.Copy(_kernel, _normalizedKernel, _kernelSize);
|
|
}
|
|
|
|
protected override void ManageState(bool isNew)
|
|
{
|
|
if (isNew)
|
|
{
|
|
_lastValidValue = Input.Value;
|
|
_index++;
|
|
}
|
|
}
|
|
|
|
protected override double GetLastValid()
|
|
{
|
|
return _lastValidValue;
|
|
}
|
|
|
|
protected override double Calculation()
|
|
{
|
|
ManageState(Input.IsNew);
|
|
|
|
_buffer.Add(Input.Value, Input.IsNew);
|
|
|
|
// Normalize kernel on each calculation until buffer is full
|
|
if (_index <= _kernelSize)
|
|
{
|
|
NormalizeKernel();
|
|
}
|
|
|
|
double result = ConvolveBuffer();
|
|
IsHot = _index >= _kernelSize;
|
|
|
|
return result;
|
|
}
|
|
|
|
private void NormalizeKernel()
|
|
{
|
|
int activeLength = Math.Min(_index, _kernelSize);
|
|
double sum = 0;
|
|
|
|
// Calculate the sum of the active kernel elements
|
|
for (int i = 0; i < activeLength; i++)
|
|
{
|
|
sum += _kernel[i];
|
|
}
|
|
|
|
// Normalize the kernel or set equal weights if the sum is zero
|
|
double normalizationFactor = (sum != 0) ? sum : activeLength;
|
|
for (int i = 0; i < activeLength; i++)
|
|
{
|
|
_normalizedKernel[i] = _kernel[i] / normalizationFactor;
|
|
}
|
|
|
|
// Set the rest of the normalized kernel to zero
|
|
Array.Clear(_normalizedKernel, activeLength, _kernelSize - activeLength);
|
|
}
|
|
|
|
private double ConvolveBuffer()
|
|
{
|
|
double sum = 0;
|
|
var bufferSpan = _buffer.GetSpan();
|
|
int activeLength = Math.Min(_index, _kernelSize);
|
|
|
|
for (int i = 0; i < activeLength; i++)
|
|
{
|
|
sum += bufferSpan[activeLength - 1 - i] * _normalizedKernel[i];
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
}
|
|
|
|
#!csharp
|
|
|
|
public class Wma : AbstractBase
|
|
{
|
|
private readonly int _period;
|
|
private readonly Convolution _convolution;
|
|
|
|
public Wma(int period)
|
|
{
|
|
if (period < 1)
|
|
{
|
|
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
|
|
}
|
|
_period = period;
|
|
_convolution = new Convolution(GenerateWmaKernel(_period));
|
|
Init();
|
|
}
|
|
|
|
public Wma(object source, int period) : this(period)
|
|
{
|
|
var pubEvent = source.GetType().GetEvent("Pub");
|
|
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
|
}
|
|
|
|
private static double[] GenerateWmaKernel(int period)
|
|
{
|
|
double[] kernel = new double[period];
|
|
double weightSum = period * (period + 1) / 2.0;
|
|
|
|
for (int i = 0; i < period; i++)
|
|
{
|
|
kernel[i] = (period - i) / weightSum;
|
|
}
|
|
|
|
return kernel;
|
|
}
|
|
|
|
private new void Init()
|
|
{
|
|
base.Init();
|
|
_convolution.Init();
|
|
}
|
|
|
|
protected override void ManageState(bool isNew)
|
|
{
|
|
if (isNew)
|
|
{
|
|
_lastValidValue = Input.Value;
|
|
_index++;
|
|
}
|
|
}
|
|
|
|
protected override double GetLastValid()
|
|
{
|
|
return _lastValidValue;
|
|
}
|
|
|
|
protected override double Calculation()
|
|
{
|
|
ManageState(Input.IsNew);
|
|
|
|
// Use Convolution for calculation
|
|
TValue convolutionResult = _convolution.Calc(Input);
|
|
|
|
double result = convolutionResult.Value;
|
|
IsHot = _index >= _period;
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
#!csharp
|
|
|
|
TSeries input = new();
|
|
double[] kernel = new[]{4.0,3,2,1};
|
|
|
|
Wma cc = new(input, 5);
|
|
TSeries output = new(cc);
|
|
|
|
input.Add(new double[]{1.0,2,3,4,5,6,7,8});
|
|
|
|
|
|
display((double[])output);
|