Merge branch 'dev'

This commit is contained in:
Miha Kralj
2024-10-08 10:47:21 -07:00
56 changed files with 986 additions and 568 deletions
+41 -38
View File
@@ -26,7 +26,7 @@ jobs:
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.x'
- name: Install JDK11 for Sonar Scanner - name: Install JDK11 for Sonar Scanner
uses: actions/setup-java@v3 uses: actions/setup-java@v3
@@ -79,7 +79,7 @@ jobs:
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.x'
- name: Install dotnet tools - name: Install dotnet tools
run: | run: |
@@ -134,7 +134,7 @@ jobs:
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v3
@@ -150,39 +150,42 @@ jobs:
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v3
SecurityCodeScan: # converting SARIF action not working yet.
runs-on: windows-latest #SecurityCodeScan:
steps: # runs-on: windows-latest
- name: Checkout repository # steps:
uses: actions/checkout@v4 # - name: Checkout repository
with: # uses: actions/checkout@v4
fetch-depth: 0 # with:
# fetch-depth: 0
- name: Setup NuGet #
uses: nuget/setup-nuget@v1 # - name: Setup NuGet
# uses: nuget/setup-nuget@v1
- name: Setup MSBuild #
uses: microsoft/setup-msbuild@v1 # - name: Setup MSBuild
# uses: microsoft/setup-msbuild@v1
- name: Setup .NET SDK #
uses: actions/setup-dotnet@v3 # - name: Setup .NET SDK
with: # uses: actions/setup-dotnet@v3
dotnet-version: '3.1.x' # with:
# dotnet-version: |
- name: Set up projects for analysis # 8.x
uses: security-code-scan/security-code-scan-add-action@v1 # 3.1.x
# dotnet-quality: 'preview'
- name: Restore dependencies #
run: dotnet restore # - name: Set up projects for analysis
# uses: security-code-scan/security-code-scan-add-action@v1
- name: Build #
run: dotnet build --no-restore --configuration Debug # - name: Build
# run: |
- name: Convert SARIF for uploading to GitHub # dotnet restore
uses: security-code-scan/security-code-scan-results-action@v1 # dotnet build --no-restore --configuration Debug
#
- name: Upload SARIF # - name: Convert SARIF for uploading to GitHub
uses: github/codeql-action/upload-sarif@v3 # uses: security-code-scan/security-code-scan-results-action@v1
#
# - name: Upload SARIF
# uses: github/codeql-action/upload-sarif@v3
Codacy_Scan: Codacy_Scan:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -212,7 +215,7 @@ jobs:
sarif_file: results.sarif sarif_file: results.sarif
build_publish: build_publish:
needs: [SonarCloud, Code_Coverage, CodeQL, SecurityCodeScan, Codacy_Scan] needs: [SonarCloud, Code_Coverage, CodeQL, Codacy_Scan]
if: success() if: success()
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -224,7 +227,7 @@ jobs:
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.x'
- name: Install GitVersion - name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0 uses: gittools/actions/gitversion/setup@v0
+4 -1
View File
@@ -10,5 +10,8 @@
"sonarlint.connectedMode.project": { "sonarlint.connectedMode.project": {
"connectionId": "mihakralj", "connectionId": "mihakralj",
"projectKey": "mihakralj_QuanTAlib" "projectKey": "mihakralj_QuanTAlib"
} },
"dotnet.dotnetPath": "C:/Program Files/dotnet",
"omnisharp.useModernNet": true,
"omnisharp.sdkPath": "C:/Program Files/dotnet/sdk"
} }
+3 -14
View File
@@ -1,11 +1,12 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<NeutralLanguage>en-US</NeutralLanguage>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<LangVersion>preview</LangVersion> <NeutralLanguage>en-US</NeutralLanguage>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports> <DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
@@ -20,12 +21,6 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<IsLocalBuild Condition="'$(GITHUB_ACTIONS)' == ''">true</IsLocalBuild> <IsLocalBuild Condition="'$(GITHUB_ACTIONS)' == ''">true</IsLocalBuild>
<!-- GitVersion Properties -->
<Version>$(GitVersion_NuGetVersion)</Version>
<AssemblyVersion>$(GitVersion_AssemblySemVer)</AssemblyVersion>
<FileVersion>$(GitVersion_AssemblySemFileVer)</FileVersion>
<InformationalVersion>$(GitVersion_InformationalVersion)</InformationalVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <PropertyGroup Condition="'$(Configuration)' == 'Release'">
@@ -50,10 +45,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="GitVersion.MsBuild" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.DotNet.Interactive.Formatting" Version="1.0.0-beta.21459.1" /> <PackageReference Include="Microsoft.DotNet.Interactive.Formatting" Version="1.0.0-beta.21459.1" />
</ItemGroup> </ItemGroup>
@@ -63,5 +54,3 @@
</PropertyGroup> </PropertyGroup>
</Project> </Project>
+38 -30
View File
@@ -1,18 +1,21 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59 VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 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 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 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 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 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -23,29 +26,34 @@ Global
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1E050FA4-630E-4801-9DE9-D2536DACA9B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E050FA4-630E-4801-9DE9-D2536DACA9B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E050FA4-630E-4801-9DE9-D2536DACA9B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}.Release|Any CPU.Build.0 = Release|Any CPU {1E050FA4-630E-4801-9DE9-D2536DACA9B0}.Release|Any CPU.Build.0 = Release|Any CPU
{B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E9427C7-144F-488E-A29D-789ACC1C32AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E9427C7-144F-488E-A29D-789ACC1C32AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E9427C7-144F-488E-A29D-789ACC1C32AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}.Release|Any CPU.Build.0 = Release|Any CPU {2E9427C7-144F-488E-A29D-789ACC1C32AE}.Release|Any CPU.Build.0 = Release|Any CPU
{C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6BE10C39-4127-446C-818B-7976FCDD51D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}.Debug|Any CPU.Build.0 = Debug|Any CPU {6BE10C39-4127-446C-818B-7976FCDD51D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BE10C39-4127-446C-818B-7976FCDD51D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}.Release|Any CPU.Build.0 = Release|Any CPU {6BE10C39-4127-446C-818B-7976FCDD51D5}.Release|Any CPU.Build.0 = Release|Any CPU
{D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4E5F6G7-H8I9-50J1-K2L3-M4N5O6P7Q8R9}.Release|Any CPU.Build.0 = Release|Any CPU {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Release|Any CPU.Build.0 = Release|Any CPU
{E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5F6G7H8-I9J0-51K2-L3M4-N5O6P7Q8R9S0}.Release|Any CPU.Build.0 = Release|Any CPU {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Release|Any CPU.Build.0 = Release|Any CPU
{F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E204F173-5840-4AA3-BED1-98C8D2F813E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}.Debug|Any CPU.Build.0 = Debug|Any CPU {E204F173-5840-4AA3-BED1-98C8D2F813E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}.Release|Any CPU.ActiveCfg = Release|Any CPU {E204F173-5840-4AA3-BED1-98C8D2F813E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6G7H8I9-J0K1-52L3-M4N5-O6P7Q8R9S0T1}.Release|Any CPU.Build.0 = 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 EndGlobalSection
EndGlobal EndGlobal
+10 -5
View File
@@ -1,22 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>QuanTAlib.Tests</RootNamespace> <RootNamespace>QuanTAlib.Tests</RootNamespace>
<AssemblyName>QuanTAlib.Tests</AssemblyName> <AssemblyName>QuanTAlib.Tests</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0-pre.35">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> <PackageReference Include="xunit.runner.console" Version="2.9.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Skender.Stock.Indicators" Version="2.5.0" /> <PackageReference Include="Skender.Stock.Indicators" Version="2.5.0" />
<PackageReference Include="TALib.NETCore" Version="0.4.4" /> <PackageReference Include="TALib.NETCore" Version="0.4.4" />
<PackageReference Include="Tulip.NETCore" Version="0.8.0.1" /> <PackageReference Include="Tulip.NETCore" Version="0.8.0.1" />
@@ -29,6 +33,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="xunit" Version="2.4.1" />
<ProjectReference Include="..\lib\quantalib.csproj" /> <ProjectReference Include="..\lib\quantalib.csproj" />
</ItemGroup> </ItemGroup>
+74
View File
@@ -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);
}
}
}
+95 -11
View File
@@ -4,55 +4,139 @@ using System.Diagnostics.CodeAnalysis;
namespace QuanTAlib; namespace QuanTAlib;
/// <summary>
/// Contains unit tests for bar-based indicators in QuanTAlib.
/// </summary>
[SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")] [SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")]
public class BarIndicatorTests public class BarIndicatorTests
{ {
private readonly Random rnd; private readonly Random rnd;
private const int SeriesLen = 1000; private const int SeriesLen = 1000;
private const int Corrections = 100; private const int Corrections = 100;
/// <summary>
/// Initializes a new instance of the BarIndicatorTests class.
/// </summary>
public BarIndicatorTests() public BarIndicatorTests()
{ {
rnd = new Random((int)DateTime.Now.Ticks); 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
}; };
/// <summary>
/// Tests if the indicator produces consistent results when processing new and updated bars.
/// </summary>
/// <param name="indicator">The indicator to test.</param>
[Theory] [Theory]
[MemberData(nameof(GetIndicators))] [MemberData(nameof(GetIndicators))]
public void IndicatorIsNew(iTValue indicator) public void IndicatorIsNew(ITValue indicator)
{ {
var indicator1 = indicator; var indicator1 = indicator;
var indicator2 = indicator; var indicator2 = indicator;
MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; MethodInfo calcMethod = FindCalcMethod(indicator.GetType());
if (calcMethod == null) 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++) 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); TBar item1 = GenerateRandomBar(isNew: true);
calcMethod.Invoke(indicator1, new object[] { item1 }); InvokeCalc(indicator1, calcMethod, item1);
for (int j = 0; j < Corrections; j++) 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); item1 = GenerateRandomBar(isNew: false);
calcMethod.Invoke(indicator1, new object[] { item1 }); InvokeCalc(indicator1, calcMethod, item1);
} }
var item2 = new TBar(item1.Time, item1.Open, item1.High, item1.Low, item1.Close, item1.Volume, IsNew: true); 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); Assert.Equal(indicator1.Value, indicator2.Value);
} }
} }
/// <summary>
/// Finds the appropriate Calc method for the given indicator type.
/// </summary>
/// <param name="type">The type of the indicator.</param>
/// <returns>The MethodInfo for the Calc method.</returns>
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!;
}
/// <summary>
/// Invokes the Calc method on the given indicator with the provided input.
/// </summary>
/// <param name="indicator">The indicator instance.</param>
/// <param name="calcMethod">The Calc method to invoke.</param>
/// <param name="input">The input TBar.</param>
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}");
}
}
/// <summary>
/// Generates a random TBar for testing purposes.
/// </summary>
/// <param name="isNew">Indicates whether the generated bar should be marked as new.</param>
/// <returns>A randomly generated TBar.</returns>
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);
}
/// <summary>
/// Provides the list of indicators for parameterized tests.
/// </summary>
/// <returns>An enumerable of object arrays, each containing an indicator instance.</returns>
public static IEnumerable<object[]> GetIndicators() public static IEnumerable<object[]> GetIndicators()
{ {
return indicators.Select(indicator => new object[] { indicator }); return indicators.Select(indicator => new object[] { indicator });
+98 -49
View File
@@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis;
namespace QuanTAlib; namespace QuanTAlib;
[SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")] [SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")]
public class IndicatorTests public class IndicatorTests
{ {
private readonly Random rnd; private readonly Random rnd;
@@ -17,82 +16,132 @@ public class IndicatorTests
rnd = new Random((int)DateTime.Now.Ticks); 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 Ema(period: 10, useSma: true),
new Alma(period: 14, offset: 0.85, sigma: 6), new Alma(period: 14, offset: 0.85, sigma: 6),
new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman), new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman),
new Convolution(new double[] { 1.0, 2, 3, 2, 1 }), new Convolution(new[] { 1.0, 2, 3, 2, 1 }),
new Dema(period: 14), new Dema(period: 14),
new Dsma(period: 14), new Dsma(period: 14),
new Dwma(period: 14), new Dwma(period: 14),
new Epma(period: 14), new Epma(period: 14),
new Frama(period: 14), new Frama(period: 14),
new Fwma(period: 14), new Fwma(period: 14),
new Gma(period: 14), new Gma(period: 14),
new Hma(period: 14), new Hma(period: 14),
new Hwma(period: 14), new Hwma(period: 14),
new Kama(period: 14), new Kama(period: 14),
new Mama(fastLimit: 0.5, slowLimit: 0.05), new Mama(fastLimit: 0.5, slowLimit: 0.05),
new Mgdi(period: 14), new Mgdi(period: 14),
new Mma(period: 14), new Mma(period: 14),
new Qema(), new Qema(),
new Rema(period: 14), new Rema(period: 14),
new Rma(period: 14), new Rma(period: 14),
new Sinema(period: 14), new Sinema(period: 14),
new Sma(period: 14), new Sma(period: 14),
new Smma(period: 14), new Smma(period: 14),
new T3(period: 14), new T3(period: 14),
new Tema(period: 14), new Tema(period: 14),
new Trima(period: 14), new Trima(period: 14),
new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2), new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2),
new Wma(period: 14), new Wma(period: 14),
new Zlema(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 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] [Theory]
[MemberData(nameof(GetIndicators))] [MemberData(nameof(GetIndicators))]
public void IndicatorIsNew(iTValue indicator) public void IndicatorIsNew(ITValue indicator)
{ {
var indicator1 = indicator; var indicator1 = indicator;
var indicator2 = indicator; var indicator2 = indicator;
MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; MethodInfo calcMethod = FindCalcMethod(indicator.GetType());
if (calcMethod == null) 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++) for (int i = 0; i < SeriesLen; i++)
{ {
TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); 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++) for (int j = 0; j < Corrections; j++)
{ {
item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); 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); 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); 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<object[]> GetIndicators() public static IEnumerable<object[]> GetIndicators()
{ {
return indicators.Select(indicator => new object[] { indicator }); return indicators.Select(indicator => new object[] { indicator });
+9 -5
View File
@@ -12,7 +12,8 @@ public class SkenderTests
private readonly GbmFeed feed; private readonly GbmFeed feed;
private readonly Random rnd; private readonly Random rnd;
private readonly double range; private readonly double range;
private int period, iterations; private int period;
private readonly int iterations;
private readonly IEnumerable<Quote> quotes; private readonly IEnumerable<Quote> quotes;
@@ -288,7 +289,6 @@ public class SkenderTests
{ {
for (int run = 0; run < iterations; run++) for (int run = 0; run < iterations; run++)
{ {
//period = rnd.Next(50) + 5;
Mama ma = new(fastLimit: 0.5, slowLimit: 0.05); Mama ma = new(fastLimit: 0.5, slowLimit: 0.05);
TSeries QL = new(); TSeries QL = new();
foreach (TBar item in feed) foreach (TBar item in feed)
@@ -335,12 +335,16 @@ public class SkenderTests
TSeries QL = new(); TSeries QL = new();
foreach (TBar item in bars) { QL.Add(ma.Calc(item)); } foreach (TBar item in bars) { QL.Add(ma.Calc(item)); }
var SK = quotes.GetAtr(lookbackPeriods: period).Select(i => i.Atr.Null2NaN()!); var atrValues = quotes.GetAtr(lookbackPeriods: period).Select(i => i.Atr.Null2NaN()!);
Assert.Equal(QL.Length, QL.Length); const int AdditionalPeriods = 500;
<<<<<<< HEAD
for (int i = QL.Length - 1; i > period + 500; i--) 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);
} }
} }
} }
+145 -156
View File
@@ -1,162 +1,151 @@
using System; namespace QuanTAlib;
namespace QuanTAlib public class Afirma : AbstractBase
{ {
public class Afirma : AbstractBase public enum WindowType
{ {
Rectangular,
public enum WindowType Hanning1,
{ Hanning2,
Rectangular, Blackman,
Hanning1, BlackmanHarris
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;
}
} }
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;
}
} }
+1 -1
View File
@@ -25,7 +25,7 @@ public class Alma : AbstractBase
/// <param name="offset">Controls the smoothness and high-frequency filtering. Default is 0.85.</param> /// <param name="offset">Controls the smoothness and high-frequency filtering. Default is 0.85.</param>
/// <param name="sigma">Controls the shape of the Gaussian distribution. Default is 6.</param> /// <param name="sigma">Controls the shape of the Gaussian distribution. Default is 6.</param>
/// <exception cref="ArgumentException">Thrown when period is less than 1.</exception> /// <exception cref="ArgumentException">Thrown when period is less than 1.</exception>
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) if (period < 1)
{ {
+2 -2
View File
@@ -4,8 +4,8 @@ public class Convolution : AbstractBase
{ {
private readonly double[] _kernel; private readonly double[] _kernel;
private readonly int _kernelSize; private readonly int _kernelSize;
private CircularBuffer _buffer; private readonly CircularBuffer _buffer;
private double[] _normalizedKernel; private readonly double[] _normalizedKernel;
public Convolution(double[] kernel) public Convolution(double[] kernel)
{ {
+1 -1
View File
@@ -28,7 +28,7 @@ public class Dema : AbstractBase
private double _lastEma2, _p_lastEma2; private double _lastEma2, _p_lastEma2;
private double _k, _e, _p_e; private double _k, _e, _p_e;
public Dema(int period) : base() public Dema(int period)
{ {
if (period < 1) if (period < 1)
{ {
+6 -4
View File
@@ -27,10 +27,12 @@ public class Ema : AbstractBase
private readonly int _period; private readonly int _period;
private CircularBuffer _sma; private CircularBuffer _sma;
private double _lastEma, _p_lastEma; private double _lastEma, _p_lastEma;
private double _k, _e, _p_e; private double _e, _p_e;
private bool _isInit, _p_isInit, _useSma; 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) if (period < 1)
{ {
@@ -45,7 +47,7 @@ public class Ema : AbstractBase
Init(); Init();
} }
public Ema(double alpha) : base() public Ema(double alpha)
{ {
_k = alpha; _k = alpha;
_useSma = false; _useSma = false;
+80 -69
View File
@@ -1,99 +1,110 @@
using System; 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; if (period < 2)
private readonly double _fc; throw new ArgumentException("Period must be at least 2", nameof(period));
private CircularBuffer _buffer;
private double _lastFrama;
private double _prevLastFrama;
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) _prevLastFrama = _lastFrama;
throw new ArgumentException("Period must be at least 2", nameof(period)); _index++;
}
else
{
_lastFrama = _prevLastFrama;
}
}
_period = period; protected override double Calculation()
_fc = fc; {
_buffer = new CircularBuffer(period); ManageState(Input.IsNew);
WarmupPeriod = period;
_buffer.Add(Input.Value, Input.IsNew);
if (_buffer.Count < _period)
{
_lastFrama = _buffer.Average();
return _lastFrama;
} }
public override void Init() int half = _period / 2;
{ double hh = double.MinValue, ll = double.MaxValue;
base.Init(); double hh1 = double.MinValue, ll1 = double.MaxValue;
_buffer.Clear(); double hh2 = double.MinValue, ll2 = double.MaxValue;
_lastFrama = 0;
_prevLastFrama = 0;
}
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; hh1 = Math.Max(hh1, price);
_index++; ll1 = Math.Min(ll1, price);
} }
else else
{ {
_lastFrama = _prevLastFrama; hh2 = Math.Max(hh2, price);
ll2 = Math.Min(ll2, price);
} }
} }
<<<<<<< HEAD
protected override double Calculation() protected override double Calculation()
{ {
ManageState(Input.IsNew); ManageState(Input.IsNew);
_buffer.Add(Input.Value, 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) double d = (Math.Log(n2 + double.Epsilon) - Math.Log(n1 + double.Epsilon)) / Math.Log(2);
{
_lastFrama = _buffer.Average();
return _lastFrama;
}
int half = _period / 2; double alpha = Math.Exp(-4.6 * (d - 1));
double hh = double.MinValue, ll = double.MaxValue; alpha = Math.Max(Math.Min(alpha, 1), 0.01); // Ensure alpha is between 0.01 and 1
double hh1 = double.MinValue, ll1 = double.MaxValue;
double hh2 = double.MinValue, ll2 = double.MaxValue;
for (int i = 0; i < _period; i++) _lastFrama = alpha * (Input.Value - _lastFrama) + _lastFrama;
{
double price = _buffer[i];
hh = Math.Max(hh, price);
ll = Math.Min(ll, price);
if (i < half) IsHot = _index >= WarmupPeriod;
{ return _lastFrama;
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; protected override double GetLastValid()
double n2 = (hh1 - ll1 + hh2 - ll2) / (_period / 2); {
return _lastFrama;
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;
}
} }
} }
+4 -6
View File
@@ -1,7 +1,7 @@
//not working yet //not working yet
//TODO consistency test //TODO consistency test
using QuanTAlib; namespace QuanTAlib;
public class Htit : AbstractBase public class Htit : AbstractBase
{ {
@@ -21,7 +21,7 @@ public class Htit : AbstractBase
private double _lastPd = 0; private double _lastPd = 0;
private double _p_lastPd = 0; private double _p_lastPd = 0;
public Htit() : base() public Htit()
{ {
Name = "Htit"; Name = "Htit";
WarmupPeriod = 12; WarmupPeriod = 12;
@@ -138,9 +138,7 @@ public class Htit : AbstractBase
{ {
return ((4 * _itBuffer[0]) + (3 * _itBuffer[1]) + (2 * _itBuffer[2]) + _itBuffer[3]) / 10; return ((4 * _itBuffer[0]) + (3 * _itBuffer[1]) + (2 * _itBuffer[2]) + _itBuffer[3]) / 10;
} }
else
{ return pr;
return pr;
}
} }
} }
+1 -1
View File
@@ -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) if (period < 1)
{ {
+12 -6
View File
@@ -1,20 +1,20 @@
using QuanTAlib; namespace QuanTAlib;
//TODO consistency test //TODO consistency test
public class Jma : AbstractBase public class Jma : AbstractBase
{ {
public readonly int Period; public readonly int Period;
private readonly double _phase; private readonly double _phase;
private readonly int _vshort, _vlong; private readonly int _vshort, _vlong;
private CircularBuffer _values; private readonly CircularBuffer _values;
private CircularBuffer _voltyShort; private readonly CircularBuffer _voltyShort;
private CircularBuffer _vsumBuff; private readonly CircularBuffer _vsumBuff;
private CircularBuffer _avoltyBuff; private readonly CircularBuffer _avoltyBuff;
private double _beta, _len1, _pow1; private double _beta, _len1, _pow1;
private double _upperBand, _lowerBand, _prevMa1, _prevDet0, _prevDet1, _prevJma; private double _upperBand, _lowerBand, _prevMa1, _prevDet0, _prevDet1, _prevJma;
private double _p_UpperBand, _p_LowerBand, _p_prevMa1, _p_prevDet0, _p_prevDet1, _p_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) if (period < 1)
{ {
@@ -35,6 +35,12 @@ public class Jma : AbstractBase
Init(); 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() public override void Init()
{ {
_upperBand = _lowerBand = _prevMa1 = _prevDet0 = _prevDet1 = _prevJma = 0.0; _upperBand = _lowerBand = _prevMa1 = _prevDet0 = _prevDet1 = _prevJma = 0.0;
+1 -3
View File
@@ -1,5 +1,3 @@
using System;
namespace QuanTAlib; namespace QuanTAlib;
public class Kama : AbstractBase public class Kama : AbstractBase
@@ -9,7 +7,7 @@ public class Kama : AbstractBase
private CircularBuffer? _buffer; private CircularBuffer? _buffer;
private double _lastKama, _p_lastKama; 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) if (period < 1)
{ {
+1 -1
View File
@@ -10,7 +10,7 @@ public class Ltma : AbstractBase
public double Gamma => _gamma; public double Gamma => _gamma;
public Ltma(double gamma = 0.1) : base() public Ltma(double gamma = 0.1)
{ {
if (gamma < 0 || gamma > 1) if (gamma < 0 || gamma > 1)
throw new ArgumentOutOfRangeException(nameof(gamma), "Gamma must be between 0 and 1."); throw new ArgumentOutOfRangeException(nameof(gamma), "Gamma must be between 0 and 1.");
+3 -2
View File
@@ -8,12 +8,13 @@ public class Maaf : AbstractBase
{ {
private readonly CircularBuffer _priceBuffer; private readonly CircularBuffer _priceBuffer;
private readonly CircularBuffer _smoothBuffer; private readonly CircularBuffer _smoothBuffer;
private double _prevFilter, _prevValue2, _threshold; private double _prevFilter, _prevValue2;
private readonly double _threshold;
private double _p_prevFilter, _p_prevValue2; private double _p_prevFilter, _p_prevValue2;
private readonly int _period; 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; _period = Period;
_threshold = Threshold; _threshold = Threshold;
+3 -4
View File
@@ -1,17 +1,16 @@
using QuanTAlib; namespace QuanTAlib;
using System;
public class Mama : AbstractBase public class Mama : AbstractBase
{ {
private readonly double _fastLimit, _slowLimit; 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 _mama, _fama;
private double _prevMama, _prevFama, _sumPr; private double _prevMama, _prevFama, _sumPr;
private double _p_prevMama, _p_prevFama, _p_sumPr; private double _p_prevMama, _p_prevFama, _p_sumPr;
public TValue Fama { get; private set; } 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(); Fama = new TValue();
Name = $"Mama({_fastLimit:F2}, {_slowLimit:F2})"; Name = $"Mama({_fastLimit:F2}, {_slowLimit:F2})";
+1 -1
View File
@@ -5,7 +5,7 @@ public class Mgdi : AbstractBase
private readonly int _period; private readonly int _period;
private readonly double _kFactor; private readonly double _kFactor;
private double _prevMd, _p_prevMd; 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) if (period <= 0)
{ {
+65 -69
View File
@@ -1,78 +1,74 @@
using System; namespace QuanTAlib;
using System.Linq;
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; if (period < 2)
private readonly CircularBuffer _buffer;
private double _lastMma;
public Mma(int period) : base()
{ {
if (period < 2) throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 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();
} }
_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"); _index++;
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;
} }
} }
}
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;
}
}
+4
View File
@@ -6,7 +6,11 @@ public class Qema : AbstractBase
private readonly Ema _ema1, _ema2, _ema3, _ema4; private readonly Ema _ema1, _ema2, _ema3, _ema4;
private double _lastQema, _p_lastQema; 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) : 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) if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0)
{ {
+6 -2
View File
@@ -12,7 +12,7 @@ public class Rema : AbstractBase
public int Period => _period; public int Period => _period;
public double Lambda => _lambda; public double Lambda => _lambda;
public Rema(int period, double lambda = 0.5) : base() public Rema(int period, double lambda = 0.5)
{ {
if (period < 1) if (period < 1)
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 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; WarmupPeriod = period;
Init(); 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() public override void Init()
{ {
base.Init(); base.Init();
+80 -1
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
<<<<<<< HEAD
namespace QuanTAlib namespace QuanTAlib
{ {
@@ -29,6 +30,80 @@ namespace QuanTAlib
var pubEvent = source.GetType().GetEvent("Pub"); var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); 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() public override void Init()
{ {
@@ -78,4 +153,8 @@ namespace QuanTAlib
return rma; return rma;
} }
} }
} <<<<<<< HEAD
}
=======
}
>>>>>>> dev
+1 -1
View File
@@ -6,7 +6,7 @@ public class Sma : AbstractBase
// inherited _value // inherited _value
private readonly CircularBuffer _buffer; private readonly CircularBuffer _buffer;
public Sma(int period) : base() public Sma(int period)
{ {
if (period < 1) if (period < 1)
{ {
+1 -1
View File
@@ -8,7 +8,7 @@ public class Smma : AbstractBase
private CircularBuffer? _buffer; private CircularBuffer? _buffer;
private double _lastSmma, _p_lastSmma; private double _lastSmma, _p_lastSmma;
public Smma(int period) : base() public Smma(int period)
{ {
if (period < 1) if (period < 1)
{ {
+1 -1
View File
@@ -8,7 +8,7 @@ public class Tema : AbstractBase
private double _lastEma3, _p_lastEma3; private double _lastEma3, _p_lastEma3;
private double _k, _e, _p_e; private double _k, _e, _p_e;
public Tema(int period) : base() public Tema(int period)
{ {
if (period < 1) if (period < 1)
{ {
+1 -8
View File
@@ -30,14 +30,7 @@ public class Trima : AbstractBase
for (int i = 0; i < period; i++) for (int i = 0; i < period; i++)
{ {
if (i < halfPeriod) kernel[i] = i < halfPeriod ? i + 1 : period - i;
{
kernel[i] = i + 1;
}
else
{
kernel[i] = period - i;
}
weightSum += kernel[i]; weightSum += kernel[i];
} }
+3 -3
View File
@@ -7,11 +7,11 @@ public class Zlema : AbstractBase
{ {
private readonly int _period; private readonly int _period;
private CircularBuffer? _buffer; private CircularBuffer? _buffer;
private double _alpha; private readonly double _alpha;
private int _lag; private readonly int _lag;
private double _lastZLEMA, _p_lastZLEMA; private double _lastZLEMA, _p_lastZLEMA;
public Zlema(int period) : base() public Zlema(int period)
{ {
if (period < 1) if (period < 1)
{ {
+72 -13
View File
@@ -8,13 +8,16 @@ namespace QuanTAlib;
/// and methods used by inheriting indicator types. It handles the basic flow of /// and methods used by inheriting indicator types. It handles the basic flow of
/// receiving data, performing calculations, and publishing results. /// receiving data, performing calculations, and publishing results.
/// </remarks> /// </remarks>
public abstract class AbstractBase : iTValue public abstract class AbstractBase : ITValue
{ {
public DateTime Time { get; set; } public DateTime Time { get; set; }
public double Value { get; set; } public double Value { get; set; }
public bool IsNew { get; set; } public bool IsNew { get; set; }
public bool IsHot { get; set; } public bool IsHot { get; set; }
public TValue Input { 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 String Name { get; set; } = "";
public int WarmupPeriod { get; set; } public int WarmupPeriod { get; set; }
public TValue Tick => new(Time, Value, IsNew, IsHot); public TValue Tick => new(Time, Value, IsNew, IsHot);
@@ -34,6 +37,11 @@ public abstract class AbstractBase : iTValue
/// <param name="args">The argument containing the new data point.</param> /// <param name="args">The argument containing the new data point.</param>
public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick); 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);
/// <summary> /// <summary>
/// Initializes the indicator's state. /// Initializes the indicator's state.
/// </summary> /// </summary>
@@ -43,24 +51,75 @@ public abstract class AbstractBase : iTValue
_lastValidValue = 0; _lastValidValue = 0;
} }
/// <summary>
/// Calculates the indicator value based on the input.
/// </summary>
/// <param name="input">The input value for the calculation.</param>
/// <returns>A TValue representing the calculated indicator value.</returns>
/// <remarks>
/// 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.
/// </remarks>
public virtual TValue Calc(TValue input) public virtual TValue Calc(TValue input)
{ {
Input = 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);
}
/// <summary>
/// Handles error calculations and invalid input values.
/// </summary>
/// <param name="value">The primary input value to check.</param>
/// <param name="time">The timestamp of the input.</param>
/// <param name="isNew">Indicates if the input is new.</param>
/// <returns>A TValue object with the calculated or last valid value.</returns>
/// <remarks>
/// 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.
/// </remarks>
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(); 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));
}
/// <summary>
/// Handles error calculations for inputs with two values.
/// </summary>
/// <param name="value1">The first input value to check.</param>
/// <param name="value2">The second input value to check.</param>
/// <param name="time">The timestamp of the input.</param>
/// <param name="isNew">Indicates if the input is new.</param>
/// <returns>A TValue object with the calculated or last valid value.</returns>
/// <remarks>
/// 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.
/// </remarks>
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));
} }
/// <summary> /// <summary>
+5
View File
@@ -221,11 +221,16 @@ public class CircularBuffer : IEnumerable<double>
if (_start + _size <= Capacity) if (_start + _size <= Capacity)
{ {
return new ReadOnlySpan<double>(_buffer, _start, _size); return new ReadOnlySpan<double>(_buffer, _start, _size);
<<<<<<< HEAD
} }
else else
{ {
return new ReadOnlySpan<double>(ToArray()); return new ReadOnlySpan<double>(ToArray());
=======
>>>>>>> dev
} }
return new ReadOnlySpan<double>(ToArray());
} }
/// <summary> /// <summary>
+1 -1
View File
@@ -9,7 +9,7 @@ public static class Formatters
const string pad = "18"; const string pad = "18";
public static void Initialize() public static void Initialize()
{ {
Formatter.Register<iTValue>((tick, writer) => Formatter.Register<ITValue>((tick, writer) =>
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("<table style='border-collapse: collapse; text-align: left;'><tr>"); sb.Append("<table style='border-collapse: collapse; text-align: left;'><tr>");
+2 -2
View File
@@ -1,6 +1,6 @@
namespace QuanTAlib; namespace QuanTAlib;
public interface iTBar public interface ITBar
{ {
DateTime Time { get; } DateTime Time { get; }
double Open { get; } double Open { get; }
@@ -11,7 +11,7 @@ public interface iTBar
bool IsNew { get; } 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 DateTime Time { get; init; } = Time;
public double Open { get; init; } = Open; public double Open { get; init; } = Open;
+6 -5
View File
@@ -1,6 +1,6 @@
namespace QuanTAlib; namespace QuanTAlib;
public interface iTValue public interface ITValue
{ {
DateTime Time { get; } DateTime Time { get; }
double Value { get; } double Value { get; }
@@ -8,7 +8,7 @@ public interface iTValue
bool IsHot { get; } 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 DateTime Time { get; init; } = Time;
public double Value { get; init; } = Value; public double Value { get; init; } = Value;
@@ -52,12 +52,13 @@ public class TSeries : List<TValue>
var pubEvent = source.GetType().GetEvent("Pub"); var pubEvent = source.GetType().GetEvent("Pub");
if (pubEvent != null) if (pubEvent != null)
{ {
/*
var nameProperty = source.GetType().GetProperty("Name"); var nameProperty = source.GetType().GetProperty("Name");
if (nameProperty != null) { if (nameProperty != null)
{
Name = nameProperty.GetValue(nameProperty)?.ToString()!; Name = nameProperty.GetValue(nameProperty)?.ToString()!;
} }
*/
pubEvent.AddEventHandler(source, new ValueSignal(Sub)); pubEvent.AddEventHandler(source, new ValueSignal(Sub));
} }
} }
+4
View File
@@ -8,7 +8,11 @@ public class GbmFeed : TBarSeries
private readonly Random _random; private readonly Random _random;
private double _lastClose, _lastHigh, _lastLow; 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) : base()
=======
public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2)
>>>>>>> dev
{ {
_lastClose = _lastHigh = _lastLow = initialPrice; _lastClose = _lastHigh = _lastLow = initialPrice;
_mu = mu; _mu = mu;
+1 -1
View File
@@ -16,7 +16,7 @@ public class Entropy : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is less than 2. /// Thrown when the period is less than 2.
/// </exception> /// </exception>
public Entropy(int period) : base() public Entropy(int period)
{ {
if (period < 2) if (period < 2)
{ {
+1 -1
View File
@@ -16,7 +16,7 @@ public class Kurtosis : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is less than 4. /// Thrown when the period is less than 4.
/// </exception> /// </exception>
public Kurtosis(int period) : base() public Kurtosis(int period)
{ {
if (period < 4) if (period < 4)
{ {
+2 -2
View File
@@ -20,7 +20,7 @@ public class Max : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is less than 1 or decay is negative. /// Thrown when the period is less than 1 or decay is negative.
/// </exception> /// </exception>
public Max(int period, double decay = 0) : base() public Max(int period, double decay = 0)
{ {
if (period < 1) if (period < 1)
{ {
@@ -105,7 +105,7 @@ public class Max : AbstractBase
} }
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period); 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()); _currentMax = Math.Min(_currentMax, _buffer.Max());
IsHot = true; IsHot = true;
+2 -11
View File
@@ -16,7 +16,7 @@ public class Median : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is less than 1. /// Thrown when the period is less than 1.
/// </exception> /// </exception>
public Median(int period) : base() public Median(int period)
{ {
if (period < 1) if (period < 1)
{ {
@@ -76,16 +76,7 @@ public class Median : AbstractBase
Array.Sort(sortedValues); Array.Sort(sortedValues);
int middleIndex = sortedValues.Length / 2; int middleIndex = sortedValues.Length / 2;
if (sortedValues.Length % 2 == 0) median = (sortedValues.Length % 2 == 0) ? (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0 : sortedValues[middleIndex];
{
// 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];
}
} }
else else
{ {
+5 -1
View File
@@ -26,7 +26,11 @@ public class Min : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 1 or decay is negative. /// Thrown when period is less than 1 or decay is negative.
/// </exception> /// </exception>
<<<<<<< HEAD
public Min(int period, double decay = 0) : base() public Min(int period, double decay = 0) : base()
=======
public Min(int period, double decay = 0)
>>>>>>> dev
{ {
if (period < 1) if (period < 1)
{ {
@@ -109,7 +113,7 @@ public class Min : AbstractBase
} }
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period); 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()); _currentMin = Math.Max(_currentMin, _buffer.Min());
IsHot = true; IsHot = true;
+4
View File
@@ -21,7 +21,11 @@ public class Mode : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 1. /// Thrown when period is less than 1.
/// </exception> /// </exception>
<<<<<<< HEAD
public Mode(int period) : base() public Mode(int period) : base()
=======
public Mode(int period)
>>>>>>> dev
{ {
if (period < 1) if (period < 1)
{ {
+4
View File
@@ -24,7 +24,11 @@ public class Percentile : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2 or percent is not between 0 and 100. /// Thrown when period is less than 2 or percent is not between 0 and 100.
/// </exception> /// </exception>
<<<<<<< HEAD
public Percentile(int period, double percent) : base() public Percentile(int period, double percent) : base()
=======
public Percentile(int period, double percent)
>>>>>>> dev
{ {
if (period < 2) if (period < 2)
{ {
+4
View File
@@ -22,7 +22,11 @@ public class Skew : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 3. /// Thrown when period is less than 3.
/// </exception> /// </exception>
<<<<<<< HEAD
public Skew(int period) : base() public Skew(int period) : base()
=======
public Skew(int period)
>>>>>>> dev
{ {
if (period < 3) if (period < 3)
{ {
+4
View File
@@ -26,7 +26,11 @@ public class Stddev : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2. /// Thrown when period is less than 2.
/// </exception> /// </exception>
<<<<<<< HEAD
public Stddev(int period, bool isPopulation = false) : base() public Stddev(int period, bool isPopulation = false) : base()
=======
public Stddev(int period, bool isPopulation = false)
>>>>>>> dev
{ {
if (period < 2) if (period < 2)
{ {
+4
View File
@@ -26,7 +26,11 @@ public class Variance : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2. /// Thrown when period is less than 2.
/// </exception> /// </exception>
<<<<<<< HEAD
public Variance(int period, bool isPopulation = false) : base() public Variance(int period, bool isPopulation = false) : base()
=======
public Variance(int period, bool isPopulation = false)
>>>>>>> dev
{ {
if (period < 2) if (period < 2)
{ {
+4
View File
@@ -21,7 +21,11 @@ public class Zscore : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2. /// Thrown when period is less than 2.
/// </exception> /// </exception>
<<<<<<< HEAD
public Zscore(int period) : base() public Zscore(int period) : base()
=======
public Zscore(int period)
>>>>>>> dev
{ {
if (period < 2) if (period < 2)
{ {
+16 -4
View File
@@ -8,7 +8,11 @@ namespace QuanTAlib;
/// of the true range. The true range is the greatest of: current high - current low, /// 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. /// absolute value of current high - previous close, or absolute value of current low - previous close.
/// </remarks> /// </remarks>
<<<<<<< HEAD
public class Atr : AbstractBarBase public class Atr : AbstractBarBase
=======
public class Atr : AbstractBase
>>>>>>> dev
{ {
private readonly Ema _ma; private readonly Ema _ma;
private double _prevClose, _p_prevClose; private double _prevClose, _p_prevClose;
@@ -82,23 +86,31 @@ public class Atr : AbstractBarBase
/// </remarks> /// </remarks>
protected override double Calculation() protected override double Calculation()
{ {
<<<<<<< HEAD
ManageState(Input.IsNew); ManageState(Input.IsNew);
=======
ManageState(BarInput.IsNew);
>>>>>>> dev
double trueRange = Math.Max( double trueRange = Math.Max(
Math.Max( Math.Max(
Input.High - Input.Low, BarInput.High - BarInput.Low,
Math.Abs(Input.High - _prevClose) Math.Abs(BarInput.High - _prevClose)
), ),
Math.Abs(Input.Low - _prevClose) Math.Abs(BarInput.Low - _prevClose)
); );
if (_index < 2) if (_index < 2)
{ {
<<<<<<< HEAD
trueRange = Input.High - Input.Low; trueRange = Input.High - Input.Low;
=======
trueRange = BarInput.High - BarInput.Low;
>>>>>>> dev
} }
TValue emaTrueRange = _ma.Calc(new TValue(Input.Time, trueRange, Input.IsNew)); TValue emaTrueRange = _ma.Calc(new TValue(Input.Time, trueRange, Input.IsNew));
IsHot = _ma.IsHot; IsHot = _ma.IsHot;
_prevClose = Input.Close; _prevClose = BarInput.Close;
return emaTrueRange.Value; return emaTrueRange.Value;
} }
+4
View File
@@ -25,7 +25,11 @@ public class Historical : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2. /// Thrown when period is less than 2.
/// </exception> /// </exception>
<<<<<<< HEAD
public Historical(int period, bool isAnnualized = true) : base() public Historical(int period, bool isAnnualized = true) : base()
=======
public Historical(int period, bool isAnnualized = true)
>>>>>>> dev
{ {
if (period < 2) if (period < 2)
{ {
+4
View File
@@ -25,7 +25,11 @@ public class Realized : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2. /// Thrown when period is less than 2.
/// </exception> /// </exception>
<<<<<<< HEAD
public Realized(int period, bool isAnnualized = true) : base() public Realized(int period, bool isAnnualized = true) : base()
=======
public Realized(int period, bool isAnnualized = true)
>>>>>>> dev
{ {
if (period < 2) if (period < 2)
{ {
+10 -2
View File
@@ -16,8 +16,8 @@ namespace QuanTAlib;
public class Rvi : AbstractBase public class Rvi : AbstractBase
{ {
private readonly int Period; private readonly int Period;
private Stddev _upStdDev, _downStdDev; private readonly Stddev _upStdDev, _downStdDev;
private Sma _upSma, _downSma; private readonly Sma _upSma, _downSma;
private double _previousClose; private double _previousClose;
/// <summary> /// <summary>
@@ -27,7 +27,11 @@ public class Rvi : AbstractBase
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2. /// Thrown when period is less than 2.
/// </exception> /// </exception>
<<<<<<< HEAD
public Rvi(int period) : base() public Rvi(int period) : base()
=======
public Rvi(int period)
>>>>>>> dev
{ {
if (period < 2) if (period < 2)
{ {
@@ -105,6 +109,7 @@ public class Rvi : AbstractBase
_downSma.Calc(_downStdDev.Calc(new TValue(Input.Time, downMove, Input.IsNew))); _downSma.Calc(_downStdDev.Calc(new TValue(Input.Time, downMove, Input.IsNew)));
double rvi; double rvi;
<<<<<<< HEAD
if (_upSma.Value + _downSma.Value != 0) if (_upSma.Value + _downSma.Value != 0)
{ {
rvi = 100 * _upSma.Value / (_upSma.Value + _downSma.Value); rvi = 100 * _upSma.Value / (_upSma.Value + _downSma.Value);
@@ -113,6 +118,9 @@ public class Rvi : AbstractBase
{ {
rvi = 0; rvi = 0;
} }
=======
rvi = (_upSma.Value + _downSma.Value != 0) ? 100 * _upSma.Value / (_upSma.Value + _downSma.Value) : 0;
>>>>>>> dev
_previousClose = close; _previousClose = close;
IsHot = _index >= WarmupPeriod; IsHot = _index >= WarmupPeriod;
+13 -9
View File
@@ -10,15 +10,19 @@ QuanTAlib.Formatters.Initialize();
#!csharp #!csharp
Sma ma1 = new(6); TSeries input = new();
Gmean ma2 = new (6); Sma ma1 = new (6);
Hmean ma3 = 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}; Random random = new Random();
for (int i=0; i<input.Length; i++) {
double out1 = ma1.Calc(input[i]);
double out2 = ma2.Calc(input[i]);
double out3 = ma3.Calc(input[i]);
Console.WriteLine($"{input[i]:F2}\t {out1:F2}\t {out2:F2}\t {out3:F2}"); for (int i = 0; i < 100; i++) {
double randomValue = random.NextDouble() * 100;
input.Add(randomValue);
ma1.Calc(randomValue);
} }
#!csharp
display(ma1);
display(ma2);
+1 -1
View File
@@ -7,7 +7,7 @@ public class AtrIndicator : IndicatorBarBase
public int Period { get; set; } = 20; public int Period { get; set; } = 20;
private Atr? atr; private Atr? atr;
protected override AbstractBarBase QuanTAlib => atr!; protected override AbstractBase QuanTAlib => atr!;
public override string ShortName => $"ATR {Period}"; public override string ShortName => $"ATR {Period}";
public AtrIndicator() public AtrIndicator()
{ {
+18 -19
View File
@@ -19,7 +19,7 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator
// LineSeries.LineSeries(string, Color, int, LineStyle)' // LineSeries.LineSeries(string, Color, int, LineStyle)'
protected LineSeries? Series; protected LineSeries? Series;
protected abstract AbstractBarBase QuanTAlib { get; } protected abstract AbstractBase QuanTAlib { get; }
int IWatchlistIndicator.MinHistoryDepths => 0; int IWatchlistIndicator.MinHistoryDepths => 0;
@@ -80,7 +80,7 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator
int barX = (int)converter.GetChartX(Time(i)); int barX = (int)converter.GetChartX(Time(i));
int barY = (int)converter.GetChartY(Series![i]); int barY = (int)converter.GetChartY(Series![i]);
int halfBarWidth = CurrentChart.BarsWidth / 2; int halfBarWidth = CurrentChart.BarsWidth / 2;
Point point = new Point(barX + halfBarWidth, barY); Point point = new(barX + halfBarWidth, barY);
allPoints.Add(point); allPoints.Add(point);
} }
@@ -94,22 +94,21 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator
{ {
if (allPoints.Count < 2) { return; } if (allPoints.Count < 2) { return; }
using (Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }) using Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) };
using (Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }) 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);
}
// Draw the cold part // Draw the hot part
if (ShowColdValues && hotCount < allPoints.Count) if (hotCount > 0)
{ {
var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray(); var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray();
gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)0.1); 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) 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) 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); SizeF textSize = gr.MeasureString(text, font);
RectangleF textRect = new RectangleF(clientRect.Left + 5, RectangleF textRect = new(clientRect.Left + 5,
clientRect.Bottom - textSize.Height - 10, clientRect.Bottom - textSize.Height - 10,
textSize.Width + 10, textSize.Height + 10); textSize.Width + 10, textSize.Height + 10);
gr.FillRectangle(SystemBrushes.ControlDarkDark, textRect); gr.FillRectangle(SystemBrushes.ControlDarkDark, textRect);