#!meta {"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}} #!csharp using System.Collections; using System.Runtime.CompilerServices; using System.Numerics; public class CircularBuffer : IEnumerable { 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(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 IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public struct Enumerator : IEnumerator { 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 GetSpan() { if (_size == 0) return ReadOnlySpan.Empty; if (_start + _size <= Capacity) { return new ReadOnlySpan(_buffer, _start, _size); } else { return new ReadOnlySpan(ToArray()); } } public double[] InternalBuffer => _buffer; [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan 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.Count; var maxVector = new Vector(double.MinValue); int i = 0; for (; i <= span.Length - vectorSize; i += vectorSize) { maxVector = Vector.Max(maxVector, new Vector(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.Count; var minVector = new Vector(double.MaxValue); int i = 0; for (; i <= span.Length - vectorSize; i += vectorSize) { minVector = Vector.Min(minVector, new Vector(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.Count; var sumVector = Vector.Zero; int i = 0; for (; i <= span.Length - vectorSize; i += vectorSize) { sumVector += new Vector(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 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 { private readonly TValue Default = new(DateTime.MinValue, double.NaN); public IEnumerable t => this.Select(item => item.t); public IEnumerable 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(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 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 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{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 { 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 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{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);