mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 10:38:05 +00:00
Refactor trend indicators to use record structs for state management
This commit is contained in:
+29
-11
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
@@ -13,7 +14,7 @@ namespace QuanTAlib;
|
||||
public class GBM : IFeed
|
||||
#pragma warning restore S101
|
||||
{
|
||||
private readonly Random _rnd;
|
||||
private readonly Random? _rnd;
|
||||
|
||||
private double _lastPrice;
|
||||
private long _lastTime;
|
||||
@@ -52,7 +53,7 @@ public class GBM : IFeed
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(startPrice);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(sigma);
|
||||
|
||||
_rnd = seed.HasValue ? new Random(seed.Value) : new Random();
|
||||
_rnd = seed.HasValue ? new Random(seed.Value) : null;
|
||||
_lastPrice = startPrice;
|
||||
_lastTime = DateTime.UtcNow.Ticks;
|
||||
|
||||
@@ -71,6 +72,23 @@ public class GBM : IFeed
|
||||
_vol = sigma * Math.Sqrt(dt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random double in [0, 1) using either the seeded Random or RandomNumberGenerator.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double NextDouble()
|
||||
{
|
||||
if (_rnd != null)
|
||||
{
|
||||
return _rnd.NextDouble();
|
||||
}
|
||||
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
RandomNumberGenerator.Fill(buffer);
|
||||
ulong ul = BitConverter.ToUInt64(buffer);
|
||||
return (ul >> 11) * (1.0 / (1ul << 53));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates next standard normal using Box-Muller transform with caching.
|
||||
/// </summary>
|
||||
@@ -83,8 +101,8 @@ public class GBM : IFeed
|
||||
return _cachedZ;
|
||||
}
|
||||
|
||||
double u1 = 1.0 - _rnd.NextDouble(); // nosemgrep
|
||||
double u2 = 1.0 - _rnd.NextDouble(); // nosemgrep
|
||||
double u1 = 1.0 - NextDouble();
|
||||
double u2 = 1.0 - NextDouble();
|
||||
double mag = Math.Sqrt(-2.0 * Math.Log(u1));
|
||||
double angle = 2.0 * Math.PI * u2;
|
||||
|
||||
@@ -110,12 +128,12 @@ public class GBM : IFeed
|
||||
|
||||
double z = NextNormal();
|
||||
double price = _lastPrice * Math.Exp(_drift + _vol * z);
|
||||
double volume = 1000 + _rnd.NextDouble() * 1000;
|
||||
double volume = 1000 + NextDouble() * 1000;
|
||||
|
||||
double open = _lastPrice;
|
||||
double close = price;
|
||||
double high = Math.Max(open, close) * (1.0 + _rnd.NextDouble() * 0.01);
|
||||
double low = Math.Min(open, close) * (1.0 - _rnd.NextDouble() * 0.01);
|
||||
double high = Math.Max(open, close) * (1.0 + NextDouble() * 0.01);
|
||||
double low = Math.Min(open, close) * (1.0 - NextDouble() * 0.01);
|
||||
|
||||
_currentBar = new TBar(currentTime, open, high, low, close, volume);
|
||||
_hasCurrentBar = true;
|
||||
@@ -128,7 +146,7 @@ public class GBM : IFeed
|
||||
// Update current bar (intra-bar tick)
|
||||
double z = NextNormal();
|
||||
double price = _lastPrice * Math.Exp(_drift + _vol * z);
|
||||
double additionalVolume = 1000 + _rnd.NextDouble() * 1000;
|
||||
double additionalVolume = 1000 + NextDouble() * 1000;
|
||||
|
||||
var bar = _currentBar;
|
||||
double newClose = price;
|
||||
@@ -190,9 +208,9 @@ public class GBM : IFeed
|
||||
double open = currentPrice;
|
||||
double close = price;
|
||||
|
||||
double rnd1 = _rnd.NextDouble();
|
||||
double rnd2 = _rnd.NextDouble();
|
||||
double rnd3 = _rnd.NextDouble();
|
||||
double rnd1 = NextDouble();
|
||||
double rnd2 = NextDouble();
|
||||
double rnd3 = NextDouble();
|
||||
|
||||
t[i] = currentTime;
|
||||
o[i] = open;
|
||||
|
||||
+18
-5
@@ -31,7 +31,10 @@ public sealed class Alma : ITValuePublisher
|
||||
private readonly double[] _weights;
|
||||
private readonly double _weightSum;
|
||||
private readonly RingBuffer _buffer;
|
||||
private double _lastValidValue;
|
||||
|
||||
private record struct State(double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the indicator.
|
||||
@@ -99,15 +102,24 @@ public sealed class Alma : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(input))
|
||||
{
|
||||
_lastValidValue = input;
|
||||
_state.LastValidValue = input;
|
||||
return input;
|
||||
}
|
||||
return _lastValidValue;
|
||||
return _state.LastValidValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double val = GetValidValue(input.Value);
|
||||
_buffer.Add(val, isNew);
|
||||
|
||||
@@ -140,7 +152,7 @@ public sealed class Alma : ITValuePublisher
|
||||
|
||||
// Restore state
|
||||
_buffer.Clear();
|
||||
_lastValidValue = 0;
|
||||
_state = default;
|
||||
|
||||
// Replay last part to restore buffer state
|
||||
int startIndex = Math.Max(0, len - _period);
|
||||
@@ -301,7 +313,8 @@ public sealed class Alma : ITValuePublisher
|
||||
public void Reset()
|
||||
{
|
||||
_buffer.Clear();
|
||||
_lastValidValue = 0;
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-15
@@ -25,10 +25,9 @@ public sealed class Conv : ITValuePublisher
|
||||
private readonly double[] _kernel;
|
||||
private readonly RingBuffer _buffer;
|
||||
|
||||
private double _lastValidValue;
|
||||
|
||||
// State for bar correction
|
||||
private double _p_lastValidValue;
|
||||
private record struct State(double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
public string Name { get; }
|
||||
public TValue Last { get; private set; }
|
||||
@@ -45,8 +44,8 @@ public sealed class Conv : ITValuePublisher
|
||||
Array.Copy(kernel, _kernel, _period);
|
||||
_buffer = new RingBuffer(_period);
|
||||
Name = $"Conv({_period})";
|
||||
_lastValidValue = double.NaN;
|
||||
_p_lastValidValue = double.NaN;
|
||||
_state.LastValidValue = double.NaN;
|
||||
_p_state.LastValidValue = double.NaN;
|
||||
}
|
||||
|
||||
public Conv(ITValuePublisher source, double[] kernel) : this(kernel)
|
||||
@@ -59,10 +58,10 @@ public sealed class Conv : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(input))
|
||||
{
|
||||
_lastValidValue = input;
|
||||
_state.LastValidValue = input;
|
||||
return input;
|
||||
}
|
||||
return _lastValidValue;
|
||||
return _state.LastValidValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -70,11 +69,11 @@ public sealed class Conv : ITValuePublisher
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = _p_lastValidValue;
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double val = GetValidValue(input.Value);
|
||||
@@ -145,14 +144,14 @@ public sealed class Conv : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(sourceValues[i]))
|
||||
{
|
||||
_lastValidValue = sourceValues[i];
|
||||
_state.LastValidValue = sourceValues[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = double.NaN;
|
||||
_state.LastValidValue = double.NaN;
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
@@ -168,7 +167,7 @@ public sealed class Conv : ITValuePublisher
|
||||
Last = new TValue(source.Times[len - 1], vSpan[len - 1]);
|
||||
|
||||
// Save state for isNew=false
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
_p_state = _state;
|
||||
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
@@ -241,8 +240,8 @@ public sealed class Conv : ITValuePublisher
|
||||
public void Reset()
|
||||
{
|
||||
_buffer.Clear();
|
||||
_lastValidValue = double.NaN;
|
||||
_p_lastValidValue = double.NaN;
|
||||
_state.LastValidValue = double.NaN;
|
||||
_p_state.LastValidValue = double.NaN;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-20
@@ -24,28 +24,9 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Dema : ITValuePublisher
|
||||
{
|
||||
private struct EmaState : IEquatable<EmaState>
|
||||
private record struct EmaState(double Ema, double E, bool IsHot, bool IsCompensated)
|
||||
{
|
||||
public double Ema;
|
||||
public double E;
|
||||
public bool IsHot;
|
||||
public bool IsCompensated;
|
||||
|
||||
public static EmaState New() => new() { Ema = 0, E = 1.0, IsHot = false, IsCompensated = false };
|
||||
|
||||
public override bool Equals(object? obj) => obj is EmaState other && Equals(other);
|
||||
|
||||
public bool Equals(EmaState other) =>
|
||||
Ema == other.Ema &&
|
||||
E == other.E &&
|
||||
IsHot == other.IsHot &&
|
||||
IsCompensated == other.IsCompensated;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Ema, E, IsHot, IsCompensated);
|
||||
|
||||
public static bool operator ==(EmaState left, EmaState right) => left.Equals(right);
|
||||
|
||||
public static bool operator !=(EmaState left, EmaState right) => !left.Equals(right);
|
||||
}
|
||||
|
||||
private readonly double _alpha;
|
||||
|
||||
+1
-20
@@ -27,28 +27,9 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ema : ITValuePublisher
|
||||
{
|
||||
private struct State : IEquatable<State>
|
||||
private record struct State(double Ema, double E, bool IsHot, bool IsCompensated)
|
||||
{
|
||||
public double Ema;
|
||||
public double E;
|
||||
public bool IsHot;
|
||||
public bool IsCompensated;
|
||||
|
||||
public static State New() => new() { Ema = 0, E = 1.0, IsHot = false, IsCompensated = false };
|
||||
|
||||
public override bool Equals(object? obj) => obj is State other && Equals(other);
|
||||
|
||||
public bool Equals(State other) =>
|
||||
Ema == other.Ema &&
|
||||
E == other.E &&
|
||||
IsHot == other.IsHot &&
|
||||
IsCompensated == other.IsCompensated;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Ema, E, IsHot, IsCompensated);
|
||||
|
||||
public static bool operator ==(State left, State right) => left.Equals(right);
|
||||
|
||||
public static bool operator !=(State left, State right) => !left.Equals(right);
|
||||
}
|
||||
|
||||
private readonly double _alpha;
|
||||
|
||||
+52
-37
@@ -24,12 +24,10 @@ public sealed class Kama : ITValuePublisher
|
||||
private readonly double _fastAlpha;
|
||||
private readonly double _slowAlpha;
|
||||
private readonly RingBuffer _buffer;
|
||||
private double _kama;
|
||||
private double _p_kama;
|
||||
private double _volatilitySum;
|
||||
private double _p_volatilitySum;
|
||||
private double _lastDiffOut;
|
||||
private double _lastValidValue;
|
||||
|
||||
private record struct State(double Kama, double VolatilitySum, double NextDiffOut, double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the indicator.
|
||||
@@ -74,8 +72,10 @@ public sealed class Kama : ITValuePublisher
|
||||
_slowAlpha = 2.0 / (slowPeriod + 1);
|
||||
|
||||
Name = $"Kama({period}, {fastPeriod}, {slowPeriod})";
|
||||
_kama = double.NaN;
|
||||
_lastValidValue = double.NaN;
|
||||
_state.Kama = double.NaN;
|
||||
_state.LastValidValue = double.NaN;
|
||||
_p_state.Kama = double.NaN;
|
||||
_p_state.LastValidValue = double.NaN;
|
||||
}
|
||||
|
||||
public Kama(ITValuePublisher source, int period = 10, int fastPeriod = 2, int slowPeriod = 30)
|
||||
@@ -89,15 +89,24 @@ public sealed class Kama : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(input))
|
||||
{
|
||||
_lastValidValue = input;
|
||||
_state.LastValidValue = input;
|
||||
return input;
|
||||
}
|
||||
return _lastValidValue;
|
||||
return _state.LastValidValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double val = GetValidValue(input.Value);
|
||||
if (double.IsNaN(val))
|
||||
{
|
||||
@@ -108,55 +117,58 @@ public sealed class Kama : ITValuePublisher
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
_p_kama = _kama;
|
||||
_p_volatilitySum = _volatilitySum;
|
||||
|
||||
bool wasFull = _buffer.IsFull;
|
||||
double removed = _buffer.Add(val);
|
||||
_buffer.Add(val);
|
||||
|
||||
if (wasFull)
|
||||
{
|
||||
double diff_out = Math.Abs(removed - _buffer[0]);
|
||||
_lastDiffOut = diff_out;
|
||||
|
||||
double diff_out = _p_state.NextDiffOut;
|
||||
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
||||
_volatilitySum += diff_in - diff_out;
|
||||
_state.VolatilitySum += diff_in - diff_out;
|
||||
|
||||
// Calculate NextDiffOut for the next step
|
||||
// NextDiffOut = abs(buffer[0] - buffer[1])
|
||||
_state.NextDiffOut = Math.Abs(_buffer[0] - _buffer[1]);
|
||||
}
|
||||
else if (_buffer.Count >= 2)
|
||||
{
|
||||
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
||||
_volatilitySum += diff_in;
|
||||
_lastDiffOut = 0;
|
||||
_state.VolatilitySum += diff_in;
|
||||
|
||||
if (_buffer.IsFull)
|
||||
{
|
||||
// Buffer just became full.
|
||||
// NextDiffOut = abs(buffer[0] - buffer[1])
|
||||
_state.NextDiffOut = Math.Abs(_buffer[0] - _buffer[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore state
|
||||
_kama = _p_kama;
|
||||
_buffer.UpdateNewest(val);
|
||||
|
||||
if (_buffer.IsFull)
|
||||
{
|
||||
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
||||
_volatilitySum = _p_volatilitySum + diff_in - _lastDiffOut;
|
||||
// Use NextDiffOut from _p_state (which is the correct DiffOut for this transition)
|
||||
_state.VolatilitySum = _p_state.VolatilitySum + diff_in - _p_state.NextDiffOut;
|
||||
}
|
||||
else if (_buffer.Count >= 2)
|
||||
{
|
||||
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
||||
_volatilitySum = _p_volatilitySum + diff_in;
|
||||
_state.VolatilitySum = _p_state.VolatilitySum + diff_in;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate KAMA
|
||||
if (double.IsNaN(_kama))
|
||||
if (double.IsNaN(_state.Kama))
|
||||
{
|
||||
_kama = val;
|
||||
_p_kama = val; // Ensure p_kama is initialized
|
||||
_state.Kama = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
double change = Math.Abs(_buffer[^1] - _buffer[0]);
|
||||
double volatility = _volatilitySum;
|
||||
double volatility = _state.VolatilitySum;
|
||||
|
||||
// Avoid division by zero
|
||||
double er = (volatility > double.Epsilon) ? change / volatility : 0.0;
|
||||
@@ -166,10 +178,16 @@ public sealed class Kama : ITValuePublisher
|
||||
double sc = er * (_fastAlpha - _slowAlpha) + _slowAlpha;
|
||||
sc *= sc;
|
||||
|
||||
_kama = _p_kama + sc * (val - _p_kama);
|
||||
double prevKama = _p_state.Kama;
|
||||
if (double.IsNaN(prevKama))
|
||||
{
|
||||
prevKama = _state.Kama;
|
||||
}
|
||||
|
||||
_state.Kama = prevKama + sc * (val - prevKama);
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, _kama);
|
||||
Last = new TValue(input.Time, _state.Kama);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
}
|
||||
@@ -290,7 +308,6 @@ public sealed class Kama : ITValuePublisher
|
||||
double change = 0;
|
||||
change = (count == bufSize) ? Math.Abs(val - buffer[bufferIdx]) : Math.Abs(val - buffer[0]);
|
||||
|
||||
|
||||
double er = (volatilitySum > double.Epsilon) ? change / volatilitySum : 0.0;
|
||||
if (er > 1.0) er = 1.0;
|
||||
|
||||
@@ -306,12 +323,10 @@ public sealed class Kama : ITValuePublisher
|
||||
public void Reset()
|
||||
{
|
||||
_buffer.Clear();
|
||||
_kama = double.NaN;
|
||||
_p_kama = double.NaN;
|
||||
_volatilitySum = 0;
|
||||
_p_volatilitySum = 0;
|
||||
_lastDiffOut = 0;
|
||||
_lastValidValue = double.NaN;
|
||||
_state = default;
|
||||
_state.Kama = double.NaN;
|
||||
_state.LastValidValue = double.NaN;
|
||||
_p_state = _state;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-43
@@ -34,15 +34,10 @@ public sealed class Lsma : ITValuePublisher
|
||||
private readonly double _sum_x;
|
||||
private readonly double _denominator;
|
||||
|
||||
private double _sum_y;
|
||||
private double _sum_xy;
|
||||
private record struct State(double SumY, double SumXY, double LastVal, double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private double _p_sum_y;
|
||||
private double _p_sum_xy;
|
||||
private double _p_last_val;
|
||||
|
||||
private double _lastValidValue;
|
||||
private double _p_lastValidValue;
|
||||
private int _tickCount;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
@@ -100,10 +95,10 @@ public sealed class Lsma : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(input))
|
||||
{
|
||||
_lastValidValue = input;
|
||||
_state.LastValidValue = input;
|
||||
return input;
|
||||
}
|
||||
return _lastValidValue;
|
||||
return _state.LastValidValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -112,24 +107,24 @@ public sealed class Lsma : ITValuePublisher
|
||||
if (_buffer.IsFull)
|
||||
{
|
||||
double oldest = _buffer.Oldest;
|
||||
double prev_sum_y = _sum_y;
|
||||
double prev_sum_y = _state.SumY;
|
||||
|
||||
// O(1) update for sum_xy
|
||||
// sum_xy_new = sum_xy_old + sum_y_prev - n * oldest
|
||||
_sum_xy = _sum_xy + prev_sum_y - _period * oldest;
|
||||
_state.SumXY = _state.SumXY + prev_sum_y - _period * oldest;
|
||||
|
||||
// O(1) update for sum_y
|
||||
_sum_y = _sum_y - oldest + val;
|
||||
_state.SumY = _state.SumY - oldest + val;
|
||||
|
||||
_buffer.Add(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffer.Add(val);
|
||||
_sum_y += val;
|
||||
_state.SumY += val;
|
||||
|
||||
// Recalculate sum_xy from scratch during warmup
|
||||
_sum_xy = 0;
|
||||
_state.SumXY = 0;
|
||||
var span = _buffer.GetSpan();
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
@@ -138,7 +133,7 @@ public sealed class Lsma : ITValuePublisher
|
||||
// index j in buffer corresponds to x = count - 1 - j
|
||||
// sum_xy = sum(x * y)
|
||||
int x = span.Length - 1 - i;
|
||||
_sum_xy += x * span[i];
|
||||
_state.SumXY += x * span[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,13 +147,13 @@ public sealed class Lsma : ITValuePublisher
|
||||
|
||||
private void Resync()
|
||||
{
|
||||
_sum_y = _buffer.Sum;
|
||||
_sum_xy = 0;
|
||||
_state.SumY = _buffer.Sum;
|
||||
_state.SumXY = 0;
|
||||
var span = _buffer.GetSpan();
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
int x = span.Length - 1 - i;
|
||||
_sum_xy += x * span[i];
|
||||
_state.SumXY += x * span[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,25 +165,23 @@ public sealed class Lsma : ITValuePublisher
|
||||
double val = GetValidValue(input.Value);
|
||||
UpdateState(val);
|
||||
|
||||
_p_sum_y = _sum_y;
|
||||
_p_sum_xy = _sum_xy;
|
||||
_p_last_val = val;
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
_p_state = _state;
|
||||
_state.LastVal = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = _p_lastValidValue;
|
||||
_state.LastValidValue = _p_state.LastValidValue;
|
||||
double val = GetValidValue(input.Value);
|
||||
|
||||
// For isNew=false, we update the current bar.
|
||||
// sum_xy remains constant because it depends on the previous window state which hasn't changed.
|
||||
// sum_y updates to reflect the change in the newest value.
|
||||
|
||||
_sum_y = _p_sum_y - _p_last_val + val;
|
||||
_sum_xy = _p_sum_xy; // Restore sum_xy to the state after the shift
|
||||
_state.SumY = _p_state.SumY - _p_state.LastVal + val;
|
||||
_state.SumXY = _p_state.SumXY; // Restore sum_xy to the state after the shift
|
||||
|
||||
_buffer.UpdateNewest(val);
|
||||
_p_last_val = val;
|
||||
_state.LastVal = val;
|
||||
}
|
||||
|
||||
double result;
|
||||
@@ -218,8 +211,8 @@ public sealed class Lsma : ITValuePublisher
|
||||
}
|
||||
else
|
||||
{
|
||||
double m = (n * _sum_xy - sx * _sum_y) / denom;
|
||||
double b = (_sum_y - m * sx) / n;
|
||||
double m = (n * _state.SumXY - sx * _state.SumY) / denom;
|
||||
double b = (_state.SumY - m * sx) / n;
|
||||
|
||||
// LSMA = b - m * offset
|
||||
result = b - m * _offset;
|
||||
@@ -261,17 +254,17 @@ public sealed class Lsma : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(source.Values[i]))
|
||||
{
|
||||
_lastValidValue = source.Values[i];
|
||||
_state.LastValidValue = source.Values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = 0;
|
||||
_state.LastValidValue = 0;
|
||||
}
|
||||
|
||||
double lastProcessedValue = _lastValidValue;
|
||||
double lastProcessedValue = _state.LastValidValue;
|
||||
for (int i = startIndex; i < len; i++)
|
||||
{
|
||||
double val = GetValidValue(source.Values[i]);
|
||||
@@ -279,10 +272,8 @@ public sealed class Lsma : ITValuePublisher
|
||||
lastProcessedValue = val;
|
||||
}
|
||||
|
||||
_p_sum_y = _sum_y;
|
||||
_p_sum_xy = _sum_xy;
|
||||
_p_last_val = lastProcessedValue;
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
_state.LastVal = lastProcessedValue;
|
||||
_p_state = _state;
|
||||
|
||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||
return new TSeries(t, v);
|
||||
@@ -409,14 +400,9 @@ public sealed class Lsma : ITValuePublisher
|
||||
public void Reset()
|
||||
{
|
||||
_buffer.Clear();
|
||||
_sum_y = 0;
|
||||
_sum_xy = 0;
|
||||
_p_sum_y = 0;
|
||||
_p_sum_xy = 0;
|
||||
_p_last_val = 0;
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
_tickCount = 0;
|
||||
_lastValidValue = 0;
|
||||
_p_lastValidValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
+39
-70
@@ -12,25 +12,18 @@ public sealed class Mama : ITValuePublisher
|
||||
{
|
||||
public TValue Last { get; private set; }
|
||||
public TValue Fama { get; private set; }
|
||||
public bool IsHot => _index > 6;
|
||||
public bool IsHot => _state.Index > 6;
|
||||
public event Action<TValue>? Pub;
|
||||
|
||||
private readonly double _fastLimit;
|
||||
private readonly double _slowLimit;
|
||||
|
||||
private double _period, _p_period;
|
||||
private double _phase, _p_phase;
|
||||
private double _mama, _p_mama;
|
||||
private double _fama, _p_fama;
|
||||
private double _sumPr, _p_sumPr;
|
||||
private int _index;
|
||||
|
||||
// State variables for IIR filters need to be preserved
|
||||
private double _i2, _p_i2;
|
||||
private double _q2, _p_q2;
|
||||
private double _re, _p_re;
|
||||
private double _im, _p_im;
|
||||
private double _lastValidPrice;
|
||||
private record struct State(
|
||||
double Period, double Phase, double Mama, double Fama, double SumPr,
|
||||
double I2, double Q2, double Re, double Im, double LastValidPrice, int Index
|
||||
);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private readonly RingBuffer _priceBuffer;
|
||||
private readonly RingBuffer _smoothBuffer;
|
||||
@@ -69,18 +62,10 @@ public sealed class Mama : ITValuePublisher
|
||||
|
||||
public void Init()
|
||||
{
|
||||
_period = _p_period = 0.0;
|
||||
_phase = _p_phase = 0.0;
|
||||
_mama = _p_mama = double.NaN;
|
||||
_fama = _p_fama = double.NaN;
|
||||
_sumPr = _p_sumPr = 0.0;
|
||||
_index = 0;
|
||||
|
||||
_i2 = _p_i2 = 0.0;
|
||||
_q2 = _p_q2 = 0.0;
|
||||
_re = _p_re = 0.0;
|
||||
_im = _p_im = 0.0;
|
||||
_lastValidPrice = 0.0;
|
||||
_state = default;
|
||||
_state.Mama = double.NaN;
|
||||
_state.Fama = double.NaN;
|
||||
_p_state = _state;
|
||||
|
||||
_priceBuffer.Clear();
|
||||
_smoothBuffer.Clear();
|
||||
@@ -97,45 +82,29 @@ public sealed class Mama : ITValuePublisher
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_period = _period;
|
||||
_p_phase = _phase;
|
||||
_p_mama = _mama;
|
||||
_p_fama = _fama;
|
||||
_p_sumPr = _sumPr;
|
||||
_p_i2 = _i2;
|
||||
_p_q2 = _q2;
|
||||
_p_re = _re;
|
||||
_p_im = _im;
|
||||
_index++;
|
||||
_p_state = _state;
|
||||
_state.Index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_period = _p_period;
|
||||
_phase = _p_phase;
|
||||
_mama = _p_mama;
|
||||
_fama = _p_fama;
|
||||
_sumPr = _p_sumPr;
|
||||
_i2 = _p_i2;
|
||||
_q2 = _p_q2;
|
||||
_re = _p_re;
|
||||
_im = _p_im;
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
double price = input.Value;
|
||||
if (!double.IsFinite(price))
|
||||
{
|
||||
price = _lastValidPrice;
|
||||
price = _state.LastValidPrice;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidPrice = price;
|
||||
_state.LastValidPrice = price;
|
||||
}
|
||||
|
||||
_priceBuffer.Add(price, isNew);
|
||||
|
||||
if (_index > 6)
|
||||
if (_state.Index > 6)
|
||||
{
|
||||
double adj = (0.075 * _period) + 0.54;
|
||||
double adj = (0.075 * _state.Period) + 0.54;
|
||||
|
||||
// Smooth
|
||||
double smooth = (4.0 * _priceBuffer[0] + 3.0 * _priceBuffer[1] + 2.0 * _priceBuffer[2] + _priceBuffer[3]) * 0.1;
|
||||
@@ -164,50 +133,50 @@ public sealed class Mama : ITValuePublisher
|
||||
double q2_val = q1 + jI;
|
||||
|
||||
// Smooth i2, q2
|
||||
_i2 = 0.2 * i2_val + 0.8 * _p_i2;
|
||||
_q2 = 0.2 * q2_val + 0.8 * _p_q2;
|
||||
_state.I2 = 0.2 * i2_val + 0.8 * _p_state.I2;
|
||||
_state.Q2 = 0.2 * q2_val + 0.8 * _p_state.Q2;
|
||||
|
||||
// Homodyne discriminator
|
||||
double re_val = (_i2 * _p_i2) + (_q2 * _p_q2);
|
||||
double im_val = (_i2 * _p_q2) - (_q2 * _p_i2);
|
||||
double re_val = (_state.I2 * _p_state.I2) + (_state.Q2 * _p_state.Q2);
|
||||
double im_val = (_state.I2 * _p_state.Q2) - (_state.Q2 * _p_state.I2);
|
||||
|
||||
// Smooth re, im
|
||||
_re = 0.2 * re_val + 0.8 * _p_re;
|
||||
_im = 0.2 * im_val + 0.8 * _p_im;
|
||||
_state.Re = 0.2 * re_val + 0.8 * _p_state.Re;
|
||||
_state.Im = 0.2 * im_val + 0.8 * _p_state.Im;
|
||||
|
||||
// Calculate Period
|
||||
double period = (Math.Abs(_im) > double.Epsilon && Math.Abs(_re) > double.Epsilon)
|
||||
? TWOPI / Math.Atan(_im / _re)
|
||||
double period = (Math.Abs(_state.Im) > double.Epsilon && Math.Abs(_state.Re) > double.Epsilon)
|
||||
? TWOPI / Math.Atan(_state.Im / _state.Re)
|
||||
: 0.0;
|
||||
|
||||
// Adjust Period
|
||||
period = period > 1.5 * _p_period ? 1.5 * _p_period : period;
|
||||
period = period < 0.67 * _p_period ? 0.67 * _p_period : period;
|
||||
period = period > 1.5 * _p_state.Period ? 1.5 * _p_state.Period : period;
|
||||
period = period < 0.67 * _p_state.Period ? 0.67 * _p_state.Period : period;
|
||||
period = period < 6.0 ? 6.0 : period;
|
||||
period = period > 50.0 ? 50.0 : period;
|
||||
|
||||
// Smooth Period
|
||||
_period = 0.2 * period + 0.8 * _p_period;
|
||||
_state.Period = 0.2 * period + 0.8 * _p_state.Period;
|
||||
|
||||
// Phase calculation
|
||||
_phase = Math.Abs(i1) >= double.Epsilon ? Math.Atan(q1 / i1) * RadToDeg : 0.0;
|
||||
_state.Phase = Math.Abs(i1) >= double.Epsilon ? Math.Atan(q1 / i1) * RadToDeg : 0.0;
|
||||
|
||||
// Adaptive alpha
|
||||
double delta = Math.Max(_p_phase - _phase, 1.0);
|
||||
double delta = Math.Max(_p_state.Phase - _state.Phase, 1.0);
|
||||
double alpha = _fastLimit / delta;
|
||||
alpha = Math.Clamp(alpha, _slowLimit, _fastLimit);
|
||||
|
||||
// Final indicators
|
||||
_mama = alpha * _priceBuffer[0] + (1.0 - alpha) * _p_mama;
|
||||
_fama = 0.5 * alpha * _mama + (1.0 - 0.5 * alpha) * _p_fama;
|
||||
_state.Mama = alpha * _priceBuffer[0] + (1.0 - alpha) * _p_state.Mama;
|
||||
_state.Fama = 0.5 * alpha * _state.Mama + (1.0 - 0.5 * alpha) * _p_state.Fama;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Initialization phase
|
||||
_sumPr += price;
|
||||
double avg = _index > 0 ? _sumPr / _index : price;
|
||||
_mama = avg;
|
||||
_fama = avg;
|
||||
_state.SumPr += price;
|
||||
double avg = _state.Index > 0 ? _state.SumPr / _state.Index : price;
|
||||
_state.Mama = avg;
|
||||
_state.Fama = avg;
|
||||
|
||||
// Initialize buffers with 0
|
||||
_smoothBuffer.Add(0, isNew);
|
||||
@@ -216,8 +185,8 @@ public sealed class Mama : ITValuePublisher
|
||||
_Q1_buffer.Add(0, isNew);
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, _mama);
|
||||
Fama = new TValue(input.Time, _fama);
|
||||
Last = new TValue(input.Time, _state.Mama);
|
||||
Fama = new TValue(input.Time, _state.Fama);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
}
|
||||
|
||||
+24
-34
@@ -31,12 +31,9 @@ public sealed class Sma : ITValuePublisher
|
||||
private readonly int _period;
|
||||
private readonly RingBuffer _buffer;
|
||||
|
||||
private double _sum;
|
||||
private double _p_sum;
|
||||
private double _p_lastInput;
|
||||
private double _lastValidValue;
|
||||
private double _p_lastValidValue;
|
||||
private int _tickCount;
|
||||
private record struct State(double Sum, double LastInput, double LastValidValue, int TickCount);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
@@ -85,10 +82,10 @@ public sealed class Sma : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(input))
|
||||
{
|
||||
_lastValidValue = input;
|
||||
_state.LastValidValue = input;
|
||||
return input;
|
||||
}
|
||||
return _lastValidValue;
|
||||
return _state.LastValidValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -96,19 +93,18 @@ public sealed class Sma : ITValuePublisher
|
||||
{
|
||||
double removedValue = _buffer.Count == _buffer.Capacity ? _buffer.Oldest : 0.0;
|
||||
|
||||
_sum = _sum - removedValue + val;
|
||||
_state.Sum = _state.Sum - removedValue + val;
|
||||
|
||||
_buffer.Add(val);
|
||||
|
||||
_tickCount++;
|
||||
if (_buffer.IsFull && _tickCount >= ResyncInterval)
|
||||
_state.TickCount++;
|
||||
if (_buffer.IsFull && _state.TickCount >= ResyncInterval)
|
||||
{
|
||||
_tickCount = 0;
|
||||
_sum = _buffer.RecalculateSum();
|
||||
_state.TickCount = 0;
|
||||
_state.Sum = _buffer.RecalculateSum();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
@@ -116,21 +112,20 @@ public sealed class Sma : ITValuePublisher
|
||||
{
|
||||
double val = GetValidValue(input.Value);
|
||||
UpdateState(val);
|
||||
_state.LastInput = val;
|
||||
|
||||
_p_sum = _sum;
|
||||
_p_lastInput = val;
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = _p_lastValidValue;
|
||||
_state = _p_state;
|
||||
double val = GetValidValue(input.Value);
|
||||
|
||||
_sum = _p_sum - _p_lastInput + val;
|
||||
_state.Sum = _state.Sum - _state.LastInput + val;
|
||||
_buffer.UpdateNewest(val);
|
||||
}
|
||||
|
||||
double result = _sum / _buffer.Count;
|
||||
double result = _state.Sum / _buffer.Count;
|
||||
Last = new TValue(input.Time, result);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
@@ -158,34 +153,33 @@ public sealed class Sma : ITValuePublisher
|
||||
|
||||
if (startIndex > 0)
|
||||
{
|
||||
_lastValidValue = 0;
|
||||
_state.LastValidValue = 0;
|
||||
for (int i = startIndex - 1; i >= 0; i--)
|
||||
{
|
||||
if (double.IsFinite(source.Values[i]))
|
||||
{
|
||||
_lastValidValue = source.Values[i];
|
||||
_state.LastValidValue = source.Values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = 0;
|
||||
_state.LastValidValue = 0;
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
_sum = 0;
|
||||
_tickCount = 0;
|
||||
_state.Sum = 0;
|
||||
_state.TickCount = 0;
|
||||
|
||||
for (int i = startIndex; i < len; i++)
|
||||
{
|
||||
double val = GetValidValue(source.Values[i]);
|
||||
UpdateState(val);
|
||||
_state.LastInput = val;
|
||||
}
|
||||
|
||||
_p_sum = _sum;
|
||||
_p_lastInput = GetValidValue(source.Values[len - 1]);
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
_p_state = _state;
|
||||
|
||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||
return new TSeries(t, v);
|
||||
@@ -528,12 +522,8 @@ public sealed class Sma : ITValuePublisher
|
||||
public void Reset()
|
||||
{
|
||||
_buffer.Clear();
|
||||
_sum = 0;
|
||||
_p_sum = 0;
|
||||
_p_lastInput = 0;
|
||||
_lastValidValue = 0;
|
||||
_p_lastValidValue = 0;
|
||||
_tickCount = 0;
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-19
@@ -26,27 +26,9 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class T3 : ITValuePublisher
|
||||
{
|
||||
private struct State : IEquatable<State>
|
||||
private record struct State(double E1, double E2, double E3, double E4, double E5, double E6, bool IsInitialized)
|
||||
{
|
||||
public double E1, E2, E3, E4, E5, E6;
|
||||
public bool IsInitialized;
|
||||
|
||||
public static State New() => new() { IsInitialized = false };
|
||||
|
||||
public override bool Equals(object? obj) => obj is State other && Equals(other);
|
||||
|
||||
#pragma warning disable S1244 // Do not check floating point equality with exact values
|
||||
public bool Equals(State other) =>
|
||||
E1 == other.E1 && E2 == other.E2 && E3 == other.E3 &&
|
||||
E4 == other.E4 && E5 == other.E5 && E6 == other.E6 &&
|
||||
IsInitialized == other.IsInitialized;
|
||||
#pragma warning restore S1244 // Do not check floating point equality with exact values
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(E1, E2, E3, E4, E5, E6, IsInitialized);
|
||||
|
||||
public static bool operator ==(State left, State right) => left.Equals(right);
|
||||
|
||||
public static bool operator !=(State left, State right) => !left.Equals(right);
|
||||
}
|
||||
|
||||
private readonly struct Parameters : IEquatable<Parameters>
|
||||
|
||||
+1
-20
@@ -26,28 +26,9 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Tema : ITValuePublisher
|
||||
{
|
||||
private struct EmaState : IEquatable<EmaState>
|
||||
private record struct EmaState(double Ema, double E, bool IsHot, bool IsCompensated)
|
||||
{
|
||||
public double Ema;
|
||||
public double E;
|
||||
public bool IsHot;
|
||||
public bool IsCompensated;
|
||||
|
||||
public static EmaState New() => new() { Ema = 0, E = 1.0, IsHot = false, IsCompensated = false };
|
||||
|
||||
public override bool Equals(object? obj) => obj is EmaState other && Equals(other);
|
||||
|
||||
public bool Equals(EmaState other) =>
|
||||
Ema == other.Ema &&
|
||||
E == other.E &&
|
||||
IsHot == other.IsHot &&
|
||||
IsCompensated == other.IsCompensated;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Ema, E, IsHot, IsCompensated);
|
||||
|
||||
public static bool operator ==(EmaState left, EmaState right) => left.Equals(right);
|
||||
|
||||
public static bool operator !=(EmaState left, EmaState right) => !left.Equals(right);
|
||||
}
|
||||
|
||||
private readonly double _alpha;
|
||||
|
||||
+73
-67
@@ -31,18 +31,19 @@ public sealed class Trima : ITValuePublisher
|
||||
private readonly RingBuffer _buffer1;
|
||||
private readonly RingBuffer _buffer2;
|
||||
|
||||
private double _sum1, _p_sum1, _p_lastInput1, _lastValidValue1, _p_lastValidValue1;
|
||||
private int _tickCount1;
|
||||
private record struct State(
|
||||
double Sum1, double LastInput1, double LastValidValue1, int TickCount1, double NextRemoved1,
|
||||
double Sum2, double LastInput2, int TickCount2, double NextRemoved2,
|
||||
int SampleCount
|
||||
);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private double _sum2, _p_sum2, _p_lastInput2;
|
||||
private int _tickCount2;
|
||||
|
||||
private int _sampleCount;
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
public string Name { get; }
|
||||
public TValue Last { get; private set; }
|
||||
public bool IsHot => _sampleCount >= _period;
|
||||
public bool IsHot => _state.SampleCount >= _period;
|
||||
public event Action<TValue>? Pub;
|
||||
|
||||
public Trima(int period)
|
||||
@@ -69,10 +70,10 @@ public sealed class Trima : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(input))
|
||||
{
|
||||
_lastValidValue1 = input;
|
||||
_state.LastValidValue1 = input;
|
||||
return input;
|
||||
}
|
||||
return _lastValidValue1;
|
||||
return _state.LastValidValue1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -80,61 +81,72 @@ public sealed class Trima : ITValuePublisher
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_sampleCount++;
|
||||
|
||||
// SMA 1
|
||||
double val1 = GetValidValue(input.Value);
|
||||
double removed1 = _buffer1.Count == _buffer1.Capacity ? _buffer1.Oldest : 0.0;
|
||||
_sum1 = _sum1 - removed1 + val1;
|
||||
_buffer1.Add(val1);
|
||||
|
||||
_tickCount1++;
|
||||
if (_buffer1.IsFull && _tickCount1 >= ResyncInterval)
|
||||
{
|
||||
_tickCount1 = 0;
|
||||
_sum1 = _buffer1.Sum();
|
||||
}
|
||||
|
||||
_p_sum1 = _sum1;
|
||||
_p_lastInput1 = val1;
|
||||
_p_lastValidValue1 = _lastValidValue1;
|
||||
|
||||
double sma1Result = _sum1 / _buffer1.Count;
|
||||
|
||||
// SMA 2
|
||||
double removed2 = _buffer2.Count == _buffer2.Capacity ? _buffer2.Oldest : 0.0;
|
||||
_sum2 = _sum2 - removed2 + sma1Result;
|
||||
_buffer2.Add(sma1Result);
|
||||
|
||||
_tickCount2++;
|
||||
if (_buffer2.IsFull && _tickCount2 >= ResyncInterval)
|
||||
{
|
||||
_tickCount2 = 0;
|
||||
_sum2 = _buffer2.Sum();
|
||||
}
|
||||
|
||||
_p_sum2 = _sum2;
|
||||
_p_lastInput2 = sma1Result;
|
||||
|
||||
Last = new TValue(input.Time, _sum2 / _buffer2.Count);
|
||||
_p_state = _state;
|
||||
_state.SampleCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// SMA 1 Correction
|
||||
_lastValidValue1 = _p_lastValidValue1;
|
||||
double val1 = GetValidValue(input.Value);
|
||||
_sum1 = _p_sum1 - _p_lastInput1 + val1;
|
||||
_buffer1.UpdateNewest(val1);
|
||||
|
||||
double sma1Result = _sum1 / _buffer1.Count;
|
||||
|
||||
// SMA 2 Correction
|
||||
_sum2 = _p_sum2 - _p_lastInput2 + sma1Result;
|
||||
_buffer2.UpdateNewest(sma1Result);
|
||||
|
||||
Last = new TValue(input.Time, _sum2 / _buffer2.Count);
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
// SMA 1
|
||||
double val1 = GetValidValue(input.Value);
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
double removed1 = _buffer1.Count == _buffer1.Capacity ? _buffer1.Oldest : 0.0;
|
||||
_state.Sum1 = _state.Sum1 - removed1 + val1;
|
||||
_buffer1.Add(val1);
|
||||
|
||||
// Store NextRemoved1 for next step
|
||||
_state.NextRemoved1 = _buffer1.Count == _buffer1.Capacity ? _buffer1.Oldest : 0.0;
|
||||
|
||||
_state.TickCount1++;
|
||||
if (_buffer1.IsFull && _state.TickCount1 >= ResyncInterval)
|
||||
{
|
||||
_state.TickCount1 = 0;
|
||||
_state.Sum1 = _buffer1.Sum();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use NextRemoved1 from _p_state
|
||||
double removed1 = _p_state.NextRemoved1;
|
||||
_state.Sum1 = _p_state.Sum1 - removed1 + val1;
|
||||
_buffer1.UpdateNewest(val1);
|
||||
}
|
||||
|
||||
_state.LastInput1 = val1;
|
||||
double sma1Result = _state.Sum1 / _buffer1.Count;
|
||||
|
||||
// SMA 2
|
||||
if (isNew)
|
||||
{
|
||||
double removed2 = _buffer2.Count == _buffer2.Capacity ? _buffer2.Oldest : 0.0;
|
||||
_state.Sum2 = _state.Sum2 - removed2 + sma1Result;
|
||||
_buffer2.Add(sma1Result);
|
||||
|
||||
// Store NextRemoved2 for next step
|
||||
_state.NextRemoved2 = _buffer2.Count == _buffer2.Capacity ? _buffer2.Oldest : 0.0;
|
||||
|
||||
_state.TickCount2++;
|
||||
if (_buffer2.IsFull && _state.TickCount2 >= ResyncInterval)
|
||||
{
|
||||
_state.TickCount2 = 0;
|
||||
_state.Sum2 = _buffer2.Sum();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use NextRemoved2 from _p_state
|
||||
double removed2 = _p_state.NextRemoved2;
|
||||
_state.Sum2 = _p_state.Sum2 - removed2 + sma1Result;
|
||||
_buffer2.UpdateNewest(sma1Result);
|
||||
}
|
||||
|
||||
_state.LastInput2 = sma1Result;
|
||||
|
||||
Last = new TValue(input.Time, _state.Sum2 / _buffer2.Count);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
}
|
||||
@@ -203,14 +215,8 @@ public sealed class Trima : ITValuePublisher
|
||||
{
|
||||
_buffer1.Clear();
|
||||
_buffer2.Clear();
|
||||
|
||||
_sum1 = _p_sum1 = _p_lastInput1 = _lastValidValue1 = _p_lastValidValue1 = 0;
|
||||
_tickCount1 = 0;
|
||||
|
||||
_sum2 = _p_sum2 = _p_lastInput2 = 0;
|
||||
_tickCount2 = 0;
|
||||
|
||||
_sampleCount = 0;
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
+34
-76
@@ -31,12 +31,13 @@ public sealed class Vidya : ITValuePublisher
|
||||
private readonly RingBuffer _ups;
|
||||
private readonly RingBuffer _downs;
|
||||
|
||||
private double _prevClose;
|
||||
private double _lastVidya;
|
||||
private double _currentClose;
|
||||
private double _currentVidya;
|
||||
private bool _isInitialized;
|
||||
private int _barCount;
|
||||
private record struct State(
|
||||
double PrevClose, double LastVidya,
|
||||
double CurrentClose, double CurrentVidya,
|
||||
bool IsInitialized, int BarCount
|
||||
);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the indicator.
|
||||
@@ -78,12 +79,18 @@ public sealed class Vidya : ITValuePublisher
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_barCount++;
|
||||
if (_isInitialized)
|
||||
{
|
||||
_prevClose = _currentClose;
|
||||
_lastVidya = _currentVidya;
|
||||
}
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = _p_state;
|
||||
}
|
||||
|
||||
_state.BarCount++;
|
||||
if (_state.IsInitialized)
|
||||
{
|
||||
_state.PrevClose = _state.CurrentClose;
|
||||
_state.LastVidya = _state.CurrentVidya;
|
||||
}
|
||||
|
||||
double price = input.Value;
|
||||
@@ -91,25 +98,25 @@ public sealed class Vidya : ITValuePublisher
|
||||
{
|
||||
// Handle NaN/Infinity by using the last known valid values
|
||||
// If not initialized, we can't do much, just return input
|
||||
if (!_isInitialized) return input;
|
||||
price = _currentClose; // Use last valid close
|
||||
if (!_state.IsInitialized) return input;
|
||||
price = _state.CurrentClose; // Use last valid close
|
||||
}
|
||||
|
||||
if (_barCount <= 1)
|
||||
if (_state.BarCount <= 1)
|
||||
{
|
||||
_prevClose = price;
|
||||
_lastVidya = price;
|
||||
_currentClose = price;
|
||||
_currentVidya = price;
|
||||
_isInitialized = true;
|
||||
_state.PrevClose = price;
|
||||
_state.LastVidya = price;
|
||||
_state.CurrentClose = price;
|
||||
_state.CurrentVidya = price;
|
||||
_state.IsInitialized = true;
|
||||
_ups.Add(0, isNew);
|
||||
_downs.Add(0, isNew);
|
||||
Last = new TValue(input.Time, _currentVidya);
|
||||
Last = new TValue(input.Time, _state.CurrentVidya);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
}
|
||||
|
||||
double change = price - _prevClose;
|
||||
double change = price - _state.PrevClose;
|
||||
double up = change > 0 ? change : 0;
|
||||
double down = change < 0 ? -change : 0;
|
||||
|
||||
@@ -127,10 +134,10 @@ public sealed class Vidya : ITValuePublisher
|
||||
}
|
||||
|
||||
double dynamicAlpha = _alpha * vi;
|
||||
_currentVidya = dynamicAlpha * price + (1.0 - dynamicAlpha) * _lastVidya;
|
||||
_currentClose = price;
|
||||
_state.CurrentVidya = dynamicAlpha * price + (1.0 - dynamicAlpha) * _state.LastVidya;
|
||||
_state.CurrentClose = price;
|
||||
|
||||
Last = new TValue(input.Time, _currentVidya);
|
||||
Last = new TValue(input.Time, _state.CurrentVidya);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
}
|
||||
@@ -150,42 +157,10 @@ public sealed class Vidya : ITValuePublisher
|
||||
var sourceValues = source.Values;
|
||||
var sourceTimes = source.Times;
|
||||
|
||||
// We can't easily use a static Calculate here because of the complex state (RingBuffers)
|
||||
// So we'll iterate and use the instance Update logic, but optimized for series
|
||||
// Actually, we can implement a static Calculate that uses temporary buffers
|
||||
|
||||
Calculate(sourceValues, vSpan, _period);
|
||||
|
||||
sourceTimes.CopyTo(tSpan);
|
||||
|
||||
// Update internal state to match the end of the series
|
||||
// This is tricky because Calculate is static and doesn't update instance state.
|
||||
// To support "Update(TSeries)", we should probably just run the instance update loop.
|
||||
// But for performance, we want to use the static method if possible.
|
||||
// The standard pattern in this library seems to be:
|
||||
// 1. Call static Calculate to fill the output
|
||||
// 2. Re-run the last N updates on the instance to sync state
|
||||
|
||||
// Re-sync state
|
||||
// We need to feed at least 'period' bars to fill the buffers
|
||||
// But since VIDYA is recursive, we really need the whole history to match exactly.
|
||||
// So for VIDYA, it's safer to just reset and run the update loop.
|
||||
|
||||
Reset();
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
Update(new TValue(sourceTimes[i], sourceValues[i]), true);
|
||||
}
|
||||
|
||||
// Overwrite the vSpan with the results we just calculated?
|
||||
// Or just trust the loop we just ran.
|
||||
// Since we ran the loop, 'v' is already populated? No, Update(TValue) updates 'Last', not a list.
|
||||
// So we need to populate 'v'.
|
||||
|
||||
// Let's do this:
|
||||
// 1. Reset
|
||||
// 2. Loop and populate
|
||||
|
||||
Reset();
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
@@ -210,14 +185,6 @@ public sealed class Vidya : ITValuePublisher
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
|
||||
// We need buffers for Up and Down sums
|
||||
// Since we can't allocate RingBuffers on the stack easily for dynamic period,
|
||||
// and we want to avoid heap allocations in the hot path if possible.
|
||||
// But for a static Calculate with a large span, a few allocations are acceptable.
|
||||
// Or we can use a circular buffer logic with a stackalloc array if period is small,
|
||||
// but period can be large.
|
||||
|
||||
// Let's use a simple array for the circular buffer logic
|
||||
double[] ups = new double[period];
|
||||
double[] downs = new double[period];
|
||||
int head = 0;
|
||||
@@ -227,11 +194,8 @@ public sealed class Vidya : ITValuePublisher
|
||||
double prevClose = source[0];
|
||||
double lastVidya = source[0];
|
||||
|
||||
// Initialize first element
|
||||
output[0] = source[0];
|
||||
|
||||
// Fill buffers with 0 initially (already done by new double[])
|
||||
|
||||
for (int i = 1; i < source.Length; i++)
|
||||
{
|
||||
double price = source[i];
|
||||
@@ -244,7 +208,6 @@ public sealed class Vidya : ITValuePublisher
|
||||
double up = change > 0 ? change : 0;
|
||||
double down = change < 0 ? -change : 0;
|
||||
|
||||
// Update sums: remove old, add new
|
||||
sumUp -= ups[head];
|
||||
sumDown -= downs[head];
|
||||
|
||||
@@ -277,13 +240,8 @@ public sealed class Vidya : ITValuePublisher
|
||||
{
|
||||
_ups.Clear();
|
||||
_downs.Clear();
|
||||
_prevClose = 0;
|
||||
_lastVidya = 0;
|
||||
_currentClose = 0;
|
||||
_currentVidya = 0;
|
||||
_isInitialized = false;
|
||||
_barCount = 0;
|
||||
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
+32
-37
@@ -32,10 +32,10 @@ public sealed class Wma : ITValuePublisher
|
||||
private readonly double _divisor;
|
||||
private readonly RingBuffer _buffer;
|
||||
|
||||
private double _sum, _wsum;
|
||||
private double _p_sum, _p_wsum, _p_lastInput;
|
||||
private double _lastValidValue, _p_lastValidValue;
|
||||
private int _tickCount;
|
||||
private record struct State(double Sum, double WSum, double LastInput, double LastValidValue, int TickCount);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
private const int ResyncInterval = 1000;
|
||||
|
||||
public string Name { get; }
|
||||
@@ -63,10 +63,10 @@ public sealed class Wma : ITValuePublisher
|
||||
{
|
||||
if (double.IsFinite(input))
|
||||
{
|
||||
_lastValidValue = input;
|
||||
_state.LastValidValue = input;
|
||||
return input;
|
||||
}
|
||||
return _lastValidValue;
|
||||
return _state.LastValidValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -74,24 +74,24 @@ public sealed class Wma : ITValuePublisher
|
||||
{
|
||||
if (_buffer.IsFull)
|
||||
{
|
||||
double oldSum = _sum;
|
||||
double oldSum = _state.Sum;
|
||||
double oldest = _buffer.Oldest;
|
||||
_sum = _sum - oldest + val;
|
||||
_wsum = _wsum - oldSum + (_period * val);
|
||||
_state.Sum = _state.Sum - oldest + val;
|
||||
_state.WSum = _state.WSum - oldSum + (_period * val);
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = _buffer.Count + 1;
|
||||
_sum += val;
|
||||
_wsum += count * val;
|
||||
_state.Sum += val;
|
||||
_state.WSum += count * val;
|
||||
}
|
||||
|
||||
_buffer.Add(val);
|
||||
|
||||
_tickCount++;
|
||||
if (_buffer.IsFull && _tickCount >= ResyncInterval)
|
||||
_state.TickCount++;
|
||||
if (_buffer.IsFull && _state.TickCount >= ResyncInterval)
|
||||
{
|
||||
_tickCount = 0;
|
||||
_state.TickCount = 0;
|
||||
double recalcSum = 0;
|
||||
double recalcWsum = 0;
|
||||
int weight = 1;
|
||||
@@ -101,8 +101,8 @@ public sealed class Wma : ITValuePublisher
|
||||
recalcWsum += weight * item;
|
||||
weight++;
|
||||
}
|
||||
_sum = recalcSum;
|
||||
_wsum = recalcWsum;
|
||||
_state.Sum = recalcSum;
|
||||
_state.WSum = recalcWsum;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,29 +113,24 @@ public sealed class Wma : ITValuePublisher
|
||||
{
|
||||
double val = GetValidValue(input.Value);
|
||||
UpdateState(val);
|
||||
_state.LastInput = val;
|
||||
|
||||
_p_sum = _sum;
|
||||
_p_wsum = _wsum;
|
||||
_p_lastInput = val;
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
_p_state = _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = _p_lastValidValue;
|
||||
_state = _p_state;
|
||||
double val = GetValidValue(input.Value);
|
||||
|
||||
_sum = _p_sum;
|
||||
_wsum = _p_wsum;
|
||||
|
||||
int weight = _buffer.IsFull ? _period : _buffer.Count;
|
||||
_sum = _sum - _p_lastInput + val;
|
||||
_wsum += weight * (val - _p_lastInput);
|
||||
_state.Sum = _state.Sum - _state.LastInput + val;
|
||||
_state.WSum += weight * (val - _state.LastInput);
|
||||
|
||||
_buffer.UpdateNewest(val);
|
||||
}
|
||||
|
||||
double currentDivisor = _buffer.IsFull ? _divisor : (double)_buffer.Count * (_buffer.Count + 1) * 0.5;
|
||||
Last = new TValue(input.Time, _wsum / currentDivisor);
|
||||
Last = new TValue(input.Time, _state.WSum / currentDivisor);
|
||||
Pub?.Invoke(Last);
|
||||
return Last;
|
||||
}
|
||||
@@ -162,35 +157,34 @@ public sealed class Wma : ITValuePublisher
|
||||
|
||||
if (startIndex > 0)
|
||||
{
|
||||
_state.LastValidValue = 0;
|
||||
for (int i = startIndex - 1; i >= 0; i--)
|
||||
{
|
||||
if (double.IsFinite(source.Values[i]))
|
||||
{
|
||||
_lastValidValue = source.Values[i];
|
||||
_state.LastValidValue = source.Values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastValidValue = 0;
|
||||
_state.LastValidValue = 0;
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
_sum = 0;
|
||||
_wsum = 0;
|
||||
_tickCount = 0;
|
||||
_state.Sum = 0;
|
||||
_state.WSum = 0;
|
||||
_state.TickCount = 0;
|
||||
|
||||
for (int i = startIndex; i < len; i++)
|
||||
{
|
||||
double val = GetValidValue(source.Values[i]);
|
||||
UpdateState(val);
|
||||
_state.LastInput = val;
|
||||
}
|
||||
|
||||
_p_sum = _sum;
|
||||
_p_wsum = _wsum;
|
||||
_p_lastInput = GetValidValue(source.Values[len - 1]);
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
_p_state = _state;
|
||||
|
||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||
return new TSeries(t, v);
|
||||
@@ -754,7 +748,8 @@ public sealed class Wma : ITValuePublisher
|
||||
public void Reset()
|
||||
{
|
||||
_buffer.Clear();
|
||||
_sum = _wsum = _p_sum = _p_wsum = _p_lastInput = _lastValidValue = _p_lastValidValue = 0;
|
||||
_state = default;
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user