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;