diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml index b3ca034c..c6cc6d3d 100644 --- a/.github/workflows/Publish.yml +++ b/.github/workflows/Publish.yml @@ -26,7 +26,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '8.x' - name: Install JDK11 for Sonar Scanner uses: actions/setup-java@v3 @@ -79,7 +79,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '8.x' - name: Install dotnet tools run: | @@ -134,7 +134,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '8.x' - name: Initialize CodeQL uses: github/codeql-action/init@v3 @@ -150,39 +150,42 @@ 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 +# converting SARIF action not working yet. + #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: | + # 8.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 +215,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 +227,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '8.x' - 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..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 @@ -20,12 +21,6 @@ snupkg AnyCPU true - - - $(GitVersion_NuGetVersion) - $(GitVersion_AssemblySemVer) - $(GitVersion_AssemblySemFileVer) - $(GitVersion_InformationalVersion) @@ -50,10 +45,6 @@ - - all - runtime; build; native; contentfiles; analyzers - @@ -63,5 +54,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..44158e8a 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,22 +1,26 @@ - net8.0 QuanTAlib.Tests QuanTAlib.Tests - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + all + runtime; build; native; contentfiles; analyzers + + + - @@ -29,6 +33,7 @@ + diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs new file mode 100644 index 00000000..dd8063f3 --- /dev/null +++ b/Tests/test_eventing.cs @@ -0,0 +1,74 @@ +using Xunit; + +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 3c8afe4a..79c8563c 100644 --- a/Tests/test_iTBar.cs +++ b/Tests/test_iTBar.cs @@ -4,55 +4,139 @@ 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); } - private static readonly iTValue[] indicators = new iTValue[] + 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) + public void IndicatorIsNew(ITValue indicator) { var indicator1 = indicator; var indicator2 = indicator; - MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; + MethodInfo calcMethod = FindCalcMethod(indicator.GetType()); 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++) { - 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 4e5ceeb8..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,82 +16,132 @@ public class IndicatorTests rnd = new Random((int)DateTime.Now.Ticks); } - private static readonly iTValue[] indicators = - [ + // 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] [MemberData(nameof(GetIndicators))] - public void IndicatorIsNew(iTValue indicator) + public void IndicatorIsNew(ITValue indicator) { var indicator1 = indicator; var indicator2 = indicator; - MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; + MethodInfo calcMethod = FindCalcMethod(indicator.GetType()); 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++) { 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/Tests/test_skender.stock.cs b/Tests/test_skender.stock.cs index 1520c22c..7c7fe3d5 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; @@ -288,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) @@ -335,12 +335,16 @@ 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; +<<<<<<< HEAD for (int i = QL.Length - 1; i > period + 500; i--) +======= + for (int i = QL.Length - 1; i > period + AdditionalPeriods; i--) +>>>>>>> dev { - 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/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 75ba2f4f..a7de9b6c 100644 --- a/lib/averages/Frama.cs +++ b/lib/averages/Frama.cs @@ -1,99 +1,110 @@ using System; -namespace QuanTAlib +namespace QuanTAlib; + +public class Frama : AbstractBase { - public class Frama : AbstractBase + private readonly int _period; + private readonly CircularBuffer _buffer; + private double _lastFrama; + private double _prevLastFrama; + + public Frama(int period) { - 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; + _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(); + _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); } } +<<<<<<< HEAD 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); +>>>>>>> dev - 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); - } - } + IsHot = _index >= WarmupPeriod; + return _lastFrama; + } - 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; - } + protected override double GetLastValid() + { + return _lastFrama; } } \ No newline at end of file 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..00b77de8 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) { @@ -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/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 25d4ea34..f137821d 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 fa0cd771..76d1f96f 100644 --- a/lib/averages/Qema.cs +++ b/lib/averages/Qema.cs @@ -6,7 +6,11 @@ public class Qema : AbstractBase private readonly Ema _ema1, _ema2, _ema3, _ema4; private double _lastQema, _p_lastQema; +<<<<<<< HEAD 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) +>>>>>>> dev { if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0) { diff --git a/lib/averages/Rema.cs b/lib/averages/Rema.cs index c89782c7..afa22046 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."); @@ -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/averages/Rma.cs b/lib/averages/Rma.cs index a2ae9aba..d1381725 100644 --- a/lib/averages/Rma.cs +++ b/lib/averages/Rma.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; +<<<<<<< HEAD namespace QuanTAlib { @@ -29,6 +30,80 @@ namespace QuanTAlib var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } +======= +namespace QuanTAlib; + + +public class Rma : AbstractBase +{ + private readonly int _period; + private readonly double _alpha; + private double _lastRMA; + private double _savedLastRMA; + + public Rma(int period) + { + if (period < 1) + { + throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); + } + _period = period; + WarmupPeriod = period * 2; + _alpha = 1.0 / _period; // Wilder's smoothing factor + Name = $"Rma({_period})"; + Init(); + } + + public Rma(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + public override void Init() + { + base.Init(); + _lastRMA = 0; + _savedLastRMA = 0; + } + + protected override void ManageState(bool isNew) + { + if (!isNew) + { + _lastRMA = _savedLastRMA; + return; + } + + _savedLastRMA = _lastRMA; + _lastValidValue = Input.Value; + _index++; + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + double rma; + + if (_index == 1) + { + return Input.Value; + } + + if (_index <= _period) + { + // Simple average during initial period + return (_lastRMA * (_index - 1) + Input.Value) / _index; + } + + // Wilder's smoothing method + return _alpha * (Input.Value - _lastRMA) + _lastRMA; + } + + _lastRMA = rma; + IsHot = _index >= WarmupPeriod; +>>>>>>> dev public override void Init() { @@ -78,4 +153,8 @@ namespace QuanTAlib return rma; } } -} \ No newline at end of file +<<<<<<< HEAD +} +======= +} +>>>>>>> dev 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 a71d7e4a..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) { 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 7c72e96d..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) { diff --git a/lib/core/abstractBase.cs b/lib/core/abstractBase.cs index ee6ea0cb..203b8329 100644 --- a/lib/core/abstractBase.cs +++ b/lib/core/abstractBase.cs @@ -8,13 +8,16 @@ 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; } 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/circularbuffer.cs b/lib/core/circularbuffer.cs index daf9394a..5a2e9c7a 100644 --- a/lib/core/circularbuffer.cs +++ b/lib/core/circularbuffer.cs @@ -221,11 +221,16 @@ public class CircularBuffer : IEnumerable if (_start + _size <= Capacity) { return new ReadOnlySpan(_buffer, _start, _size); +<<<<<<< HEAD } else { return new ReadOnlySpan(ToArray()); +======= +>>>>>>> dev } + + return new ReadOnlySpan(ToArray()); } /// 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 1db617d3..441994ea 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 a83f1492..f7c83cff 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; @@ -52,12 +52,13 @@ public class TSeries : List var pubEvent = source.GetType().GetEvent("Pub"); if (pubEvent != null) { - /* + var nameProperty = source.GetType().GetProperty("Name"); - if (nameProperty != null) { + if (nameProperty != null) + { Name = nameProperty.GetValue(nameProperty)?.ToString()!; } - */ + pubEvent.AddEventHandler(source, new ValueSignal(Sub)); } } diff --git a/lib/feeds/GbmFeed.cs b/lib/feeds/GbmFeed.cs index ca0d03bc..f4f93f77 100644 --- a/lib/feeds/GbmFeed.cs +++ b/lib/feeds/GbmFeed.cs @@ -8,7 +8,11 @@ public class GbmFeed : TBarSeries private readonly Random _random; private double _lastClose, _lastHigh, _lastLow; +<<<<<<< HEAD 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) +>>>>>>> dev { _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 3c3fe8ac..fade78e9 100644 --- a/lib/statistics/Min.cs +++ b/lib/statistics/Min.cs @@ -26,7 +26,11 @@ public class Min : AbstractBase /// /// Thrown when period is less than 1 or decay is negative. /// +<<<<<<< HEAD public Min(int period, double decay = 0) : base() +======= + public Min(int period, double decay = 0) +>>>>>>> dev { if (period < 1) { @@ -109,7 +113,7 @@ public class Min : AbstractBase } 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 658a8052..7579bf84 100644 --- a/lib/statistics/Mode.cs +++ b/lib/statistics/Mode.cs @@ -21,7 +21,11 @@ public class Mode : AbstractBase /// /// Thrown when period is less than 1. /// +<<<<<<< HEAD public Mode(int period) : base() +======= + public Mode(int period) +>>>>>>> dev { if (period < 1) { diff --git a/lib/statistics/Percentile.cs b/lib/statistics/Percentile.cs index ddbbb9eb..b4de2b69 100644 --- a/lib/statistics/Percentile.cs +++ b/lib/statistics/Percentile.cs @@ -24,7 +24,11 @@ public class Percentile : AbstractBase /// /// Thrown when period is less than 2 or percent is not between 0 and 100. /// +<<<<<<< HEAD public Percentile(int period, double percent) : base() +======= + public Percentile(int period, double percent) +>>>>>>> dev { if (period < 2) { diff --git a/lib/statistics/Skew.cs b/lib/statistics/Skew.cs index 772b72e1..babb967f 100644 --- a/lib/statistics/Skew.cs +++ b/lib/statistics/Skew.cs @@ -22,7 +22,11 @@ public class Skew : AbstractBase /// /// Thrown when period is less than 3. /// +<<<<<<< HEAD public Skew(int period) : base() +======= + public Skew(int period) +>>>>>>> dev { if (period < 3) { diff --git a/lib/statistics/Stddev.cs b/lib/statistics/Stddev.cs index 9047467c..d7d01fd8 100644 --- a/lib/statistics/Stddev.cs +++ b/lib/statistics/Stddev.cs @@ -26,7 +26,11 @@ public class Stddev : AbstractBase /// /// Thrown when period is less than 2. /// +<<<<<<< HEAD public Stddev(int period, bool isPopulation = false) : base() +======= + public Stddev(int period, bool isPopulation = false) +>>>>>>> dev { if (period < 2) { diff --git a/lib/statistics/Variance.cs b/lib/statistics/Variance.cs index 71b2770d..a669a828 100644 --- a/lib/statistics/Variance.cs +++ b/lib/statistics/Variance.cs @@ -26,7 +26,11 @@ public class Variance : AbstractBase /// /// Thrown when period is less than 2. /// +<<<<<<< HEAD public Variance(int period, bool isPopulation = false) : base() +======= + public Variance(int period, bool isPopulation = false) +>>>>>>> dev { if (period < 2) { diff --git a/lib/statistics/Zscore.cs b/lib/statistics/Zscore.cs index c082cca3..2a7e4664 100644 --- a/lib/statistics/Zscore.cs +++ b/lib/statistics/Zscore.cs @@ -21,7 +21,11 @@ public class Zscore : AbstractBase /// /// Thrown when period is less than 2. /// +<<<<<<< HEAD public Zscore(int period) : base() +======= + public Zscore(int period) +>>>>>>> dev { if (period < 2) { diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs index 134cd1af..34df9043 100644 --- a/lib/volatility/Atr.cs +++ b/lib/volatility/Atr.cs @@ -8,7 +8,11 @@ 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. /// +<<<<<<< HEAD public class Atr : AbstractBarBase +======= +public class Atr : AbstractBase +>>>>>>> dev { private readonly Ema _ma; private double _prevClose, _p_prevClose; @@ -82,23 +86,31 @@ public class Atr : AbstractBarBase /// protected override double Calculation() { +<<<<<<< HEAD ManageState(Input.IsNew); +======= + ManageState(BarInput.IsNew); +>>>>>>> dev 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) { +<<<<<<< HEAD trueRange = Input.High - Input.Low; +======= + trueRange = BarInput.High - BarInput.Low; +>>>>>>> dev } 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/lib/volatility/Historical.cs b/lib/volatility/Historical.cs index c3a781cd..a6fcb880 100644 --- a/lib/volatility/Historical.cs +++ b/lib/volatility/Historical.cs @@ -25,7 +25,11 @@ public class Historical : AbstractBase /// /// Thrown when period is less than 2. /// +<<<<<<< HEAD public Historical(int period, bool isAnnualized = true) : base() +======= + public Historical(int period, bool isAnnualized = true) +>>>>>>> dev { if (period < 2) { diff --git a/lib/volatility/Realized.cs b/lib/volatility/Realized.cs index 6f2774a3..09dfb139 100644 --- a/lib/volatility/Realized.cs +++ b/lib/volatility/Realized.cs @@ -25,7 +25,11 @@ public class Realized : AbstractBase /// /// Thrown when period is less than 2. /// +<<<<<<< HEAD public Realized(int period, bool isAnnualized = true) : base() +======= + public Realized(int period, bool isAnnualized = true) +>>>>>>> dev { if (period < 2) { diff --git a/lib/volatility/Rvi.cs b/lib/volatility/Rvi.cs index 34663e61..35ff6824 100644 --- a/lib/volatility/Rvi.cs +++ b/lib/volatility/Rvi.cs @@ -16,8 +16,8 @@ namespace QuanTAlib; 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; /// @@ -27,7 +27,11 @@ public class Rvi : AbstractBase /// /// Thrown when period is less than 2. /// +<<<<<<< HEAD public Rvi(int period) : base() +======= + public Rvi(int period) +>>>>>>> dev { if (period < 2) { @@ -105,6 +109,7 @@ public class Rvi : AbstractBase _downSma.Calc(_downStdDev.Calc(new TValue(Input.Time, downMove, Input.IsNew))); double rvi; +<<<<<<< HEAD if (_upSma.Value + _downSma.Value != 0) { rvi = 100 * _upSma.Value / (_upSma.Value + _downSma.Value); @@ -113,6 +118,9 @@ public class Rvi : AbstractBase { rvi = 0; } +======= + rvi = (_upSma.Value + _downSma.Value != 0) ? 100 * _upSma.Value / (_upSma.Value + _downSma.Value) : 0; +>>>>>>> dev _previousClose = close; IsHot = _index >= WarmupPeriod; 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);