mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-26 06:18:05 +00:00
Refactor trend indicators to use record structs for state management
This commit is contained in:
@@ -39,8 +39,9 @@ Each indicator resides in its own directory such as `lib/trends/`, `lib/indicato
|
|||||||
|
|
||||||
### State Management
|
### State Management
|
||||||
|
|
||||||
* Use `RingBuffer` for sliding window data.
|
* **Scalar State:** Use a `private record struct State` to group all scalar state variables. This ensures value semantics, automatic `IEquatable` implementation, and cleaner rollback logic.
|
||||||
* Maintain separate state variables for the *current* calculation (`_sum`, `_lastVal`) and the *previous* valid state (`_p_sum`, `_p_lastVal`) to support `isNew=false` updates.
|
* **State Variables:** Maintain `private State _state;` (current) and `private State _p_state;` (previous valid state).
|
||||||
|
* **Buffers:** Use `RingBuffer` for sliding window data.
|
||||||
* **Resync:** Implement a periodic full recalculation (e.g., every 1000 ticks) to prevent floating-point drift in running sums.
|
* **Resync:** Implement a periodic full recalculation (e.g., every 1000 ticks) to prevent floating-point drift in running sums.
|
||||||
|
|
||||||
### Constructor
|
### Constructor
|
||||||
@@ -54,10 +55,17 @@ Each indicator resides in its own directory such as `lib/trends/`, `lib/indicato
|
|||||||
* **Signature:** `public TValue Update(TValue input, bool isNew = true)`
|
* **Signature:** `public TValue Update(TValue input, bool isNew = true)`
|
||||||
* **Attribute:** `[MethodImpl(MethodImplOptions.AggressiveInlining)]`
|
* **Attribute:** `[MethodImpl(MethodImplOptions.AggressiveInlining)]`
|
||||||
* **Logic:**
|
* **Logic:**
|
||||||
1. **Input Validation:** Check `double.IsFinite`. If not, use `_lastValidValue`.
|
1. **State Rollback:**
|
||||||
2. **State Management:**
|
```csharp
|
||||||
* If `isNew=true`: Save current state to `_p_*` variables, then update.
|
if (isNew) {
|
||||||
* If `isNew=false`: Restore state from `_p_*` variables, then update.
|
_p_state = _state;
|
||||||
|
// ... update state (e.g. counters) ...
|
||||||
|
} else {
|
||||||
|
_state = _p_state;
|
||||||
|
// ... update state ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. **Input Validation:** Check `double.IsFinite`. If not, use `_lastValidValue` (stored in `State`).
|
||||||
3. **Calculation:** Perform the math.
|
3. **Calculation:** Perform the math.
|
||||||
4. **Publish:** Update `Last` property, invoke `Pub` event, return `Last`.
|
4. **Publish:** Update `Last` property, invoke `Pub` event, return `Last`.
|
||||||
|
|
||||||
@@ -68,7 +76,7 @@ Each indicator resides in its own directory such as `lib/trends/`, `lib/indicato
|
|||||||
* **Logic:**
|
* **Logic:**
|
||||||
1. Create output series.
|
1. Create output series.
|
||||||
2. Call static `Calculate(Span)` for performance.
|
2. Call static `Calculate(Span)` for performance.
|
||||||
3. Restore internal state by replaying the last `Period` bars.
|
3. Restore internal state by replaying the last `Period` bars (or full series if recursive).
|
||||||
|
|
||||||
### Static Calculate (TSeries)
|
### Static Calculate (TSeries)
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,10 @@ public TValue Update(TValue input, bool isNew = true)
|
|||||||
|
|
||||||
### State Management
|
### State Management
|
||||||
|
|
||||||
* Use `RingBuffer` for sliding windows.
|
* **Scalar State:** Use a `private record struct State` to group all scalar state variables. This ensures value semantics, automatic `IEquatable` implementation, and cleaner rollback logic.
|
||||||
* Maintain `_state` and `_p_state` (previous state) variables to support `isNew=false` rollbacks.
|
* **State Variables:** Maintain `private State _state;` (current) and `private State _p_state;` (previous valid state).
|
||||||
* **Resync**: Periodically recalculate running sums to prevent floating-point drift.
|
* **Buffers:** Use `RingBuffer` for sliding windows.
|
||||||
|
* **Resync:** Periodically recalculate running sums to prevent floating-point drift.
|
||||||
|
|
||||||
### Dual API Requirement
|
### Dual API Requirement
|
||||||
|
|
||||||
|
|||||||
+29
-11
@@ -1,4 +1,5 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace QuanTAlib;
|
namespace QuanTAlib;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ namespace QuanTAlib;
|
|||||||
public class GBM : IFeed
|
public class GBM : IFeed
|
||||||
#pragma warning restore S101
|
#pragma warning restore S101
|
||||||
{
|
{
|
||||||
private readonly Random _rnd;
|
private readonly Random? _rnd;
|
||||||
|
|
||||||
private double _lastPrice;
|
private double _lastPrice;
|
||||||
private long _lastTime;
|
private long _lastTime;
|
||||||
@@ -52,7 +53,7 @@ public class GBM : IFeed
|
|||||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(startPrice);
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(startPrice);
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(sigma);
|
ArgumentOutOfRangeException.ThrowIfNegative(sigma);
|
||||||
|
|
||||||
_rnd = seed.HasValue ? new Random(seed.Value) : new Random();
|
_rnd = seed.HasValue ? new Random(seed.Value) : null;
|
||||||
_lastPrice = startPrice;
|
_lastPrice = startPrice;
|
||||||
_lastTime = DateTime.UtcNow.Ticks;
|
_lastTime = DateTime.UtcNow.Ticks;
|
||||||
|
|
||||||
@@ -71,6 +72,23 @@ public class GBM : IFeed
|
|||||||
_vol = sigma * Math.Sqrt(dt);
|
_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>
|
/// <summary>
|
||||||
/// Generates next standard normal using Box-Muller transform with caching.
|
/// Generates next standard normal using Box-Muller transform with caching.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -83,8 +101,8 @@ public class GBM : IFeed
|
|||||||
return _cachedZ;
|
return _cachedZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
double u1 = 1.0 - _rnd.NextDouble(); // nosemgrep
|
double u1 = 1.0 - NextDouble();
|
||||||
double u2 = 1.0 - _rnd.NextDouble(); // nosemgrep
|
double u2 = 1.0 - NextDouble();
|
||||||
double mag = Math.Sqrt(-2.0 * Math.Log(u1));
|
double mag = Math.Sqrt(-2.0 * Math.Log(u1));
|
||||||
double angle = 2.0 * Math.PI * u2;
|
double angle = 2.0 * Math.PI * u2;
|
||||||
|
|
||||||
@@ -110,12 +128,12 @@ public class GBM : IFeed
|
|||||||
|
|
||||||
double z = NextNormal();
|
double z = NextNormal();
|
||||||
double price = _lastPrice * Math.Exp(_drift + _vol * z);
|
double price = _lastPrice * Math.Exp(_drift + _vol * z);
|
||||||
double volume = 1000 + _rnd.NextDouble() * 1000;
|
double volume = 1000 + NextDouble() * 1000;
|
||||||
|
|
||||||
double open = _lastPrice;
|
double open = _lastPrice;
|
||||||
double close = price;
|
double close = price;
|
||||||
double high = Math.Max(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 - _rnd.NextDouble() * 0.01);
|
double low = Math.Min(open, close) * (1.0 - NextDouble() * 0.01);
|
||||||
|
|
||||||
_currentBar = new TBar(currentTime, open, high, low, close, volume);
|
_currentBar = new TBar(currentTime, open, high, low, close, volume);
|
||||||
_hasCurrentBar = true;
|
_hasCurrentBar = true;
|
||||||
@@ -128,7 +146,7 @@ public class GBM : IFeed
|
|||||||
// Update current bar (intra-bar tick)
|
// Update current bar (intra-bar tick)
|
||||||
double z = NextNormal();
|
double z = NextNormal();
|
||||||
double price = _lastPrice * Math.Exp(_drift + _vol * z);
|
double price = _lastPrice * Math.Exp(_drift + _vol * z);
|
||||||
double additionalVolume = 1000 + _rnd.NextDouble() * 1000;
|
double additionalVolume = 1000 + NextDouble() * 1000;
|
||||||
|
|
||||||
var bar = _currentBar;
|
var bar = _currentBar;
|
||||||
double newClose = price;
|
double newClose = price;
|
||||||
@@ -190,9 +208,9 @@ public class GBM : IFeed
|
|||||||
double open = currentPrice;
|
double open = currentPrice;
|
||||||
double close = price;
|
double close = price;
|
||||||
|
|
||||||
double rnd1 = _rnd.NextDouble();
|
double rnd1 = NextDouble();
|
||||||
double rnd2 = _rnd.NextDouble();
|
double rnd2 = NextDouble();
|
||||||
double rnd3 = _rnd.NextDouble();
|
double rnd3 = NextDouble();
|
||||||
|
|
||||||
t[i] = currentTime;
|
t[i] = currentTime;
|
||||||
o[i] = open;
|
o[i] = open;
|
||||||
|
|||||||
+18
-5
@@ -31,7 +31,10 @@ public sealed class Alma : ITValuePublisher
|
|||||||
private readonly double[] _weights;
|
private readonly double[] _weights;
|
||||||
private readonly double _weightSum;
|
private readonly double _weightSum;
|
||||||
private readonly RingBuffer _buffer;
|
private readonly RingBuffer _buffer;
|
||||||
private double _lastValidValue;
|
|
||||||
|
private record struct State(double LastValidValue);
|
||||||
|
private State _state;
|
||||||
|
private State _p_state;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Display name for the indicator.
|
/// Display name for the indicator.
|
||||||
@@ -99,15 +102,24 @@ public sealed class Alma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(input))
|
if (double.IsFinite(input))
|
||||||
{
|
{
|
||||||
_lastValidValue = input;
|
_state.LastValidValue = input;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
return _lastValidValue;
|
return _state.LastValidValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public TValue Update(TValue input, bool isNew = true)
|
public TValue Update(TValue input, bool isNew = true)
|
||||||
{
|
{
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
_p_state = _state;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
double val = GetValidValue(input.Value);
|
double val = GetValidValue(input.Value);
|
||||||
_buffer.Add(val, isNew);
|
_buffer.Add(val, isNew);
|
||||||
|
|
||||||
@@ -140,7 +152,7 @@ public sealed class Alma : ITValuePublisher
|
|||||||
|
|
||||||
// Restore state
|
// Restore state
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_lastValidValue = 0;
|
_state = default;
|
||||||
|
|
||||||
// Replay last part to restore buffer state
|
// Replay last part to restore buffer state
|
||||||
int startIndex = Math.Max(0, len - _period);
|
int startIndex = Math.Max(0, len - _period);
|
||||||
@@ -301,7 +313,8 @@ public sealed class Alma : ITValuePublisher
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_lastValidValue = 0;
|
_state = default;
|
||||||
|
_p_state = default;
|
||||||
Last = default;
|
Last = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-15
@@ -25,10 +25,9 @@ public sealed class Conv : ITValuePublisher
|
|||||||
private readonly double[] _kernel;
|
private readonly double[] _kernel;
|
||||||
private readonly RingBuffer _buffer;
|
private readonly RingBuffer _buffer;
|
||||||
|
|
||||||
private double _lastValidValue;
|
private record struct State(double LastValidValue);
|
||||||
|
private State _state;
|
||||||
// State for bar correction
|
private State _p_state;
|
||||||
private double _p_lastValidValue;
|
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public TValue Last { get; private set; }
|
public TValue Last { get; private set; }
|
||||||
@@ -45,8 +44,8 @@ public sealed class Conv : ITValuePublisher
|
|||||||
Array.Copy(kernel, _kernel, _period);
|
Array.Copy(kernel, _kernel, _period);
|
||||||
_buffer = new RingBuffer(_period);
|
_buffer = new RingBuffer(_period);
|
||||||
Name = $"Conv({_period})";
|
Name = $"Conv({_period})";
|
||||||
_lastValidValue = double.NaN;
|
_state.LastValidValue = double.NaN;
|
||||||
_p_lastValidValue = double.NaN;
|
_p_state.LastValidValue = double.NaN;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Conv(ITValuePublisher source, double[] kernel) : this(kernel)
|
public Conv(ITValuePublisher source, double[] kernel) : this(kernel)
|
||||||
@@ -59,10 +58,10 @@ public sealed class Conv : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(input))
|
if (double.IsFinite(input))
|
||||||
{
|
{
|
||||||
_lastValidValue = input;
|
_state.LastValidValue = input;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
return _lastValidValue;
|
return _state.LastValidValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -70,11 +69,11 @@ public sealed class Conv : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_p_lastValidValue = _lastValidValue;
|
_p_state = _state;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidValue = _p_lastValidValue;
|
_state = _p_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
double val = GetValidValue(input.Value);
|
double val = GetValidValue(input.Value);
|
||||||
@@ -145,14 +144,14 @@ public sealed class Conv : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(sourceValues[i]))
|
if (double.IsFinite(sourceValues[i]))
|
||||||
{
|
{
|
||||||
_lastValidValue = sourceValues[i];
|
_state.LastValidValue = sourceValues[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidValue = double.NaN;
|
_state.LastValidValue = double.NaN;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
@@ -168,7 +167,7 @@ public sealed class Conv : ITValuePublisher
|
|||||||
Last = new TValue(source.Times[len - 1], vSpan[len - 1]);
|
Last = new TValue(source.Times[len - 1], vSpan[len - 1]);
|
||||||
|
|
||||||
// Save state for isNew=false
|
// Save state for isNew=false
|
||||||
_p_lastValidValue = _lastValidValue;
|
_p_state = _state;
|
||||||
|
|
||||||
return new TSeries(t, v);
|
return new TSeries(t, v);
|
||||||
}
|
}
|
||||||
@@ -241,8 +240,8 @@ public sealed class Conv : ITValuePublisher
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_lastValidValue = double.NaN;
|
_state.LastValidValue = double.NaN;
|
||||||
_p_lastValidValue = double.NaN;
|
_p_state.LastValidValue = double.NaN;
|
||||||
Last = default;
|
Last = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-20
@@ -24,28 +24,9 @@ namespace QuanTAlib;
|
|||||||
[SkipLocalsInit]
|
[SkipLocalsInit]
|
||||||
public sealed class Dema : ITValuePublisher
|
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 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;
|
private readonly double _alpha;
|
||||||
|
|||||||
+1
-20
@@ -27,28 +27,9 @@ namespace QuanTAlib;
|
|||||||
[SkipLocalsInit]
|
[SkipLocalsInit]
|
||||||
public sealed class Ema : ITValuePublisher
|
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 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;
|
private readonly double _alpha;
|
||||||
|
|||||||
+52
-37
@@ -24,12 +24,10 @@ public sealed class Kama : ITValuePublisher
|
|||||||
private readonly double _fastAlpha;
|
private readonly double _fastAlpha;
|
||||||
private readonly double _slowAlpha;
|
private readonly double _slowAlpha;
|
||||||
private readonly RingBuffer _buffer;
|
private readonly RingBuffer _buffer;
|
||||||
private double _kama;
|
|
||||||
private double _p_kama;
|
private record struct State(double Kama, double VolatilitySum, double NextDiffOut, double LastValidValue);
|
||||||
private double _volatilitySum;
|
private State _state;
|
||||||
private double _p_volatilitySum;
|
private State _p_state;
|
||||||
private double _lastDiffOut;
|
|
||||||
private double _lastValidValue;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Display name for the indicator.
|
/// Display name for the indicator.
|
||||||
@@ -74,8 +72,10 @@ public sealed class Kama : ITValuePublisher
|
|||||||
_slowAlpha = 2.0 / (slowPeriod + 1);
|
_slowAlpha = 2.0 / (slowPeriod + 1);
|
||||||
|
|
||||||
Name = $"Kama({period}, {fastPeriod}, {slowPeriod})";
|
Name = $"Kama({period}, {fastPeriod}, {slowPeriod})";
|
||||||
_kama = double.NaN;
|
_state.Kama = double.NaN;
|
||||||
_lastValidValue = 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)
|
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))
|
if (double.IsFinite(input))
|
||||||
{
|
{
|
||||||
_lastValidValue = input;
|
_state.LastValidValue = input;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
return _lastValidValue;
|
return _state.LastValidValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public TValue Update(TValue input, bool isNew = true)
|
public TValue Update(TValue input, bool isNew = true)
|
||||||
{
|
{
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
_p_state = _state;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_state = _p_state;
|
||||||
|
}
|
||||||
|
|
||||||
double val = GetValidValue(input.Value);
|
double val = GetValidValue(input.Value);
|
||||||
if (double.IsNaN(val))
|
if (double.IsNaN(val))
|
||||||
{
|
{
|
||||||
@@ -108,55 +117,58 @@ public sealed class Kama : ITValuePublisher
|
|||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_p_kama = _kama;
|
|
||||||
_p_volatilitySum = _volatilitySum;
|
|
||||||
|
|
||||||
bool wasFull = _buffer.IsFull;
|
bool wasFull = _buffer.IsFull;
|
||||||
double removed = _buffer.Add(val);
|
_buffer.Add(val);
|
||||||
|
|
||||||
if (wasFull)
|
if (wasFull)
|
||||||
{
|
{
|
||||||
double diff_out = Math.Abs(removed - _buffer[0]);
|
double diff_out = _p_state.NextDiffOut;
|
||||||
_lastDiffOut = diff_out;
|
|
||||||
|
|
||||||
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
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)
|
else if (_buffer.Count >= 2)
|
||||||
{
|
{
|
||||||
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
||||||
_volatilitySum += diff_in;
|
_state.VolatilitySum += diff_in;
|
||||||
_lastDiffOut = 0;
|
|
||||||
|
if (_buffer.IsFull)
|
||||||
|
{
|
||||||
|
// Buffer just became full.
|
||||||
|
// NextDiffOut = abs(buffer[0] - buffer[1])
|
||||||
|
_state.NextDiffOut = Math.Abs(_buffer[0] - _buffer[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Restore state
|
|
||||||
_kama = _p_kama;
|
|
||||||
_buffer.UpdateNewest(val);
|
_buffer.UpdateNewest(val);
|
||||||
|
|
||||||
if (_buffer.IsFull)
|
if (_buffer.IsFull)
|
||||||
{
|
{
|
||||||
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
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)
|
else if (_buffer.Count >= 2)
|
||||||
{
|
{
|
||||||
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
double diff_in = Math.Abs(_buffer[^1] - _buffer[^2]);
|
||||||
_volatilitySum = _p_volatilitySum + diff_in;
|
_state.VolatilitySum = _p_state.VolatilitySum + diff_in;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate KAMA
|
// Calculate KAMA
|
||||||
if (double.IsNaN(_kama))
|
if (double.IsNaN(_state.Kama))
|
||||||
{
|
{
|
||||||
_kama = val;
|
_state.Kama = val;
|
||||||
_p_kama = val; // Ensure p_kama is initialized
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double change = Math.Abs(_buffer[^1] - _buffer[0]);
|
double change = Math.Abs(_buffer[^1] - _buffer[0]);
|
||||||
double volatility = _volatilitySum;
|
double volatility = _state.VolatilitySum;
|
||||||
|
|
||||||
// Avoid division by zero
|
// Avoid division by zero
|
||||||
double er = (volatility > double.Epsilon) ? change / volatility : 0.0;
|
double er = (volatility > double.Epsilon) ? change / volatility : 0.0;
|
||||||
@@ -166,10 +178,16 @@ public sealed class Kama : ITValuePublisher
|
|||||||
double sc = er * (_fastAlpha - _slowAlpha) + _slowAlpha;
|
double sc = er * (_fastAlpha - _slowAlpha) + _slowAlpha;
|
||||||
sc *= sc;
|
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);
|
Pub?.Invoke(Last);
|
||||||
return Last;
|
return Last;
|
||||||
}
|
}
|
||||||
@@ -290,7 +308,6 @@ public sealed class Kama : ITValuePublisher
|
|||||||
double change = 0;
|
double change = 0;
|
||||||
change = (count == bufSize) ? Math.Abs(val - buffer[bufferIdx]) : Math.Abs(val - buffer[0]);
|
change = (count == bufSize) ? Math.Abs(val - buffer[bufferIdx]) : Math.Abs(val - buffer[0]);
|
||||||
|
|
||||||
|
|
||||||
double er = (volatilitySum > double.Epsilon) ? change / volatilitySum : 0.0;
|
double er = (volatilitySum > double.Epsilon) ? change / volatilitySum : 0.0;
|
||||||
if (er > 1.0) er = 1.0;
|
if (er > 1.0) er = 1.0;
|
||||||
|
|
||||||
@@ -306,12 +323,10 @@ public sealed class Kama : ITValuePublisher
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_kama = double.NaN;
|
_state = default;
|
||||||
_p_kama = double.NaN;
|
_state.Kama = double.NaN;
|
||||||
_volatilitySum = 0;
|
_state.LastValidValue = double.NaN;
|
||||||
_p_volatilitySum = 0;
|
_p_state = _state;
|
||||||
_lastDiffOut = 0;
|
|
||||||
_lastValidValue = double.NaN;
|
|
||||||
Last = default;
|
Last = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-43
@@ -34,15 +34,10 @@ public sealed class Lsma : ITValuePublisher
|
|||||||
private readonly double _sum_x;
|
private readonly double _sum_x;
|
||||||
private readonly double _denominator;
|
private readonly double _denominator;
|
||||||
|
|
||||||
private double _sum_y;
|
private record struct State(double SumY, double SumXY, double LastVal, double LastValidValue);
|
||||||
private double _sum_xy;
|
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 int _tickCount;
|
||||||
|
|
||||||
private const int ResyncInterval = 1000;
|
private const int ResyncInterval = 1000;
|
||||||
@@ -100,10 +95,10 @@ public sealed class Lsma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(input))
|
if (double.IsFinite(input))
|
||||||
{
|
{
|
||||||
_lastValidValue = input;
|
_state.LastValidValue = input;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
return _lastValidValue;
|
return _state.LastValidValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -112,24 +107,24 @@ public sealed class Lsma : ITValuePublisher
|
|||||||
if (_buffer.IsFull)
|
if (_buffer.IsFull)
|
||||||
{
|
{
|
||||||
double oldest = _buffer.Oldest;
|
double oldest = _buffer.Oldest;
|
||||||
double prev_sum_y = _sum_y;
|
double prev_sum_y = _state.SumY;
|
||||||
|
|
||||||
// O(1) update for sum_xy
|
// O(1) update for sum_xy
|
||||||
// sum_xy_new = sum_xy_old + sum_y_prev - n * oldest
|
// 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
|
// O(1) update for sum_y
|
||||||
_sum_y = _sum_y - oldest + val;
|
_state.SumY = _state.SumY - oldest + val;
|
||||||
|
|
||||||
_buffer.Add(val);
|
_buffer.Add(val);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_buffer.Add(val);
|
_buffer.Add(val);
|
||||||
_sum_y += val;
|
_state.SumY += val;
|
||||||
|
|
||||||
// Recalculate sum_xy from scratch during warmup
|
// Recalculate sum_xy from scratch during warmup
|
||||||
_sum_xy = 0;
|
_state.SumXY = 0;
|
||||||
var span = _buffer.GetSpan();
|
var span = _buffer.GetSpan();
|
||||||
for (int i = 0; i < span.Length; i++)
|
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
|
// index j in buffer corresponds to x = count - 1 - j
|
||||||
// sum_xy = sum(x * y)
|
// sum_xy = sum(x * y)
|
||||||
int x = span.Length - 1 - i;
|
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()
|
private void Resync()
|
||||||
{
|
{
|
||||||
_sum_y = _buffer.Sum;
|
_state.SumY = _buffer.Sum;
|
||||||
_sum_xy = 0;
|
_state.SumXY = 0;
|
||||||
var span = _buffer.GetSpan();
|
var span = _buffer.GetSpan();
|
||||||
for (int i = 0; i < span.Length; i++)
|
for (int i = 0; i < span.Length; i++)
|
||||||
{
|
{
|
||||||
int x = span.Length - 1 - 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);
|
double val = GetValidValue(input.Value);
|
||||||
UpdateState(val);
|
UpdateState(val);
|
||||||
|
|
||||||
_p_sum_y = _sum_y;
|
_p_state = _state;
|
||||||
_p_sum_xy = _sum_xy;
|
_state.LastVal = val;
|
||||||
_p_last_val = val;
|
|
||||||
_p_lastValidValue = _lastValidValue;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidValue = _p_lastValidValue;
|
_state.LastValidValue = _p_state.LastValidValue;
|
||||||
double val = GetValidValue(input.Value);
|
double val = GetValidValue(input.Value);
|
||||||
|
|
||||||
// For isNew=false, we update the current bar.
|
// 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_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 updates to reflect the change in the newest value.
|
||||||
|
|
||||||
_sum_y = _p_sum_y - _p_last_val + val;
|
_state.SumY = _p_state.SumY - _p_state.LastVal + val;
|
||||||
_sum_xy = _p_sum_xy; // Restore sum_xy to the state after the shift
|
_state.SumXY = _p_state.SumXY; // Restore sum_xy to the state after the shift
|
||||||
|
|
||||||
_buffer.UpdateNewest(val);
|
_buffer.UpdateNewest(val);
|
||||||
_p_last_val = val;
|
_state.LastVal = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
double result;
|
double result;
|
||||||
@@ -218,8 +211,8 @@ public sealed class Lsma : ITValuePublisher
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double m = (n * _sum_xy - sx * _sum_y) / denom;
|
double m = (n * _state.SumXY - sx * _state.SumY) / denom;
|
||||||
double b = (_sum_y - m * sx) / n;
|
double b = (_state.SumY - m * sx) / n;
|
||||||
|
|
||||||
// LSMA = b - m * offset
|
// LSMA = b - m * offset
|
||||||
result = b - m * _offset;
|
result = b - m * _offset;
|
||||||
@@ -261,17 +254,17 @@ public sealed class Lsma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(source.Values[i]))
|
if (double.IsFinite(source.Values[i]))
|
||||||
{
|
{
|
||||||
_lastValidValue = source.Values[i];
|
_state.LastValidValue = source.Values[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidValue = 0;
|
_state.LastValidValue = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
double lastProcessedValue = _lastValidValue;
|
double lastProcessedValue = _state.LastValidValue;
|
||||||
for (int i = startIndex; i < len; i++)
|
for (int i = startIndex; i < len; i++)
|
||||||
{
|
{
|
||||||
double val = GetValidValue(source.Values[i]);
|
double val = GetValidValue(source.Values[i]);
|
||||||
@@ -279,10 +272,8 @@ public sealed class Lsma : ITValuePublisher
|
|||||||
lastProcessedValue = val;
|
lastProcessedValue = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
_p_sum_y = _sum_y;
|
_state.LastVal = lastProcessedValue;
|
||||||
_p_sum_xy = _sum_xy;
|
_p_state = _state;
|
||||||
_p_last_val = lastProcessedValue;
|
|
||||||
_p_lastValidValue = _lastValidValue;
|
|
||||||
|
|
||||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||||
return new TSeries(t, v);
|
return new TSeries(t, v);
|
||||||
@@ -409,14 +400,9 @@ public sealed class Lsma : ITValuePublisher
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_sum_y = 0;
|
_state = default;
|
||||||
_sum_xy = 0;
|
_p_state = default;
|
||||||
_p_sum_y = 0;
|
|
||||||
_p_sum_xy = 0;
|
|
||||||
_p_last_val = 0;
|
|
||||||
Last = default;
|
Last = default;
|
||||||
_tickCount = 0;
|
_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 Last { get; private set; }
|
||||||
public TValue Fama { 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;
|
public event Action<TValue>? Pub;
|
||||||
|
|
||||||
private readonly double _fastLimit;
|
private readonly double _fastLimit;
|
||||||
private readonly double _slowLimit;
|
private readonly double _slowLimit;
|
||||||
|
|
||||||
private double _period, _p_period;
|
private record struct State(
|
||||||
private double _phase, _p_phase;
|
double Period, double Phase, double Mama, double Fama, double SumPr,
|
||||||
private double _mama, _p_mama;
|
double I2, double Q2, double Re, double Im, double LastValidPrice, int Index
|
||||||
private double _fama, _p_fama;
|
);
|
||||||
private double _sumPr, _p_sumPr;
|
private State _state;
|
||||||
private int _index;
|
private State _p_state;
|
||||||
|
|
||||||
// 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 readonly RingBuffer _priceBuffer;
|
private readonly RingBuffer _priceBuffer;
|
||||||
private readonly RingBuffer _smoothBuffer;
|
private readonly RingBuffer _smoothBuffer;
|
||||||
@@ -69,18 +62,10 @@ public sealed class Mama : ITValuePublisher
|
|||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
_period = _p_period = 0.0;
|
_state = default;
|
||||||
_phase = _p_phase = 0.0;
|
_state.Mama = double.NaN;
|
||||||
_mama = _p_mama = double.NaN;
|
_state.Fama = double.NaN;
|
||||||
_fama = _p_fama = double.NaN;
|
_p_state = _state;
|
||||||
_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;
|
|
||||||
|
|
||||||
_priceBuffer.Clear();
|
_priceBuffer.Clear();
|
||||||
_smoothBuffer.Clear();
|
_smoothBuffer.Clear();
|
||||||
@@ -97,45 +82,29 @@ public sealed class Mama : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_p_period = _period;
|
_p_state = _state;
|
||||||
_p_phase = _phase;
|
_state.Index++;
|
||||||
_p_mama = _mama;
|
|
||||||
_p_fama = _fama;
|
|
||||||
_p_sumPr = _sumPr;
|
|
||||||
_p_i2 = _i2;
|
|
||||||
_p_q2 = _q2;
|
|
||||||
_p_re = _re;
|
|
||||||
_p_im = _im;
|
|
||||||
_index++;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_period = _p_period;
|
_state = _p_state;
|
||||||
_phase = _p_phase;
|
|
||||||
_mama = _p_mama;
|
|
||||||
_fama = _p_fama;
|
|
||||||
_sumPr = _p_sumPr;
|
|
||||||
_i2 = _p_i2;
|
|
||||||
_q2 = _p_q2;
|
|
||||||
_re = _p_re;
|
|
||||||
_im = _p_im;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double price = input.Value;
|
double price = input.Value;
|
||||||
if (!double.IsFinite(price))
|
if (!double.IsFinite(price))
|
||||||
{
|
{
|
||||||
price = _lastValidPrice;
|
price = _state.LastValidPrice;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidPrice = price;
|
_state.LastValidPrice = price;
|
||||||
}
|
}
|
||||||
|
|
||||||
_priceBuffer.Add(price, isNew);
|
_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
|
// Smooth
|
||||||
double smooth = (4.0 * _priceBuffer[0] + 3.0 * _priceBuffer[1] + 2.0 * _priceBuffer[2] + _priceBuffer[3]) * 0.1;
|
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;
|
double q2_val = q1 + jI;
|
||||||
|
|
||||||
// Smooth i2, q2
|
// Smooth i2, q2
|
||||||
_i2 = 0.2 * i2_val + 0.8 * _p_i2;
|
_state.I2 = 0.2 * i2_val + 0.8 * _p_state.I2;
|
||||||
_q2 = 0.2 * q2_val + 0.8 * _p_q2;
|
_state.Q2 = 0.2 * q2_val + 0.8 * _p_state.Q2;
|
||||||
|
|
||||||
// Homodyne discriminator
|
// Homodyne discriminator
|
||||||
double re_val = (_i2 * _p_i2) + (_q2 * _p_q2);
|
double re_val = (_state.I2 * _p_state.I2) + (_state.Q2 * _p_state.Q2);
|
||||||
double im_val = (_i2 * _p_q2) - (_q2 * _p_i2);
|
double im_val = (_state.I2 * _p_state.Q2) - (_state.Q2 * _p_state.I2);
|
||||||
|
|
||||||
// Smooth re, im
|
// Smooth re, im
|
||||||
_re = 0.2 * re_val + 0.8 * _p_re;
|
_state.Re = 0.2 * re_val + 0.8 * _p_state.Re;
|
||||||
_im = 0.2 * im_val + 0.8 * _p_im;
|
_state.Im = 0.2 * im_val + 0.8 * _p_state.Im;
|
||||||
|
|
||||||
// Calculate Period
|
// Calculate Period
|
||||||
double period = (Math.Abs(_im) > double.Epsilon && Math.Abs(_re) > double.Epsilon)
|
double period = (Math.Abs(_state.Im) > double.Epsilon && Math.Abs(_state.Re) > double.Epsilon)
|
||||||
? TWOPI / Math.Atan(_im / _re)
|
? TWOPI / Math.Atan(_state.Im / _state.Re)
|
||||||
: 0.0;
|
: 0.0;
|
||||||
|
|
||||||
// Adjust Period
|
// Adjust Period
|
||||||
period = period > 1.5 * _p_period ? 1.5 * _p_period : period;
|
period = period > 1.5 * _p_state.Period ? 1.5 * _p_state.Period : period;
|
||||||
period = period < 0.67 * _p_period ? 0.67 * _p_period : period;
|
period = period < 0.67 * _p_state.Period ? 0.67 * _p_state.Period : period;
|
||||||
period = period < 6.0 ? 6.0 : period;
|
period = period < 6.0 ? 6.0 : period;
|
||||||
period = period > 50.0 ? 50.0 : period;
|
period = period > 50.0 ? 50.0 : period;
|
||||||
|
|
||||||
// Smooth Period
|
// Smooth Period
|
||||||
_period = 0.2 * period + 0.8 * _p_period;
|
_state.Period = 0.2 * period + 0.8 * _p_state.Period;
|
||||||
|
|
||||||
// Phase calculation
|
// 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
|
// 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;
|
double alpha = _fastLimit / delta;
|
||||||
alpha = Math.Clamp(alpha, _slowLimit, _fastLimit);
|
alpha = Math.Clamp(alpha, _slowLimit, _fastLimit);
|
||||||
|
|
||||||
// Final indicators
|
// Final indicators
|
||||||
_mama = alpha * _priceBuffer[0] + (1.0 - alpha) * _p_mama;
|
_state.Mama = alpha * _priceBuffer[0] + (1.0 - alpha) * _p_state.Mama;
|
||||||
_fama = 0.5 * alpha * _mama + (1.0 - 0.5 * alpha) * _p_fama;
|
_state.Fama = 0.5 * alpha * _state.Mama + (1.0 - 0.5 * alpha) * _p_state.Fama;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Initialization phase
|
// Initialization phase
|
||||||
_sumPr += price;
|
_state.SumPr += price;
|
||||||
double avg = _index > 0 ? _sumPr / _index : price;
|
double avg = _state.Index > 0 ? _state.SumPr / _state.Index : price;
|
||||||
_mama = avg;
|
_state.Mama = avg;
|
||||||
_fama = avg;
|
_state.Fama = avg;
|
||||||
|
|
||||||
// Initialize buffers with 0
|
// Initialize buffers with 0
|
||||||
_smoothBuffer.Add(0, isNew);
|
_smoothBuffer.Add(0, isNew);
|
||||||
@@ -216,8 +185,8 @@ public sealed class Mama : ITValuePublisher
|
|||||||
_Q1_buffer.Add(0, isNew);
|
_Q1_buffer.Add(0, isNew);
|
||||||
}
|
}
|
||||||
|
|
||||||
Last = new TValue(input.Time, _mama);
|
Last = new TValue(input.Time, _state.Mama);
|
||||||
Fama = new TValue(input.Time, _fama);
|
Fama = new TValue(input.Time, _state.Fama);
|
||||||
Pub?.Invoke(Last);
|
Pub?.Invoke(Last);
|
||||||
return Last;
|
return Last;
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-34
@@ -31,12 +31,9 @@ public sealed class Sma : ITValuePublisher
|
|||||||
private readonly int _period;
|
private readonly int _period;
|
||||||
private readonly RingBuffer _buffer;
|
private readonly RingBuffer _buffer;
|
||||||
|
|
||||||
private double _sum;
|
private record struct State(double Sum, double LastInput, double LastValidValue, int TickCount);
|
||||||
private double _p_sum;
|
private State _state;
|
||||||
private double _p_lastInput;
|
private State _p_state;
|
||||||
private double _lastValidValue;
|
|
||||||
private double _p_lastValidValue;
|
|
||||||
private int _tickCount;
|
|
||||||
|
|
||||||
private const int ResyncInterval = 1000;
|
private const int ResyncInterval = 1000;
|
||||||
|
|
||||||
@@ -85,10 +82,10 @@ public sealed class Sma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(input))
|
if (double.IsFinite(input))
|
||||||
{
|
{
|
||||||
_lastValidValue = input;
|
_state.LastValidValue = input;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
return _lastValidValue;
|
return _state.LastValidValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -96,19 +93,18 @@ public sealed class Sma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
double removedValue = _buffer.Count == _buffer.Capacity ? _buffer.Oldest : 0.0;
|
double removedValue = _buffer.Count == _buffer.Capacity ? _buffer.Oldest : 0.0;
|
||||||
|
|
||||||
_sum = _sum - removedValue + val;
|
_state.Sum = _state.Sum - removedValue + val;
|
||||||
|
|
||||||
_buffer.Add(val);
|
_buffer.Add(val);
|
||||||
|
|
||||||
_tickCount++;
|
_state.TickCount++;
|
||||||
if (_buffer.IsFull && _tickCount >= ResyncInterval)
|
if (_buffer.IsFull && _state.TickCount >= ResyncInterval)
|
||||||
{
|
{
|
||||||
_tickCount = 0;
|
_state.TickCount = 0;
|
||||||
_sum = _buffer.RecalculateSum();
|
_state.Sum = _buffer.RecalculateSum();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public TValue Update(TValue input, bool isNew = true)
|
public TValue Update(TValue input, bool isNew = true)
|
||||||
{
|
{
|
||||||
@@ -116,21 +112,20 @@ public sealed class Sma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
double val = GetValidValue(input.Value);
|
double val = GetValidValue(input.Value);
|
||||||
UpdateState(val);
|
UpdateState(val);
|
||||||
|
_state.LastInput = val;
|
||||||
|
|
||||||
_p_sum = _sum;
|
_p_state = _state;
|
||||||
_p_lastInput = val;
|
|
||||||
_p_lastValidValue = _lastValidValue;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidValue = _p_lastValidValue;
|
_state = _p_state;
|
||||||
double val = GetValidValue(input.Value);
|
double val = GetValidValue(input.Value);
|
||||||
|
|
||||||
_sum = _p_sum - _p_lastInput + val;
|
_state.Sum = _state.Sum - _state.LastInput + val;
|
||||||
_buffer.UpdateNewest(val);
|
_buffer.UpdateNewest(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
double result = _sum / _buffer.Count;
|
double result = _state.Sum / _buffer.Count;
|
||||||
Last = new TValue(input.Time, result);
|
Last = new TValue(input.Time, result);
|
||||||
Pub?.Invoke(Last);
|
Pub?.Invoke(Last);
|
||||||
return Last;
|
return Last;
|
||||||
@@ -158,34 +153,33 @@ public sealed class Sma : ITValuePublisher
|
|||||||
|
|
||||||
if (startIndex > 0)
|
if (startIndex > 0)
|
||||||
{
|
{
|
||||||
_lastValidValue = 0;
|
_state.LastValidValue = 0;
|
||||||
for (int i = startIndex - 1; i >= 0; i--)
|
for (int i = startIndex - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(source.Values[i]))
|
if (double.IsFinite(source.Values[i]))
|
||||||
{
|
{
|
||||||
_lastValidValue = source.Values[i];
|
_state.LastValidValue = source.Values[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidValue = 0;
|
_state.LastValidValue = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_sum = 0;
|
_state.Sum = 0;
|
||||||
_tickCount = 0;
|
_state.TickCount = 0;
|
||||||
|
|
||||||
for (int i = startIndex; i < len; i++)
|
for (int i = startIndex; i < len; i++)
|
||||||
{
|
{
|
||||||
double val = GetValidValue(source.Values[i]);
|
double val = GetValidValue(source.Values[i]);
|
||||||
UpdateState(val);
|
UpdateState(val);
|
||||||
|
_state.LastInput = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
_p_sum = _sum;
|
_p_state = _state;
|
||||||
_p_lastInput = GetValidValue(source.Values[len - 1]);
|
|
||||||
_p_lastValidValue = _lastValidValue;
|
|
||||||
|
|
||||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||||
return new TSeries(t, v);
|
return new TSeries(t, v);
|
||||||
@@ -528,12 +522,8 @@ public sealed class Sma : ITValuePublisher
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_sum = 0;
|
_state = default;
|
||||||
_p_sum = 0;
|
_p_state = default;
|
||||||
_p_lastInput = 0;
|
|
||||||
_lastValidValue = 0;
|
|
||||||
_p_lastValidValue = 0;
|
|
||||||
_tickCount = 0;
|
|
||||||
Last = default;
|
Last = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-19
@@ -26,27 +26,9 @@ namespace QuanTAlib;
|
|||||||
[SkipLocalsInit]
|
[SkipLocalsInit]
|
||||||
public sealed class T3 : ITValuePublisher
|
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 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>
|
private readonly struct Parameters : IEquatable<Parameters>
|
||||||
|
|||||||
+1
-20
@@ -26,28 +26,9 @@ namespace QuanTAlib;
|
|||||||
[SkipLocalsInit]
|
[SkipLocalsInit]
|
||||||
public sealed class Tema : ITValuePublisher
|
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 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;
|
private readonly double _alpha;
|
||||||
|
|||||||
+73
-67
@@ -31,18 +31,19 @@ public sealed class Trima : ITValuePublisher
|
|||||||
private readonly RingBuffer _buffer1;
|
private readonly RingBuffer _buffer1;
|
||||||
private readonly RingBuffer _buffer2;
|
private readonly RingBuffer _buffer2;
|
||||||
|
|
||||||
private double _sum1, _p_sum1, _p_lastInput1, _lastValidValue1, _p_lastValidValue1;
|
private record struct State(
|
||||||
private int _tickCount1;
|
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;
|
private const int ResyncInterval = 1000;
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public TValue Last { get; private set; }
|
public TValue Last { get; private set; }
|
||||||
public bool IsHot => _sampleCount >= _period;
|
public bool IsHot => _state.SampleCount >= _period;
|
||||||
public event Action<TValue>? Pub;
|
public event Action<TValue>? Pub;
|
||||||
|
|
||||||
public Trima(int period)
|
public Trima(int period)
|
||||||
@@ -69,10 +70,10 @@ public sealed class Trima : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(input))
|
if (double.IsFinite(input))
|
||||||
{
|
{
|
||||||
_lastValidValue1 = input;
|
_state.LastValidValue1 = input;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
return _lastValidValue1;
|
return _state.LastValidValue1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -80,61 +81,72 @@ public sealed class Trima : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_sampleCount++;
|
_p_state = _state;
|
||||||
|
_state.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);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// SMA 1 Correction
|
_state = _p_state;
|
||||||
_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
Pub?.Invoke(Last);
|
||||||
return Last;
|
return Last;
|
||||||
}
|
}
|
||||||
@@ -203,14 +215,8 @@ public sealed class Trima : ITValuePublisher
|
|||||||
{
|
{
|
||||||
_buffer1.Clear();
|
_buffer1.Clear();
|
||||||
_buffer2.Clear();
|
_buffer2.Clear();
|
||||||
|
_state = default;
|
||||||
_sum1 = _p_sum1 = _p_lastInput1 = _lastValidValue1 = _p_lastValidValue1 = 0;
|
_p_state = default;
|
||||||
_tickCount1 = 0;
|
|
||||||
|
|
||||||
_sum2 = _p_sum2 = _p_lastInput2 = 0;
|
|
||||||
_tickCount2 = 0;
|
|
||||||
|
|
||||||
_sampleCount = 0;
|
|
||||||
Last = default;
|
Last = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-76
@@ -31,12 +31,13 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
private readonly RingBuffer _ups;
|
private readonly RingBuffer _ups;
|
||||||
private readonly RingBuffer _downs;
|
private readonly RingBuffer _downs;
|
||||||
|
|
||||||
private double _prevClose;
|
private record struct State(
|
||||||
private double _lastVidya;
|
double PrevClose, double LastVidya,
|
||||||
private double _currentClose;
|
double CurrentClose, double CurrentVidya,
|
||||||
private double _currentVidya;
|
bool IsInitialized, int BarCount
|
||||||
private bool _isInitialized;
|
);
|
||||||
private int _barCount;
|
private State _state;
|
||||||
|
private State _p_state;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Display name for the indicator.
|
/// Display name for the indicator.
|
||||||
@@ -78,12 +79,18 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
_barCount++;
|
_p_state = _state;
|
||||||
if (_isInitialized)
|
}
|
||||||
{
|
else
|
||||||
_prevClose = _currentClose;
|
{
|
||||||
_lastVidya = _currentVidya;
|
_state = _p_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_state.BarCount++;
|
||||||
|
if (_state.IsInitialized)
|
||||||
|
{
|
||||||
|
_state.PrevClose = _state.CurrentClose;
|
||||||
|
_state.LastVidya = _state.CurrentVidya;
|
||||||
}
|
}
|
||||||
|
|
||||||
double price = input.Value;
|
double price = input.Value;
|
||||||
@@ -91,25 +98,25 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
{
|
{
|
||||||
// Handle NaN/Infinity by using the last known valid values
|
// Handle NaN/Infinity by using the last known valid values
|
||||||
// If not initialized, we can't do much, just return input
|
// If not initialized, we can't do much, just return input
|
||||||
if (!_isInitialized) return input;
|
if (!_state.IsInitialized) return input;
|
||||||
price = _currentClose; // Use last valid close
|
price = _state.CurrentClose; // Use last valid close
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_barCount <= 1)
|
if (_state.BarCount <= 1)
|
||||||
{
|
{
|
||||||
_prevClose = price;
|
_state.PrevClose = price;
|
||||||
_lastVidya = price;
|
_state.LastVidya = price;
|
||||||
_currentClose = price;
|
_state.CurrentClose = price;
|
||||||
_currentVidya = price;
|
_state.CurrentVidya = price;
|
||||||
_isInitialized = true;
|
_state.IsInitialized = true;
|
||||||
_ups.Add(0, isNew);
|
_ups.Add(0, isNew);
|
||||||
_downs.Add(0, isNew);
|
_downs.Add(0, isNew);
|
||||||
Last = new TValue(input.Time, _currentVidya);
|
Last = new TValue(input.Time, _state.CurrentVidya);
|
||||||
Pub?.Invoke(Last);
|
Pub?.Invoke(Last);
|
||||||
return Last;
|
return Last;
|
||||||
}
|
}
|
||||||
|
|
||||||
double change = price - _prevClose;
|
double change = price - _state.PrevClose;
|
||||||
double up = change > 0 ? change : 0;
|
double up = change > 0 ? change : 0;
|
||||||
double down = change < 0 ? -change : 0;
|
double down = change < 0 ? -change : 0;
|
||||||
|
|
||||||
@@ -127,10 +134,10 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
}
|
}
|
||||||
|
|
||||||
double dynamicAlpha = _alpha * vi;
|
double dynamicAlpha = _alpha * vi;
|
||||||
_currentVidya = dynamicAlpha * price + (1.0 - dynamicAlpha) * _lastVidya;
|
_state.CurrentVidya = dynamicAlpha * price + (1.0 - dynamicAlpha) * _state.LastVidya;
|
||||||
_currentClose = price;
|
_state.CurrentClose = price;
|
||||||
|
|
||||||
Last = new TValue(input.Time, _currentVidya);
|
Last = new TValue(input.Time, _state.CurrentVidya);
|
||||||
Pub?.Invoke(Last);
|
Pub?.Invoke(Last);
|
||||||
return Last;
|
return Last;
|
||||||
}
|
}
|
||||||
@@ -150,42 +157,10 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
var sourceValues = source.Values;
|
var sourceValues = source.Values;
|
||||||
var sourceTimes = source.Times;
|
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);
|
Calculate(sourceValues, vSpan, _period);
|
||||||
|
|
||||||
sourceTimes.CopyTo(tSpan);
|
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();
|
Reset();
|
||||||
for (int i = 0; i < len; i++)
|
for (int i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
@@ -210,14 +185,6 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
|
|
||||||
double alpha = 2.0 / (period + 1);
|
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[] ups = new double[period];
|
||||||
double[] downs = new double[period];
|
double[] downs = new double[period];
|
||||||
int head = 0;
|
int head = 0;
|
||||||
@@ -227,11 +194,8 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
double prevClose = source[0];
|
double prevClose = source[0];
|
||||||
double lastVidya = source[0];
|
double lastVidya = source[0];
|
||||||
|
|
||||||
// Initialize first element
|
|
||||||
output[0] = source[0];
|
output[0] = source[0];
|
||||||
|
|
||||||
// Fill buffers with 0 initially (already done by new double[])
|
|
||||||
|
|
||||||
for (int i = 1; i < source.Length; i++)
|
for (int i = 1; i < source.Length; i++)
|
||||||
{
|
{
|
||||||
double price = source[i];
|
double price = source[i];
|
||||||
@@ -244,7 +208,6 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
double up = change > 0 ? change : 0;
|
double up = change > 0 ? change : 0;
|
||||||
double down = change < 0 ? -change : 0;
|
double down = change < 0 ? -change : 0;
|
||||||
|
|
||||||
// Update sums: remove old, add new
|
|
||||||
sumUp -= ups[head];
|
sumUp -= ups[head];
|
||||||
sumDown -= downs[head];
|
sumDown -= downs[head];
|
||||||
|
|
||||||
@@ -277,13 +240,8 @@ public sealed class Vidya : ITValuePublisher
|
|||||||
{
|
{
|
||||||
_ups.Clear();
|
_ups.Clear();
|
||||||
_downs.Clear();
|
_downs.Clear();
|
||||||
_prevClose = 0;
|
_state = default;
|
||||||
_lastVidya = 0;
|
_p_state = default;
|
||||||
_currentClose = 0;
|
|
||||||
_currentVidya = 0;
|
|
||||||
_isInitialized = false;
|
|
||||||
_barCount = 0;
|
|
||||||
|
|
||||||
Last = default;
|
Last = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-37
@@ -32,10 +32,10 @@ public sealed class Wma : ITValuePublisher
|
|||||||
private readonly double _divisor;
|
private readonly double _divisor;
|
||||||
private readonly RingBuffer _buffer;
|
private readonly RingBuffer _buffer;
|
||||||
|
|
||||||
private double _sum, _wsum;
|
private record struct State(double Sum, double WSum, double LastInput, double LastValidValue, int TickCount);
|
||||||
private double _p_sum, _p_wsum, _p_lastInput;
|
private State _state;
|
||||||
private double _lastValidValue, _p_lastValidValue;
|
private State _p_state;
|
||||||
private int _tickCount;
|
|
||||||
private const int ResyncInterval = 1000;
|
private const int ResyncInterval = 1000;
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
@@ -63,10 +63,10 @@ public sealed class Wma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(input))
|
if (double.IsFinite(input))
|
||||||
{
|
{
|
||||||
_lastValidValue = input;
|
_state.LastValidValue = input;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
return _lastValidValue;
|
return _state.LastValidValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -74,24 +74,24 @@ public sealed class Wma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
if (_buffer.IsFull)
|
if (_buffer.IsFull)
|
||||||
{
|
{
|
||||||
double oldSum = _sum;
|
double oldSum = _state.Sum;
|
||||||
double oldest = _buffer.Oldest;
|
double oldest = _buffer.Oldest;
|
||||||
_sum = _sum - oldest + val;
|
_state.Sum = _state.Sum - oldest + val;
|
||||||
_wsum = _wsum - oldSum + (_period * val);
|
_state.WSum = _state.WSum - oldSum + (_period * val);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int count = _buffer.Count + 1;
|
int count = _buffer.Count + 1;
|
||||||
_sum += val;
|
_state.Sum += val;
|
||||||
_wsum += count * val;
|
_state.WSum += count * val;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Add(val);
|
_buffer.Add(val);
|
||||||
|
|
||||||
_tickCount++;
|
_state.TickCount++;
|
||||||
if (_buffer.IsFull && _tickCount >= ResyncInterval)
|
if (_buffer.IsFull && _state.TickCount >= ResyncInterval)
|
||||||
{
|
{
|
||||||
_tickCount = 0;
|
_state.TickCount = 0;
|
||||||
double recalcSum = 0;
|
double recalcSum = 0;
|
||||||
double recalcWsum = 0;
|
double recalcWsum = 0;
|
||||||
int weight = 1;
|
int weight = 1;
|
||||||
@@ -101,8 +101,8 @@ public sealed class Wma : ITValuePublisher
|
|||||||
recalcWsum += weight * item;
|
recalcWsum += weight * item;
|
||||||
weight++;
|
weight++;
|
||||||
}
|
}
|
||||||
_sum = recalcSum;
|
_state.Sum = recalcSum;
|
||||||
_wsum = recalcWsum;
|
_state.WSum = recalcWsum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,29 +113,24 @@ public sealed class Wma : ITValuePublisher
|
|||||||
{
|
{
|
||||||
double val = GetValidValue(input.Value);
|
double val = GetValidValue(input.Value);
|
||||||
UpdateState(val);
|
UpdateState(val);
|
||||||
|
_state.LastInput = val;
|
||||||
|
|
||||||
_p_sum = _sum;
|
_p_state = _state;
|
||||||
_p_wsum = _wsum;
|
|
||||||
_p_lastInput = val;
|
|
||||||
_p_lastValidValue = _lastValidValue;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidValue = _p_lastValidValue;
|
_state = _p_state;
|
||||||
double val = GetValidValue(input.Value);
|
double val = GetValidValue(input.Value);
|
||||||
|
|
||||||
_sum = _p_sum;
|
|
||||||
_wsum = _p_wsum;
|
|
||||||
|
|
||||||
int weight = _buffer.IsFull ? _period : _buffer.Count;
|
int weight = _buffer.IsFull ? _period : _buffer.Count;
|
||||||
_sum = _sum - _p_lastInput + val;
|
_state.Sum = _state.Sum - _state.LastInput + val;
|
||||||
_wsum += weight * (val - _p_lastInput);
|
_state.WSum += weight * (val - _state.LastInput);
|
||||||
|
|
||||||
_buffer.UpdateNewest(val);
|
_buffer.UpdateNewest(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
double currentDivisor = _buffer.IsFull ? _divisor : (double)_buffer.Count * (_buffer.Count + 1) * 0.5;
|
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);
|
Pub?.Invoke(Last);
|
||||||
return Last;
|
return Last;
|
||||||
}
|
}
|
||||||
@@ -162,35 +157,34 @@ public sealed class Wma : ITValuePublisher
|
|||||||
|
|
||||||
if (startIndex > 0)
|
if (startIndex > 0)
|
||||||
{
|
{
|
||||||
|
_state.LastValidValue = 0;
|
||||||
for (int i = startIndex - 1; i >= 0; i--)
|
for (int i = startIndex - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (double.IsFinite(source.Values[i]))
|
if (double.IsFinite(source.Values[i]))
|
||||||
{
|
{
|
||||||
_lastValidValue = source.Values[i];
|
_state.LastValidValue = source.Values[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_lastValidValue = 0;
|
_state.LastValidValue = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_sum = 0;
|
_state.Sum = 0;
|
||||||
_wsum = 0;
|
_state.WSum = 0;
|
||||||
_tickCount = 0;
|
_state.TickCount = 0;
|
||||||
|
|
||||||
for (int i = startIndex; i < len; i++)
|
for (int i = startIndex; i < len; i++)
|
||||||
{
|
{
|
||||||
double val = GetValidValue(source.Values[i]);
|
double val = GetValidValue(source.Values[i]);
|
||||||
UpdateState(val);
|
UpdateState(val);
|
||||||
|
_state.LastInput = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
_p_sum = _sum;
|
_p_state = _state;
|
||||||
_p_wsum = _wsum;
|
|
||||||
_p_lastInput = GetValidValue(source.Values[len - 1]);
|
|
||||||
_p_lastValidValue = _lastValidValue;
|
|
||||||
|
|
||||||
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
||||||
return new TSeries(t, v);
|
return new TSeries(t, v);
|
||||||
@@ -754,7 +748,8 @@ public sealed class Wma : ITValuePublisher
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_sum = _wsum = _p_sum = _p_wsum = _p_lastInput = _lastValidValue = _p_lastValidValue = 0;
|
_state = default;
|
||||||
|
_p_state = default;
|
||||||
Last = default;
|
Last = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace QuanTAlib;
|
||||||
|
|
||||||
|
public class RecordStructTest
|
||||||
|
{
|
||||||
|
private record struct State(double Val, bool Flag);
|
||||||
|
|
||||||
|
public static void Run()
|
||||||
|
{
|
||||||
|
var s1 = new State(1.0, true);
|
||||||
|
var s2 = new State(1.0, true);
|
||||||
|
var s3 = new State(2.0, false);
|
||||||
|
|
||||||
|
Console.WriteLine($"s1 == s2: {s1 == s2}"); // Should be True
|
||||||
|
Console.WriteLine($"s1 == s3: {s1 == s3}"); // Should be False
|
||||||
|
Console.WriteLine($"s1 equals s2: {s1.Equals(s2)}"); // Should be True
|
||||||
|
|
||||||
|
// Verify mutability
|
||||||
|
s1.Val = 3.0;
|
||||||
|
Console.WriteLine($"s1 modified: {s1.Val}");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user