diff --git a/.github/agents/beast.agent.md b/.github/agents/beast.agent.md index 25ca9f57..947ea58d 100644 --- a/.github/agents/beast.agent.md +++ b/.github/agents/beast.agent.md @@ -2,20 +2,7 @@ name: beast description: Meticulous auto-agent for high-performance .NET library development with full MCP tool integration tools: - - execute - - read - - edit - - search - - web - - codacy-mcp-server/* - - gitkraken/* - - github/* - - qdrant-mcp/* - - sequential-thinking-mcp/* - - tavily-mcp/* - - wolfram-mcp/* - - agent - - todo + [execute/runNotebookCell, execute/testFailure, execute/getTerminalOutput, execute/awaitTerminal, execute/killTerminal, execute/runTask, execute/createAndRunTask, execute/runInTerminal, execute/runTests, read/getNotebookSummary, read/problems, read/readFile, read/readNotebookCellOutput, read/terminalSelection, read/terminalLastCommand, read/getTaskOutput, agent/runSubagent, edit/createDirectory, edit/createFile, edit/createJupyterNotebook, edit/editFiles, edit/editNotebook, search/changes, search/codebase, search/fileSearch, search/listDirectory, search/searchResults, search/textSearch, search/usages, web/fetch, github/add_comment_to_pending_review, github/add_issue_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, codacy-mcp-server/codacy_cli_analyze, codacy-mcp-server/codacy_cli_install, codacy-mcp-server/codacy_get_file_clones, codacy-mcp-server/codacy_get_file_coverage, codacy-mcp-server/codacy_get_file_issues, codacy-mcp-server/codacy_get_file_with_analysis, codacy-mcp-server/codacy_get_issue, codacy-mcp-server/codacy_get_pattern, codacy-mcp-server/codacy_get_pull_request_files_coverage, codacy-mcp-server/codacy_get_pull_request_git_diff, codacy-mcp-server/codacy_get_repository_pull_request, codacy-mcp-server/codacy_get_repository_with_analysis, codacy-mcp-server/codacy_list_files, codacy-mcp-server/codacy_list_organization_repositories, codacy-mcp-server/codacy_list_organizations, codacy-mcp-server/codacy_list_pull_request_issues, codacy-mcp-server/codacy_list_repository_issues, codacy-mcp-server/codacy_list_repository_pull_requests, codacy-mcp-server/codacy_list_repository_tool_patterns, codacy-mcp-server/codacy_list_repository_tools, codacy-mcp-server/codacy_list_tools, codacy-mcp-server/codacy_search_organization_srm_items, codacy-mcp-server/codacy_search_repository_srm_items, codacy-mcp-server/codacy_setup_repository, dotnet-semantic-mcp/ast_diff_unified, dotnet-semantic-mcp/attrs, dotnet-semantic-mcp/code_security, dotnet-semantic-mcp/deps, dotnet-semantic-mcp/diag, dotnet-semantic-mcp/diff, dotnet-semantic-mcp/explore, dotnet-semantic-mcp/hierarchy, dotnet-semantic-mcp/map, dotnet-semantic-mcp/metrics, dotnet-semantic-mcp/nuget_vulnerabilities, dotnet-semantic-mcp/prepare_change, dotnet-semantic-mcp/refs, dotnet-semantic-mcp/scan_cancel, dotnet-semantic-mcp/scan_list, dotnet-semantic-mcp/scan_status, dotnet-semantic-mcp/search, dotnet-semantic-mcp/source, dotnet-semantic-mcp/symbol, dotnet-semantic-mcp/understand, gitkraken/git_add_or_commit, gitkraken/git_blame, gitkraken/git_branch, gitkraken/git_checkout, gitkraken/git_log_or_diff, gitkraken/git_push, gitkraken/git_stash, gitkraken/git_status, gitkraken/git_worktree, gitkraken/gitkraken_workspace_list, gitkraken/issues_add_comment, gitkraken/issues_assigned_to_me, gitkraken/issues_get_detail, gitkraken/pull_request_assigned_to_me, gitkraken/pull_request_create, gitkraken/pull_request_create_review, gitkraken/pull_request_get_comments, gitkraken/pull_request_get_detail, gitkraken/repository_get_file_content, qdrant/qdrant-find, qdrant/qdrant-store, ref/ref_read_url, ref/ref_search_documentation, sequential-thinking/sequentialthinking, tavily/tavily_crawl, tavily/tavily_extract, tavily/tavily_map, tavily/tavily_research, tavily/tavily_search, todo] --- # Role: Meticulous Auto-Agent diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 51aed5c6..f9eae59d 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -41,36 +41,28 @@ jobs: **/*.sln - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v4.2.0 + uses: gittools/actions/gitversion/setup@d0139503a9321f76b4a417dfdc8aebcec24decdd # v4.2.0 with: versionSpec: "6.x" includePrerelease: true - name: Determine Version id: gitversion - uses: gittools/actions/gitversion/execute@v4.2.0 + uses: gittools/actions/gitversion/execute@d0139503a9321f76b4a417dfdc8aebcec24decdd # v4.2.0 with: updateAssemblyInfo: false - name: Restore dependencies run: dotnet restore - - name: Build - run: | - dotnet build ./lib/quantalib.csproj --configuration Release --no-restore --nologo - dotnet build ./quantower/Channels.csproj --configuration Release --nologo - dotnet build ./quantower/Cycles.csproj --configuration Release --nologo - dotnet build ./quantower/Dynamics.csproj --configuration Release --nologo - dotnet build ./quantower/Filters.csproj --configuration Release --nologo - dotnet build ./quantower/Forecasts.csproj --configuration Release --nologo - dotnet build ./quantower/Momentum.csproj --configuration Release --nologo - dotnet build ./quantower/Oscillators.csproj --configuration Release --nologo - dotnet build ./quantower/Reversals.csproj --configuration Release --nologo - dotnet build ./quantower/Statistics.csproj --configuration Release --nologo - dotnet build ./quantower/Trends_FIR.csproj --configuration Release --nologo - dotnet build ./quantower/Trends_IIR.csproj --configuration Release --nologo - dotnet build ./quantower/Volatility.csproj --configuration Release --nologo - dotnet build ./quantower/Volume.csproj --configuration Release --nologo + - name: Build (warnings as errors) + run: dotnet build ./QuanTAlib.slnx --configuration Release --no-restore --nologo /warnaserror + + - name: Test (core) + run: dotnet test ./lib/QuanTAlib.Tests.csproj --configuration Release --no-build --nologo + + - name: Test (quantower) + run: dotnet test ./quantower/Quantower.Tests.csproj --configuration Release --no-build --nologo - name: Pack NuGet run: | diff --git a/lib/QuanTAlib.Tests.csproj b/lib/QuanTAlib.Tests.csproj index 8f529793..e351131b 100644 --- a/lib/QuanTAlib.Tests.csproj +++ b/lib/QuanTAlib.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/lib/channels/accbands/AccBands.Validation.Tests.cs b/lib/channels/accbands/AccBands.Validation.Tests.cs index 57932532..8da2a831 100644 --- a/lib/channels/accbands/AccBands.Validation.Tests.cs +++ b/lib/channels/accbands/AccBands.Validation.Tests.cs @@ -387,7 +387,7 @@ public sealed class AccBandsValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.AccbandsLookback(period); @@ -432,7 +432,7 @@ public sealed class AccBandsValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.AccbandsLookback(period); @@ -466,7 +466,7 @@ public sealed class AccBandsValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var (qMiddle, qUpper, qLower) = AccBands.Batch(_testData.Bars, period, 4.0); diff --git a/lib/channels/atrbands/AtrBands.Validation.Tests.cs b/lib/channels/atrbands/AtrBands.Validation.Tests.cs index 230e931a..b6a63aee 100644 --- a/lib/channels/atrbands/AtrBands.Validation.Tests.cs +++ b/lib/channels/atrbands/AtrBands.Validation.Tests.cs @@ -180,7 +180,7 @@ public sealed class AtrBandsValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.SmaLookback(period); @@ -215,7 +215,7 @@ public sealed class AtrBandsValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.AtrLookback(period); var (offset, _) = outRange.GetOffsetAndLength(atrOutput.Length); diff --git a/lib/channels/bbands/Bbands.Validation.Tests.cs b/lib/channels/bbands/Bbands.Validation.Tests.cs index e35fc625..50685cac 100644 --- a/lib/channels/bbands/Bbands.Validation.Tests.cs +++ b/lib/channels/bbands/Bbands.Validation.Tests.cs @@ -140,9 +140,9 @@ public sealed class BbandsValidationTests : IDisposable period, multiplier, multiplier, - Core.MAType.Sma); + TALib.Core.MAType.Sma); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.BbandsLookback(period); @@ -185,9 +185,9 @@ public sealed class BbandsValidationTests : IDisposable period, multiplier, multiplier, - Core.MAType.Sma); + TALib.Core.MAType.Sma); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.BbandsLookback(period); @@ -228,9 +228,9 @@ public sealed class BbandsValidationTests : IDisposable period, multiplier, multiplier, - Core.MAType.Sma); + TALib.Core.MAType.Sma); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.BbandsLookback(period); diff --git a/lib/channels/dchannel/Dchannel.Validation.Tests.cs b/lib/channels/dchannel/Dchannel.Validation.Tests.cs index d2b4eb90..759ba61a 100644 --- a/lib/channels/dchannel/Dchannel.Validation.Tests.cs +++ b/lib/channels/dchannel/Dchannel.Validation.Tests.cs @@ -347,4 +347,4 @@ public sealed class DchannelValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/channels/fcb/Fcb.Validation.Tests.cs b/lib/channels/fcb/Fcb.Validation.Tests.cs index 9f77a14d..2f639379 100644 --- a/lib/channels/fcb/Fcb.Validation.Tests.cs +++ b/lib/channels/fcb/Fcb.Validation.Tests.cs @@ -392,4 +392,4 @@ public sealed class FcbValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/channels/kchannel/Kchannel.Validation.Tests.cs b/lib/channels/kchannel/Kchannel.Validation.Tests.cs index 0aa6ad1e..c72eb8ce 100644 --- a/lib/channels/kchannel/Kchannel.Validation.Tests.cs +++ b/lib/channels/kchannel/Kchannel.Validation.Tests.cs @@ -556,4 +556,4 @@ public sealed class KchannelValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/channels/maenv/Maenv.Quantower.Tests.cs b/lib/channels/maenv/Maenv.Quantower.Tests.cs index 9668870a..e4a599cd 100644 --- a/lib/channels/maenv/Maenv.Quantower.Tests.cs +++ b/lib/channels/maenv/Maenv.Quantower.Tests.cs @@ -12,7 +12,7 @@ public class MaenvIndicatorTests Assert.Equal(20, ind.Period); Assert.Equal(1.0, ind.Percentage); - Assert.Equal(MaenvType.EMA, ind.MaType); + Assert.Equal(MaenvType.EMA, ind.maType); Assert.Equal(PriceType.Close, ind.SourceType); Assert.True(ind.ShowColdValues); Assert.Equal("Maenv - Moving Average Envelope", ind.Name); @@ -30,7 +30,7 @@ public class MaenvIndicatorTests [Fact] public void ShortName_ReflectsParameters() { - var ind = new MaenvIndicator { Period = 12, Percentage = 2.5, MaType = MaenvType.SMA }; + var ind = new MaenvIndicator { Period = 12, Percentage = 2.5, maType = MaenvType.SMA }; Assert.Contains("12", ind.ShortName, StringComparison.Ordinal); Assert.Contains("2.5", ind.ShortName, StringComparison.Ordinal); Assert.Contains("SMA", ind.ShortName, StringComparison.Ordinal); @@ -209,11 +209,11 @@ public class MaenvIndicatorTests } [Fact] - public void AllMaTypes_ProduceFiniteResults() + public void AllmaTypes_ProduceFiniteResults() { foreach (MaenvType maType in Enum.GetValues()) { - var ind = new MaenvIndicator { Period = 10, Percentage = 2.0, MaType = maType }; + var ind = new MaenvIndicator { Period = 10, Percentage = 2.0, maType = maType }; ind.Initialize(); var now = DateTime.UtcNow; diff --git a/lib/channels/maenv/Maenv.Tests.cs b/lib/channels/maenv/Maenv.Tests.cs index 28756994..196b85c5 100644 --- a/lib/channels/maenv/Maenv.Tests.cs +++ b/lib/channels/maenv/Maenv.Tests.cs @@ -347,7 +347,7 @@ public class MaenvTests } [Fact] - public void Maenv_AllMaTypes_ProduceFiniteResults() + public void Maenv_AllmaTypes_ProduceFiniteResults() { var gbm = new GBM(startPrice: 100, mu: 0.01, sigma: 0.1, seed: 42); diff --git a/lib/channels/maenv/Maenv.Validation.Tests.cs b/lib/channels/maenv/Maenv.Validation.Tests.cs index 374f1142..dd7bbd11 100644 --- a/lib/channels/maenv/Maenv.Validation.Tests.cs +++ b/lib/channels/maenv/Maenv.Validation.Tests.cs @@ -440,7 +440,7 @@ public sealed class MaenvValidationTests : IDisposable } [Fact] - public void Validate_MaTypesDifferent() + public void Validate_maTypesDifferent() { // Different MA types should produce different results (except for first bar) var indSma = new Maenv(10, 2.0, MaenvType.SMA); @@ -595,4 +595,4 @@ public sealed class MaenvValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/channels/regchannel/Regchannel.Validation.Tests.cs b/lib/channels/regchannel/Regchannel.Validation.Tests.cs index a00c29dd..134ee812 100644 --- a/lib/channels/regchannel/Regchannel.Validation.Tests.cs +++ b/lib/channels/regchannel/Regchannel.Validation.Tests.cs @@ -559,7 +559,7 @@ public sealed class RegchannelValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.LinearRegLookback(period); @@ -593,7 +593,7 @@ public sealed class RegchannelValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.LinearRegSlopeLookback(period); diff --git a/lib/channels/sdchannel/Sdchannel.Validation.Tests.cs b/lib/channels/sdchannel/Sdchannel.Validation.Tests.cs index 7e29e339..7d7e42e8 100644 --- a/lib/channels/sdchannel/Sdchannel.Validation.Tests.cs +++ b/lib/channels/sdchannel/Sdchannel.Validation.Tests.cs @@ -650,4 +650,4 @@ public sealed class SdchannelValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/channels/starchannel/Starchannel.Validation.Tests.cs b/lib/channels/starchannel/Starchannel.Validation.Tests.cs index 9f24f913..c523554e 100644 --- a/lib/channels/starchannel/Starchannel.Validation.Tests.cs +++ b/lib/channels/starchannel/Starchannel.Validation.Tests.cs @@ -680,4 +680,4 @@ public sealed class StarchannelValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/channels/ttm_lrc/TtmLrc.Validation.Tests.cs b/lib/channels/ttm_lrc/TtmLrc.Validation.Tests.cs index d68903a1..552ffdc6 100644 --- a/lib/channels/ttm_lrc/TtmLrc.Validation.Tests.cs +++ b/lib/channels/ttm_lrc/TtmLrc.Validation.Tests.cs @@ -671,7 +671,7 @@ public sealed class TtmLrcValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.LinearRegLookback(period); @@ -704,7 +704,7 @@ public sealed class TtmLrcValidationTests : IDisposable out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.LinearRegSlopeLookback(period); var (offset, _) = outRange.GetOffsetAndLength(slopeOutput.Length); diff --git a/lib/core/BiInputIndicatorBase.cs b/lib/core/BiInputIndicatorBase.cs index 2fac514c..9f07e624 100644 --- a/lib/core/BiInputIndicatorBase.cs +++ b/lib/core/BiInputIndicatorBase.cs @@ -19,15 +19,21 @@ public delegate void BiInputBatchDelegate( int period); /// -/// Abstract base class for bi-input indicators (indicators that require two inputs like error metrics). +/// Abstract base class for error-metric indicators that compare two input series (actual vs predicted). /// Provides common infrastructure for RingBuffer-based sliding window calculations with O(1) updates. /// /// -/// This base class eliminates code duplication across error indicators (MAE, MSE, RMSE, MAPE, etc.) -/// by providing: -/// - Common state management with bar correction (isNew semantics) -/// - RingBuffer-based sliding window with running sum -/// - Periodic resync for floating-point drift correction +/// This base class is designed specifically for error metrics (MAE, MSE, RMSE, MAPE, SMAPE, etc.) +/// where each bar contributes a single scalar error value to a running mean. +/// +/// It is NOT intended for statistical bi-input indicators (Correlation, Cointegration) which +/// maintain multiple running sums (Σx, Σy, Σx², Σy², Σxy) and have different state-restoration +/// semantics — those indicators manage their own state directly. +/// +/// Infrastructure provided: +/// - _p_state / _buffer.Snapshot() / _buffer.Restore() for bar correction (isNew semantics) +/// - RingBuffer-based sliding window with a single running sum +/// - Periodic resync every 1000 updates for floating-point drift correction /// - NaN/Infinity handling with last-valid-value substitution /// - Template Method pattern: subclasses only implement ComputeError and optionally PostProcess /// diff --git a/lib/core/avgprice/Avgprice.Validation.Tests.cs b/lib/core/avgprice/Avgprice.Validation.Tests.cs index 58df1cd9..5b451ac8 100644 --- a/lib/core/avgprice/Avgprice.Validation.Tests.cs +++ b/lib/core/avgprice/Avgprice.Validation.Tests.cs @@ -49,7 +49,7 @@ public sealed class AvgpriceValidationTests : IDisposable var taOut = new double[open.Length]; var retCode = Functions.AvgPrice(open.AsSpan(), high.AsSpan(), low.AsSpan(), close.AsSpan(), 0..^0, taOut, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var (offset, length) = outRange.GetOffsetAndLength(taOut.Length); // QuanTAlib batch span diff --git a/lib/core/medprice/Medprice.Validation.Tests.cs b/lib/core/medprice/Medprice.Validation.Tests.cs index 07778bc9..1f98a51e 100644 --- a/lib/core/medprice/Medprice.Validation.Tests.cs +++ b/lib/core/medprice/Medprice.Validation.Tests.cs @@ -46,7 +46,7 @@ public sealed class MedpriceValidationTests : IDisposable // TA-Lib MedPrice var taOut = new double[high.Length]; var retCode = Functions.MedPrice(high.AsSpan(), low.AsSpan(), 0..^0, taOut, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var (offset, length) = outRange.GetOffsetAndLength(taOut.Length); // QuanTAlib batch via TBarSeries diff --git a/lib/core/midpoint/Midpoint.Validation.Tests.cs b/lib/core/midpoint/Midpoint.Validation.Tests.cs index dddc5f65..fad8d3bf 100644 --- a/lib/core/midpoint/Midpoint.Validation.Tests.cs +++ b/lib/core/midpoint/Midpoint.Validation.Tests.cs @@ -51,7 +51,7 @@ public sealed class MidpointValidationTests : IDisposable // Calculate TA-Lib MIDPOINT var retCode = TALib.Functions.MidPoint(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MidPointLookback(period); @@ -81,7 +81,7 @@ public sealed class MidpointValidationTests : IDisposable // Calculate TA-Lib MIDPOINT var retCode = TALib.Functions.MidPoint(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MidPointLookback(period); @@ -107,7 +107,7 @@ public sealed class MidpointValidationTests : IDisposable // Calculate TA-Lib MIDPOINT var retCode = TALib.Functions.MidPoint(sourceData, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MidPointLookback(period); diff --git a/lib/core/midprice/Midprice.Validation.Tests.cs b/lib/core/midprice/Midprice.Validation.Tests.cs index e51c04df..a4230ba5 100644 --- a/lib/core/midprice/Midprice.Validation.Tests.cs +++ b/lib/core/midprice/Midprice.Validation.Tests.cs @@ -47,7 +47,7 @@ public sealed class MidpriceValidationTests : IDisposable // TA-Lib MidPrice var taOut = new double[high.Length]; var retCode = Functions.MidPrice(high.AsSpan(), low.AsSpan(), 0..^0, taOut, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var (offset, length) = outRange.GetOffsetAndLength(taOut.Length); // QuanTAlib batch span @@ -76,7 +76,7 @@ public sealed class MidpriceValidationTests : IDisposable var taOut = new double[high.Length]; var retCode = Functions.MidPrice(high.AsSpan(), low.AsSpan(), 0..^0, taOut, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var (offset, length) = outRange.GetOffsetAndLength(taOut.Length); var qlOut = new double[high.Length]; diff --git a/lib/core/ringbuffer/RingBuffer.cs b/lib/core/ringbuffer/RingBuffer.cs index 50298dae..0e85ee94 100644 --- a/lib/core/ringbuffer/RingBuffer.cs +++ b/lib/core/ringbuffer/RingBuffer.cs @@ -91,25 +91,51 @@ public sealed class RingBuffer : IEnumerable } /// - /// Recalculates the sum by iterating over all elements. + /// Recalculates the sum by iterating over all elements using SIMD acceleration. /// Useful for correcting floating-point drift after many updates. /// Uses GetSequencedSpans to avoid allocation when buffer wraps. /// public double RecalculateSum() { - double sum = 0; GetSequencedSpans(out var first, out var second); + _sum = SumSpanSimd(first) + SumSpanSimd(second); + return _sum; + } - for (int i = 0; i < first.Length; i++) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double SumSpanSimd(ReadOnlySpan span) + { + if (span.IsEmpty) { - sum += first[i]; - } - for (int i = 0; i < second.Length; i++) - { - sum += second[i]; + return 0.0; + } + + int vectorSize = Vector.Count; + var acc = Vector.Zero; + int i = 0; + + if (span.Length >= vectorSize) + { + ref double spanRef = ref MemoryMarshal.GetReference(span); + for (; i <= span.Length - vectorSize; i += vectorSize) + { + acc += Unsafe.As>(ref Unsafe.Add(ref spanRef, i)); + } + } + + // Horizontal sum of SIMD accumulator + double sum = 0.0; + for (int j = 0; j < vectorSize; j++) + { + sum += acc[j]; + } + + // Scalar tail + for (; i < span.Length; i++) + { + sum += span[i]; } - _sum = sum; return sum; } diff --git a/lib/core/tests/IndicatorProperties.Tests.cs b/lib/core/tests/IndicatorProperties.Tests.cs new file mode 100644 index 00000000..2fbcf21f --- /dev/null +++ b/lib/core/tests/IndicatorProperties.Tests.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using Xunit; +using QuanTAlib; + +namespace QuanTAlib.Tests.Core; + +/// +/// Tests verifying the structural stability of indicators across the repository. +/// +public class IndicatorPropertiesTests +{ + [Fact] + public void Sma_ShouldNotProduceNaN_WithValidInputs() + { + var sma = new Sma(period: 10); + var random = new Random(42); + + for (int i = 0; i < 100; i++) + { + double price = (random.Next(1, 1000000) / 10.0); + sma.Update(new TValue(DateTime.Today.AddDays(i), price)); + + // Only check for NaN after warmup + if (i >= sma.WarmupPeriod && double.IsNaN(sma.Last.Value)) + { + Assert.Fail($"Produced NaN at index {i}"); + } + } + } + + [Fact] + public void Ema_ShouldNotProduceNaN_WithValidInputs() + { + var ema = new Ema(period: 10); + var random = new Random(42); + + for (int i = 0; i < 100; i++) + { + double price = (random.Next(1, 1000000) / 10.0); + ema.Update(new TValue(DateTime.Today.AddDays(i), price)); + + // Only check for NaN after warmup + if (i >= ema.WarmupPeriod && double.IsNaN(ema.Last.Value)) + { + Assert.Fail($"Produced NaN at index {i}"); + } + } + } + + [Fact] + public void Indicator_ShouldRecoverFromNaN_WhenReset() + { + var ema = new Ema(period: 10); + + // Feed valid value + ema.Update(new TValue(DateTime.Today.AddDays(1), 100)); + + // Feed NaN, which should corrupt state + ema.Update(new TValue(DateTime.Today.AddDays(2), double.NaN)); + + // Let's actually ensure it is corrupted depending on implementation + // Some robust implementations might discard NaN internally, so we don't assert it strictly + // We just ensure it recovers properly. + + // Reset should clear the corrupted state + ema.Reset(); + + // Feed valid value again + ema.Update(new TValue(DateTime.Today.AddDays(3), 100)); + + // Wait for Warmup + for (int i = 4; i < 3 + ema.WarmupPeriod; i++) { + ema.Update(new TValue(DateTime.Today.AddDays(i), 100)); + } + + // Verify recovery after warmup + Assert.False(double.IsNaN(ema.Last.Value)); + Assert.Equal(100, Math.Round(ema.Last.Value, 5)); + } +} diff --git a/lib/core/typprice/Typprice.Validation.Tests.cs b/lib/core/typprice/Typprice.Validation.Tests.cs index 00893390..760c67f0 100644 --- a/lib/core/typprice/Typprice.Validation.Tests.cs +++ b/lib/core/typprice/Typprice.Validation.Tests.cs @@ -48,7 +48,7 @@ public sealed class TyppriceValidationTests : IDisposable var taOut = new double[high.Length]; var retCode = Functions.TypPrice(high.AsSpan(), low.AsSpan(), close.AsSpan(), 0..^0, taOut, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var (offset, length) = outRange.GetOffsetAndLength(taOut.Length); // QuanTAlib batch span diff --git a/lib/core/wclprice/Wclprice.Validation.Tests.cs b/lib/core/wclprice/Wclprice.Validation.Tests.cs index 24004a0a..9dbb4c62 100644 --- a/lib/core/wclprice/Wclprice.Validation.Tests.cs +++ b/lib/core/wclprice/Wclprice.Validation.Tests.cs @@ -48,7 +48,7 @@ public sealed class WclpriceValidationTests : IDisposable var taOut = new double[high.Length]; var retCode = Functions.WclPrice(high.AsSpan(), low.AsSpan(), close.AsSpan(), 0..^0, taOut, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var (offset, length) = outRange.GetOffsetAndLength(taOut.Length); // QuanTAlib batch span diff --git a/lib/cycles/ccyc/Ccyc.Validation.Tests.cs b/lib/cycles/ccyc/Ccyc.Validation.Tests.cs index eb726c0a..481ea41b 100644 --- a/lib/cycles/ccyc/Ccyc.Validation.Tests.cs +++ b/lib/cycles/ccyc/Ccyc.Validation.Tests.cs @@ -380,4 +380,4 @@ public class CcycValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/cycles/cg/Cg.Quantower.Tests.cs b/lib/cycles/cg/Cg.Quantower.Tests.cs index 05e759c7..b99b0a49 100644 --- a/lib/cycles/cg/Cg.Quantower.Tests.cs +++ b/lib/cycles/cg/Cg.Quantower.Tests.cs @@ -338,4 +338,4 @@ public class CgIndicatorTests double cgValue = indicator.LinesSeries[0].GetValue(0); Assert.True(cgValue < 0, $"Downtrend should produce negative CG, got {cgValue}"); } -} \ No newline at end of file +} diff --git a/lib/cycles/cg/Cg.Tests.cs b/lib/cycles/cg/Cg.Tests.cs index b05064e1..5b5d036e 100644 --- a/lib/cycles/cg/Cg.Tests.cs +++ b/lib/cycles/cg/Cg.Tests.cs @@ -755,4 +755,4 @@ public class CgTests } #endregion -} \ No newline at end of file +} diff --git a/lib/cycles/cg/Cg.Validation.Tests.cs b/lib/cycles/cg/Cg.Validation.Tests.cs index eaad1cfa..0eb4b70e 100644 --- a/lib/cycles/cg/Cg.Validation.Tests.cs +++ b/lib/cycles/cg/Cg.Validation.Tests.cs @@ -378,4 +378,4 @@ public class CgValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/cycles/dsp/Dsp.Quantower.Tests.cs b/lib/cycles/dsp/Dsp.Quantower.Tests.cs index c6326e36..24b2f3d4 100644 --- a/lib/cycles/dsp/Dsp.Quantower.Tests.cs +++ b/lib/cycles/dsp/Dsp.Quantower.Tests.cs @@ -339,4 +339,4 @@ public class DspIndicatorTests Assert.True(positiveCount > 0, "Should have positive DSP values"); Assert.True(negativeCount > 0, "Should have negative DSP values"); } -} \ No newline at end of file +} diff --git a/lib/cycles/dsp/Dsp.Tests.cs b/lib/cycles/dsp/Dsp.Tests.cs index 2d10ec94..e1b36fed 100644 --- a/lib/cycles/dsp/Dsp.Tests.cs +++ b/lib/cycles/dsp/Dsp.Tests.cs @@ -451,4 +451,4 @@ public class DspTests } #endregion -} \ No newline at end of file +} diff --git a/lib/cycles/dsp/Dsp.Validation.Tests.cs b/lib/cycles/dsp/Dsp.Validation.Tests.cs index 53b8b2ea..1ab16364 100644 --- a/lib/cycles/dsp/Dsp.Validation.Tests.cs +++ b/lib/cycles/dsp/Dsp.Validation.Tests.cs @@ -401,4 +401,4 @@ public class DspValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/cycles/eacp/Eacp.Quantower.Tests.cs b/lib/cycles/eacp/Eacp.Quantower.Tests.cs index 65f482aa..a20ce4d0 100644 --- a/lib/cycles/eacp/Eacp.Quantower.Tests.cs +++ b/lib/cycles/eacp/Eacp.Quantower.Tests.cs @@ -359,4 +359,4 @@ public class EacpIndicatorTests Assert.True(double.IsFinite(indicatorEnhanced.LinesSeries[0].GetValue(0))); Assert.True(double.IsFinite(indicatorNormal.LinesSeries[0].GetValue(0))); } -} \ No newline at end of file +} diff --git a/lib/cycles/eacp/Eacp.Tests.cs b/lib/cycles/eacp/Eacp.Tests.cs index 15c7c172..5b6beb04 100644 --- a/lib/cycles/eacp/Eacp.Tests.cs +++ b/lib/cycles/eacp/Eacp.Tests.cs @@ -521,4 +521,4 @@ public class EacpTests } #endregion -} \ No newline at end of file +} diff --git a/lib/cycles/eacp/Eacp.Validation.Tests.cs b/lib/cycles/eacp/Eacp.Validation.Tests.cs index e2badd10..0fb55c84 100644 --- a/lib/cycles/eacp/Eacp.Validation.Tests.cs +++ b/lib/cycles/eacp/Eacp.Validation.Tests.cs @@ -419,4 +419,4 @@ public class EacpValidationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/cycles/ebsw/Ebsw.Quantower.Tests.cs b/lib/cycles/ebsw/Ebsw.Quantower.Tests.cs index 81ccbe4d..60816ed7 100644 --- a/lib/cycles/ebsw/Ebsw.Quantower.Tests.cs +++ b/lib/cycles/ebsw/Ebsw.Quantower.Tests.cs @@ -383,4 +383,4 @@ public class EbswIndicatorTests // Should have multiple zero crossings for oscillating price Assert.True(crossings >= 3, $"Should have multiple zero crossings, got {crossings}"); } -} \ No newline at end of file +} diff --git a/lib/cycles/ebsw/Ebsw.Tests.cs b/lib/cycles/ebsw/Ebsw.Tests.cs index 451bbf91..cca57bf2 100644 --- a/lib/cycles/ebsw/Ebsw.Tests.cs +++ b/lib/cycles/ebsw/Ebsw.Tests.cs @@ -505,4 +505,4 @@ public class EbswTests } #endregion -} \ No newline at end of file +} diff --git a/lib/cycles/ebsw/Ebsw.Validation.Tests.cs b/lib/cycles/ebsw/Ebsw.Validation.Tests.cs index d3169900..656608f9 100644 --- a/lib/cycles/ebsw/Ebsw.Validation.Tests.cs +++ b/lib/cycles/ebsw/Ebsw.Validation.Tests.cs @@ -559,4 +559,4 @@ public class EbswValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/cycles/homod/Homod.Quantower.Tests.cs b/lib/cycles/homod/Homod.Quantower.Tests.cs index 8f611bec..fd601d33 100644 --- a/lib/cycles/homod/Homod.Quantower.Tests.cs +++ b/lib/cycles/homod/Homod.Quantower.Tests.cs @@ -338,4 +338,4 @@ public class HomodIndicatorTests double cycleValue = indicator.LinesSeries[0].GetValue(0); Assert.True(double.IsFinite(cycleValue)); } -} \ No newline at end of file +} diff --git a/lib/cycles/homod/Homod.Tests.cs b/lib/cycles/homod/Homod.Tests.cs index 5c55be22..77bc7a9d 100644 --- a/lib/cycles/homod/Homod.Tests.cs +++ b/lib/cycles/homod/Homod.Tests.cs @@ -482,4 +482,4 @@ public class HomodTests } #endregion -} \ No newline at end of file +} diff --git a/lib/cycles/homod/Homod.Validation.Tests.cs b/lib/cycles/homod/Homod.Validation.Tests.cs index d497af3d..43655c3f 100644 --- a/lib/cycles/homod/Homod.Validation.Tests.cs +++ b/lib/cycles/homod/Homod.Validation.Tests.cs @@ -378,4 +378,4 @@ public class HomodValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/cycles/ht_dcperiod/HtDcperiod.Validation.Tests.cs b/lib/cycles/ht_dcperiod/HtDcperiod.Validation.Tests.cs index 9f1b0b8c..ccc6a1a8 100644 --- a/lib/cycles/ht_dcperiod/HtDcperiod.Validation.Tests.cs +++ b/lib/cycles/ht_dcperiod/HtDcperiod.Validation.Tests.cs @@ -42,7 +42,7 @@ public sealed class HtDcperiodValidationTests : IDisposable var outPeriod = new double[input.Length]; var rc = TALib.Functions.HtDcPeriod(input, 0..^0, outPeriod, out var outRange); - Assert.Equal(Core.RetCode.Success, rc); + Assert.Equal(TALib.Core.RetCode.Success, rc); var q = new HtDcperiod(); var qSeries = q.Update(_data.Data); @@ -65,7 +65,7 @@ public sealed class HtDcperiodValidationTests : IDisposable var outPeriod = new double[input.Length]; var rc = TALib.Functions.HtDcPeriod(input, 0..^0, outPeriod, out var outRange); - Assert.Equal(Core.RetCode.Success, rc); + Assert.Equal(TALib.Core.RetCode.Success, rc); var streaming = new List(_data.Data.Count); var q = new HtDcperiod(); diff --git a/lib/cycles/ht_dcphase/HtDcphase.Validation.Tests.cs b/lib/cycles/ht_dcphase/HtDcphase.Validation.Tests.cs index 6d82e928..d3d56426 100644 --- a/lib/cycles/ht_dcphase/HtDcphase.Validation.Tests.cs +++ b/lib/cycles/ht_dcphase/HtDcphase.Validation.Tests.cs @@ -42,7 +42,7 @@ public sealed class HtDcphaseValidationTests : IDisposable var outPhase = new double[input.Length]; var rc = TALib.Functions.HtDcPhase(input, 0..^0, outPhase, out var outRange); - Assert.Equal(Core.RetCode.Success, rc); + Assert.Equal(TALib.Core.RetCode.Success, rc); var q = new HtDcphase(); var qSeries = q.Update(_data.Data); @@ -65,7 +65,7 @@ public sealed class HtDcphaseValidationTests : IDisposable var outPhase = new double[input.Length]; var rc = TALib.Functions.HtDcPhase(input, 0..^0, outPhase, out var outRange); - Assert.Equal(Core.RetCode.Success, rc); + Assert.Equal(TALib.Core.RetCode.Success, rc); var streaming = new List(_data.Data.Count); var q = new HtDcphase(); diff --git a/lib/cycles/ht_sine/HtSine.Quantower.Tests.cs b/lib/cycles/ht_sine/HtSine.Quantower.Tests.cs index 9a2cae80..a628ceb8 100644 --- a/lib/cycles/ht_sine/HtSine.Quantower.Tests.cs +++ b/lib/cycles/ht_sine/HtSine.Quantower.Tests.cs @@ -292,4 +292,4 @@ public class HtSineIndicatorTests Assert.Contains("github.com/mihakralj/QuanTAlib", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("HtSine.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/cycles/ht_sine/HtSine.Tests.cs b/lib/cycles/ht_sine/HtSine.Tests.cs index 0370f729..07f92ed1 100644 --- a/lib/cycles/ht_sine/HtSine.Tests.cs +++ b/lib/cycles/ht_sine/HtSine.Tests.cs @@ -487,4 +487,4 @@ public class HtSineTests } #endregion -} \ No newline at end of file +} diff --git a/lib/cycles/ht_sine/HtSine.Validation.Tests.cs b/lib/cycles/ht_sine/HtSine.Validation.Tests.cs index 41b468ff..676a7c0b 100644 --- a/lib/cycles/ht_sine/HtSine.Validation.Tests.cs +++ b/lib/cycles/ht_sine/HtSine.Validation.Tests.cs @@ -42,7 +42,7 @@ public sealed class HtSineValidationTests : IDisposable var outLeadSine = new double[input.Length]; var retCode = TALib.Functions.HtSine(input, 0..^0, outSine, outLeadSine, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); // Calculate QuanTAlib HtSine var htSine = new HtSine(); @@ -83,7 +83,7 @@ public sealed class HtSineValidationTests : IDisposable var outLeadSine = new double[input.Length]; var retCode = TALib.Functions.HtSine(input, 0..^0, outSine, outLeadSine, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); // Calculate QuanTAlib HtSine Streaming var htSine = new HtSine(); diff --git a/lib/cycles/lunar/Lunar.Quantower.Tests.cs b/lib/cycles/lunar/Lunar.Quantower.Tests.cs index d8fa0c4c..5271a7a0 100644 --- a/lib/cycles/lunar/Lunar.Quantower.Tests.cs +++ b/lib/cycles/lunar/Lunar.Quantower.Tests.cs @@ -266,4 +266,4 @@ public class LunarIndicatorTests Assert.Contains("Lunar.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/cycles/lunar/Lunar.Tests.cs b/lib/cycles/lunar/Lunar.Tests.cs index b1768131..5f226532 100644 --- a/lib/cycles/lunar/Lunar.Tests.cs +++ b/lib/cycles/lunar/Lunar.Tests.cs @@ -306,4 +306,4 @@ public class LunarTests Assert.Equal(phase1, phase2, Tolerance); } -} \ No newline at end of file +} diff --git a/lib/cycles/solar/Solar.Quantower.Tests.cs b/lib/cycles/solar/Solar.Quantower.Tests.cs index c1e4c5a5..8c862bb3 100644 --- a/lib/cycles/solar/Solar.Quantower.Tests.cs +++ b/lib/cycles/solar/Solar.Quantower.Tests.cs @@ -259,4 +259,4 @@ public class SolarIndicatorTests Assert.Contains("Solar.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/cycles/solar/Solar.Tests.cs b/lib/cycles/solar/Solar.Tests.cs index 7ede4003..e8ccad92 100644 --- a/lib/cycles/solar/Solar.Tests.cs +++ b/lib/cycles/solar/Solar.Tests.cs @@ -295,4 +295,4 @@ public class SolarTests Assert.True(cycle > 0.95, $"Expected cycle > 0.95 at 2000 summer solstice, got {cycle}"); } -} \ No newline at end of file +} diff --git a/lib/cycles/ssfdsp/Ssfdsp.Quantower.Tests.cs b/lib/cycles/ssfdsp/Ssfdsp.Quantower.Tests.cs index 59d44d4e..ca90a77b 100644 --- a/lib/cycles/ssfdsp/Ssfdsp.Quantower.Tests.cs +++ b/lib/cycles/ssfdsp/Ssfdsp.Quantower.Tests.cs @@ -269,4 +269,4 @@ public class SsfdspIndicatorTests Assert.Contains("github.com/mihakralj/QuanTAlib", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("Ssfdsp.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/cycles/ssfdsp/Ssfdsp.Tests.cs b/lib/cycles/ssfdsp/Ssfdsp.Tests.cs index b03ea65f..2503585f 100644 --- a/lib/cycles/ssfdsp/Ssfdsp.Tests.cs +++ b/lib/cycles/ssfdsp/Ssfdsp.Tests.cs @@ -500,4 +500,4 @@ public class SsfdspTests } #endregion -} \ No newline at end of file +} diff --git a/lib/cycles/ssfdsp/Ssfdsp.Validation.Tests.cs b/lib/cycles/ssfdsp/Ssfdsp.Validation.Tests.cs index e7493b48..c42f0781 100644 --- a/lib/cycles/ssfdsp/Ssfdsp.Validation.Tests.cs +++ b/lib/cycles/ssfdsp/Ssfdsp.Validation.Tests.cs @@ -352,4 +352,4 @@ public class SsfdspValidationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/dynamics/adx/Adx.Validation.Tests.cs b/lib/dynamics/adx/Adx.Validation.Tests.cs index 7bb3d6df..36fdc7af 100644 --- a/lib/dynamics/adx/Adx.Validation.Tests.cs +++ b/lib/dynamics/adx/Adx.Validation.Tests.cs @@ -56,7 +56,7 @@ public sealed class AdxValidationTests : IDisposable double[] outReal = new double[_data.Bars.Count]; var retCode = Functions.Adx(hData, lData, cData, 0..^0, outReal, out var outRange, 14); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.AdxLookback(14); ValidationHelper.VerifyData(results, outReal, outRange, lookback); diff --git a/lib/dynamics/adxr/Adxr.Validation.Tests.cs b/lib/dynamics/adxr/Adxr.Validation.Tests.cs index d60dba56..b5a4cf76 100644 --- a/lib/dynamics/adxr/Adxr.Validation.Tests.cs +++ b/lib/dynamics/adxr/Adxr.Validation.Tests.cs @@ -35,7 +35,7 @@ public sealed class AdxrValidationTests : IDisposable double[] outReal = new double[_data.Bars.Count]; var retCode = Functions.Adxr(hData, lData, cData, 0..^0, outReal, out var outRange, 14); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.AdxrLookback(14); ValidationHelper.VerifyData(results, outReal, outRange, lookback); diff --git a/lib/dynamics/alligator/Alligator.Validation.Tests.cs b/lib/dynamics/alligator/Alligator.Validation.Tests.cs index 99237b96..5515827d 100644 --- a/lib/dynamics/alligator/Alligator.Validation.Tests.cs +++ b/lib/dynamics/alligator/Alligator.Validation.Tests.cs @@ -164,4 +164,4 @@ public sealed class AlligatorValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/dynamics/amat/Amat.Validation.Tests.cs b/lib/dynamics/amat/Amat.Validation.Tests.cs index 2f956b92..9ffc68ea 100644 --- a/lib/dynamics/amat/Amat.Validation.Tests.cs +++ b/lib/dynamics/amat/Amat.Validation.Tests.cs @@ -126,7 +126,7 @@ public sealed class AmatValidationTests : IDisposable // Calculate TA-Lib EMA (fast period) var retCode = TALib.Functions.Ema(tData, 0..^0, outEma, out var outRange, fastPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.EmaLookback(fastPeriod); @@ -161,7 +161,7 @@ public sealed class AmatValidationTests : IDisposable // Calculate TA-Lib EMA (slow period) var retCode = TALib.Functions.Ema(tData, 0..^0, outEma, out var outRange, slowPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.EmaLookback(slowPeriod); diff --git a/lib/dynamics/aroon/Aroon.Validation.Tests.cs b/lib/dynamics/aroon/Aroon.Validation.Tests.cs index c113d5fb..d7ad2b9f 100644 --- a/lib/dynamics/aroon/Aroon.Validation.Tests.cs +++ b/lib/dynamics/aroon/Aroon.Validation.Tests.cs @@ -70,11 +70,11 @@ public sealed class AroonValidationTests : IDisposable // TA-Lib Aroon (Up/Down) var retCode = TALib.Functions.Aroon(hData, lData, 0..^0, outAroonDown, outAroonUp, out var outRange, 14); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); // TA-Lib AroonOsc var retCodeOsc = TALib.Functions.AroonOsc(hData, lData, 0..^0, outAroonOsc, out var outRangeOsc, 14); - Assert.Equal(Core.RetCode.Success, retCodeOsc); + Assert.Equal(TALib.Core.RetCode.Success, retCodeOsc); int lookback = TALib.Functions.AroonLookback(14); diff --git a/lib/dynamics/aroonosc/AroonOsc.Validation.Tests.cs b/lib/dynamics/aroonosc/AroonOsc.Validation.Tests.cs index bda99970..47208a42 100644 --- a/lib/dynamics/aroonosc/AroonOsc.Validation.Tests.cs +++ b/lib/dynamics/aroonosc/AroonOsc.Validation.Tests.cs @@ -54,7 +54,7 @@ public sealed class AroonOscValidationTests : IDisposable // TA-Lib AroonOsc var retCodeOsc = TALib.Functions.AroonOsc(hData, lData, 0..^0, outAroonOsc, out var outRangeOsc, 14); - Assert.Equal(Core.RetCode.Success, retCodeOsc); + Assert.Equal(TALib.Core.RetCode.Success, retCodeOsc); int lookback = TALib.Functions.AroonLookback(14); diff --git a/lib/dynamics/dx/Dx.Validation.Tests.cs b/lib/dynamics/dx/Dx.Validation.Tests.cs index 796fd006..40711525 100644 --- a/lib/dynamics/dx/Dx.Validation.Tests.cs +++ b/lib/dynamics/dx/Dx.Validation.Tests.cs @@ -72,7 +72,7 @@ public sealed class DxValidationTests : IDisposable double[] outReal = new double[_data.Bars.Count]; var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.PlusDILookback(14); ValidationHelper.VerifyData(diPlusResults, outReal, outRange, lookback); @@ -96,7 +96,7 @@ public sealed class DxValidationTests : IDisposable double[] outReal = new double[_data.Bars.Count]; var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.MinusDILookback(14); ValidationHelper.VerifyData(diMinusResults, outReal, outRange, lookback); diff --git a/lib/dynamics/ht_trendmode/HtTrendmode.Validation.Tests.cs b/lib/dynamics/ht_trendmode/HtTrendmode.Validation.Tests.cs index fd8557ae..cb83ae72 100644 --- a/lib/dynamics/ht_trendmode/HtTrendmode.Validation.Tests.cs +++ b/lib/dynamics/ht_trendmode/HtTrendmode.Validation.Tests.cs @@ -162,7 +162,7 @@ public sealed class HtTrendmodeValidationTests : IDisposable int[] outInteger = new int[inReal.Length]; var retCode = Functions.HtTrendMode(inReal, 0..^0, outInteger, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); // Compare after warmup int lookback = Functions.HtTrendModeLookback(); diff --git a/lib/dynamics/qstick/Qstick.Quantower.Tests.cs b/lib/dynamics/qstick/Qstick.Quantower.Tests.cs index af6524ab..fc02e996 100644 --- a/lib/dynamics/qstick/Qstick.Quantower.Tests.cs +++ b/lib/dynamics/qstick/Qstick.Quantower.Tests.cs @@ -52,10 +52,10 @@ public class QstickIndicatorTests } [Fact] - public void DefaultMaType_IsSMA() + public void DefaultTALib.Core.MAType_IsSMA() { var indicator = new QstickIndicator(); - Assert.Equal("SMA", indicator.MaType); + Assert.Equal("SMA", indicator.TALib.Core.MAType); } [Fact] @@ -68,7 +68,7 @@ public class QstickIndicatorTests // ── ShortName ─────────────────────────────────────────────────────── [Fact] - public void ShortName_DefaultParameters_IncludesPeriodAndMaType() + public void ShortName_DefaultParameters_IncludesPeriodAndTALib.Core.MAType() { var indicator = new QstickIndicator(); Assert.Equal("QSTICK(14,SMA)", indicator.ShortName); @@ -84,7 +84,7 @@ public class QstickIndicatorTests [Fact] public void ShortName_EmaMode_IncludesEMA() { - var indicator = new QstickIndicator { Period = 20, MaType = "EMA" }; + var indicator = new QstickIndicator { Period = 20, TALib.Core.MAType = "EMA" }; Assert.Equal("QSTICK(20,EMA)", indicator.ShortName); } @@ -109,7 +109,7 @@ public class QstickIndicatorTests [Fact] public void Initialize_SmaMode_CreatesInternalIndicator() { - var indicator = new QstickIndicator { MaType = "SMA" }; + var indicator = new QstickIndicator { TALib.Core.MAType = "SMA" }; indicator.Initialize(); Assert.NotNull(indicator); @@ -118,7 +118,7 @@ public class QstickIndicatorTests [Fact] public void Initialize_EmaMode_CreatesInternalIndicator() { - var indicator = new QstickIndicator { MaType = "EMA" }; + var indicator = new QstickIndicator { TALib.Core.MAType = "EMA" }; indicator.Initialize(); Assert.NotNull(indicator); @@ -139,7 +139,7 @@ public class QstickIndicatorTests [Fact] public void ProcessUpdate_HistoricalBar_ComputesValue() { - var indicator = new QstickIndicator { Period = 3, MaType = "SMA" }; + var indicator = new QstickIndicator { Period = 3, TALib.Core.MAType = "SMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -156,7 +156,7 @@ public class QstickIndicatorTests [Fact] public void ProcessUpdate_HistoricalBar_BullishBars_PositiveValue() { - var indicator = new QstickIndicator { Period = 3, MaType = "SMA" }; + var indicator = new QstickIndicator { Period = 3, TALib.Core.MAType = "SMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -175,7 +175,7 @@ public class QstickIndicatorTests [Fact] public void ProcessUpdate_NewBar_ComputesValue() { - var indicator = new QstickIndicator { Period = 3, MaType = "SMA" }; + var indicator = new QstickIndicator { Period = 3, TALib.Core.MAType = "SMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -193,7 +193,7 @@ public class QstickIndicatorTests [Fact] public void ProcessUpdate_NewTick_ProcessesWithoutError() { - var indicator = new QstickIndicator { Period = 3, MaType = "SMA" }; + var indicator = new QstickIndicator { Period = 3, TALib.Core.MAType = "SMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -214,7 +214,7 @@ public class QstickIndicatorTests [Fact] public void SmaMode_BearishBars_ProducesNegativeQstick() { - var indicator = new QstickIndicator { Period = 5, MaType = "SMA" }; + var indicator = new QstickIndicator { Period = 5, TALib.Core.MAType = "SMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -231,7 +231,7 @@ public class QstickIndicatorTests [Fact] public void SmaMode_DojiBars_ProducesZeroQstick() { - var indicator = new QstickIndicator { Period = 5, MaType = "SMA" }; + var indicator = new QstickIndicator { Period = 5, TALib.Core.MAType = "SMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -247,7 +247,7 @@ public class QstickIndicatorTests [Fact] public void EmaMode_BullishBars_ProducesPositiveQstick() { - var indicator = new QstickIndicator { Period = 5, MaType = "EMA" }; + var indicator = new QstickIndicator { Period = 5, TALib.Core.MAType = "EMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -663,8 +663,8 @@ public class QstickIndicatorTests [Fact] public void DifferentPeriods_ProduceDifferentResults() { - var indicator1 = new QstickIndicator { Period = 3, MaType = "SMA" }; - var indicator2 = new QstickIndicator { Period = 10, MaType = "SMA" }; + var indicator1 = new QstickIndicator { Period = 3, TALib.Core.MAType = "SMA" }; + var indicator2 = new QstickIndicator { Period = 10, TALib.Core.MAType = "SMA" }; indicator1.Initialize(); indicator2.Initialize(); @@ -687,8 +687,8 @@ public class QstickIndicatorTests [Fact] public void SmaVsEma_SameData_ProduceDifferentResults() { - var smaIndicator = new QstickIndicator { Period = 5, MaType = "SMA" }; - var emaIndicator = new QstickIndicator { Period = 5, MaType = "EMA" }; + var smaIndicator = new QstickIndicator { Period = 5, TALib.Core.MAType = "SMA" }; + var emaIndicator = new QstickIndicator { Period = 5, TALib.Core.MAType = "EMA" }; smaIndicator.Initialize(); emaIndicator.Initialize(); @@ -715,7 +715,7 @@ public class QstickIndicatorTests [Fact] public void Reinitialize_WithDifferentParameters_ResetsState() { - var indicator = new QstickIndicator { Period = 5, MaType = "SMA" }; + var indicator = new QstickIndicator { Period = 5, TALib.Core.MAType = "SMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -726,7 +726,7 @@ public class QstickIndicatorTests } indicator.Period = 10; - indicator.MaType = "EMA"; + indicator.TALib.Core.MAType = "EMA"; indicator.Initialize(); Assert.Equal("QSTICK(10,EMA)", indicator.ShortName); @@ -762,7 +762,7 @@ public class QstickIndicatorTests [Fact] public void MultipleBars_ThroughAdapter_ProducesExpectedValues() { - var indicator = new QstickIndicator { Period = 3, MaType = "SMA" }; + var indicator = new QstickIndicator { Period = 3, TALib.Core.MAType = "SMA" }; indicator.Initialize(); var now = DateTime.UtcNow; @@ -795,12 +795,12 @@ public class QstickIndicatorTests } [Fact] - public void MaType_CanBeChanged() + public void TALib.Core.MAType_CanBeChanged() { var indicator = new QstickIndicator(); - Assert.Equal("SMA", indicator.MaType); + Assert.Equal("SMA", indicator.TALib.Core.MAType); - indicator.MaType = "EMA"; - Assert.Equal("EMA", indicator.MaType); + indicator.TALib.Core.MAType = "EMA"; + Assert.Equal("EMA", indicator.TALib.Core.MAType); } } diff --git a/lib/errors/mdape/Mdape.Tests.cs b/lib/errors/mdape/Mdape.Tests.cs index 4f9228cf..51b5675e 100644 --- a/lib/errors/mdape/Mdape.Tests.cs +++ b/lib/errors/mdape/Mdape.Tests.cs @@ -362,4 +362,4 @@ public class MdapeTests Assert.Equal(mdape1.Last.Value, mdape2.Last.Value, Precision); } -} \ No newline at end of file +} diff --git a/lib/filters/edcf/Edcf.cs b/lib/filters/edcf/Edcf.cs index 08dea7bd..203a8fbb 100644 --- a/lib/filters/edcf/Edcf.cs +++ b/lib/filters/edcf/Edcf.cs @@ -33,6 +33,9 @@ public sealed class Edcf : AbstractBase private State _s; private State _ps; + /// + /// Initializes an Ehlers Distance Coefficient Filter. + /// /// Filter window length (≥ 2). Default: 15. public Edcf(int length = 15) { @@ -51,6 +54,9 @@ public sealed class Edcf : AbstractBase Name = $"Edcf({_length})"; } + /// + /// Initializes an EDCF indicator and subscribes it to a source publisher. + /// /// Input data source for event-based chaining. /// Filter window length (≥ 2). Default: 15. public Edcf(ITValuePublisher source, int length = 15) : this(length) diff --git a/lib/filters/gauss/Gauss.Validation.Tests.cs b/lib/filters/gauss/Gauss.Validation.Tests.cs index f1b591d7..8135c528 100644 --- a/lib/filters/gauss/Gauss.Validation.Tests.cs +++ b/lib/filters/gauss/Gauss.Validation.Tests.cs @@ -191,4 +191,4 @@ public class GaussValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/filters/hpf/Hpf.Validation.Tests.cs b/lib/filters/hpf/Hpf.Validation.Tests.cs index 754a884f..77c28580 100644 --- a/lib/filters/hpf/Hpf.Validation.Tests.cs +++ b/lib/filters/hpf/Hpf.Validation.Tests.cs @@ -154,4 +154,4 @@ public class HpfValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/momentum/bias/Bias.Quantower.Tests.cs b/lib/momentum/bias/Bias.Quantower.Tests.cs index 42d02280..f4bc26ef 100644 --- a/lib/momentum/bias/Bias.Quantower.Tests.cs +++ b/lib/momentum/bias/Bias.Quantower.Tests.cs @@ -241,4 +241,4 @@ public class BiasIndicatorTests indicator.Period = 100; Assert.Equal(100, indicator.Period); } -} \ No newline at end of file +} diff --git a/lib/momentum/bias/Bias.Tests.cs b/lib/momentum/bias/Bias.Tests.cs index d0cbf551..04b70eb8 100644 --- a/lib/momentum/bias/Bias.Tests.cs +++ b/lib/momentum/bias/Bias.Tests.cs @@ -591,4 +591,4 @@ public class BiasTests // SMA = 0, Bias = (0-0)/0 = 0/0 -> should return 0 to avoid NaN Assert.Equal(0.0, bias.Last.Value, 1e-10); } -} \ No newline at end of file +} diff --git a/lib/momentum/bias/Bias.Validation.Tests.cs b/lib/momentum/bias/Bias.Validation.Tests.cs index 71b01735..357e96aa 100644 --- a/lib/momentum/bias/Bias.Validation.Tests.cs +++ b/lib/momentum/bias/Bias.Validation.Tests.cs @@ -433,4 +433,4 @@ public sealed class BiasValidationTests : IDisposable _output.WriteLine($"Oscillating sequence: avg bias = {avgBias:F6}"); } -} \ No newline at end of file +} diff --git a/lib/momentum/bop/Bop.Validation.Tests.cs b/lib/momentum/bop/Bop.Validation.Tests.cs index 771b006e..82585365 100644 --- a/lib/momentum/bop/Bop.Validation.Tests.cs +++ b/lib/momentum/bop/Bop.Validation.Tests.cs @@ -39,7 +39,7 @@ public sealed class BopValidationTests : IDisposable var talibResult = new double[_data.Bars.Count]; var retCode = TALib.Functions.Bop(open, high, low, close, 0..^0, talibResult, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var quanTAlibResult = Bop.Batch(_data.Bars); diff --git a/lib/momentum/cci/Cci.Validation.Tests.cs b/lib/momentum/cci/Cci.Validation.Tests.cs index eb347bed..49ad13df 100644 --- a/lib/momentum/cci/Cci.Validation.Tests.cs +++ b/lib/momentum/cci/Cci.Validation.Tests.cs @@ -47,7 +47,7 @@ public sealed class CciValidationTests(ITestOutputHelper output) : IDisposable // TALib CCI double[] tOutput = new double[high.Length]; var retCode = TALib.Functions.Cci(high, low, close, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.CciLookback(TestPeriod); @@ -86,7 +86,7 @@ public sealed class CciValidationTests(ITestOutputHelper output) : IDisposable // TALib CCI double[] tOutput = new double[high.Length]; var retCode = TALib.Functions.Cci(high, low, close, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.CciLookback(TestPeriod); @@ -123,7 +123,7 @@ public sealed class CciValidationTests(ITestOutputHelper output) : IDisposable double[] tOutput = new double[high.Length]; var retCode = TALib.Functions.Cci(high, low, close, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.CciLookback(period); diff --git a/lib/momentum/cmo/Cmo.Validation.Tests.cs b/lib/momentum/cmo/Cmo.Validation.Tests.cs index c4d8bfbf..aa877329 100644 --- a/lib/momentum/cmo/Cmo.Validation.Tests.cs +++ b/lib/momentum/cmo/Cmo.Validation.Tests.cs @@ -280,7 +280,7 @@ public sealed class CmoValidationTests(ITestOutputHelper output) : IDisposable // --- TALib CMO --- double[] taOut = new double[tData.Length]; var retCode = Functions.Cmo(tData, 0..^0, taOut, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); var (taOffset, taLength) = outRange.GetOffsetAndLength(taOut.Length); Assert.True(taLength > 0, "TALib produced no output"); diff --git a/lib/momentum/macd/Macd.Validation.Tests.cs b/lib/momentum/macd/Macd.Validation.Tests.cs index b9efc5ea..04c86da0 100644 --- a/lib/momentum/macd/Macd.Validation.Tests.cs +++ b/lib/momentum/macd/Macd.Validation.Tests.cs @@ -127,7 +127,7 @@ public sealed class MacdValidationTests : IDisposable // Calculate TA-Lib MACD var retCode = TALib.Functions.Macd(tData, 0..^0, outMacd, outSignal, outHist, out var outRange, fastPeriod, slowPeriod, signalPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MacdLookback(fastPeriod, slowPeriod, signalPeriod); diff --git a/lib/momentum/mom/Mom.Validation.Tests.cs b/lib/momentum/mom/Mom.Validation.Tests.cs index 657e923d..c1d78cf8 100644 --- a/lib/momentum/mom/Mom.Validation.Tests.cs +++ b/lib/momentum/mom/Mom.Validation.Tests.cs @@ -48,7 +48,7 @@ public sealed class MomValidationTests(ITestOutputHelper output) : IDisposable // TALib Mom double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.Mom(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MomLookback(TestPeriod); @@ -81,7 +81,7 @@ public sealed class MomValidationTests(ITestOutputHelper output) : IDisposable // TALib Mom double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.Mom(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MomLookback(TestPeriod); @@ -106,7 +106,7 @@ public sealed class MomValidationTests(ITestOutputHelper output) : IDisposable // TALib Mom double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.Mom(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MomLookback(TestPeriod); @@ -129,7 +129,7 @@ public sealed class MomValidationTests(ITestOutputHelper output) : IDisposable double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.Mom(tData, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MomLookback(period); @@ -413,4 +413,4 @@ public sealed class MomValidationTests(ITestOutputHelper output) : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/momentum/ppo/Ppo.Validation.Tests.cs b/lib/momentum/ppo/Ppo.Validation.Tests.cs index e5c03522..6ec3f394 100644 --- a/lib/momentum/ppo/Ppo.Validation.Tests.cs +++ b/lib/momentum/ppo/Ppo.Validation.Tests.cs @@ -126,11 +126,11 @@ public sealed class PpoValidationTests(ITestOutputHelper output) : IDisposable qPpo.Add(ppo.Last.Value); } - // TA-Lib PPO (must specify MAType.Ema — default is SMA which differs from our EMA-based PPO) - var retCode = TALib.Functions.Ppo(tData, 0..^0, outPpo, out var outRange, fastPeriod, slowPeriod, Core.MAType.Ema); - Assert.Equal(Core.RetCode.Success, retCode); + // TA-Lib PPO (must specify TALib.Core.MAType.Ema — default is SMA which differs from our EMA-based PPO) + var retCode = TALib.Functions.Ppo(tData, 0..^0, outPpo, out var outRange, fastPeriod, slowPeriod, TALib.Core.MAType.Ema); + Assert.Equal(TALib.Core.RetCode.Success, retCode); - int lookback = TALib.Functions.PpoLookback(fastPeriod, slowPeriod, Core.MAType.Ema); + int lookback = TALib.Functions.PpoLookback(fastPeriod, slowPeriod, TALib.Core.MAType.Ema); // Compare ValidationHelper.VerifyData(qPpo, outPpo, outRange, lookback); diff --git a/lib/momentum/roc/Roc.Validation.Tests.cs b/lib/momentum/roc/Roc.Validation.Tests.cs index bee80ac0..98a01ba1 100644 --- a/lib/momentum/roc/Roc.Validation.Tests.cs +++ b/lib/momentum/roc/Roc.Validation.Tests.cs @@ -309,7 +309,7 @@ public sealed class RocValidationTests(ITestOutputHelper output) : IDisposable // TALib MOM (absolute momentum = price - prevPrice) double[] taOut = new double[tData.Length]; var retCode = Functions.Mom(tData, 0..^0, taOut, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.MomLookback(TestPeriod); ValidationHelper.VerifyData(qOutput, taOut, outRange, lookback); @@ -333,7 +333,7 @@ public sealed class RocValidationTests(ITestOutputHelper output) : IDisposable // TALib MOM double[] taOut = new double[tData.Length]; var retCode = Functions.Mom(tData, 0..^0, taOut, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.MomLookback(TestPeriod); ValidationHelper.VerifyData(qResults, taOut, outRange, lookback); @@ -355,7 +355,7 @@ public sealed class RocValidationTests(ITestOutputHelper output) : IDisposable double[] taOut = new double[tData.Length]; var retCode = Functions.Mom(tData, 0..^0, taOut, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.MomLookback(period); ValidationHelper.VerifyData(qOutput, taOut, outRange, lookback); diff --git a/lib/momentum/rocp/Rocp.Validation.Tests.cs b/lib/momentum/rocp/Rocp.Validation.Tests.cs index b250523b..9664f2c3 100644 --- a/lib/momentum/rocp/Rocp.Validation.Tests.cs +++ b/lib/momentum/rocp/Rocp.Validation.Tests.cs @@ -52,7 +52,7 @@ public sealed class RocpValidationTests(ITestOutputHelper output) : IDisposable // TALib RocP (returns decimal fraction) double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.RocP(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RocPLookback(TestPeriod); @@ -93,7 +93,7 @@ public sealed class RocpValidationTests(ITestOutputHelper output) : IDisposable // TALib RocP double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.RocP(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RocPLookback(TestPeriod); @@ -137,7 +137,7 @@ public sealed class RocpValidationTests(ITestOutputHelper output) : IDisposable // TALib RocP double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.RocP(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RocPLookback(TestPeriod); @@ -179,7 +179,7 @@ public sealed class RocpValidationTests(ITestOutputHelper output) : IDisposable double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.RocP(tData, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RocPLookback(period); var (offset, length) = outRange.GetOffsetAndLength(tOutput.Length); diff --git a/lib/momentum/rocr/Rocr.Validation.Tests.cs b/lib/momentum/rocr/Rocr.Validation.Tests.cs index 32dce1a7..b7ab6e2b 100644 --- a/lib/momentum/rocr/Rocr.Validation.Tests.cs +++ b/lib/momentum/rocr/Rocr.Validation.Tests.cs @@ -45,7 +45,7 @@ public sealed class RocrValidationTests(ITestOutputHelper output) : IDisposable // TALib RocR double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.RocR(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RocRLookback(TestPeriod); @@ -78,7 +78,7 @@ public sealed class RocrValidationTests(ITestOutputHelper output) : IDisposable // TALib RocR double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.RocR(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RocRLookback(TestPeriod); @@ -115,7 +115,7 @@ public sealed class RocrValidationTests(ITestOutputHelper output) : IDisposable // TALib RocR double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.RocR(tData, 0..^0, tOutput, out var outRange, TestPeriod); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RocRLookback(TestPeriod); @@ -150,7 +150,7 @@ public sealed class RocrValidationTests(ITestOutputHelper output) : IDisposable double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.RocR(tData, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RocRLookback(period); var (offset, length) = outRange.GetOffsetAndLength(tOutput.Length); diff --git a/lib/momentum/rsi/Rsi.Validation.Tests.cs b/lib/momentum/rsi/Rsi.Validation.Tests.cs index 693d74b8..029b2829 100644 --- a/lib/momentum/rsi/Rsi.Validation.Tests.cs +++ b/lib/momentum/rsi/Rsi.Validation.Tests.cs @@ -65,7 +65,7 @@ public sealed class RsiValidationTests(ITestOutputHelper output) : IDisposable double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.Rsi(tData, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RsiLookback(period); @@ -143,7 +143,7 @@ public sealed class RsiValidationTests(ITestOutputHelper output) : IDisposable // Calculate TA-Lib RSI var retCode = TALib.Functions.Rsi(tData, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.RsiLookback(period); diff --git a/lib/momentum/rsx/Rsx.Validation.Tests.cs b/lib/momentum/rsx/Rsx.Validation.Tests.cs index 75da3c80..eba80a3d 100644 --- a/lib/momentum/rsx/Rsx.Validation.Tests.cs +++ b/lib/momentum/rsx/Rsx.Validation.Tests.cs @@ -142,4 +142,4 @@ public class RsxValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/numerics/highest/Highest.Validation.Tests.cs b/lib/numerics/highest/Highest.Validation.Tests.cs index 592e0622..11d3aafc 100644 --- a/lib/numerics/highest/Highest.Validation.Tests.cs +++ b/lib/numerics/highest/Highest.Validation.Tests.cs @@ -51,7 +51,7 @@ public sealed class HighestValidationTests : IDisposable // Calculate TA-Lib MAX var retCode = TALib.Functions.Max(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MaxLookback(period); @@ -81,7 +81,7 @@ public sealed class HighestValidationTests : IDisposable // Calculate TA-Lib MAX var retCode = TALib.Functions.Max(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MaxLookback(period); @@ -107,7 +107,7 @@ public sealed class HighestValidationTests : IDisposable // Calculate TA-Lib MAX var retCode = TALib.Functions.Max(sourceData, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MaxLookback(period); diff --git a/lib/numerics/lowest/Lowest.Validation.Tests.cs b/lib/numerics/lowest/Lowest.Validation.Tests.cs index 8ad66bce..bb13a0bc 100644 --- a/lib/numerics/lowest/Lowest.Validation.Tests.cs +++ b/lib/numerics/lowest/Lowest.Validation.Tests.cs @@ -51,7 +51,7 @@ public sealed class LowestValidationTests : IDisposable // Calculate TA-Lib MIN var retCode = TALib.Functions.Min(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MinLookback(period); @@ -81,7 +81,7 @@ public sealed class LowestValidationTests : IDisposable // Calculate TA-Lib MIN var retCode = TALib.Functions.Min(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MinLookback(period); @@ -107,7 +107,7 @@ public sealed class LowestValidationTests : IDisposable // Calculate TA-Lib MIN var retCode = TALib.Functions.Min(sourceData, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.MinLookback(period); diff --git a/lib/oscillators/ac/Ac.Validation.Tests.cs b/lib/oscillators/ac/Ac.Validation.Tests.cs index b58db3e1..b72b50bb 100644 --- a/lib/oscillators/ac/Ac.Validation.Tests.cs +++ b/lib/oscillators/ac/Ac.Validation.Tests.cs @@ -197,4 +197,4 @@ public sealed class AcValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/oscillators/ac/Ac.cs b/lib/oscillators/ac/Ac.cs index 3e004f6d..0cd219d7 100644 --- a/lib/oscillators/ac/Ac.cs +++ b/lib/oscillators/ac/Ac.cs @@ -15,6 +15,13 @@ namespace QuanTAlib; /// AO = SMA(Median Price, fastPeriod) - SMA(Median Price, slowPeriod) /// AC = AO - SMA(AO, acPeriod) /// +/// Design note: Ac implements directly rather than inheriting +/// from AbstractBase. This is intentional: Ac is an OHLC-based indicator whose primary input +/// is a (requiring High and Low), not a single . +/// AbstractBase's contract (Update(TValue), Prime(ReadOnlySpan<double>)) does not fit +/// OHLC indicators. The practical entry points are Update(TBar) and Prime(TBarSeries). +/// If a future TBarIndicatorBase is introduced, Ac would be a candidate to migrate. +/// /// Sources: /// https://www.investopedia.com/terms/a/accelerationdeceleration-indicator.asp /// https://www.tradingview.com/support/solutions/43000501837-accelerator-oscillator-ac/ diff --git a/lib/oscillators/ao/Ao.cs b/lib/oscillators/ao/Ao.cs index 9a35183b..1354dafc 100644 --- a/lib/oscillators/ao/Ao.cs +++ b/lib/oscillators/ao/Ao.cs @@ -16,6 +16,13 @@ namespace QuanTAlib; /// Median Price = (High + Low) / 2 /// AO = SMA(Median Price, 5) - SMA(Median Price, 34) /// +/// Design note: Ao implements directly rather than inheriting +/// from AbstractBase. This is intentional: Ao is an OHLC-based indicator whose primary input +/// is a (requiring High and Low), not a single . +/// AbstractBase's contract (Update(TValue), Prime(ReadOnlySpan<double>)) does not fit +/// OHLC indicators. The practical entry points are Update(TBar) and Prime(TBarSeries). +/// If a future TBarIndicatorBase is introduced, Ao would be a candidate to migrate. +/// /// Sources: /// https://www.investopedia.com/terms/a/awesomeoscillator.asp /// https://www.tradingview.com/support/solutions/43000501826-awesome-oscillator-ao/ diff --git a/lib/oscillators/apo/Apo.Validation.Tests.cs b/lib/oscillators/apo/Apo.Validation.Tests.cs index 73836718..83dccce8 100644 --- a/lib/oscillators/apo/Apo.Validation.Tests.cs +++ b/lib/oscillators/apo/Apo.Validation.Tests.cs @@ -43,8 +43,8 @@ public sealed class ApoValidationTests : IDisposable double[] input = _testData.Data.Values.ToArray(); double[] output = new double[input.Length]; - // TA-Lib APO: double[] inReal, int optInFastPeriod, int optInSlowPeriod, int optInMAType - // MAType 1 = EMA + // TA-Lib APO: double[] inReal, int optInFastPeriod, int optInSlowPeriod, int optInTALib.Core.MAType + // TALib.Core.MAType 1 = EMA var retCode = TALib.Functions.Apo(input, 0..^0, output, out var outRange, fastPeriod, slowPeriod, TALib.Core.MAType.Ema); Assert.Equal(TALib.Core.RetCode.Success, retCode); diff --git a/lib/oscillators/dem/Dem.Validation.Tests.cs b/lib/oscillators/dem/Dem.Validation.Tests.cs index fdf20aed..880166de 100644 --- a/lib/oscillators/dem/Dem.Validation.Tests.cs +++ b/lib/oscillators/dem/Dem.Validation.Tests.cs @@ -308,4 +308,4 @@ public sealed class DemValidationTests(ITestOutputHelper output) int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/oscillators/dymoi/Dymoi.Validation.Tests.cs b/lib/oscillators/dymoi/Dymoi.Validation.Tests.cs index a8690c69..3b8eec85 100644 --- a/lib/oscillators/dymoi/Dymoi.Validation.Tests.cs +++ b/lib/oscillators/dymoi/Dymoi.Validation.Tests.cs @@ -243,4 +243,4 @@ public sealed class DymoiValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/oscillators/inertia/Inertia.Validation.Tests.cs b/lib/oscillators/inertia/Inertia.Validation.Tests.cs index d3c1e3f2..71b4499e 100644 --- a/lib/oscillators/inertia/Inertia.Validation.Tests.cs +++ b/lib/oscillators/inertia/Inertia.Validation.Tests.cs @@ -226,4 +226,4 @@ public sealed class InertiaValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/oscillators/lrsi/Lrsi.Validation.Tests.cs b/lib/oscillators/lrsi/Lrsi.Validation.Tests.cs index ae621dec..0cb63da0 100644 --- a/lib/oscillators/lrsi/Lrsi.Validation.Tests.cs +++ b/lib/oscillators/lrsi/Lrsi.Validation.Tests.cs @@ -350,4 +350,4 @@ public sealed class LrsiValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/oscillators/pgo/Pgo.Validation.Tests.cs b/lib/oscillators/pgo/Pgo.Validation.Tests.cs index 4aedc46b..d3a967c1 100644 --- a/lib/oscillators/pgo/Pgo.Validation.Tests.cs +++ b/lib/oscillators/pgo/Pgo.Validation.Tests.cs @@ -242,4 +242,4 @@ public sealed class PgoValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/oscillators/smi/Smi.Validation.Tests.cs b/lib/oscillators/smi/Smi.Validation.Tests.cs index 175bcfac..5b713995 100644 --- a/lib/oscillators/smi/Smi.Validation.Tests.cs +++ b/lib/oscillators/smi/Smi.Validation.Tests.cs @@ -178,4 +178,4 @@ public sealed class SmiValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/oscillators/stoch/Stoch.Validation.Tests.cs b/lib/oscillators/stoch/Stoch.Validation.Tests.cs index ac6c5a03..cd35878a 100644 --- a/lib/oscillators/stoch/Stoch.Validation.Tests.cs +++ b/lib/oscillators/stoch/Stoch.Validation.Tests.cs @@ -258,10 +258,10 @@ public sealed class StochValidationTests : IDisposable // --- I) TALib cross-validation --- /// - /// TALib Stoch(fastKPeriod=14, slowKPeriod=1, slowKMAType=SMA, slowDPeriod=3, slowDMAType=SMA) + /// TALib Stoch(fastKPeriod=14, slowKPeriod=1, slowKTALib.Core.MAType=SMA, slowDPeriod=3, slowDTALib.Core.MAType=SMA) /// with slowKPeriod=1 (no K smoothing) produces raw %K == our K output. /// slowD with SMA(3) matches our D output. - /// Note: TALib Stoch uses SMA for both K and D smoothing (MAType=SMA). + /// Note: TALib Stoch uses SMA for both K and D smoothing (TALib.Core.MAType=SMA). /// QuanTAlib Stoch also uses SMA. With slowKPeriod=1 (identity) the K lines match directly. /// [Fact] diff --git a/lib/oscillators/trix/Trix.Validation.Tests.cs b/lib/oscillators/trix/Trix.Validation.Tests.cs index a4d36b29..d9246bfd 100644 --- a/lib/oscillators/trix/Trix.Validation.Tests.cs +++ b/lib/oscillators/trix/Trix.Validation.Tests.cs @@ -105,7 +105,7 @@ public sealed class TrixValidationTests(ITestOutputHelper output) : IDisposable double[] tOutput = new double[tData.Length]; var retCode = TALib.Functions.Trix(tData, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.TrixLookback(period); @@ -132,7 +132,7 @@ public sealed class TrixValidationTests(ITestOutputHelper output) : IDisposable } var retCode = TALib.Functions.Trix(tData, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.TrixLookback(period); diff --git a/lib/oscillators/ultosc/Ultosc.Validation.Tests.cs b/lib/oscillators/ultosc/Ultosc.Validation.Tests.cs index 11cb099c..93095439 100644 --- a/lib/oscillators/ultosc/Ultosc.Validation.Tests.cs +++ b/lib/oscillators/ultosc/Ultosc.Validation.Tests.cs @@ -243,7 +243,7 @@ public sealed class UltoscValidationTests : IDisposable var qResult = ultosc.Update(_data.Bars); var retCode = TALib.Functions.UltOsc(hData, lData, cData, 0..^0, output, out var outRange, p1, p2, p3); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.UltOscLookback(p1, p2, p3); @@ -279,7 +279,7 @@ public sealed class UltoscValidationTests : IDisposable } var retCode = TALib.Functions.UltOsc(hData, lData, cData, 0..^0, output, out var outRange, p1, p2, p3); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.UltOscLookback(p1, p2, p3); diff --git a/lib/reversals/pivot/Pivot.Validation.Tests.cs b/lib/reversals/pivot/Pivot.Validation.Tests.cs index 188d1a66..9f4d60ce 100644 --- a/lib/reversals/pivot/Pivot.Validation.Tests.cs +++ b/lib/reversals/pivot/Pivot.Validation.Tests.cs @@ -259,4 +259,4 @@ public sealed class PivotValidationTests } } -} \ No newline at end of file +} diff --git a/lib/reversals/pivotcam/Pivotcam.Validation.Tests.cs b/lib/reversals/pivotcam/Pivotcam.Validation.Tests.cs index 70e5cee4..9c9872cc 100644 --- a/lib/reversals/pivotcam/Pivotcam.Validation.Tests.cs +++ b/lib/reversals/pivotcam/Pivotcam.Validation.Tests.cs @@ -273,4 +273,4 @@ public sealed class PivotcamValidationTests } } -} \ No newline at end of file +} diff --git a/lib/reversals/pivotdem/Pivotdem.Validation.Tests.cs b/lib/reversals/pivotdem/Pivotdem.Validation.Tests.cs index 40f267b5..932f562e 100644 --- a/lib/reversals/pivotdem/Pivotdem.Validation.Tests.cs +++ b/lib/reversals/pivotdem/Pivotdem.Validation.Tests.cs @@ -240,4 +240,4 @@ public sealed class PivotdemValidationTests } } -} \ No newline at end of file +} diff --git a/lib/reversals/pivotfib/Pivotfib.Validation.Tests.cs b/lib/reversals/pivotfib/Pivotfib.Validation.Tests.cs index 77f6402f..2d78b1e0 100644 --- a/lib/reversals/pivotfib/Pivotfib.Validation.Tests.cs +++ b/lib/reversals/pivotfib/Pivotfib.Validation.Tests.cs @@ -246,4 +246,4 @@ public sealed class PivotfibValidationTests } } -} \ No newline at end of file +} diff --git a/lib/reversals/pivotwood/Pivotwood.Validation.Tests.cs b/lib/reversals/pivotwood/Pivotwood.Validation.Tests.cs index 78495a20..c1a27495 100644 --- a/lib/reversals/pivotwood/Pivotwood.Validation.Tests.cs +++ b/lib/reversals/pivotwood/Pivotwood.Validation.Tests.cs @@ -254,4 +254,4 @@ public sealed class PivotwoodValidationTests } } -} \ No newline at end of file +} diff --git a/lib/reversals/psar/Psar.Validation.Tests.cs b/lib/reversals/psar/Psar.Validation.Tests.cs index 7e998bd9..fef48163 100644 --- a/lib/reversals/psar/Psar.Validation.Tests.cs +++ b/lib/reversals/psar/Psar.Validation.Tests.cs @@ -210,7 +210,7 @@ public sealed class PsarValidationTests highData, lowData, 0..^0, taOut, out var outRange, afStep, afMax); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); (int offset, int length) = outRange.GetOffsetAndLength(taOut.Length); Assert.True(length > 100, $"TALib SAR produced only {length} values"); diff --git a/lib/statistics/acf/Acf.Quantower.Tests.cs b/lib/statistics/acf/Acf.Quantower.Tests.cs index b16cc536..358d0ec7 100644 --- a/lib/statistics/acf/Acf.Quantower.Tests.cs +++ b/lib/statistics/acf/Acf.Quantower.Tests.cs @@ -294,4 +294,4 @@ public class AcfIndicatorTests Assert.True(value >= -1 && value <= 1, $"ACF value at index {i} should be bounded [-1, 1], got {value}"); } } -} \ No newline at end of file +} diff --git a/lib/statistics/acf/Acf.Tests.cs b/lib/statistics/acf/Acf.Tests.cs index 19d8b3cb..b81832b6 100644 --- a/lib/statistics/acf/Acf.Tests.cs +++ b/lib/statistics/acf/Acf.Tests.cs @@ -539,4 +539,4 @@ public class AcfTests } #endregion -} \ No newline at end of file +} diff --git a/lib/statistics/acf/Acf.Validation.Tests.cs b/lib/statistics/acf/Acf.Validation.Tests.cs index ed2daf0b..3a2f8a4c 100644 --- a/lib/statistics/acf/Acf.Validation.Tests.cs +++ b/lib/statistics/acf/Acf.Validation.Tests.cs @@ -114,11 +114,13 @@ public class AcfValidationTests // For white noise, ACF at any lag > 0 should be close to zero var acf = new Acf(100, 5); - // Generate pseudo-random values with zero mean via GBM log-returns - var random = new GBM(startPrice: 100.0, sigma: 1.0, seed: 42); - for (int i = 0; i < 500; i++) + // Generate incremental bar-to-bar log-returns: log(close_i / close_{i-1}) + // These are approximately i.i.d. N(0, σ²·dt) — genuine white noise + var gbm = new GBM(startPrice: 100.0, sigma: 0.2, seed: 42); + var bars = gbm.Fetch(501, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + for (int i = 1; i < 501; i++) { - double val = Math.Log(random.Next().Close / 100.0); // ~N(0, vol²*dt) centered near 0 + double val = Math.Log(bars[i].Close / bars[i - 1].Close); // incremental log-return acf.Update(new TValue(DateTime.UtcNow.AddSeconds(i), val)); } @@ -215,12 +217,15 @@ public class AcfValidationTests double[] ar1Data = new double[n]; ar1Data[0] = 0; - var random = new GBM(startPrice: 100.0, sigma: 1.0, seed: 42); + + // Use incremental bar-to-bar log-returns as i.i.d. noise: log(close_i / close_{i-1}) + var gbm = new GBM(startPrice: 100.0, sigma: 0.2, seed: 42); + var bars = gbm.Fetch(n, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); // Generate AR(1) process for (int i = 1; i < n; i++) { - double epsilon = Math.Log(random.Next().Close / 100.0) * 0.1; // Small noise + double epsilon = Math.Log(bars[i].Close / bars[i - 1].Close) * 0.5; // incremental log-return scaled as noise ar1Data[i] = phi * ar1Data[i - 1] + epsilon; } @@ -280,4 +285,4 @@ public class AcfValidationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/statistics/acf/Acf.cs b/lib/statistics/acf/Acf.cs index 43990f6e..f0a6c093 100644 --- a/lib/statistics/acf/Acf.cs +++ b/lib/statistics/acf/Acf.cs @@ -42,12 +42,10 @@ public sealed class Acf : AbstractBase // Running sums for O(1) updates private double _sum; private double _sumSq; - private double _sumLagged; // Snapshot state for bar correction private double _p_sum; private double _p_sumSq; - private double _p_sumLagged; private int _updateCount; private const int ResyncInterval = 1000; @@ -110,7 +108,6 @@ public sealed class Acf : AbstractBase // Snapshot state for rollback _p_sum = _sum; _p_sumSq = _sumSq; - _p_sumLagged = _sumLagged; _buffer.Snapshot(); } else @@ -118,7 +115,6 @@ public sealed class Acf : AbstractBase // Restore state from snapshot _sum = _p_sum; _sumSq = _p_sumSq; - _sumLagged = _p_sumLagged; _buffer.Restore(); } @@ -129,12 +125,6 @@ public sealed class Acf : AbstractBase _sum -= oldVal; _sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, _sumSq); - // Remove contribution to lagged sum - if (_buffer.Count > _lag) - { - double oldLaggedVal = _buffer[_lag]; // value that was _lag positions from oldest - _sumLagged -= oldVal * oldLaggedVal; - } } // Add new value @@ -142,14 +132,6 @@ public sealed class Acf : AbstractBase _sum += value; _sumSq = Math.FusedMultiplyAdd(value, value, _sumSq); - // Update lagged sum: add product of new value and value at lag positions before - if (_buffer.Count > _lag) - { - int lagIndex = _buffer.Count - 1 - _lag; - double laggedVal = _buffer[lagIndex]; - _sumLagged += value * laggedVal; - } - if (isNew) { _updateCount++; @@ -257,18 +239,11 @@ public sealed class Acf : AbstractBase int n = _buffer.Count; _sum = 0; _sumSq = 0; - _sumLagged = 0; - for (int i = 0; i < n; i++) { double val = _buffer[i]; _sum += val; _sumSq += val * val; - - if (i >= _lag) - { - _sumLagged += val * _buffer[i - _lag]; - } } } @@ -277,19 +252,18 @@ public sealed class Acf : AbstractBase _buffer.Clear(); _sum = 0; _sumSq = 0; - _sumLagged = 0; _p_sum = 0; _p_sumSq = 0; - _p_sumLagged = 0; _updateCount = 0; Last = default; } public override void Prime(ReadOnlySpan source, TimeSpan? step = null) { + Reset(); foreach (double value in source) { - Update(new TValue(DateTime.UtcNow, value)); + Update(new TValue(DateTime.MinValue, value)); } } diff --git a/lib/statistics/acf/Acf.md b/lib/statistics/acf/Acf.md index 05c36d52..e46a82c8 100644 --- a/lib/statistics/acf/Acf.md +++ b/lib/statistics/acf/Acf.md @@ -15,7 +15,7 @@ - Parameterized by `period`, `lag` (default 1). - Output range: Varies (see docs). - Requires `period` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- Validated against mathematical properties and theoretical AR-process expectations. > "The past doesn't predict the future, but it whispers patterns to those who listen." diff --git a/lib/statistics/beta/Beta.Validation.Tests.cs b/lib/statistics/beta/Beta.Validation.Tests.cs index fa0b6dc0..f3eef404 100644 --- a/lib/statistics/beta/Beta.Validation.Tests.cs +++ b/lib/statistics/beta/Beta.Validation.Tests.cs @@ -130,7 +130,7 @@ public sealed class BetaValidationTests : IDisposable var retCode = Functions.Beta( assetPrices.AsSpan(), marketPrices.AsSpan(), 0..^0, taOut, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); (int offset, int length) = outRange.GetOffsetAndLength(taOut.Length); diff --git a/lib/statistics/cointegration/Cointegration.Quantower.Tests.cs b/lib/statistics/cointegration/Cointegration.Quantower.Tests.cs index a8f12c40..5843ef20 100644 --- a/lib/statistics/cointegration/Cointegration.Quantower.Tests.cs +++ b/lib/statistics/cointegration/Cointegration.Quantower.Tests.cs @@ -264,4 +264,4 @@ public class CointegrationIndicatorTests Assert.Contains("cointegration", indicator.Description, StringComparison.OrdinalIgnoreCase); Assert.Contains("ADF", indicator.Description, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/statistics/cointegration/Cointegration.Tests.cs b/lib/statistics/cointegration/Cointegration.Tests.cs index 63ac5508..6145a71e 100644 --- a/lib/statistics/cointegration/Cointegration.Tests.cs +++ b/lib/statistics/cointegration/Cointegration.Tests.cs @@ -154,27 +154,34 @@ public class CointegrationTests [Fact] public void Update_WithIsNewFalse_DoesNotAdvanceState() { - var indicator = new Cointegration(5); + var corrected = new Cointegration(5); + var direct = new Cointegration(5); + var gbmA = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.1, seed: 12345); var gbmB = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.1, seed: 54321); - // Build up some state + // Build identical state for (int i = 0; i < 10; i++) { - indicator.Update(gbmA.Next().Close, gbmB.Next().Close, isNew: true); + double a = gbmA.Next().Close; + double b = gbmB.Next().Close; + corrected.Update(a, b, isNew: true); + direct.Update(a, b, isNew: true); } - // Update with same values and isNew=false - indicator.Update(gbmA.Next().Close, gbmB.Next().Close, isNew: false); - var valueAfterFirst = indicator.Last.Value; + const double finalA = 105.0; + const double finalB = 55.0; - // Another correction - indicator.Update(gbmA.Next().Close, gbmB.Next().Close, isNew: false); - var valueAfterSecond = indicator.Last.Value; + // Correction path: add + multiple rewrites + final rewrite to target value + corrected.Update(finalA, finalB, isNew: true); + corrected.Update(finalA + 10.0, finalB + 10.0, isNew: false); + corrected.Update(finalA - 3.0, finalB - 3.0, isNew: false); + corrected.Update(finalA, finalB, isNew: false); - // All corrections replace the same bar, state should be consistent - Assert.True(double.IsFinite(valueAfterFirst) || double.IsNaN(valueAfterFirst)); - Assert.True(double.IsFinite(valueAfterSecond) || double.IsNaN(valueAfterSecond)); + // Direct path: only final new bar + direct.Update(finalA, finalB, isNew: true); + + Assert.Equal(direct.Last.Value, corrected.Last.Value, Tolerance); } [Fact] @@ -648,4 +655,4 @@ public class CointegrationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/statistics/cointegration/Cointegration.Validation.Tests.cs b/lib/statistics/cointegration/Cointegration.Validation.Tests.cs index bfc917b5..e07f11ea 100644 --- a/lib/statistics/cointegration/Cointegration.Validation.Tests.cs +++ b/lib/statistics/cointegration/Cointegration.Validation.Tests.cs @@ -18,20 +18,26 @@ public class CointegrationValidationTests [Fact] public void Cointegration_PerfectlyCointegrated_ProducesStrongNegativeAdf() { - // Two series with near-perfect linear relationship should show strong cointegration - // Adding small noise to avoid zero-variance residuals - var indicator = new Cointegration(20); - var random = new GBM(startPrice: 100.0, sigma: 1.0, seed: 42); + // Two series with near-perfect linear relationship should show strong cointegration. + // Use incremental log-returns (i.i.d.) as noise so residuals are stationary. + // Period=30 gives ADF sufficient window; 200 samples ensure stable regression. + var indicator = new Cointegration(30); + var gbm = new GBM(startPrice: 100.0, sigma: 0.2, seed: 42); + var bars = gbm.Fetch(201, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - for (int i = 0; i < 100; i++) + for (int i = 1; i <= 200; i++) { - double a = 100.0 + i * 0.5 + GbmNoise(random) * 0.1; - double b = 2.0 * a + 10.0 + GbmNoise(random) * 0.1; + // Incremental log-return: truly i.i.d. noise, variance ~(0.2²·dt) + double noise = Math.Log(bars[i].Close / bars[i - 1].Close); + double a = 100.0 + i * 0.5 + noise * 0.1; + double b = 2.0 * a + 10.0 + noise * 0.1; indicator.Update(a, b); } - // Near-perfect cointegration should produce strongly negative ADF statistic - Assert.True(indicator.Last.Value < -2.0, $"ADF should be strongly negative for cointegrated series, got {indicator.Last.Value}"); + // Near-perfect cointegration should produce ADF below the 5% critical value. + // Engle-Granger critical values (residual-based, no constant): -1.95 at 5%, -2.86 for large N. + // With period=30 and 200 samples of near-linear data the statistic should clear -1.95 comfortably. + Assert.True(indicator.Last.Value < -1.95, $"ADF should be below 5% critical value (-1.95) for cointegrated series, got {indicator.Last.Value}"); } [Fact] @@ -68,7 +74,8 @@ public class CointegrationValidationTests indicator.Update(a, b); } - Assert.True(indicator.Last.Value < 0, $"ADF should be negative for near-proportional series, got {indicator.Last.Value}"); + // Proportional series with small noise should produce ADF well below 0; -1.0 is a conservative bound. + Assert.True(indicator.Last.Value < -1.0, $"ADF should be well negative for near-proportional series, got {indicator.Last.Value}"); } [Fact] @@ -86,8 +93,8 @@ public class CointegrationValidationTests indicator.Update(a, b); } - // Should still detect cointegration despite small noise - Assert.True(indicator.Last.Value < 0, $"ADF should be negative even with small noise, got {indicator.Last.Value}"); + // Linear relationship with small noise should still clear -1.0. + Assert.True(indicator.Last.Value < -1.0, $"ADF should be well negative with small noise, got {indicator.Last.Value}"); } [Fact] @@ -226,8 +233,8 @@ public class CointegrationValidationTests indicator.Update(100.0, 50.0); } - // Should handle constant series without crashing (result may be NaN due to zero variance) - Assert.True(double.IsNaN(indicator.Last.Value) || double.IsFinite(indicator.Last.Value)); + // Constant series → zero variance → ADF denominator is zero → NaN is correct. + Assert.True(double.IsNaN(indicator.Last.Value), $"Expected NaN for constant series, got {indicator.Last.Value}"); } [Fact] @@ -240,8 +247,8 @@ public class CointegrationValidationTests indicator.Update(100.0, 50.0 + i); // A constant, B trending } - // Should handle mixed constant/trending without crashing - Assert.True(double.IsNaN(indicator.Last.Value) || double.IsFinite(indicator.Last.Value)); + // Constant A → zero variance in A → ADF is undefined → NaN. + Assert.True(double.IsNaN(indicator.Last.Value), $"Expected NaN when series A is constant, got {indicator.Last.Value}"); } [Fact] @@ -330,4 +337,4 @@ public class CointegrationValidationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/statistics/cointegration/Cointegration.cs b/lib/statistics/cointegration/Cointegration.cs index d71e53bc..e6a0d69b 100644 --- a/lib/statistics/cointegration/Cointegration.cs +++ b/lib/statistics/cointegration/Cointegration.cs @@ -45,8 +45,7 @@ public sealed class Cointegration : AbstractBase // ADF regression running sums (period-1 window) private readonly RingBuffer _deltaResiduals; private readonly RingBuffer _laggedResiduals; - private double _sumDelta, _sumLagged; - private double _sumDeltaLagged, _sumLagged2; + private double _sumDeltaLagged, _sumLagged2, _sumDelta2; // Last valid values for NaN handling private double _lastValidA, _lastValidB; @@ -113,7 +112,7 @@ public sealed class Cointegration : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(double seriesA, double seriesB, bool isNew = true) { - return Update(new TValue(DateTime.UtcNow, seriesA), new TValue(DateTime.UtcNow, seriesB), isNew); + return Update(new TValue(DateTime.MinValue, seriesA), new TValue(DateTime.MinValue, seriesB), isNew); } /// @@ -195,19 +194,17 @@ public sealed class Cointegration : AbstractBase { double oldDelta = _deltaResiduals.Oldest; double oldLagged = _laggedResiduals.Oldest; - _sumDelta -= oldDelta; - _sumLagged -= oldLagged; _sumDeltaLagged = FusedMultiplyAdd(-oldDelta, oldLagged, _sumDeltaLagged); _sumLagged2 = FusedMultiplyAdd(-oldLagged, oldLagged, _sumLagged2); + _sumDelta2 = FusedMultiplyAdd(-oldDelta, oldDelta, _sumDelta2); } _deltaResiduals.Add(delta); _laggedResiduals.Add(lagged); - _sumDelta += delta; - _sumLagged += lagged; _sumDeltaLagged = FusedMultiplyAdd(delta, lagged, _sumDeltaLagged); _sumLagged2 = FusedMultiplyAdd(lagged, lagged, _sumLagged2); + _sumDelta2 = FusedMultiplyAdd(delta, delta, _sumDelta2); } _prevResidual = residual; @@ -230,31 +227,24 @@ public sealed class Cointegration : AbstractBase _hasPrevResidual = _p_hasPrevResidual; // Update newest values in main buffers - if (_bufferA.Count > 0) + if (_bufferA.Count == 0) { - double oldA = _bufferA.Newest; - double oldB = _bufferB.Newest; - - _sumA = FusedMultiplyAdd(1.0, a, FusedMultiplyAdd(-1.0, oldA, _sumA)); - _sumB = FusedMultiplyAdd(1.0, b, FusedMultiplyAdd(-1.0, oldB, _sumB)); - _sumA2 = FusedMultiplyAdd(a, a, FusedMultiplyAdd(-oldA, oldA, _sumA2)); - _sumB2 = FusedMultiplyAdd(b, b, FusedMultiplyAdd(-oldB, oldB, _sumB2)); - _sumAB = FusedMultiplyAdd(a, b, FusedMultiplyAdd(-oldA, oldB, _sumAB)); - - _bufferA.UpdateNewest(a); - _bufferB.UpdateNewest(b); - } - else - { - _bufferA.Add(a); - _bufferB.Add(b); - _sumA = a; - _sumB = b; - _sumA2 = a * a; - _sumB2 = b * b; - _sumAB = a * b; + // Nothing to correct yet; no current bar exists + return; } + double oldA = _bufferA.Newest; + double oldB = _bufferB.Newest; + + _sumA += a - oldA; + _sumB += b - oldB; + _sumA2 = FusedMultiplyAdd(a, a, FusedMultiplyAdd(-oldA, oldA, _sumA2)); + _sumB2 = FusedMultiplyAdd(b, b, FusedMultiplyAdd(-oldB, oldB, _sumB2)); + _sumAB = FusedMultiplyAdd(a, b, FusedMultiplyAdd(-oldA, oldB, _sumAB)); + + _bufferA.UpdateNewest(a); + _bufferB.UpdateNewest(b); + // Calculate current residual double residual = CalculateResidual(a, b); @@ -264,28 +254,21 @@ public sealed class Cointegration : AbstractBase double delta = residual - _prevResidual; double lagged = _prevResidual; - if (_deltaResiduals.Count > 0) + if (_deltaResiduals.Count == 0) { - double oldDelta = _deltaResiduals.Newest; - double oldLagged = _laggedResiduals.Newest; - - _sumDelta = FusedMultiplyAdd(1.0, delta, FusedMultiplyAdd(-1.0, oldDelta, _sumDelta)); - _sumLagged = FusedMultiplyAdd(1.0, lagged, FusedMultiplyAdd(-1.0, oldLagged, _sumLagged)); - _sumDeltaLagged = FusedMultiplyAdd(delta, lagged, FusedMultiplyAdd(-oldDelta, oldLagged, _sumDeltaLagged)); - _sumLagged2 = FusedMultiplyAdd(lagged, lagged, FusedMultiplyAdd(-oldLagged, oldLagged, _sumLagged2)); - - _deltaResiduals.UpdateNewest(delta); - _laggedResiduals.UpdateNewest(lagged); - } - else - { - _deltaResiduals.Add(delta); - _laggedResiduals.Add(lagged); - _sumDelta = delta; - _sumLagged = lagged; - _sumDeltaLagged = delta * lagged; - _sumLagged2 = lagged * lagged; + // Nothing to correct yet in ADF buffers; no current entry exists + return; } + + double oldDelta = _deltaResiduals.Newest; + double oldLagged = _laggedResiduals.Newest; + + _sumDeltaLagged = FusedMultiplyAdd(delta, lagged, FusedMultiplyAdd(-oldDelta, oldLagged, _sumDeltaLagged)); + _sumLagged2 = FusedMultiplyAdd(lagged, lagged, FusedMultiplyAdd(-oldLagged, oldLagged, _sumLagged2)); + _sumDelta2 = FusedMultiplyAdd(delta, delta, FusedMultiplyAdd(-oldDelta, oldDelta, _sumDelta2)); + + _deltaResiduals.UpdateNewest(delta); + _laggedResiduals.UpdateNewest(lagged); } _prevResidual = residual; @@ -305,28 +288,15 @@ public sealed class Cointegration : AbstractBase double meanA = _sumA / n; double meanB = _sumB / n; - // Calculate variances and covariance - double varA = Max(0.0, (_sumA2 / n) - (meanA * meanA)); + // Calculate variance of B and covariance double varB = Max(0.0, (_sumB2 / n) - (meanB * meanB)); double cov = (_sumAB / n) - (meanA * meanB); - // Calculate standard deviations - double stdA = Sqrt(varA); - double stdB = Sqrt(varB); - - // Calculate correlation - double correlation = 0.0; - double denom = stdA * stdB; - if (Abs(denom) > Epsilon) - { - correlation = cov / denom; - } - // Calculate beta and alpha double beta = 0.0; - if (Abs(stdB) > Epsilon) + if (varB > Epsilon) { - beta = correlation * (stdA / stdB); + beta = cov / varB; } double alpha = meanA - (beta * meanB); @@ -343,42 +313,23 @@ public sealed class Cointegration : AbstractBase return double.NaN; } - // Calculate gamma (coefficient in ADF regression) - // Δε_t = γ × ε_{t-1} + u_t - // γ = Cov(Δε, ε_{t-1}) / Var(ε_{t-1}) - - double meanDelta = _sumDelta / n; - double meanLagged = _sumLagged / n; - - // Variance of lagged residuals - double varLagged = (_sumLagged2 / n) - (meanLagged * meanLagged); - if (Abs(varLagged) < Epsilon) + if (_sumLagged2 < Epsilon) { return double.NaN; } - // Covariance of delta and lagged - double covDeltaLagged = (_sumDeltaLagged / n) - (meanDelta * meanLagged); + // No-intercept ADF regression: Δε_t = γ × ε_{t-1} + u_t + double gamma = _sumDeltaLagged / _sumLagged2; - // Gamma coefficient - double gamma = covDeltaLagged / varLagged; + // Calculate sum of squared regression errors in O(1) + // Sum((Δε_t - γ ε_{t-1})^2) = Sum(Δε_t^2) - 2γ Sum(Δε_t ε_{t-1}) + γ^2 Sum(ε_{t-1}^2) + double sumErrorSq = _sumDelta2 - (2.0 * gamma * _sumDeltaLagged) + (gamma * gamma * _sumLagged2); - // Calculate standard error of gamma - // SE(γ) = sqrt(Var(u) / (n × Var(ε_{t-1}))) - // where u_t = Δε_t - γ × ε_{t-1} + // Ensure non-negative due to floating point errors + sumErrorSq = Max(0.0, sumErrorSq); - // Calculate sum of squared regression errors - double sumErrorSq = 0.0; - for (int i = 0; i < n; i++) - { - double delta = _deltaResiduals[i]; - double lagged = _laggedResiduals[i]; - double error = delta - (gamma * lagged); - sumErrorSq = FusedMultiplyAdd(error, error, sumErrorSq); - } - - double varError = sumErrorSq / n; - double seGammaSq = varError / (n * varLagged); + double varError = sumErrorSq / (n - 1); + double seGammaSq = varError / _sumLagged2; if (seGammaSq <= 0 || !double.IsFinite(seGammaSq)) { @@ -386,7 +337,7 @@ public sealed class Cointegration : AbstractBase } double seGamma = Sqrt(seGammaSq); - if (Abs(seGamma) < Epsilon) + if (seGamma < Epsilon) { return double.NaN; } @@ -396,17 +347,20 @@ public sealed class Cointegration : AbstractBase private void Resync() { - // Resync main buffer sums + // Resync main buffer sums using span access to avoid per-element modulo in indexer. + // Both buffers are always updated together so their sequenced spans align element-by-element. _sumA = 0; _sumB = 0; _sumA2 = 0; _sumB2 = 0; _sumAB = 0; - for (int i = 0; i < _bufferA.Count; i++) + _bufferA.GetSequencedSpans(out var aFirst, out var aSecond); + _bufferB.GetSequencedSpans(out var bFirst, out var bSecond); + + for (int i = 0; i < aFirst.Length; i++) { - double a = _bufferA[i]; - double b = _bufferB[i]; + double a = aFirst[i], b = bFirst[i]; _sumA += a; _sumB += b; _sumA2 = FusedMultiplyAdd(a, a, _sumA2); @@ -414,20 +368,38 @@ public sealed class Cointegration : AbstractBase _sumAB = FusedMultiplyAdd(a, b, _sumAB); } - // Resync ADF regression sums - _sumDelta = 0; - _sumLagged = 0; + for (int i = 0; i < aSecond.Length; i++) + { + double a = aSecond[i], b = bSecond[i]; + _sumA += a; + _sumB += b; + _sumA2 = FusedMultiplyAdd(a, a, _sumA2); + _sumB2 = FusedMultiplyAdd(b, b, _sumB2); + _sumAB = FusedMultiplyAdd(a, b, _sumAB); + } + + // Resync ADF regression sums (delta/lagged buffers also always updated together). _sumDeltaLagged = 0; _sumLagged2 = 0; + _sumDelta2 = 0; - for (int i = 0; i < _deltaResiduals.Count; i++) + _deltaResiduals.GetSequencedSpans(out var dFirst, out var dSecond); + _laggedResiduals.GetSequencedSpans(out var lFirst, out var lSecond); + + for (int i = 0; i < dFirst.Length; i++) { - double delta = _deltaResiduals[i]; - double lagged = _laggedResiduals[i]; - _sumDelta += delta; - _sumLagged += lagged; + double delta = dFirst[i], lagged = lFirst[i]; _sumDeltaLagged = FusedMultiplyAdd(delta, lagged, _sumDeltaLagged); _sumLagged2 = FusedMultiplyAdd(lagged, lagged, _sumLagged2); + _sumDelta2 = FusedMultiplyAdd(delta, delta, _sumDelta2); + } + + for (int i = 0; i < dSecond.Length; i++) + { + double delta = dSecond[i], lagged = lSecond[i]; + _sumDeltaLagged = FusedMultiplyAdd(delta, lagged, _sumDeltaLagged); + _sumLagged2 = FusedMultiplyAdd(lagged, lagged, _sumLagged2); + _sumDelta2 = FusedMultiplyAdd(delta, delta, _sumDelta2); } } @@ -450,10 +422,9 @@ public sealed class Cointegration : AbstractBase _sumB2 = 0; _sumAB = 0; - _sumDelta = 0; - _sumLagged = 0; _sumDeltaLagged = 0; _sumLagged2 = 0; + _sumDelta2 = 0; _prevResidual = 0; _p_prevResidual = 0; @@ -473,28 +444,7 @@ public sealed class Cointegration : AbstractBase /// Calculates cointegration for two time series. /// public static TSeries Batch(TSeries seriesA, TSeries seriesB, int period = 20) - { - if (seriesA.Count != seriesB.Count) - { - throw new ArgumentException("Series must have the same length", nameof(seriesB)); - } - - var indicator = new Cointegration(period); - var result = new TSeries(seriesA.Count); - - var timesA = seriesA.Times; - var valuesA = seriesA.Values; - var valuesB = seriesB.Values; - - for (int i = 0; i < seriesA.Count; i++) - { - var tvalA = new TValue(timesA[i], valuesA[i]); - var tvalB = new TValue(timesA[i], valuesB[i]); - result.Add(indicator.Update(tvalA, tvalB, isNew: true)); - } - - return result; - } + => Calculate(seriesA, seriesB, period).Results; /// /// Static batch calculation for span-based processing. @@ -531,9 +481,24 @@ public sealed class Cointegration : AbstractBase public static (TSeries Results, Cointegration Indicator) Calculate(TSeries seriesA, TSeries seriesB, int period = 20) { + if (seriesA.Count != seriesB.Count) + { + throw new ArgumentException("Series must have the same length", nameof(seriesB)); + } + var indicator = new Cointegration(period); - TSeries results = Batch(seriesA, seriesB, period); - return (results, indicator); + var result = new TSeries(seriesA.Count); + + var timesA = seriesA.Times; + var valuesA = seriesA.Values; + var valuesB = seriesB.Values; + + for (int i = 0; i < seriesA.Count; i++) + { + result.Add(indicator.Update(new TValue(timesA[i], valuesA[i]), new TValue(timesA[i], valuesB[i]), isNew: true)); + } + + return (result, indicator); } } diff --git a/lib/statistics/cointegration/Cointegration.md b/lib/statistics/cointegration/Cointegration.md index 7abb9bf0..eb1dcf08 100644 --- a/lib/statistics/cointegration/Cointegration.md +++ b/lib/statistics/cointegration/Cointegration.md @@ -3,9 +3,9 @@ | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | -| **Inputs** | Source (close) | +| **Inputs** | Two series (A, B) | | **Parameters** | `period` (default 20) | -| **Outputs** | Single series (Cointegration) | +| **Outputs** | Single series (ADF statistic) | | **Output range** | Varies (see docs) | | **Warmup** | `period + 1` bars | @@ -15,7 +15,7 @@ - Parameterized by `period` (default 20). - Output range: Varies (see docs). - Requires `period + 1` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- Validated against TradingView PineScript reference and statistical property tests. > "Correlation tells you they move together. Cointegration tells you they're bound together. Two stocks can be uncorrelated yet cointegrated, or perfectly correlated yet destined to drift apart forever. The difference between 'similar direction' and 'shared destiny' is the difference between a tourist attraction and a gravitational orbit." @@ -235,7 +235,7 @@ double[] pricesA = new double[1000]; double[] pricesB = new double[1000]; double[] output = new double[1000]; // ... populate inputs ... -Cointegration.Calculate(pricesA.AsSpan(), pricesB.AsSpan(), output.AsSpan(), period: 20); +Cointegration.Batch(pricesA.AsSpan(), pricesB.AsSpan(), output.AsSpan(), period: 20); ``` ### Bar Correction Support diff --git a/lib/statistics/correlation/Correlation.Quantower.Tests.cs b/lib/statistics/correlation/Correlation.Quantower.Tests.cs index be2ce707..22c1d4ab 100644 --- a/lib/statistics/correlation/Correlation.Quantower.Tests.cs +++ b/lib/statistics/correlation/Correlation.Quantower.Tests.cs @@ -304,4 +304,4 @@ public class CorrelationIndicatorTests Assert.Equal(1.0, lastValue, precision: 6); } } -} \ No newline at end of file +} diff --git a/lib/statistics/correlation/Correlation.Tests.cs b/lib/statistics/correlation/Correlation.Tests.cs index f2a2d686..6a368ab2 100644 --- a/lib/statistics/correlation/Correlation.Tests.cs +++ b/lib/statistics/correlation/Correlation.Tests.cs @@ -119,31 +119,33 @@ public class CorrelationTests [Fact] public void Update_IterativeCorrections_Restore() { - var indicator = new Correlation(5); + var corrected = new Correlation(5); + var direct = new Correlation(5); - // Feed initial data + // Feed identical initial state for (int i = 0; i < 8; i++) { double x = 100.0 + i; double y = 200.0 + (i * 2); - indicator.Update(x, y, true); + corrected.Update(x, y, true); + direct.Update(x, y, true); } - // Add new bar - indicator.Update(108.0, 216.0, true); + // Target final value for the current bar + const double finalX = 108.0; + const double finalY = 216.0; - // Make multiple corrections - for (int j = 0; j < 5; j++) - { - double x = 108.0 + (j * 0.1); - double y = 216.0 + (j * 0.2); - _ = indicator.Update(x, y, false); - } + // Correction path: new bar, several rewrites, final rewrite back to target + corrected.Update(finalX, finalY, true); + corrected.Update(finalX + 1.0, finalY + 2.0, false); + corrected.Update(finalX - 0.5, finalY - 1.0, false); + corrected.Update(finalX + 0.25, finalY + 0.5, false); + corrected.Update(finalX, finalY, false); - // Final correction back to original values - indicator.Update(108.0, 216.0, false); + // Direct path: same initial state + one new bar with final value + direct.Update(finalX, finalY, true); - Assert.True(double.IsFinite(indicator.Last.Value)); + Assert.Equal(direct.Last.Value, corrected.Last.Value, 1e-12); } [Fact] @@ -159,9 +161,9 @@ public class CorrelationTests _ = indicator.Last.Value; - // Add NaN - should use last valid value + // Add NaN - should use last valid value, result must be finite var result = indicator.Update(double.NaN, double.NaN, true); - Assert.True(double.IsFinite(result.Value) || double.IsNaN(result.Value)); + Assert.True(double.IsFinite(result.Value)); } [Fact] @@ -175,9 +177,9 @@ public class CorrelationTests indicator.Update(100.0 + i, 200.0 + i, true); } - // Add Infinity - should use last valid value + // Add Infinity - should use last valid value, result must be finite var result = indicator.Update(double.PositiveInfinity, double.NegativeInfinity, true); - Assert.True(double.IsFinite(result.Value) || double.IsNaN(result.Value)); + Assert.True(double.IsFinite(result.Value)); } [Fact] @@ -189,11 +191,13 @@ public class CorrelationTests } [Fact] - public void IsHot_AtLeastTwoValues_ReturnsTrue() + public void IsHot_AtPeriod_ReturnsTrue() { var indicator = new Correlation(10); - indicator.Update(100.0, 200.0, true); - indicator.Update(101.0, 201.0, true); + for (int i = 0; i < 10; i++) + { + indicator.Update(100.0 + i, 200.0 + i, true); + } Assert.True(indicator.IsHot); } @@ -385,4 +389,4 @@ public class CorrelationTests } } } -} \ No newline at end of file +} diff --git a/lib/statistics/correlation/Correlation.Validation.Tests.cs b/lib/statistics/correlation/Correlation.Validation.Tests.cs index c70bc149..324d7cb9 100644 --- a/lib/statistics/correlation/Correlation.Validation.Tests.cs +++ b/lib/statistics/correlation/Correlation.Validation.Tests.cs @@ -609,14 +609,20 @@ public sealed class CorrelationValidationTests : IDisposable [Fact] public void Correlation_WeakCorrelation_DetectedCorrectly() { - // Create two series with weak correlation (lots of noise) + // Create two series with weak correlation: pure independent noise, no shared trend. + // Use two independent GBMs (different seeds) and feed their incremental log-returns directly. + // With period=20 and fully independent noise sequences, correlation should be near zero. var indicator = new Correlation(20); - var random = new GBM(startPrice: 100.0, sigma: 1.0, seed: 43); + var gbmX = new GBM(startPrice: 100.0, sigma: 0.2, seed: 43); + var gbmY = new GBM(startPrice: 100.0, sigma: 0.2, seed: 9871); + var barsX = gbmX.Fetch(101, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + var barsY = gbmY.Fetch(101, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); - for (int i = 0; i < 100; i++) + for (int i = 1; i <= 100; i++) { - double x = 100.0 + i + Math.Log(random.Next().Close / 100.0) * 50; - double y = 100.0 + 0.1 * i + Math.Log(random.Next().Close / 100.0) * 50; // Weak relationship + // Pure independent white noise — no shared linear component + double x = Math.Log(barsX[i].Close / barsX[i - 1].Close); + double y = Math.Log(barsY[i].Close / barsY[i - 1].Close); indicator.Update(x, y); } @@ -720,7 +726,7 @@ public sealed class CorrelationValidationTests : IDisposable double[] taOut = new double[_data.Count]; var retCode = Functions.Correl(closeArr, openArr, 0..^0, taOut, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); (int offset, int length) = outRange.GetOffsetAndLength(taOut.Length); Assert.True(length > 100, $"TALib Correl produced only {length} values"); @@ -760,7 +766,7 @@ public sealed class CorrelationValidationTests : IDisposable { double[] taOut = new double[_data.Count]; var retCode = Functions.Correl(highArr, lowArr, 0..^0, taOut, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); (int offset, int length) = outRange.GetOffsetAndLength(taOut.Length); diff --git a/lib/statistics/correlation/Correlation.cs b/lib/statistics/correlation/Correlation.cs index f7c5a4b4..2212e521 100644 --- a/lib/statistics/correlation/Correlation.cs +++ b/lib/statistics/correlation/Correlation.cs @@ -39,12 +39,13 @@ public sealed class Correlation : AbstractBase // Last valid values for NaN handling private double _lastValidX, _lastValidY; + private double _p_lastValidX, _p_lastValidY; private int _updateCount; private const int ResyncInterval = 1000; private const double Epsilon = 1e-10; - public override bool IsHot => _bufferX.Count >= 2; + public override bool IsHot => _bufferX.Count >= WarmupPeriod; /// /// Creates a new Correlation indicator. @@ -74,6 +75,17 @@ public sealed class Correlation : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TValue seriesX, TValue seriesY, bool isNew = true) { + if (isNew) + { + _p_lastValidX = _lastValidX; + _p_lastValidY = _lastValidY; + } + else + { + _lastValidX = _p_lastValidX; + _lastValidY = _p_lastValidY; + } + double x = SanitizeX(seriesX.Value); double y = SanitizeY(seriesY.Value); @@ -99,7 +111,7 @@ public sealed class Correlation : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(double seriesX, double seriesY, bool isNew = true) { - return Update(new TValue(DateTime.UtcNow, seriesX), new TValue(DateTime.UtcNow, seriesY), isNew); + return Update(new TValue(DateTime.MinValue, seriesX), new TValue(DateTime.MinValue, seriesY), isNew); } /// @@ -175,14 +187,7 @@ public sealed class Correlation : AbstractBase { if (_bufferX.Count == 0) { - // No data yet, just add - _bufferX.Add(x); - _bufferY.Add(y); - _sumX = x; - _sumY = y; - _sumX2 = x * x; - _sumY2 = y * y; - _sumXY = x * y; + // Nothing to correct yet; no current bar exists return; } @@ -190,12 +195,12 @@ public sealed class Correlation : AbstractBase double oldX = _bufferX.Newest; double oldY = _bufferY.Newest; - // Update the running sums: remove old, add new + // Update the running sums: remove old, add new (using FMA for consistency with ProcessNewBar) _sumX = _sumX - oldX + x; _sumY = _sumY - oldY + y; - _sumX2 = _sumX2 - (oldX * oldX) + (x * x); - _sumY2 = _sumY2 - (oldY * oldY) + (y * y); - _sumXY = _sumXY - (oldX * oldY) + (x * y); + _sumX2 = FusedMultiplyAdd(x, x, FusedMultiplyAdd(-oldX, oldX, _sumX2)); + _sumY2 = FusedMultiplyAdd(y, y, FusedMultiplyAdd(-oldY, oldY, _sumY2)); + _sumXY = FusedMultiplyAdd(x, y, FusedMultiplyAdd(-oldX, oldY, _sumXY)); // Update the buffer values _bufferX.UpdateNewest(x); @@ -278,6 +283,8 @@ public sealed class Correlation : AbstractBase _lastValidX = 0; _lastValidY = 0; + _p_lastValidX = 0; + _p_lastValidY = 0; _updateCount = 0; Last = default; @@ -287,28 +294,7 @@ public sealed class Correlation : AbstractBase /// Calculates correlation for two time series. /// public static TSeries Batch(TSeries seriesX, TSeries seriesY, int period = 20) - { - if (seriesX.Count != seriesY.Count) - { - throw new ArgumentException("Series must have the same length", nameof(seriesY)); - } - - var indicator = new Correlation(period); - var result = new TSeries(seriesX.Count); - - var timesX = seriesX.Times; - var valuesX = seriesX.Values; - var valuesY = seriesY.Values; - - for (int i = 0; i < seriesX.Count; i++) - { - var tvalX = new TValue(timesX[i], valuesX[i]); - var tvalY = new TValue(timesX[i], valuesY[i]); - result.Add(indicator.Update(tvalX, tvalY, isNew: true)); - } - - return result; - } + => Calculate(seriesX, seriesY, period).Results; /// /// Static batch calculation for span-based processing. @@ -345,9 +331,24 @@ public sealed class Correlation : AbstractBase public static (TSeries Results, Correlation Indicator) Calculate(TSeries seriesX, TSeries seriesY, int period = 20) { + if (seriesX.Count != seriesY.Count) + { + throw new ArgumentException("Series must have the same length", nameof(seriesY)); + } + var indicator = new Correlation(period); - TSeries results = Batch(seriesX, seriesY, period); - return (results, indicator); + var result = new TSeries(seriesX.Count); + + var timesX = seriesX.Times; + var valuesX = seriesX.Values; + var valuesY = seriesY.Values; + + for (int i = 0; i < seriesX.Count; i++) + { + result.Add(indicator.Update(new TValue(timesX[i], valuesX[i]), new TValue(timesX[i], valuesY[i]), isNew: true)); + } + + return (result, indicator); } } diff --git a/lib/statistics/correlation/Correlation.md b/lib/statistics/correlation/Correlation.md index 57678065..91874d36 100644 --- a/lib/statistics/correlation/Correlation.md +++ b/lib/statistics/correlation/Correlation.md @@ -3,9 +3,9 @@ | Property | Value | | ---------------- | -------------------------------- | | **Category** | Statistic | -| **Inputs** | Source (close) | +| **Inputs** | Two series (X, Y) | | **Parameters** | `period` (default 20) | -| **Outputs** | Single series (Correlation) | +| **Outputs** | Single series (Pearson r) | | **Output range** | Varies (see docs) | | **Warmup** | `period` bars | @@ -15,7 +15,7 @@ - Parameterized by `period` (default 20). - Output range: Varies (see docs). - Requires `period` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- Validated against TradingView reference behavior and mathematical invariants. > "Correlation is not causation, but it sure is a hint. The market doesn't care why two instruments move together—only that they do, and whether that relationship will persist long enough for you to profit from it." @@ -219,7 +219,7 @@ double[] pricesA = new double[1000]; double[] pricesB = new double[1000]; double[] output = new double[1000]; // ... populate inputs ... -Correlation.Calculate(pricesA.AsSpan(), pricesB.AsSpan(), output.AsSpan(), period: 20); +Correlation.Batch(pricesA.AsSpan(), pricesB.AsSpan(), output.AsSpan(), period: 20); ``` ### Bar Correction Support diff --git a/lib/statistics/hurst/Hurst.Validation.Tests.cs b/lib/statistics/hurst/Hurst.Validation.Tests.cs index aad9adb9..1ba8125d 100644 --- a/lib/statistics/hurst/Hurst.Validation.Tests.cs +++ b/lib/statistics/hurst/Hurst.Validation.Tests.cs @@ -181,4 +181,4 @@ public sealed class HurstValidationTests Assert.Equal(h1.Last.Value, h2.Last.Value, 1e-15); } -} \ No newline at end of file +} diff --git a/lib/statistics/pacf/Pacf.Quantower.Tests.cs b/lib/statistics/pacf/Pacf.Quantower.Tests.cs index 2cd6b541..fd46f1b7 100644 --- a/lib/statistics/pacf/Pacf.Quantower.Tests.cs +++ b/lib/statistics/pacf/Pacf.Quantower.Tests.cs @@ -325,4 +325,4 @@ public class PacfIndicatorTests Assert.Equal(acfValue, pacfValue, 6); // Allow for minor floating-point differences } -} \ No newline at end of file +} diff --git a/lib/statistics/pacf/Pacf.Tests.cs b/lib/statistics/pacf/Pacf.Tests.cs index ff05e20f..f89edc3a 100644 --- a/lib/statistics/pacf/Pacf.Tests.cs +++ b/lib/statistics/pacf/Pacf.Tests.cs @@ -575,4 +575,4 @@ public class PacfTests } #endregion -} \ No newline at end of file +} diff --git a/lib/statistics/pacf/Pacf.Validation.Tests.cs b/lib/statistics/pacf/Pacf.Validation.Tests.cs index 96005cc6..3553268f 100644 --- a/lib/statistics/pacf/Pacf.Validation.Tests.cs +++ b/lib/statistics/pacf/Pacf.Validation.Tests.cs @@ -105,13 +105,16 @@ public class PacfValidationTests // For AR(1) process: x_t = φ*x_{t-1} + ε_t // PACF should be significant at lag 1 and cut off (near zero) after double phi = 0.7; // AR(1) coefficient - var random = new GBM(startPrice: 100.0, sigma: 1.0, seed: 42); - var arProcess = new List { 100.0 }; - // Generate AR(1) process + // Use incremental bar-to-bar log-returns as i.i.d. noise: log(close_i / close_{i-1}) + var gbm = new GBM(startPrice: 100.0, sigma: 0.2, seed: 42); + var bars = gbm.Fetch(501, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)); + var arProcess = new List { 0.0 }; + + // Generate AR(1) process using incremental log-returns as white noise ε for (int i = 1; i < 500; i++) { - double noise = Math.Log(random.Next().Close / 100.0); // ~N(0, vol²*dt) noise + double noise = Math.Log(bars[i].Close / bars[i - 1].Close); // i.i.d. incremental return double newValue = phi * arProcess[^1] + noise; arProcess.Add(newValue); } @@ -345,4 +348,4 @@ public class PacfValidationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/statistics/pacf/Pacf.cs b/lib/statistics/pacf/Pacf.cs index 69586f44..8073ffe6 100644 --- a/lib/statistics/pacf/Pacf.cs +++ b/lib/statistics/pacf/Pacf.cs @@ -294,9 +294,10 @@ public sealed class Pacf : AbstractBase public override void Prime(ReadOnlySpan source, TimeSpan? step = null) { + Reset(); foreach (double value in source) { - Update(new TValue(DateTime.UtcNow, value)); + Update(new TValue(DateTime.MinValue, value)); } } diff --git a/lib/statistics/pacf/Pacf.md b/lib/statistics/pacf/Pacf.md index 8b7f9ab0..6e34020f 100644 --- a/lib/statistics/pacf/Pacf.md +++ b/lib/statistics/pacf/Pacf.md @@ -15,7 +15,7 @@ - Parameterized by `period`, `lag` (default 1). - Output range: Varies (see docs). - Requires `period` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- Validated against mathematical properties and Durbin-Levinson recursion expectations. > "Strip away the intermediaries, and you'll see the true direct relationship." diff --git a/lib/statistics/spearman/Spearman.Validation.Tests.cs b/lib/statistics/spearman/Spearman.Validation.Tests.cs index 53988061..1b98ca58 100644 --- a/lib/statistics/spearman/Spearman.Validation.Tests.cs +++ b/lib/statistics/spearman/Spearman.Validation.Tests.cs @@ -124,4 +124,4 @@ public sealed class SpearmanValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/statistics/stderr/Stderr.md b/lib/statistics/stderr/Stderr.md index e52180f5..202a3fd9 100644 --- a/lib/statistics/stderr/Stderr.md +++ b/lib/statistics/stderr/Stderr.md @@ -11,11 +11,11 @@ ### TL;DR -- ````markdown +- `Stderr` computes the standard error of an OLS regression fit over a rolling window. - Parameterized by `period`. -- Output range: Varies (see docs). -- Requires `period` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- Output range: non-negative real values (or 0 during insufficient/degenerate windows). +- Requires `period` bars of warmup before first stable output (`IsHot = true`). +- Validated against an internal brute-force OLS reference implementation. > "How confident are you in your line of best fit?" @@ -58,16 +58,16 @@ $$ b = \frac{\sum y - m \sum x}{N} $$ ### Operation Count (Streaming Mode) -Standard Error = StdDev / sqrt(N), computed atop the O(1) StdDev computation. +`Stderr` keeps regression sums in O(1), then performs an O(N) residual pass to compute SSR. | Operation | Count | Cost (cycles) | Subtotal | | :--- | :---: | :---: | :---: | -| O(1) StdDev computation | 1 | 28 cy | ~28 cy | -| Divide by sqrt(N) (precomputed) | 1 | 4 cy | ~4 cy | -| NaN guard + state update | 1 | 2 cy | ~2 cy | -| **Total** | **O(1)** | — | **~34 cy** | +| Running-sum updates | O(1) | — | small | +| Residual SSR scan | O(N) | dominant | dominant | +| Final sqrt/divide | O(1) | — | small | +| **Total** | **O(N)** | — | period-dependent | -O(1) per update. sqrt(N) is precomputed in the constructor. Negligible additional cost over StdDev. +Per-update complexity is O(N) because residuals must be re-evaluated for the current window. | Metric | Score | Notes | | :--- | :--- | :--- | @@ -80,8 +80,9 @@ O(1) per update. sqrt(N) is precomputed in the constructor. Negligible additiona | Library | Status | Notes | | :--- | :--- | :--- | -| **TA-Lib** | ✅ | Matches `STDERR` output. | -| **TradingView** | ✅ | Matches Pine Script `ta.stdev` of residuals. | +| **TA-Lib** | ⚠️ | Formula differs (`stderr` in Tulip/other libs often means standard error of mean). | +| **TradingView** | ✅ | Matches Pine-style OLS residual standard error behavior for this implementation. | +| **Reference OLS** | ✅ | Cross-validated against brute-force OLS residual calculation. | ## Usage @@ -101,5 +102,4 @@ double value = stderr.Last.Value; ## See Also * **LinReg** — Linear Regression Curve (the trend line itself). -* **StdDev** — Standard Deviation (dispersion from the mean, not from a regression line). -```` +- **StdDev** — Standard Deviation (dispersion from the mean, not from a regression line). diff --git a/lib/statistics/sum/Sum.Validation.Tests.cs b/lib/statistics/sum/Sum.Validation.Tests.cs index 4f9e2597..3c8690b1 100644 --- a/lib/statistics/sum/Sum.Validation.Tests.cs +++ b/lib/statistics/sum/Sum.Validation.Tests.cs @@ -51,7 +51,7 @@ public sealed class SumValidationTests : IDisposable var qResult = sum.Update(_testData.Data); var retCode = Functions.Sum(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.SumLookback(period); @@ -77,7 +77,7 @@ public sealed class SumValidationTests : IDisposable } var retCode = Functions.Sum(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.SumLookback(period); @@ -99,7 +99,7 @@ public sealed class SumValidationTests : IDisposable Sum.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period); var retCode = Functions.Sum(sourceData, 0..^0, tOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.SumLookback(period); diff --git a/lib/statistics/variance/Variance.cs b/lib/statistics/variance/Variance.cs index eaa17357..6816d125 100644 --- a/lib/statistics/variance/Variance.cs +++ b/lib/statistics/variance/Variance.cs @@ -162,9 +162,10 @@ public sealed class Variance : AbstractBase public override void Prime(ReadOnlySpan source, TimeSpan? step = null) { + Reset(); foreach (double value in source) { - Update(new TValue(DateTime.UtcNow, value)); + Update(new TValue(DateTime.MinValue, value)); } } diff --git a/lib/statistics/zscore/Zscore.Validation.Tests.cs b/lib/statistics/zscore/Zscore.Validation.Tests.cs index 95cf75c2..a214b6e4 100644 --- a/lib/statistics/zscore/Zscore.Validation.Tests.cs +++ b/lib/statistics/zscore/Zscore.Validation.Tests.cs @@ -139,4 +139,4 @@ public sealed class ZscoreValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/statistics/zscore/Zscore.cs b/lib/statistics/zscore/Zscore.cs index 63ca05b5..972ec4f5 100644 --- a/lib/statistics/zscore/Zscore.cs +++ b/lib/statistics/zscore/Zscore.cs @@ -27,13 +27,16 @@ public sealed class Zscore : AbstractBase private readonly RingBuffer _buffer; private readonly TValuePublishedHandler _handler; private double _lastValidValue; - - [StructLayout(LayoutKind.Auto)] - private record struct State(double LastValidZScore, double LastValidValue); - private State _s, _ps; + private double _sumSq; + private double _p_sumSq; + private int _updateCount; + private const int ResyncInterval = 1000; public override bool IsHot => _buffer.Count >= _period; + /// + /// Initializes a rolling z-score indicator. + /// /// Lookback period (default 14, must be >= 2) public Zscore(int period = 14) { @@ -46,11 +49,14 @@ public sealed class Zscore : AbstractBase _buffer = new RingBuffer(period); Name = $"Zscore({period})"; WarmupPeriod = period; - _s = new State(0.0, 0.0); - _ps = _s; + _sumSq = 0.0; + _p_sumSq = 0.0; _handler = Handle; } + /// + /// Initializes a rolling z-score indicator and subscribes it to a source publisher. + /// /// Source indicator for event-based chaining /// Lookback period (default 14) public Zscore(ITValuePublisher source, int period = 14) : this(period) @@ -61,16 +67,6 @@ public sealed class Zscore : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public override TValue Update(TValue input, bool isNew = true) { - if (isNew) - { - _ps = _s; - } - else - { - _s = _ps; - _lastValidValue = _s.LastValidValue; - } - double value = input.Value; if (!double.IsFinite(value)) @@ -82,11 +78,37 @@ public sealed class Zscore : AbstractBase _lastValidValue = value; } - _buffer.Add(value, isNew); + if (isNew) + { + _p_sumSq = _sumSq; + _buffer.Snapshot(); + } + else + { + _sumSq = _p_sumSq; + _buffer.Restore(); + } + + if (_buffer.IsFull) + { + double oldVal = _buffer.Oldest; + _sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, _sumSq); + } + + _buffer.Add(value); + _sumSq = Math.FusedMultiplyAdd(value, value, _sumSq); + + if (isNew) + { + _updateCount++; + if (_updateCount % ResyncInterval == 0) + { + Resync(); + } + } double result; - ReadOnlySpan data = _buffer.GetSpan(); - int n = data.Length; + int n = _buffer.Count; if (n < 2) { @@ -94,25 +116,16 @@ public sealed class Zscore : AbstractBase } else { - double sum = 0.0; - double sumSq = 0.0; - - for (int i = 0; i < n; i++) - { - double v = data[i]; - sum += v; - sumSq += v * v; - } - + double sum = _buffer.Sum; double mean = sum / n; - // Population variance: E[X²] - (E[X])² - double popVariance = (sumSq / n) - (mean * mean); - if (popVariance < 0.0) + double numerator = _sumSq - (sum * sum) / n; + if (numerator < 0) { - popVariance = 0.0; + numerator = 0; } + double popVariance = numerator / n; double stdDev = Math.Sqrt(popVariance); if (stdDev > 1e-10) @@ -125,7 +138,6 @@ public sealed class Zscore : AbstractBase } } - _s = new State(result, _lastValidValue); Last = new TValue(input.Time, result); PubEvent(Last, isNew); return Last; @@ -133,17 +145,30 @@ public sealed class Zscore : AbstractBase public override TSeries Update(TSeries source) { - var result = new TSeries(source.Count); - ReadOnlySpan values = source.Values; - ReadOnlySpan times = source.Times; - - for (int i = 0; i < source.Count; i++) + if (source.Count == 0) { - var tv = Update(new TValue(new DateTime(times[i], DateTimeKind.Utc), values[i]), true); - result.Add(tv, true); + return new TSeries(); } - return result; + int len = source.Count; + var t = new List(len); + var v = new List(len); + CollectionsMarshal.SetCount(t, len); + CollectionsMarshal.SetCount(v, len); + + var tSpan = CollectionsMarshal.AsSpan(t); + var vSpan = CollectionsMarshal.AsSpan(v); + + Batch(source.Values, vSpan, _buffer.Capacity); + source.Times.CopyTo(tSpan); + + int primeStart = Math.Max(0, len - _buffer.Capacity); + for (int i = primeStart; i < len; i++) + { + Update(source[i]); + } + + return new TSeries(t, v); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -153,11 +178,24 @@ public sealed class Zscore : AbstractBase { _buffer.Clear(); _lastValidValue = 0; - _s = new State(0.0, 0.0); - _ps = _s; + _sumSq = 0.0; + _p_sumSq = 0.0; + _updateCount = 0; Last = default; } + private void Resync() + { + var span = _buffer.GetSpan(); + double sumSq = 0; + for (int i = 0; i < span.Length; i++) + { + sumSq += span[i] * span[i]; + } + _sumSq = sumSq; + _buffer.RecalculateSum(); + } + public override void Prime(ReadOnlySpan source, TimeSpan? step = null) { TimeSpan interval = step ?? TimeSpan.FromSeconds(1); @@ -214,6 +252,8 @@ public sealed class Zscore : AbstractBase int head = 0; int count = 0; double lastValid = 0.0; + double sum = 0.0; + double sumSq = 0.0; for (int i = 0; i < source.Length; i++) { @@ -228,43 +268,53 @@ public sealed class Zscore : AbstractBase lastValid = val; } - if (count < ringSize) + if (count == ringSize) { - ring[count] = val; - count++; + double oldVal = ring[head]; + sum = Math.FusedMultiplyAdd(-1.0, oldVal, sum + val); + sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, sumSq); } else { - ring[head] = val; + count++; + sum += val; } + ring[head] = val; + sumSq = Math.FusedMultiplyAdd(val, val, sumSq); + head = (head + 1) % ringSize; + if ((i + 1) % 1000 == 0 && count == ringSize) + { + double resyncSum = 0; + double resyncSumSq = 0; + for (int j = 0; j < ringSize; j++) + { + double v = ring[j]; + resyncSum += v; + resyncSumSq += v * v; + } + sum = resyncSum; + sumSq = resyncSumSq; + } + if (count < 2) { output[i] = 0.0; continue; } - double sum = 0.0; - double sumSq = 0.0; int n = count; - - for (int j = 0; j < n; j++) - { - double v = ring[j]; - sum += v; - sumSq += v * v; - } - double mean = sum / n; - double popVariance = (sumSq / n) - (mean * mean); - if (popVariance < 0.0) + double numerator = sumSq - (sum * sum) / n; + if (numerator < 0) { - popVariance = 0.0; + numerator = 0; } + double popVariance = numerator / n; double stdDev = Math.Sqrt(popVariance); if (stdDev > 1e-10) diff --git a/lib/statistics/zscore/Zscore.md b/lib/statistics/zscore/Zscore.md index 6699aa97..7c5e8e7c 100644 --- a/lib/statistics/zscore/Zscore.md +++ b/lib/statistics/zscore/Zscore.md @@ -15,7 +15,7 @@ - Parameterized by `period` (default 14). - Output range: Unbounded. - Requires `period` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- Validated against manual computation, PineScript parity, and statistical invariants. > "How far from normal is this?" — Every risk manager, every day. diff --git a/lib/statistics/ztest/Ztest.Tests.cs b/lib/statistics/ztest/Ztest.Tests.cs index 984bcb69..fd6507ab 100644 --- a/lib/statistics/ztest/Ztest.Tests.cs +++ b/lib/statistics/ztest/Ztest.Tests.cs @@ -354,7 +354,7 @@ public class ZtestTests for (int i = 0; i < count; i++) { - Assert.Equal(batchResult[i].Value, spanOutput[i], 1e-4); // t-stat magnifies FP drift (values ~6000) + Assert.Equal(batchResult[i].Value, spanOutput[i], 5e-4); // t-stat magnifies FP drift (values ~15000) } } diff --git a/lib/statistics/ztest/Ztest.cs b/lib/statistics/ztest/Ztest.cs index ea8b313b..c445a40a 100644 --- a/lib/statistics/ztest/Ztest.cs +++ b/lib/statistics/ztest/Ztest.cs @@ -29,13 +29,16 @@ public sealed class Ztest : AbstractBase private readonly RingBuffer _buffer; private readonly TValuePublishedHandler _handler; private double _lastValidValue; - - [StructLayout(LayoutKind.Auto)] - private record struct State(double LastValidTStat, double LastValidValue); - private State _s, _ps; + private double _sumSq; + private double _p_sumSq; + private int _updateCount; + private const int ResyncInterval = 1000; public override bool IsHot => _buffer.Count >= _period; + /// + /// Initializes a rolling one-sample t-test indicator. + /// /// Lookback period (default 30, must be >= 2) /// Hypothesized population mean (default 0.0) public Ztest(int period = 30, double mu0 = 0.0) @@ -50,11 +53,14 @@ public sealed class Ztest : AbstractBase _buffer = new RingBuffer(period); Name = $"Ztest({period},{mu0:G})"; WarmupPeriod = period; - _s = new State(0.0, 0.0); - _ps = _s; + _sumSq = 0.0; + _p_sumSq = 0.0; _handler = Handle; } + /// + /// Initializes a rolling one-sample t-test indicator and subscribes it to a source publisher. + /// /// Source indicator for event-based chaining /// Lookback period (default 30) /// Hypothesized population mean (default 0.0) @@ -66,16 +72,6 @@ public sealed class Ztest : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public override TValue Update(TValue input, bool isNew = true) { - if (isNew) - { - _ps = _s; - } - else - { - _s = _ps; - _lastValidValue = _s.LastValidValue; - } - double value = input.Value; if (!double.IsFinite(value)) @@ -87,11 +83,37 @@ public sealed class Ztest : AbstractBase _lastValidValue = value; } - _buffer.Add(value, isNew); + if (isNew) + { + _p_sumSq = _sumSq; + _buffer.Snapshot(); + } + else + { + _sumSq = _p_sumSq; + _buffer.Restore(); + } + + if (_buffer.IsFull) + { + double oldVal = _buffer.Oldest; + _sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, _sumSq); + } + + _buffer.Add(value); + _sumSq = Math.FusedMultiplyAdd(value, value, _sumSq); + + if (isNew) + { + _updateCount++; + if (_updateCount % ResyncInterval == 0) + { + Resync(); + } + } double result; - ReadOnlySpan data = _buffer.GetSpan(); - int n = data.Length; + int n = _buffer.Count; if (n < 2) { @@ -99,27 +121,19 @@ public sealed class Ztest : AbstractBase } else { - double sum = 0.0; - double sumSq = 0.0; - - for (int i = 0; i < n; i++) - { - double v = data[i]; - sum += v; - sumSq += v * v; - } - + double sum = _buffer.Sum; double mean = sum / n; - // Population variance first: E[X²] - (E[X])² - double popVariance = (sumSq / n) - (mean * mean); - if (popVariance < 0.0) + double numerator = _sumSq - (sum * sum) / n; + if (numerator < 0) { - popVariance = 0.0; + numerator = 0; } // Bessel correction: sample variance = popVariance * n / (n - 1) - double sampleStdDev = Math.Sqrt(popVariance * n / (n - 1)); + // which is numerator / (n - 1) + double sampleVariance = numerator / (n - 1); + double sampleStdDev = Math.Sqrt(sampleVariance); double standardError = sampleStdDev / Math.Sqrt(n); if (standardError > 1e-10) @@ -132,7 +146,6 @@ public sealed class Ztest : AbstractBase } } - _s = new State(result, _lastValidValue); Last = new TValue(input.Time, result); PubEvent(Last, isNew); return Last; @@ -140,17 +153,30 @@ public sealed class Ztest : AbstractBase public override TSeries Update(TSeries source) { - var result = new TSeries(source.Count); - ReadOnlySpan values = source.Values; - ReadOnlySpan times = source.Times; - - for (int i = 0; i < source.Count; i++) + if (source.Count == 0) { - var tv = Update(new TValue(new DateTime(times[i], DateTimeKind.Utc), values[i]), true); - result.Add(tv, true); + return new TSeries(); } - return result; + int len = source.Count; + var t = new List(len); + var v = new List(len); + CollectionsMarshal.SetCount(t, len); + CollectionsMarshal.SetCount(v, len); + + var tSpan = CollectionsMarshal.AsSpan(t); + var vSpan = CollectionsMarshal.AsSpan(v); + + Batch(source.Values, vSpan, _buffer.Capacity, _mu0); + source.Times.CopyTo(tSpan); + + int primeStart = Math.Max(0, len - _buffer.Capacity); + for (int i = primeStart; i < len; i++) + { + Update(source[i]); + } + + return new TSeries(t, v); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -160,11 +186,24 @@ public sealed class Ztest : AbstractBase { _buffer.Clear(); _lastValidValue = 0; - _s = new State(0.0, 0.0); - _ps = _s; + _sumSq = 0.0; + _p_sumSq = 0.0; + _updateCount = 0; Last = default; } + private void Resync() + { + var span = _buffer.GetSpan(); + double sumSq = 0; + for (int i = 0; i < span.Length; i++) + { + sumSq += span[i] * span[i]; + } + _sumSq = sumSq; + _buffer.RecalculateSum(); + } + public override void Prime(ReadOnlySpan source, TimeSpan? step = null) { TimeSpan interval = step ?? TimeSpan.FromSeconds(1); @@ -221,6 +260,8 @@ public sealed class Ztest : AbstractBase int head = 0; int count = 0; double lastValid = 0.0; + double sum = 0.0; + double sumSq = 0.0; for (int i = 0; i < source.Length; i++) { @@ -235,45 +276,55 @@ public sealed class Ztest : AbstractBase lastValid = val; } - if (count < ringSize) + if (count == ringSize) { - ring[count] = val; - count++; + double oldVal = ring[head]; + sum = Math.FusedMultiplyAdd(-1.0, oldVal, sum + val); + sumSq = Math.FusedMultiplyAdd(-oldVal, oldVal, sumSq); } else { - ring[head] = val; + count++; + sum += val; } + ring[head] = val; + sumSq = Math.FusedMultiplyAdd(val, val, sumSq); + head = (head + 1) % ringSize; + if ((i + 1) % 1000 == 0 && count == ringSize) + { + double resyncSum = 0; + double resyncSumSq = 0; + for (int j = 0; j < ringSize; j++) + { + double v = ring[j]; + resyncSum += v; + resyncSumSq += v * v; + } + sum = resyncSum; + sumSq = resyncSumSq; + } + if (count < 2) { output[i] = 0.0; continue; } - double sum = 0.0; - double sumSq = 0.0; int n = count; - - for (int j = 0; j < n; j++) - { - double v = ring[j]; - sum += v; - sumSq += v * v; - } - double mean = sum / n; - double popVariance = (sumSq / n) - (mean * mean); - if (popVariance < 0.0) + double numerator = sumSq - (sum * sum) / n; + if (numerator < 0) { - popVariance = 0.0; + numerator = 0; } // Bessel correction: sample variance = popVariance * n / (n - 1) - double sampleStdDev = Math.Sqrt(popVariance * n / (n - 1)); + double sampleVariance = numerator / (n - 1); + double sampleStdDev = Math.Sqrt(sampleVariance); double standardError = sampleStdDev / Math.Sqrt(n); if (standardError > 1e-10) diff --git a/lib/statistics/ztest/Ztest.md b/lib/statistics/ztest/Ztest.md index 7ccfed18..b75b20d2 100644 --- a/lib/statistics/ztest/Ztest.md +++ b/lib/statistics/ztest/Ztest.md @@ -15,7 +15,7 @@ - Parameterized by `period` (default 30), `mu0` (default 0.0). - Output range: Unbounded. - Requires `period` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- Validated against manual computation, PineScript parity, and testable statistical properties. > "The purpose of hypothesis testing is not to prove what we believe, but to measure what we observe." — Adapted from R.A. Fisher @@ -91,21 +91,12 @@ The indicators answer different questions: ### Operation Count (Streaming Mode) -Z-Test computes a rolling mean and standard deviation for O(1) hypothesis testing per bar. - -| Operation | Count | Cost (cycles) | Subtotal | -| :--- | :---: | :---: | :---: | -| O(1) StdDev computation | 1 | 28 cy | ~28 cy | -| Compute Z = (x - mu) / (sigma / sqrt(N)) | 1 | 5 cy | ~5 cy | -| NaN guard (sigma = 0 guard) | 1 | 2 cy | ~2 cy | -| **Total** | **O(1)** | — | **~35 cy** | - -O(1) per update. Z-statistic is a trivial transformation of the running mean and standard deviation already computed by StdDev. +ZTEST uses a rolling window with running sums and periodic resynchronization. | Operation | Complexity | Notes | |-----------|-----------|-------| -| Update (streaming) | $O(n)$ | Full window scan for sum/sumSq | -| Batch (span) | $O(N \cdot p)$ | N data points, p period | +| Update (streaming) | $O(1)$ amortized | Running sum/sumSq maintenance; periodic full resync every 1000 updates | +| Batch (span) | $O(N)$ | Single pass over source with O(1) ring maintenance per element | | Memory | $O(p)$ | RingBuffer + scalar state | | Allocations per update | 0 | Zero-allocation hot path | diff --git a/lib/trends_FIR/dwma/Dwma.Validation.Tests.cs b/lib/trends_FIR/dwma/Dwma.Validation.Tests.cs index bab2be0b..a568b5e3 100644 --- a/lib/trends_FIR/dwma/Dwma.Validation.Tests.cs +++ b/lib/trends_FIR/dwma/Dwma.Validation.Tests.cs @@ -163,7 +163,7 @@ public sealed class DwmaValidationTests : IDisposable // TA-Lib WMA 1 double[] wma1Output = new double[_testData.RawData.Length]; var retCode1 = TALib.Functions.Wma(_testData.RawData.Span, 0..^0, wma1Output, out var outRange1, period); - Assert.Equal(Core.RetCode.Success, retCode1); + Assert.Equal(TALib.Core.RetCode.Success, retCode1); // Prepare input for WMA 2 (only valid data from WMA 1) int count1 = outRange1.End.Value - outRange1.Start.Value; @@ -173,7 +173,7 @@ public sealed class DwmaValidationTests : IDisposable // TA-Lib WMA 2 double[] dwmaOutput = new double[wma1Valid.Length]; var retCode2 = TALib.Functions.Wma(wma1Valid, 0..^0, dwmaOutput, out _, period); - Assert.Equal(Core.RetCode.Success, retCode2); + Assert.Equal(TALib.Core.RetCode.Success, retCode2); int totalLookback = (period - 1) * 2; ValidationHelper.VerifyData(qResult, dwmaOutput, totalLookback, tolerance: ValidationHelper.TalibTolerance); diff --git a/lib/trends_FIR/lsma/Lsma.Validation.Tests.cs b/lib/trends_FIR/lsma/Lsma.Validation.Tests.cs index 751d9718..7cc8f815 100644 --- a/lib/trends_FIR/lsma/Lsma.Validation.Tests.cs +++ b/lib/trends_FIR/lsma/Lsma.Validation.Tests.cs @@ -97,4 +97,4 @@ public class LsmaValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/trends_FIR/sma/Sma.Validation.Tests.cs b/lib/trends_FIR/sma/Sma.Validation.Tests.cs index 7f17bdec..0d9899ea 100644 --- a/lib/trends_FIR/sma/Sma.Validation.Tests.cs +++ b/lib/trends_FIR/sma/Sma.Validation.Tests.cs @@ -122,7 +122,7 @@ public sealed class SmaValidationTests : IDisposable // Calculate TA-Lib SMA var retCode = TALib.Functions.Sma(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.SmaLookback(period); @@ -153,7 +153,7 @@ public sealed class SmaValidationTests : IDisposable // Calculate TA-Lib SMA var retCode = TALib.Functions.Sma(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.SmaLookback(period); @@ -180,7 +180,7 @@ public sealed class SmaValidationTests : IDisposable // Calculate TA-Lib SMA var retCode = TALib.Functions.Sma(sourceData, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.SmaLookback(period); diff --git a/lib/trends_FIR/trima/Trima.Tolerance.Tests.cs b/lib/trends_FIR/trima/Trima.Tolerance.Tests.cs index 00ee884e..255894fd 100644 --- a/lib/trends_FIR/trima/Trima.Tolerance.Tests.cs +++ b/lib/trends_FIR/trima/Trima.Tolerance.Tests.cs @@ -25,7 +25,7 @@ public sealed class TrimaToleranceTests : IDisposable double[] output = new double[_testData.RawData.Length]; var retCode = TALib.Functions.Trima(_testData.RawData.Span, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.TrimaLookback(period); diff --git a/lib/trends_FIR/trima/Trima.Validation.Tests.cs b/lib/trends_FIR/trima/Trima.Validation.Tests.cs index 06d844e9..9b975948 100644 --- a/lib/trends_FIR/trima/Trima.Validation.Tests.cs +++ b/lib/trends_FIR/trima/Trima.Validation.Tests.cs @@ -67,7 +67,7 @@ public class TrimaValidationTests // Calculate TA-Lib TRIMA var retCode = TALib.Functions.Trima(_testData.RawData.Span, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.TrimaLookback(period); @@ -127,7 +127,7 @@ public class TrimaValidationTests // Calculate TA-Lib TRIMA var retCode = TALib.Functions.Trima(_testData.RawData.Span, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.TrimaLookback(period); diff --git a/lib/trends_FIR/wma/Wma.Validation.Tests.cs b/lib/trends_FIR/wma/Wma.Validation.Tests.cs index 42c98475..a9b8bfd5 100644 --- a/lib/trends_FIR/wma/Wma.Validation.Tests.cs +++ b/lib/trends_FIR/wma/Wma.Validation.Tests.cs @@ -109,7 +109,7 @@ public sealed class WmaValidationTests : IDisposable var retCode = TALib.Functions.Wma( tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.WmaLookback(period); diff --git a/lib/trends_IIR/dema/Dema.Validation.Tests.cs b/lib/trends_IIR/dema/Dema.Validation.Tests.cs index cf339950..eeb4a99c 100644 --- a/lib/trends_IIR/dema/Dema.Validation.Tests.cs +++ b/lib/trends_IIR/dema/Dema.Validation.Tests.cs @@ -73,7 +73,7 @@ public sealed class DemaValidationTests : IDisposable // Calculate TA-Lib DEMA var retCode = TALib.Functions.Dema(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.DemaLookback(period); @@ -144,7 +144,7 @@ public sealed class DemaValidationTests : IDisposable // Calculate TA-Lib DEMA var retCode = TALib.Functions.Dema(sourceData, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.DemaLookback(period); diff --git a/lib/trends_IIR/dema/Dema.md b/lib/trends_IIR/dema/Dema.md index 85ff22ff..10d5170b 100644 --- a/lib/trends_IIR/dema/Dema.md +++ b/lib/trends_IIR/dema/Dema.md @@ -1,25 +1,36 @@ # DEMA: Double Exponential Moving Average +> "EMA is good. DEMA is better. It's like an EMA that drank a double espresso and stopped lagging behind the conversation." + +## Quick Reference + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | | **Inputs** | Source (close) | -| **Parameters** | `period` | -| **Outputs** | Single series (Dema) | -| **Output range** | Tracks input | -| **Warmup** | `period` bars | +| **Parameters** | `period` (int > 0) | +| **Outputs** | Single series (Dema) | +| **Output range** | Tracks input | +| **Warmup** | `period` bars | -### TL;DR +## Key Takeaways -- DEMA (Double Exponential Moving Average) is not just "two EMAs." It's a clever mathematical hack to cancel out the lag inherent in a standard EMA. -- Parameterized by `period`. -- Output range: Tracks input. -- Requires `period` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- **Lag Reduction**: Cuts EMA lag by ~50% through mathematical extrapolation +- **Trend Hugging**: Tighter fit to price action than standard EMA +- **Overshoot Risk**: Can amplify reversals due to predictive nature +- **Computational Cost**: ~2× EMA operations for the dual-stage design +- **Best For**: Trend-following systems needing responsive signals -> "EMA is good. DEMA is better. It's like an EMA that drank a double espresso and stopped lagging behind the conversation." +## What It Measures and Why It Matters -DEMA (Double Exponential Moving Average) is not just "two EMAs." It's a clever mathematical hack to cancel out the lag inherent in a standard EMA. By subtracting the "error" (the difference between a single EMA and a double EMA) from the original EMA, DEMA produces a curve that hugs the price action much tighter. The extrapolation formula $2 \times \text{EMA}_1 - \text{EMA}_2$ effectively predicts where EMA "should be" based on its current trajectory. +DEMA measures the smoothed trend of price action with significantly reduced lag compared to traditional moving averages. It matters because lag is the enemy of timely signals—traditional EMAs lag by roughly N/2 bars, making them slow to react to trend changes. DEMA's extrapolation formula (2×EMA₁ - EMA₂) mathematically projects the EMA forward by one lag unit, creating a "lead indicator" that anticipates rather than follows. + +This makes DEMA particularly valuable for: + +- **Trend-following strategies** requiring quick entries/exits +- **Oscillator construction** (MACD uses DEMA variants) +- **Signal generation** where timeliness trumps smoothness +- **High-frequency trading** where every bar counts ## Historical Context @@ -53,6 +64,32 @@ $$\text{DEMA} = 2 \times \text{EMA}_1 - \text{EMA}_2$$ The "physics" relies on the fact that EMA2 lags EMA1 roughly as much as EMA1 lags the price. The coefficient 2 on EMA1 and -1 on EMA2 creates a unity-gain filter ($2 - 1 = 1$) that projects forward by one lag unit. +## Interpretation and Signals + +### Trend Direction + +- **Above Price**: Bullish trend signal (DEMA acting as support) +- **Below Price**: Bearish trend signal (DEMA acting as resistance) +- **Slope Analysis**: Positive slope = uptrend, negative slope = downtrend + +### Crossover Signals + +- **Price crosses above DEMA**: Potential buy signal in uptrends +- **Price crosses below DEMA**: Potential sell signal in downtrends +- **Zero crossings**: Momentum shifts (less reliable than EMA due to overshoot) + +### Divergence Analysis + +- **Bullish Divergence**: Price makes lower low, DEMA makes higher low +- **Bearish Divergence**: Price makes higher high, DEMA makes lower high +- **Convergence**: Price and DEMA moving toward each other (caution signal) + +### Signal Quality Factors + +- **Strength**: Distance between price and DEMA (larger = stronger trend) +- **Consistency**: How long the trend has maintained direction +- **Volume Confirmation**: Higher volume on breakouts improves reliability + ## Mathematical Foundation ### EMA Alpha Calculation @@ -66,6 +103,7 @@ For a single EMA with smoothing factor $\alpha$, the mean lag is: $$L = \frac{1 - \alpha}{\alpha} = \frac{N - 1}{2}$$ For cascaded EMAs: + - EMA1 lag: $L$ - EMA2 lag (from price): $2L$ @@ -82,7 +120,6 @@ In the z-domain, DEMA's transfer function: $$H(z) = 2 \cdot H_{EMA}(z) - H_{EMA}^2(z)$$ where $H_{EMA}(z) = \frac{\alpha}{1 - (1-\alpha)z^{-1}}$ - ## Performance Profile ### Operation Count (Streaming Mode) @@ -127,6 +164,47 @@ Due to the recursive nature of EMA, SIMD vectorization is limited. However, FMA *Benchmarked on Intel i7-12700K @ 3.6 GHz, AVX2, .NET 10.0* +## Related Indicators + +- **EMA**: Single exponential smoothing (higher lag, smoother) +- **TEMA**: Triple exponential (even less lag, more overshoot) +- **KAMA**: Adaptive smoothing based on volatility +- **VIDYA**: Variable index dynamic average +- **WMA**: Weighted moving average (FIR, no lag reduction) +- **SMA**: Simple moving average (maximum lag) + +## Usage Examples + +### Basic Trend Following + +```csharp +var dema = new Dema(source, 20); +if (price > dema.Last.Value && dema.Last.Value > dema.Previous.Value) +{ + // Bullish trend confirmed + EnterLong(); +} +``` + +### MACD Construction + +```csharp +// DEMA is often used in MACD for signal line +var fast = new Dema(source, 12); +var slow = new Dema(source, 26); +var macd = fast.Last.Value - slow.Last.Value; +var signal = new Dema(new TSeries() { macd }, 9); +``` + +### Adaptive Period Selection + +```csharp +// Shorter periods for ranging markets, longer for trending +var volatility = CalculateVolatility(source); +int period = volatility > threshold ? 10 : 20; // Responsive in trends +var dema = new Dema(source, period); +``` + ## Validation | Library | Status | Notes | @@ -236,6 +314,35 @@ else Both EMA states are rolled back atomically for consistent correction. +## Reference Calculation Table + +| Period | Price Sequence | EMA₁ | EMA₂ | DEMA | Notes | +|--------|----------------|------|------|------|-------| +| 5 | 10 | 10.00 | 10.00 | 10.00 | Initial values | +| 5 | 10, 20 | 13.33 | 11.11 | 15.56 | First calculation | +| 5 | 10, 20, 30 | 18.52 | 13.58 | 23.46 | Trend acceleration | +| 5 | 10, 20, 30, 40 | 24.69 | 16.80 | 32.58 | Extrapolation effect | +| 5 | 10, 20, 30, 40, 50 | 31.13 | 20.74 | 41.52 | Full convergence | + +*α = 2/(5+1) = 0.333, Decay = 1-α = 0.667* + +## FAQ + +**Q: How does DEMA compare to EMA for period N?** +A: DEMA(N) responds roughly like EMA(N×0.7) but with more overshoot. The lag reduction makes it faster but noisier. + +**Q: When should I use DEMA vs TEMA?** +A: DEMA for most cases—it's 80% of TEMA's lag reduction with 50% less computation. Use TEMA only if DEMA still lags too much. + +**Q: Does DEMA work well in sideways markets?** +A: Poorly. The extrapolation amplifies noise, creating false signals. Combine with trend strength filters. + +**Q: Can DEMA be used for any period?** +A: Yes, but very short periods (<5) amplify noise excessively. Very long periods (>50) lose the lag-reduction benefit. + +**Q: How does bar correction work?** +A: When `isNew=false`, QuanTAlib rolls back both EMA states to pre-update values, then reapplies the correction. This ensures identical results regardless of update order. + ## Common Pitfalls 1. **Overshoot on Reversals**: Because DEMA extrapolates using the EMA "velocity," it overshoots when price reverses direction. This is the fundamental tradeoff for reduced lag—the filter commits to trends and resists reversals. diff --git a/lib/trends_IIR/dsma/Dsma.Validation.Tests.cs b/lib/trends_IIR/dsma/Dsma.Validation.Tests.cs index 8f22bb82..c70a3074 100644 --- a/lib/trends_IIR/dsma/Dsma.Validation.Tests.cs +++ b/lib/trends_IIR/dsma/Dsma.Validation.Tests.cs @@ -355,4 +355,4 @@ public class DsmaValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/ema/Ema.Validation.Tests.cs b/lib/trends_IIR/ema/Ema.Validation.Tests.cs index 1ede1d78..fa068596 100644 --- a/lib/trends_IIR/ema/Ema.Validation.Tests.cs +++ b/lib/trends_IIR/ema/Ema.Validation.Tests.cs @@ -122,7 +122,7 @@ public sealed class EmaValidationTests : IDisposable // Calculate TA-Lib EMA var retCode = TALib.Functions.Ema(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.EmaLookback(period); @@ -153,7 +153,7 @@ public sealed class EmaValidationTests : IDisposable // Calculate TA-Lib EMA var retCode = TALib.Functions.Ema(tData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.EmaLookback(period); @@ -180,7 +180,7 @@ public sealed class EmaValidationTests : IDisposable // Calculate TA-Lib EMA var retCode = TALib.Functions.Ema(sourceData, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.EmaLookback(period); diff --git a/lib/trends_IIR/ema/Ema.md b/lib/trends_IIR/ema/Ema.md index f9cb42ca..07a4009d 100644 --- a/lib/trends_IIR/ema/Ema.md +++ b/lib/trends_IIR/ema/Ema.md @@ -1,25 +1,56 @@ # EMA: Exponential Moving Average +> "The SMA drops an old price, the average jumps, the signal fires, the market does something unhelpful. The EMA exists because someone finally asked: what if old data just... mattered less?" + +## Quick Reference + | Property | Value | | ---------------- | -------------------------------- | | **Category** | Trend (IIR MA) | | **Inputs** | Source (close) | -| **Parameters** | `period` | -| **Outputs** | Single series (Ema) | -| **Output range** | Tracks input | -| **Warmup** | `period` bars | +| **Parameters** | `period` (int > 0) | +| **Outputs** | Single series (Ema) | +| **Output range** | Tracks input | +| **Warmup** | `period` bars | -### TL;DR +## Key Takeaways -- The Exponential Moving Average is the reference standard for trend-following indicators. -- Parameterized by `period`. -- Output range: Tracks input. -- Requires `period` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. +- **Lag Reduction**: Cuts SMA lag by ~50% through exponential weighting +- **Smooth Response**: Reacts faster to price changes than SMA +- **No Drop-off Effect**: Eliminates window boundary discontinuities +- **Bias Compensation**: Mathematically correct warmup (unlike most libraries) +- **Computational Efficiency**: O(1) per update with FMA optimization +- **Universal Standard**: Foundation for MACD, RSI, and countless other indicators -> "The SMA drops an old price, the average jumps, the signal fires, the market does something unhelpful. The EMA exists because someone finally asked: what if old data just... mattered less?" +## What It Measures and Why It Matters -The Exponential Moving Average is the reference standard for trend-following indicators. Unlike the SMA, which treats data from 10 days ago with the same reverence as data from 10 seconds ago (a touching but mathematically questionable form of loyalty), the EMA applies exponentially decaying weights to older prices. The result: faster reaction to new information without the "drop-off effect" that makes SMA users twitch nervously around window boundaries. Simple, well-understood, computationally cheap. The indicator equivalent of a reliable sedan: not glamorous, but it starts every morning. +The EMA measures the exponentially weighted average trend of price action, giving more importance to recent data while never completely discarding historical information. It matters because traditional simple moving averages suffer from the "drop-off effect"—sudden jumps when old data expires from the calculation window. The EMA's infinite impulse response eliminates this discontinuity, providing smoother, more reliable trend signals. This makes it the gold standard for trend-following systems, serving as the computational backbone for most technical analysis tools. + +## Interpretation and Signals + +### Trend Direction + +- **Above Price**: Potential uptrend (EMA as support) +- **Below Price**: Potential downtrend (EMA as resistance) +- **Slope Analysis**: Positive slope = bullish momentum, negative slope = bearish + +### Crossover Signals + +- **Price crosses above EMA**: Bullish momentum signal +- **Price crosses below EMA**: Bearish momentum signal +- **Multiple EMAs**: Fast EMA over slow EMA = bullish trend + +### Divergence Analysis + +- **Bullish Divergence**: Price makes lower low, EMA makes higher low +- **Bearish Divergence**: Price makes higher high, EMA makes lower high +- **Hidden Divergence**: Price makes higher low, EMA makes lower low (continuation) + +### Signal Quality Factors + +- **Trend Strength**: Distance between price and EMA +- **Slope Steepness**: Rate of EMA angle change +- **Volume Confirmation**: Higher volume validates EMA breakouts ## Historical Context @@ -165,6 +196,45 @@ QuanTAlib matches C-based libraries (Tulip, TA-Lib) in throughput while providin | **Overshoot** | 8/10 | Minimal on reversals | | **Smoothness** | 7/10 | Good noise rejection | +## Related Indicators + +- **SMA**: Simple moving average (equal weights, maximum lag) +- **DEMA**: Double exponential (less lag, more overshoot) +- **TEMA**: Triple exponential (minimum lag, maximum overshoot) +- **WMA**: Weighted moving average (linear decay, FIR) +- **KAMA**: Adaptive smoothing based on volatility +- **VIDYA**: Variable index dynamic average +- **HMA**: Hull moving average (triple smoothing) + +## Reference Calculation Table + +| Period | Price Sequence | α | EMA Values | Notes | +|--------|----------------|---|------------|-------| +| 5 | 10 | 0.333 | 10.00 | Initial value | +| 5 | 10, 20 | 0.333 | 10.00, 13.33 | First calculation | +| 5 | 10, 20, 30 | 0.333 | 10.00, 13.33, 18.52 | Trend acceleration | +| 5 | 10, 20, 30, 40 | 0.333 | 10.00, 13.33, 18.52, 24.69 | Convergence | +| 5 | 10, 20, 30, 40, 50 | 0.333 | 10.00, 13.33, 18.52, 24.69, 31.13 | Full convergence | + +*α = 2/(5+1) = 0.333, Decay = 1-α = 0.667* + +## FAQ + +**Q: How does EMA differ from SMA?** +A: EMA gives exponentially decreasing weights to older data, eliminating the "drop-off effect" where SMA jumps when old data expires. EMA responds faster and smoother. + +**Q: Why does QuanTAlib's EMA differ from other libraries initially?** +A: QuanTAlib uses mathematical bias compensation for accurate warmup values. Other libraries approximate. Results converge after ~3×period bars. + +**Q: What's the optimal EMA period?** +A: No universal optimum. Shorter periods (<10) for scalping, longer periods (>50) for trend following. Match to your timeframe and strategy horizon. + +**Q: Can EMA be used for mean reversion?** +A: Poorly. EMA follows trends. For mean reversion, consider Bollinger Bands or RSI around EMA levels. + +**Q: How does bar correction work?** +A: Use `isNew=false` when updating the same bar with revised prices. QuanTAlib maintains previous state for atomic rollback. + ## Validation Validated against external libraries in `Ema.Validation.Tests.cs`. Tests run against 5,000 bars with tolerance of 1e-9. diff --git a/lib/trends_IIR/frama/Frama.Validation.Tests.cs b/lib/trends_IIR/frama/Frama.Validation.Tests.cs index 507a82e5..9527d7ea 100644 --- a/lib/trends_IIR/frama/Frama.Validation.Tests.cs +++ b/lib/trends_IIR/frama/Frama.Validation.Tests.cs @@ -200,4 +200,4 @@ public class FramaValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/htit/Htit.Validation.Tests.cs b/lib/trends_IIR/htit/Htit.Validation.Tests.cs index 29d37436..0047e18c 100644 --- a/lib/trends_IIR/htit/Htit.Validation.Tests.cs +++ b/lib/trends_IIR/htit/Htit.Validation.Tests.cs @@ -43,7 +43,7 @@ public sealed class HtitValidationTests : IDisposable var output = new double[input.Length]; var retCode = TALib.Functions.HtTrendline(input, 0..^0, output, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); // Calculate QuanTAlib HTIT var htit = new Htit(); diff --git a/lib/trends_IIR/jma/Jma.Validation.Tests.cs b/lib/trends_IIR/jma/Jma.Validation.Tests.cs index 9fdcfdc3..32728ead 100644 --- a/lib/trends_IIR/jma/Jma.Validation.Tests.cs +++ b/lib/trends_IIR/jma/Jma.Validation.Tests.cs @@ -69,4 +69,4 @@ public class JmaValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/kama/Kama.Validation.Tests.cs b/lib/trends_IIR/kama/Kama.Validation.Tests.cs index 374069e6..1cec2800 100644 --- a/lib/trends_IIR/kama/Kama.Validation.Tests.cs +++ b/lib/trends_IIR/kama/Kama.Validation.Tests.cs @@ -127,7 +127,7 @@ public sealed class KamaValidationTests : IDisposable // Calculate TA-Lib KAMA var retCode = TALib.Functions.Kama(cData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.KamaLookback(period); @@ -158,7 +158,7 @@ public sealed class KamaValidationTests : IDisposable // Calculate TA-Lib KAMA var retCode = TALib.Functions.Kama(cData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.KamaLookback(period); diff --git a/lib/trends_IIR/mama/Mama.Validation.Tests.cs b/lib/trends_IIR/mama/Mama.Validation.Tests.cs index fa414b68..4c779e44 100644 --- a/lib/trends_IIR/mama/Mama.Validation.Tests.cs +++ b/lib/trends_IIR/mama/Mama.Validation.Tests.cs @@ -156,7 +156,7 @@ public class MamaValidationTests taMama, taFama, out var outRange, fastLimit, slowLimit); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); (int offset, int length) = outRange.GetOffsetAndLength(taMama.Length); Assert.True(length > 50, $"TALib MAMA produced only {length} values"); diff --git a/lib/trends_IIR/rema/Rema.Validation.Tests.cs b/lib/trends_IIR/rema/Rema.Validation.Tests.cs index 7f83cbf1..3ad2a66b 100644 --- a/lib/trends_IIR/rema/Rema.Validation.Tests.cs +++ b/lib/trends_IIR/rema/Rema.Validation.Tests.cs @@ -351,4 +351,4 @@ public sealed class RemaValidationTests : IDisposable int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/t3/T3.Validation.Tests.cs b/lib/trends_IIR/t3/T3.Validation.Tests.cs index b3585c40..70ccd8eb 100644 --- a/lib/trends_IIR/t3/T3.Validation.Tests.cs +++ b/lib/trends_IIR/t3/T3.Validation.Tests.cs @@ -55,7 +55,7 @@ public class T3ValidationTests // Calculate TA-Lib T3 var retCode = TALib.Functions.T3(_testData.RawData.Span, 0..^0, output, out var outRange, period, vFactor); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.T3Lookback(period); @@ -86,7 +86,7 @@ public class T3ValidationTests // Calculate TA-Lib T3 var retCode = TALib.Functions.T3(_testData.RawData.Span, 0..^0, output, out var outRange, period, vFactor); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.T3Lookback(period); @@ -113,7 +113,7 @@ public class T3ValidationTests // Calculate TA-Lib T3 var retCode = TALib.Functions.T3(_testData.RawData.Span, 0..^0, talibOutput, out var outRange, period, vFactor); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.T3Lookback(period); diff --git a/lib/trends_IIR/tema/Tema.Validation.Tests.cs b/lib/trends_IIR/tema/Tema.Validation.Tests.cs index da7b770f..80a3eb41 100644 --- a/lib/trends_IIR/tema/Tema.Validation.Tests.cs +++ b/lib/trends_IIR/tema/Tema.Validation.Tests.cs @@ -55,7 +55,7 @@ public class TemaValidationTests // Calculate TA-Lib TEMA var retCode = TALib.Functions.Tema(_testData.RawData.Span, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.TemaLookback(period); @@ -110,7 +110,7 @@ public class TemaValidationTests // Calculate TA-Lib TEMA var retCode = TALib.Functions.Tema(_testData.RawData.Span, 0..^0, talibOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.TemaLookback(period); diff --git a/lib/trends_IIR/tema/Tema.md b/lib/trends_IIR/tema/Tema.md index 8d107789..ef9b4bb2 100644 --- a/lib/trends_IIR/tema/Tema.md +++ b/lib/trends_IIR/tema/Tema.md @@ -1,30 +1,36 @@ # TEMA: Triple Exponential Moving Average -| Property | Value | -| ---------------- | -------------------------------- | -| **Category** | Trend (IIR MA) | -| **Inputs** | Source (close) | -| **Parameters** | `period` | -| **Outputs** | Single series (Tema) | -| **Output range** | Tracks input | -| **Warmup** | `period * 3` bars | - -### TL;DR - -- The Triple Exponential Moving Average (TEMA) is a lag-reducing filter that combines a single, double, and triple EMA. -- Parameterized by `period`. -- Output range: Tracks input. -- Requires `period * 3` bars of warmup before first valid output (IsHot = true). -- Validated against TA-Lib, Skender, and Tulip reference implementations where available. - > "Patrick Mulloy looked at the lag of an EMA and took it personally. TEMA is what happens when you apply algebra to impatience." -The Triple Exponential Moving Average (TEMA) is a lag-reducing filter that combines a single, double, and triple EMA. Unlike a simple triple smoothing (which would be incredibly slow), TEMA uses a weighted combination of the three to cancel out the lag, resulting in an indicator that hugs price action tighter than a spandex cycling short. + + +| Property | Value | +|--------------|-------| +| Category | Trend (IIR MA) | +| Inputs | Source (close) | +| Parameters | `period` (int, default: 30, valid: >= 1) | +| Outputs | Single series (TEMA) | +| Output range | Tracks input | +| Warmup | `period * 3` bars | + +### Key takeaways + +- TEMA combines three cascaded EMAs using a weighted formula to dramatically reduce lag while maintaining smoothness. +- It achieves near-zero-lag tracking by mathematically canceling out the delay inherent in exponential smoothing. +- Particularly effective for fast-moving markets where traditional EMAs are too sluggish. +- The aggressive responsiveness comes at the cost of increased noise and occasional overshoot on sharp reversals. +- Often used as a replacement for EMA in MACD and other trend-following systems requiring minimal delay. ## Historical Context Introduced by Patrick Mulloy in *Technical Analysis of Stocks & Commodities* (Jan 1994), "Smoothing Data With Less Lag." Mulloy's goal was to replace the standard moving averages in MACD and other indicators to reduce the delay in signal generation. +## What It Measures and Why It Matters + +TEMA measures the smoothed trend of price action with dramatically reduced lag compared to traditional exponential moving averages. It mathematically compensates for the inherent delay in exponential smoothing by combining three cascaded EMAs in a weighted formula that effectively "looks ahead" in the trend. + +This matters because traditional EMAs introduce significant lag - an EMA with period N takes approximately 3.45×(N+1) bars to converge, creating delayed signals in fast-moving markets. TEMA reduces this lag by 60-80% while maintaining the smoothness and noise reduction properties of exponential smoothing. Traders use TEMA when they need responsive trend signals without the whipsaw noise of simple moving averages, particularly in MACD-based systems where lag can significantly degrade performance. + ## Architecture & Physics TEMA is not just "EMA applied three times." That would be $EMA(EMA(EMA(x)))$. TEMA is a composite: @@ -36,6 +42,32 @@ This formula effectively projects the trend forward to compensate for the delay Because of the aggressive weighting, TEMA converges (warms up) faster than a standard EMA. While an EMA takes $\approx 3.45(N+1)$ steps to converge to 99.9%, TEMA stabilizes quicker due to the subtraction terms canceling out the initial error. +## Interpretation and Signals + +### Trend Direction + +TEMA tracks price trends with minimal lag, making it excellent for identifying trend changes. When TEMA slopes upward, it indicates bullish momentum; downward slope indicates bearish momentum. The reduced lag means TEMA will turn direction sooner than traditional EMAs during trend changes. + +### Crossover Signals + +TEMA crossovers with price or other moving averages provide entry/exit signals: + +- **Price crossovers**: When price crosses above TEMA, it suggests bullish momentum; crossing below suggests bearish momentum. +- **TEMA/EMA crossovers**: TEMA crossing above a slower EMA indicates accelerating bullish momentum. + +### Divergence Analysis + +TEMA divergences from price can signal potential reversals: + +- **Bullish divergence**: Price makes lower lows while TEMA makes higher lows. +- **Bearish divergence**: Price makes higher highs while TEMA makes lower highs. + +### Signal Quality Factors + +- **Strength**: The steeper the TEMA slope, the stronger the trend momentum. +- **Smoothness**: Despite reduced lag, TEMA maintains reasonable smoothness for reliable signals. +- **Confirmation**: Best used with volume confirmation and other momentum indicators. + ## Mathematical Foundation ### 1. The Cascade @@ -86,6 +118,14 @@ TEMA is inherently recursive due to cascaded EMAs. SIMD parallelization across b | **Overshoot** | 5/10 | Significant overshoot on sharp reversals | | **Smoothness** | 6/10 | Less smooth than SMA/EMA due to high responsiveness | +## Related Indicators + +- **[EMA](../../trends_IIR/ema/Ema.md)**: Single exponential smoothing; TEMA is essentially EMA with lag cancellation. +- **[DEMA](../../trends_IIR/dema/Dema.md)**: Double exponential smoothing; TEMA extends this to triple smoothing. +- **[T3](../../trends_IIR/t3/T3.md)**: Generalized Tillson moving average; TEMA is T3 with volume factor = 1. +- **[MACD](../../oscillators/macd/Macd.md)**: Often uses TEMA instead of EMA for faster signals. +- **[KAMA](../../trends_IIR/kama/Kama.md)**: Adaptive smoothing; complementary approach to fixed-period TEMA. + ### Benchmark Results | Metric | Value | Notes | @@ -183,3 +223,28 @@ Each EmaState contains: Ema (8B), E (8B), IsHot (1B), IsCompensated (1B) + paddi 1. **Overshoot**: TEMA is so responsive it can overshoot price turns, creating a "whiplash" effect in volatile markets. 2. **Noise**: By reducing lag, TEMA sacrifices some noise suppression. It is "nervous" compared to an SMA. 3. **Identity Crisis**: Often confused with T3 (Tillson). T3 is a generalized version; TEMA is specifically T3 with $v=1$. +4. **Warmup period**: Requires 3× period bars before producing valid output; premature signals are unreliable. +5. **Parameter sensitivity**: Small period values (< 10) create excessive noise; large values (> 50) reduce responsiveness. +6. **False signals**: In choppy, sideways markets, frequent crossovers generate misleading signals. +7. **Computational cost**: 4× more expensive than simple EMA due to cascaded calculations. + +## FAQ + +**Q: How does TEMA differ from a triple-smoothed EMA?** +A: A triple-smoothed EMA would be EMA(EMA(EMA(price))), which introduces massive lag. TEMA uses the formula 3×EMA₁ - 3×EMA₂ + EMA₃ to cancel out lag while maintaining the smoothing effect. + +**Q: What's the relationship between TEMA and DEMA?** +A: DEMA is 2×EMA₁ - EMA₂. TEMA extends this to 3×EMA₁ - 3×EMA₂ + EMA₃. Both use weighted combinations to reduce lag, but TEMA goes further with triple smoothing. + +**Q: When should I use TEMA instead of EMA?** +A: Use TEMA when you need minimal lag for timing-critical signals (like MACD triggers) but still want the smoothness of exponential smoothing. Use EMA for general trend following where some lag is acceptable. + +**Q: Can TEMA be used for scalping?** +A: Yes, with short periods (5-15), but be aware of increased noise and false signals. Combine with volume confirmation and other filters to reduce whipsaws. + +## References + +- Mulloy, P. (1994). "Smoothing Data With Less Lag." *Technical Analysis of Stocks & Commodities*, 12(1). +- Kaufman, P. J. (1995). *Smarter Trading*. McGraw-Hill. (Chapter on adaptive moving averages) +- Tillson, T. (1998). "Generalized Moving Averages." *Technical Analysis of Stocks & Commodities*. +- Ehler, J. (2001). *Rocket Science for Traders*. Wiley. (Discussion of lag reduction techniques) diff --git a/lib/trends_IIR/zldema/Zldema.Quantower.Tests.cs b/lib/trends_IIR/zldema/Zldema.Quantower.Tests.cs index 90991d4e..02244d77 100644 --- a/lib/trends_IIR/zldema/Zldema.Quantower.Tests.cs +++ b/lib/trends_IIR/zldema/Zldema.Quantower.Tests.cs @@ -124,4 +124,4 @@ public class ZldemaIndicatorTests Assert.Equal(20, indicator.Period); Assert.Equal(0, ZldemaIndicator.MinHistoryDepths); } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/zldema/Zldema.Tests.cs b/lib/trends_IIR/zldema/Zldema.Tests.cs index 100206c5..2e1a1d01 100644 --- a/lib/trends_IIR/zldema/Zldema.Tests.cs +++ b/lib/trends_IIR/zldema/Zldema.Tests.cs @@ -199,4 +199,4 @@ public class ZldemaTests return series; } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/zldema/Zldema.Validation.Tests.cs b/lib/trends_IIR/zldema/Zldema.Validation.Tests.cs index 0daec811..b070878c 100644 --- a/lib/trends_IIR/zldema/Zldema.Validation.Tests.cs +++ b/lib/trends_IIR/zldema/Zldema.Validation.Tests.cs @@ -151,4 +151,4 @@ public class ZldemaValidationTests return series; } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/zltema/Zltema.Quantower.Tests.cs b/lib/trends_IIR/zltema/Zltema.Quantower.Tests.cs index 51a54043..a1a8812e 100644 --- a/lib/trends_IIR/zltema/Zltema.Quantower.Tests.cs +++ b/lib/trends_IIR/zltema/Zltema.Quantower.Tests.cs @@ -124,4 +124,4 @@ public class ZltemaIndicatorTests Assert.Equal(20, indicator.Period); Assert.Equal(0, ZltemaIndicator.MinHistoryDepths); } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/zltema/Zltema.Tests.cs b/lib/trends_IIR/zltema/Zltema.Tests.cs index 99bd4acd..ba7e3c37 100644 --- a/lib/trends_IIR/zltema/Zltema.Tests.cs +++ b/lib/trends_IIR/zltema/Zltema.Tests.cs @@ -199,4 +199,4 @@ public class ZltemaTests return series; } -} \ No newline at end of file +} diff --git a/lib/trends_IIR/zltema/Zltema.Validation.Tests.cs b/lib/trends_IIR/zltema/Zltema.Validation.Tests.cs index 4ff82513..183efe46 100644 --- a/lib/trends_IIR/zltema/Zltema.Validation.Tests.cs +++ b/lib/trends_IIR/zltema/Zltema.Validation.Tests.cs @@ -178,4 +178,4 @@ public class ZltemaValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/volatility/atr/Atr.Validation.Tests.cs b/lib/volatility/atr/Atr.Validation.Tests.cs index 961933bb..da3484dc 100644 --- a/lib/volatility/atr/Atr.Validation.Tests.cs +++ b/lib/volatility/atr/Atr.Validation.Tests.cs @@ -102,7 +102,7 @@ public sealed class AtrValidationTests : IDisposable // Calculate TA-Lib ATR var retCode = TALib.Functions.Atr(hData, lData, cData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.AtrLookback(period); @@ -135,7 +135,7 @@ public sealed class AtrValidationTests : IDisposable // Calculate TA-Lib ATR var retCode = TALib.Functions.Atr(hData, lData, cData, 0..^0, output, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.AtrLookback(period); diff --git a/lib/volatility/bbw/Bbw.Quantower.Tests.cs b/lib/volatility/bbw/Bbw.Quantower.Tests.cs index 2b6879bf..beff66c2 100644 --- a/lib/volatility/bbw/Bbw.Quantower.Tests.cs +++ b/lib/volatility/bbw/Bbw.Quantower.Tests.cs @@ -212,4 +212,4 @@ public class BbwIndicatorTests Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("Bbw.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/volatility/bbw/Bbw.Tests.cs b/lib/volatility/bbw/Bbw.Tests.cs index a3e5e5e2..de1d6952 100644 --- a/lib/volatility/bbw/Bbw.Tests.cs +++ b/lib/volatility/bbw/Bbw.Tests.cs @@ -426,4 +426,4 @@ public class BbwTests double expectedBbw = (2.0 * 2.0 * 5.0) / 105.0; Assert.Equal(expectedBbw, bbw.Last.Value, 1e-10); } -} \ No newline at end of file +} diff --git a/lib/volatility/bbw/Bbw.Validation.Tests.cs b/lib/volatility/bbw/Bbw.Validation.Tests.cs index 526f7359..ec530488 100644 --- a/lib/volatility/bbw/Bbw.Validation.Tests.cs +++ b/lib/volatility/bbw/Bbw.Validation.Tests.cs @@ -267,4 +267,4 @@ public sealed class BbwValidationTests : IDisposable Assert.True(finiteCount > 100, $"Expected >100 finite BBW pairs, got {finiteCount}"); _output.WriteLine($"BBW Ooples structural: {finiteCount} finite pairs verified."); } -} \ No newline at end of file +} diff --git a/lib/volatility/bbwn/Bbwn.Quantower.Tests.cs b/lib/volatility/bbwn/Bbwn.Quantower.Tests.cs index 85ebf56b..1af5846c 100644 --- a/lib/volatility/bbwn/Bbwn.Quantower.Tests.cs +++ b/lib/volatility/bbwn/Bbwn.Quantower.Tests.cs @@ -214,4 +214,4 @@ public class BbwnIndicatorTests Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("Bbwn.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/volatility/bbwn/Bbwn.Tests.cs b/lib/volatility/bbwn/Bbwn.Tests.cs index 3e52bb75..b1ef465e 100644 --- a/lib/volatility/bbwn/Bbwn.Tests.cs +++ b/lib/volatility/bbwn/Bbwn.Tests.cs @@ -485,4 +485,4 @@ public class BbwnTests Assert.Equal(result1.Value, result3.Value, Tolerance); } -} \ No newline at end of file +} diff --git a/lib/volatility/bbwn/Bbwn.Validation.Tests.cs b/lib/volatility/bbwn/Bbwn.Validation.Tests.cs index 045a70cc..0e251cdc 100644 --- a/lib/volatility/bbwn/Bbwn.Validation.Tests.cs +++ b/lib/volatility/bbwn/Bbwn.Validation.Tests.cs @@ -176,4 +176,4 @@ public class BbwnValidationTests Assert.Equal(multiplier, bbwn.Multiplier); Assert.Equal(lookback, bbwn.Lookback); } -} \ No newline at end of file +} diff --git a/lib/volatility/bbwp/Bbwp.Quantower.Tests.cs b/lib/volatility/bbwp/Bbwp.Quantower.Tests.cs index 8c4a97cf..d393db1f 100644 --- a/lib/volatility/bbwp/Bbwp.Quantower.Tests.cs +++ b/lib/volatility/bbwp/Bbwp.Quantower.Tests.cs @@ -214,4 +214,4 @@ public class BbwpIndicatorTests Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("Bbwp.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/volatility/bbwp/Bbwp.Tests.cs b/lib/volatility/bbwp/Bbwp.Tests.cs index 1b097e81..2bed1ae0 100644 --- a/lib/volatility/bbwp/Bbwp.Tests.cs +++ b/lib/volatility/bbwp/Bbwp.Tests.cs @@ -483,4 +483,4 @@ public class BbwpTests Assert.Equal(result1.Value, result3.Value, Tolerance); } -} \ No newline at end of file +} diff --git a/lib/volatility/bbwp/Bbwp.Validation.Tests.cs b/lib/volatility/bbwp/Bbwp.Validation.Tests.cs index 05859511..eff2f5f1 100644 --- a/lib/volatility/bbwp/Bbwp.Validation.Tests.cs +++ b/lib/volatility/bbwp/Bbwp.Validation.Tests.cs @@ -222,4 +222,4 @@ public class BbwpValidationTests Assert.Equal(bbwpStream.Last.Value, output[output.Length - 1], 1e-10); } -} \ No newline at end of file +} diff --git a/lib/volatility/ccv/Ccv.Quantower.Tests.cs b/lib/volatility/ccv/Ccv.Quantower.Tests.cs index 8ad96c88..912fe3b0 100644 --- a/lib/volatility/ccv/Ccv.Quantower.Tests.cs +++ b/lib/volatility/ccv/Ccv.Quantower.Tests.cs @@ -212,4 +212,4 @@ public class CcvIndicatorTests Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("Ccv.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/volatility/ccv/Ccv.Tests.cs b/lib/volatility/ccv/Ccv.Tests.cs index 460f11de..01a44380 100644 --- a/lib/volatility/ccv/Ccv.Tests.cs +++ b/lib/volatility/ccv/Ccv.Tests.cs @@ -433,4 +433,4 @@ public class CcvTests Assert.True(sma.IsHot); Assert.True(double.IsFinite(sma.Last.Value)); } -} \ No newline at end of file +} diff --git a/lib/volatility/ccv/Ccv.Validation.Tests.cs b/lib/volatility/ccv/Ccv.Validation.Tests.cs index b02c9c94..7979e6a3 100644 --- a/lib/volatility/ccv/Ccv.Validation.Tests.cs +++ b/lib/volatility/ccv/Ccv.Validation.Tests.cs @@ -301,4 +301,4 @@ public class CcvValidationTests Assert.True(ccv20.Last.Value >= 0); Assert.True(ccv50.Last.Value >= 0); } -} \ No newline at end of file +} diff --git a/lib/volatility/cv/Cv.Quantower.Tests.cs b/lib/volatility/cv/Cv.Quantower.Tests.cs index 8f55b5e8..3f26732b 100644 --- a/lib/volatility/cv/Cv.Quantower.Tests.cs +++ b/lib/volatility/cv/Cv.Quantower.Tests.cs @@ -305,4 +305,4 @@ public class CvIndicatorTests double max = values.Max(); Assert.True(max > min, "CV should vary with changing volatility patterns"); } -} \ No newline at end of file +} diff --git a/lib/volatility/cv/Cv.Tests.cs b/lib/volatility/cv/Cv.Tests.cs index 70314d18..840e2eb0 100644 --- a/lib/volatility/cv/Cv.Tests.cs +++ b/lib/volatility/cv/Cv.Tests.cs @@ -456,4 +456,4 @@ public class CvTests Assert.True(sma.IsHot); Assert.True(double.IsFinite(sma.Last.Value)); } -} \ No newline at end of file +} diff --git a/lib/volatility/cv/Cv.Validation.Tests.cs b/lib/volatility/cv/Cv.Validation.Tests.cs index d10daef8..27d2eb7c 100644 --- a/lib/volatility/cv/Cv.Validation.Tests.cs +++ b/lib/volatility/cv/Cv.Validation.Tests.cs @@ -451,4 +451,4 @@ public class CvValidationTests Assert.True(cv.Last.Value >= 0, "Volatility cannot be negative"); Assert.True(cv.Last.Value < 500, "Volatility should be reasonable (< 500% annualized)"); } -} \ No newline at end of file +} diff --git a/lib/volatility/cvi/Cvi.Quantower.Tests.cs b/lib/volatility/cvi/Cvi.Quantower.Tests.cs index bb5ccb97..7e6eca42 100644 --- a/lib/volatility/cvi/Cvi.Quantower.Tests.cs +++ b/lib/volatility/cvi/Cvi.Quantower.Tests.cs @@ -281,4 +281,4 @@ public class CviIndicatorTests // With constant but different ranges, the absolute values may differ // but both should be close to 0 (no rate of change) } -} \ No newline at end of file +} diff --git a/lib/volatility/cvi/Cvi.Tests.cs b/lib/volatility/cvi/Cvi.Tests.cs index a04d5020..3960c192 100644 --- a/lib/volatility/cvi/Cvi.Tests.cs +++ b/lib/volatility/cvi/Cvi.Tests.cs @@ -497,4 +497,4 @@ public class CviTests Assert.Throws(() => Cvi.Batch(source, outputCorrect, 0, 2)); Assert.Throws(() => Cvi.Batch(source, outputCorrect, 2, 0)); } -} \ No newline at end of file +} diff --git a/lib/volatility/cvi/Cvi.Validation.Tests.cs b/lib/volatility/cvi/Cvi.Validation.Tests.cs index 2e2cb6f8..0a039ec0 100644 --- a/lib/volatility/cvi/Cvi.Validation.Tests.cs +++ b/lib/volatility/cvi/Cvi.Validation.Tests.cs @@ -621,4 +621,4 @@ public class CviValidationTests double mean = values.Average(); return values.Average(v => Math.Pow(v - mean, 2)); } -} \ No newline at end of file +} diff --git a/lib/volatility/ewma/Ewma.Quantower.Tests.cs b/lib/volatility/ewma/Ewma.Quantower.Tests.cs index 23be01bf..9ac642fa 100644 --- a/lib/volatility/ewma/Ewma.Quantower.Tests.cs +++ b/lib/volatility/ewma/Ewma.Quantower.Tests.cs @@ -308,4 +308,4 @@ public class EwmaIndicatorTests Assert.True(shortIncrease > longIncrease, "Shorter period should respond more strongly to shocks"); } -} \ No newline at end of file +} diff --git a/lib/volatility/ewma/Ewma.Tests.cs b/lib/volatility/ewma/Ewma.Tests.cs index c6f7eee0..916de76c 100644 --- a/lib/volatility/ewma/Ewma.Tests.cs +++ b/lib/volatility/ewma/Ewma.Tests.cs @@ -550,4 +550,4 @@ public class EwmaTests Assert.True(double.IsFinite(resultZero.Value)); Assert.True(resultZero.Value >= 0); } -} \ No newline at end of file +} diff --git a/lib/volatility/ewma/Ewma.Validation.Tests.cs b/lib/volatility/ewma/Ewma.Validation.Tests.cs index 0890b71d..515fdbe0 100644 --- a/lib/volatility/ewma/Ewma.Validation.Tests.cs +++ b/lib/volatility/ewma/Ewma.Validation.Tests.cs @@ -492,4 +492,4 @@ public class EwmaValidationTests Assert.True(shortIncrease > longIncrease, "Shorter period should respond more strongly to shocks"); } -} \ No newline at end of file +} diff --git a/lib/volatility/gkv/Gkv.Quantower.Tests.cs b/lib/volatility/gkv/Gkv.Quantower.Tests.cs index 4d1e7767..3c490f33 100644 --- a/lib/volatility/gkv/Gkv.Quantower.Tests.cs +++ b/lib/volatility/gkv/Gkv.Quantower.Tests.cs @@ -301,4 +301,4 @@ public class GkvIndicatorTests Assert.True(double.IsFinite(val)); Assert.True(val < 0.001, "Constant price should produce near-zero volatility"); } -} \ No newline at end of file +} diff --git a/lib/volatility/gkv/Gkv.Tests.cs b/lib/volatility/gkv/Gkv.Tests.cs index e37f2bfc..a2bc7cfd 100644 --- a/lib/volatility/gkv/Gkv.Tests.cs +++ b/lib/volatility/gkv/Gkv.Tests.cs @@ -643,4 +643,4 @@ public class GkvTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/gkv/Gkv.Validation.Tests.cs b/lib/volatility/gkv/Gkv.Validation.Tests.cs index e6052c17..9e9605c8 100644 --- a/lib/volatility/gkv/Gkv.Validation.Tests.cs +++ b/lib/volatility/gkv/Gkv.Validation.Tests.cs @@ -625,4 +625,4 @@ public class GkvValidationTests double mean = values.Average(); return values.Average(v => Math.Pow(v - mean, 2)); } -} \ No newline at end of file +} diff --git a/lib/volatility/hlv/Hlv.Quantower.Tests.cs b/lib/volatility/hlv/Hlv.Quantower.Tests.cs index f5c02ec7..d0758bfc 100644 --- a/lib/volatility/hlv/Hlv.Quantower.Tests.cs +++ b/lib/volatility/hlv/Hlv.Quantower.Tests.cs @@ -301,4 +301,4 @@ public class HlvIndicatorTests Assert.True(double.IsFinite(val)); Assert.True(val < 0.001, "Constant price should produce near-zero volatility"); } -} \ No newline at end of file +} diff --git a/lib/volatility/hlv/Hlv.Tests.cs b/lib/volatility/hlv/Hlv.Tests.cs index 04e6e739..a4f1af17 100644 --- a/lib/volatility/hlv/Hlv.Tests.cs +++ b/lib/volatility/hlv/Hlv.Tests.cs @@ -646,4 +646,4 @@ public class HlvTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/hlv/Hlv.Validation.Tests.cs b/lib/volatility/hlv/Hlv.Validation.Tests.cs index 99e33853..7578acb7 100644 --- a/lib/volatility/hlv/Hlv.Validation.Tests.cs +++ b/lib/volatility/hlv/Hlv.Validation.Tests.cs @@ -644,4 +644,4 @@ public class HlvValidationTests double mean = values.Average(); return values.Average(v => Math.Pow(v - mean, 2)); } -} \ No newline at end of file +} diff --git a/lib/volatility/hv/Hv.Quantower.Tests.cs b/lib/volatility/hv/Hv.Quantower.Tests.cs index 07f2b22a..7a7afab5 100644 --- a/lib/volatility/hv/Hv.Quantower.Tests.cs +++ b/lib/volatility/hv/Hv.Quantower.Tests.cs @@ -324,4 +324,4 @@ public class HvIndicatorTests Assert.True(double.IsFinite(val)); Assert.True(val > 0, "Varying returns should produce non-zero volatility"); } -} \ No newline at end of file +} diff --git a/lib/volatility/hv/Hv.Tests.cs b/lib/volatility/hv/Hv.Tests.cs index 73cc6fbb..cdc6db39 100644 --- a/lib/volatility/hv/Hv.Tests.cs +++ b/lib/volatility/hv/Hv.Tests.cs @@ -734,4 +734,4 @@ public class HvTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/hv/Hv.Validation.Tests.cs b/lib/volatility/hv/Hv.Validation.Tests.cs index a0fc597f..e0dd35a3 100644 --- a/lib/volatility/hv/Hv.Validation.Tests.cs +++ b/lib/volatility/hv/Hv.Validation.Tests.cs @@ -667,4 +667,4 @@ public class HvValidationTests double mean = values.Average(); return values.Average(v => Math.Pow(v - mean, 2)); } -} \ No newline at end of file +} diff --git a/lib/volatility/jvolty/Jvolty.Quantower.Tests.cs b/lib/volatility/jvolty/Jvolty.Quantower.Tests.cs index 25944b4d..7bd153c9 100644 --- a/lib/volatility/jvolty/Jvolty.Quantower.Tests.cs +++ b/lib/volatility/jvolty/Jvolty.Quantower.Tests.cs @@ -173,4 +173,4 @@ public class JvoltyIndicatorTests Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal); Assert.Contains("Jvolty.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal); } -} \ No newline at end of file +} diff --git a/lib/volatility/jvolty/Jvolty.Tests.cs b/lib/volatility/jvolty/Jvolty.Tests.cs index 8c2af310..6f7ebda0 100644 --- a/lib/volatility/jvolty/Jvolty.Tests.cs +++ b/lib/volatility/jvolty/Jvolty.Tests.cs @@ -650,4 +650,4 @@ public class JvoltyTests Assert.InRange(lastValue, 1.0, double.MaxValue); // Volatility >= minimum (1.0) } #pragma warning restore S2699 -} \ No newline at end of file +} diff --git a/lib/volatility/jvoltyn/Jvoltyn.Quantower.Tests.cs b/lib/volatility/jvoltyn/Jvoltyn.Quantower.Tests.cs index f027a6d8..21d2f061 100644 --- a/lib/volatility/jvoltyn/Jvoltyn.Quantower.Tests.cs +++ b/lib/volatility/jvoltyn/Jvoltyn.Quantower.Tests.cs @@ -195,4 +195,4 @@ public class JvoltynIndicatorTests Assert.True(val <= 100.0, $"Bar {i}: value {val} should be <= 100"); } } -} \ No newline at end of file +} diff --git a/lib/volatility/jvoltyn/Jvoltyn.Tests.cs b/lib/volatility/jvoltyn/Jvoltyn.Tests.cs index 4c312632..9c48838b 100644 --- a/lib/volatility/jvoltyn/Jvoltyn.Tests.cs +++ b/lib/volatility/jvoltyn/Jvoltyn.Tests.cs @@ -706,4 +706,4 @@ public class JvoltynTests Assert.InRange(lastValue, 0.0, 100.0); // Normalized volatility in [0, 100] } #pragma warning restore S2699 -} \ No newline at end of file +} diff --git a/lib/volatility/massi/Massi.Quantower.Tests.cs b/lib/volatility/massi/Massi.Quantower.Tests.cs index c2533de2..50b637c8 100644 --- a/lib/volatility/massi/Massi.Quantower.Tests.cs +++ b/lib/volatility/massi/Massi.Quantower.Tests.cs @@ -206,4 +206,4 @@ public class MassiIndicatorTests Assert.True(double.IsFinite(smallRangeVal)); Assert.True(double.IsFinite(largeRangeVal)); } -} \ No newline at end of file +} diff --git a/lib/volatility/massi/Massi.Tests.cs b/lib/volatility/massi/Massi.Tests.cs index 2be0ae73..40072278 100644 --- a/lib/volatility/massi/Massi.Tests.cs +++ b/lib/volatility/massi/Massi.Tests.cs @@ -724,4 +724,4 @@ public class MassiTests // Both should produce same result Assert.Equal(massi1.Last.Value, massi2.Last.Value, Tolerance); } -} \ No newline at end of file +} diff --git a/lib/volatility/massi/Massi.Validation.Tests.cs b/lib/volatility/massi/Massi.Validation.Tests.cs index 3bfdea47..15a6ba1c 100644 --- a/lib/volatility/massi/Massi.Validation.Tests.cs +++ b/lib/volatility/massi/Massi.Validation.Tests.cs @@ -275,4 +275,4 @@ public class MassiValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/volatility/natr/Natr.Quantower.Tests.cs b/lib/volatility/natr/Natr.Quantower.Tests.cs index 863441ac..d857dc06 100644 --- a/lib/volatility/natr/Natr.Quantower.Tests.cs +++ b/lib/volatility/natr/Natr.Quantower.Tests.cs @@ -155,4 +155,4 @@ public class NatrIndicatorTests var indicator = new NatrIndicator(); Assert.Contains("percentage", indicator.Description, StringComparison.OrdinalIgnoreCase); } -} \ No newline at end of file +} diff --git a/lib/volatility/natr/Natr.Tests.cs b/lib/volatility/natr/Natr.Tests.cs index 038d090b..7702373b 100644 --- a/lib/volatility/natr/Natr.Tests.cs +++ b/lib/volatility/natr/Natr.Tests.cs @@ -598,4 +598,4 @@ public class NatrTests Assert.True(double.IsFinite(natr.Last.Value), "Last value should be finite after Prime"); } #pragma warning restore S2699 -} \ No newline at end of file +} diff --git a/lib/volatility/natr/Natr.Validation.Tests.cs b/lib/volatility/natr/Natr.Validation.Tests.cs index 66b0b09f..c76cdb84 100644 --- a/lib/volatility/natr/Natr.Validation.Tests.cs +++ b/lib/volatility/natr/Natr.Validation.Tests.cs @@ -150,7 +150,7 @@ public sealed class NatrValidationTests : IDisposable // Calculate TA-Lib ATR var retCode = TALib.Functions.Atr(hData, lData, cData, 0..^0, atrOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.AtrLookback(period); @@ -199,7 +199,7 @@ public sealed class NatrValidationTests : IDisposable // Calculate TA-Lib ATR var retCode = TALib.Functions.Atr(hData, lData, cData, 0..^0, atrOutput, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = TALib.Functions.AtrLookback(period); @@ -348,4 +348,4 @@ public sealed class NatrValidationTests : IDisposable } _output.WriteLine("NATR Batch(TBarSeries) validated successfully against Ooples ATR"); } -} \ No newline at end of file +} diff --git a/lib/volatility/rsv/Rsv.Quantower.Tests.cs b/lib/volatility/rsv/Rsv.Quantower.Tests.cs index cb53f189..7cb2f64e 100644 --- a/lib/volatility/rsv/Rsv.Quantower.Tests.cs +++ b/lib/volatility/rsv/Rsv.Quantower.Tests.cs @@ -323,4 +323,4 @@ public class RsvIndicatorTests Assert.True(double.IsFinite(val)); Assert.True(val > 0, "Trending market with volatility should produce positive RSV"); } -} \ No newline at end of file +} diff --git a/lib/volatility/rsv/Rsv.Tests.cs b/lib/volatility/rsv/Rsv.Tests.cs index 80575f0f..7ae9f16c 100644 --- a/lib/volatility/rsv/Rsv.Tests.cs +++ b/lib/volatility/rsv/Rsv.Tests.cs @@ -714,4 +714,4 @@ public class RsvTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/rsv/Rsv.Validation.Tests.cs b/lib/volatility/rsv/Rsv.Validation.Tests.cs index 9d172021..bff14360 100644 --- a/lib/volatility/rsv/Rsv.Validation.Tests.cs +++ b/lib/volatility/rsv/Rsv.Validation.Tests.cs @@ -710,4 +710,4 @@ public class RsvValidationTests double mean = values.Average(); return values.Average(v => Math.Pow(v - mean, 2)); } -} \ No newline at end of file +} diff --git a/lib/volatility/rv/Rv.Quantower.Tests.cs b/lib/volatility/rv/Rv.Quantower.Tests.cs index 0a6e7aa9..54dd677f 100644 --- a/lib/volatility/rv/Rv.Quantower.Tests.cs +++ b/lib/volatility/rv/Rv.Quantower.Tests.cs @@ -342,4 +342,4 @@ public class RvIndicatorTests // Different smoothing periods should produce different results Assert.NotEqual(val1, val2); } -} \ No newline at end of file +} diff --git a/lib/volatility/rv/Rv.Tests.cs b/lib/volatility/rv/Rv.Tests.cs index db56a38c..0309afed 100644 --- a/lib/volatility/rv/Rv.Tests.cs +++ b/lib/volatility/rv/Rv.Tests.cs @@ -712,4 +712,4 @@ public class RvTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/rv/Rv.Validation.Tests.cs b/lib/volatility/rv/Rv.Validation.Tests.cs index 6a3fdd9f..8b4a2690 100644 --- a/lib/volatility/rv/Rv.Validation.Tests.cs +++ b/lib/volatility/rv/Rv.Validation.Tests.cs @@ -558,4 +558,4 @@ public class RvValidationTests double mean = values.Average(); return values.Average(v => Math.Pow(v - mean, 2)); } -} \ No newline at end of file +} diff --git a/lib/volatility/rvi/Rvi.Quantower.Tests.cs b/lib/volatility/rvi/Rvi.Quantower.Tests.cs index f787ee74..7322698b 100644 --- a/lib/volatility/rvi/Rvi.Quantower.Tests.cs +++ b/lib/volatility/rvi/Rvi.Quantower.Tests.cs @@ -313,4 +313,4 @@ public class RviIndicatorTests // In a neutral market, RVI should be near 50 Assert.True(val >= 30 && val <= 70, $"Neutral market should produce RVI near 50, got {val}"); } -} \ No newline at end of file +} diff --git a/lib/volatility/rvi/Rvi.Tests.cs b/lib/volatility/rvi/Rvi.Tests.cs index daeb9fce..f2904f31 100644 --- a/lib/volatility/rvi/Rvi.Tests.cs +++ b/lib/volatility/rvi/Rvi.Tests.cs @@ -668,4 +668,4 @@ public class RviTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/rvi/Rvi.Validation.Tests.cs b/lib/volatility/rvi/Rvi.Validation.Tests.cs index b893c022..44a7043a 100644 --- a/lib/volatility/rvi/Rvi.Validation.Tests.cs +++ b/lib/volatility/rvi/Rvi.Validation.Tests.cs @@ -616,4 +616,4 @@ public class RviValidationTests return values.Average(v => Math.Pow(v - mean, 2)); } -} \ No newline at end of file +} diff --git a/lib/volatility/tr/Tr.Quantower.Tests.cs b/lib/volatility/tr/Tr.Quantower.Tests.cs index c7b714a6..dad66349 100644 --- a/lib/volatility/tr/Tr.Quantower.Tests.cs +++ b/lib/volatility/tr/Tr.Quantower.Tests.cs @@ -293,4 +293,4 @@ public class TrIndicatorTests // TR = max(10, 60, 50) = 60 Assert.Equal(60.0, secondTr, 10); } -} \ No newline at end of file +} diff --git a/lib/volatility/tr/Tr.Tests.cs b/lib/volatility/tr/Tr.Tests.cs index 4d33b06d..7dee9890 100644 --- a/lib/volatility/tr/Tr.Tests.cs +++ b/lib/volatility/tr/Tr.Tests.cs @@ -530,4 +530,4 @@ public class TrTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/tr/Tr.Validation.Tests.cs b/lib/volatility/tr/Tr.Validation.Tests.cs index 41f5c7ec..f0e230ab 100644 --- a/lib/volatility/tr/Tr.Validation.Tests.cs +++ b/lib/volatility/tr/Tr.Validation.Tests.cs @@ -672,7 +672,7 @@ public class TrValidationTests double[] output = new double[high.Length]; var retCode = Functions.TRange(high, low, close, 0..^0, output, out var outRange); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.TRangeLookback(); @@ -713,4 +713,4 @@ public class TrValidationTests $"TR mismatch at {qIdx}: QuanTAlib={qOutput[qIdx]:G17}, Tulip={outputs[0][i]:G17}"); } } -} \ No newline at end of file +} diff --git a/lib/volatility/ui/Ui.Quantower.Tests.cs b/lib/volatility/ui/Ui.Quantower.Tests.cs index 7f7e2ffa..dc491b04 100644 --- a/lib/volatility/ui/Ui.Quantower.Tests.cs +++ b/lib/volatility/ui/Ui.Quantower.Tests.cs @@ -335,4 +335,4 @@ public class UiIndicatorTests // After 15 bars of rising prices past the old high, UI should be near zero or much lower Assert.True(afterRecovery < duringDrawdown, "Recovery from drawdown should reduce UI value"); } -} \ No newline at end of file +} diff --git a/lib/volatility/ui/Ui.Tests.cs b/lib/volatility/ui/Ui.Tests.cs index b8a65ad0..bbdaadbd 100644 --- a/lib/volatility/ui/Ui.Tests.cs +++ b/lib/volatility/ui/Ui.Tests.cs @@ -611,4 +611,4 @@ public class UiTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/ui/Ui.Validation.Tests.cs b/lib/volatility/ui/Ui.Validation.Tests.cs index bdee9d5e..7045c392 100644 --- a/lib/volatility/ui/Ui.Validation.Tests.cs +++ b/lib/volatility/ui/Ui.Validation.Tests.cs @@ -696,4 +696,4 @@ public class UiValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite Ooples UI values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/volatility/vov/Vov.Quantower.Tests.cs b/lib/volatility/vov/Vov.Quantower.Tests.cs index d419c0d4..de437ed0 100644 --- a/lib/volatility/vov/Vov.Quantower.Tests.cs +++ b/lib/volatility/vov/Vov.Quantower.Tests.cs @@ -326,4 +326,4 @@ public class VovIndicatorTests // During volatility regime change, VOV should typically increase Assert.True(transitionVal > 0, "Changing volatility regime should produce positive VOV"); } -} \ No newline at end of file +} diff --git a/lib/volatility/vov/Vov.Tests.cs b/lib/volatility/vov/Vov.Tests.cs index 0b3bc15a..c62e5701 100644 --- a/lib/volatility/vov/Vov.Tests.cs +++ b/lib/volatility/vov/Vov.Tests.cs @@ -644,4 +644,4 @@ public class VovTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/vov/Vov.Validation.Tests.cs b/lib/volatility/vov/Vov.Validation.Tests.cs index 29d867a8..24ca7ab5 100644 --- a/lib/volatility/vov/Vov.Validation.Tests.cs +++ b/lib/volatility/vov/Vov.Validation.Tests.cs @@ -623,4 +623,4 @@ public class VovValidationTests Assert.Equal(0.5, result.Value, 8); } -} \ No newline at end of file +} diff --git a/lib/volatility/vr/Vr.Quantower.Tests.cs b/lib/volatility/vr/Vr.Quantower.Tests.cs index 725cf1d8..0e86439c 100644 --- a/lib/volatility/vr/Vr.Quantower.Tests.cs +++ b/lib/volatility/vr/Vr.Quantower.Tests.cs @@ -381,4 +381,4 @@ public class VrIndicatorTests // In steady state with consistent volatility, VR should hover around 1 Assert.True(avgVr >= 0.5 && avgVr <= 2.0, $"Average VR should be around 1, got {avgVr}"); } -} \ No newline at end of file +} diff --git a/lib/volatility/vr/Vr.Tests.cs b/lib/volatility/vr/Vr.Tests.cs index 15ce5745..4d73555c 100644 --- a/lib/volatility/vr/Vr.Tests.cs +++ b/lib/volatility/vr/Vr.Tests.cs @@ -625,4 +625,4 @@ public class VrTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/vr/Vr.Validation.Tests.cs b/lib/volatility/vr/Vr.Validation.Tests.cs index f4e100b0..2d36f774 100644 --- a/lib/volatility/vr/Vr.Validation.Tests.cs +++ b/lib/volatility/vr/Vr.Validation.Tests.cs @@ -421,4 +421,4 @@ public class VrValidationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/yzv/Yzv.Quantower.Tests.cs b/lib/volatility/yzv/Yzv.Quantower.Tests.cs index 381ce4d2..3b1e4c0a 100644 --- a/lib/volatility/yzv/Yzv.Quantower.Tests.cs +++ b/lib/volatility/yzv/Yzv.Quantower.Tests.cs @@ -337,4 +337,4 @@ public class YzvIndicatorTests Assert.True(double.IsFinite(highVolVal)); Assert.True(highVolVal > lowVolVal, "High volatility regime should produce higher YZV"); } -} \ No newline at end of file +} diff --git a/lib/volatility/yzv/Yzv.Tests.cs b/lib/volatility/yzv/Yzv.Tests.cs index 5061aa9b..cfbcf125 100644 --- a/lib/volatility/yzv/Yzv.Tests.cs +++ b/lib/volatility/yzv/Yzv.Tests.cs @@ -608,4 +608,4 @@ public class YzvTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volatility/yzv/Yzv.Validation.Tests.cs b/lib/volatility/yzv/Yzv.Validation.Tests.cs index 6849e3c0..608eed76 100644 --- a/lib/volatility/yzv/Yzv.Validation.Tests.cs +++ b/lib/volatility/yzv/Yzv.Validation.Tests.cs @@ -314,4 +314,4 @@ public class YzvValidationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volume/aobv/Aobv.Quantower.Tests.cs b/lib/volume/aobv/Aobv.Quantower.Tests.cs index 61002d0b..3067b7f8 100644 --- a/lib/volume/aobv/Aobv.Quantower.Tests.cs +++ b/lib/volume/aobv/Aobv.Quantower.Tests.cs @@ -157,4 +157,4 @@ public class AobvIndicatorTests Assert.Equal("Fast", indicator.LinesSeries[0].Name); Assert.Equal("Slow", indicator.LinesSeries[1].Name); } -} \ No newline at end of file +} diff --git a/lib/volume/aobv/Aobv.Tests.cs b/lib/volume/aobv/Aobv.Tests.cs index 5b45949a..04bd347a 100644 --- a/lib/volume/aobv/Aobv.Tests.cs +++ b/lib/volume/aobv/Aobv.Tests.cs @@ -353,4 +353,4 @@ public class AobvTests Assert.NotEqual(0, aobv.LastFast.Value); Assert.NotEqual(0, aobv.LastSlow.Value); } -} \ No newline at end of file +} diff --git a/lib/volume/aobv/Aobv.Validation.Tests.cs b/lib/volume/aobv/Aobv.Validation.Tests.cs index aba3e976..2df0638c 100644 --- a/lib/volume/aobv/Aobv.Validation.Tests.cs +++ b/lib/volume/aobv/Aobv.Validation.Tests.cs @@ -202,4 +202,4 @@ public class AobvValidationTests Assert.True(risingFastCount > 20, $"Fast EMA should trend up in uptrend, rising count: {risingFastCount}"); Assert.True(risingSlowCount > 15, $"Slow EMA should trend up in uptrend, rising count: {risingSlowCount}"); } -} \ No newline at end of file +} diff --git a/lib/volume/efi/Efi.Quantower.Tests.cs b/lib/volume/efi/Efi.Quantower.Tests.cs index 8c6b6170..32d57d11 100644 --- a/lib/volume/efi/Efi.Quantower.Tests.cs +++ b/lib/volume/efi/Efi.Quantower.Tests.cs @@ -156,4 +156,4 @@ public class EfiIndicatorTests double val = indicator.LinesSeries[0].GetValue(0); Assert.True(val < 0, $"EFI should be negative on sustained price decrease, got {val}"); } -} \ No newline at end of file +} diff --git a/lib/volume/efi/Efi.Tests.cs b/lib/volume/efi/Efi.Tests.cs index 419ec282..b0670e78 100644 --- a/lib/volume/efi/Efi.Tests.cs +++ b/lib/volume/efi/Efi.Tests.cs @@ -334,4 +334,4 @@ public class EfiTests var val = efi.Update(new TBar(time.AddMinutes(1), 10, 11, 4, 5, 500)); Assert.True(val.Value < 0); } -} \ No newline at end of file +} diff --git a/lib/volume/efi/Efi.Validation.Tests.cs b/lib/volume/efi/Efi.Validation.Tests.cs index e5be2c87..d7e434da 100644 --- a/lib/volume/efi/Efi.Validation.Tests.cs +++ b/lib/volume/efi/Efi.Validation.Tests.cs @@ -83,4 +83,4 @@ public class EfiValidationTests ValidationHelper.VerifyData(streamingValues.ToArray(), spanValues, 0, 100, 1e-12); } -} \ No newline at end of file +} diff --git a/lib/volume/eom/Eom.Quantower.Tests.cs b/lib/volume/eom/Eom.Quantower.Tests.cs index 9bff69ab..7d24183a 100644 --- a/lib/volume/eom/Eom.Quantower.Tests.cs +++ b/lib/volume/eom/Eom.Quantower.Tests.cs @@ -186,4 +186,4 @@ public class EomIndicatorTests Assert.True(double.IsFinite(val1)); Assert.True(double.IsFinite(val2)); } -} \ No newline at end of file +} diff --git a/lib/volume/eom/Eom.Validation.Tests.cs b/lib/volume/eom/Eom.Validation.Tests.cs index 06f312f1..fdaf5a15 100644 --- a/lib/volume/eom/Eom.Validation.Tests.cs +++ b/lib/volume/eom/Eom.Validation.Tests.cs @@ -137,4 +137,4 @@ public sealed class EomValidationTests : IDisposable ValidationHelper.VerifyData(streamingValues.ToArray(), spanValues, 0, 100, 1e-9); } -} \ No newline at end of file +} diff --git a/lib/volume/iii/Iii.Quantower.Tests.cs b/lib/volume/iii/Iii.Quantower.Tests.cs index 90005a99..8446ee11 100644 --- a/lib/volume/iii/Iii.Quantower.Tests.cs +++ b/lib/volume/iii/Iii.Quantower.Tests.cs @@ -195,4 +195,4 @@ public class IiiIndicatorTests Assert.True(double.IsFinite(val1)); Assert.True(double.IsFinite(val2)); } -} \ No newline at end of file +} diff --git a/lib/volume/iii/Iii.Tests.cs b/lib/volume/iii/Iii.Tests.cs index 6379ae87..bf45a56a 100644 --- a/lib/volume/iii/Iii.Tests.cs +++ b/lib/volume/iii/Iii.Tests.cs @@ -408,4 +408,4 @@ public class IiiTests var result2 = iii.Update(bar2); Assert.Equal(-0.5 * 200, result2.Value, 10); // -0.5 * volume } -} \ No newline at end of file +} diff --git a/lib/volume/iii/Iii.Validation.Tests.cs b/lib/volume/iii/Iii.Validation.Tests.cs index 4cb6ea56..101cdfb3 100644 --- a/lib/volume/iii/Iii.Validation.Tests.cs +++ b/lib/volume/iii/Iii.Validation.Tests.cs @@ -294,4 +294,4 @@ public class IiiValidationTests } } } -} \ No newline at end of file +} diff --git a/lib/volume/kvo/Kvo.Quantower.Tests.cs b/lib/volume/kvo/Kvo.Quantower.Tests.cs index 84e4f32a..34bdba3a 100644 --- a/lib/volume/kvo/Kvo.Quantower.Tests.cs +++ b/lib/volume/kvo/Kvo.Quantower.Tests.cs @@ -208,4 +208,4 @@ public class KvoIndicatorTests Assert.True(double.IsFinite(val1)); Assert.True(double.IsFinite(val2)); } -} \ No newline at end of file +} diff --git a/lib/volume/kvo/Kvo.Tests.cs b/lib/volume/kvo/Kvo.Tests.cs index 5590121e..ff1dcfc5 100644 --- a/lib/volume/kvo/Kvo.Tests.cs +++ b/lib/volume/kvo/Kvo.Tests.cs @@ -509,4 +509,4 @@ public class KvoTests Assert.True(kvo.IsHot); } -} \ No newline at end of file +} diff --git a/lib/volume/mfi/Mfi.Quantower.Tests.cs b/lib/volume/mfi/Mfi.Quantower.Tests.cs index 165133a2..e367ce54 100644 --- a/lib/volume/mfi/Mfi.Quantower.Tests.cs +++ b/lib/volume/mfi/Mfi.Quantower.Tests.cs @@ -119,4 +119,4 @@ public class MfiIndicatorTests Assert.Equal(21, indicator.MinHistoryDepths); Assert.Equal(21, ((IWatchlistIndicator)indicator).MinHistoryDepths); } -} \ No newline at end of file +} diff --git a/lib/volume/mfi/Mfi.Tests.cs b/lib/volume/mfi/Mfi.Tests.cs index 04da523a..fde5ebc5 100644 --- a/lib/volume/mfi/Mfi.Tests.cs +++ b/lib/volume/mfi/Mfi.Tests.cs @@ -409,4 +409,4 @@ public class MfiTests Assert.True(mfi.IsHot); } -} \ No newline at end of file +} diff --git a/lib/volume/mfi/Mfi.Validation.Tests.cs b/lib/volume/mfi/Mfi.Validation.Tests.cs index 65ea25fb..b0ef218a 100644 --- a/lib/volume/mfi/Mfi.Validation.Tests.cs +++ b/lib/volume/mfi/Mfi.Validation.Tests.cs @@ -52,7 +52,7 @@ public class MfiValidationTests var retCode = Functions.Mfi( highData, lowData, closeData, volumeData, 0..^0, taOut, out var outRange, period); - Assert.Equal(Core.RetCode.Success, retCode); + Assert.Equal(TALib.Core.RetCode.Success, retCode); (int offset, int length) = outRange.GetOffsetAndLength(taOut.Length); Assert.True(length > 100, $"TALib MFI produced only {length} values"); @@ -192,4 +192,4 @@ public class MfiValidationTests Assert.False(allEqual, "Different periods should produce different results"); } -} \ No newline at end of file +} diff --git a/lib/volume/nvi/Nvi.Quantower.Tests.cs b/lib/volume/nvi/Nvi.Quantower.Tests.cs index b03a3f71..b981ef1c 100644 --- a/lib/volume/nvi/Nvi.Quantower.Tests.cs +++ b/lib/volume/nvi/Nvi.Quantower.Tests.cs @@ -184,4 +184,4 @@ public class NviIndicatorTests Assert.True(secondVal > firstVal, $"NVI should increase when volume decreases and price rises: {secondVal} vs {firstVal}"); } -} \ No newline at end of file +} diff --git a/lib/volume/nvi/Nvi.Tests.cs b/lib/volume/nvi/Nvi.Tests.cs index 0fc00bd4..1272e9f8 100644 --- a/lib/volume/nvi/Nvi.Tests.cs +++ b/lib/volume/nvi/Nvi.Tests.cs @@ -426,4 +426,4 @@ public class NviTests Assert.True(nvi.IsHot); } -} \ No newline at end of file +} diff --git a/lib/volume/nvi/Nvi.Validation.Tests.cs b/lib/volume/nvi/Nvi.Validation.Tests.cs index 9f89c232..18e75cf3 100644 --- a/lib/volume/nvi/Nvi.Validation.Tests.cs +++ b/lib/volume/nvi/Nvi.Validation.Tests.cs @@ -260,4 +260,4 @@ public sealed class NviValidationTests : IDisposable nvi.Update(new TBar(time.AddMinutes(3), 110, 112, 98, 100, 70000)); Assert.Equal(1050 * (100.0 / 110.0), nvi.Last.Value, 6); } -} \ No newline at end of file +} diff --git a/lib/volume/obv/Obv.Quantower.Tests.cs b/lib/volume/obv/Obv.Quantower.Tests.cs index 7c3e8a57..f2bf9b90 100644 --- a/lib/volume/obv/Obv.Quantower.Tests.cs +++ b/lib/volume/obv/Obv.Quantower.Tests.cs @@ -221,4 +221,4 @@ public class ObvIndicatorTests double firstVal = indicator.LinesSeries[0].GetValue(0); Assert.Equal(0, firstVal); } -} \ No newline at end of file +} diff --git a/lib/volume/obv/Obv.Tests.cs b/lib/volume/obv/Obv.Tests.cs index 8a37fee7..c4a2e463 100644 --- a/lib/volume/obv/Obv.Tests.cs +++ b/lib/volume/obv/Obv.Tests.cs @@ -400,4 +400,4 @@ public class ObvTests obv.Update(new TBar(time.AddMinutes(4), 102, 112, 100, 110, 8000)); Assert.Equal(11000, obv.Last.Value); } -} \ No newline at end of file +} diff --git a/lib/volume/obv/Obv.Validation.Tests.cs b/lib/volume/obv/Obv.Validation.Tests.cs index ed50e572..9e0c4642 100644 --- a/lib/volume/obv/Obv.Validation.Tests.cs +++ b/lib/volume/obv/Obv.Validation.Tests.cs @@ -159,4 +159,4 @@ public class ObvValidationTests ValidationHelper.VerifyData(streamingValues.ToArray(), spanOutput, 0, 100, 1e-9); } -} \ No newline at end of file +} diff --git a/lib/volume/pvd/Pvd.Quantower.Tests.cs b/lib/volume/pvd/Pvd.Quantower.Tests.cs index d9cd42bb..217ce868 100644 --- a/lib/volume/pvd/Pvd.Quantower.Tests.cs +++ b/lib/volume/pvd/Pvd.Quantower.Tests.cs @@ -220,4 +220,4 @@ public class PvdIndicatorTests double val = indicator.LinesSeries[0].GetValue(0); Assert.True(double.IsFinite(val)); } -} \ No newline at end of file +} diff --git a/lib/volume/pvd/Pvd.Tests.cs b/lib/volume/pvd/Pvd.Tests.cs index 39fdd933..3dd3725e 100644 --- a/lib/volume/pvd/Pvd.Tests.cs +++ b/lib/volume/pvd/Pvd.Tests.cs @@ -600,4 +600,4 @@ public class PvdTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volume/pvd/Pvd.Validation.Tests.cs b/lib/volume/pvd/Pvd.Validation.Tests.cs index ce4ec02d..91bac401 100644 --- a/lib/volume/pvd/Pvd.Validation.Tests.cs +++ b/lib/volume/pvd/Pvd.Validation.Tests.cs @@ -375,4 +375,4 @@ public class PvdValidationTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volume/pvi/Pvi.Quantower.Tests.cs b/lib/volume/pvi/Pvi.Quantower.Tests.cs index d3dc48ec..a8029583 100644 --- a/lib/volume/pvi/Pvi.Quantower.Tests.cs +++ b/lib/volume/pvi/Pvi.Quantower.Tests.cs @@ -184,4 +184,4 @@ public class PviIndicatorTests Assert.True(secondVal > firstVal, $"PVI should increase when volume increases and price rises: {secondVal} vs {firstVal}"); } -} \ No newline at end of file +} diff --git a/lib/volume/pvi/Pvi.Tests.cs b/lib/volume/pvi/Pvi.Tests.cs index fa04ecd2..97ac7ff1 100644 --- a/lib/volume/pvi/Pvi.Tests.cs +++ b/lib/volume/pvi/Pvi.Tests.cs @@ -426,4 +426,4 @@ public class PviTests Assert.True(pvi.IsHot); } -} \ No newline at end of file +} diff --git a/lib/volume/pvi/Pvi.Validation.Tests.cs b/lib/volume/pvi/Pvi.Validation.Tests.cs index c172362f..328d8219 100644 --- a/lib/volume/pvi/Pvi.Validation.Tests.cs +++ b/lib/volume/pvi/Pvi.Validation.Tests.cs @@ -260,4 +260,4 @@ public sealed class PviValidationTests : IDisposable pvi.Update(new TBar(time.AddMinutes(3), 110, 112, 98, 100, 150000)); Assert.Equal(1050 * (100.0 / 110.0), pvi.Last.Value, 6); } -} \ No newline at end of file +} diff --git a/lib/volume/pvo/Pvo.Quantower.Tests.cs b/lib/volume/pvo/Pvo.Quantower.Tests.cs index bf5172e4..91713c3c 100644 --- a/lib/volume/pvo/Pvo.Quantower.Tests.cs +++ b/lib/volume/pvo/Pvo.Quantower.Tests.cs @@ -234,4 +234,4 @@ public class PvoIndicatorTests Assert.True(double.IsFinite(val1)); Assert.True(double.IsFinite(val2)); } -} \ No newline at end of file +} diff --git a/lib/volume/pvo/Pvo.Tests.cs b/lib/volume/pvo/Pvo.Tests.cs index cba74d1f..0b2421b1 100644 --- a/lib/volume/pvo/Pvo.Tests.cs +++ b/lib/volume/pvo/Pvo.Tests.cs @@ -517,4 +517,4 @@ public class PvoTests // After warmup with constant volume, PVO should be very close to 0 Assert.True(Math.Abs(pvo.Last.Value) < 0.01, $"PVO should be ~0 with constant volume, but was {pvo.Last.Value}"); } -} \ No newline at end of file +} diff --git a/lib/volume/pvo/Pvo.Validation.Tests.cs b/lib/volume/pvo/Pvo.Validation.Tests.cs index 9fe4c265..a5fed847 100644 --- a/lib/volume/pvo/Pvo.Validation.Tests.cs +++ b/lib/volume/pvo/Pvo.Validation.Tests.cs @@ -245,4 +245,4 @@ public class PvoValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite Ooples PVO values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/volume/pvr/Pvr.Quantower.Tests.cs b/lib/volume/pvr/Pvr.Quantower.Tests.cs index ec591f57..35b5c2c1 100644 --- a/lib/volume/pvr/Pvr.Quantower.Tests.cs +++ b/lib/volume/pvr/Pvr.Quantower.Tests.cs @@ -135,4 +135,4 @@ public class PvrIndicatorTests double val = indicator.LinesSeries[0].GetValue(0); Assert.Equal(4.0, val); } -} \ No newline at end of file +} diff --git a/lib/volume/pvr/Pvr.Tests.cs b/lib/volume/pvr/Pvr.Tests.cs index 8b964010..4b567015 100644 --- a/lib/volume/pvr/Pvr.Tests.cs +++ b/lib/volume/pvr/Pvr.Tests.cs @@ -354,4 +354,4 @@ public class PvrTests Assert.True(pvr.IsHot); } -} \ No newline at end of file +} diff --git a/lib/volume/pvr/Pvr.Validation.Tests.cs b/lib/volume/pvr/Pvr.Validation.Tests.cs index e298ec35..cf0f28f3 100644 --- a/lib/volume/pvr/Pvr.Validation.Tests.cs +++ b/lib/volume/pvr/Pvr.Validation.Tests.cs @@ -136,4 +136,4 @@ public class PvrValidationTests ValidationHelper.VerifyData(mode1Values.ToArray(), mode3Values, 0, 100, 1e-9); ValidationHelper.VerifyData(mode1Values.ToArray(), mode4Values, 0, 100, 1e-9); } -} \ No newline at end of file +} diff --git a/lib/volume/pvt/Pvt.Quantower.Tests.cs b/lib/volume/pvt/Pvt.Quantower.Tests.cs index e3f6996f..9391374a 100644 --- a/lib/volume/pvt/Pvt.Quantower.Tests.cs +++ b/lib/volume/pvt/Pvt.Quantower.Tests.cs @@ -225,4 +225,4 @@ public class PvtIndicatorTests double firstVal = indicator.LinesSeries[0].GetValue(0); Assert.Equal(0, firstVal); } -} \ No newline at end of file +} diff --git a/lib/volume/pvt/Pvt.Tests.cs b/lib/volume/pvt/Pvt.Tests.cs index 036a5025..34ba5918 100644 --- a/lib/volume/pvt/Pvt.Tests.cs +++ b/lib/volume/pvt/Pvt.Tests.cs @@ -458,4 +458,4 @@ public class PvtTests pvt.Update(new TBar(time.AddMinutes(3), 105, 108, 102, 105, 20000)); Assert.Equal(prevPvt, pvt.Last.Value, Tolerance); } -} \ No newline at end of file +} diff --git a/lib/volume/pvt/Pvt.Validation.Tests.cs b/lib/volume/pvt/Pvt.Validation.Tests.cs index ac0fd228..071a3541 100644 --- a/lib/volume/pvt/Pvt.Validation.Tests.cs +++ b/lib/volume/pvt/Pvt.Validation.Tests.cs @@ -112,4 +112,4 @@ public class PvtValidationTests double expected3 = expected2 + 2500 * (10.0 / 105.0); // = 369.9134... Assert.Equal(expected3, result3.Value, 1e-10); } -} \ No newline at end of file +} diff --git a/lib/volume/tvi/Tvi.Quantower.Tests.cs b/lib/volume/tvi/Tvi.Quantower.Tests.cs index 930c9e5a..de6681fa 100644 --- a/lib/volume/tvi/Tvi.Quantower.Tests.cs +++ b/lib/volume/tvi/Tvi.Quantower.Tests.cs @@ -275,4 +275,4 @@ public class TviIndicatorTests Assert.True(double.IsFinite(smallVal)); Assert.True(double.IsFinite(largeVal)); } -} \ No newline at end of file +} diff --git a/lib/volume/tvi/Tvi.Tests.cs b/lib/volume/tvi/Tvi.Tests.cs index a20f79ad..f05b8c17 100644 --- a/lib/volume/tvi/Tvi.Tests.cs +++ b/lib/volume/tvi/Tvi.Tests.cs @@ -480,4 +480,4 @@ public class TviTests // Results should differ due to sticky direction behavior Assert.NotEqual(tvi01.Last.Value, tvi05.Last.Value); } -} \ No newline at end of file +} diff --git a/lib/volume/tvi/Tvi.Validation.Tests.cs b/lib/volume/tvi/Tvi.Validation.Tests.cs index 47e4146a..0a1807b9 100644 --- a/lib/volume/tvi/Tvi.Validation.Tests.cs +++ b/lib/volume/tvi/Tvi.Validation.Tests.cs @@ -193,4 +193,4 @@ public class TviValidationTests int finiteCount = values.Count(v => double.IsFinite(v)); Assert.True(finiteCount > 100, $"Expected >100 finite values, got {finiteCount}"); } -} \ No newline at end of file +} diff --git a/lib/volume/twap/Twap.Quantower.Tests.cs b/lib/volume/twap/Twap.Quantower.Tests.cs index 7930adf0..293b4bfb 100644 --- a/lib/volume/twap/Twap.Quantower.Tests.cs +++ b/lib/volume/twap/Twap.Quantower.Tests.cs @@ -284,4 +284,4 @@ public class TwapIndicatorTests Assert.True(double.IsFinite(val), $"Value at bar {i} should be finite"); } } -} \ No newline at end of file +} diff --git a/lib/volume/twap/Twap.Tests.cs b/lib/volume/twap/Twap.Tests.cs index 825f93a7..91509e92 100644 --- a/lib/volume/twap/Twap.Tests.cs +++ b/lib/volume/twap/Twap.Tests.cs @@ -421,4 +421,4 @@ public class TwapTests Assert.Equal(100, result.Value, 10); } -} \ No newline at end of file +} diff --git a/lib/volume/twap/Twap.Validation.Tests.cs b/lib/volume/twap/Twap.Validation.Tests.cs index 838bcd95..6d16b7ea 100644 --- a/lib/volume/twap/Twap.Validation.Tests.cs +++ b/lib/volume/twap/Twap.Validation.Tests.cs @@ -178,4 +178,4 @@ public class TwapValidationTests $"TWAP {v} should be within reasonable bounds of price range [{minPrice}, {maxPrice}]"); } } -} \ No newline at end of file +} diff --git a/lib/volume/va/Va.Quantower.Tests.cs b/lib/volume/va/Va.Quantower.Tests.cs index ece5dcc2..e590eeb7 100644 --- a/lib/volume/va/Va.Quantower.Tests.cs +++ b/lib/volume/va/Va.Quantower.Tests.cs @@ -227,4 +227,4 @@ public class VaIndicatorTests double val = indicator.LinesSeries[0].GetValue(0); Assert.Equal(0, val, 1); } -} \ No newline at end of file +} diff --git a/lib/volume/va/Va.Tests.cs b/lib/volume/va/Va.Tests.cs index f087626c..abc20c4a 100644 --- a/lib/volume/va/Va.Tests.cs +++ b/lib/volume/va/Va.Tests.cs @@ -343,4 +343,4 @@ public class VaTests va.Update(new TBar(time.AddMinutes(2), 100, 115, 95, 98, 1500)); Assert.Equal(4500, va.Last.Value, 10); } -} \ No newline at end of file +} diff --git a/lib/volume/vf/Vf.Quantower.Tests.cs b/lib/volume/vf/Vf.Quantower.Tests.cs index 351fb3a6..c299c4fa 100644 --- a/lib/volume/vf/Vf.Quantower.Tests.cs +++ b/lib/volume/vf/Vf.Quantower.Tests.cs @@ -305,4 +305,4 @@ public class VfIndicatorTests Assert.True(hasPositive && hasNegative, "VF should oscillate around zero"); } -} \ No newline at end of file +} diff --git a/lib/volume/vf/Vf.Tests.cs b/lib/volume/vf/Vf.Tests.cs index 83d7c706..340c30f6 100644 --- a/lib/volume/vf/Vf.Tests.cs +++ b/lib/volume/vf/Vf.Tests.cs @@ -586,4 +586,4 @@ public class VfTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volume/vo/Vo.Quantower.Tests.cs b/lib/volume/vo/Vo.Quantower.Tests.cs index c0cdaf63..0c716a27 100644 --- a/lib/volume/vo/Vo.Quantower.Tests.cs +++ b/lib/volume/vo/Vo.Quantower.Tests.cs @@ -310,4 +310,4 @@ public class VoIndicatorTests Assert.True(hasPositive && hasNegative, "VO should oscillate around zero"); } -} \ No newline at end of file +} diff --git a/lib/volume/vo/Vo.Tests.cs b/lib/volume/vo/Vo.Tests.cs index 3ba385a9..3e08a50a 100644 --- a/lib/volume/vo/Vo.Tests.cs +++ b/lib/volume/vo/Vo.Tests.cs @@ -599,4 +599,4 @@ public class VoTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volume/vroc/Vroc.Quantower.Tests.cs b/lib/volume/vroc/Vroc.Quantower.Tests.cs index 48946118..0b6a692b 100644 --- a/lib/volume/vroc/Vroc.Quantower.Tests.cs +++ b/lib/volume/vroc/Vroc.Quantower.Tests.cs @@ -334,4 +334,4 @@ public class VrocIndicatorTests // Division by zero protection should return 0 Assert.Equal(0.0, val, 1); } -} \ No newline at end of file +} diff --git a/lib/volume/vroc/Vroc.Tests.cs b/lib/volume/vroc/Vroc.Tests.cs index b4412b41..4898f427 100644 --- a/lib/volume/vroc/Vroc.Tests.cs +++ b/lib/volume/vroc/Vroc.Tests.cs @@ -687,4 +687,4 @@ public class VrocTests } #endregion -} \ No newline at end of file +} diff --git a/lib/volume/vwad/Vwad.Quantower.Tests.cs b/lib/volume/vwad/Vwad.Quantower.Tests.cs index 24fb3d57..280bbeb0 100644 --- a/lib/volume/vwad/Vwad.Quantower.Tests.cs +++ b/lib/volume/vwad/Vwad.Quantower.Tests.cs @@ -161,4 +161,4 @@ public class VwadIndicatorTests // Different periods should produce different results Assert.NotEqual(val10, val20, 6); } -} \ No newline at end of file +} diff --git a/lib/volume/vwad/Vwad.Tests.cs b/lib/volume/vwad/Vwad.Tests.cs index d05b1559..f9669dc3 100644 --- a/lib/volume/vwad/Vwad.Tests.cs +++ b/lib/volume/vwad/Vwad.Tests.cs @@ -426,4 +426,4 @@ public class VwadTests Assert.Equal(expected, spanResult, precision: 9); Assert.Equal(expected, streamingResult, precision: 9); } -} \ No newline at end of file +} diff --git a/lib/volume/vwad/Vwad.Validation.Tests.cs b/lib/volume/vwad/Vwad.Validation.Tests.cs index 59e2f13e..49eeb330 100644 --- a/lib/volume/vwad/Vwad.Validation.Tests.cs +++ b/lib/volume/vwad/Vwad.Validation.Tests.cs @@ -244,4 +244,4 @@ public class VwadValidationTests // Most bars should cause changes (unless MFM happens to be exactly 0) Assert.True(changeCount > _data.Bars.Count * 0.5, "VWAD should change for most bars with non-zero MFM"); } -} \ No newline at end of file +} diff --git a/lib/volume/vwap/Vwap.Quantower.Tests.cs b/lib/volume/vwap/Vwap.Quantower.Tests.cs index a1ed5313..6c424cc8 100644 --- a/lib/volume/vwap/Vwap.Quantower.Tests.cs +++ b/lib/volume/vwap/Vwap.Quantower.Tests.cs @@ -189,4 +189,4 @@ public class VwapIndicatorTests // All values after reset should be similar (since price is constant) Assert.True(valuesAtReset.Count >= 2, "Should have multiple reset points"); } -} \ No newline at end of file +} diff --git a/lib/volume/vwap/Vwap.Tests.cs b/lib/volume/vwap/Vwap.Tests.cs index 24dccd02..b5048975 100644 --- a/lib/volume/vwap/Vwap.Tests.cs +++ b/lib/volume/vwap/Vwap.Tests.cs @@ -426,4 +426,4 @@ public class VwapTests Assert.Equal(batchResult.Values[i], streamingResults[i], 10); } } -} \ No newline at end of file +} diff --git a/lib/volume/vwap/Vwap.Validation.Tests.cs b/lib/volume/vwap/Vwap.Validation.Tests.cs index 27170b11..b6875b9a 100644 --- a/lib/volume/vwap/Vwap.Validation.Tests.cs +++ b/lib/volume/vwap/Vwap.Validation.Tests.cs @@ -258,4 +258,4 @@ public class VwapValidationTests double expectedTypicalPrice = (30.0 + 10.0 + 20.0) / 3.0; Assert.Equal(expectedTypicalPrice, result.Value, 10); } -} \ No newline at end of file +} diff --git a/lib/volume/vwma/Vwma.Quantower.Tests.cs b/lib/volume/vwma/Vwma.Quantower.Tests.cs index 7291e605..56080b26 100644 --- a/lib/volume/vwma/Vwma.Quantower.Tests.cs +++ b/lib/volume/vwma/Vwma.Quantower.Tests.cs @@ -190,4 +190,4 @@ public class VwmaIndicatorTests Assert.True(valueAfterHigh > valueAtConstant + 50, $"VWMA should increase as low-price bars drop out: {valueAtConstant} -> {valueAfterHigh}"); } -} \ No newline at end of file +} diff --git a/lib/volume/vwma/Vwma.Tests.cs b/lib/volume/vwma/Vwma.Tests.cs index 613d60f6..1b240baf 100644 --- a/lib/volume/vwma/Vwma.Tests.cs +++ b/lib/volume/vwma/Vwma.Tests.cs @@ -452,4 +452,4 @@ public class VwmaTests Assert.Equal(batchResult.Values[i], streamingResults[i], 10); } } -} \ No newline at end of file +} diff --git a/lib/volume/vwma/Vwma.Validation.Tests.cs b/lib/volume/vwma/Vwma.Validation.Tests.cs index 807b1010..13947235 100644 --- a/lib/volume/vwma/Vwma.Validation.Tests.cs +++ b/lib/volume/vwma/Vwma.Validation.Tests.cs @@ -399,4 +399,4 @@ public class VwmaValidationTests Assert.True(shortDiff < longDiff, "Short period VWMA should track price more closely"); } -} \ No newline at end of file +} diff --git a/lib/volume/wad/Wad.Quantower.Tests.cs b/lib/volume/wad/Wad.Quantower.Tests.cs index 40765749..1f5ef98e 100644 --- a/lib/volume/wad/Wad.Quantower.Tests.cs +++ b/lib/volume/wad/Wad.Quantower.Tests.cs @@ -85,4 +85,4 @@ public class WadIndicatorTests Assert.Equal(2, indicator.LinesSeries[0].Count); } -} \ No newline at end of file +} diff --git a/lib/volume/wad/Wad.Tests.cs b/lib/volume/wad/Wad.Tests.cs index 096f6ba5..5654e737 100644 --- a/lib/volume/wad/Wad.Tests.cs +++ b/lib/volume/wad/Wad.Tests.cs @@ -257,4 +257,4 @@ public class WadTests wad.Update(new TBar(DateTime.UtcNow, 100, 105, 95, 100, 1000)); Assert.True(wad.IsHot); } -} \ No newline at end of file +} diff --git a/perf/Benchmark.cs b/perf/Benchmark.cs index 50cef945..105fa0d5 100644 --- a/perf/Benchmark.cs +++ b/perf/Benchmark.cs @@ -48,6 +48,7 @@ public class IndicatorBenchmarks private const int Period = 220; private double[] _closeValues = null!; + private double[] _openValues = null!; private TSeries _closeTseries = null!; private List _quotes = null!; private List _ooplesData = null!; @@ -90,6 +91,7 @@ public class IndicatorBenchmarks _bars = bars; _closeValues = bars.Close.Values.ToArray(); + _openValues = bars.Open.Values.ToArray(); _highValues = bars.High.Values.ToArray(); _lowValues = bars.Low.Values.ToArray(); _volumeValues = bars.Volume.Values.ToArray(); @@ -384,6 +386,30 @@ public class IndicatorBenchmarks [Benchmark(Description = "Ooples HMA")] public object Ooples_Hma() => new StockData(_ooplesData).CalculateHullMovingAverage(MovingAvgType.WeightedMovingAverage, Period); + // ==================== CORRELATION ==================== + [BenchmarkCategory("CORRELATION")] + [Benchmark(Description = "QuanTAlib Correlation (Span)")] + public void QuanTAlib_Correlation_Span() => Correlation.Batch(_closeValues.AsSpan(), _openValues.AsSpan(), _quantalibOutput.AsSpan(), Period); + + [BenchmarkCategory("CORRELATION")] + [Benchmark(Description = "QuanTAlib Correlation (Streaming)")] + public void QuanTAlib_Correlation_Streaming() + { + var corr = new Correlation(Period); + for (int i = 0; i < _closeValues.Length; i++) + { + _quantalibOutput[i] = corr.Update(_closeValues[i], _openValues[i]).Value; + } + } + + [BenchmarkCategory("CORRELATION")] + [Benchmark(Description = "TALib Correlation")] + public Core.RetCode TALib_Correlation() => TALib.Functions.Correl(_closeValues, _openValues, 0..^0, _talibOutput, out _, Period); + + [BenchmarkCategory("CORRELATION")] + [Benchmark(Description = "Skender Correlation")] + public object Skender_Correlation() => _quotes.GetCorrelation(_quotes, Period); + // ==================== SKEW ==================== [BenchmarkCategory("SKEW")] [Benchmark(Description = "QuanTAlib Skew (Span)")]