From b7b5a4a1bfccef44a3647b210429ed872b6c4ecb Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Sun, 6 Oct 2024 14:44:43 -0700 Subject: [PATCH 1/4] clean code fixes --- .github/workflows/Publish.yml | 82 ++++----- .vscode/settings.json | 5 +- Directory.Build.props | 12 -- QuanTAlib.sln | 68 ++++---- Tests/Tests.csproj | 16 +- Tests/test_iTBar.cs | 6 +- Tests/test_iTValue.cs | 10 +- Tests/test_skender.stock.cs | 5 +- lib/averages/Afirma.cs | 301 ++++++++++++++++------------------ lib/averages/Alma.cs | 2 +- lib/averages/Convolution.cs | 4 +- lib/averages/Dema.cs | 2 +- lib/averages/Ema.cs | 10 +- lib/averages/Frama.cs | 151 +++++++++-------- lib/averages/Htit.cs | 10 +- lib/averages/Hwma.cs | 2 +- lib/averages/Jma.cs | 12 +- lib/averages/Kama.cs | 4 +- lib/averages/Ltma.cs | 2 +- lib/averages/Maaf.cs | 5 +- lib/averages/Mama.cs | 7 +- lib/averages/Mgdi.cs | 2 +- lib/averages/Mma.cs | 134 ++++++++------- lib/averages/Qema.cs | 2 +- lib/averages/Rema.cs | 2 +- lib/averages/Rma.cs | 45 +++-- lib/averages/Sma.cs | 2 +- lib/averages/Smma.cs | 2 +- lib/averages/Tema.cs | 4 +- lib/averages/Trima.cs | 9 +- lib/averages/Zlema.cs | 8 +- lib/core/AbstractBarBase.cs | 2 +- lib/core/abstractBase.cs | 2 +- lib/core/circularbuffer.cs | 142 ++++++++++------ lib/core/formatters.cs | 2 +- lib/core/tbar.cs | 4 +- lib/core/tvalue.cs | 4 +- lib/feeds/GbmFeed.cs | 2 +- lib/statistics/Entropy.cs | 2 +- lib/statistics/Kurtosis.cs | 2 +- lib/statistics/Max.cs | 4 +- lib/statistics/Median.cs | 13 +- lib/statistics/Min.cs | 36 ++-- lib/statistics/Mode.cs | 28 +++- lib/statistics/Percentile.cs | 41 +++-- lib/statistics/Skew.cs | 33 ++-- lib/statistics/Stddev.cs | 27 ++- lib/statistics/Variance.cs | 27 ++- lib/statistics/Zscore.cs | 30 ++-- lib/volatility/Historical.cs | 36 ++-- lib/volatility/Realized.cs | 33 ++-- lib/volatility/Rvi.cs | 34 ++-- 52 files changed, 787 insertions(+), 643 deletions(-) diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml index b3ca034c..094cd6e0 100644 --- a/.github/workflows/Publish.yml +++ b/.github/workflows/Publish.yml @@ -26,7 +26,8 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '9.x' + dotnet-quality: 'preview' - name: Install JDK11 for Sonar Scanner uses: actions/setup-java@v3 @@ -79,7 +80,8 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '9.x' + dotnet-quality: 'preview' - name: Install dotnet tools run: | @@ -134,7 +136,8 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '9.x' + dotnet-quality: 'preview' - name: Initialize CodeQL uses: github/codeql-action/init@v3 @@ -150,39 +153,41 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 - SecurityCodeScan: - runs-on: windows-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup NuGet - uses: nuget/setup-nuget@v1 - - - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 - with: - dotnet-version: '3.1.x' - - - name: Set up projects for analysis - uses: security-code-scan/security-code-scan-add-action@v1 - - - name: Restore dependencies - run: dotnet restore - - - name: Build - run: dotnet build --no-restore --configuration Debug - - - name: Convert SARIF for uploading to GitHub - uses: security-code-scan/security-code-scan-results-action@v1 - - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@v3 + #SecurityCodeScan: + # runs-on: windows-latest + # steps: + # - name: Checkout repository + # uses: actions/checkout@v4 + # with: + # fetch-depth: 0 +# + # - name: Setup NuGet + # uses: nuget/setup-nuget@v1 +# + # - name: Setup MSBuild + # uses: microsoft/setup-msbuild@v1 +# + # - name: Setup .NET SDK + # uses: actions/setup-dotnet@v3 + # with: + # dotnet-version: | + # 9.x + # 3.1.x + # dotnet-quality: 'preview' +# + # - name: Set up projects for analysis + # uses: security-code-scan/security-code-scan-add-action@v1 +# + # - name: Build + # run: | + # dotnet restore + # dotnet build --no-restore --configuration Debug +# + # - name: Convert SARIF for uploading to GitHub + # uses: security-code-scan/security-code-scan-results-action@v1 +# + # - name: Upload SARIF + # uses: github/codeql-action/upload-sarif@v3 Codacy_Scan: runs-on: ubuntu-latest @@ -212,7 +217,7 @@ jobs: sarif_file: results.sarif build_publish: - needs: [SonarCloud, Code_Coverage, CodeQL, SecurityCodeScan, Codacy_Scan] + needs: [SonarCloud, Code_Coverage, CodeQL, Codacy_Scan] if: success() runs-on: ubuntu-latest steps: @@ -224,7 +229,8 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '9.x' + dotnet-quality: 'preview' - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0 diff --git a/.vscode/settings.json b/.vscode/settings.json index b477d0fa..75b2bf41 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,8 @@ "sonarlint.connectedMode.project": { "connectionId": "mihakralj", "projectKey": "mihakralj_QuanTAlib" - } + }, + "dotnet.dotnetPath": "C:/Program Files/dotnet", + "omnisharp.useModernNet": true, + "omnisharp.sdkPath": "C:/Program Files/dotnet/sdk" } \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 65b4088f..83427b1d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,12 +20,6 @@ snupkg AnyCPU true - - - $(GitVersion_NuGetVersion) - $(GitVersion_AssemblySemVer) - $(GitVersion_AssemblySemFileVer) - $(GitVersion_InformationalVersion) @@ -50,10 +44,6 @@ - - all - runtime; build; native; contentfiles; analyzers - @@ -63,5 +53,3 @@ - - diff --git a/QuanTAlib.sln b/QuanTAlib.sln index 4d6759fe..bd69454d 100644 --- a/QuanTAlib.sln +++ b/QuanTAlib.sln @@ -1,18 +1,21 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "quantalib", "lib\quantalib.csproj", "{A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "quantalib", "lib\quantalib.csproj", "{1E050FA4-630E-4801-9DE9-D2536DACA9B0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "quantower", "quantower", "{1B9AC248-76F8-44DD-958D-F1DC08EE1E87}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyntheticVendor", "SyntheticVendor\SyntheticVendor.csproj", "{C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Statistics", "quantower\Statistics\Statistics.csproj", "{2E9427C7-144F-488E-A29D-789ACC1C32AE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Averages", "quantower\Averages\Averages.csproj", "{D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Averages", "quantower\Averages\Averages.csproj", "{6BE10C39-4127-446C-818B-7976FCDD51D5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Statistics", "quantower\Statistics\Statistics.csproj", "{E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volatility", "quantower\Volatility\Volatility.csproj", "{B7DC44F7-D3A3-4C70-9025-513E0182B646}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volatility", "quantower\Volatility\Volatility.csproj", "{F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyntheticVendor", "SyntheticVendor\SyntheticVendor.csproj", "{1CF111D9-33E6-4A11-8FEC-F23300A78D15}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{E204F173-5840-4AA3-BED1-98C8D2F813E3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -23,29 +26,34 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}.Release|Any CPU.Build.0 = Release|Any CPU - {B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}.Release|Any CPU.Build.0 = Release|Any CPU - {C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}.Release|Any CPU.Build.0 = Release|Any CPU - {D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}.Release|Any CPU.Build.0 = Release|Any CPU - {F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}.Release|Any CPU.Build.0 = Release|Any CPU + {1E050FA4-630E-4801-9DE9-D2536DACA9B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E050FA4-630E-4801-9DE9-D2536DACA9B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E050FA4-630E-4801-9DE9-D2536DACA9B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E050FA4-630E-4801-9DE9-D2536DACA9B0}.Release|Any CPU.Build.0 = Release|Any CPU + {2E9427C7-144F-488E-A29D-789ACC1C32AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E9427C7-144F-488E-A29D-789ACC1C32AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E9427C7-144F-488E-A29D-789ACC1C32AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E9427C7-144F-488E-A29D-789ACC1C32AE}.Release|Any CPU.Build.0 = Release|Any CPU + {6BE10C39-4127-446C-818B-7976FCDD51D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BE10C39-4127-446C-818B-7976FCDD51D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BE10C39-4127-446C-818B-7976FCDD51D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BE10C39-4127-446C-818B-7976FCDD51D5}.Release|Any CPU.Build.0 = Release|Any CPU + {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Release|Any CPU.Build.0 = Release|Any CPU + {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Release|Any CPU.Build.0 = Release|Any CPU + {E204F173-5840-4AA3-BED1-98C8D2F813E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E204F173-5840-4AA3-BED1-98C8D2F813E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E204F173-5840-4AA3-BED1-98C8D2F813E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E204F173-5840-4AA3-BED1-98C8D2F813E3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2E9427C7-144F-488E-A29D-789ACC1C32AE} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} + {6BE10C39-4127-446C-818B-7976FCDD51D5} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} + {B7DC44F7-D3A3-4C70-9025-513E0182B646} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} EndGlobalSection EndGlobal diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 0f30adf3..14c4f00a 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,22 +1,27 @@ - net8.0 + net9.0 QuanTAlib.Tests QuanTAlib.Tests - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + all + runtime; build; native; contentfiles; analyzers + + + - @@ -29,6 +34,7 @@ + diff --git a/Tests/test_iTBar.cs b/Tests/test_iTBar.cs index 3c8afe4a..ae0ba3f5 100644 --- a/Tests/test_iTBar.cs +++ b/Tests/test_iTBar.cs @@ -17,14 +17,14 @@ public class BarIndicatorTests rnd = new Random((int)DateTime.Now.Ticks); } - private static readonly iTValue[] indicators = new iTValue[] + private static readonly ITValue[] indicators = new ITValue[] { new Atr(period: 14), }; [Theory] [MemberData(nameof(GetIndicators))] - public void IndicatorIsNew(iTValue indicator) + public void IndicatorIsNew(ITValue indicator) { var indicator1 = indicator; var indicator2 = indicator; @@ -32,7 +32,7 @@ public class BarIndicatorTests MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; if (calcMethod == null) { - throw new Exception($"Calc method not found for indicator type: {indicator.GetType().Name}"); + throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}"); } for (int i = 0; i < SeriesLen; i++) diff --git a/Tests/test_iTValue.cs b/Tests/test_iTValue.cs index 4e5ceeb8..e7a2c41c 100644 --- a/Tests/test_iTValue.cs +++ b/Tests/test_iTValue.cs @@ -17,8 +17,8 @@ public class IndicatorTests rnd = new Random((int)DateTime.Now.Ticks); } - private static readonly iTValue[] indicators = - [ + private static readonly ITValue[] indicators = + { new Ema(period: 10, useSma: true), new Alma(period: 14, offset: 0.85, sigma: 6), new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman), @@ -60,11 +60,11 @@ public class IndicatorTests new Variance(period: 14), new Zscore(period: 14) - ]; + }; [Theory] [MemberData(nameof(GetIndicators))] - public void IndicatorIsNew(iTValue indicator) + public void IndicatorIsNew(ITValue indicator) { var indicator1 = indicator; var indicator2 = indicator; @@ -72,7 +72,7 @@ public class IndicatorTests MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; if (calcMethod == null) { - throw new Exception($"Calc method not found for indicator type: {indicator.GetType().Name}"); + throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}"); } for (int i = 0; i < SeriesLen; i++) diff --git a/Tests/test_skender.stock.cs b/Tests/test_skender.stock.cs index 65bc9f7d..09ef7ad7 100644 --- a/Tests/test_skender.stock.cs +++ b/Tests/test_skender.stock.cs @@ -12,7 +12,8 @@ public class SkenderTests private readonly GbmFeed feed; private readonly Random rnd; private readonly double range; - private int period, iterations; + private int period; + private readonly int iterations; private readonly IEnumerable quotes; @@ -338,7 +339,7 @@ public class SkenderTests var SK = quotes.GetAtr(lookbackPeriods: period).Select(i => i.Atr.Null2NaN()!); Assert.Equal(QL.Length, QL.Length); - for (int i = QL.Length - 1; i > period +500; i--) + for (int i = QL.Length - 1; i > period + 500; i--) { Assert.InRange(SK.ElementAt(i) - QL[i].Value, -range, range); } diff --git a/lib/averages/Afirma.cs b/lib/averages/Afirma.cs index 2bbb5b35..3832ad55 100644 --- a/lib/averages/Afirma.cs +++ b/lib/averages/Afirma.cs @@ -1,162 +1,151 @@ -using System; +namespace QuanTAlib; -namespace QuanTAlib +public class Afirma : AbstractBase { - public class Afirma : AbstractBase + public enum WindowType { - - public enum WindowType - { - Rectangular, - Hanning1, - Hanning2, - Blackman, - BlackmanHarris - } - - private readonly int Periods; - private readonly int Taps; - private readonly WindowType Window; - private readonly CircularBuffer _buffer; - private readonly double[] _weights; - private readonly double _wsum; - private readonly double[] _armaBuffer; - private readonly int _n; - private readonly double _sx2, _sx3, _sx4, _sx5, _sx6, _den; - - public Afirma(int periods, int taps, WindowType window) - { - if (periods < 1) - { - throw new ArgumentOutOfRangeException(nameof(periods), "Periods must be greater than or equal to 1."); - } - if (taps < 1) - { - throw new ArgumentOutOfRangeException(nameof(taps), "Taps must be greater than or equal to 1."); - } - Periods = periods; - Taps = taps; - Window = window; - WarmupPeriod = taps; - _buffer = new CircularBuffer(taps); - _weights = new double[taps]; - _wsum = CalculateWeights(); - _armaBuffer = new double[taps]; - _n = (Taps - 1) / 2; - - // Calculate least squares coefficients in the constructor - _sx2 = (2 * _n + 1) / 3.0; - _sx3 = _n * (_n + 1) / 2.0; - _sx4 = _sx2 * (3 * _n * _n + 3 * _n - 1) / 5.0; - _sx5 = _sx3 * (2 * _n * _n + 2 * _n - 1) / 3.0; - _sx6 = _sx2 * (3 * Math.Pow(_n, 3) * (_n + 2) - 3 * _n + 1) / 7.0; - _den = _sx6 * _sx4 / _sx5 - _sx5; - - Name = "Afirma"; - Init(); - } - - public Afirma(object source, int periods, int taps, WindowType window) : this(periods, taps, window) - { - var pubEvent = source.GetType().GetEvent("Pub"); - pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); - } - - protected override void ManageState(bool isNew) - { - if (isNew) - { - _lastValidValue = Input.Value; - _index++; - } - } - - protected override double Calculation() - { - ManageState(IsNew); - _buffer.Add(Input.Value, Input.IsNew); - - if (_index >= Taps) - { - double a0 = _buffer[_n]; - double a1 = _buffer[_n] - _buffer[_n + 1]; - double sx2y = 0.0; - double sx3y = 0.0; - - for (int i = 0; i <= _n; i++) - { - sx2y += i * i * _buffer[_n - i]; - sx3y += i * i * i * _buffer[_n - i]; - } - - sx2y = 2.0 * sx2y / _n / (_n + 1); - sx3y = 2.0 * sx3y / _n / (_n + 1); - double p = sx2y - a0 * _sx2 - a1 * _sx3; - double q = sx3y - a0 * _sx3 - a1 * _sx4; - double a2 = (p * _sx6 / _sx5 - q) / _den; - double a3 = (q * _sx4 / _sx5 - p) / _den; - - for (int k = 0; k <= _n; k++) - { - _armaBuffer[_n - k] = a0 + k * a1 + k * k * a2 + k * k * k * a3; - } - } - - double result = 0.0; - for (int k = 0; k < Taps; k++) - { - result += _buffer[k] * _weights[k] / _wsum; - } - - IsHot = _index >= WarmupPeriod; - return result; - } - - private double CalculateWeights() - { - double wsum = 0.0; - double centerTap = (Taps - 1) / 2.0; - for (int k = 0; k < Taps; k++) - { - double windowWeight; - switch (Window) - { - case WindowType.Rectangular: - windowWeight = 1.0; - break; - case WindowType.Hanning1: - windowWeight = 0.50 - 0.50 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)); - break; - case WindowType.Hanning2: - windowWeight = 0.54 - 0.46 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)); - break; - case WindowType.Blackman: - windowWeight = 0.42 - 0.50 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)) + 0.08 * Math.Cos(4.0 * Math.PI * k / (Taps - 1)); - break; - case WindowType.BlackmanHarris: - windowWeight = 0.35875 - 0.48829 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)) + 0.14128 * Math.Cos(4.0 * Math.PI * k / (Taps - 1)) - 0.01168 * Math.Cos(6.0 * Math.PI * k / (Taps - 1)); - break; - default: - windowWeight = 1.0; - break; - } - - double sincWeight; - if (Math.Abs(k - centerTap) < 1e-10) - { - sincWeight = 1.0; - } - else - { - sincWeight = Math.Sin(Math.PI * (k - centerTap) / Periods) / (Math.PI * (k - centerTap) / Periods); - } - - _weights[k] = windowWeight * sincWeight; - wsum += _weights[k]; - } - return wsum; - } - + Rectangular, + Hanning1, + Hanning2, + Blackman, + BlackmanHarris } + + private readonly int Periods; + private readonly int Taps; + private readonly WindowType Window; + private readonly CircularBuffer _buffer; + private readonly double[] _weights; + private readonly double _wsum; + private readonly double[] _armaBuffer; + private readonly int _n; + private readonly double _sx2, _sx3, _sx4, _sx5, _sx6, _den; + + public Afirma(int periods, int taps, WindowType window) + { + if (periods < 1) + { + throw new ArgumentOutOfRangeException(nameof(periods), "Periods must be greater than or equal to 1."); + } + if (taps < 1) + { + throw new ArgumentOutOfRangeException(nameof(taps), "Taps must be greater than or equal to 1."); + } + Periods = periods; + Taps = taps; + Window = window; + WarmupPeriod = taps; + _buffer = new CircularBuffer(taps); + _weights = new double[taps]; + _wsum = CalculateWeights(); + _armaBuffer = new double[taps]; + _n = (Taps - 1) / 2; + + // Calculate least squares coefficients in the constructor + _sx2 = (2 * _n + 1) / 3.0; + _sx3 = _n * (_n + 1) / 2.0; + _sx4 = _sx2 * (3 * _n * _n + 3 * _n - 1) / 5.0; + _sx5 = _sx3 * (2 * _n * _n + 2 * _n - 1) / 3.0; + _sx6 = _sx2 * (3 * Math.Pow(_n, 3) * (_n + 2) - 3 * _n + 1) / 7.0; + _den = _sx6 * _sx4 / _sx5 - _sx5; + + Name = "Afirma"; + Init(); + } + + public Afirma(object source, int periods, int taps, WindowType window) : this(periods, taps, window) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Input.Value; + _index++; + } + } + + protected override double Calculation() + { + ManageState(IsNew); + _buffer.Add(Input.Value, Input.IsNew); + + if (_index >= Taps) + { + double a0 = _buffer[_n]; + double a1 = _buffer[_n] - _buffer[_n + 1]; + double sx2y = 0.0; + double sx3y = 0.0; + + for (int i = 0; i <= _n; i++) + { + sx2y += i * i * _buffer[_n - i]; + sx3y += i * i * i * _buffer[_n - i]; + } + + sx2y = 2.0 * sx2y / _n / (_n + 1); + sx3y = 2.0 * sx3y / _n / (_n + 1); + double p = sx2y - a0 * _sx2 - a1 * _sx3; + double q = sx3y - a0 * _sx3 - a1 * _sx4; + double a2 = (p * _sx6 / _sx5 - q) / _den; + double a3 = (q * _sx4 / _sx5 - p) / _den; + + for (int k = 0; k <= _n; k++) + { + _armaBuffer[_n - k] = a0 + k * a1 + k * k * a2 + k * k * k * a3; + } + } + + double result = 0.0; + for (int k = 0; k < Taps; k++) + { + result += _buffer[k] * _weights[k] / _wsum; + } + + IsHot = _index >= WarmupPeriod; + return result; + } + + private double CalculateWeights() + { + double wsum = 0.0; + double centerTap = (Taps - 1) / 2.0; + for (int k = 0; k < Taps; k++) + { + double windowWeight; + switch (Window) + { + case WindowType.Rectangular: + windowWeight = 1.0; + break; + case WindowType.Hanning1: + windowWeight = 0.50 - 0.50 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)); + break; + case WindowType.Hanning2: + windowWeight = 0.54 - 0.46 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)); + break; + case WindowType.Blackman: + windowWeight = 0.42 - 0.50 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)) + 0.08 * Math.Cos(4.0 * Math.PI * k / (Taps - 1)); + break; + case WindowType.BlackmanHarris: + windowWeight = 0.35875 - 0.48829 * Math.Cos(2.0 * Math.PI * k / (Taps - 1)) + 0.14128 * Math.Cos(4.0 * Math.PI * k / (Taps - 1)) - 0.01168 * Math.Cos(6.0 * Math.PI * k / (Taps - 1)); + break; + default: + windowWeight = 1.0; + break; + } + + double sincWeight; + sincWeight = Math.Abs(k - centerTap) < 1e-10 ? 1.0 : Math.Sin(Math.PI * (k - centerTap) / Periods) / (Math.PI * (k - centerTap) / Periods); + + _weights[k] = windowWeight * sincWeight; + wsum += _weights[k]; + } + return wsum; + } + } diff --git a/lib/averages/Alma.cs b/lib/averages/Alma.cs index dd031ffd..6f363795 100644 --- a/lib/averages/Alma.cs +++ b/lib/averages/Alma.cs @@ -25,7 +25,7 @@ public class Alma : AbstractBase /// Controls the smoothness and high-frequency filtering. Default is 0.85. /// Controls the shape of the Gaussian distribution. Default is 6. /// Thrown when period is less than 1. - public Alma(int period, double offset = 0.85, double sigma = 6) : base() + public Alma(int period, double offset = 0.85, double sigma = 6) { if (period < 1) { diff --git a/lib/averages/Convolution.cs b/lib/averages/Convolution.cs index 470f426c..60226d61 100644 --- a/lib/averages/Convolution.cs +++ b/lib/averages/Convolution.cs @@ -4,8 +4,8 @@ public class Convolution : AbstractBase { private readonly double[] _kernel; private readonly int _kernelSize; - private CircularBuffer _buffer; - private double[] _normalizedKernel; + private readonly CircularBuffer _buffer; + private readonly double[] _normalizedKernel; public Convolution(double[] kernel) { diff --git a/lib/averages/Dema.cs b/lib/averages/Dema.cs index afd57c81..d3e37bfb 100644 --- a/lib/averages/Dema.cs +++ b/lib/averages/Dema.cs @@ -28,7 +28,7 @@ public class Dema : AbstractBase private double _lastEma2, _p_lastEma2; private double _k, _e, _p_e; - public Dema(int period) : base() + public Dema(int period) { if (period < 1) { diff --git a/lib/averages/Ema.cs b/lib/averages/Ema.cs index 056ec24a..9d53fa4e 100644 --- a/lib/averages/Ema.cs +++ b/lib/averages/Ema.cs @@ -27,10 +27,12 @@ public class Ema : AbstractBase private readonly int _period; private CircularBuffer _sma; private double _lastEma, _p_lastEma; - private double _k, _e, _p_e; - private bool _isInit, _p_isInit, _useSma; + private double _e, _p_e; + private readonly double _k; + private bool _isInit, _p_isInit; + private readonly bool _useSma; - public Ema(int period, bool useSma = true) : base() + public Ema(int period, bool useSma = true) { if (period < 1) { @@ -45,7 +47,7 @@ public class Ema : AbstractBase Init(); } - public Ema(double alpha) : base() + public Ema(double alpha) { _k = alpha; _useSma = false; diff --git a/lib/averages/Frama.cs b/lib/averages/Frama.cs index 8a260cf3..76905d02 100644 --- a/lib/averages/Frama.cs +++ b/lib/averages/Frama.cs @@ -1,99 +1,98 @@ using System; -namespace QuanTAlib +namespace QuanTAlib; + +public class Frama : AbstractBase { - public class Frama : AbstractBase + private readonly int _period; + private readonly double _fc; + private readonly CircularBuffer _buffer; + private double _lastFrama; + private double _prevLastFrama; + + public Frama(int period, double fc = 0.5) { - private readonly int _period; - private readonly double _fc; - private CircularBuffer _buffer; - private double _lastFrama; - private double _prevLastFrama; + if (period < 2) + throw new ArgumentException("Period must be at least 2", nameof(period)); - public Frama(int period, double fc = 0.5) : base() + _period = period; + _fc = fc; + _buffer = new CircularBuffer(period); + WarmupPeriod = period; + } + + public override void Init() + { + base.Init(); + _buffer.Clear(); + _lastFrama = 0; + _prevLastFrama = 0; + } + + protected override void ManageState(bool isNew) + { + if (isNew) { - if (period < 2) - throw new ArgumentException("Period must be at least 2", nameof(period)); + _prevLastFrama = _lastFrama; + _index++; + } + else + { + _lastFrama = _prevLastFrama; + } + } - _period = period; - _fc = fc; - _buffer = new CircularBuffer(period); - WarmupPeriod = period; + protected override double Calculation() + { + ManageState(Input.IsNew); + + _buffer.Add(Input.Value, Input.IsNew); + + if (_buffer.Count < _period) + { + _lastFrama = _buffer.Average(); + return _lastFrama; } - public override void Init() - { - base.Init(); - _buffer.Clear(); - _lastFrama = 0; - _prevLastFrama = 0; - } + int half = _period / 2; + double hh = double.MinValue, ll = double.MaxValue; + double hh1 = double.MinValue, ll1 = double.MaxValue; + double hh2 = double.MinValue, ll2 = double.MaxValue; - protected override void ManageState(bool isNew) + for (int i = 0; i < _period; i++) { - if (isNew) + double price = _buffer[i]; + hh = Math.Max(hh, price); + ll = Math.Min(ll, price); + + if (i < half) { - _prevLastFrama = _lastFrama; - _index++; + hh1 = Math.Max(hh1, price); + ll1 = Math.Min(ll1, price); } else { - _lastFrama = _prevLastFrama; + hh2 = Math.Max(hh2, price); + ll2 = Math.Min(ll2, price); } } - protected override double Calculation() - { - ManageState(Input.IsNew); - - _buffer.Add(Input.Value, Input.IsNew); + double n1 = (hh - ll) / _period; + double n2 = (hh1 - ll1 + hh2 - ll2) / (_period / 2); - if (_buffer.Count < _period) - { - _lastFrama = _buffer.Average(); - return _lastFrama; - } + double d = (Math.Log(n2 + double.Epsilon) - Math.Log(n1 + double.Epsilon)) / Math.Log(2); - int half = _period / 2; - double hh = double.MinValue, ll = double.MaxValue; - double hh1 = double.MinValue, ll1 = double.MaxValue; - double hh2 = double.MinValue, ll2 = double.MaxValue; + double alpha = Math.Exp(-4.6 * (d - 1)); + alpha = Math.Max(Math.Min(alpha, 1), 0.01); // Ensure alpha is between 0.01 and 1 - for (int i = 0; i < _period; i++) - { - double price = _buffer[i]; - hh = Math.Max(hh, price); - ll = Math.Min(ll, price); + _lastFrama = alpha * (Input.Value - _lastFrama) + _lastFrama; - if (i < half) - { - hh1 = Math.Max(hh1, price); - ll1 = Math.Min(ll1, price); - } - else - { - hh2 = Math.Max(hh2, price); - ll2 = Math.Min(ll2, price); - } - } - - double n1 = (hh - ll) / _period; - double n2 = (hh1 - ll1 + hh2 - ll2) / (_period / 2); - - double d = (Math.Log(n2 + double.Epsilon) - Math.Log(n1 + double.Epsilon)) / Math.Log(2); - - double alpha = Math.Exp(-4.6 * (d - 1)); - alpha = Math.Max(Math.Min(alpha, 1), 0.01); // Ensure alpha is between 0.01 and 1 - - _lastFrama = alpha * (Input.Value - _lastFrama) + _lastFrama; - - IsHot = _index >= WarmupPeriod; - return _lastFrama; - } - - protected override double GetLastValid() - { - return _lastFrama; - } + IsHot = _index >= WarmupPeriod; + return _lastFrama; } -} \ No newline at end of file + + protected override double GetLastValid() + { + return _lastFrama; + } +} diff --git a/lib/averages/Htit.cs b/lib/averages/Htit.cs index 08767763..cc1a2f2b 100644 --- a/lib/averages/Htit.cs +++ b/lib/averages/Htit.cs @@ -1,7 +1,7 @@ //not working yet //TODO consistency test -using QuanTAlib; +namespace QuanTAlib; public class Htit : AbstractBase { @@ -21,7 +21,7 @@ public class Htit : AbstractBase private double _lastPd = 0; private double _p_lastPd = 0; - public Htit() : base() + public Htit() { Name = "Htit"; WarmupPeriod = 12; @@ -138,9 +138,7 @@ public class Htit : AbstractBase { return ((4 * _itBuffer[0]) + (3 * _itBuffer[1]) + (2 * _itBuffer[2]) + _itBuffer[3]) / 10; } - else - { - return pr; - } + + return pr; } } \ No newline at end of file diff --git a/lib/averages/Hwma.cs b/lib/averages/Hwma.cs index b6e8d492..c2473349 100644 --- a/lib/averages/Hwma.cs +++ b/lib/averages/Hwma.cs @@ -15,7 +15,7 @@ public class Hwma : AbstractBase { } - public Hwma(int period, double nA, double nB, double nC) : base() + public Hwma(int period, double nA, double nB, double nC) { if (period < 1) { diff --git a/lib/averages/Jma.cs b/lib/averages/Jma.cs index e5a58b11..702fc6db 100644 --- a/lib/averages/Jma.cs +++ b/lib/averages/Jma.cs @@ -1,20 +1,20 @@ -using QuanTAlib; +namespace QuanTAlib; //TODO consistency test public class Jma : AbstractBase { public readonly int Period; private readonly double _phase; private readonly int _vshort, _vlong; - private CircularBuffer _values; - private CircularBuffer _voltyShort; - private CircularBuffer _vsumBuff; - private CircularBuffer _avoltyBuff; + private readonly CircularBuffer _values; + private readonly CircularBuffer _voltyShort; + private readonly CircularBuffer _vsumBuff; + private readonly CircularBuffer _avoltyBuff; private double _beta, _len1, _pow1; private double _upperBand, _lowerBand, _prevMa1, _prevDet0, _prevDet1, _prevJma; private double _p_UpperBand, _p_LowerBand, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_prevJma; - public Jma(int period, double phase = 0, int vshort = 10) : base() + public Jma(int period, double phase = 0, int vshort = 10) { if (period < 1) { diff --git a/lib/averages/Kama.cs b/lib/averages/Kama.cs index 81cda9cf..e509c5e3 100644 --- a/lib/averages/Kama.cs +++ b/lib/averages/Kama.cs @@ -1,5 +1,3 @@ -using System; - namespace QuanTAlib; public class Kama : AbstractBase @@ -9,7 +7,7 @@ public class Kama : AbstractBase private CircularBuffer? _buffer; private double _lastKama, _p_lastKama; - public Kama(int period, int fast = 2, int slow = 30) : base() + public Kama(int period, int fast = 2, int slow = 30) { if (period < 1) { diff --git a/lib/averages/Ltma.cs b/lib/averages/Ltma.cs index 5d64d249..e0aca68b 100644 --- a/lib/averages/Ltma.cs +++ b/lib/averages/Ltma.cs @@ -10,7 +10,7 @@ public class Ltma : AbstractBase public double Gamma => _gamma; - public Ltma(double gamma = 0.1) : base() + public Ltma(double gamma = 0.1) { if (gamma < 0 || gamma > 1) throw new ArgumentOutOfRangeException(nameof(gamma), "Gamma must be between 0 and 1."); diff --git a/lib/averages/Maaf.cs b/lib/averages/Maaf.cs index 8230d4e8..261adf52 100644 --- a/lib/averages/Maaf.cs +++ b/lib/averages/Maaf.cs @@ -8,12 +8,13 @@ public class Maaf : AbstractBase { private readonly CircularBuffer _priceBuffer; private readonly CircularBuffer _smoothBuffer; - private double _prevFilter, _prevValue2, _threshold; + private double _prevFilter, _prevValue2; + private readonly double _threshold; private double _p_prevFilter, _p_prevValue2; private readonly int _period; - public Maaf(int Period = 39, double Threshold = 0.002) : base() + public Maaf(int Period = 39, double Threshold = 0.002) { _period = Period; _threshold = Threshold; diff --git a/lib/averages/Mama.cs b/lib/averages/Mama.cs index b6647561..3af81ddb 100644 --- a/lib/averages/Mama.cs +++ b/lib/averages/Mama.cs @@ -1,17 +1,16 @@ -using QuanTAlib; -using System; +namespace QuanTAlib; public class Mama : AbstractBase { private readonly double _fastLimit, _slowLimit; - private CircularBuffer _pr, _sm, _dt, _i1, _q1, _i2, _q2, _re, _im, _pd, _ph; + private readonly CircularBuffer _pr, _sm, _dt, _i1, _q1, _i2, _q2, _re, _im, _pd, _ph; private double _mama, _fama; private double _prevMama, _prevFama, _sumPr; private double _p_prevMama, _p_prevFama, _p_sumPr; public TValue Fama { get; private set; } - public Mama(double fastLimit = 0.5, double slowLimit = 0.05) : base() + public Mama(double fastLimit = 0.5, double slowLimit = 0.05) { Fama = new TValue(); Name = $"Mama({_fastLimit:F2}, {_slowLimit:F2})"; diff --git a/lib/averages/Mgdi.cs b/lib/averages/Mgdi.cs index 59970815..7435b898 100644 --- a/lib/averages/Mgdi.cs +++ b/lib/averages/Mgdi.cs @@ -5,7 +5,7 @@ public class Mgdi : AbstractBase private readonly int _period; private readonly double _kFactor; private double _prevMd, _p_prevMd; - public Mgdi(int period, double kFactor = 0.6) : base() + public Mgdi(int period, double kFactor = 0.6) { if (period <= 0) { diff --git a/lib/averages/Mma.cs b/lib/averages/Mma.cs index 0a7d27a6..cc02e1bc 100644 --- a/lib/averages/Mma.cs +++ b/lib/averages/Mma.cs @@ -1,78 +1,74 @@ -using System; -using System.Linq; +namespace QuanTAlib; -namespace QuanTAlib +public class Mma : AbstractBase { - public class Mma : AbstractBase + private readonly int _period; + private readonly CircularBuffer _buffer; + private double _lastMma; + + public Mma(int period) { - private readonly int _period; - private readonly CircularBuffer _buffer; - private double _lastMma; - - public Mma(int period) : base() + if (period < 2) { - if (period < 2) - { - throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); - } - _period = period; - _buffer = new CircularBuffer(period); - Name = "Mma"; - WarmupPeriod = period; - Init(); + throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); } + _period = period; + _buffer = new CircularBuffer(period); + Name = "Mma"; + WarmupPeriod = period; + Init(); + } - public Mma(object source, int period) : this(period) + public Mma(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + public override void Init() + { + base.Init(); + _lastMma = 0; + _buffer.Clear(); + } + + protected override void ManageState(bool isNew) + { + if (isNew) { - var pubEvent = source.GetType().GetEvent("Pub"); - pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); - } - - public override void Init() - { - base.Init(); - _lastMma = 0; - _buffer.Clear(); - } - - protected override void ManageState(bool isNew) - { - if (isNew) - { - _index++; - } - } - - protected override double Calculation() - { - ManageState(Input.IsNew); - _buffer.Add(Input.Value, Input.IsNew); - - if (_index >= _period) - { - double T = _buffer.Sum(); - double S = CalculateWeightedSum(); - _lastMma = (T / _period) + (6 * S) / ((_period + 1) * _period); - } - else - { - // Use simple average until we have enough data points - _lastMma = _buffer.Average(); - } - - IsHot = _index >= _period; - return _lastMma; - } - - private double CalculateWeightedSum() - { - double sum = 0; - for (int i = 0; i < _period; i++) - { - double weight = (_period - (2 * i + 1)) / 2.0; - sum += weight * _buffer[^(i + 1)]; - } - return sum; + _index++; } } -} \ No newline at end of file + + protected override double Calculation() + { + ManageState(Input.IsNew); + _buffer.Add(Input.Value, Input.IsNew); + + if (_index >= _period) + { + double T = _buffer.Sum(); + double S = CalculateWeightedSum(); + _lastMma = (T / _period) + (6 * S) / ((_period + 1) * _period); + } + else + { + // Use simple average until we have enough data points + _lastMma = _buffer.Average(); + } + + IsHot = _index >= _period; + return _lastMma; + } + + private double CalculateWeightedSum() + { + double sum = 0; + for (int i = 0; i < _period; i++) + { + double weight = (_period - (2 * i + 1)) / 2.0; + sum += weight * _buffer[^(i + 1)]; + } + return sum; + } +} diff --git a/lib/averages/Qema.cs b/lib/averages/Qema.cs index f032cd07..87f38f28 100644 --- a/lib/averages/Qema.cs +++ b/lib/averages/Qema.cs @@ -6,7 +6,7 @@ public class Qema : AbstractBase private readonly Ema _ema1, _ema2, _ema3, _ema4; private double _lastQema, _p_lastQema; - public Qema(double k1=0.2, double k2=0.2, double k3=0.2, double k4=0.2) : base() + public Qema(double k1=0.2, double k2=0.2, double k3=0.2, double k4=0.2) { if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0 ) { diff --git a/lib/averages/Rema.cs b/lib/averages/Rema.cs index c89782c7..be9168d1 100644 --- a/lib/averages/Rema.cs +++ b/lib/averages/Rema.cs @@ -12,7 +12,7 @@ public class Rema : AbstractBase public int Period => _period; public double Lambda => _lambda; - public Rema(int period, double lambda = 0.5) : base() + public Rema(int period, double lambda = 0.5) { if (period < 1) throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); diff --git a/lib/averages/Rma.cs b/lib/averages/Rma.cs index 3dadbba2..b6255690 100644 --- a/lib/averages/Rma.cs +++ b/lib/averages/Rma.cs @@ -1,16 +1,20 @@ using System; using System.Runtime.CompilerServices; -namespace QuanTAlib { +namespace QuanTAlib; -public class Rma : AbstractBase { + +public class Rma : AbstractBase +{ private readonly int _period; - private double _alpha; + private readonly double _alpha; private double _lastRMA; private double _savedLastRMA; - public Rma(int period) : base() { - if (period < 1) { + public Rma(int period) + { + if (period < 1) + { throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); } _period = period; @@ -20,38 +24,50 @@ public class Rma : AbstractBase { Init(); } - public Rma(object source, int period) : this(period) { + public Rma(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } - public override void Init() { + public override void Init() + { base.Init(); _lastRMA = 0; _savedLastRMA = 0; } - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _savedLastRMA = _lastRMA; _lastValidValue = Input.Value; _index++; - } else { + } + else + { _lastRMA = _savedLastRMA; } } - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); double rma; - if (_index == 1) { + if (_index == 1) + { rma = Input.Value; - } else if (_index <= _period) { + } + else if (_index <= _period) + { // Simple average during initial period rma = (_lastRMA * (_index - 1) + Input.Value) / _index; - } else { + } + else + { // Wilder's smoothing method rma = _alpha * (Input.Value - _lastRMA) + _lastRMA; } @@ -62,4 +78,3 @@ public class Rma : AbstractBase { return rma; } } -} \ No newline at end of file diff --git a/lib/averages/Sma.cs b/lib/averages/Sma.cs index 0df625b2..c0294bd0 100644 --- a/lib/averages/Sma.cs +++ b/lib/averages/Sma.cs @@ -6,7 +6,7 @@ public class Sma : AbstractBase // inherited _value private readonly CircularBuffer _buffer; - public Sma(int period) : base() + public Sma(int period) { if (period < 1) { diff --git a/lib/averages/Smma.cs b/lib/averages/Smma.cs index 1e20301c..0ca8c403 100644 --- a/lib/averages/Smma.cs +++ b/lib/averages/Smma.cs @@ -8,7 +8,7 @@ public class Smma : AbstractBase private CircularBuffer? _buffer; private double _lastSmma, _p_lastSmma; - public Smma(int period) : base() + public Smma(int period) { if (period < 1) { diff --git a/lib/averages/Tema.cs b/lib/averages/Tema.cs index 2b12f6ee..4d4abef7 100644 --- a/lib/averages/Tema.cs +++ b/lib/averages/Tema.cs @@ -8,7 +8,7 @@ public class Tema : AbstractBase private double _lastEma3, _p_lastEma3; private double _k, _e, _p_e; - public Tema(int period) : base() + public Tema(int period) { if (period < 1) { @@ -58,7 +58,7 @@ public class Tema : AbstractBase { double result, _ema1, _ema2, _ema3; ManageState(Input.IsNew); - + _e = (_e > 1e-10) ? (1 - _k) * _e : 0; double _invE = (_e > 1e-10) ? 1 / (1 - _e) : 1; diff --git a/lib/averages/Trima.cs b/lib/averages/Trima.cs index 7175d40a..7cfeb715 100644 --- a/lib/averages/Trima.cs +++ b/lib/averages/Trima.cs @@ -30,14 +30,7 @@ public class Trima : AbstractBase for (int i = 0; i < period; i++) { - if (i < halfPeriod) - { - kernel[i] = i + 1; - } - else - { - kernel[i] = period - i; - } + kernel[i] = i < halfPeriod ? i + 1 : period - i; weightSum += kernel[i]; } diff --git a/lib/averages/Zlema.cs b/lib/averages/Zlema.cs index b7115d66..32868b5f 100644 --- a/lib/averages/Zlema.cs +++ b/lib/averages/Zlema.cs @@ -7,11 +7,11 @@ public class Zlema : AbstractBase { private readonly int _period; private CircularBuffer? _buffer; - private double _alpha; - private int _lag; + private readonly double _alpha; + private readonly int _lag; private double _lastZLEMA, _p_lastZLEMA; - public Zlema(int period) : base() + public Zlema(int period) { if (period < 1) { @@ -55,7 +55,7 @@ public class Zlema : AbstractBase protected override double Calculation() { ManageState(Input.IsNew); - + _buffer!.Add(Input.Value, Input.IsNew); int lag = Math.Max(Math.Min((int)((_period - 1) * 0.5), _buffer.Count - 1), 0) + 1; diff --git a/lib/core/AbstractBarBase.cs b/lib/core/AbstractBarBase.cs index 9a4de1cf..989c17ef 100644 --- a/lib/core/AbstractBarBase.cs +++ b/lib/core/AbstractBarBase.cs @@ -8,7 +8,7 @@ namespace QuanTAlib; /// and methods used by inheriting indicator types. It handles the basic flow of /// receiving bar data, performing calculations, and publishing results. /// -public abstract class AbstractBarBase : iTValue { +public abstract class AbstractBarBase : ITValue { public DateTime Time { get; set; } public double Value { get; set; } public bool IsNew { get; set; } diff --git a/lib/core/abstractBase.cs b/lib/core/abstractBase.cs index ee6ea0cb..71b0ca6d 100644 --- a/lib/core/abstractBase.cs +++ b/lib/core/abstractBase.cs @@ -8,7 +8,7 @@ namespace QuanTAlib; /// and methods used by inheriting indicator types. It handles the basic flow of /// receiving data, performing calculations, and publishing results. /// -public abstract class AbstractBase : iTValue +public abstract class AbstractBase : ITValue { public DateTime Time { get; set; } public double Value { get; set; } diff --git a/lib/core/circularbuffer.cs b/lib/core/circularbuffer.cs index 04af52a5..c6d1fd0a 100644 --- a/lib/core/circularbuffer.cs +++ b/lib/core/circularbuffer.cs @@ -12,7 +12,8 @@ namespace QuanTAlib; /// a fixed-size buffer of double values. It uses SIMD operations for improved performance /// on supported hardware. /// -public class CircularBuffer : IEnumerable { +public class CircularBuffer : IEnumerable +{ private readonly double[] _buffer; private int _start = 0; private int _size = 0; @@ -31,7 +32,8 @@ public class CircularBuffer : IEnumerable { /// Initializes a new instance of the CircularBuffer class with the specified capacity. /// /// The maximum number of elements the buffer can hold. - public CircularBuffer(int capacity) { + public CircularBuffer(int capacity) + { Capacity = capacity; _buffer = GC.AllocateArray(capacity, pinned: true); } @@ -42,16 +44,23 @@ public class CircularBuffer : IEnumerable { /// The item to add to the buffer. /// Indicates whether the item is a new value or an update to the last added value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(double item, bool isNew = true) { - if (_size == 0 || isNew) { - if (_size < Capacity) { + public void Add(double item, bool isNew = true) + { + if (_size == 0 || isNew) + { + if (_size < Capacity) + { _buffer[(_start + _size) % Capacity] = item; _size++; - } else { + } + else + { _buffer[_start] = item; _start = (_start + 1) % Capacity; } - } else { + } + else + { _buffer[(_start + _size - 1) % Capacity] = item; } } @@ -61,15 +70,18 @@ public class CircularBuffer : IEnumerable { /// /// The zero-based index of the element to get or set. /// The element at the specified index. - public double this[Index index] { + public double this[Index index] + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get { + get + { int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value; actualIndex = Math.Clamp(actualIndex, 0, _size - 1); return _buffer[(_start + actualIndex) % Capacity]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - set { + set + { int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value; actualIndex = Math.Clamp(actualIndex, 0, _size - 1); _buffer[(_start + actualIndex) % Capacity] = value; @@ -77,7 +89,8 @@ public class CircularBuffer : IEnumerable { } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowArgumentOutOfRangeException() { + private static void ThrowArgumentOutOfRangeException() + { throw new ArgumentOutOfRangeException("index", "Index is out of range."); } @@ -86,7 +99,8 @@ public class CircularBuffer : IEnumerable { /// /// The newest element in the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double Newest() { + public double Newest() + { if (_size == 0) return 0; return _buffer[(_start + _size - 1) % Capacity]; @@ -97,14 +111,16 @@ public class CircularBuffer : IEnumerable { /// /// The oldest element in the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double Oldest() { + public double Oldest() + { if (_size == 0) ThrowInvalidOperationException(); return _buffer[_start]; } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowInvalidOperationException() { + private static void ThrowInvalidOperationException() + { throw new InvalidOperationException("Buffer is empty."); } @@ -119,13 +135,15 @@ public class CircularBuffer : IEnumerable { /// /// Represents an enumerator for the CircularBuffer. /// - public struct Enumerator : IEnumerator { + public struct Enumerator : IEnumerator + { private readonly CircularBuffer _buffer; private int _index; private double _current; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(CircularBuffer buffer) { + internal Enumerator(CircularBuffer buffer) + { _buffer = buffer; _index = -1; _current = default; @@ -136,7 +154,8 @@ public class CircularBuffer : IEnumerable { /// /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() { + public bool MoveNext() + { if (_index + 1 >= _buffer._size) return false; @@ -154,7 +173,8 @@ public class CircularBuffer : IEnumerable { /// /// Sets the enumerator to its initial position, which is before the first element in the buffer. /// - public void Reset() { + public void Reset() + { _index = -1; _current = default; } @@ -171,13 +191,17 @@ public class CircularBuffer : IEnumerable { /// The one-dimensional array that is the destination of the elements copied from the buffer. /// The zero-based index in array at which copying begins. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(double[] destination, int destinationIndex) { + public void CopyTo(double[] destination, int destinationIndex) + { if (_size == 0) return; - if (_start + _size <= Capacity) { + if (_start + _size <= Capacity) + { Array.Copy(_buffer, _start, destination, destinationIndex, _size); - } else { + } + else + { int firstPartLength = Capacity - _start; Array.Copy(_buffer, _start, destination, destinationIndex, firstPartLength); Array.Copy(_buffer, 0, destination, destinationIndex + firstPartLength, _size - firstPartLength); @@ -189,15 +213,17 @@ public class CircularBuffer : IEnumerable { /// /// A read-only span over the buffer contents. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetSpan() { + public ReadOnlySpan GetSpan() + { if (_size == 0) return ReadOnlySpan.Empty; - if (_start + _size <= Capacity) { + if (_start + _size <= Capacity) + { return new ReadOnlySpan(_buffer, _start, _size); - } else { - return new ReadOnlySpan(ToArray()); } + + return new ReadOnlySpan(ToArray()); } /// @@ -216,7 +242,8 @@ public class CircularBuffer : IEnumerable { /// Removes all elements from the buffer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() { + public void Clear() + { Array.Clear(_buffer, 0, _buffer.Length); _start = 0; _size = 0; @@ -227,7 +254,8 @@ public class CircularBuffer : IEnumerable { /// /// The maximum value in the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double Max() { + public double Max() + { if (_size == 0) ThrowInvalidOperationException(); @@ -239,7 +267,8 @@ public class CircularBuffer : IEnumerable { /// /// The minimum value in the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double Min() { + public double Min() + { if (_size == 0) ThrowInvalidOperationException(); @@ -251,7 +280,8 @@ public class CircularBuffer : IEnumerable { /// /// The sum of all values in the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double Sum() { + public double Sum() + { return SumSimd(); } @@ -260,7 +290,8 @@ public class CircularBuffer : IEnumerable { /// /// The average of all values in the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double Average() { + public double Average() + { if (_size == 0) ThrowInvalidOperationException(); @@ -268,22 +299,26 @@ public class CircularBuffer : IEnumerable { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private double MaxSimd() { + private double MaxSimd() + { var span = GetSpan(); var vectorSize = Vector.Count; var maxVector = new Vector(double.MinValue); int i = 0; - for (; i <= span.Length - vectorSize; i += vectorSize) { + for (; i <= span.Length - vectorSize; i += vectorSize) + { maxVector = Vector.Max(maxVector, new Vector(span.Slice(i, vectorSize))); } double max = double.MinValue; - for (int j = 0; j < vectorSize; j++) { + for (int j = 0; j < vectorSize; j++) + { max = Math.Max(max, maxVector[j]); } - for (; i < span.Length; i++) { + for (; i < span.Length; i++) + { max = Math.Max(max, span[i]); } @@ -291,22 +326,26 @@ public class CircularBuffer : IEnumerable { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private double MinSimd() { + private double MinSimd() + { var span = GetSpan(); var vectorSize = Vector.Count; var minVector = new Vector(double.MaxValue); int i = 0; - for (; i <= span.Length - vectorSize; i += vectorSize) { + for (; i <= span.Length - vectorSize; i += vectorSize) + { minVector = Vector.Min(minVector, new Vector(span.Slice(i, vectorSize))); } double min = double.MaxValue; - for (int j = 0; j < vectorSize; j++) { + for (int j = 0; j < vectorSize; j++) + { min = Math.Min(min, minVector[j]); } - for (; i < span.Length; i++) { + for (; i < span.Length; i++) + { min = Math.Min(min, span[i]); } @@ -314,22 +353,26 @@ public class CircularBuffer : IEnumerable { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private double SumSimd() { + private double SumSimd() + { var span = GetSpan(); var vectorSize = Vector.Count; var sumVector = Vector.Zero; int i = 0; - for (; i <= span.Length - vectorSize; i += vectorSize) { + for (; i <= span.Length - vectorSize; i += vectorSize) + { sumVector += new Vector(span.Slice(i, vectorSize)); } double sum = 0; - for (int j = 0; j < vectorSize; j++) { + for (int j = 0; j < vectorSize; j++) + { sum += sumVector[j]; } - for (; i < span.Length; i++) { + for (; i < span.Length; i++) + { sum += span[i]; } @@ -340,7 +383,8 @@ public class CircularBuffer : IEnumerable { /// Copies the buffer elements to a new array. /// /// An array containing copies of the buffer elements. - public double[] ToArray() { + public double[] ToArray() + { double[] array = new double[_size]; CopyTo(array, 0); return array; @@ -350,10 +394,12 @@ public class CircularBuffer : IEnumerable { /// Performs a parallel operation on the buffer elements. /// /// The operation to perform on each partition of the buffer. - public void ParallelOperation(Func operation) { + public void ParallelOperation(Func operation) + { const int MinimumPartitionSize = 1024; - if (_size < MinimumPartitionSize) { + if (_size < MinimumPartitionSize) + { var span = GetSpan(); var array = span.ToArray(); operation(array, 0, array.Length); @@ -363,7 +409,8 @@ public class CircularBuffer : IEnumerable { int partitionCount = Environment.ProcessorCount; int partitionSize = _size / partitionCount; - if (partitionSize < MinimumPartitionSize) { + if (partitionSize < MinimumPartitionSize) + { partitionCount = Math.Max(1, _size / MinimumPartitionSize); partitionSize = _size / partitionCount; } @@ -371,7 +418,8 @@ public class CircularBuffer : IEnumerable { var buffer = ToArray(); var results = new double[partitionCount]; - Parallel.For(0, partitionCount, i => { + Parallel.For(0, partitionCount, i => + { int start = i * partitionSize; int length = (i == partitionCount - 1) ? _size - start : partitionSize; results[i] = operation(buffer, start, length); diff --git a/lib/core/formatters.cs b/lib/core/formatters.cs index 685ad8e2..9e567ce7 100644 --- a/lib/core/formatters.cs +++ b/lib/core/formatters.cs @@ -9,7 +9,7 @@ public static class Formatters const string pad = "18"; public static void Initialize() { - Formatter.Register((tick, writer) => + Formatter.Register((tick, writer) => { var sb = new StringBuilder(); sb.Append(""); diff --git a/lib/core/tbar.cs b/lib/core/tbar.cs index 73f96a5f..85cc8e09 100644 --- a/lib/core/tbar.cs +++ b/lib/core/tbar.cs @@ -1,6 +1,6 @@ namespace QuanTAlib; -public interface iTBar +public interface ITBar { DateTime Time { get; } double Open { get; } @@ -11,7 +11,7 @@ public interface iTBar bool IsNew { get; } } -public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : iTBar +public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : ITBar { public DateTime Time { get; init; } = Time; public double Open { get; init; } = Open; diff --git a/lib/core/tvalue.cs b/lib/core/tvalue.cs index aea352ba..26bdc1c0 100644 --- a/lib/core/tvalue.cs +++ b/lib/core/tvalue.cs @@ -1,6 +1,6 @@ namespace QuanTAlib; -public interface iTValue +public interface ITValue { DateTime Time { get; } double Value { get; } @@ -8,7 +8,7 @@ public interface iTValue bool IsHot { get; } } -public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) : iTValue +public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) : ITValue { public DateTime Time { get; init; } = Time; public double Value { get; init; } = Value; diff --git a/lib/feeds/GbmFeed.cs b/lib/feeds/GbmFeed.cs index dfcf2365..52531078 100644 --- a/lib/feeds/GbmFeed.cs +++ b/lib/feeds/GbmFeed.cs @@ -8,7 +8,7 @@ public class GbmFeed : TBarSeries private readonly Random _random; private double _lastClose, _lastHigh, _lastLow; - public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2) : base() + public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2) { _lastClose = _lastHigh = _lastLow = initialPrice; _mu = mu; diff --git a/lib/statistics/Entropy.cs b/lib/statistics/Entropy.cs index df59cb93..b0ed654c 100644 --- a/lib/statistics/Entropy.cs +++ b/lib/statistics/Entropy.cs @@ -16,7 +16,7 @@ public class Entropy : AbstractBase /// /// Thrown when the period is less than 2. /// - public Entropy(int period) : base() + public Entropy(int period) { if (period < 2) { diff --git a/lib/statistics/Kurtosis.cs b/lib/statistics/Kurtosis.cs index af13ac0b..8e4a5658 100644 --- a/lib/statistics/Kurtosis.cs +++ b/lib/statistics/Kurtosis.cs @@ -16,7 +16,7 @@ public class Kurtosis : AbstractBase /// /// Thrown when the period is less than 4. /// - public Kurtosis(int period) : base() + public Kurtosis(int period) { if (period < 4) { diff --git a/lib/statistics/Max.cs b/lib/statistics/Max.cs index b96fa31c..b0f72fa4 100644 --- a/lib/statistics/Max.cs +++ b/lib/statistics/Max.cs @@ -20,7 +20,7 @@ public class Max : AbstractBase /// /// Thrown when the period is less than 1 or decay is negative. /// - public Max(int period, double decay = 0) : base() + public Max(int period, double decay = 0) { if (period < 1) { @@ -105,7 +105,7 @@ public class Max : AbstractBase } double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period); - _currentMax = _currentMax - decayRate * (_currentMax - _buffer.Average()); + _currentMax -= decayRate * (_currentMax - _buffer.Average()); _currentMax = Math.Min(_currentMax, _buffer.Max()); IsHot = true; diff --git a/lib/statistics/Median.cs b/lib/statistics/Median.cs index 666dc6a8..6518faba 100644 --- a/lib/statistics/Median.cs +++ b/lib/statistics/Median.cs @@ -16,7 +16,7 @@ public class Median : AbstractBase /// /// Thrown when the period is less than 1. /// - public Median(int period) : base() + public Median(int period) { if (period < 1) { @@ -76,16 +76,7 @@ public class Median : AbstractBase Array.Sort(sortedValues); int middleIndex = sortedValues.Length / 2; - if (sortedValues.Length % 2 == 0) - { - // Even number of values: average of two middle values - median = (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0; - } - else - { - // Odd number of values: middle value - median = sortedValues[middleIndex]; - } + median = (sortedValues.Length % 2 == 0) ? (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0 : sortedValues[middleIndex]; } else { diff --git a/lib/statistics/Min.cs b/lib/statistics/Min.cs index a525d966..6eea3fad 100644 --- a/lib/statistics/Min.cs +++ b/lib/statistics/Min.cs @@ -10,7 +10,8 @@ namespace QuanTAlib; /// efficiently. It also implements a decay mechanism to adjust the minimum value over /// time, allowing for a more responsive indicator in changing market conditions. /// -public class Min : AbstractBase { +public class Min : AbstractBase +{ private readonly int Period; private readonly CircularBuffer _buffer; private readonly double _halfLife; @@ -25,11 +26,14 @@ public class Min : AbstractBase { /// /// Thrown when period is less than 1 or decay is negative. /// - public Min(int period, double decay = 0) : base() { - if (period < 1) { + public Min(int period, double decay = 0) + { + if (period < 1) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } - if (decay < 0) { + if (decay < 0) + { throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative."); } Period = period; @@ -46,7 +50,8 @@ public class Min : AbstractBase { /// The source object to subscribe to for value updates. /// The period over which to calculate the minimum value. /// The decay factor to apply to older values (default is 0). - public Min(object source, int period, double decay = 0) : this(period, decay) { + public Min(object source, int period, double decay = 0) : this(period, decay) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -54,7 +59,8 @@ public class Min : AbstractBase { /// /// Initializes the Min instance by setting initial values. /// - public override void Init() { + public override void Init() + { base.Init(); _currentMin = double.MaxValue; _timeSinceNewMin = 0; @@ -64,14 +70,18 @@ public class Min : AbstractBase { /// Manages the state of the Min instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _p_currentMin = _currentMin; _lastValidValue = Input.Value; _index++; _timeSinceNewMin++; _p_timeSinceNewMin = _timeSinceNewMin; - } else { + } + else + { _currentMin = _p_currentMin; _timeSinceNewMin = _p_timeSinceNewMin; } @@ -87,17 +97,19 @@ public class Min : AbstractBase { /// The decay rate is calculated using an exponential function based on the time since /// the last new minimum and the specified half-life. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); _buffer.Add(Input.Value, Input.IsNew); - if (Input.Value <= _currentMin) { + if (Input.Value <= _currentMin) + { _currentMin = Input.Value; _timeSinceNewMin = 0; } double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period); - _currentMin = _currentMin + decayRate * (_buffer.Average() - _currentMin); + _currentMin += decayRate * (_buffer.Average() - _currentMin); _currentMin = Math.Max(_currentMin, _buffer.Min()); IsHot = true; diff --git a/lib/statistics/Mode.cs b/lib/statistics/Mode.cs index bcd3ab57..33438f7b 100644 --- a/lib/statistics/Mode.cs +++ b/lib/statistics/Mode.cs @@ -9,7 +9,8 @@ namespace QuanTAlib; /// efficiently. Before the specified period is reached, it returns the average of /// the available values as an approximation. /// -public class Mode : AbstractBase { +public class Mode : AbstractBase +{ private readonly int Period; private readonly CircularBuffer _buffer; @@ -20,8 +21,10 @@ public class Mode : AbstractBase { /// /// Thrown when period is less than 1. /// - public Mode(int period) : base() { - if (period < 1) { + public Mode(int period) + { + if (period < 1) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } Period = period; @@ -36,7 +39,8 @@ public class Mode : AbstractBase { /// /// The source object to subscribe to for value updates. /// The period over which to calculate the mode. - public Mode(object source, int period) : this(period) { + public Mode(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -45,8 +49,10 @@ public class Mode : AbstractBase { /// Manages the state of the Mode instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Input.Value; _index++; } @@ -64,12 +70,14 @@ public class Mode : AbstractBase { /// the available values as an approximation of the mode. Once the period is /// reached, it calculates the true mode by grouping and counting the values. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); _buffer.Add(Input.Value, Input.IsNew); double mode; - if (_index >= Period) { + if (_index >= Period) + { var values = _buffer.GetSpan().ToArray(); var groupedValues = values.GroupBy(v => v) .OrderByDescending(g => g.Count()) @@ -82,7 +90,9 @@ public class Mode : AbstractBase { .ToList(); mode = modes.Average(); // If there are multiple modes, we return their average - } else { + } + else + { mode = _buffer.Average(); // Use average until we have enough data points } diff --git a/lib/statistics/Percentile.cs b/lib/statistics/Percentile.cs index a985a637..b15155c8 100644 --- a/lib/statistics/Percentile.cs +++ b/lib/statistics/Percentile.cs @@ -10,7 +10,8 @@ namespace QuanTAlib; /// between two data points. Before the specified period is reached, it returns the /// average of the available values as an approximation. /// -public class Percentile : AbstractBase { +public class Percentile : AbstractBase +{ private readonly int Period; private readonly double Percent; private readonly CircularBuffer _buffer; @@ -23,11 +24,14 @@ public class Percentile : AbstractBase { /// /// Thrown when period is less than 2 or percent is not between 0 and 100. /// - public Percentile(int period, double percent) : base() { - if (period < 2) { + public Percentile(int period, double percent) + { + if (period < 2) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for percentile calculation."); } - if (percent < 0 || percent > 100) { + if (percent < 0 || percent > 100) + { throw new ArgumentOutOfRangeException(nameof(percent), "Percent must be between 0 and 100."); } Period = period; @@ -44,7 +48,8 @@ public class Percentile : AbstractBase { /// The source object to subscribe to for value updates. /// The period over which to calculate the percentile. /// The percentile to calculate (between 0 and 100). - public Percentile(object source, int period, double percent) : this(period, percent) { + public Percentile(object source, int period, double percent) : this(period, percent) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -52,7 +57,8 @@ public class Percentile : AbstractBase { /// /// Initializes the Percentile instance by clearing the buffer. /// - public override void Init() { + public override void Init() + { base.Init(); _buffer.Clear(); } @@ -61,8 +67,10 @@ public class Percentile : AbstractBase { /// Manages the state of the Percentile instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Input.Value; _index++; } @@ -80,12 +88,14 @@ public class Percentile : AbstractBase { /// as an approximation. Once the period is reached, it calculates the true percentile by /// sorting the values and interpolating as necessary. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); _buffer.Add(Input.Value, Input.IsNew); double result; - if (_buffer.Count >= Period) { + if (_buffer.Count >= Period) + { var values = _buffer.GetSpan().ToArray(); Array.Sort(values); @@ -93,16 +103,21 @@ public class Percentile : AbstractBase { int lowerIndex = (int)Math.Floor(position); int upperIndex = (int)Math.Ceiling(position); - if (lowerIndex == upperIndex) { + if (lowerIndex == upperIndex) + { result = values[lowerIndex]; - } else { + } + else + { // Interpolate between the two nearest values double lowerValue = values[lowerIndex]; double upperValue = values[upperIndex]; double fraction = position - lowerIndex; result = lowerValue + (upperValue - lowerValue) * fraction; } - } else { + } + else + { // Use average for insufficient data, like the Median class result = _buffer.Average(); } diff --git a/lib/statistics/Skew.cs b/lib/statistics/Skew.cs index 0d32f72a..38e4e1be 100644 --- a/lib/statistics/Skew.cs +++ b/lib/statistics/Skew.cs @@ -10,7 +10,8 @@ namespace QuanTAlib; /// for sample skewness calculation. A minimum of 3 data points is required for the /// calculation. /// -public class Skew : AbstractBase { +public class Skew : AbstractBase +{ private readonly int Period; private readonly CircularBuffer _buffer; @@ -21,8 +22,10 @@ public class Skew : AbstractBase { /// /// Thrown when period is less than 3. /// - public Skew(int period) : base() { - if (period < 3) { + public Skew(int period) + { + if (period < 3) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 3 for skewness calculation."); } Period = period; @@ -37,7 +40,8 @@ public class Skew : AbstractBase { /// /// The source object to subscribe to for value updates. /// The period over which to calculate the skewness. - public Skew(object source, int period) : this(period) { + public Skew(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -45,7 +49,8 @@ public class Skew : AbstractBase { /// /// Initializes the Skew instance by clearing the buffer. /// - public override void Init() { + public override void Init() + { base.Init(); _buffer.Clear(); } @@ -54,8 +59,10 @@ public class Skew : AbstractBase { /// Manages the state of the Skew instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Input.Value; _index++; } @@ -73,13 +80,15 @@ public class Skew : AbstractBase { /// calculation. If there are fewer than 3 data points, or if the standard /// deviation is zero, the method returns 0. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); _buffer.Add(Input.Value, Input.IsNew); double skew = 0; - if (_buffer.Count >= 3) { // We need at least 3 data points for skewness + if (_buffer.Count >= 3) + { // We need at least 3 data points for skewness var values = _buffer.GetSpan().ToArray(); double mean = values.Average(); double n = values.Length; @@ -87,7 +96,8 @@ public class Skew : AbstractBase { double sumCubedDeviations = 0; double sumSquaredDeviations = 0; - foreach (var value in values) { + foreach (var value in values) + { double deviation = value - mean; sumCubedDeviations += Math.Pow(deviation, 3); sumSquaredDeviations += Math.Pow(deviation, 2); @@ -98,7 +108,8 @@ public class Skew : AbstractBase { double m2 = sumSquaredDeviations / n; double s3 = Math.Pow(m2, 1.5); - if (s3 != 0) { // Avoid division by zero + if (s3 != 0) + { // Avoid division by zero skew = (Math.Sqrt(n * (n - 1)) / (n - 2)) * (m3 / s3); } } diff --git a/lib/statistics/Stddev.cs b/lib/statistics/Stddev.cs index 8d5c30e9..2282964a 100644 --- a/lib/statistics/Stddev.cs +++ b/lib/statistics/Stddev.cs @@ -9,7 +9,8 @@ namespace QuanTAlib; /// standard deviation based on the isPopulation parameter. It uses a circular buffer /// to efficiently manage the data points within the specified period. /// -public class Stddev : AbstractBase { +public class Stddev : AbstractBase +{ private readonly int Period; private readonly bool IsPopulation; private readonly CircularBuffer _buffer; @@ -25,8 +26,10 @@ public class Stddev : AbstractBase { /// /// Thrown when period is less than 2. /// - public Stddev(int period, bool isPopulation = false) : base() { - if (period < 2) { + public Stddev(int period, bool isPopulation = false) + { + if (period < 2) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); } Period = period; @@ -46,7 +49,8 @@ public class Stddev : AbstractBase { /// /// A flag indicating whether to calculate population (true) or sample (false) standard deviation. /// - public Stddev(object source, int period, bool isPopulation = false) : this(period, isPopulation) { + public Stddev(object source, int period, bool isPopulation = false) : this(period, isPopulation) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -54,7 +58,8 @@ public class Stddev : AbstractBase { /// /// Initializes the Stddev instance by clearing the buffer. /// - public override void Init() { + public override void Init() + { base.Init(); _buffer.Clear(); } @@ -63,8 +68,10 @@ public class Stddev : AbstractBase { /// Manages the state of the Stddev instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Input.Value; _index++; } @@ -83,13 +90,15 @@ public class Stddev : AbstractBase { /// where x is each value, mean is the average of all values, and n is the number of values. /// If there's only one value in the buffer, the method returns 0. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); _buffer.Add(Input.Value, Input.IsNew); double stddev = 0; - if (_buffer.Count > 1) { + if (_buffer.Count > 1) + { var values = _buffer.GetSpan().ToArray(); double mean = values.Average(); double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2)); diff --git a/lib/statistics/Variance.cs b/lib/statistics/Variance.cs index dc910203..60fb6816 100644 --- a/lib/statistics/Variance.cs +++ b/lib/statistics/Variance.cs @@ -9,7 +9,8 @@ namespace QuanTAlib; /// variance based on the isPopulation parameter. It uses a circular buffer /// to efficiently manage the data points within the specified period. /// -public class Variance : AbstractBase { +public class Variance : AbstractBase +{ private readonly int Period; private readonly bool IsPopulation; private readonly CircularBuffer _buffer; @@ -25,8 +26,10 @@ public class Variance : AbstractBase { /// /// Thrown when period is less than 2. /// - public Variance(int period, bool isPopulation = false) : base() { - if (period < 2) { + public Variance(int period, bool isPopulation = false) + { + if (period < 2) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); } Period = period; @@ -46,7 +49,8 @@ public class Variance : AbstractBase { /// /// A flag indicating whether to calculate population (true) or sample (false) variance. /// - public Variance(object source, int period, bool isPopulation = false) : this(period, isPopulation) { + public Variance(object source, int period, bool isPopulation = false) : this(period, isPopulation) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -54,7 +58,8 @@ public class Variance : AbstractBase { /// /// Initializes the Variance instance by clearing the buffer. /// - public override void Init() { + public override void Init() + { base.Init(); _buffer.Clear(); } @@ -63,8 +68,10 @@ public class Variance : AbstractBase { /// Manages the state of the Variance instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Input.Value; _index++; } @@ -83,13 +90,15 @@ public class Variance : AbstractBase { /// where x is each value, mean is the average of all values, and n is the number of values. /// If there's only one value in the buffer, the method returns 0. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); _buffer.Add(Input.Value, Input.IsNew); double variance = 0; - if (_buffer.Count > 1) { + if (_buffer.Count > 1) + { var values = _buffer.GetSpan().ToArray(); double mean = values.Average(); double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2)); diff --git a/lib/statistics/Zscore.cs b/lib/statistics/Zscore.cs index dafd0f20..946a8e34 100644 --- a/lib/statistics/Zscore.cs +++ b/lib/statistics/Zscore.cs @@ -9,7 +9,8 @@ namespace QuanTAlib; /// the most recent value in a given period. It uses a circular buffer to /// efficiently manage the data points within the specified period. /// -public class Zscore : AbstractBase { +public class Zscore : AbstractBase +{ private readonly int Period; private readonly CircularBuffer _buffer; @@ -20,8 +21,10 @@ public class Zscore : AbstractBase { /// /// Thrown when period is less than 2. /// - public Zscore(int period) : base() { - if (period < 2) { + public Zscore(int period) + { + if (period < 2) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for Z-score calculation."); } Period = period; @@ -36,7 +39,8 @@ public class Zscore : AbstractBase { /// /// The source object to subscribe to for value updates. /// The period over which to calculate the Z-score. - public Zscore(object source, int period) : this(period) { + public Zscore(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -44,7 +48,8 @@ public class Zscore : AbstractBase { /// /// Initializes the Zscore instance by clearing the buffer. /// - public override void Init() { + public override void Init() + { base.Init(); _buffer.Clear(); } @@ -53,8 +58,10 @@ public class Zscore : AbstractBase { /// Manages the state of the Zscore instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Input.Value; _index++; } @@ -72,13 +79,15 @@ public class Zscore : AbstractBase { /// where x is the input value, μ is the mean of the period, and σ is the sample standard deviation. /// If there are fewer than 2 data points or if the standard deviation is 0, the method returns 0. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); _buffer.Add(Input.Value, Input.IsNew); double zScore = 0; - if (_buffer.Count >= 2) { // We need at least 2 data points for Z-score + if (_buffer.Count >= 2) + { // We need at least 2 data points for Z-score var values = _buffer.GetSpan().ToArray(); double mean = values.Average(); double n = values.Length; @@ -86,7 +95,8 @@ public class Zscore : AbstractBase { double sumSquaredDeviations = values.Sum(x => Math.Pow(x - mean, 2)); double standardDeviation = Math.Sqrt(sumSquaredDeviations / (n - 1)); // Sample standard deviation - if (standardDeviation != 0) { // Avoid division by zero + if (standardDeviation != 0) + { // Avoid division by zero zScore = (Input.Value - mean) / standardDeviation; } } diff --git a/lib/volatility/Historical.cs b/lib/volatility/Historical.cs index 21b80dd8..9417b14d 100644 --- a/lib/volatility/Historical.cs +++ b/lib/volatility/Historical.cs @@ -9,7 +9,8 @@ namespace QuanTAlib; /// both annualized and non-annualized volatility measures. The calculation uses a sample /// standard deviation formula and assumes 252 trading days in a year for annualization. /// -public class Historical : AbstractBase { +public class Historical : AbstractBase +{ private readonly int Period; private readonly bool IsAnnualized; private readonly CircularBuffer _buffer; @@ -24,8 +25,10 @@ public class Historical : AbstractBase { /// /// Thrown when period is less than 2. /// - public Historical(int period, bool isAnnualized = true) : base() { - if (period < 2) { + public Historical(int period, bool isAnnualized = true) + { + if (period < 2) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); } Period = period; @@ -43,7 +46,8 @@ public class Historical : AbstractBase { /// The source object to subscribe to for value updates. /// The period over which to calculate historical volatility. /// Whether to annualize the volatility (default is true). - public Historical(object source, int period, bool isAnnualized = true) : this(period, isAnnualized) { + public Historical(object source, int period, bool isAnnualized = true) : this(period, isAnnualized) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -51,7 +55,8 @@ public class Historical : AbstractBase { /// /// Initializes the Historical instance by clearing buffers and resetting the previous close value. /// - public override void Init() { + public override void Init() + { base.Init(); _buffer.Clear(); _logReturns.Clear(); @@ -62,8 +67,10 @@ public class Historical : AbstractBase { /// Manages the state of the Historical instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Input.Value; _index++; } @@ -82,19 +89,23 @@ public class Historical : AbstractBase { /// 3. If annualized, multiply by the square root of 252 (assumed trading days in a year). /// The method returns 0 until enough data points are available for the calculation. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); _buffer.Add(Input.Value, Input.IsNew); double volatility = 0; - if (_buffer.Count > 1) { - if (_previousClose != 0) { + if (_buffer.Count > 1) + { + if (_previousClose != 0) + { double logReturn = Math.Log(Input.Value / _previousClose); _logReturns.Add(logReturn, Input.IsNew); } - if (_logReturns.Count == Period) { + if (_logReturns.Count == Period) + { var returns = _logReturns.GetSpan().ToArray(); double mean = returns.Average(); double sumOfSquaredDifferences = returns.Sum(x => Math.Pow(x - mean, 2)); @@ -102,7 +113,8 @@ public class Historical : AbstractBase { double variance = sumOfSquaredDifferences / (Period - 1); // Using sample standard deviation volatility = Math.Sqrt(variance); - if (IsAnnualized) { + if (IsAnnualized) + { // Assuming 252 trading days in a year. Adjust as needed. volatility *= Math.Sqrt(252); } diff --git a/lib/volatility/Realized.cs b/lib/volatility/Realized.cs index ddcd8c76..5798919b 100644 --- a/lib/volatility/Realized.cs +++ b/lib/volatility/Realized.cs @@ -9,7 +9,8 @@ namespace QuanTAlib; /// both annualized and non-annualized volatility measures. The calculation uses a rolling /// sum of squared returns for efficiency and assumes 252 trading days in a year for annualization. /// -public class Realized : AbstractBase { +public class Realized : AbstractBase +{ private readonly int Period; private readonly bool IsAnnualized; private readonly CircularBuffer _returns; @@ -24,8 +25,10 @@ public class Realized : AbstractBase { /// /// Thrown when period is less than 2. /// - public Realized(int period, bool isAnnualized = true) : base() { - if (period < 2) { + public Realized(int period, bool isAnnualized = true) + { + if (period < 2) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); } Period = period; @@ -39,7 +42,8 @@ public class Realized : AbstractBase { /// /// Initializes the Realized instance by clearing buffers and resetting calculation variables. /// - public override void Init() { + public override void Init() + { base.Init(); _returns.Clear(); _previousClose = 0; @@ -50,8 +54,10 @@ public class Realized : AbstractBase { /// Manages the state of the Realized instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Input.Value; _index++; } @@ -72,14 +78,17 @@ public class Realized : AbstractBase { /// 5. If annualized, multiply by the square root of 252 (assumed trading days in a year). /// The method returns 0 until enough data points are available for the calculation. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); double volatility = 0; - if (_previousClose != 0) { + if (_previousClose != 0) + { double logReturn = Math.Log(Input.Value / _previousClose); - if (_returns.Count == Period) { + if (_returns.Count == Period) + { // Remove the oldest squared return from the sum _sumSquaredReturns -= Math.Pow(_returns[0], 2); } @@ -87,11 +96,13 @@ public class Realized : AbstractBase { _returns.Add(logReturn, Input.IsNew); _sumSquaredReturns += Math.Pow(logReturn, 2); - if (_returns.Count == Period) { + if (_returns.Count == Period) + { double variance = _sumSquaredReturns / Period; volatility = Math.Sqrt(variance); - if (IsAnnualized) { + if (IsAnnualized) + { // Assuming 252 trading days in a year. Adjust as needed. volatility *= Math.Sqrt(252); } diff --git a/lib/volatility/Rvi.cs b/lib/volatility/Rvi.cs index ee3349ea..e59d915b 100644 --- a/lib/volatility/Rvi.cs +++ b/lib/volatility/Rvi.cs @@ -13,10 +13,11 @@ namespace QuanTAlib; /// This implementation uses a combination of Standard Deviation and Simple Moving Average /// calculations to compute the RVI. /// -public class Rvi : AbstractBase { +public class Rvi : AbstractBase +{ private readonly int Period; - private Stddev _upStdDev, _downStdDev; - private Sma _upSma, _downSma; + private readonly Stddev _upStdDev, _downStdDev; + private readonly Sma _upSma, _downSma; private double _previousClose; /// @@ -26,8 +27,10 @@ public class Rvi : AbstractBase { /// /// Thrown when period is less than 2. /// - public Rvi(int period) : base() { - if (period < 2) { + public Rvi(int period) + { + if (period < 2) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); } Period = period; @@ -45,7 +48,8 @@ public class Rvi : AbstractBase { /// /// The source object to subscribe to for value updates. /// The period over which to calculate the RVI. - public Rvi(object source, int period) : this(period) { + public Rvi(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } @@ -53,7 +57,8 @@ public class Rvi : AbstractBase { /// /// Initializes the Rvi instance by setting up the initial state. /// - public override void Init() { + public override void Init() + { base.Init(); _previousClose = 0; } @@ -62,8 +67,10 @@ public class Rvi : AbstractBase { /// Manages the state of the Rvi instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _lastValidValue = Value; _index++; } @@ -84,7 +91,8 @@ public class Rvi : AbstractBase { /// 5. Compute the RVI as a percentage of up volatility to total volatility. /// The method returns 0 if the sum of up and down volatility is zero. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(Input.IsNew); double close = Input.Value; @@ -97,11 +105,7 @@ public class Rvi : AbstractBase { _downSma.Calc(_downStdDev.Calc(new TValue(Input.Time, downMove, Input.IsNew))); double rvi; - if (_upSma.Value + _downSma.Value != 0) { - rvi = 100 * _upSma.Value / (_upSma.Value + _downSma.Value); - } else { - rvi = 0; - } + rvi = (_upSma.Value + _downSma.Value != 0) ? 100 * _upSma.Value / (_upSma.Value + _downSma.Value) : 0; _previousClose = close; IsHot = _index >= WarmupPeriod; From af234594cc0070687e0071ea3e8e3bab586b4f37 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Mon, 7 Oct 2024 21:41:26 -0700 Subject: [PATCH 2/4] event tests --- Directory.Build.props | 5 +- Tests/Tests.csproj | 1 - Tests/test_eventing.cs | 72 ++++++++++++++ Tests/test_iTBar.cs | 100 +++++++++++++++++-- Tests/test_iTValue.cs | 137 ++++++++++++++++++--------- lib/averages/Frama.cs | 12 ++- lib/averages/Jma.cs | 6 ++ lib/averages/Rema.cs | 6 +- lib/core/AbstractBarBase.cs | 90 ------------------ lib/core/abstractBase.cs | 83 +++++++++++++--- lib/core/tvalue.cs | 4 +- lib/volatility/Atr.cs | 14 +-- notebooks/means.dib | 22 +++-- quantower/Volatility/AtrIndicator.cs | 2 +- quantower/_IndicatorBarBase.cs | 37 ++++---- 15 files changed, 391 insertions(+), 200 deletions(-) create mode 100644 Tests/test_eventing.cs delete mode 100644 lib/core/AbstractBarBase.cs diff --git a/Directory.Build.props b/Directory.Build.props index 83427b1d..6ee76064 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,11 +1,12 @@ - en-US net8.0 + preview + enable enable true - preview + en-US false false true diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 14c4f00a..dee01cd9 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,6 +1,5 @@ - net9.0 QuanTAlib.Tests QuanTAlib.Tests diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs new file mode 100644 index 00000000..832d990c --- /dev/null +++ b/Tests/test_eventing.cs @@ -0,0 +1,72 @@ +namespace QuanTAlib; + +public class EventingTests +{ + [Fact] + public void VerifyEventBasedCalculations() + { + // Create a random number generator with a fixed seed for reproducibility + var random = new Random(42); + + // Create an input series to hold our random values + var input = new TSeries(); + int p = 10; + + // Create a list of indicator pairs (direct calculation and event-based) + var indicators = new List<(AbstractBase Direct, AbstractBase EventBased)> + { + (new Afirma(p,p,Afirma.WindowType.BlackmanHarris), new Afirma(input, p,p,Afirma.WindowType.BlackmanHarris)), + (new Alma(p), new Alma(input, p)), + (new Convolution([1,2,3,2,1]), new Convolution(input, [1,2,3,2,1])), + (new Dema(p), new Dema(input, p)), + (new Dsma(p), new Dsma(input, p)), + (new Dwma(p), new Dwma(input, p)), + (new Ema(p), new Ema(input, p)), + (new Epma(p), new Epma(input, p)), + (new Frama(p), new Frama(input, p)), + (new Fwma(p), new Fwma(input, p)), + (new Gma(p), new Gma(input, p)), + (new Hma(p), new Hma(input, p)), + (new Htit(), new Htit(input)), + (new Hwma(p), new Hwma(input, p)), + (new Jma(p), new Jma(input, p)), + (new Kama(p), new Kama(input, p)), + (new Ltma(gamma: 0.2), new Ltma(input, gamma: 0.2)), + (new Maaf(p), new Maaf(input, p)), + (new Mama(p), new Mama(input, p)), + (new Mgdi(p), new Mgdi(input, p)), + (new Mma(p), new Mma(input, p)), + (new Qema(k1: 0.2, k2: 0.2, k3: 0.2, k4: 0.2), new Qema(input, k1: 0.2, k2: 0.2, k3: 0.2, k4: 0.2)), + (new Rema(p), new Rema(input, p)), + (new Rma(p), new Rma(input, p)), + + (new Sma(p), new Sma(input, p)), + (new Wma(p), new Wma(input, p)), + (new Rma(p), new Rma(input, p)), + + (new Tema(p), new Tema(input, p)), + (new Kama(2, 30, 6), new Kama(input, 2, 30, 6)), + + (new Zlema(p), new Zlema(input, p)) + }; + + // Generate 200 random values and feed them to both direct and event-based indicators + for (int i = 0; i < 200; i++) + { + double randomValue = random.NextDouble() * 100; + input.Add(randomValue); + + // Calculate direct indicators + foreach (var (direct, _) in indicators) + { + direct.Calc(randomValue); + } + } + + // Compare the results of direct and event-based calculations + foreach (var (direct, eventBased) in indicators) + { + Assert.Equal(direct.Value, eventBased.Value, 9); + } + } +} diff --git a/Tests/test_iTBar.cs b/Tests/test_iTBar.cs index ae0ba3f5..79c8563c 100644 --- a/Tests/test_iTBar.cs +++ b/Tests/test_iTBar.cs @@ -4,14 +4,19 @@ using System.Diagnostics.CodeAnalysis; namespace QuanTAlib; +/// +/// Contains unit tests for bar-based indicators in QuanTAlib. +/// [SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")] - public class BarIndicatorTests { private readonly Random rnd; private const int SeriesLen = 1000; private const int Corrections = 100; + /// + /// Initializes a new instance of the BarIndicatorTests class. + /// public BarIndicatorTests() { rnd = new Random((int)DateTime.Now.Ticks); @@ -19,9 +24,14 @@ public class BarIndicatorTests private static readonly ITValue[] indicators = new ITValue[] { - new Atr(period: 14), + new Atr(period: 14), + // Add other TBar-based indicators here }; + /// + /// Tests if the indicator produces consistent results when processing new and updated bars. + /// + /// The indicator to test. [Theory] [MemberData(nameof(GetIndicators))] public void IndicatorIsNew(ITValue indicator) @@ -29,7 +39,7 @@ public class BarIndicatorTests var indicator1 = indicator; var indicator2 = indicator; - MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; + MethodInfo calcMethod = FindCalcMethod(indicator.GetType()); if (calcMethod == null) { throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}"); @@ -37,22 +47,96 @@ public class BarIndicatorTests for (int i = 0; i < SeriesLen; i++) { - TBar item1 = new(Time: DateTime.Now, Open: rnd.Next(-100, 100), High: rnd.Next(-100, 100), Low: rnd.Next(-100, 100), Close: rnd.Next(-100, 100), Volume: rnd.Next(-1000, 1000), IsNew: true); - calcMethod.Invoke(indicator1, new object[] { item1 }); + TBar item1 = GenerateRandomBar(isNew: true); + InvokeCalc(indicator1, calcMethod, item1); for (int j = 0; j < Corrections; j++) { - item1 = new(Time: DateTime.Now, Open: rnd.Next(-100, 100), High: rnd.Next(-100, 100), Low: rnd.Next(-100, 100), Close: rnd.Next(-100, 100), Volume: rnd.Next(-1000, 1000), IsNew: false); - calcMethod.Invoke(indicator1, new object[] { item1 }); + item1 = GenerateRandomBar(isNew: false); + InvokeCalc(indicator1, calcMethod, item1); } var item2 = new TBar(item1.Time, item1.Open, item1.High, item1.Low, item1.Close, item1.Volume, IsNew: true); - calcMethod.Invoke(indicator2, new object[] { item2 }); + InvokeCalc(indicator2, calcMethod, item2); Assert.Equal(indicator1.Value, indicator2.Value); } } + /// + /// Finds the appropriate Calc method for the given indicator type. + /// + /// The type of the indicator. + /// The MethodInfo for the Calc method. + private static MethodInfo FindCalcMethod(Type type) + { + while (type != null && type != typeof(object)) + { + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(m => m.Name == "Calc") + .ToList(); + + if (methods.Count > 0) + { + // Prefer the method with TBar parameter + var method = methods.FirstOrDefault(m => + { + var parameters = m.GetParameters(); + return parameters.Length == 1 && parameters[0].ParameterType == typeof(TBar); + }); + + // If not found, return the first method + return method ?? methods.First(); + } + + type = type.BaseType!; + } + return null!; + } + + /// + /// Invokes the Calc method on the given indicator with the provided input. + /// + /// The indicator instance. + /// The Calc method to invoke. + /// The input TBar. + private static void InvokeCalc(ITValue indicator, MethodInfo calcMethod, TBar input) + { + var parameters = calcMethod.GetParameters(); + if (parameters.Length == 1) + { + calcMethod.Invoke(indicator, new object[] { input }); + } + else if (parameters.Length == 2) + { + calcMethod.Invoke(indicator, new object[] { input, double.NaN }); + } + else + { + throw new InvalidOperationException($"Invalid number of parameters for Calc method in indicator type: {indicator.GetType().Name}"); + } + } + + /// + /// Generates a random TBar for testing purposes. + /// + /// Indicates whether the generated bar should be marked as new. + /// A randomly generated TBar. + private TBar GenerateRandomBar(bool isNew) + { + double open = rnd.NextDouble() * 200 - 100; + double close = rnd.NextDouble() * 200 - 100; + double high = Math.Max(open, close) + rnd.NextDouble() * 10; + double low = Math.Min(open, close) - rnd.NextDouble() * 10; + long volume = rnd.Next(0, 10000); + + return new TBar(Time: DateTime.Now, Open: open, High: high, Low: low, Close: close, Volume: volume, IsNew: isNew); + } + + /// + /// Provides the list of indicators for parameterized tests. + /// + /// An enumerable of object arrays, each containing an indicator instance. public static IEnumerable GetIndicators() { return indicators.Select(indicator => new object[] { indicator }); diff --git a/Tests/test_iTValue.cs b/Tests/test_iTValue.cs index e7a2c41c..f36bec3e 100644 --- a/Tests/test_iTValue.cs +++ b/Tests/test_iTValue.cs @@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis; namespace QuanTAlib; [SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")] - public class IndicatorTests { private readonly Random rnd; @@ -17,49 +16,56 @@ public class IndicatorTests rnd = new Random((int)DateTime.Now.Ticks); } + // skipcq: CS-R1055 private static readonly ITValue[] indicators = { new Ema(period: 10, useSma: true), - new Alma(period: 14, offset: 0.85, sigma: 6), - new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman), - new Convolution(new double[] { 1.0, 2, 3, 2, 1 }), - new Dema(period: 14), - new Dsma(period: 14), - new Dwma(period: 14), - new Epma(period: 14), - new Frama(period: 14), - new Fwma(period: 14), - new Gma(period: 14), - new Hma(period: 14), - new Hwma(period: 14), - new Kama(period: 14), - new Mama(fastLimit: 0.5, slowLimit: 0.05), - new Mgdi(period: 14), - new Mma(period: 14), - new Qema(), - new Rema(period: 14), - new Rma(period: 14), - new Sinema(period: 14), - new Sma(period: 14), - new Smma(period: 14), - new T3(period: 14), - new Tema(period: 14), - new Trima(period: 14), - new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2), - new Wma(period: 14), - new Zlema(period: 14), - new Entropy(period: 14), - new Kurtosis(period: 14), - new Max(period: 14, decay: 0.01), - new Min(period: 14, decay: 0.01), - new Median(period: 14), - new Mode(period: 14), - new Percentile(period: 14, percent: 50), - new Skew(period: 14), - new Stddev(period: 14), - new Variance(period: 14), - new Zscore(period: 14) + new Alma(period: 14, offset: 0.85, sigma: 6), + new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman), + new Convolution(new[] { 1.0, 2, 3, 2, 1 }), + new Dema(period: 14), + new Dsma(period: 14), + new Dwma(period: 14), + new Epma(period: 14), + new Frama(period: 14), + new Fwma(period: 14), + new Gma(period: 14), + new Hma(period: 14), + new Hwma(period: 14), + new Kama(period: 14), + new Mama(fastLimit: 0.5, slowLimit: 0.05), + new Mgdi(period: 14), + new Mma(period: 14), + new Qema(), + new Rema(period: 14), + new Rma(period: 14), + new Sinema(period: 14), + new Sma(period: 14), + new Smma(period: 14), + new T3(period: 14), + new Tema(period: 14), + new Trima(period: 14), + new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2), + new Wma(period: 14), + new Zlema(period: 14), + new Curvature(period: 14), + new Entropy(period: 14), + new Kurtosis(period: 14), + new Max(period: 14, decay: 0.01), + new Median(period: 14), + new Min(period: 14, decay: 0.01), + new Median(period: 14), + new Mode(period: 14), + new Percentile(period: 14, percent: 50), + new Skew(period: 14), + new Slope(period: 14), + new Stddev(period: 14), + new Variance(period: 14), + new Zscore(period: 14), + + new Historical(period: 14), + new Realized(period: 14) }; [Theory] @@ -69,7 +75,7 @@ public class IndicatorTests var indicator1 = indicator; var indicator2 = indicator; - MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; + MethodInfo calcMethod = FindCalcMethod(indicator.GetType()); if (calcMethod == null) { throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}"); @@ -78,21 +84,64 @@ public class IndicatorTests for (int i = 0; i < SeriesLen; i++) { TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - calcMethod.Invoke(indicator1, new object[] { item1 }); + InvokeCalc(indicator1, calcMethod, item1); for (int j = 0; j < Corrections; j++) { item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - calcMethod.Invoke(indicator1, new object[] { item1 }); + InvokeCalc(indicator1, calcMethod, item1); } var item2 = new TValue(item1.Time, item1.Value, IsNew: true); - calcMethod.Invoke(indicator2, new object[] { item2 }); + InvokeCalc(indicator2, calcMethod, item2); Assert.Equal(indicator1.Value, indicator2.Value); } } + private static MethodInfo FindCalcMethod(Type type) + { + while (type != null && type != typeof(object)) + { + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(m => m.Name == "Calc") + .ToList(); + + if (methods.Count > 0) + { + // Prefer the method with TValue parameter + var method = methods.FirstOrDefault(m => + { + var parameters = m.GetParameters(); + return parameters.Length == 1 && parameters[0].ParameterType == typeof(TValue); + }); + + // If not found, return the first method + return method ?? methods.First(); + } + + type = type.BaseType!; + } + return null!; + } + + private static void InvokeCalc(ITValue indicator, MethodInfo calcMethod, TValue input) + { + var parameters = calcMethod.GetParameters(); + if (parameters.Length == 1) + { + calcMethod.Invoke(indicator, new object[] { input }); + } + else if (parameters.Length == 2) + { + calcMethod.Invoke(indicator, new object[] { input, double.NaN }); + } + else + { + throw new InvalidOperationException($"Invalid number of parameters for Calc method in indicator type: {indicator.GetType().Name}"); + } + } + public static IEnumerable GetIndicators() { return indicators.Select(indicator => new object[] { indicator }); diff --git a/lib/averages/Frama.cs b/lib/averages/Frama.cs index 76905d02..223b1458 100644 --- a/lib/averages/Frama.cs +++ b/lib/averages/Frama.cs @@ -5,22 +5,26 @@ namespace QuanTAlib; public class Frama : AbstractBase { private readonly int _period; - private readonly double _fc; private readonly CircularBuffer _buffer; private double _lastFrama; private double _prevLastFrama; - public Frama(int period, double fc = 0.5) + public Frama(int period) { if (period < 2) throw new ArgumentException("Period must be at least 2", nameof(period)); _period = period; - _fc = fc; _buffer = new CircularBuffer(period); WarmupPeriod = period; } + public Frama(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + public override void Init() { base.Init(); @@ -95,4 +99,4 @@ public class Frama : AbstractBase { return _lastFrama; } -} +} \ No newline at end of file diff --git a/lib/averages/Jma.cs b/lib/averages/Jma.cs index 702fc6db..00b77de8 100644 --- a/lib/averages/Jma.cs +++ b/lib/averages/Jma.cs @@ -35,6 +35,12 @@ public class Jma : AbstractBase Init(); } + public Jma(object source, int period, double phase = 0, int vshort = 10) : this(period, phase, vshort) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + public override void Init() { _upperBand = _lowerBand = _prevMa1 = _prevDet0 = _prevDet1 = _prevJma = 0.0; diff --git a/lib/averages/Rema.cs b/lib/averages/Rema.cs index be9168d1..afa22046 100644 --- a/lib/averages/Rema.cs +++ b/lib/averages/Rema.cs @@ -25,7 +25,11 @@ public class Rema : AbstractBase WarmupPeriod = period; Init(); } - + public Rema(object source, int period, double lambda = 0.5) : this(period, lambda) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } public override void Init() { base.Init(); diff --git a/lib/core/AbstractBarBase.cs b/lib/core/AbstractBarBase.cs deleted file mode 100644 index 989c17ef..00000000 --- a/lib/core/AbstractBarBase.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace QuanTAlib; - -/// -/// Provides a base implementation for financial indicators that work with bar data in the QuanTAlib library. -/// -/// -/// This abstract class implements the iTValue interface and defines common properties -/// and methods used by inheriting indicator types. It handles the basic flow of -/// receiving bar data, performing calculations, and publishing results. -/// -public abstract class AbstractBarBase : ITValue { - public DateTime Time { get; set; } - public double Value { get; set; } - public bool IsNew { get; set; } - public bool IsHot { get; set; } - public TBar Input { get; set; } - public String Name { get; set; } = ""; - public int WarmupPeriod { get; set; } - public TValue Tick => new(Time, Value, IsNew, IsHot); - public event ValueSignal Pub = delegate { }; - protected int _index; - protected double _lastValidValue; - protected AbstractBarBase() { - // Add parameters into constructor if needed - } - - /// - /// Subscribes to bar data updates. - /// - /// The source of the bar data. - /// The event arguments containing the bar data. - public void Sub(object source, in TBarEventArgs args) => Calc(args.Bar); - - /// - /// Initializes the indicator's state. - /// - public virtual void Init() { - _index = 0; - _lastValidValue = 0; - } - - /// - /// Calculates the indicator value based on the input bar. - /// - /// The input bar data. - /// A TValue containing the calculated result. - public virtual TValue Calc(TBar input) { - Input = input; - if (double.IsNaN(input.Close) || double.IsInfinity(input.Close)) { - return Process(new TValue(Time: input.Time, Value: GetLastValid(), IsNew: input.IsNew, IsHot: true)); - } - this.Value = Calculation(); - return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot)); - } - - /// - /// Retrieves the last valid calculated value. - /// - /// The last valid value of the indicator. - protected virtual double GetLastValid() { - return this.Value; - } - - /// - /// Manages the state of the indicator based on whether a new bar is being processed. - /// - /// Indicates whether the current input is a new bar. - protected abstract void ManageState(bool isNew); - - /// - /// Performs the actual calculation of the indicator value. - /// - /// The calculated indicator value. - protected abstract double Calculation(); - - /// - /// Processes the calculated value, updates the indicator's own state, - /// and publishes the result through an event. - /// - /// The calculated TValue to process. - /// The processed TValue. - protected virtual TValue Process(TValue value) { - this.Time = value.Time; - this.Value = value.Value; - this.IsNew = value.IsNew; - this.IsHot = value.IsHot; - Pub?.Invoke(this, new ValueEventArgs(value)); - return value; - } -} diff --git a/lib/core/abstractBase.cs b/lib/core/abstractBase.cs index 71b0ca6d..203b8329 100644 --- a/lib/core/abstractBase.cs +++ b/lib/core/abstractBase.cs @@ -15,6 +15,9 @@ public abstract class AbstractBase : ITValue public bool IsNew { get; set; } public bool IsHot { get; set; } public TValue Input { get; set; } + public TValue Input2 { get; set; } + public TBar BarInput { get; set; } + public TBar BarInput2 { get; set; } public String Name { get; set; } = ""; public int WarmupPeriod { get; set; } public TValue Tick => new(Time, Value, IsNew, IsHot); @@ -34,6 +37,11 @@ public abstract class AbstractBase : ITValue /// The argument containing the new data point. public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick); + public void Sub(object source1, object source2, in ValueEventArgs args1, in ValueEventArgs args2) => + Calc(args1.Tick, args2.Tick); + + public void Sub(object source, in TBarEventArgs args) => Calc(args.Bar); + /// /// Initializes the indicator's state. /// @@ -43,24 +51,75 @@ public abstract class AbstractBase : ITValue _lastValidValue = 0; } - /// - /// Calculates the indicator value based on the input. - /// - /// The input value for the calculation. - /// A TValue representing the calculated indicator value. - /// - /// This method calls the specific Calculation() method where the actual implementation is. - /// If the input value is NaN or infinity, it returns the last valid value instead. - /// public virtual TValue Calc(TValue input) { Input = input; - if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) + Input2 = new(Time: Input.Time, Value: double.NaN, IsNew: Input.IsNew, IsHot: Input.IsHot); + return HandleErrorCalculations(input.Value, input.Time, input.IsNew); + } + + public virtual TValue Calc(TBar barInput) + { + BarInput = barInput; + return HandleErrorCalculations(barInput.Close, barInput.Time, barInput.IsNew); + } + + public virtual TValue Calc(TValue input1, TValue input2) + { + Input = input1; + Input2 = input2; + return HandleErrorCalculations(input1.Value, input2.Value, input1.Time, input1.IsNew); + } + + public virtual TValue Calc(TBar input1, TBar input2) + { + BarInput = input1; + BarInput2 = input2; + return HandleErrorCalculations(input1.Close, input2.Close, input1.Time, input1.IsNew); + } + + /// + /// Handles error calculations and invalid input values. + /// + /// The primary input value to check. + /// The timestamp of the input. + /// Indicates if the input is new. + /// A TValue object with the calculated or last valid value. + /// + /// This method checks for NaN or infinity in the input value. If an invalid value is detected, + /// it returns the last valid value. Otherwise, it proceeds with the calculation. + /// + protected virtual TValue HandleErrorCalculations(double value, DateTime time, bool isNew) + { + if (double.IsNaN(value) || double.IsInfinity(value)) { - return Process(new TValue(input.Time, GetLastValid(), input.IsNew, input.IsHot)); + return Process(new TValue(time, GetLastValid(), isNew, this.IsHot)); } this.Value = Calculation(); - return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot)); + return Process(new TValue(Time: time, Value: this.Value, IsNew: isNew, IsHot: this.IsHot)); + } + + /// + /// Handles error calculations for inputs with two values. + /// + /// The first input value to check. + /// The second input value to check. + /// The timestamp of the input. + /// Indicates if the input is new. + /// A TValue object with the calculated or last valid value. + /// + /// This method checks for NaN or infinity in both input values. If any invalid value is detected, + /// it returns the last valid value. Otherwise, it proceeds with the calculation. + /// + protected virtual TValue HandleErrorCalculations(double value1, double value2, DateTime time, bool isNew) + { + if (double.IsNaN(value1) || double.IsInfinity(value1) || + double.IsNaN(value2) || double.IsInfinity(value2)) + { + return Process(new TValue(time, GetLastValid(), isNew, this.IsHot)); + } + this.Value = Calculation(); + return Process(new TValue(Time: time, Value: this.Value, IsNew: isNew, IsHot: this.IsHot)); } /// diff --git a/lib/core/tvalue.cs b/lib/core/tvalue.cs index 26bdc1c0..ff7decb9 100644 --- a/lib/core/tvalue.cs +++ b/lib/core/tvalue.cs @@ -52,12 +52,12 @@ public class TSeries : List var pubEvent = source.GetType().GetEvent("Pub"); if (pubEvent != null) { - /* + var nameProperty = source.GetType().GetProperty("Name"); if (nameProperty != null) { Name = nameProperty.GetValue(nameProperty)?.ToString()!; } - */ + pubEvent.AddEventHandler(source, new ValueSignal(Sub)); } } diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs index 35a67a15..7f3993dd 100644 --- a/lib/volatility/Atr.cs +++ b/lib/volatility/Atr.cs @@ -8,7 +8,7 @@ namespace QuanTAlib; /// of the true range. The true range is the greatest of: current high - current low, /// absolute value of current high - previous close, or absolute value of current low - previous close. /// -public class Atr : AbstractBarBase { +public class Atr : AbstractBase { private readonly Ema _ma; private double _prevClose, _p_prevClose; @@ -72,22 +72,22 @@ public class Atr : AbstractBarBase { /// as the true range. /// protected override double Calculation() { - ManageState(Input.IsNew); + ManageState(BarInput.IsNew); double trueRange = Math.Max( Math.Max( - Input.High - Input.Low, - Math.Abs(Input.High - _prevClose) + BarInput.High - BarInput.Low, + Math.Abs(BarInput.High - _prevClose) ), - Math.Abs(Input.Low - _prevClose) + Math.Abs(BarInput.Low - _prevClose) ); if (_index < 2) { - trueRange = Input.High - Input.Low; + trueRange = BarInput.High - BarInput.Low; } TValue emaTrueRange = _ma.Calc(new TValue(Input.Time, trueRange, Input.IsNew)); IsHot = _ma.IsHot; - _prevClose = Input.Close; + _prevClose = BarInput.Close; return emaTrueRange.Value; } diff --git a/notebooks/means.dib b/notebooks/means.dib index 16530f57..5e4455f2 100644 --- a/notebooks/means.dib +++ b/notebooks/means.dib @@ -10,15 +10,19 @@ QuanTAlib.Formatters.Initialize(); #!csharp -Sma ma1 = new(6); -Gmean ma2 = new (6); -Hmean ma3 = new (6); +TSeries input = new(); +Sma ma1 = new (6); +Sma ma2 = new (input, 6); -double[] input = new[]{1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,12,13,14,15,16,17,18,19,20}; -for (int i=0; i atr!; + protected override AbstractBase QuanTAlib => atr!; public override string ShortName => $"ATR {Period}"; public AtrIndicator() { diff --git a/quantower/_IndicatorBarBase.cs b/quantower/_IndicatorBarBase.cs index a5fae449..47bbcc1b 100644 --- a/quantower/_IndicatorBarBase.cs +++ b/quantower/_IndicatorBarBase.cs @@ -19,7 +19,7 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator // LineSeries.LineSeries(string, Color, int, LineStyle)' protected LineSeries? Series; - protected abstract AbstractBarBase QuanTAlib { get; } + protected abstract AbstractBase QuanTAlib { get; } int IWatchlistIndicator.MinHistoryDepths => 0; @@ -80,7 +80,7 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator int barX = (int)converter.GetChartX(Time(i)); int barY = (int)converter.GetChartY(Series![i]); int halfBarWidth = CurrentChart.BarsWidth / 2; - Point point = new Point(barX + halfBarWidth, barY); + Point point = new(barX + halfBarWidth, barY); allPoints.Add(point); } @@ -94,22 +94,21 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator { if (allPoints.Count < 2) { return; } - using (Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }) - using (Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }) - { - // Draw the hot part - if (hotCount > 0) - { - var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray(); - gr.DrawCurve(defaultPen, hotPoints, 0, hotPoints.Length - 1, (float)0.1); - } + using Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }; + using Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }; - // Draw the cold part - if (ShowColdValues && hotCount < allPoints.Count) - { - var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray(); - gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)0.1); - } + // Draw the hot part + if (hotCount > 0) + { + var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray(); + gr.DrawCurve(defaultPen, hotPoints, 0, hotPoints.Length - 1, (float)0.1); + } + + // Draw the cold part + if (ShowColdValues && hotCount < allPoints.Count) + { + var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray(); + gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)0.1); } } private static DashStyle ConvertLineStyleToDashStyle(LineStyle lineStyle) @@ -125,9 +124,9 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator } protected static void DrawText(Graphics gr, string text, Rectangle clientRect) { - Font font = new Font("Inter", 8); + Font font = new("Inter", 8); SizeF textSize = gr.MeasureString(text, font); - RectangleF textRect = new RectangleF(clientRect.Left + 5, + RectangleF textRect = new(clientRect.Left + 5, clientRect.Bottom - textSize.Height - 10, textSize.Width + 10, textSize.Height + 10); gr.FillRectangle(SystemBrushes.ControlDarkDark, textRect); From 931bbdb6a0e0694d8c57cd8e82ab48c600dda59c Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Tue, 8 Oct 2024 09:49:28 -0700 Subject: [PATCH 3/4] pull request changes --- .github/workflows/Publish.yml | 15 +++++------ Tests/Tests.csproj | 2 +- Tests/test_eventing.cs | 2 ++ Tests/test_skender.stock.cs | 9 +++---- lib/averages/Rma.cs | 51 +++++++++++++++++------------------ 5 files changed, 38 insertions(+), 41 deletions(-) diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml index 094cd6e0..c6cc6d3d 100644 --- a/.github/workflows/Publish.yml +++ b/.github/workflows/Publish.yml @@ -26,8 +26,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '9.x' - dotnet-quality: 'preview' + dotnet-version: '8.x' - name: Install JDK11 for Sonar Scanner uses: actions/setup-java@v3 @@ -80,8 +79,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '9.x' - dotnet-quality: 'preview' + dotnet-version: '8.x' - name: Install dotnet tools run: | @@ -136,8 +134,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '9.x' - dotnet-quality: 'preview' + dotnet-version: '8.x' - name: Initialize CodeQL uses: github/codeql-action/init@v3 @@ -153,6 +150,7 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 +# converting SARIF action not working yet. #SecurityCodeScan: # runs-on: windows-latest # steps: @@ -171,7 +169,7 @@ jobs: # uses: actions/setup-dotnet@v3 # with: # dotnet-version: | - # 9.x + # 8.x # 3.1.x # dotnet-quality: 'preview' # @@ -229,8 +227,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '9.x' - dotnet-quality: 'preview' + dotnet-version: '8.x' - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0 diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index dee01cd9..44158e8a 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -33,7 +33,7 @@ - + diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs index 832d990c..3c0e8aa6 100644 --- a/Tests/test_eventing.cs +++ b/Tests/test_eventing.cs @@ -1,3 +1,5 @@ +using Xunit; + namespace QuanTAlib; public class EventingTests diff --git a/Tests/test_skender.stock.cs b/Tests/test_skender.stock.cs index 09ef7ad7..2b8a2659 100644 --- a/Tests/test_skender.stock.cs +++ b/Tests/test_skender.stock.cs @@ -289,7 +289,6 @@ public class SkenderTests { for (int run = 0; run < iterations; run++) { - //period = rnd.Next(50) + 5; Mama ma = new(fastLimit: 0.5, slowLimit: 0.05); TSeries QL = new(); foreach (TBar item in feed) @@ -336,12 +335,12 @@ public class SkenderTests TSeries QL = new(); foreach (TBar item in bars) { QL.Add(ma.Calc(item)); } - var SK = quotes.GetAtr(lookbackPeriods: period).Select(i => i.Atr.Null2NaN()!); - Assert.Equal(QL.Length, QL.Length); + var atrValues = quotes.GetAtr(lookbackPeriods: period).Select(i => i.Atr.Null2NaN()!); + const int AdditionalPeriods = 500; - for (int i = QL.Length - 1; i > period + 500; i--) + for (int i = QL.Length - 1; i > period + AdditionalPeriods; i--) { - Assert.InRange(SK.ElementAt(i) - QL[i].Value, -range, range); + Assert.InRange(atrValues.ElementAt(i) - QL[i].Value, -range, range); } } } diff --git a/lib/averages/Rma.cs b/lib/averages/Rma.cs index b6255690..28bad44b 100644 --- a/lib/averages/Rma.cs +++ b/lib/averages/Rma.cs @@ -22,54 +22,53 @@ public class Rma : AbstractBase _alpha = 1.0 / _period; // Wilder's smoothing factor Name = $"Rma({_period})"; Init(); - } + } - public Rma(object source, int period) : this(period) - { + public Rma(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); - } + } - public override void Init() - { + public override void Init() + { base.Init(); _lastRMA = 0; _savedLastRMA = 0; - } - - protected override void ManageState(bool isNew) - { - if (isNew) - { - _savedLastRMA = _lastRMA; - _lastValidValue = Input.Value; - _index++; } - else + + protected override void ManageState(bool isNew) + { + if (!isNew) { _lastRMA = _savedLastRMA; + return; } - } - protected override double Calculation() - { + _savedLastRMA = _lastRMA; + _lastValidValue = Input.Value; + _index++; + } + + protected override double Calculation() + { ManageState(Input.IsNew); double rma; if (_index == 1) { - rma = Input.Value; + return Input.Value; } - else if (_index <= _period) + + if (_index <= _period) { // Simple average during initial period - rma = (_lastRMA * (_index - 1) + Input.Value) / _index; + return (_lastRMA * (_index - 1) + Input.Value) / _index; } - else - { - // Wilder's smoothing method - rma = _alpha * (Input.Value - _lastRMA) + _lastRMA; + + // Wilder's smoothing method + return _alpha * (Input.Value - _lastRMA) + _lastRMA; } _lastRMA = rma; From 85b9d9217aefaddbc51c52bb75b795b651385016 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:31:29 +0000 Subject: [PATCH 4/4] style: format code with dotnet-format This commit fixes the style issues introduced in 931bbdb according to the output from dotnet-format. Details: https://github.com/mihakralj/QuanTAlib/pull/30 --- Tests/test_eventing.cs | 16 +++--- lib/averages/Mgdi.cs | 7 ++- lib/averages/Qema.cs | 6 +-- lib/averages/Rma.cs | 28 +++++------ lib/core/tbar.cs | 38 +++++++------- lib/core/tvalue.cs | 29 +++++------ lib/feeds/GbmFeed.cs | 112 ++++++++++++++++++++--------------------- lib/volatility/Atr.cs | 33 ++++++++---- 8 files changed, 142 insertions(+), 127 deletions(-) diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs index 3c0e8aa6..dd8063f3 100644 --- a/Tests/test_eventing.cs +++ b/Tests/test_eventing.cs @@ -53,22 +53,22 @@ public class EventingTests }; // Generate 200 random values and feed them to both direct and event-based indicators - for (int i = 0; i < 200; i++) + for (int i = 0; i< 200; i++) { double randomValue = random.NextDouble() * 100; - input.Add(randomValue); + input.Add(randomValue); // Calculate direct indicators foreach (var (direct, _) in indicators) { direct.Calc(randomValue); } - } +} - // Compare the results of direct and event-based calculations - foreach (var (direct, eventBased) in indicators) - { - Assert.Equal(direct.Value, eventBased.Value, 9); - } +// Compare the results of direct and event-based calculations +foreach (var (direct, eventBased) in indicators) +{ + Assert.Equal(direct.Value, eventBased.Value, 9); +} } } diff --git a/lib/averages/Mgdi.cs b/lib/averages/Mgdi.cs index 7435b898..f137821d 100644 --- a/lib/averages/Mgdi.cs +++ b/lib/averages/Mgdi.cs @@ -40,7 +40,9 @@ public class Mgdi : AbstractBase { _p_prevMd = _prevMd; _index++; - } else { + } + else + { _prevMd = _p_prevMd; } } @@ -50,7 +52,8 @@ public class Mgdi : AbstractBase ManageState(Input.IsNew); double value = Input.Value; - if (_index < 2){ + if (_index < 2) + { _prevMd = value; } else diff --git a/lib/averages/Qema.cs b/lib/averages/Qema.cs index 87f38f28..0e151fa7 100644 --- a/lib/averages/Qema.cs +++ b/lib/averages/Qema.cs @@ -6,9 +6,9 @@ public class Qema : AbstractBase private readonly Ema _ema1, _ema2, _ema3, _ema4; private double _lastQema, _p_lastQema; - public Qema(double k1=0.2, double k2=0.2, double k3=0.2, double k4=0.2) + public Qema(double k1 = 0.2, double k2 = 0.2, double k3 = 0.2, double k4 = 0.2) { - if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0 ) + if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0) { throw new ArgumentOutOfRangeException("All k values must be in the range (0, 1]."); } @@ -26,7 +26,7 @@ public class Qema : AbstractBase Name = $"QEMA ({k1:F2},{k2:F2},{k3:F2},{k4:F2})"; double smK = Math.Min(Math.Min(_k1, _k2), Math.Min(_k3, _k4)); - WarmupPeriod = (int) ((2 - smK) / smK); + WarmupPeriod = (int)((2 - smK) / smK); Init(); } diff --git a/lib/averages/Rma.cs b/lib/averages/Rma.cs index 28bad44b..1d1e4e4a 100644 --- a/lib/averages/Rma.cs +++ b/lib/averages/Rma.cs @@ -22,23 +22,23 @@ public class Rma : AbstractBase _alpha = 1.0 / _period; // Wilder's smoothing factor Name = $"Rma({_period})"; Init(); - } + } - public Rma(object source, int period) : this(period) - { + public Rma(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); - } + } - public override void Init() - { + public override void Init() + { base.Init(); _lastRMA = 0; _savedLastRMA = 0; - } + } - protected override void ManageState(bool isNew) - { + protected override void ManageState(bool isNew) + { if (!isNew) { _lastRMA = _savedLastRMA; @@ -48,10 +48,10 @@ public class Rma : AbstractBase _savedLastRMA = _lastRMA; _lastValidValue = Input.Value; _index++; - } + } - protected override double Calculation() - { + protected override double Calculation() + { ManageState(Input.IsNew); double rma; @@ -69,9 +69,9 @@ public class Rma : AbstractBase // Wilder's smoothing method return _alpha * (Input.Value - _lastRMA) + _lastRMA; - } + } - _lastRMA = rma; + _lastRMA = rma; IsHot = _index >= WarmupPeriod; return rma; diff --git a/lib/core/tbar.cs b/lib/core/tbar.cs index 85cc8e09..441994ea 100644 --- a/lib/core/tbar.cs +++ b/lib/core/tbar.cs @@ -14,28 +14,28 @@ public interface ITBar public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : ITBar { public DateTime Time { get; init; } = Time; - public double Open { get; init; } = Open; - public double High { get; init; } = High; - public double Low { get; init; } = Low; - public double Close { get; init; } = Close; - public double Volume { get; init; } = Volume; - public bool IsNew { get; init; } = IsNew; +public double Open { get; init; } = Open; +public double High { get; init; } = High; +public double Low { get; init; } = Low; +public double Close { get; init; } = Close; +public double Volume { get; init; } = Volume; +public bool IsNew { get; init; } = IsNew; - public double HL2 => (High + Low) * 0.5; - public double OC2 => (Open + Close) * 0.5; - public double OHL3 => (Open + High + Low) / 3; - public double HLC3 => (High + Low + Close) / 3; - public double OHLC4 => (Open + High + Low + Close) * 0.25; - public double HLCC4 => (High + Low + Close + Close) * 0.25; +public double HL2 => (High + Low) * 0.5; +public double OC2 => (Open + Close) * 0.5; +public double OHL3 => (Open + High + Low) / 3; +public double HLC3 => (High + Low + Close) / 3; +public double OHLC4 => (Open + High + Low + Close) * 0.25; +public double HLCC4 => (High + Low + Close + Close) * 0.25; - public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { } - public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { } - public TBar(double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) { } - public TBar(TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) { } +public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { } +public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { } +public TBar(double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) { } +public TBar(TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) { } - public static implicit operator double(TBar bar) => bar.Close; - public static implicit operator DateTime(TBar tv) => tv.Time; - public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; +public static implicit operator double(TBar bar) => bar.Close; +public static implicit operator DateTime(TBar tv) => tv.Time; +public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; } public delegate void BarSignal(object source, in TBarEventArgs args); diff --git a/lib/core/tvalue.cs b/lib/core/tvalue.cs index ff7decb9..f7c83cff 100644 --- a/lib/core/tvalue.cs +++ b/lib/core/tvalue.cs @@ -11,19 +11,19 @@ public interface ITValue public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) : ITValue { public DateTime Time { get; init; } = Time; - public double Value { get; init; } = Value; - public bool IsNew { get; init; } = IsNew; - public bool IsHot { get; init; } = IsHot; - public DateTime t => Time; - public double v => Value; +public double Value { get; init; } = Value; +public bool IsNew { get; init; } = IsNew; +public bool IsHot { get; init; } = IsHot; +public DateTime t => Time; +public double v => Value; - public TValue() : this(DateTime.UtcNow, 0) { } - public TValue(double value, bool isNew = true, bool isHot = true) : this(DateTime.UtcNow, value, IsNew: isNew, IsHot: isHot) { } - public static implicit operator double(TValue tv) => tv.Value; - public static implicit operator DateTime(TValue tv) => tv.Time; - public static implicit operator TValue(double value) => new TValue(DateTime.UtcNow, value); +public TValue() : this(DateTime.UtcNow, 0) { } +public TValue(double value, bool isNew = true, bool isHot = true) : this(DateTime.UtcNow, value, IsNew: isNew, IsHot: isHot) { } +public static implicit operator double(TValue tv) => tv.Value; +public static implicit operator DateTime(TValue tv) => tv.Time; +public static implicit operator TValue(double value) => new TValue(DateTime.UtcNow, value); - public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}, {Value:F2}, IsNew: {IsNew}, IsHot: {IsHot}]"; +public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}, {Value:F2}, IsNew: {IsNew}, IsHot: {IsHot}]"; } public delegate void ValueSignal(object source, in ValueEventArgs args); @@ -54,10 +54,11 @@ public class TSeries : List { var nameProperty = source.GetType().GetProperty("Name"); - if (nameProperty != null) { + if (nameProperty != null) + { Name = nameProperty.GetValue(nameProperty)?.ToString()!; } - + pubEvent.AddEventHandler(source, new ValueSignal(Sub)); } } @@ -66,7 +67,7 @@ public class TSeries : List public new virtual void Add(TValue tick) { - if (tick.IsNew || base.Count==0) { base.Add(tick); } + if (tick.IsNew || base.Count == 0) { base.Add(tick); } else { this[^1] = tick; } Pub?.Invoke(this, new ValueEventArgs(tick)); } diff --git a/lib/feeds/GbmFeed.cs b/lib/feeds/GbmFeed.cs index 52531078..9d48b0d5 100644 --- a/lib/feeds/GbmFeed.cs +++ b/lib/feeds/GbmFeed.cs @@ -4,67 +4,67 @@ namespace QuanTAlib; public class GbmFeed : TBarSeries { - private readonly double _mu, _sigma; - private readonly Random _random; - private double _lastClose, _lastHigh, _lastLow; + private readonly double _mu, _sigma; + private readonly Random _random; + private double _lastClose, _lastHigh, _lastLow; - public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2) - { - _lastClose = _lastHigh = _lastLow = initialPrice; - _mu = mu; - _sigma = sigma; - _random = new Random((int)DateTime.Now.Ticks); - this.Name = $"GBM({_sigma:F2})"; - } + public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2) + { + _lastClose = _lastHigh = _lastLow = initialPrice; + _mu = mu; + _sigma = sigma; + _random = new Random((int)DateTime.Now.Ticks); + this.Name = $"GBM({_sigma:F2})"; + } - public void Add(bool isNew = true) => Add(time: DateTime.Now, isNew: isNew); - public void Add(DateTime time, bool isNew = true) => base.Add(Generate(time, isNew)); - public void Add(int count) - { - DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count); - TBar lastBar = new(); - for (int i = 0; i < count; i++) - { - Add(startTime, true); - Add(startTime, false); - Add(startTime, false); - startTime = startTime.AddHours(1); - } - } + public void Add(bool isNew = true) => Add(time: DateTime.Now, isNew: isNew); + public void Add(DateTime time, bool isNew = true) => base.Add(Generate(time, isNew)); + public void Add(int count) + { + DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count); + TBar lastBar = new(); + for (int i = 0; i < count; i++) + { + Add(startTime, true); + Add(startTime, false); + Add(startTime, false); + startTime = startTime.AddHours(1); + } + } - public TBar Generate(DateTime time, bool isNew = true) - { - double dt = 1.0 / 252; - double drift = (_mu - 0.5 * _sigma * _sigma) * dt; - double diffusion = _sigma * Math.Sqrt(dt) * GenerateNormalRandom(); - double newClose = _lastClose * Math.Exp(drift + diffusion); + public TBar Generate(DateTime time, bool isNew = true) + { + double dt = 1.0 / 252; + double drift = (_mu - 0.5 * _sigma * _sigma) * dt; + double diffusion = _sigma * Math.Sqrt(dt) * GenerateNormalRandom(); + double newClose = _lastClose * Math.Exp(drift + diffusion); - double open = _lastClose; - double high = Math.Max(_lastHigh, Math.Max(open, newClose) * (1 + _random.NextDouble() * 0.01)); - double low = Math.Min(_lastLow, Math.Min(open, newClose) * (1 - _random.NextDouble() * 0.01)); - double volume = 1000 + _random.NextDouble() * 1000; + double open = _lastClose; + double high = Math.Max(_lastHigh, Math.Max(open, newClose) * (1 + _random.NextDouble() * 0.01)); + double low = Math.Min(_lastLow, Math.Min(open, newClose) * (1 - _random.NextDouble() * 0.01)); + double volume = 1000 + _random.NextDouble() * 1000; - if (isNew) - { - _lastClose = newClose; - } - else - { - high = Math.Max(_lastHigh, high); - low = Math.Min(_lastLow, low); - } - _lastHigh = high; - _lastLow = low; + if (isNew) + { + _lastClose = newClose; + } + else + { + high = Math.Max(_lastHigh, high); + low = Math.Min(_lastLow, low); + } + _lastHigh = high; + _lastLow = low; - TBar bar = new(time, open, high, low, newClose, volume, isNew); - return bar; - } + TBar bar = new(time, open, high, low, newClose, volume, isNew); + return bar; + } - private double GenerateNormalRandom() - { - // Box-Muller transform to generate standard normal random variable - double u1 = 1.0 - _random.NextDouble(); // Uniform(0,1] random doubles - double u2 = 1.0 - _random.NextDouble(); - return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); - } + private double GenerateNormalRandom() + { + // Box-Muller transform to generate standard normal random variable + double u1 = 1.0 - _random.NextDouble(); // Uniform(0,1] random doubles + double u2 = 1.0 - _random.NextDouble(); + return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); + } } \ No newline at end of file diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs index 7f3993dd..e6c20845 100644 --- a/lib/volatility/Atr.cs +++ b/lib/volatility/Atr.cs @@ -8,7 +8,8 @@ namespace QuanTAlib; /// of the true range. The true range is the greatest of: current high - current low, /// absolute value of current high - previous close, or absolute value of current low - previous close. /// -public class Atr : AbstractBase { +public class Atr : AbstractBase +{ private readonly Ema _ma; private double _prevClose, _p_prevClose; @@ -19,11 +20,13 @@ public class Atr : AbstractBase { /// /// Thrown when period is less than 1. /// - public Atr(int period) { - if (period < 1) { + public Atr(int period) + { + if (period < 1) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } - _ma = new(1.0/period); + _ma = new(1.0 / period); WarmupPeriod = _ma.WarmupPeriod; Name = $"ATR({period})"; } @@ -33,7 +36,8 @@ public class Atr : AbstractBase { /// /// The source object to subscribe to for bar updates. /// The period over which to calculate the ATR. - public Atr(object source, int period) : this(period) { + public Atr(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new BarSignal(Sub)); } @@ -41,7 +45,8 @@ public class Atr : AbstractBase { /// /// Initializes the Atr instance by setting up the initial state. /// - public override void Init() { + public override void Init() + { base.Init(); _ma.Init(); _prevClose = double.NaN; @@ -51,11 +56,15 @@ public class Atr : AbstractBase { /// Manages the state of the Atr instance based on whether a new bar is being processed. /// /// Indicates whether the current input is a new bar. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _index++; _p_prevClose = _prevClose; - } else { + } + else + { _prevClose = _p_prevClose; } } @@ -71,7 +80,8 @@ public class Atr : AbstractBase { /// to smooth the true range values. For the first bar, it uses the high-low range /// as the true range. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(BarInput.IsNew); double trueRange = Math.Max( @@ -81,7 +91,8 @@ public class Atr : AbstractBase { ), Math.Abs(BarInput.Low - _prevClose) ); - if (_index < 2) { + if (_index < 2) + { trueRange = BarInput.High - BarInput.Low; }