feat: Add new CodeQL extension for C# and SonarLint configuration

- Introduced a new CodeQL extension for C# in `.github/codeql/extensions/quantalib-csharp/codeql-pack.yml`.
- Added SonarLint configuration in `.sonarlint/CSharp/SonarLint.xml` and `.sonarlint/csharp.ruleset` to suppress specific rules for high-performance indicators.
- Removed outdated `.vscode/launch.json` configurations.
- Updated `.vscode/tasks.json` to streamline build and test tasks, including renaming and consolidating tasks.
- Modified `Directory.Build.props` to enhance SARIF output directory handling and integrate SonarLint rules.
- Refactored various indicator classes to improve code clarity and maintainability, including updates to method parameters for consistency.
- Added XML documentation comments to several classes and methods for better code understanding.
- Improved numerical stability in calculations by replacing direct comparisons with `double.Epsilon` checks in multiple classes.
This commit is contained in:
Miha Kralj
2026-01-21 23:05:38 -06:00
parent 32f3366952
commit da4e56bf40
32 changed files with 187 additions and 187 deletions
Vendored
BIN
View File
Binary file not shown.
@@ -0,0 +1,7 @@
name: mihakralj/quantalib-csharp
version: 0.0.0
library: true
extensionTargets:
codeql/csharp-all: '*'
dataExtensions:
- models/**/*.yml
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<AnalysisInput>
<Settings>
</Settings>
<Rules>
<!-- Cognitive Complexity: disabled for indicator state machines with necessary branching -->
<Rule>
<Key>csharpsquid:S3776</Key>
<Parameters>
</Parameters>
</Rule>
</Rules>
</AnalysisInput>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="QuanTAlib SonarLint Rules" Description="Custom rule suppressions for QuanTAlib high-performance indicators" ToolsVersion="17.0">
<Rules AnalyzerId="SonarAnalyzer.CSharp" RuleNamespace="SonarAnalyzer.CSharp">
<!-- S3776: Cognitive Complexity - disabled for state machine patterns in indicators -->
<Rule Id="S3776" Action="None" />
<!-- S107: Methods should not have too many parameters - SIMD methods require multiple params -->
<Rule Id="S107" Action="None" />
<!-- S1144: Unused private types or members should be removed -->
<Rule Id="S1144" Action="None" />
<!-- S2245: Random should not be used for security purposes -->
<Rule Id="S2245" Action="None" />
<!-- S3604: Remove member initializer - null! is intentional -->
<Rule Id="S3604" Action="None" />
</Rules>
</RuleSet>
-73
View File
@@ -1,73 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Build lib/quantalib.csproj (Debug net10.0)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"build",
"${workspaceFolder}/lib/quantalib.csproj",
"--configuration",
"Debug",
"--framework",
"net10.0"
],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "integratedTerminal",
"logging": {
"moduleLoad": false
}
},
{
"name": "Debug Tests",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-sln-debug",
"program": "dotnet",
"args": [
"test",
"${workspaceFolder}/QuanTAlib.sln",
"--no-build"
],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole",
"logging": {
"moduleLoad": false
}
},
{
"name": "Debug Library",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}",
"justMyCode": true,
"logging": {
"moduleLoad": false
}
},
{
"name": "Build solution (Debug)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-sln-debug",
"program": "dotnet",
"args": [
"build",
"${workspaceFolder}/QuanTAlib.sln",
"--configuration",
"Debug"
],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "integratedTerminal",
"logging": {
"moduleLoad": false
}
}
]
}
+15 -57
View File
@@ -2,23 +2,8 @@
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "Build & Audit (Roslyn + JetBrains)", "label": "Build",
"dependsOrder": "sequence", "detail": "dotnet build QuanTAlib.sln",
"dependsOn": ["Create .sarif Dir", "Build: QuanTAlib", "JetBrains: Inspect"],
"group": { "kind": "build", "isDefault": true }
},
{
"label": "Create .sarif Dir",
"type": "shell",
"command": "mkdir -p .sarif",
"windows": {
"command": "powershell",
"args": ["-Command", "if (!(Test-Path .sarif)) { New-Item -Path .sarif -ItemType Directory }"]
},
"presentation": { "reveal": "silent" }
},
{
"label": "Build: QuanTAlib",
"type": "process", "type": "process",
"command": "dotnet", "command": "dotnet",
"args": [ "args": [
@@ -27,63 +12,36 @@
"/property:GenerateFullPaths=true", "/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary" "/consoleloggerparameters:NoSummary"
], ],
"problemMatcher": "$msCompile"
},
{
"label": "JetBrains: Inspect",
"type": "shell",
"command": "jb inspectcode QuanTAlib.sln --output=.sarif/jetbrains.sarif --format=Sarif",
"presentation": { "reveal": "always" },
"problemMatcher": [],
"postLines": ["Analysis complete. Open .sarif/jetbrains.sarif to view results."]
},
{
"label": "JetBrains: Cleanup",
"type": "shell",
"command": "jb cleanupcode QuanTAlib.sln",
"problemMatcher": []
},
{
"label": "test-net10",
"command": "dotnet",
"type": "process",
"args": [
"test",
"${workspaceFolder}/lib/QuanTAlib.Tests.csproj",
"--framework",
"net10.0"
],
"problemMatcher": "$msCompile", "problemMatcher": "$msCompile",
"group": { "kind": "test", "isDefault": true }, "group": { "kind": "build", "isDefault": true }
"presentation": { "reveal": "always", "panel": "new" }
}, },
{ {
"label": "build-lib-net10-debug", "label": "Build Library",
"detail": "dotnet build lib/quantalib.csproj (Debug, net10.0)",
"type": "process", "type": "process",
"command": "dotnet", "command": "dotnet",
"args": [ "args": [
"build", "build",
"${workspaceFolder}/lib/quantalib.csproj", "${workspaceFolder}/lib/quantalib.csproj",
"--configuration", "--configuration", "Debug",
"Debug", "--framework", "net10.0"
"--framework",
"net10.0"
], ],
"problemMatcher": "$msCompile", "problemMatcher": "$msCompile",
"group": { "kind": "build", "isDefault": false } "group": "build"
}, },
{ {
"label": "build-sln-debug", "label": "Test",
"detail": "dotnet test lib/QuanTAlib.Tests.csproj (net10.0)",
"type": "process", "type": "process",
"command": "dotnet", "command": "dotnet",
"args": [ "args": [
"build", "test",
"${workspaceFolder}/QuanTAlib.sln", "${workspaceFolder}/lib/QuanTAlib.Tests.csproj",
"--configuration", "--framework", "net10.0"
"Debug"
], ],
"problemMatcher": "$msCompile", "problemMatcher": "$msCompile",
"group": { "kind": "build", "isDefault": false } "group": { "kind": "test", "isDefault": true },
"presentation": { "reveal": "always", "panel": "new" }
} }
] ]
} }
+22 -8
View File
@@ -19,7 +19,7 @@
<IncludeSymbols>true</IncludeSymbols> <IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<IsLocalBuild Condition="'$(GITHUB_ACTIONS)' == ''">true</IsLocalBuild> <IsLocalBuild Condition="'$(GITHUB_ACTIONS)' == ''">true</IsLocalBuild>
<!-- AOT and Trim compatibility analyzers (build-time validation) --> <!-- AOT and Trim compatibility analyzers (build-time validation) -->
<IsAotCompatible>true</IsAotCompatible> <IsAotCompatible>true</IsAotCompatible>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer> <EnableTrimAnalyzer>true</EnableTrimAnalyzer>
@@ -33,12 +33,15 @@
<!-- SARIF 2.1 output for Roslyn analyzers - all projects --> <!-- SARIF 2.1 output for Roslyn analyzers - all projects -->
<PropertyGroup> <PropertyGroup>
<SarifOutputDir>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'QuanTAlib.sln'))\.sarif</SarifOutputDir> <!-- Use forward slashes for cross-platform compatibility (works on Windows, Linux, macOS) -->
<ErrorLog>$(SarifOutputDir)\$(MSBuildProjectName).sarif,version=2.1</ErrorLog> <SarifOutputDir>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'QuanTAlib.sln'))/.sarif</SarifOutputDir>
<ErrorLog>$(SarifOutputDir)/$(MSBuildProjectName).sarif,version=2.1</ErrorLog>
</PropertyGroup> </PropertyGroup>
<Target Name="CreateSarifDir" BeforeTargets="CoreCompile"> <Target Name="CreateSarifDir" BeforeTargets="CoreCompile">
<MakeDir Directories="$(SarifOutputDir)" Condition="!Exists('$(SarifOutputDir)')" /> <MakeDir Directories="$(SarifOutputDir)" />
<Message Importance="High" Text="SARIF output directory: $(SarifOutputDir)" />
<Message Importance="High" Text="SARIF file: $(ErrorLog)" />
</Target> </Target>
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <PropertyGroup Condition="'$(Configuration)' == 'Release'">
@@ -46,15 +49,15 @@
<DebugType>portable</DebugType> <DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<!-- Trim mode for library compatibility validation --> <!-- Trim mode for library compatibility validation -->
<TrimMode>link</TrimMode> <TrimMode>link</TrimMode>
<!-- Runtime feature switches (reduce size when library is trimmed by consumer) --> <!-- Runtime feature switches (reduce size when library is trimmed by consumer) -->
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization> <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding> <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<InvariantGlobalization>true</InvariantGlobalization> <InvariantGlobalization>true</InvariantGlobalization>
<!-- Suppress reflection-based serialization (faster startup, smaller footprint) --> <!-- Suppress reflection-based serialization (faster startup, smaller footprint) -->
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault> <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup> </PropertyGroup>
@@ -62,7 +65,8 @@
<PropertyGroup> <PropertyGroup>
<!-- S3604: "Remove member initializer" - null! is intentional for nullable reference types in Quantower adapters --> <!-- S3604: "Remove member initializer" - null! is intentional for nullable reference types in Quantower adapters -->
<!-- S107: "Methods should not have too many parameters" - high-performance SIMD methods require multiple parameters for zero-allocation patterns --> <!-- S107: "Methods should not have too many parameters" - high-performance SIMD methods require multiple parameters for zero-allocation patterns -->
<NoWarn>$(NoWarn);S107;S1144;S1944;S2053;S2245;S2259;S2583;S2589;S3329;S3604;S3655;S3776;S3949;S3966;S4158;S4347;S5773;S6781;MA0048;MA0051;MA0076;RCS1123;RCS1159;IDE0007</NoWarn> <!-- MA0007: "Add comma after the last value" - trailing commas not enforced in this codebase -->
<NoWarn>$(NoWarn);S107;S1144;S1944;S2053;S2245;S2259;S2583;S2589;S3329;S3604;S3655;S3776;S3949;S3966;S4158;S4347;S5773;S6781;MA0007;MA0048;MA0051;MA0076;RCS1123;RCS1159;IDE0007</NoWarn>
</PropertyGroup> </PropertyGroup>
<!-- Code Coverage Configuration (for NDepend integration) --> <!-- Code Coverage Configuration (for NDepend integration) -->
@@ -74,6 +78,16 @@
<Exclude>[xunit.*]*,[*.Tests]*</Exclude> <Exclude>[xunit.*]*,[*.Tests]*</Exclude>
</PropertyGroup> </PropertyGroup>
<!-- SonarLint local rule configuration -->
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory).sonarlint\csharp.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory).sonarlint\CSharp\SonarLint.xml">
<Link>Properties\SonarLint.xml</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
<PackageReference Include="GitVersion.MsBuild" Version="6.5.1"> <PackageReference Include="GitVersion.MsBuild" Version="6.5.1">
+1 -1
View File
@@ -289,7 +289,7 @@ public sealed class Maenv : ITValuePublisher
private double CalculateEMA(double value, bool isNew) private double CalculateEMA(double value, bool isNew)
{ {
// Use EmaWeight==0 to detect first value for correct isNew=false behavior // Use EmaWeight==0 to detect first value for correct isNew=false behavior
if (_state.EmaWeight == 0) if (Math.Abs(_state.EmaWeight) < double.Epsilon)
{ {
// First value - persist only for isNew=true // First value - persist only for isNew=true
if (isNew) if (isNew)
+3
View File
@@ -75,6 +75,9 @@ public sealed class Sdchannel : ITValuePublisher
public event TValuePublishedHandler? Pub; public event TValuePublishedHandler? Pub;
/// <summary>
/// Initializes a new Standard Deviation Channel indicator with specified period and multiplier.
/// </summary>
/// <param name="period">Lookback period for regression (default 20, must be > 1)</param> /// <param name="period">Lookback period for regression (default 20, must be > 1)</param>
/// <param name="multiplier">Standard deviation multiplier for bands (default 2.0, must be > 0)</param> /// <param name="multiplier">Standard deviation multiplier for bands (default 2.0, must be > 0)</param>
public Sdchannel(int period = 20, double multiplier = 2.0) public Sdchannel(int period = 20, double multiplier = 2.0)
+1 -3
View File
@@ -1,6 +1,5 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -121,7 +120,6 @@ public sealed class Stc : AbstractBase
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("SonarQube", "S3776:Cognitive Complexity", Justification = "High-performance SIMD code with intentionally optimized control flow")]
private static void UpdateMinMax(double added, double removed, bool hasRemoved, RingBuffer buf, ref double min, ref double max) private static void UpdateMinMax(double added, double removed, bool hasRemoved, RingBuffer buf, ref double min, ref double max)
{ {
if (double.IsNaN(added)) return; if (double.IsNaN(added)) return;
@@ -559,4 +557,4 @@ public sealed class Stc : AbstractBase
ArrayPool<double>.Shared.Return(rentedStoch1); ArrayPool<double>.Shared.Return(rentedStoch1);
} }
} }
} }
+6
View File
@@ -27,6 +27,9 @@ public sealed class Roc : AbstractBase
public override bool IsHot => _buffer.Count > _period; public override bool IsHot => _buffer.Count > _period;
/// <summary>
/// Initializes a new Rate of Change indicator with specified lookback period.
/// </summary>
/// <param name="period">Lookback period (must be >= 1)</param> /// <param name="period">Lookback period (must be >= 1)</param>
public Roc(int period = 9) public Roc(int period = 9)
{ {
@@ -39,6 +42,9 @@ public sealed class Roc : AbstractBase
WarmupPeriod = period + 1; WarmupPeriod = period + 1;
} }
/// <summary>
/// Initializes a new Rate of Change indicator with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
/// <param name="period">Lookback period</param> /// <param name="period">Lookback period</param>
public Roc(ITValuePublisher source, int period = 9) : this(period) public Roc(ITValuePublisher source, int period = 9) : this(period)
+8 -2
View File
@@ -27,6 +27,9 @@ public sealed class Change : AbstractBase
public override bool IsHot => _buffer.Count > _period; public override bool IsHot => _buffer.Count > _period;
/// <summary>
///
/// </summary>
/// <param name="period">Lookback period (must be >= 1)</param> /// <param name="period">Lookback period (must be >= 1)</param>
public Change(int period = 1) public Change(int period = 1)
{ {
@@ -39,6 +42,9 @@ public sealed class Change : AbstractBase
WarmupPeriod = period + 1; WarmupPeriod = period + 1;
} }
/// <summary>
///
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
/// <param name="period">Lookback period</param> /// <param name="period">Lookback period</param>
public Change(ITValuePublisher source, int period = 1) : this(period) public Change(ITValuePublisher source, int period = 1) : this(period)
@@ -99,7 +105,7 @@ public sealed class Change : AbstractBase
for (int i = 0; i < source.Length; i++) for (int i = 0; i < source.Length; i++)
{ {
Update(new TValue(time, source[i]), true); Update(new TValue(time, value: source[i]), isNew: true);
time += interval; time += interval;
} }
} }
@@ -189,4 +195,4 @@ public sealed class Change : AbstractBase
_p_state = default; _p_state = default;
Last = default; Last = default;
} }
} }
+4 -1
View File
@@ -30,6 +30,9 @@ public sealed class Exptrans : AbstractBase
WarmupPeriod = 0; WarmupPeriod = 0;
} }
/// <summary>
/// Creates Exptrans with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
public Exptrans(ITValuePublisher source) : this() public Exptrans(ITValuePublisher source) : this()
{ {
@@ -146,4 +149,4 @@ public sealed class Exptrans : AbstractBase
_p_state = new(1.0); _p_state = new(1.0);
Last = default; Last = default;
} }
} }
+7 -1
View File
@@ -26,6 +26,9 @@ public sealed class Highest : AbstractBase
public override bool IsHot => _buffer.Count >= _period; public override bool IsHot => _buffer.Count >= _period;
/// <summary>
/// Initializes a new Highest indicator with specified lookback period.
/// </summary>
/// <param name="period">Lookback window size (must be >= 1)</param> /// <param name="period">Lookback window size (must be >= 1)</param>
public Highest(int period) public Highest(int period)
{ {
@@ -38,6 +41,9 @@ public sealed class Highest : AbstractBase
WarmupPeriod = period; WarmupPeriod = period;
} }
/// <summary>
/// Initializes a new Highest indicator with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
/// <param name="period">Lookback window size</param> /// <param name="period">Lookback window size</param>
public Highest(ITValuePublisher source, int period) : this(period) public Highest(ITValuePublisher source, int period) : this(period)
@@ -89,7 +95,7 @@ public sealed class Highest : AbstractBase
for (int i = 0; i < source.Length; i++) for (int i = 0; i < source.Length; i++)
{ {
Update(new TValue(time, source[i]), true); Update(input: new TValue(time, source[i]), isNew: true);
time += interval; time += interval;
} }
} }
+3
View File
@@ -48,6 +48,9 @@ public sealed class Lineartrans : AbstractBase
WarmupPeriod = 0; WarmupPeriod = 0;
} }
/// <summary>
/// Creates a Linear transformer with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
/// <param name="slope">Multiplicative factor (default: 1.0)</param> /// <param name="slope">Multiplicative factor (default: 1.0)</param>
/// <param name="intercept">Additive constant (default: 0.0)</param> /// <param name="intercept">Additive constant (default: 0.0)</param>
+4 -1
View File
@@ -30,6 +30,9 @@ public sealed class Logtrans : AbstractBase
WarmupPeriod = 0; WarmupPeriod = 0;
} }
/// <summary>
/// Creates Logtrans with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
public Logtrans(ITValuePublisher source) : this() public Logtrans(ITValuePublisher source) : this()
{ {
@@ -132,4 +135,4 @@ public sealed class Logtrans : AbstractBase
_p_state = default; _p_state = default;
Last = default; Last = default;
} }
} }
+7 -1
View File
@@ -26,6 +26,9 @@ public sealed class Lowest : AbstractBase
public override bool IsHot => _buffer.Count >= _period; public override bool IsHot => _buffer.Count >= _period;
/// <summary>
/// Initializes a new Lowest indicator with specified lookback period.
/// </summary>
/// <param name="period">Lookback window size (must be >= 1)</param> /// <param name="period">Lookback window size (must be >= 1)</param>
public Lowest(int period) public Lowest(int period)
{ {
@@ -38,6 +41,9 @@ public sealed class Lowest : AbstractBase
WarmupPeriod = period; WarmupPeriod = period;
} }
/// <summary>
/// Initializes a new Lowest indicator with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
/// <param name="period">Lookback window size</param> /// <param name="period">Lookback window size</param>
public Lowest(ITValuePublisher source, int period) : this(period) public Lowest(ITValuePublisher source, int period) : this(period)
@@ -192,4 +198,4 @@ public sealed class Lowest : AbstractBase
_p_state = default; _p_state = default;
Last = default; Last = default;
} }
} }
+7 -1
View File
@@ -26,6 +26,9 @@ public sealed class Midpoint : AbstractBase
public override bool IsHot => _highest.IsHot && _lowest.IsHot; public override bool IsHot => _highest.IsHot && _lowest.IsHot;
/// <summary>
/// Initializes a new Midpoint indicator with specified lookback period.
/// </summary>
/// <param name="period">Lookback window size (must be >= 1)</param> /// <param name="period">Lookback window size (must be >= 1)</param>
public Midpoint(int period) public Midpoint(int period)
{ {
@@ -38,6 +41,9 @@ public sealed class Midpoint : AbstractBase
WarmupPeriod = period; WarmupPeriod = period;
} }
/// <summary>
/// Initializes a new Midpoint indicator with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
/// <param name="period">Lookback window size</param> /// <param name="period">Lookback window size</param>
public Midpoint(ITValuePublisher source, int period) : this(period) public Midpoint(ITValuePublisher source, int period) : this(period)
@@ -157,4 +163,4 @@ public sealed class Midpoint : AbstractBase
_lowest.Reset(); _lowest.Reset();
Last = default; Last = default;
} }
} }
+6
View File
@@ -30,6 +30,9 @@ public sealed class Normalize : AbstractBase
public override bool IsHot => _buffer.Count >= _period; public override bool IsHot => _buffer.Count >= _period;
/// <summary>
/// Initializes a new Normalize indicator with specified lookback period.
/// </summary>
/// <param name="period">Lookback period for min/max calculation (default 14)</param> /// <param name="period">Lookback period for min/max calculation (default 14)</param>
public Normalize(int period = 14) public Normalize(int period = 14)
{ {
@@ -44,6 +47,9 @@ public sealed class Normalize : AbstractBase
_p_state = _state; _p_state = _state;
} }
/// <summary>
/// Initializes a new Normalize indicator with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
/// <param name="period">Lookback period (default 14)</param> /// <param name="period">Lookback period (default 14)</param>
public Normalize(ITValuePublisher source, int period = 14) : this(period) public Normalize(ITValuePublisher source, int period = 14) : this(period)
+3
View File
@@ -34,6 +34,9 @@ public sealed class Relu : AbstractBase
WarmupPeriod = 0; WarmupPeriod = 0;
} }
/// <summary>
///
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
public Relu(ITValuePublisher source) : this() public Relu(ITValuePublisher source) : this()
{ {
+6
View File
@@ -30,6 +30,9 @@ public sealed class Sigmoid : AbstractBase
public override bool IsHot => true; // No warmup needed public override bool IsHot => true; // No warmup needed
/// <summary>
/// Initializes a new Sigmoid indicator with specified steepness and midpoint.
/// </summary>
/// <param name="k">Steepness factor (default 1.0). Higher values create steeper transitions.</param> /// <param name="k">Steepness factor (default 1.0). Higher values create steeper transitions.</param>
/// <param name="x0">Midpoint value where output equals 0.5 (default 0.0).</param> /// <param name="x0">Midpoint value where output equals 0.5 (default 0.0).</param>
public Sigmoid(double k = 1.0, double x0 = 0.0) public Sigmoid(double k = 1.0, double x0 = 0.0)
@@ -43,6 +46,9 @@ public sealed class Sigmoid : AbstractBase
WarmupPeriod = 0; WarmupPeriod = 0;
} }
/// <summary>
/// Initializes a new Sigmoid indicator with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
/// <param name="k">Steepness factor (default 1.0)</param> /// <param name="k">Steepness factor (default 1.0)</param>
/// <param name="x0">Midpoint value (default 0.0)</param> /// <param name="x0">Midpoint value (default 0.0)</param>
+4 -1
View File
@@ -30,6 +30,9 @@ public sealed class Sqrttrans : AbstractBase
WarmupPeriod = 0; WarmupPeriod = 0;
} }
/// <summary>
/// Creates a Square Root transformer with source for event-based chaining.
/// </summary>
/// <param name="source">Source indicator for chaining</param> /// <param name="source">Source indicator for chaining</param>
public Sqrttrans(ITValuePublisher source) : this() public Sqrttrans(ITValuePublisher source) : this()
{ {
@@ -131,4 +134,4 @@ public sealed class Sqrttrans : AbstractBase
_p_state = default; _p_state = default;
Last = default; Last = default;
} }
} }
+4 -1
View File
@@ -75,6 +75,9 @@ public sealed class Hamma : AbstractBase
_state.LastValidValue = double.NaN; _state.LastValidValue = double.NaN;
} }
/// <summary>
/// Creates HAMMA with source for event-based chaining.
/// </summary>
/// <param name="source">Data source for event-based updates</param> /// <param name="source">Data source for event-based updates</param>
/// <param name="period">Lookback period for the Hamming window (default: 10)</param> /// <param name="period">Lookback period for the Hamming window (default: 10)</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -391,4 +394,4 @@ public sealed class Hamma : AbstractBase
_p_state = _state; _p_state = _state;
Last = default; Last = default;
} }
} }
+3
View File
@@ -70,6 +70,9 @@ public sealed class Hanma : AbstractBase
_state = new State(double.NaN, IsInitialized: false); _state = new State(double.NaN, IsInitialized: false);
} }
/// <summary>
///
/// </summary>
/// <param name="source">Data source for event-based updates</param> /// <param name="source">Data source for event-based updates</param>
/// <param name="period">Lookback period for the Hanning window (default: 10)</param> /// <param name="period">Lookback period for the Hanning window (default: 10)</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
+4 -1
View File
@@ -109,6 +109,9 @@ public sealed class Hwma : AbstractBase
_state = new State(double.NaN, 0, 0, double.NaN, IsInitialized: false); _state = new State(double.NaN, 0, 0, double.NaN, IsInitialized: false);
} }
/// <summary>
/// Creates HWMA with source for event-based chaining.
/// </summary>
/// <param name="source">Data source for event-based updates</param> /// <param name="source">Data source for event-based updates</param>
/// <param name="period">Period for smoothing factor calculation (default: 10)</param> /// <param name="period">Period for smoothing factor calculation (default: 10)</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -339,4 +342,4 @@ public sealed class Hwma : AbstractBase
_p_state = _state; _p_state = _state;
Last = default; Last = default;
} }
} }
+9 -9
View File
@@ -101,7 +101,7 @@ public sealed class Sgma : AbstractBase
weights[3] = 0.3429; weights[3] = 0.3429;
weights[4] = -0.0857; weights[4] = -0.0857;
double sum5 = weights[0] + weights[1] + weights[2] + weights[3] + weights[4]; double sum5 = weights[0] + weights[1] + weights[2] + weights[3] + weights[4];
invWeightSum = sum5 != 0.0 ? 1.0 / sum5 : 0.0; invWeightSum = Math.Abs(sum5) > double.Epsilon ? 1.0 / sum5 : 0.0;
return; return;
} }
@@ -116,7 +116,7 @@ public sealed class Sgma : AbstractBase
weights[6] = -0.0476; weights[6] = -0.0476;
double sum7 = 0.0; double sum7 = 0.0;
for (int i = 0; i < 7; i++) sum7 += weights[i]; for (int i = 0; i < 7; i++) sum7 += weights[i];
invWeightSum = sum7 != 0.0 ? 1.0 / sum7 : 0.0; invWeightSum = Math.Abs(sum7) > double.Epsilon ? 1.0 / sum7 : 0.0;
return; return;
} }
@@ -133,7 +133,7 @@ public sealed class Sgma : AbstractBase
weights[8] = -0.0281; weights[8] = -0.0281;
double sum9 = 0.0; double sum9 = 0.0;
for (int i = 0; i < 9; i++) sum9 += weights[i]; for (int i = 0; i < 9; i++) sum9 += weights[i];
invWeightSum = sum9 != 0.0 ? 1.0 / sum9 : 0.0; invWeightSum = Math.Abs(sum9) > double.Epsilon ? 1.0 / sum9 : 0.0;
return; return;
} }
} }
@@ -376,7 +376,7 @@ public sealed class Sgma : AbstractBase
continue; continue;
} }
if (invWeightSum == 0.0) if (Math.Abs(invWeightSum) < double.Epsilon)
{ {
output[i] = val; output[i] = val;
continue; continue;
@@ -399,7 +399,7 @@ public sealed class Sgma : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateWeightedSumFull(RingBuffer buffer, double[] weights, double invWeightSum, double fallbackValue) private static double CalculateWeightedSumFull(RingBuffer buffer, double[] weights, double invWeightSum, double fallbackValue)
{ {
if (invWeightSum == 0.0) if (Math.Abs(invWeightSum) < double.Epsilon)
return fallbackValue; return fallbackValue;
ReadOnlySpan<double> internalBuf = buffer.InternalBuffer; ReadOnlySpan<double> internalBuf = buffer.InternalBuffer;
@@ -435,7 +435,7 @@ public sealed class Sgma : AbstractBase
Math.FusedMultiplyAdd(window[3], w3, window[4] * w4)))); Math.FusedMultiplyAdd(window[3], w3, window[4] * w4))));
double weightSum = w0 + w1 + w2 + w3 + w4; double weightSum = w0 + w1 + w2 + w3 + w4;
return weightSum != 0.0 ? sum / weightSum : fallbackValue; return Math.Abs(weightSum) > double.Epsilon ? sum / weightSum : fallbackValue;
} }
if (p == 7) if (p == 7)
@@ -458,7 +458,7 @@ public sealed class Sgma : AbstractBase
sum = Math.FusedMultiplyAdd(window[6], w6, sum); sum = Math.FusedMultiplyAdd(window[6], w6, sum);
double weightSum = w0 + w1 + w2 + w3 + w4 + w5 + w6; double weightSum = w0 + w1 + w2 + w3 + w4 + w5 + w6;
return weightSum != 0.0 ? sum / weightSum : fallbackValue; return Math.Abs(weightSum) > double.Epsilon ? sum / weightSum : fallbackValue;
} }
if (p == 9) if (p == 9)
@@ -485,7 +485,7 @@ public sealed class Sgma : AbstractBase
sum = Math.FusedMultiplyAdd(window[8], w8, sum); sum = Math.FusedMultiplyAdd(window[8], w8, sum);
double weightSum = w0 + w1 + w2 + w3 + w4 + w5 + w6 + w7 + w8; double weightSum = w0 + w1 + w2 + w3 + w4 + w5 + w6 + w7 + w8;
return weightSum != 0.0 ? sum / weightSum : fallbackValue; return Math.Abs(weightSum) > double.Epsilon ? sum / weightSum : fallbackValue;
} }
} }
@@ -522,4 +522,4 @@ public sealed class Sgma : AbstractBase
_p_lastValidValue = double.NaN; _p_lastValidValue = double.NaN;
Last = default; Last = default;
} }
} }
-1
View File
@@ -189,7 +189,6 @@ public sealed class Sma : AbstractBase
double val = GetValidValue(input.Value); double val = GetValidValue(input.Value);
UpdateState(val); UpdateState(val);
} }
else else
{ {
+6 -6
View File
@@ -39,8 +39,8 @@ public sealed class Vama : AbstractBase
{ {
public static VamaState New() => new() public static VamaState New() => new()
{ {
ShortAtr = new RmaState(0, 1.0, false), ShortAtr = new RmaState(Ema: 0, E: 1.0, IsCompensated: false),
LongAtr = new RmaState(0, 1.0, false), LongAtr = new RmaState(Ema: 0, E: 1.0, IsCompensated: false),
PrevClose = double.NaN, PrevClose = double.NaN,
BufferHead = 0, BufferHead = 0,
BufferSum = 0, BufferSum = 0,
@@ -244,7 +244,7 @@ public sealed class Vama : AbstractBase
newHead, newHead,
bufferSum, bufferSum,
validCount, validCount,
true); IsInitialized: true);
Last = new TValue(input.Time, result); Last = new TValue(input.Time, result);
PubEvent(Last, isNew); PubEvent(Last, isNew);
@@ -285,7 +285,7 @@ public sealed class Vama : AbstractBase
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
var bar = source[i]; var bar = source[i];
var result = Update(bar, true); var result = Update(bar, isNew: true);
tSpan[i] = bar.Time; tSpan[i] = bar.Time;
vSpan[i] = result.Value; vSpan[i] = result.Value;
} }
@@ -314,7 +314,7 @@ public sealed class Vama : AbstractBase
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
var result = Update(new TValue(sourceTimes[i], sourceValues[i]), true); var result = Update(new TValue(sourceTimes[i], sourceValues[i]), isNew: true);
tSpan[i] = sourceTimes[i]; tSpan[i] = sourceTimes[i];
vSpan[i] = result.Value; vSpan[i] = result.Value;
} }
@@ -330,7 +330,7 @@ public sealed class Vama : AbstractBase
Reset(); Reset();
foreach (double val in source) foreach (double val in source)
{ {
Update(new TValue(DateTime.MinValue, val), true); Update(new TValue(DateTime.MinValue, val), isNew: true);
} }
} }
+7 -7
View File
@@ -37,8 +37,8 @@ public sealed class Yzvama : AbstractBase
{ {
public static YzvamaState New() => new() public static YzvamaState New() => new()
{ {
ShortVar = new RmaState(0, 1.0, false), ShortVar = new RmaState(Ema: 0, E: 1.0, IsCompensated: false),
LongVar = new RmaState(0, 1.0, false), LongVar = new RmaState(Ema: 0, E: 1.0, IsCompensated: false),
PrevClose = double.NaN, PrevClose = double.NaN,
SourceHead = 0, SourceHead = 0,
SourceSum = 0, SourceSum = 0,
@@ -390,7 +390,7 @@ public sealed class Yzvama : AbstractBase
validCount, validCount,
yzvHead, yzvHead,
yzvCount, yzvCount,
true); IsInitialized: true);
Last = new TValue(input.Time, result); Last = new TValue(input.Time, result);
PubEvent(Last, isNew); PubEvent(Last, isNew);
@@ -428,7 +428,7 @@ public sealed class Yzvama : AbstractBase
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
var bar = source[i]; var bar = source[i];
var result = Update(bar, true); var result = Update(bar, isNew: true);
tSpan[i] = bar.Time; tSpan[i] = bar.Time;
vSpan[i] = result.Value; vSpan[i] = result.Value;
} }
@@ -457,7 +457,7 @@ public sealed class Yzvama : AbstractBase
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
var result = Update(new TValue(sourceTimes[i], sourceValues[i]), true); var result = Update(new TValue(sourceTimes[i], sourceValues[i]), isNew: true);
tSpan[i] = sourceTimes[i]; tSpan[i] = sourceTimes[i];
vSpan[i] = result.Value; vSpan[i] = result.Value;
} }
@@ -473,7 +473,7 @@ public sealed class Yzvama : AbstractBase
{ {
Reset(); Reset();
foreach (double val in source) foreach (double val in source)
Update(new TValue(DateTime.MinValue, val), true); Update(new TValue(DateTime.MinValue, val), isNew: true);
} }
/// <summary> /// <summary>
@@ -484,7 +484,7 @@ public sealed class Yzvama : AbstractBase
{ {
Reset(); Reset();
foreach (TValue tv in source) foreach (TValue tv in source)
Update(tv, true); Update(tv, isNew: true);
} }
/// <summary> /// <summary>
@@ -7,7 +7,7 @@ public class ZlemaValidationTests
[Fact] [Fact]
public void Zlema_Streaming_MatchesReference() public void Zlema_Streaming_MatchesReference()
{ {
int period = 20; const int period = 20;
TSeries series = BuildSeries(300, seed: 5); TSeries series = BuildSeries(300, seed: 5);
double[] reference = new double[series.Count]; double[] reference = new double[series.Count];
@@ -24,7 +24,7 @@ public class ZlemaValidationTests
[Fact] [Fact]
public void Zlema_Batch_MatchesReference() public void Zlema_Batch_MatchesReference()
{ {
int period = 14; const int period = 14;
TSeries series = BuildSeries(250, seed: 9); TSeries series = BuildSeries(250, seed: 9);
double[] reference = new double[series.Count]; double[] reference = new double[series.Count];
@@ -40,7 +40,7 @@ public class ZlemaValidationTests
[Fact] [Fact]
public void Zlema_Span_MatchesReference() public void Zlema_Span_MatchesReference()
{ {
int period = 30; const int period = 30;
TSeries series = BuildSeries(200, seed: 12); TSeries series = BuildSeries(200, seed: 12);
double[] values = series.Values.ToArray(); double[] values = series.Values.ToArray();
var output = new double[values.Length]; var output = new double[values.Length];
+3 -3
View File
@@ -54,7 +54,7 @@ public class AtrnTests
public void Update_ReturnsValidTValue() public void Update_ReturnsValidTValue()
{ {
var atrn = new Atrn(DefaultPeriod); var atrn = new Atrn(DefaultPeriod);
var result = atrn.Update(_bars[0], true); var result = atrn.Update(_bars[0], isNew: true);
Assert.IsType<TValue>(result); Assert.IsType<TValue>(result);
Assert.Equal(_bars[0].Time, result.Time); Assert.Equal(_bars[0].Time, result.Time);
@@ -67,7 +67,7 @@ public class AtrnTests
for (int i = 0; i < _bars.Count; i++) for (int i = 0; i < _bars.Count; i++)
{ {
var result = atrn.Update(_bars[i], true); var result = atrn.Update(_bars[i], isNew: true);
Assert.True(result.Value >= 0 && result.Value <= 1, Assert.True(result.Value >= 0 && result.Value <= 1,
$"Value {result.Value} at index {i} is outside [0,1] range"); $"Value {result.Value} at index {i} is outside [0,1] range");
} }
@@ -447,4 +447,4 @@ public class AtrnTests
} }
#endregion #endregion
} }
+6 -6
View File
@@ -51,7 +51,7 @@ public sealed class Atrn : AbstractBase
Name = $"Atrn({period})"; Name = $"Atrn({period})";
WarmupPeriod = _rma.WarmupPeriod + _lookbackWindow; WarmupPeriod = _rma.WarmupPeriod + _lookbackWindow;
_state = new State(default, false, 0.0, 0.0); _state = new State(PrevBar: default, IsInitialized: false, LastValidTr: 0.0, LastValidAtr: 0.0);
_p_state = _state; _p_state = _state;
} }
@@ -95,7 +95,7 @@ public sealed class Atrn : AbstractBase
for (int i = 0; i < source.Length; i++) for (int i = 0; i < source.Length; i++)
{ {
double tr = source[i]; double tr = source[i];
TValue atr = _rma.Update(new TValue(DateTime.UtcNow.AddMinutes(i), tr), true); TValue atr = _rma.Update(new TValue(DateTime.UtcNow.AddMinutes(i), tr), isNew: true);
_atrBuffer.Add(atr.Value); _atrBuffer.Add(atr.Value);
} }
@@ -117,7 +117,7 @@ public sealed class Atrn : AbstractBase
{ {
_rma.Reset(); _rma.Reset();
_atrBuffer.Clear(); _atrBuffer.Clear();
_state = new State(default, false, 0.0, 0.0); _state = new State(PrevBar: default, IsInitialized: false, LastValidTr: 0.0, LastValidAtr: 0.0);
_p_state = _state; _p_state = _state;
Last = default; Last = default;
} }
@@ -180,7 +180,7 @@ public sealed class Atrn : AbstractBase
// Update state // Update state
_state = isNew _state = isNew
? new State(input, true, tr, currentAtr) ? new State(PrevBar: input, IsInitialized: true, LastValidTr: tr, LastValidAtr: currentAtr)
: _state with { LastValidTr = tr, LastValidAtr = currentAtr }; : _state with { LastValidTr = tr, LastValidAtr = currentAtr };
TValue result = new(input.Time, normalized); TValue result = new(input.Time, normalized);
@@ -246,7 +246,7 @@ public sealed class Atrn : AbstractBase
for (int i = 0; i < source.Count; i++) for (int i = 0; i < source.Count; i++)
{ {
TValue result = Update(source[i], true); TValue result = Update(source[i], isNew: true);
t.Add(result.Time); t.Add(result.Time);
v.Add(result.Value); v.Add(result.Value);
} }
@@ -266,7 +266,7 @@ public sealed class Atrn : AbstractBase
for (int i = 0; i < source.Count; i++) for (int i = 0; i < source.Count; i++)
{ {
TValue result = Update(source[i], true); TValue result = Update(source[i], isNew: true);
t.Add(source[i].Time); t.Add(source[i].Time);
v.Add(result.Value); v.Add(result.Value);
} }