mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-25 05:48:06 +00:00
volume indicators
This commit is contained in:
@@ -329,7 +329,7 @@ public sealed class Bessel : AbstractBase
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, filt);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ public class BpfIndicatorTests
|
||||
{
|
||||
var indicator = new BpfIndicator();
|
||||
|
||||
Assert.Equal(40, indicator.LowerPeriod);
|
||||
Assert.Equal(10, indicator.UpperPeriod);
|
||||
Assert.Equal(10, indicator.LowerPeriod);
|
||||
Assert.Equal(40, indicator.UpperPeriod);
|
||||
Assert.Equal(SourceType.Close, indicator.Source);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("BPF - Bandpass Filter", indicator.Name);
|
||||
@@ -21,7 +21,7 @@ public class BpfIndicatorTests
|
||||
[Fact]
|
||||
public void BpfIndicator_MinHistoryDepths_EqualsZero()
|
||||
{
|
||||
var indicator = new BpfIndicator { LowerPeriod = 20, UpperPeriod = 5 };
|
||||
var indicator = new BpfIndicator { LowerPeriod = 5, UpperPeriod = 20 };
|
||||
|
||||
Assert.Equal(0, BpfIndicator.MinHistoryDepths);
|
||||
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
||||
@@ -30,17 +30,17 @@ public class BpfIndicatorTests
|
||||
[Fact]
|
||||
public void BpfIndicator_ShortName_IncludesParameters()
|
||||
{
|
||||
var indicator = new BpfIndicator { LowerPeriod = 40, UpperPeriod = 10 };
|
||||
var indicator = new BpfIndicator { LowerPeriod = 10, UpperPeriod = 40 };
|
||||
|
||||
Assert.Contains("BPF", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("40", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("10", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("40", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BpfIndicator_Initialize_CreatesInternalBpf()
|
||||
{
|
||||
var indicator = new BpfIndicator { LowerPeriod = 40, UpperPeriod = 10 };
|
||||
var indicator = new BpfIndicator { LowerPeriod = 10, UpperPeriod = 40 };
|
||||
|
||||
// Initialize should not throw
|
||||
indicator.Initialize();
|
||||
@@ -52,7 +52,7 @@ public class BpfIndicatorTests
|
||||
[Fact]
|
||||
public void BpfIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new BpfIndicator { LowerPeriod = 40, UpperPeriod = 10 };
|
||||
var indicator = new BpfIndicator { LowerPeriod = 10, UpperPeriod = 40 };
|
||||
indicator.Initialize();
|
||||
|
||||
// Add historical data
|
||||
@@ -71,7 +71,7 @@ public class BpfIndicatorTests
|
||||
[Fact]
|
||||
public void BpfIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new BpfIndicator { LowerPeriod = 40, UpperPeriod = 10 };
|
||||
var indicator = new BpfIndicator { LowerPeriod = 10, UpperPeriod = 40 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -87,7 +87,7 @@ public class BpfIndicatorTests
|
||||
[Fact]
|
||||
public void BpfIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
|
||||
{
|
||||
var indicator = new BpfIndicator { LowerPeriod = 40, UpperPeriod = 10 };
|
||||
var indicator = new BpfIndicator { LowerPeriod = 10, UpperPeriod = 40 };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -110,7 +110,7 @@ public class BpfIndicatorTests
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var indicator = new BpfIndicator { LowerPeriod = 40, UpperPeriod = 10, Source = source };
|
||||
var indicator = new BpfIndicator { LowerPeriod = 10, UpperPeriod = 40, Source = source };
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -125,13 +125,13 @@ public class BpfIndicatorTests
|
||||
[Fact]
|
||||
public void BpfIndicator_Periods_CanBeChanged()
|
||||
{
|
||||
var indicator = new BpfIndicator { LowerPeriod = 40, UpperPeriod = 10 };
|
||||
Assert.Equal(40, indicator.LowerPeriod);
|
||||
Assert.Equal(10, indicator.UpperPeriod);
|
||||
var indicator = new BpfIndicator { LowerPeriod = 10, UpperPeriod = 40 };
|
||||
Assert.Equal(10, indicator.LowerPeriod);
|
||||
Assert.Equal(40, indicator.UpperPeriod);
|
||||
|
||||
indicator.LowerPeriod = 60;
|
||||
indicator.UpperPeriod = 20;
|
||||
Assert.Equal(60, indicator.LowerPeriod);
|
||||
Assert.Equal(20, indicator.UpperPeriod);
|
||||
indicator.LowerPeriod = 20;
|
||||
indicator.UpperPeriod = 60;
|
||||
Assert.Equal(20, indicator.LowerPeriod);
|
||||
Assert.Equal(60, indicator.UpperPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class BpfIndicator : Indicator, IWatchlistIndicator
|
||||
{
|
||||
[InputParameter("Max Period (HP)", sortIndex: 1, 1, 2000, 1, 0)]
|
||||
public int LowerPeriod { get; set; } = 40;
|
||||
[InputParameter("Lower Period (HP)", sortIndex: 1, 1, 2000, 1, 0)]
|
||||
public int LowerPeriod { get; set; } = 10;
|
||||
|
||||
[InputParameter("Min Period (LP)", sortIndex: 2, 1, 2000, 1, 0)]
|
||||
public int UpperPeriod { get; set; } = 10;
|
||||
[InputParameter("Upper Period (LP)", sortIndex: 2, 1, 2000, 1, 0)]
|
||||
public int UpperPeriod { get; set; } = 40;
|
||||
|
||||
[IndicatorExtensions.DataSourceInput]
|
||||
public SourceType Source { get; set; } = SourceType.Close;
|
||||
|
||||
@@ -62,6 +62,13 @@ public sealed class Bpf : AbstractBase
|
||||
throw new ArgumentOutOfRangeException(nameof(upperPeriod), "Upper period must be >= 1");
|
||||
}
|
||||
|
||||
if (lowerPeriod >= upperPeriod)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Lower cutoff period ({lowerPeriod}) must be less than upper cutoff period ({upperPeriod}) for a valid passband.",
|
||||
nameof(lowerPeriod));
|
||||
}
|
||||
|
||||
LowerPeriod = lowerPeriod;
|
||||
UpperPeriod = upperPeriod;
|
||||
Name = $"BPF({lowerPeriod},{upperPeriod})";
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class Butter : AbstractBase
|
||||
_period = period;
|
||||
CalculateCoefficients();
|
||||
Name = $"Butter({_period})";
|
||||
WarmupPeriod = 2;
|
||||
WarmupPeriod = 4 * period;
|
||||
_handler = new TValuePublishedHandler(Handle);
|
||||
Init();
|
||||
}
|
||||
|
||||
@@ -94,6 +94,9 @@ public sealed class Elliptic : AbstractBase
|
||||
_b2 = b2_val * gain_corr;
|
||||
_a1 = a1_val;
|
||||
_a2 = a2_val;
|
||||
|
||||
// Initialize LastValid to NaN so first non-finite input doesn't use uninitialized 0.0
|
||||
_state.LastValid = double.NaN;
|
||||
}
|
||||
|
||||
public Elliptic(ITValuePublisher source, int period) : this(period)
|
||||
@@ -201,6 +204,7 @@ public sealed class Elliptic : AbstractBase
|
||||
public override void Reset()
|
||||
{
|
||||
_state = default;
|
||||
_state.LastValid = double.NaN;
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
|
||||
+40
-14
@@ -1,3 +1,4 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -235,6 +236,8 @@ public sealed class Gauss : AbstractBase
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double sigma)
|
||||
{
|
||||
const int StackallocThreshold = 256;
|
||||
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output spans must be of equal length.", nameof(output));
|
||||
@@ -242,25 +245,40 @@ public sealed class Gauss : AbstractBase
|
||||
|
||||
int kernelSize = (int)(2 * Math.Ceiling(3.0 * sigma) + 1);
|
||||
|
||||
// Precompute weights
|
||||
Span<double> weights = stackalloc double[kernelSize];
|
||||
double sum = 0;
|
||||
int center = kernelSize / 2;
|
||||
double twoSigmaSq = 2.0 * sigma * sigma;
|
||||
// Use stackalloc for small kernels, ArrayPool for large ones to avoid stack overflow
|
||||
double[]? rented = null;
|
||||
scoped Span<double> weights;
|
||||
scoped Span<double> stackBuffer = stackalloc double[Math.Min(kernelSize, StackallocThreshold)];
|
||||
|
||||
for (int i = 0; i < kernelSize; i++)
|
||||
if (kernelSize <= StackallocThreshold)
|
||||
{
|
||||
double x = i - center;
|
||||
double weight = Math.Exp(-(x * x) / twoSigmaSq);
|
||||
weights[i] = weight;
|
||||
sum += weight;
|
||||
weights = stackBuffer.Slice(0, kernelSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
rented = ArrayPool<double>.Shared.Rent(kernelSize);
|
||||
weights = rented.AsSpan(0, kernelSize);
|
||||
}
|
||||
|
||||
double invSum = 1.0 / sum;
|
||||
for (int i = 0; i < kernelSize; i++)
|
||||
try
|
||||
{
|
||||
weights[i] *= invSum;
|
||||
}
|
||||
double sum = 0;
|
||||
int center = kernelSize / 2;
|
||||
double twoSigmaSq = 2.0 * sigma * sigma;
|
||||
|
||||
for (int i = 0; i < kernelSize; i++)
|
||||
{
|
||||
double x = i - center;
|
||||
double weight = Math.Exp(-(x * x) / twoSigmaSq);
|
||||
weights[i] = weight;
|
||||
sum += weight;
|
||||
}
|
||||
|
||||
double invSum = 1.0 / sum;
|
||||
for (int i = 0; i < kernelSize; i++)
|
||||
{
|
||||
weights[i] *= invSum;
|
||||
}
|
||||
|
||||
// Apply filter
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
@@ -309,6 +327,14 @@ public sealed class Gauss : AbstractBase
|
||||
output[i] = double.NaN;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rented != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rented, clearArray: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -38,6 +38,8 @@ public sealed class Kalman : AbstractBase
|
||||
private readonly ITValuePublisher? _publisher;
|
||||
private readonly TValuePublishedHandler? _handler;
|
||||
|
||||
private const double MaxCovariance = 1e10;
|
||||
|
||||
private State _state;
|
||||
private State _pState;
|
||||
|
||||
@@ -138,7 +140,7 @@ public sealed class Kalman : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
_state.P += ProcessNoise;
|
||||
_state.P = Math.Min(_state.P + ProcessNoise, MaxCovariance);
|
||||
Last = new TValue(input.Time, _state.X);
|
||||
}
|
||||
|
||||
@@ -243,7 +245,7 @@ public sealed class Kalman : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
p += q; // predict-only
|
||||
p = Math.Min(p + q, MaxCovariance); // predict-only, capped
|
||||
output[i] = x;
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -147,27 +147,13 @@ public sealed class Notch : AbstractBase
|
||||
|
||||
if (srcSpan.Length > 0)
|
||||
{
|
||||
_index += srcSpan.Length;
|
||||
// Best effort state restoration from the end of the block
|
||||
// We assume the strict history for X is valid.
|
||||
double lastVal = srcSpan[^1];
|
||||
_state.LastValue = lastVal;
|
||||
|
||||
if (srcSpan.Length >= 2)
|
||||
// Replay last few bars through streaming Update to properly restore state
|
||||
int replayStart = Math.Max(0, srcSpan.Length - Math.Max(WarmupPeriod, 4));
|
||||
Reset();
|
||||
for (int i = replayStart; i < srcSpan.Length; i++)
|
||||
{
|
||||
_state.X1 = srcSpan[^1];
|
||||
_state.X2 = srcSpan[^2];
|
||||
_state.Y1 = outArray[^1];
|
||||
_state.Y2 = outArray[^2];
|
||||
Update(new TValue(source.Times[i], srcSpan[i]), isNew: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_state.X2 = _state.X1;
|
||||
_state.X1 = srcSpan[0];
|
||||
_state.Y2 = _state.Y1;
|
||||
_state.Y1 = outArray[0];
|
||||
}
|
||||
_p_state = _state;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -78,7 +78,8 @@ public sealed class Sgf : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
weight = 1.0 - Math.Abs((double)k) / (double)halfWindow;
|
||||
// Guard against division by zero when halfWindow == 0 (period == 1)
|
||||
weight = (halfWindow == 0) ? 1.0 : 1.0 - Math.Abs((double)k) / (double)halfWindow;
|
||||
}
|
||||
|
||||
_weights[i] = weight;
|
||||
@@ -282,7 +283,8 @@ public sealed class Sgf : AbstractBase
|
||||
}
|
||||
else
|
||||
{
|
||||
weight = 1.0 - Math.Abs((double)k) / (double)halfWindow;
|
||||
// Guard against division by zero when halfWindow == 0 (period == 1)
|
||||
weight = (halfWindow == 0) ? 1.0 : 1.0 - Math.Abs((double)k) / (double)halfWindow;
|
||||
}
|
||||
|
||||
weights[i] = weight;
|
||||
|
||||
@@ -77,13 +77,13 @@ public sealed class Usf : AbstractBase
|
||||
|
||||
public Usf(TSeries source, int period) : this(period)
|
||||
{
|
||||
_publisher = source;
|
||||
source.Pub += _handler;
|
||||
Prime(source.Values);
|
||||
if (source.Count > 0)
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
_publisher = source;
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, in TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
@@ -40,8 +40,12 @@ public sealed class Wiener : AbstractBase
|
||||
{
|
||||
if (double.IsNaN(input.Value) || double.IsInfinity(input.Value))
|
||||
{
|
||||
// If we have a valid last value, return it, otherwise return input
|
||||
return isNew ? Last : new TValue(input.Time, Last.Value);
|
||||
// If we have a valid last value, use it; otherwise fallback to input value
|
||||
double fallbackValue = double.IsFinite(Last.Value) ? Last.Value : input.Value;
|
||||
var fallbackResult = new TValue(input.Time, fallbackValue);
|
||||
Last = fallbackResult;
|
||||
PubEvent(fallbackResult, isNew);
|
||||
return fallbackResult;
|
||||
}
|
||||
|
||||
_buffer.Add(input.Value, isNew);
|
||||
@@ -55,9 +59,9 @@ public sealed class Wiener : AbstractBase
|
||||
return res;
|
||||
}
|
||||
|
||||
double result = Calc();
|
||||
double calcResult = Calc();
|
||||
|
||||
var ret = new TValue(input.Time, result);
|
||||
var ret = new TValue(input.Time, calcResult);
|
||||
Last = ret;
|
||||
PubEvent(ret, isNew);
|
||||
return ret;
|
||||
|
||||
Reference in New Issue
Block a user