mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-28 01:37:43 +00:00
Refactor event handling and improve argument validation across indicators
- Updated event handler signatures to use TValueEventArgs for consistency in Mama, Mgdi, Pwma, Rma, Sma, Ssf, Super, T3, Tema, Trima, Usf, Vidya, Wma, and Atr classes. - Enhanced argument validation by specifying parameter names in exceptions for clarity. - Adjusted tests to align with new event handler signatures. - Improved code readability and maintainability by using structured records and lambda expressions.
This commit is contained in:
@@ -9,3 +9,6 @@ echo "Modifying csproj files to target only net8.0 for Qodana..."
|
||||
find . -name "*.csproj" -exec sed -i 's|<TargetFrameworks>net10.0;net8.0</TargetFrameworks>|<TargetFramework>net8.0</TargetFramework>|g' {} +
|
||||
# Just in case some files still have single target net10.0
|
||||
find . -name "*.csproj" -exec sed -i 's|<TargetFramework>net10.0</TargetFramework>|<TargetFramework>net8.0</TargetFramework>|g' {} +
|
||||
|
||||
echo "Restoring NuGet packages in container..."
|
||||
dotnet restore --no-cache
|
||||
|
||||
Vendored
+14
-2
@@ -113,11 +113,16 @@
|
||||
"git.decorations.enabled": true,
|
||||
|
||||
// ?????????????????????????????????????????????????????????????????
|
||||
// Terminal Settings (Preserved from original)
|
||||
// Terminal Settings - WSL Debian
|
||||
// ?????????????????????????????????????????????????????????????????
|
||||
|
||||
"terminal.integrated.defaultProfile.windows": "PowerShell",
|
||||
"terminal.integrated.defaultProfile.windows": "Debian",
|
||||
"terminal.integrated.profiles.windows": {
|
||||
"Debian": {
|
||||
"path": "C:\\Windows\\System32\\wsl.exe",
|
||||
"args": ["-d", "Debian", "--", "bash", "-l"],
|
||||
"icon": "terminal-linux"
|
||||
},
|
||||
"PowerShell": {
|
||||
"source": "PowerShell",
|
||||
"icon": "terminal-powershell"
|
||||
@@ -126,6 +131,13 @@
|
||||
"terminal.integrated.shellIntegration.enabled": true,
|
||||
"terminal.integrated.suggest.enabled": true,
|
||||
|
||||
// ?????????????????????????????????????????????????????????????????
|
||||
// Cline Settings - WSL Debian Bash
|
||||
// ?????????????????????????????????????????????????????????????????
|
||||
|
||||
"cline.terminalShell.windows": "C:\\Windows\\System32\\wsl.exe",
|
||||
"cline.terminalShellArgs.windows": ["-d", "Debian", "--", "bash", "-l"],
|
||||
|
||||
// ?????????????????????????????????????????????????????????????????
|
||||
// File Exclusions (Reduce Noise)
|
||||
// ?????????????????????????????????????????????????????????????????
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
<IsLocalBuild Condition="'$(GITHUB_ACTIONS)' == ''">true</IsLocalBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<TreatWarningsAsErrors Condition="'$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>link</TrimMode>
|
||||
@@ -45,7 +51,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);S1144;S1944;S2053;S2222;S2245;S2259;S2583;S2589;S3329;S3655;S3776;S3900;S3949;S3966;S4158;S4347;S5773;S6781</NoWarn>
|
||||
<NoWarn>$(NoWarn);S1144;S1944;S2053;S2222;S2245;S2259;S2583;S2589;S3329;S3655;S3776;S3900;S3949;S3966;S4158;S4347;S5773;S6781;MA0048;MA0051</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -31,14 +31,14 @@ public abstract class AbstractBase : ITValuePublisher
|
||||
/// <summary>
|
||||
/// Event triggered when a new TValue is available.
|
||||
/// </summary>
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Helper to invoke the Pub event.
|
||||
/// </summary>
|
||||
protected void PubEvent(TValue value)
|
||||
protected void PubEvent(TValue value, bool isNew = true)
|
||||
{
|
||||
Pub?.Invoke(value);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = value, IsNew = isNew });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -395,7 +395,7 @@ public static class SimdExtensions
|
||||
public static void Add(ReadOnlySpan<double> left, ReadOnlySpan<double> right, Span<double> result)
|
||||
{
|
||||
if (left.Length != right.Length || left.Length != result.Length)
|
||||
throw new ArgumentException("All spans must have the same length");
|
||||
throw new ArgumentException("All spans must have the same length", nameof(result));
|
||||
|
||||
int i = 0;
|
||||
if (Vector.IsHardwareAccelerated && left.Length >= Vector<double>.Count)
|
||||
@@ -423,7 +423,7 @@ public static class SimdExtensions
|
||||
public static void Subtract(ReadOnlySpan<double> left, ReadOnlySpan<double> right, Span<double> result)
|
||||
{
|
||||
if (left.Length != right.Length || left.Length != result.Length)
|
||||
throw new ArgumentException("All spans must have the same length");
|
||||
throw new ArgumentException("All spans must have the same length", nameof(result));
|
||||
|
||||
int i = 0;
|
||||
if (Vector.IsHardwareAccelerated && left.Length >= Vector<double>.Count)
|
||||
@@ -451,7 +451,7 @@ public static class SimdExtensions
|
||||
public static double DotProduct(this ReadOnlySpan<double> a, ReadOnlySpan<double> b)
|
||||
{
|
||||
if (a.Length != b.Length)
|
||||
throw new ArgumentException("Spans must have equal length");
|
||||
throw new ArgumentException("Spans must have equal length", nameof(b));
|
||||
|
||||
if (a.IsEmpty) return 0.0;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
@@ -7,6 +8,7 @@ namespace QuanTAlib;
|
||||
/// Pure data type: 48 bytes (long + 5 doubles).
|
||||
/// </summary>
|
||||
[SkipLocalsInit]
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly record struct TBar(long Time, double Open, double High, double Low, double Close, double Volume)
|
||||
{
|
||||
public DateTime AsDateTime => new(Time, DateTimeKind.Utc);
|
||||
|
||||
@@ -358,8 +358,8 @@ namespace QuanTAlib.Tests
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
TBar? received = null;
|
||||
series.Pub += bar => received = bar;
|
||||
|
||||
series.Pub += (object? sender, in TBarEventArgs args) => received = args.Value;
|
||||
|
||||
var barToAdd = new TBar(100, 10, 15, 5, 12, 100);
|
||||
series.Add(barToAdd, isNew: true);
|
||||
|
||||
@@ -374,8 +374,8 @@ namespace QuanTAlib.Tests
|
||||
var series = new TBarSeries();
|
||||
TBar? received = null;
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Pub += bar => received = bar;
|
||||
|
||||
series.Pub += (object? sender, in TBarEventArgs args) => received = args.Value;
|
||||
|
||||
series.Add(100, 10, 18, 5, 15, 150, isNew: false);
|
||||
|
||||
Assert.NotNull(received);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -9,17 +10,32 @@ namespace QuanTAlib;
|
||||
/// Stores Time, Open, High, Low, Close, Volume in separate contiguous arrays for SIMD efficiency.
|
||||
/// Exposes TSeries views for each component that share the underlying Time array.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly struct TBarEventArgs
|
||||
{
|
||||
public TBar Value { get; init; }
|
||||
public bool IsNew { get; init; }
|
||||
}
|
||||
|
||||
// Performance-focused event args struct; not derived from EventArgs by design.
|
||||
// We intentionally deviate from the standard EventArgs pattern here for perf.
|
||||
#pragma warning disable MA0046 // The second parameter must be of type 'System.EventArgs' or a derived type
|
||||
public delegate void TBarPublishedHandler(object? sender, in TBarEventArgs args);
|
||||
#pragma warning restore MA0046
|
||||
|
||||
public class TBarSeries : IReadOnlyList<TBar>
|
||||
{
|
||||
#pragma warning disable MA0016 // Prefer using collection abstraction instead of implementation
|
||||
protected readonly List<long> _t;
|
||||
protected readonly List<double> _o;
|
||||
protected readonly List<double> _h;
|
||||
protected readonly List<double> _l;
|
||||
protected readonly List<double> _c;
|
||||
protected readonly List<double> _v;
|
||||
#pragma warning restore MA0016
|
||||
|
||||
public string Name { get; set; } = "Bar";
|
||||
public event Action<TBar>? Pub;
|
||||
public event TBarPublishedHandler? Pub;
|
||||
|
||||
// Note: These views share underlying storage. Do not modify directly; use TBarSeries.Add() instead.
|
||||
public TSeries Open { get; }
|
||||
@@ -102,7 +118,7 @@ public class TBarSeries : IReadOnlyList<TBar>
|
||||
_v[lastIdx] = bar.Volume;
|
||||
}
|
||||
|
||||
Pub?.Invoke(bar);
|
||||
Pub?.Invoke(this, new TBarEventArgs { Value = bar, IsNew = isNew });
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -126,7 +142,7 @@ public class TBarSeries : IReadOnlyList<TBar>
|
||||
hArr.Length != lArr.Length || lArr.Length != cArr.Length ||
|
||||
cArr.Length != vArr.Length)
|
||||
{
|
||||
throw new ArgumentException("All arrays must have the same length");
|
||||
throw new ArgumentException("All arrays must have the same length", nameof(t));
|
||||
}
|
||||
|
||||
for (int i = 0; i < tArr.Length; i++)
|
||||
|
||||
@@ -2,6 +2,14 @@ using System;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
public sealed class TValueEventArgs : EventArgs
|
||||
{
|
||||
public TValue Value { get; init; }
|
||||
public bool IsNew { get; init; }
|
||||
}
|
||||
|
||||
public delegate void TValuePublishedHandler(object? sender, TValueEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Interface for objects that publish TValue updates.
|
||||
/// </summary>
|
||||
@@ -10,5 +18,5 @@ public interface ITValuePublisher
|
||||
/// <summary>
|
||||
/// Event triggered when a new TValue is available.
|
||||
/// </summary>
|
||||
event Action<TValue> Pub;
|
||||
event TValuePublishedHandler? Pub;
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ public class TSeriesTests
|
||||
{
|
||||
var series = new TSeries();
|
||||
TValue? received = null;
|
||||
series.Pub += tv => received = tv;
|
||||
series.Pub += (object? sender, TValueEventArgs args) => received = args.Value;
|
||||
|
||||
series.Add(100, 42.0);
|
||||
|
||||
@@ -313,7 +313,7 @@ public class TSeriesTests
|
||||
var series = new TSeries();
|
||||
TValue? received = null;
|
||||
series.Add(100, 42.0);
|
||||
series.Pub += tv => received = tv;
|
||||
series.Pub += (object? sender, TValueEventArgs args) => received = args.Value;
|
||||
|
||||
series.Add(100, 43.0, isNew: false);
|
||||
|
||||
|
||||
@@ -13,12 +13,14 @@ namespace QuanTAlib;
|
||||
/// </summary>
|
||||
public class TSeries : IReadOnlyList<TValue>, ITValuePublisher
|
||||
{
|
||||
#pragma warning disable MA0016 // Prefer using collection abstraction instead of implementation
|
||||
protected readonly List<long> _t;
|
||||
protected readonly List<double> _v;
|
||||
#pragma warning restore MA0016
|
||||
|
||||
public string Name { get; set; } = "Data";
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
public TSeries() : this(0)
|
||||
{
|
||||
@@ -30,10 +32,18 @@ public class TSeries : IReadOnlyList<TValue>, ITValuePublisher
|
||||
_v = new List<double>(capacity);
|
||||
}
|
||||
|
||||
public TSeries(List<long> time, List<double> values)
|
||||
public TSeries(IReadOnlyList<long> time, IReadOnlyList<double> values)
|
||||
{
|
||||
_t = time;
|
||||
_v = values;
|
||||
if (time is List<long> timeList && values is List<double> valueList)
|
||||
{
|
||||
_t = timeList;
|
||||
_v = valueList;
|
||||
}
|
||||
else
|
||||
{
|
||||
_t = new List<long>(time);
|
||||
_v = new List<double>(values);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
@@ -98,7 +108,9 @@ public class TSeries : IReadOnlyList<TValue>, ITValuePublisher
|
||||
_t[lastIdx] = value.Time;
|
||||
_v[lastIdx] = value.Value;
|
||||
}
|
||||
Pub?.Invoke(value);
|
||||
|
||||
var args = new TValueEventArgs { Value = value, IsNew = isNew };
|
||||
Pub?.Invoke(this, args);
|
||||
}
|
||||
|
||||
// Overload for backward compatibility (assumes isNew=true)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
@@ -7,6 +8,7 @@ namespace QuanTAlib;
|
||||
/// Pure data type: 16 bytes (long + double).
|
||||
/// </summary>
|
||||
[SkipLocalsInit]
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly record struct TValue(long Time, double Value)
|
||||
{
|
||||
public DateTime AsDateTime => new(Time, DateTimeKind.Utc);
|
||||
|
||||
@@ -13,7 +13,7 @@ public static class ValidationHelper
|
||||
public const double TulipTolerance = 1e-7;
|
||||
public const double RelativeTolerance = 0.005;
|
||||
|
||||
public static void VerifyData<TResult>(TSeries qSeries, List<TResult> sSeries, Func<TResult, double?> selector, int skip = 100, double tolerance = DefaultTolerance)
|
||||
public static void VerifyData<TResult>(TSeries qSeries, IReadOnlyList<TResult> sSeries, Func<TResult, double?> selector, int skip = 100, double tolerance = DefaultTolerance)
|
||||
{
|
||||
Assert.Equal(qSeries.Count, sSeries.Count);
|
||||
|
||||
@@ -31,7 +31,7 @@ public static class ValidationHelper
|
||||
}
|
||||
}
|
||||
|
||||
public static void VerifyData<TResult>(List<double> qResults, List<TResult> sSeries, Func<TResult, double?> selector, int skip = 100, double tolerance = DefaultTolerance)
|
||||
public static void VerifyData<TResult>(IReadOnlyList<double> qResults, IReadOnlyList<TResult> sSeries, Func<TResult, double?> selector, int skip = 100, double tolerance = DefaultTolerance)
|
||||
{
|
||||
Assert.Equal(qResults.Count, sSeries.Count);
|
||||
|
||||
@@ -87,7 +87,7 @@ public static class ValidationHelper
|
||||
}
|
||||
}
|
||||
|
||||
public static void VerifyData(List<double> qResults, double[] tOutput, int lookback, int skip = 100, double tolerance = DefaultTolerance)
|
||||
public static void VerifyData(IReadOnlyList<double> qResults, double[] tOutput, int lookback, int skip = 100, double tolerance = DefaultTolerance)
|
||||
{
|
||||
int count = qResults.Count;
|
||||
int start = Math.Max(0, count - skip);
|
||||
@@ -148,7 +148,7 @@ public static class ValidationHelper
|
||||
}
|
||||
}
|
||||
|
||||
public static void VerifyData(List<double> qResults, double[] tOutput, Range outRange, int lookback, int skip = 100, double tolerance = DefaultTolerance)
|
||||
public static void VerifyData(IReadOnlyList<double> qResults, double[] tOutput, Range outRange, int lookback, int skip = 100, double tolerance = DefaultTolerance)
|
||||
{
|
||||
int count = qResults.Count;
|
||||
int start = Math.Max(0, count - skip);
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class Adx : ITValuePublisher
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current ADX value.
|
||||
@@ -265,7 +265,7 @@ public sealed class Adx : ITValuePublisher
|
||||
DiMinus = new TValue(input.Time, diMinus);
|
||||
Last = new TValue(input.Time, _adx);
|
||||
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
return Last;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class Adxr : ITValuePublisher
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current ADXR value.
|
||||
@@ -130,7 +130,7 @@ public sealed class Adxr : ITValuePublisher
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, adxr);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -233,3 +233,4 @@ public sealed class Adxr : ITValuePublisher
|
||||
return new TSeries(tList, [.. v]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class Ao : ITValuePublisher
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current AO value.
|
||||
@@ -99,7 +99,7 @@ public sealed class Ao : ITValuePublisher
|
||||
|
||||
double ao = sFast.Value - sSlow.Value;
|
||||
Last = new TValue(input.Time, ao);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ public sealed class Ao : ITValuePublisher
|
||||
|
||||
double ao = sFast.Value - sSlow.Value;
|
||||
Last = new TValue(input.Time, ao);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ public sealed class Ao : ITValuePublisher
|
||||
public static void Calculate(ReadOnlySpan<double> high, ReadOnlySpan<double> low, Span<double> destination, int fastPeriod = 5, int slowPeriod = 34)
|
||||
{
|
||||
if (high.Length != low.Length || high.Length != destination.Length)
|
||||
throw new ArgumentException("High, low, and destination spans must have the same length.");
|
||||
throw new ArgumentException("High, low, and destination spans must have the same length.", nameof(destination));
|
||||
|
||||
int len = high.Length;
|
||||
if (len == 0) return;
|
||||
|
||||
+11
-4
@@ -26,13 +26,14 @@ public sealed class Apo : ITValuePublisher
|
||||
{
|
||||
private readonly Ema _emaFast;
|
||||
private readonly Ema _emaSlow;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the indicator.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current APO value.
|
||||
@@ -65,6 +66,7 @@ public sealed class Apo : ITValuePublisher
|
||||
|
||||
_emaFast = new Ema(fastPeriod);
|
||||
_emaSlow = new Ema(slowPeriod);
|
||||
_handler = Handle;
|
||||
WarmupPeriod = slowPeriod;
|
||||
Name = $"Apo({fastPeriod},{slowPeriod})";
|
||||
}
|
||||
@@ -77,7 +79,7 @@ public sealed class Apo : ITValuePublisher
|
||||
/// <param name="slowPeriod">Slow EMA period (default 26)</param>
|
||||
public Apo(ITValuePublisher source, int fastPeriod = 12, int slowPeriod = 26) : this(fastPeriod, slowPeriod)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,7 +107,7 @@ public sealed class Apo : ITValuePublisher
|
||||
|
||||
double apo = eFast.Value - eSlow.Value;
|
||||
Last = new TValue(input.Time, apo);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -143,6 +145,11 @@ public sealed class Apo : ITValuePublisher
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates APO for the entire series using a new instance.
|
||||
/// </summary>
|
||||
@@ -167,7 +174,7 @@ public sealed class Apo : ITValuePublisher
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int fastPeriod = 12, int slowPeriod = 26)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output spans must be of the same length.");
|
||||
throw new ArgumentException("Source and output spans must be of the same length.", nameof(output));
|
||||
|
||||
Span<double> fastEma = source.Length <= 1024 ? stackalloc double[source.Length] : new double[source.Length];
|
||||
Span<double> slowEma = source.Length <= 1024 ? stackalloc double[source.Length] : new double[source.Length];
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class Aroon : ITValuePublisher
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current Aroon Oscillator value (Up - Down).
|
||||
@@ -150,7 +150,7 @@ public sealed class Aroon : ITValuePublisher
|
||||
Down = new TValue(input.Time, down);
|
||||
Last = new TValue(input.Time, osc);
|
||||
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -255,3 +255,4 @@ public sealed class Aroon : ITValuePublisher
|
||||
return new TSeries(tList, [.. v]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class AroonOsc : ITValuePublisher
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current Aroon Oscillator value.
|
||||
@@ -136,7 +136,7 @@ public sealed class AroonOsc : ITValuePublisher
|
||||
|
||||
Last = new TValue(input.Time, osc);
|
||||
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -241,3 +241,4 @@ public sealed class AroonOsc : ITValuePublisher
|
||||
return new TSeries(tList, [.. v]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class Bop : ITValuePublisher
|
||||
/// </summary>
|
||||
public static string Name => "Bop";
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current BOP value.
|
||||
@@ -76,7 +76,7 @@ public sealed class Bop : ITValuePublisher
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, bop);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public sealed class Bop : ITValuePublisher
|
||||
// Or we could throw NotSupportedException.
|
||||
// Given the interface contract, returning 0 is safer than crashing.
|
||||
Last = new TValue(input.Time, 0);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -180,3 +180,4 @@ public sealed class Bop : ITValuePublisher
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,12 +37,14 @@ public sealed class Cfb : ITValuePublisher
|
||||
private readonly double[] _runningSums;
|
||||
private readonly double[] _p_runningSums;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double PrevCfb, double LastPrice, double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
public string Name { get; }
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
public TValue Last { get; private set; }
|
||||
public bool IsHot => _prices.IsFull;
|
||||
public int WarmupPeriod { get; }
|
||||
@@ -81,14 +83,18 @@ public sealed class Cfb : ITValuePublisher
|
||||
_p_runningSums = new double[_lengths.Length];
|
||||
|
||||
Name = "Jurik Composite Fractal Behavior";
|
||||
_handler = Handle;
|
||||
_state.PrevCfb = 1.0;
|
||||
}
|
||||
|
||||
public Cfb(ITValuePublisher source, int[]? lengths = null) : this(lengths)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
@@ -210,7 +216,7 @@ public sealed class Cfb : ITValuePublisher
|
||||
_state.PrevCfb = cfb;
|
||||
|
||||
Last = new TValue(input.Time, cfb);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
return Last;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed class Dmx : ITValuePublisher
|
||||
private bool _isInitialized;
|
||||
|
||||
public string Name { get; }
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
public TValue Last { get; private set; }
|
||||
public int WarmupPeriod { get; }
|
||||
|
||||
@@ -114,7 +114,7 @@ public sealed class Dmx : ITValuePublisher
|
||||
double dmxValue = diPlus - diMinus;
|
||||
|
||||
Last = new TValue(input.Time, dmxValue);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public sealed class Dmx : ITValuePublisher
|
||||
return;
|
||||
|
||||
if (low.Length != len || close.Length != len || destination.Length != len)
|
||||
throw new ArgumentException("All input spans must have the same length");
|
||||
throw new ArgumentException("All input spans must have the same length", nameof(destination));
|
||||
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than zero.", nameof(period));
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed class Macd : ITValuePublisher
|
||||
private readonly Ema _fastEma;
|
||||
private readonly Ema _slowEma;
|
||||
private readonly Ema _signalEma;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
public string Name { get; }
|
||||
public bool IsHot => _fastEma.IsHot && _slowEma.IsHot && _signalEma.IsHot;
|
||||
@@ -32,13 +33,14 @@ public sealed class Macd : ITValuePublisher
|
||||
public TValue Signal { get; private set; }
|
||||
public TValue Histogram { get; private set; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
public Macd(int fastPeriod = 12, int slowPeriod = 26, int signalPeriod = 9)
|
||||
{
|
||||
_fastEma = new Ema(fastPeriod);
|
||||
_slowEma = new Ema(slowPeriod);
|
||||
_signalEma = new Ema(signalPeriod);
|
||||
_handler = Handle;
|
||||
|
||||
Name = $"Macd({fastPeriod},{slowPeriod},{signalPeriod})";
|
||||
WarmupPeriod = Math.Max(fastPeriod, slowPeriod) + signalPeriod;
|
||||
@@ -47,7 +49,7 @@ public sealed class Macd : ITValuePublisher
|
||||
public Macd(ITValuePublisher source, int fastPeriod = 12, int slowPeriod = 26, int signalPeriod = 9)
|
||||
: this(fastPeriod, slowPeriod, signalPeriod)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -78,7 +80,7 @@ public sealed class Macd : ITValuePublisher
|
||||
Signal = signal;
|
||||
Histogram = new TValue(input.Time, histValue);
|
||||
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -100,6 +102,11 @@ public sealed class Macd : ITValuePublisher
|
||||
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the MACD Line (Fast EMA - Slow EMA).
|
||||
@@ -108,7 +115,7 @@ public sealed class Macd : ITValuePublisher
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> destination, int fastPeriod = 12, int slowPeriod = 26)
|
||||
{
|
||||
if (source.Length != destination.Length)
|
||||
throw new ArgumentException("Source and destination must be same length");
|
||||
throw new ArgumentException("Source and destination must be same length", nameof(destination));
|
||||
|
||||
int len = source.Length;
|
||||
double[] fastBuffer = ArrayPool<double>.Shared.Rent(len);
|
||||
|
||||
+10
-3
@@ -26,6 +26,7 @@ public sealed class Rsi : AbstractBase
|
||||
private readonly int _period;
|
||||
private readonly Rma _avgGain;
|
||||
private readonly Rma _avgLoss;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
private double _prevValue;
|
||||
private double _p_prevValue;
|
||||
|
||||
@@ -39,6 +40,7 @@ public sealed class Rsi : AbstractBase
|
||||
_period = period;
|
||||
_avgGain = new Rma(period);
|
||||
_avgLoss = new Rma(period);
|
||||
_handler = Handle;
|
||||
_prevValue = double.NaN;
|
||||
_p_prevValue = double.NaN;
|
||||
|
||||
@@ -48,7 +50,7 @@ public sealed class Rsi : AbstractBase
|
||||
|
||||
public Rsi(ITValuePublisher source, int period = 14) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -110,7 +112,7 @@ public sealed class Rsi : AbstractBase
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, rsi);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -145,6 +147,11 @@ public sealed class Rsi : AbstractBase
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
foreach (var value in source)
|
||||
@@ -163,7 +170,7 @@ public sealed class Rsi : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ public sealed class Rsx : ITValuePublisher
|
||||
private readonly int _period;
|
||||
private readonly double _alpha;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State
|
||||
{
|
||||
// Momentum filters (3 stages, 2 filters each)
|
||||
@@ -46,13 +47,14 @@ public sealed class Rsx : ITValuePublisher
|
||||
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the indicator.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// The number of bars required to warm up the indicator.
|
||||
@@ -72,11 +74,12 @@ public sealed class Rsx : ITValuePublisher
|
||||
WarmupPeriod = period;
|
||||
_alpha = 3.0 / (period + 2.0);
|
||||
Name = $"Rsx({period})";
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
public Rsx(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -90,6 +93,8 @@ public sealed class Rsx : ITValuePublisher
|
||||
public bool IsHot => _state.IsInitialized;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
||||
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -177,7 +182,7 @@ public sealed class Rsx : ITValuePublisher
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, rsx);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -219,7 +224,7 @@ public sealed class Rsx : ITValuePublisher
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -22,12 +22,13 @@ public sealed class Vel : ITValuePublisher
|
||||
private readonly Pwma _pwma;
|
||||
private readonly Wma _wma;
|
||||
private readonly int _period;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
public string Name { get; }
|
||||
public TValue Last { get; private set; }
|
||||
public bool IsHot => _pwma.IsHot && _wma.IsHot;
|
||||
public int WarmupPeriod { get; }
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
public Vel(int period)
|
||||
{
|
||||
@@ -38,13 +39,17 @@ public sealed class Vel : ITValuePublisher
|
||||
_period = period;
|
||||
WarmupPeriod = period;
|
||||
Name = $"Vel({period})";
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
public Vel(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
@@ -52,7 +57,7 @@ public sealed class Vel : ITValuePublisher
|
||||
var wma = _wma.Update(input, isNew);
|
||||
|
||||
Last = new TValue(input.Time, pwma.Value - wma.Value);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -110,7 +115,7 @@ public sealed class Vel : ITValuePublisher
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
Span<double> pwma = source.Length <= 1024 ? stackalloc double[source.Length] : new double[source.Length];
|
||||
Span<double> wma = source.Length <= 1024 ? stackalloc double[source.Length] : new double[source.Length];
|
||||
|
||||
@@ -43,11 +43,29 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="GitVersion.MsBuild" Version="6.5.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4" />
|
||||
|
||||
<PackageReference Include="Roslynator.Analyzers" Version="4.12.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.183">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.*">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -180,7 +180,7 @@ public sealed class Covariance : AbstractBase
|
||||
public static TSeries Calculate(TSeries sourceX, TSeries sourceY, int period, bool isPopulation = false)
|
||||
{
|
||||
if (sourceX.Count != sourceY.Count)
|
||||
throw new ArgumentException("Source series must have the same length");
|
||||
throw new ArgumentException("Source series must have the same length", nameof(sourceY));
|
||||
|
||||
int len = sourceX.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -201,7 +201,7 @@ public sealed class Covariance : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> sourceX, ReadOnlySpan<double> sourceY, Span<double> output, int period, bool isPopulation = false)
|
||||
{
|
||||
if (sourceX.Length != sourceY.Length || sourceX.Length != output.Length)
|
||||
throw new ArgumentException("All spans must have the same length");
|
||||
throw new ArgumentException("All spans must have the same length", nameof(output));
|
||||
if (period < 2)
|
||||
throw new ArgumentException("Period must be greater than or equal to 2", nameof(period));
|
||||
|
||||
|
||||
@@ -36,9 +36,11 @@ public sealed class LinReg : AbstractBase
|
||||
private readonly double _sum_x;
|
||||
private readonly double _denominator;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double SumY, double SumXY, double SumY2, double LastVal, double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
private int _tickCount;
|
||||
private const int ResyncInterval = 1000;
|
||||
@@ -81,6 +83,7 @@ public sealed class LinReg : AbstractBase
|
||||
_buffer = new RingBuffer(period);
|
||||
Name = $"LinReg({period})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
|
||||
// Precalculate constants
|
||||
// sum_x = 0 + 1 + ... + (n-1) = n(n-1)/2
|
||||
@@ -95,9 +98,12 @@ public sealed class LinReg : AbstractBase
|
||||
|
||||
public LinReg(ITValuePublisher source, int period, int offset = 0) : this(period, offset)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double GetValidValue(double input)
|
||||
{
|
||||
@@ -250,7 +256,7 @@ public sealed class LinReg : AbstractBase
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, result);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -329,7 +335,7 @@ public sealed class LinReg : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, int offset = 0, double initialLastValid = 0)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public sealed class MedianValidationTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private static double CalculateMedian(List<double> sortedWindow)
|
||||
private static double CalculateMedian(IReadOnlyList<double> sortedWindow)
|
||||
{
|
||||
int count = sortedWindow.Count;
|
||||
if (count == 0) return 0; // Or NaN
|
||||
|
||||
@@ -25,6 +25,7 @@ public sealed class Median : AbstractBase
|
||||
private readonly int _period;
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly double[] _sortedBuffer;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Median indicator with the specified period.
|
||||
@@ -40,11 +41,12 @@ public sealed class Median : AbstractBase
|
||||
_sortedBuffer = new double[period];
|
||||
Name = $"Median({period})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
public Median(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public Median(TSeries source, int period) : this(period)
|
||||
@@ -54,7 +56,7 @@ public sealed class Median : AbstractBase
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -79,6 +81,9 @@ public sealed class Median : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
@@ -123,7 +128,7 @@ public sealed class Median : AbstractBase
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, median);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -202,7 +207,7 @@ public sealed class Median : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ public sealed class Skew : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, bool isPopulation = false)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period < 3)
|
||||
throw new ArgumentException("Period must be greater than or equal to 3", nameof(period));
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ public sealed class Variance : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, bool isPopulation = false)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period < 2)
|
||||
throw new ArgumentException("Period must be greater than or equal to 2", nameof(period));
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class Alma : AbstractBase, IDisposable
|
||||
private readonly double _invWeightSum;
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly ITValuePublisher? _source;
|
||||
private readonly Action<TValue>? _pubHandler;
|
||||
private readonly TValuePublishedHandler? _pubHandler;
|
||||
|
||||
private record struct State(double LastValidValue);
|
||||
private State _state;
|
||||
@@ -84,10 +84,13 @@ public sealed class Alma : AbstractBase, IDisposable
|
||||
: this(period, offset, sigma)
|
||||
{
|
||||
_source = source;
|
||||
_pubHandler = (item) => Update(item);
|
||||
_pubHandler = Handle;
|
||||
_source.Pub += _pubHandler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_source != null && _pubHandler != null)
|
||||
@@ -235,7 +238,7 @@ public sealed class Alma : AbstractBase, IDisposable
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
// Precompute weights
|
||||
// Use stackalloc for small periods to avoid heap allocation, ArrayPool for large
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Bessel : AbstractBase, IDisposable
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double F1, double F2, double LastValidValue, int Count, bool IsHot)
|
||||
{
|
||||
public static State New() => new()
|
||||
@@ -37,7 +38,7 @@ public sealed class Bessel : AbstractBase, IDisposable
|
||||
|
||||
private readonly double _c1, _c2, _c3;
|
||||
private readonly ITValuePublisher? _publisher;
|
||||
private readonly Action<TValue>? _handler;
|
||||
private readonly TValuePublishedHandler? _handler;
|
||||
private State _state = State.New();
|
||||
private State _p_state = State.New();
|
||||
|
||||
@@ -68,7 +69,7 @@ public sealed class Bessel : AbstractBase, IDisposable
|
||||
public Bessel(ITValuePublisher source, int length) : this(length)
|
||||
{
|
||||
_publisher = source;
|
||||
_handler = item => Update(item);
|
||||
_handler = Handle;
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ public sealed class Bessel : AbstractBase, IDisposable
|
||||
}
|
||||
|
||||
_publisher = source;
|
||||
_handler = item => Update(item);
|
||||
_handler = Handle;
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
@@ -291,7 +292,7 @@ public sealed class Bessel : AbstractBase, IDisposable
|
||||
throw new ArgumentException("Length must be greater than 0", nameof(length));
|
||||
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
if (source.Length == 0)
|
||||
return;
|
||||
@@ -309,6 +310,9 @@ public sealed class Bessel : AbstractBase, IDisposable
|
||||
CalculateCore(source, output, c1, c2, c3, length, ref state);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_state = State.New();
|
||||
|
||||
@@ -111,7 +111,7 @@ public sealed class BilateralValidationTests : IDisposable
|
||||
_output.WriteLine("Bilateral Span validated successfully against Reference");
|
||||
}
|
||||
|
||||
private List<double> GetReferenceData(int period, double sigmaSRatio, double sigmaRMult)
|
||||
private IReadOnlyList<double> GetReferenceData(int period, double sigmaSRatio, double sigmaRMult)
|
||||
{
|
||||
var reference = new BilateralReference(period, sigmaSRatio, sigmaRMult);
|
||||
var results = new List<double>();
|
||||
@@ -180,7 +180,7 @@ public sealed class BilateralValidationTests : IDisposable
|
||||
return sumWeights < 1e-10 ? centerVal : sumWeightedSrc / sumWeights;
|
||||
}
|
||||
|
||||
private static double CalculateStDev(List<double> values)
|
||||
private static double CalculateStDev(IReadOnlyList<double> values)
|
||||
{
|
||||
if (values.Count < 2) return 0;
|
||||
|
||||
|
||||
@@ -28,9 +28,11 @@ public sealed class Bilateral : AbstractBase
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly double[] _spatialWeights;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double SumSq, double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Bilateral Filter with specified parameters.
|
||||
@@ -49,6 +51,7 @@ public sealed class Bilateral : AbstractBase
|
||||
_buffer = new RingBuffer(period);
|
||||
Name = $"Bilateral({period}, {sigmaSRatio:F2}, {sigmaRMult:F2})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
|
||||
_spatialWeights = new double[period];
|
||||
PrecalculateSpatialWeights();
|
||||
@@ -57,7 +60,7 @@ public sealed class Bilateral : AbstractBase
|
||||
public Bilateral(ITValuePublisher source, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
|
||||
: this(period, sigmaSRatio, sigmaRMult)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public override bool IsHot => _buffer.IsFull;
|
||||
@@ -113,6 +116,9 @@ public sealed class Bilateral : AbstractBase
|
||||
_p_state = _state;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
@@ -171,7 +177,7 @@ public sealed class Bilateral : AbstractBase
|
||||
|
||||
double result = CalculateBilateral();
|
||||
Last = new TValue(input.Time, result);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,8 @@ public class BlmaTests
|
||||
var input = new double[] { 1, 2, 3, 4, 5 };
|
||||
var timestamps = new List<DateTime>();
|
||||
|
||||
blma.Pub += (item) => timestamps.Add(item.AsDateTime);
|
||||
blma.Pub += (object? sender, TValueEventArgs args) => timestamps.Add(args.Value.AsDateTime);
|
||||
|
||||
|
||||
blma.Prime(input);
|
||||
|
||||
@@ -164,7 +165,8 @@ public class BlmaTests
|
||||
];
|
||||
var timestamps = new List<DateTime>();
|
||||
|
||||
blma.Pub += (item) => timestamps.Add(item.AsDateTime);
|
||||
blma.Pub += (object? sender, TValueEventArgs args) => timestamps.Add(args.Value.AsDateTime);
|
||||
|
||||
|
||||
blma.Prime(input);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class BlmaValidationTests
|
||||
}
|
||||
}
|
||||
|
||||
private static List<double> CalculateReference(TSeries source, int period)
|
||||
private static IReadOnlyList<double> CalculateReference(TSeries source, int period)
|
||||
{
|
||||
var result = new List<double>();
|
||||
var buffer = new List<double>();
|
||||
|
||||
@@ -11,6 +11,7 @@ public sealed class Blma : AbstractBase, IDisposable
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly double[] _weights;
|
||||
private readonly double _weightSum;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
private ITValuePublisher? _publisher;
|
||||
private bool _hasLast;
|
||||
|
||||
@@ -31,26 +32,27 @@ public sealed class Blma : AbstractBase, IDisposable
|
||||
|
||||
// Pre-calculate weights for the full period
|
||||
_weightSum = CalculateWeights(period, _weights);
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
public Blma(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
_publisher = source;
|
||||
source.Pub += Handle;
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_publisher != null)
|
||||
{
|
||||
_publisher.Pub -= Handle;
|
||||
_publisher.Pub -= _handler;
|
||||
_publisher = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Handle(TValue value)
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(value);
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
@@ -118,7 +120,7 @@ public sealed class Blma : AbstractBase, IDisposable
|
||||
var tValue = new TValue(input.Time, result);
|
||||
Last = tValue;
|
||||
_hasLast = true;
|
||||
PubEvent(tValue);
|
||||
PubEvent(tValue, isNew);
|
||||
return tValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ButterValidationTests
|
||||
}
|
||||
}
|
||||
|
||||
private static List<double> CalculateReference(TSeries source, int period)
|
||||
private static IReadOnlyList<double> CalculateReference(TSeries source, int period)
|
||||
{
|
||||
var result = new List<double>();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
@@ -8,9 +9,11 @@ public sealed class Butter : AbstractBase
|
||||
private readonly int _period;
|
||||
private double _a1, _a2, _b0, _b1, _b2;
|
||||
private double _invA0;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State
|
||||
{
|
||||
public double X1, X2;
|
||||
@@ -30,18 +33,18 @@ public sealed class Butter : AbstractBase
|
||||
CalculateCoefficients();
|
||||
Name = $"Butter({_period})";
|
||||
WarmupPeriod = 2;
|
||||
_handler = Handle;
|
||||
Init();
|
||||
}
|
||||
|
||||
public Butter(object source, int period) : this(period)
|
||||
public Butter(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
var pub = (ITValuePublisher)source;
|
||||
pub.Pub += Handle;
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(TValue value)
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(value);
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -121,7 +124,7 @@ public sealed class Butter : AbstractBase
|
||||
|
||||
var tValue = new TValue(input.Time, y);
|
||||
Last = tValue;
|
||||
PubEvent(tValue);
|
||||
PubEvent(tValue, isNew);
|
||||
return tValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class Conv : AbstractBase, IDisposable
|
||||
private readonly double[] _kernel;
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly ITValuePublisher? _source;
|
||||
private readonly Action<TValue>? _subHandler;
|
||||
private readonly TValuePublishedHandler? _subHandler;
|
||||
|
||||
private record struct State(double LastValidValue);
|
||||
private State _state;
|
||||
@@ -54,7 +54,7 @@ public sealed class Conv : AbstractBase, IDisposable
|
||||
public Conv(ITValuePublisher source, double[] kernel) : this(kernel)
|
||||
{
|
||||
_source = source;
|
||||
_subHandler = (item) => Update(item);
|
||||
_subHandler = Handle;
|
||||
_source.Pub += _subHandler;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ public sealed class Conv : AbstractBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double GetValidValue(double input)
|
||||
{
|
||||
@@ -204,7 +206,7 @@ public sealed class Conv : AbstractBase, IDisposable
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, double[] kernel)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (kernel == null || kernel.Length == 0)
|
||||
throw new ArgumentException("Kernel must not be empty", nameof(kernel));
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Dema : AbstractBase, IDisposable
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct EmaState(double Ema, double E, bool IsHot, bool IsCompensated)
|
||||
{
|
||||
public static EmaState New() => new() { Ema = 0, E = 1.0, IsHot = false, IsCompensated = false };
|
||||
@@ -40,7 +41,7 @@ public sealed class Dema : AbstractBase, IDisposable
|
||||
private double _lastValidValue = double.NaN;
|
||||
private double _p_lastValidValue = double.NaN;
|
||||
private readonly ITValuePublisher? _publisher;
|
||||
private readonly Action<TValue>? _listener;
|
||||
private readonly TValuePublishedHandler? _listener;
|
||||
|
||||
public override bool IsHot => _state2.IsHot;
|
||||
|
||||
@@ -57,7 +58,7 @@ public sealed class Dema : AbstractBase, IDisposable
|
||||
public Dema(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
_publisher = source;
|
||||
_listener = (item) => Update(item);
|
||||
_listener = Handle;
|
||||
source.Pub += _listener;
|
||||
}
|
||||
|
||||
@@ -230,7 +231,7 @@ public sealed class Dema : AbstractBase, IDisposable
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (alpha <= 0 || alpha > 1)
|
||||
throw new ArgumentException("Alpha must be between 0 and 1", nameof(alpha));
|
||||
|
||||
@@ -328,4 +329,6 @@ public sealed class Dema : AbstractBase, IDisposable
|
||||
_publisher.Pub -= _listener;
|
||||
}
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
}
|
||||
|
||||
+10
-3
@@ -20,6 +20,7 @@ public sealed class Dwma : AbstractBase
|
||||
private readonly int _period;
|
||||
private readonly Wma _wma1;
|
||||
private readonly Wma _wma2;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
public override bool IsHot => _wma1.IsHot && _wma2.IsHot;
|
||||
|
||||
@@ -35,13 +36,14 @@ public sealed class Dwma : AbstractBase
|
||||
_period = period;
|
||||
_wma1 = new Wma(period);
|
||||
_wma2 = new Wma(period);
|
||||
_handler = Handle;
|
||||
Name = $"Dwma({period})";
|
||||
WarmupPeriod = period * 2;
|
||||
}
|
||||
|
||||
public Dwma(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -49,7 +51,7 @@ public sealed class Dwma : AbstractBase
|
||||
{
|
||||
TValue wma1Result = _wma1.Update(input, isNew);
|
||||
Last = _wma2.Update(wma1Result, isNew);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -88,6 +90,11 @@ public sealed class Dwma : AbstractBase
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
Reset();
|
||||
@@ -110,7 +117,7 @@ public sealed class Dwma : AbstractBase
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than zero");
|
||||
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
// We need a temporary buffer for the first WMA pass
|
||||
// Use stackalloc for small sizes, heap for large
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ema : AbstractBase
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double Ema, double E, bool IsHot, bool IsCompensated)
|
||||
{
|
||||
public static State New() => new() { Ema = 0, E = 1.0, IsHot = false, IsCompensated = false };
|
||||
@@ -63,7 +64,7 @@ public sealed class Ema : AbstractBase
|
||||
/// <param name="period">Period for EMA calculation</param>
|
||||
public Ema(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += Handle;
|
||||
}
|
||||
|
||||
public Ema(TSeries source, int period) : this(period)
|
||||
@@ -73,7 +74,7 @@ public sealed class Ema : AbstractBase
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += Handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -180,6 +181,9 @@ public sealed class Ema : AbstractBase
|
||||
_p_lastValidValue = _lastValidValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double GetValidValue(double input)
|
||||
{
|
||||
|
||||
+10
-3
@@ -27,6 +27,7 @@ public sealed class Hma : AbstractBase
|
||||
private readonly Wma _wmaFull;
|
||||
private readonly Wma _wmaHalf;
|
||||
private readonly Wma _wmaSqrt;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
private int _sampleCount;
|
||||
|
||||
public override bool IsHot => _sampleCount >= WarmupPeriod;
|
||||
@@ -42,6 +43,7 @@ public sealed class Hma : AbstractBase
|
||||
_wmaFull = new Wma(period);
|
||||
_wmaHalf = new Wma(halfPeriod);
|
||||
_wmaSqrt = new Wma(_sqrtPeriod);
|
||||
_handler = Handle;
|
||||
|
||||
Name = $"Hma({period})";
|
||||
WarmupPeriod = period + _sqrtPeriod - 1; // WMA needs period, then WMA(sqrt) needs sqrt_period. Total lag/warmup.
|
||||
@@ -49,7 +51,7 @@ public sealed class Hma : AbstractBase
|
||||
|
||||
public Hma(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -69,7 +71,7 @@ public sealed class Hma : AbstractBase
|
||||
// 4. Calculate HMA = WMA(sqrt(n), intermediate)
|
||||
Last = _wmaSqrt.Update(new TValue(input.Time, intermediate), isNew);
|
||||
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -115,6 +117,11 @@ public sealed class Hma : AbstractBase
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
foreach (var value in source)
|
||||
@@ -141,7 +148,7 @@ public sealed class Hma : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 1)
|
||||
throw new ArgumentException("Period must be greater than 1", nameof(period));
|
||||
|
||||
|
||||
+12
-4
@@ -20,6 +20,7 @@ public sealed class Htit : AbstractBase
|
||||
{
|
||||
public override bool IsHot => _state.Index >= WarmupPeriod;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(
|
||||
double I2, double Q2, double Re, double Im,
|
||||
double Period, double SmoothPeriod,
|
||||
@@ -34,6 +35,7 @@ public sealed class Htit : AbstractBase
|
||||
private readonly RingBuffer _i1Buffer;
|
||||
private readonly RingBuffer _q1Buffer;
|
||||
private readonly RingBuffer _itBuffer;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
// High-precision constants
|
||||
private const double c1 = 5.0 / 52.0; // ~0.09615385
|
||||
@@ -47,7 +49,8 @@ public sealed class Htit : AbstractBase
|
||||
{
|
||||
Name = "Htit";
|
||||
WarmupPeriod = 12;
|
||||
|
||||
_handler = Handle;
|
||||
|
||||
// Initialize buffers with size 8 (power of 2) for consistency with Calculate optimization
|
||||
// except priceBuffer which needs to be larger for IT calculation
|
||||
_priceBuffer = new RingBuffer(64); // Needs to hold enough history for IT calculation (up to 50 bars)
|
||||
@@ -62,7 +65,7 @@ public sealed class Htit : AbstractBase
|
||||
|
||||
public Htit(ITValuePublisher source) : this()
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Init()
|
||||
@@ -215,7 +218,7 @@ public sealed class Htit : AbstractBase
|
||||
{
|
||||
double val = Step(input.Value, isNew);
|
||||
Last = new TValue(input.Time, val);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -237,6 +240,11 @@ public sealed class Htit : AbstractBase
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
foreach (var value in source)
|
||||
@@ -255,7 +263,7 @@ public sealed class Htit : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
if (source.Length == 0) return;
|
||||
|
||||
|
||||
@@ -33,11 +33,13 @@ public sealed class Jma : AbstractBase
|
||||
private readonly RingBuffer _devBuffer;
|
||||
private readonly RingBuffer _volBuffer;
|
||||
private readonly double[] _sorted;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
// Streaming state (current + previous snapshot for isNew=false)
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State
|
||||
{
|
||||
// Jurik "envelope" anchors
|
||||
@@ -97,6 +99,7 @@ public sealed class Jma : AbstractBase
|
||||
// same warmup heuristic used in the AFL port (SetBarsRequired)
|
||||
WarmupPeriod = (int)Math.Ceiling(20.0 + 80.0 * Math.Pow(period, 0.36));
|
||||
|
||||
_handler = Handle;
|
||||
Name = $"Jma({period},{phase},{power})"; // power kept for signature compatibility
|
||||
|
||||
_devBuffer = new RingBuffer(DevWindowSize);
|
||||
@@ -109,7 +112,7 @@ public sealed class Jma : AbstractBase
|
||||
public Jma(ITValuePublisher source, int period, int phase = 0, double power = 0.45)
|
||||
: this(period, phase, power)
|
||||
{
|
||||
source.Pub += item => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -238,7 +241,7 @@ public sealed class Jma : AbstractBase
|
||||
{
|
||||
double j = Step(input.Value, isNew);
|
||||
Last = new TValue(input.Time, j);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -289,6 +292,8 @@ public sealed class Jma : AbstractBase
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
foreach (var value in source)
|
||||
|
||||
+11
-3
@@ -25,7 +25,9 @@ public sealed class Kama : AbstractBase
|
||||
private readonly double _fastAlpha;
|
||||
private readonly double _slowAlpha;
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double Kama, double VolatilitySum, double NextDiffOut, double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
@@ -55,6 +57,7 @@ public sealed class Kama : AbstractBase
|
||||
|
||||
_fastAlpha = 2.0 / (fastPeriod + 1);
|
||||
_slowAlpha = 2.0 / (slowPeriod + 1);
|
||||
_handler = Handle;
|
||||
|
||||
Name = $"Kama({period}, {fastPeriod}, {slowPeriod})";
|
||||
WarmupPeriod = period + 1;
|
||||
@@ -68,7 +71,7 @@ public sealed class Kama : AbstractBase
|
||||
public Kama(ITValuePublisher source, int period = 10, int fastPeriod = 2, int slowPeriod = 30)
|
||||
: this(period, fastPeriod, slowPeriod)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -177,7 +180,7 @@ public sealed class Kama : AbstractBase
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, _state.Kama);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -205,6 +208,11 @@ public sealed class Kama : AbstractBase
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args)
|
||||
{
|
||||
Update(args.Value, args.IsNew);
|
||||
}
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
foreach (var value in source)
|
||||
@@ -225,7 +233,7 @@ public sealed class Kama : AbstractBase
|
||||
if (fastPeriod <= 0) throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod));
|
||||
if (slowPeriod <= 0) throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod));
|
||||
if (fastPeriod >= slowPeriod) throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod));
|
||||
if (source.Length != output.Length) throw new ArgumentException("Source and output must have the same length");
|
||||
if (source.Length != output.Length) throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
double fastAlpha = 2.0 / (fastPeriod + 1);
|
||||
double slowAlpha = 2.0 / (slowPeriod + 1);
|
||||
|
||||
@@ -33,7 +33,9 @@ public sealed class Lsma : AbstractBase
|
||||
|
||||
private readonly double _sum_x;
|
||||
private readonly double _denominator;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double SumY, double SumXY, double LastVal, double LastValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
@@ -59,6 +61,7 @@ public sealed class Lsma : AbstractBase
|
||||
_buffer = new RingBuffer(period);
|
||||
Name = $"Lsma({period})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
|
||||
// Precalculate constants
|
||||
// sum_x = 0 + 1 + ... + (n-1) = n(n-1)/2
|
||||
@@ -73,9 +76,11 @@ public sealed class Lsma : AbstractBase
|
||||
|
||||
public Lsma(ITValuePublisher source, int period, int offset = 0) : this(period, offset)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double GetValidValue(double input)
|
||||
{
|
||||
@@ -197,7 +202,7 @@ public sealed class Lsma : AbstractBase
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, result);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -282,7 +287,7 @@ public sealed class Lsma : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, int offset = 0, double initialLastValid = 0)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public class MamaTests
|
||||
|
||||
// Manually chain for test
|
||||
bool eventFired = false;
|
||||
mama.Pub += (item) => eventFired = true;
|
||||
mama.Pub += (object? sender, TValueEventArgs args) => eventFired = true;
|
||||
|
||||
mama.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ public sealed class Mama : AbstractBase
|
||||
private readonly double _fastLimit;
|
||||
private readonly double _slowLimit;
|
||||
private readonly double _scaledFastLimit;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(
|
||||
double Period, double Phase, double Mama, double Fama, double SumPr,
|
||||
double I2, double Q2, double Re, double Im, double LastValidPrice, int Index
|
||||
@@ -55,7 +57,7 @@ public sealed class Mama : AbstractBase
|
||||
{
|
||||
if (fastLimit <= slowLimit || fastLimit <= 0 || slowLimit <= 0)
|
||||
{
|
||||
throw new ArgumentException("FastLimit must be > SlowLimit and > 0");
|
||||
throw new ArgumentException("FastLimit must be > SlowLimit and > 0", nameof(fastLimit));
|
||||
}
|
||||
_fastLimit = fastLimit;
|
||||
_slowLimit = slowLimit;
|
||||
@@ -69,14 +71,17 @@ public sealed class Mama : AbstractBase
|
||||
|
||||
Name = $"Mama({fastLimit:F2},{slowLimit:F2})";
|
||||
WarmupPeriod = 50;
|
||||
_handler = Handle;
|
||||
Init();
|
||||
}
|
||||
|
||||
public Mama(ITValuePublisher source, double fastLimit = 0.5, double slowLimit = 0.05) : this(fastLimit, slowLimit)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
private void Init()
|
||||
{
|
||||
Reset();
|
||||
@@ -229,7 +234,7 @@ public sealed class Mama : AbstractBase
|
||||
double mama = Step(input.Value, isNew);
|
||||
Last = new TValue(input.Time, mama);
|
||||
Fama = new TValue(input.Time, _state.Fama);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ public sealed class Mgdi : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly double _k;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double LastMgdi, double LastValidValue, int Count, bool HasValidValue);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
@@ -38,14 +40,17 @@ public sealed class Mgdi : AbstractBase
|
||||
_k = k;
|
||||
Name = $"Mgdi({period},{k})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
Init();
|
||||
}
|
||||
|
||||
public Mgdi(ITValuePublisher source, int period = 14, double k = 0.6) : this(period, k)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
private void Init()
|
||||
{
|
||||
_state = default;
|
||||
@@ -110,7 +115,7 @@ public sealed class Mgdi : AbstractBase
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, _state.LastMgdi);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -163,7 +168,7 @@ public sealed class Mgdi : AbstractBase
|
||||
if (double.IsNaN(k) || double.IsInfinity(k) || k <= 0) throw new ArgumentOutOfRangeException(nameof(k), "k must be a finite value greater than 0");
|
||||
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
if (source.Length == 0) return;
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ public sealed class Pwma : AbstractBase
|
||||
private readonly double _divisor;
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly RingBuffer _p_buffer;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double Sum, double WSum, double PSum, double LastInput, double LastValidValue, int TickCount);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
@@ -52,13 +54,16 @@ public sealed class Pwma : AbstractBase
|
||||
_p_buffer = new RingBuffer(period);
|
||||
Name = $"Pwma({period})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
public Pwma(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double GetValidValue(double input)
|
||||
{
|
||||
@@ -152,7 +157,7 @@ public sealed class Pwma : AbstractBase
|
||||
double count = _buffer.Count;
|
||||
double currentDivisor = _buffer.IsFull ? _divisor : count * (count + 1.0) * (2.0 * count + 1.0) / 6.0;
|
||||
Last = new TValue(input.Time, _state.PSum / currentDivisor);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -231,7 +236,7 @@ public sealed class Pwma : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class Rma : AbstractBase
|
||||
public Rma(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += Handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,7 +61,7 @@ public sealed class Rma : AbstractBase
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += Handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,6 +80,8 @@ public sealed class Rma : AbstractBase
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
public override TValue Update(TValue input, bool isNew = true)
|
||||
{
|
||||
TValue result = _ema.Update(input, isNew);
|
||||
|
||||
@@ -30,7 +30,9 @@ public sealed class Sma : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double Sum, double LastInput, double LastValidValue, int TickCount);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
@@ -50,11 +52,12 @@ public sealed class Sma : AbstractBase
|
||||
_buffer = new RingBuffer(period);
|
||||
Name = $"Sma({period})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
public Sma(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public Sma(TSeries source, int period) : this(period)
|
||||
@@ -64,9 +67,11 @@ public sealed class Sma : AbstractBase
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Mode B: Streaming (Stateful)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -198,7 +203,7 @@ public sealed class Sma : AbstractBase
|
||||
|
||||
double result = _buffer.Count > 0 ? _state.Sum / _buffer.Count : double.NaN;
|
||||
Last = new TValue(input.Time, result);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -253,7 +258,7 @@ public sealed class Sma : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -19,12 +19,14 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ssf : AbstractBase
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double Ssf1, double Ssf2, double PrevInput, double LastValidValue, int Count, bool IsHot)
|
||||
{
|
||||
public static State New() => new() { Ssf1 = 0, Ssf2 = 0, PrevInput = 0, LastValidValue = 0, Count = 0, IsHot = false };
|
||||
}
|
||||
|
||||
private readonly double _c1, _c2, _c3;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
private State _state = State.New();
|
||||
private State _p_state = State.New();
|
||||
|
||||
@@ -52,6 +54,7 @@ public sealed class Ssf : AbstractBase
|
||||
|
||||
Name = $"Ssf({period})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,7 +64,7 @@ public sealed class Ssf : AbstractBase
|
||||
/// <param name="period">Period for SSF calculation</param>
|
||||
public Ssf(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public Ssf(TSeries source, int period) : this(period)
|
||||
@@ -71,9 +74,11 @@ public sealed class Ssf : AbstractBase
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
public override bool IsHot => _state.IsHot;
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
@@ -171,7 +176,7 @@ public sealed class Ssf : AbstractBase
|
||||
_state.IsHot = true;
|
||||
|
||||
Last = new TValue(input.Time, ssf);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -276,7 +281,7 @@ public sealed class Ssf : AbstractBase
|
||||
double c1 = 1.0 - c2 - c3;
|
||||
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
if (source.Length == 0) return;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
@@ -15,6 +16,7 @@ public sealed class Super : ITValuePublisher
|
||||
private TBar _lastInput;
|
||||
private int _sampleCount;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State
|
||||
{
|
||||
public bool IsBullish;
|
||||
@@ -33,7 +35,7 @@ public sealed class Super : ITValuePublisher
|
||||
/// </summary>
|
||||
public string Name => $"Super({_period},{_multiplier})";
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current SuperTrend value.
|
||||
@@ -213,7 +215,7 @@ public sealed class Super : ITValuePublisher
|
||||
UpperBand = new TValue(input.Time, upperBand);
|
||||
LowerBand = new TValue(input.Time, lowerBand);
|
||||
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ public class T3Tests
|
||||
|
||||
private class TestPublisher : ITValuePublisher
|
||||
{
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
public int SubscriberCount => Pub?.GetInvocationList().Length ?? 0;
|
||||
}
|
||||
|
||||
@@ -222,3 +222,4 @@ public class T3Tests
|
||||
Assert.Null(exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-4
@@ -27,11 +27,13 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class T3 : AbstractBase, IDisposable
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double E1, double E2, double E3, double E4, double E5, double E6, bool IsInitialized)
|
||||
{
|
||||
public static State New() => new() { IsInitialized = false };
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private readonly record struct Parameters(double Alpha, double C1, double C2, double C3, double C4);
|
||||
|
||||
private readonly Parameters _params;
|
||||
@@ -40,7 +42,7 @@ public sealed class T3 : AbstractBase, IDisposable
|
||||
private double _lastValidValue;
|
||||
private double _p_lastValidValue;
|
||||
private ITValuePublisher? _publisher;
|
||||
private Action<TValue>? _handler;
|
||||
private TValuePublishedHandler? _handler;
|
||||
|
||||
/// <summary>
|
||||
/// Creates T3 with specified period and volume factor.
|
||||
@@ -80,7 +82,7 @@ public sealed class T3 : AbstractBase, IDisposable
|
||||
public T3(ITValuePublisher source, int period, double vfactor = 0.7) : this(period, vfactor)
|
||||
{
|
||||
_publisher = source;
|
||||
_handler = (item) => Update(item);
|
||||
_handler = Handle;
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
@@ -98,10 +100,12 @@ public sealed class T3 : AbstractBase, IDisposable
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
_handler = (item) => Update(item);
|
||||
_handler = Handle;
|
||||
_publisher.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
/// <summary>
|
||||
/// True if the T3 has been initialized (received at least one value).
|
||||
/// </summary>
|
||||
@@ -285,7 +289,7 @@ public sealed class T3 : AbstractBase, IDisposable
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
double alpha = 2.0 / (period + 1);
|
||||
double v = vfactor;
|
||||
|
||||
+10
-4
@@ -26,6 +26,7 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Tema : AbstractBase
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct EmaState(double Ema, double E, bool IsHot, bool IsCompensated)
|
||||
{
|
||||
public static EmaState New() => new() { Ema = 0, E = 1.0, IsHot = false, IsCompensated = false };
|
||||
@@ -40,6 +41,7 @@ public sealed class Tema : AbstractBase
|
||||
private EmaState _p_state1 = EmaState.New();
|
||||
private EmaState _p_state2 = EmaState.New();
|
||||
private EmaState _p_state3 = EmaState.New();
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
|
||||
private double _lastValidValue;
|
||||
private double _p_lastValidValue;
|
||||
@@ -54,11 +56,12 @@ public sealed class Tema : AbstractBase
|
||||
_decay = 1.0 - _alpha;
|
||||
Name = $"Tema({period})";
|
||||
WarmupPeriod = period * 3;
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
public Tema(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public Tema(TSeries source, int period) : this(period)
|
||||
@@ -68,7 +71,7 @@ public sealed class Tema : AbstractBase
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public Tema(double alpha)
|
||||
@@ -79,8 +82,11 @@ public sealed class Tema : AbstractBase
|
||||
_decay = 1.0 - alpha;
|
||||
Name = $"Tema(α={alpha:F4})";
|
||||
WarmupPeriod = (int)(3 * (2.0 / alpha - 1.0));
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the indicator state using the provided history.
|
||||
/// </summary>
|
||||
@@ -203,7 +209,7 @@ public sealed class Tema : AbstractBase
|
||||
|
||||
double result = 3 * e1 - 3 * e2 + e3;
|
||||
Last = new TValue(input.Time, result);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -315,7 +321,7 @@ public sealed class Tema : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, double alpha)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (alpha <= 0 || alpha >= 1)
|
||||
throw new ArgumentException("Alpha must be strictly between 0 and 1", nameof(alpha));
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class Trima : AbstractBase, IDisposable
|
||||
private readonly int _period;
|
||||
private readonly Sma _sma1;
|
||||
private readonly Sma _sma2;
|
||||
private readonly Action<TValue> _updateHandler;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
private ITValuePublisher? _publisher;
|
||||
|
||||
public Trima(int period)
|
||||
@@ -43,7 +43,7 @@ public sealed class Trima : AbstractBase, IDisposable
|
||||
|
||||
_sma1 = new Sma(p1);
|
||||
_sma2 = new Sma(p2);
|
||||
_updateHandler = (item) => Update(item);
|
||||
_handler = Handle;
|
||||
|
||||
Name = $"Trima({period})";
|
||||
WarmupPeriod = p1 + p2 - 1;
|
||||
@@ -52,14 +52,14 @@ public sealed class Trima : AbstractBase, IDisposable
|
||||
public Trima(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
_publisher = source;
|
||||
source.Pub += _updateHandler;
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_publisher != null)
|
||||
{
|
||||
_publisher.Pub -= _updateHandler;
|
||||
_publisher.Pub -= _handler;
|
||||
_publisher = null;
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public sealed class Trima : AbstractBase, IDisposable
|
||||
TValue v2 = _sma2.Update(v1, isNew);
|
||||
|
||||
Last = v2;
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -99,6 +99,8 @@ public sealed class Trima : AbstractBase, IDisposable
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
_sma1.Reset();
|
||||
@@ -138,7 +140,7 @@ public sealed class Trima : AbstractBase, IDisposable
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -20,12 +20,14 @@ namespace QuanTAlib;
|
||||
[SkipLocalsInit]
|
||||
public sealed class Usf : AbstractBase
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double Usf1, double Usf2, double PrevInput1, double PrevInput2, double LastValidValue, int Count, bool IsHot)
|
||||
{
|
||||
public static State New() => new() { Usf1 = 0, Usf2 = 0, PrevInput1 = 0, PrevInput2 = 0, LastValidValue = double.NaN, Count = 0, IsHot = false };
|
||||
}
|
||||
|
||||
private readonly double _c1, _c2, _c3;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
private State _state = State.New();
|
||||
private State _p_state = State.New();
|
||||
|
||||
@@ -48,6 +50,7 @@ public sealed class Usf : AbstractBase
|
||||
|
||||
Name = $"Usf({period})";
|
||||
WarmupPeriod = period;
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,7 +60,7 @@ public sealed class Usf : AbstractBase
|
||||
/// <param name="period">Period for USF calculation</param>
|
||||
public Usf(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
public Usf(TSeries source, int period) : this(period)
|
||||
@@ -67,9 +70,11 @@ public sealed class Usf : AbstractBase
|
||||
{
|
||||
Last = new TValue(source.LastTime, Last.Value);
|
||||
}
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
public override bool IsHot => _state.IsHot;
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
@@ -171,7 +176,7 @@ public sealed class Usf : AbstractBase
|
||||
_state.IsHot = true;
|
||||
|
||||
Last = new TValue(input.Time, usf);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -278,7 +283,7 @@ public sealed class Usf : AbstractBase
|
||||
double c1 = (1.0 + c2 - c3) / 4.0;
|
||||
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
if (source.Length == 0) return;
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public class VidyaValidationTests
|
||||
_output.WriteLine("VIDYA Batch validated successfully against reference implementation");
|
||||
}
|
||||
|
||||
private static List<double> CalculateVidyaReference(TSeries data, int period)
|
||||
private static IReadOnlyList<double> CalculateVidyaReference(TSeries data, int period)
|
||||
{
|
||||
var results = new List<double>();
|
||||
var prices = data.Select(x => x.Value).ToList();
|
||||
|
||||
@@ -33,8 +33,9 @@ public sealed class Vidya : AbstractBase, IDisposable
|
||||
private readonly RingBuffer _ups;
|
||||
private readonly RingBuffer _downs;
|
||||
private readonly ITValuePublisher? _source;
|
||||
private readonly Action<TValue>? _pubHandler;
|
||||
private readonly TValuePublishedHandler? _pubHandler;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(
|
||||
double PrevClose, double LastVidya,
|
||||
double CurrentClose, double CurrentVidya,
|
||||
@@ -59,7 +60,7 @@ public sealed class Vidya : AbstractBase, IDisposable
|
||||
public Vidya(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
_source = source;
|
||||
_pubHandler = (item) => Update(item);
|
||||
_pubHandler = Handle;
|
||||
source.Pub += _pubHandler;
|
||||
}
|
||||
|
||||
@@ -71,6 +72,8 @@ public sealed class Vidya : AbstractBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
public override bool IsHot => _state.BarCount >= _period;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -277,7 +280,7 @@ public sealed class Vidya : AbstractBase, IDisposable
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
|
||||
if (source.Length == 0) return;
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@ public sealed class Wma : AbstractBase, IDisposable
|
||||
private readonly double _divisor;
|
||||
private readonly RingBuffer _buffer;
|
||||
private readonly ITValuePublisher? _source;
|
||||
private readonly Action<TValue>? _handler;
|
||||
private readonly TValuePublishedHandler? _handler;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
private record struct State(double Sum, double WSum, double LastInput, double LastValidValue, int TickCount, bool HasSeenValidData);
|
||||
private State _state;
|
||||
private State _p_state;
|
||||
@@ -68,7 +69,7 @@ public sealed class Wma : AbstractBase, IDisposable
|
||||
public Wma(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
_source = source;
|
||||
_handler = (item) => Update(item);
|
||||
_handler = Handle;
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ public sealed class Wma : AbstractBase, IDisposable
|
||||
|
||||
double currentDivisor = _buffer.IsFull ? _divisor : (double)_buffer.Count * (_buffer.Count + 1) * 0.5;
|
||||
Last = new TValue(input.Time, _state.WSum / currentDivisor);
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -185,6 +186,8 @@ public sealed class Wma : AbstractBase, IDisposable
|
||||
return new TSeries(t, v);
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
public override void Prime(ReadOnlySpan<double> source)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
@@ -248,7 +251,7 @@ public sealed class Wma : AbstractBase, IDisposable
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
throw new ArgumentException("Source and output must have the same length");
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
if (period <= 0)
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace QuanTAlib;
|
||||
public sealed class Atr : AbstractBase
|
||||
{
|
||||
private readonly Rma _rma;
|
||||
private readonly TValuePublishedHandler _handler;
|
||||
private TBar _prevBar;
|
||||
private bool _isInitialized;
|
||||
|
||||
@@ -37,6 +38,7 @@ public sealed class Atr : AbstractBase
|
||||
Name = $"Atr({period})";
|
||||
WarmupPeriod = period;
|
||||
_isInitialized = false;
|
||||
_handler = Handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -46,7 +48,7 @@ public sealed class Atr : AbstractBase
|
||||
/// <param name="period">Period for ATR calculation</param>
|
||||
public Atr(ITValuePublisher source, int period) : this(period)
|
||||
{
|
||||
source.Pub += (item) => Update(item);
|
||||
source.Pub += _handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,6 +64,8 @@ public sealed class Atr : AbstractBase
|
||||
// but we can rely on manual updates or the user subscribing.
|
||||
}
|
||||
|
||||
private void Handle(object? sender, TValueEventArgs e) => Update(e.Value, e.IsNew);
|
||||
|
||||
/// <summary>
|
||||
/// True if the ATR has warmed up and is providing valid results.
|
||||
/// </summary>
|
||||
@@ -119,7 +123,7 @@ public sealed class Atr : AbstractBase
|
||||
TValue result = _rma.Update(new TValue(input.Time, tr), isNew);
|
||||
|
||||
Last = result;
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -132,7 +136,7 @@ public sealed class Atr : AbstractBase
|
||||
// If user passes a single value, we assume it IS the True Range
|
||||
TValue result = _rma.Update(input, isNew);
|
||||
Last = result;
|
||||
PubEvent(Last);
|
||||
PubEvent(Last, isNew);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,8 +103,8 @@ public class AdlTests
|
||||
{
|
||||
var adl = new Adl();
|
||||
bool eventFired = false;
|
||||
adl.Pub += (val) => eventFired = true;
|
||||
|
||||
adl.Pub += (object? sender, TValueEventArgs args) => eventFired = true;
|
||||
|
||||
adl.Update(new TBar(DateTime.UtcNow, 10, 12, 8, 10, 100));
|
||||
Assert.True(eventFired);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class Adl : ITValuePublisher
|
||||
/// </summary>
|
||||
public static string Name => "ADL";
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current ADL value.
|
||||
@@ -90,7 +90,7 @@ public sealed class Adl : ITValuePublisher
|
||||
|
||||
_isInitialized = true;
|
||||
Last = new TValue(input.Time, _adl);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public sealed class Adl : ITValuePublisher
|
||||
}
|
||||
|
||||
Last = new TValue(input.Time, _adl);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ public sealed class Adl : ITValuePublisher
|
||||
public static void Calculate(ReadOnlySpan<double> high, ReadOnlySpan<double> low, ReadOnlySpan<double> close, ReadOnlySpan<double> volume, Span<double> output)
|
||||
{
|
||||
if (high.Length != low.Length || high.Length != close.Length || high.Length != volume.Length || high.Length != output.Length)
|
||||
throw new ArgumentException("All spans must be of the same length");
|
||||
throw new ArgumentException("All spans must be of the same length", nameof(output));
|
||||
|
||||
int len = high.Length;
|
||||
int i = 0;
|
||||
@@ -197,3 +197,4 @@ public sealed class Adl : ITValuePublisher
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class Adosc : ITValuePublisher
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public event Action<TValue>? Pub;
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
/// <summary>
|
||||
/// Current ADOSC value.
|
||||
@@ -96,7 +96,7 @@ public sealed class Adosc : ITValuePublisher
|
||||
|
||||
double adosc = eFast.Value - eSlow.Value;
|
||||
Last = new TValue(input.Time, adosc);
|
||||
Pub?.Invoke(Last);
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public sealed class Adosc : ITValuePublisher
|
||||
public static void Calculate(ReadOnlySpan<double> high, ReadOnlySpan<double> low, ReadOnlySpan<double> close, ReadOnlySpan<double> volume, Span<double> output, int fastPeriod = 3, int slowPeriod = 10)
|
||||
{
|
||||
if (high.Length != output.Length)
|
||||
throw new ArgumentException("Source and output spans must be of the same length.");
|
||||
throw new ArgumentException("Source and output spans must be of the same length.", nameof(output));
|
||||
|
||||
Span<double> adl = high.Length <= 1024 ? stackalloc double[high.Length] : new double[high.Length];
|
||||
Adl.Calculate(high, low, close, volume, adl);
|
||||
@@ -176,3 +176,4 @@ public sealed class Adosc : ITValuePublisher
|
||||
SimdExtensions.Subtract(fastEma, slowEma, output);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+49
-106
@@ -1,115 +1,58 @@
|
||||
#-------------------------------------------------------------------------------#
|
||||
#-------------------------------------------------------------------------------#
|
||||
# Qodana analysis is configured by qodana.yaml file #
|
||||
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||
#-------------------------------------------------------------------------------#
|
||||
version: "1.0"
|
||||
|
||||
#Specify inspection profile for code analysis
|
||||
profile:
|
||||
name: qodana.starter
|
||||
name: qodana.recommended
|
||||
|
||||
#Enable inspections
|
||||
#include:
|
||||
# - name: <SomeEnabledInspectionId>
|
||||
|
||||
#Disable inspections
|
||||
exclude:
|
||||
# Disable XML documentation comment validation (allows unescaped < > in comments)
|
||||
- name: XmlDocAnalyzer
|
||||
- name: InvalidXmlDocComment
|
||||
# Library project - public API properties are intentionally unused internally
|
||||
- name: UnusedAutoPropertyAccessor.Global
|
||||
# Style preference - fully qualified names used intentionally for clarity
|
||||
- name: RedundantNameQualifier
|
||||
- name: RCS1036 # Roslyn: Remove redundant empty line
|
||||
- name: IDE0001 # Simplify name
|
||||
- name: IDE0002 # Simplify member access
|
||||
|
||||
# HIGH-PERFORMANCE LIBRARY EXCLUSIONS
|
||||
# ------------------------------------
|
||||
# Flat namespace structure is intentional for this library
|
||||
- name: CheckNamespace
|
||||
|
||||
# Float comparisons are intentional in financial calculations (checking 0.0, NaN, sentinel values)
|
||||
- name: CompareOfFloatsByEqualityOperator
|
||||
|
||||
# Explicit default args improve code clarity and self-documentation
|
||||
- name: RedundantArgumentDefaultValue
|
||||
|
||||
# Platform-specific optimizations (SIMD, intrinsics) are intentional
|
||||
- name: CA1416 # Validate platform compatibility
|
||||
|
||||
# Public API unused internally - this is a library
|
||||
- name: UnusedMember.Global
|
||||
- name: MemberCanBePrivate.Global
|
||||
- name: MemberCanBePrivate.Local
|
||||
- name: ClassNeverInstantiated.Global
|
||||
- name: UnusedType.Global
|
||||
- name: UnusedMethodReturnValue.Global
|
||||
- name: AutoPropertyCanBeMadeGetOnly.Global
|
||||
- name: MemberCanBeMadeStatic.Global
|
||||
- name: MemberCanBeMadeStatic.Local
|
||||
|
||||
# Redundant using directives - managed by IDE/build, not critical for library
|
||||
- name: RedundantUsingDirective
|
||||
- name: IDE0005 # Remove unnecessary using directives
|
||||
- name: CS8019 # Unnecessary using directive
|
||||
|
||||
# Nullable warning suppressions - used intentionally for null safety patterns
|
||||
- name: RedundantSuppressNullableWarningExpression
|
||||
|
||||
# Redundant type specifications - explicit types used for clarity/documentation
|
||||
- name: RedundantTypeArgumentsOfMethod
|
||||
- name: RedundantCast
|
||||
- name: RedundantExplicitArrayCreation
|
||||
|
||||
# Unused local variables - often used in test setup or placeholder code (includes false positives for tuple deconstruction)
|
||||
- name: UnusedVariable
|
||||
- name: UnusedVariable.Compiler # False positive for tuple deconstruction
|
||||
- name: CS0219 # Variable is assigned but never used
|
||||
- name: RedundantAssignment # Value assigned is not used in any execution path
|
||||
- name: UnusedAssignment # Assignment is not used
|
||||
- name: IDE0059 # Unnecessary assignment of a value
|
||||
|
||||
# Object initializer in using statement - false positive for simple property setters
|
||||
- name: CA2000 # Dispose objects before losing scope (overly cautious for simple cases)
|
||||
- name: UseObjectOrCollectionInitializerWhenPossible
|
||||
- name: DoNotUseObjectInitializerForUsingVariable
|
||||
- name: ObjectInitializerMightCauseException
|
||||
- name: ObjectCreationAsStatement
|
||||
- name: UseObjectOrCollectionInitializer
|
||||
- name: UsingStatementResourceInitialization # "Do not use object initializer for 'using' variable"
|
||||
|
||||
# Private field can be local variable - test fixtures often use fields for clarity/organization
|
||||
- name: PrivateFieldCanBeConvertedToLocalVariable
|
||||
- name: ConvertToLocalFunction
|
||||
|
||||
# Code coverage checks - SonarCloud handles coverage, Qodana coverage unreliable
|
||||
- name: CoverageCheck
|
||||
- name: ClassCoverageCheck
|
||||
- name: MethodCoverageCheck
|
||||
- name: CodeCoverageCheck
|
||||
|
||||
# Library-specific exclusions
|
||||
- name: UnusedMember.Global # Suppresses "Method/Class is never used"
|
||||
- name: UnusedMethodReturnValue.Global # Suppresses "Return value is never used"
|
||||
- name: AutoPropertyCanBeMadeGetOnly.Global # Optional: common in libraries
|
||||
|
||||
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
||||
bootstrap: sh ./.github/prepare-qodana.sh
|
||||
|
||||
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
||||
#plugins:
|
||||
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||
|
||||
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
||||
#linter: jetbrains/qodana-dotnet:2025.3
|
||||
|
||||
linter: jetbrains/qodana-dotnet:2024.3
|
||||
linter: jetbrains/qodana-dotnet:2025.3
|
||||
|
||||
dotnet:
|
||||
solution: QuanTAlib.sln
|
||||
coverage:
|
||||
pathMappings:
|
||||
- from: /home/runner/work/QuanTAlib/QuanTAlib
|
||||
to: /data/project
|
||||
frameworks: "net10.0"
|
||||
configuration: Release
|
||||
|
||||
failureConditions:
|
||||
severityThresholds:
|
||||
critical: 0
|
||||
high: 10
|
||||
moderate: 50
|
||||
low: 100
|
||||
info: -1
|
||||
|
||||
exclude:
|
||||
- name: All
|
||||
paths:
|
||||
- "**/bin/**"
|
||||
- "**/obj/**"
|
||||
- "**/packages/**"
|
||||
- "**/TestResults/**"
|
||||
- "**/.vs/**"
|
||||
- "**/.vscode/**"
|
||||
- "**/.idea/**"
|
||||
- "**/.git/**"
|
||||
- ".github/**"
|
||||
- "**/.github/**"
|
||||
- ".github/TradingPlatform.BusinessLayer.xml"
|
||||
- "**/TradingPlatform.BusinessLayer.xml"
|
||||
- "**/.agent/**"
|
||||
- "**/.codacy/**"
|
||||
- "**/.config/**"
|
||||
- "**/.clinerules/**"
|
||||
- "**/.idea/**"
|
||||
- "**/.qodana/**"
|
||||
- "**/.sonarlint/**"
|
||||
- "**/coverage/**"
|
||||
- "**/CoverageReport/**"
|
||||
- "**/archive/**"
|
||||
- "**/notebooks/**"
|
||||
- "**/*.Designer.cs"
|
||||
- "**/*.generated.cs"
|
||||
- "**/results/**"
|
||||
- "**/TestResults/**"
|
||||
- "**/BenchmarkDotNet.Artifacts/**"
|
||||
- name: InvalidXmlDocComment
|
||||
- name: EnforceIfStatementBraces
|
||||
- name: CheckNamespace
|
||||
- name: LoopCanBeConvertedToQuery
|
||||
|
||||
@@ -149,7 +149,7 @@ public class IndicatorExtensionsTests
|
||||
var args = new PaintChartEventArgs(graphics, new Rectangle(0, 0, 100, 100));
|
||||
|
||||
// Test PaintSmoothCurve with different LineStyles and Warmup
|
||||
foreach (LineStyle style in Enum.GetValues(typeof(LineStyle)))
|
||||
foreach (LineStyle style in Enum.GetValues<LineStyle>())
|
||||
{
|
||||
var series = new LineSeries("Test", Color.Blue, 1, style);
|
||||
for (int i = 0; i < 20; i++) series.AddValue();
|
||||
|
||||
Reference in New Issue
Block a user