volume indicators

This commit is contained in:
Miha Kralj
2026-01-30 12:47:25 -08:00
parent 76d2b50cbb
commit 7b3a6520d2
99 changed files with 9539 additions and 283 deletions
+30 -3
View File
@@ -16,7 +16,7 @@ namespace QuanTAlib;
/// - Period forced to even, >= 2.
/// </remarks>
[SkipLocalsInit]
public sealed class Frama : ITValuePublisher
public sealed class Frama : ITValuePublisher, IDisposable
{
private const double AlphaFloor = 0.01;
private const double AlphaCeil = 1.0;
@@ -27,6 +27,8 @@ public sealed class Frama : ITValuePublisher
private readonly RingBuffer _highs;
private readonly RingBuffer _lows;
private readonly TValuePublishedHandler _handler;
private readonly ITValuePublisher? _source;
private bool _disposed;
[StructLayout(LayoutKind.Sequential)]
private record struct State
@@ -69,6 +71,7 @@ public sealed class Frama : ITValuePublisher
public Frama(ITValuePublisher source, int period) : this(period)
{
_source = source;
source.Pub += _handler;
}
@@ -131,12 +134,16 @@ public sealed class Frama : ITValuePublisher
double price = (high + low) * 0.5;
// Recent half: last _half values (most recent)
double maxRecent = GetMax(_highs, _half);
double minRecent = GetMin(_lows, _half);
// Full period: all _periodEven values
double maxFull = GetMax(_highs, _periodEven);
double minFull = GetMin(_lows, _periodEven);
double maxPrev = GetMax(_highs, _half, startOffset: 0);
double minPrev = GetMin(_lows, _half, startOffset: 0);
// Previous half: older _half values (starts at count - _periodEven)
int prevOffset = _highs.Count - _periodEven;
double maxPrev = GetMax(_highs, _half, startOffset: prevOffset);
double minPrev = GetMin(_lows, _half, startOffset: prevOffset);
double n1 = (maxRecent - minRecent) / _half;
double n2 = (maxPrev - minPrev) / _half;
@@ -357,4 +364,24 @@ public sealed class Frama : ITValuePublisher
return min;
}
/// <summary>
/// Disposes the indicator and unsubscribes from the source.
/// </summary>
public void Dispose()
{
Dispose(disposing: true);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing && _source != null)
{
_source.Pub -= _handler;
}
_disposed = true;
}
}
}
+3 -2
View File
@@ -10,9 +10,10 @@ public class HemaTests
{
Assert.Throws<ArgumentOutOfRangeException>(() => new Hema(0));
Assert.Throws<ArgumentOutOfRangeException>(() => new Hema(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => new Hema(1));
var hema = new Hema(1);
Assert.Equal("Hema(1)", hema.Name);
var hema = new Hema(2);
Assert.Equal("Hema(2)", hema.Name);
}
[Fact]
+2 -2
View File
@@ -75,9 +75,9 @@ public sealed class Hema : AbstractBase
public Hema(int period)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(period);
ArgumentOutOfRangeException.ThrowIfLessThan(period, 2);
double n = Math.Max((double)period, 2.0);
double n = (double)period;
_alphaSlow = AlphaFromHalfLife(n);
_alphaFast = AlphaFromHalfLife(Math.Max(1.0, n * 0.5));
_alphaSmooth = AlphaFromHalfLife(Math.Max(1.0, Math.Sqrt(n)));
+1 -1
View File
@@ -150,7 +150,7 @@ public sealed class Mama : AbstractBase
if (_state.Index > 6)
{
double adj = (AdjSlope * _p_state.Period) + AdjIntercept;
double adj = (AdjSlope * _state.Period) + AdjIntercept;
// Smooth
double smooth = (4.0 * _priceBuffer[^1] + 3.0 * _priceBuffer[^2] + 2.0 * _priceBuffer[^3] + _priceBuffer[^4]) * 0.1;
+2 -2
View File
@@ -83,7 +83,7 @@ public sealed class Mgdi : AbstractBase
else
{
Last = new TValue(input.Time, double.NaN);
PubEvent(Last);
PubEvent(Last, isNew);
return Last;
}
}
@@ -236,4 +236,4 @@ public sealed class Mgdi : AbstractBase
{
Init();
}
}
}
+20
View File
@@ -41,6 +41,9 @@ public sealed class Rgma : AbstractBase
private double _lastValidValue;
private double _p_lastValidValue;
private ITValuePublisher? _publisher;
private bool _disposed;
private const double COVERAGE_THRESHOLD = 0.05;
private const int ResyncInterval = 10000;
private const int StackAllocThreshold = 512;
@@ -78,6 +81,7 @@ public sealed class Rgma : AbstractBase
/// </summary>
public Rgma(ITValuePublisher source, int period, int passes = 3) : this(period, passes)
{
_publisher = source;
source.Pub += Handle;
}
@@ -91,6 +95,7 @@ public sealed class Rgma : AbstractBase
{
Last = new TValue(source.LastTime, Last.Value);
}
_publisher = source;
source.Pub += Handle;
}
@@ -470,4 +475,19 @@ public sealed class Rgma : AbstractBase
Array.Fill(_p_filters, double.NaN);
Last = default;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing && _publisher != null)
{
_publisher.Pub -= Handle;
_publisher = null;
}
_disposed = true;
}
base.Dispose(disposing);
}
}
+15 -14
View File
@@ -229,9 +229,15 @@ public sealed class Yzvama : AbstractBase
private void RemoveSorted(double value, int currentCount)
{
int removePos = LowerBound(_activeSortedYzv, currentCount, value);
if (removePos < currentCount && Math.Abs(_activeSortedYzv[removePos] - value) < EPSILON && removePos < currentCount - 1)
if (removePos < currentCount && Math.Abs(_activeSortedYzv[removePos] - value) < EPSILON)
{
Array.Copy(_activeSortedYzv, removePos + 1, _activeSortedYzv, removePos, currentCount - 1 - removePos);
// Shift elements left if not removing the last element
if (removePos < currentCount - 1)
{
Array.Copy(_activeSortedYzv, removePos + 1, _activeSortedYzv, removePos, currentCount - 1 - removePos);
}
// Clear the now-unused tail slot to avoid stale data
_activeSortedYzv[currentCount - 1] = double.NaN;
}
}
@@ -249,27 +255,22 @@ public sealed class Yzvama : AbstractBase
{
if (isNew)
{
// Save state - pointer swap instead of O(n) Array.Copy
// Save state - copy current to backup for potential rollback
_p_state = _state;
_p_lastValidSource = _lastValidSource;
// Swap buffer references for backup
(_activeSourceBuffer, _backupSourceBuffer) = (_backupSourceBuffer, _activeSourceBuffer);
(_activeYzvBuffer, _backupYzvBuffer) = (_backupYzvBuffer, _activeYzvBuffer);
(_activeSortedYzv, _backupSortedYzv) = (_backupSortedYzv, _activeSortedYzv);
// Copy current state to active buffer (only needed for first update after swap)
Array.Copy(_backupSourceBuffer, _activeSourceBuffer, _maxLength);
Array.Copy(_backupYzvBuffer, _activeYzvBuffer, _percentileLookback);
Array.Copy(_backupSortedYzv, _activeSortedYzv, _percentileLookback);
// Copy active to backup for rollback capability (only copy, no swap)
Array.Copy(_activeSourceBuffer, _backupSourceBuffer, _maxLength);
Array.Copy(_activeYzvBuffer, _backupYzvBuffer, _percentileLookback);
Array.Copy(_activeSortedYzv, _backupSortedYzv, _percentileLookback);
}
else
{
// Restore state - pointer swap back
// Restore state from backup - O(1) pointer swap for rollback
_state = _p_state;
_lastValidSource = _p_lastValidSource;
// Swap back to restore backup as active
// Swap pointers so backup becomes active (true O(1) rollback)
(_activeSourceBuffer, _backupSourceBuffer) = (_backupSourceBuffer, _activeSourceBuffer);
(_activeYzvBuffer, _backupYzvBuffer) = (_backupYzvBuffer, _activeYzvBuffer);
(_activeSortedYzv, _backupSortedYzv) = (_backupSortedYzv, _activeSortedYzv);
+1
View File
@@ -309,6 +309,7 @@ public sealed class Zlema : AbstractBase
_lastValidValue = double.NaN;
_p_lastValidValue = double.NaN;
// Clear the buffer and fill with zeros for proper initialization
_lagBuffer.Clear();
for (int i = 0; i < _lagBuffer.Capacity; i++)
{