mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-08 05:57:43 +00:00
Refactor tests and project files for improved consistency and clarity
This commit is contained in:
@@ -58,6 +58,27 @@ jobs:
|
||||
- name: Build
|
||||
run: dotnet build --no-restore --configuration Debug
|
||||
|
||||
- name: Collect SARIF Files
|
||||
run: |
|
||||
mkdir -p sarif-reports
|
||||
find . -name "roslyn.sarif" -type f -exec cp {} sarif-reports/ \;
|
||||
# Rename SARIF files to include project name
|
||||
cd sarif-reports
|
||||
count=1
|
||||
for file in roslyn.sarif*; do
|
||||
if [ -f "$file" ]; then
|
||||
mv "$file" "roslyn_${count}.sarif"
|
||||
count=$((count+1))
|
||||
fi
|
||||
done
|
||||
ls -la
|
||||
|
||||
- name: Upload SARIF Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sarif-reports
|
||||
path: sarif-reports/
|
||||
|
||||
- name: Run Tests
|
||||
run: |
|
||||
dotnet test --no-build --configuration Debug \
|
||||
@@ -198,9 +219,9 @@ jobs:
|
||||
args: --coverage-dir,.qodana/code-coverage
|
||||
|
||||
# ==============================================================================
|
||||
# 5. Codacy Upload
|
||||
# 5. Codacy Coverage Upload
|
||||
# ==============================================================================
|
||||
Codacy_Upload:
|
||||
Codacy_Coverage_Upload:
|
||||
needs: Build_Test_Coverage
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -216,7 +237,7 @@ jobs:
|
||||
name: coverage-reports
|
||||
path: coverage
|
||||
|
||||
- name: Upload to Codacy
|
||||
- name: Upload Coverage to Codacy
|
||||
env:
|
||||
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||
run: |
|
||||
@@ -239,6 +260,54 @@ jobs:
|
||||
./codacy.sh final
|
||||
continue-on-error: true
|
||||
|
||||
# ==============================================================================
|
||||
# 6. Codacy SARIF Upload
|
||||
# ==============================================================================
|
||||
Codacy_SARIF_Upload:
|
||||
needs: Build_Test_Coverage
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download SARIF Reports
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: sarif-reports
|
||||
path: sarif-reports
|
||||
|
||||
- name: Upload SARIF to Codacy
|
||||
env:
|
||||
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$CODACY_PROJECT_TOKEN" ]; then
|
||||
echo "CODACY_PROJECT_TOKEN is not set. Skipping upload."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Installing Codacy Analysis CLI..."
|
||||
curl -L https://github.com/codacy/codacy-analysis-cli/releases/latest/download/codacy-analysis-cli-linux -o codacy-analysis-cli
|
||||
chmod +x codacy-analysis-cli
|
||||
|
||||
# Upload each SARIF file
|
||||
for file in sarif-reports/*.sarif; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "Uploading SARIF: $file"
|
||||
./codacy-analysis-cli upload-results \
|
||||
--provider gh \
|
||||
--owner mihakralj \
|
||||
--repository QuanTAlib \
|
||||
--commit ${{ github.sha }} \
|
||||
--upload-batch-size 50000 \
|
||||
--tool roslyn \
|
||||
--results-file "$file" || echo "Warning: Failed to upload $file"
|
||||
fi
|
||||
done
|
||||
continue-on-error: true
|
||||
|
||||
# ==============================================================================
|
||||
# 6. Snyk Scan
|
||||
# ==============================================================================
|
||||
@@ -307,7 +376,7 @@ jobs:
|
||||
# 8. Publish Package
|
||||
# ==============================================================================
|
||||
Publish_Package:
|
||||
needs: [Build_Test_Coverage, Sonar_Analysis, Qodana_Scan, Codacy_Upload, Snyk_Scan, CodeQL_Analysis]
|
||||
needs: [Build_Test_Coverage, Sonar_Analysis, Qodana_Scan, Codacy_Coverage_Upload, Codacy_SARIF_Upload, Snyk_Scan, CodeQL_Analysis]
|
||||
if: >-
|
||||
(github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || endsWith(github.ref, '-dev'))) ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
|
||||
@@ -416,3 +416,7 @@ ilspy/
|
||||
|
||||
# Ignore dotnet install script
|
||||
dotnet-install.sh
|
||||
|
||||
# Roslyn SARIF files (generated during build and uploaded to Codacy)
|
||||
**/roslyn.sarif
|
||||
roslyn.sarif
|
||||
|
||||
+20
-1
@@ -26,6 +26,8 @@
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<TreatWarningsAsErrors Condition="'$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
|
||||
<ErrorLog>$(MSBuildProjectDirectory)/roslyn.sarif</ErrorLog>
|
||||
<ErrorLogFormat>SARIF2.1</ErrorLogFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
@@ -51,11 +53,28 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);S1144;S1944;S2053;S2222;S2245;S2259;S2583;S2589;S3329;S3655;S3776;S3900;S3949;S3966;S4158;S4347;S5773;S6781;MA0048;MA0051</NoWarn>
|
||||
<NoWarn>$(NoWarn);S1144;S1944;S2053;S2245;S2259;S2583;S2589;S3329;S3655;S3776;S3949;S3966;S4158;S4347;S5773;S6781;MA0048;MA0051</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
|
||||
<PackageReference Include="GitVersion.MsBuild" Version="6.5.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4" />
|
||||
<PackageReference Include="Roslynator.Analyzers" Version="4.12.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.183">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.*">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(IsLocalBuild)' == 'true' AND $([MSBuild]::IsOSPlatform('Windows'))">
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
Determining projects to restore...
|
||||
Restored C:\github\quantalib\quantower\Quantower.Tests.csproj (in 955 ms).
|
||||
Restored c:\github\quantalib\quantower\Statistics.csproj (in 955 ms).
|
||||
Restored c:\github\quantalib\quantower\Momentum.csproj (in 955 ms).
|
||||
Restored C:\github\quantalib\quantower\Trends.csproj (in 955 ms).
|
||||
2 of 6 projects are up-to-date for restore.
|
||||
Statistics -> C:\github\quantalib\quantower\bin\Debug\Statistics.dll
|
||||
quantalib -> C:\github\quantalib\lib\bin\Debug\net10.0\QuanTAlib.dll
|
||||
Momentum -> C:\github\quantalib\quantower\bin\Debug\Momentum.dll
|
||||
c:\github\quantalib\lib\trends\conv\Conv.Quantower.cs(50,23): warning S3928: The parameter name 'WeightsInput' is not declared in the argument list. (https://rules.sonarsource.com/csharp/RSPEC-3928) [c:\github\quantalib\quantower\Trends.csproj]
|
||||
c:\github\quantalib\lib\trends\conv\Conv.Quantower.cs(50,72): warning MA0015: 'WeightsInput' is not a valid parameter name (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0015.md) [c:\github\quantalib\quantower\Trends.csproj]
|
||||
Trends -> c:\github\quantalib\quantower\bin\Debug\Trends.dll
|
||||
c:\github\quantalib\lib\trends\htit\Htit.Quantower.Tests.cs(16,9): warning MA0074: Use an overload of 'StartsWith' that has a StringComparison parameter (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0074.md) [c:\github\quantalib\quantower\Quantower.Tests.csproj]
|
||||
c:\github\quantalib\lib\trends\conv\Conv.Quantower.cs(50,23): warning S3928: The parameter name 'WeightsInput' is not declared in the argument list. (https://rules.sonarsource.com/csharp/RSPEC-3928) [c:\github\quantalib\quantower\Quantower.Tests.csproj]
|
||||
c:\github\quantalib\lib\trends\conv\Conv.Quantower.cs(50,72): warning MA0015: 'WeightsInput' is not a valid parameter name (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0015.md) [c:\github\quantalib\quantower\Quantower.Tests.csproj]
|
||||
Quantower.Tests -> C:\github\quantalib\quantower\bin\Debug\Quantower.Tests.dll
|
||||
c:\github\quantalib\lib\momentum\bop\Bop.Validation.Tests.cs(65,28): warning MA0005: Use Array.Empty<T>() (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0005.md) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
c:\github\quantalib\lib\feeds\gbm\ValidationHelper.cs(52,62): warning MA0016: Prefer using collection abstraction instead of implementation (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0016.md) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
c:\github\quantalib\lib\momentum\ao\Ao.Validation.Tests.cs(72,28): warning MA0005: Use Array.Empty<T>() (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0005.md) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
c:\github\quantalib\lib\momentum\rsi\Rsi.Tests.cs(74,24): warning S6562: Provide the "DateTimeKind" when creating this object. (https://rules.sonarsource.com/csharp/RSPEC-6562) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
c:\github\quantalib\lib\momentum\rsi\Rsi.Tests.cs(202,13): warning S1481: Remove the unused local variable 'lastValid'. (https://rules.sonarsource.com/csharp/RSPEC-1481) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
QuanTAlib.Tests -> c:\github\quantalib\lib\bin\tests\Debug\net10.0\QuanTAlib.Tests.dll
|
||||
|
||||
Build succeeded.
|
||||
|
||||
c:\github\quantalib\lib\trends\conv\Conv.Quantower.cs(50,23): warning S3928: The parameter name 'WeightsInput' is not declared in the argument list. (https://rules.sonarsource.com/csharp/RSPEC-3928) [c:\github\quantalib\quantower\Trends.csproj]
|
||||
c:\github\quantalib\lib\trends\conv\Conv.Quantower.cs(50,72): warning MA0015: 'WeightsInput' is not a valid parameter name (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0015.md) [c:\github\quantalib\quantower\Trends.csproj]
|
||||
c:\github\quantalib\lib\trends\htit\Htit.Quantower.Tests.cs(16,9): warning MA0074: Use an overload of 'StartsWith' that has a StringComparison parameter (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0074.md) [c:\github\quantalib\quantower\Quantower.Tests.csproj]
|
||||
c:\github\quantalib\lib\trends\conv\Conv.Quantower.cs(50,23): warning S3928: The parameter name 'WeightsInput' is not declared in the argument list. (https://rules.sonarsource.com/csharp/RSPEC-3928) [c:\github\quantalib\quantower\Quantower.Tests.csproj]
|
||||
c:\github\quantalib\lib\trends\conv\Conv.Quantower.cs(50,72): warning MA0015: 'WeightsInput' is not a valid parameter name (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0015.md) [c:\github\quantalib\quantower\Quantower.Tests.csproj]
|
||||
c:\github\quantalib\lib\momentum\bop\Bop.Validation.Tests.cs(65,28): warning MA0005: Use Array.Empty<T>() (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0005.md) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
c:\github\quantalib\lib\feeds\gbm\ValidationHelper.cs(52,62): warning MA0016: Prefer using collection abstraction instead of implementation (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0016.md) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
c:\github\quantalib\lib\momentum\ao\Ao.Validation.Tests.cs(72,28): warning MA0005: Use Array.Empty<T>() (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0005.md) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
c:\github\quantalib\lib\momentum\rsi\Rsi.Tests.cs(74,24): warning S6562: Provide the "DateTimeKind" when creating this object. (https://rules.sonarsource.com/csharp/RSPEC-6562) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
c:\github\quantalib\lib\momentum\rsi\Rsi.Tests.cs(202,13): warning S1481: Remove the unused local variable 'lastValid'. (https://rules.sonarsource.com/csharp/RSPEC-1481) [c:\github\quantalib\lib\QuanTAlib.Tests.csproj::TargetFramework=net10.0]
|
||||
10 Warning(s)
|
||||
0 Error(s)
|
||||
|
||||
Time Elapsed 00:00:08.11
|
||||
@@ -0,0 +1,171 @@
|
||||
# Roslyn SARIF Generation and Codacy Integration
|
||||
|
||||
## Overview
|
||||
|
||||
QuanTAlib now automatically generates Roslyn SARIF (Static Analysis Results Interchange Format) files during every build and uploads them to Codacy for continuous code quality monitoring.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Build Configuration
|
||||
|
||||
The `Directory.Build.props` file has been configured to generate SARIF files for all projects:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<TreatWarningsAsErrors Condition="'$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
|
||||
<ErrorLog>$(MSBuildProjectDirectory)/roslyn.sarif</ErrorLog>
|
||||
<ErrorLogFormat>SARIF2.1</ErrorLogFormat>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
### Git Configuration
|
||||
|
||||
SARIF files are excluded from version control via `.gitignore`:
|
||||
|
||||
```
|
||||
# Roslyn SARIF files (generated during build and uploaded to Codacy)
|
||||
**/roslyn.sarif
|
||||
roslyn.sarif
|
||||
```
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
### Build Phase
|
||||
|
||||
The GitHub Actions workflow (`Publish.yml`) includes SARIF generation in the build step:
|
||||
|
||||
1. **Build Projects**: All projects are built in Debug configuration
|
||||
2. **Collect SARIF Files**: All `roslyn.sarif` files are collected from project directories
|
||||
3. **Upload Artifacts**: SARIF files are uploaded as artifacts for downstream jobs
|
||||
|
||||
### Codacy Upload Phase
|
||||
|
||||
A dedicated job (`Codacy_SARIF_Upload`) handles SARIF file uploads:
|
||||
|
||||
1. **Download SARIF Reports**: Retrieves SARIF artifacts from the build job
|
||||
2. **Install Codacy CLI**: Downloads the latest Codacy Analysis CLI
|
||||
3. **Upload to Codacy**: Uploads each SARIF file using the Codacy CLI with project metadata
|
||||
|
||||
## Local Development
|
||||
|
||||
### Generate SARIF Files
|
||||
|
||||
SARIF files are automatically generated during any build:
|
||||
|
||||
```bash
|
||||
dotnet build --configuration Debug
|
||||
```
|
||||
|
||||
After building, SARIF files will be located in each project directory:
|
||||
- `lib/roslyn.sarif` - Main library analysis
|
||||
- `quantower/roslyn.sarif` - Quantower adapter analysis
|
||||
|
||||
### View SARIF Files
|
||||
|
||||
SARIF files are JSON-formatted and can be viewed with:
|
||||
- Visual Studio Code with SARIF Viewer extension
|
||||
- Any text editor (JSON format)
|
||||
- Codacy web interface (after upload)
|
||||
|
||||
## Analyzers Included
|
||||
|
||||
The following Roslyn analyzers contribute to the SARIF reports:
|
||||
|
||||
1. **Roslynator.Analyzers** (v4.12.9)
|
||||
- Code style and quality rules
|
||||
- Performance optimizations
|
||||
- Modern C# patterns
|
||||
|
||||
2. **Meziantou.Analyzer** (v2.0.183)
|
||||
- Security and correctness rules
|
||||
- API usage guidelines
|
||||
- Best practices enforcement
|
||||
|
||||
3. **SonarAnalyzer.CSharp** (v10.x)
|
||||
- Code smells and bugs
|
||||
- Security vulnerabilities
|
||||
- Maintainability issues
|
||||
|
||||
4. **.NET SDK Analyzers**
|
||||
- Framework-specific rules
|
||||
- API compatibility
|
||||
- Performance guidelines
|
||||
|
||||
## Suppressed Rules
|
||||
|
||||
Certain rules are suppressed globally in `Directory.Build.props`:
|
||||
|
||||
```xml
|
||||
<NoWarn>$(NoWarn);S1144;S1944;S2053;S2245;S2259;S2583;S2589;S3329;S3655;S3776;S3949;S3966;S4158;S4347;S5773;S6781;MA0048;MA0051</NoWarn>
|
||||
```
|
||||
|
||||
These suppressions are intentional design decisions aligned with QuanTAlib's high-performance requirements.
|
||||
|
||||
## Codacy Integration
|
||||
|
||||
### Required Secrets
|
||||
|
||||
The GitHub Actions workflow requires the following secret:
|
||||
|
||||
- `CODACY_PROJECT_TOKEN`: API token for uploading results to Codacy
|
||||
|
||||
### Upload Process
|
||||
|
||||
1. SARIF files are collected after build
|
||||
2. Each SARIF file is uploaded individually
|
||||
3. Results are associated with the specific commit SHA
|
||||
4. Tool identifier: `roslyn`
|
||||
5. Upload continues even if individual files fail
|
||||
|
||||
### View Results
|
||||
|
||||
Analysis results are available at:
|
||||
https://app.codacy.com/gh/mihakralj/QuanTAlib
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### SARIF Not Generated
|
||||
|
||||
If SARIF files are not being generated:
|
||||
|
||||
1. Verify `ErrorLog` property is set in `Directory.Build.props`
|
||||
2. Ensure analyzers are installed (check NuGet packages)
|
||||
3. Build in Debug or Release configuration (not Clean)
|
||||
4. Check MSBuild output for analyzer warnings
|
||||
|
||||
### Upload Failures
|
||||
|
||||
If Codacy uploads fail:
|
||||
|
||||
1. Verify `CODACY_PROJECT_TOKEN` secret is set
|
||||
2. Check GitHub Actions logs for specific errors
|
||||
3. Ensure SARIF files contain valid JSON
|
||||
4. Verify network connectivity to Codacy API
|
||||
|
||||
### Large SARIF Files
|
||||
|
||||
If SARIF files become too large:
|
||||
|
||||
1. Increase `upload-batch-size` in the workflow
|
||||
2. Consider splitting uploads by project
|
||||
3. Review suppressed warnings (might need adjustment)
|
||||
4. Use `--upload-batch-size 100000` for very large files
|
||||
|
||||
## Performance Impact
|
||||
|
||||
- **Build Time**: +5-10% due to analyzer execution
|
||||
- **SARIF Generation**: <1s per project
|
||||
- **File Size**: 100KB-500KB per project
|
||||
- **Upload Time**: 2-5s per SARIF file
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements for consideration:
|
||||
|
||||
1. **Differential Analysis**: Upload only changed files
|
||||
2. **Parallel Uploads**: Upload multiple SARIF files concurrently
|
||||
3. **Local Validation**: Pre-commit hooks to validate SARIF
|
||||
4. **Custom Rules**: Project-specific analyzer configurations
|
||||
5. **Trend Analysis**: Track metrics over time
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
| **Archer On-Balance Volume** | Aobv | - | - | - | - |
|
||||
| **Arnaud Legoux Moving Average** | [Alma](../lib/trends/alma/alma.md) | - | - | ✔️ | ✔️ |
|
||||
| **Aroon** | [Aroon](../lib/momentum/aroon/aroon.md) | ✔️ | ✔️ | ✔️ | - |
|
||||
| **Aroon Oscillator** | [AroonOsc](../lib/momentum/aroonosc/AroonOsc.md) | ✔️ | ✔️ | ✔️ | [⚠️](../lib/momentum/aroonosc/AroonOsc.md#external-library-discrepancies) |
|
||||
| **Aroon Oscillator** | [AroonOsc](../lib/momentum/aroonosc/AroonOsc.md) | ✔️ | ✔️ | ✔️ | - |
|
||||
| **ATR Bands** | Atrbands | - | - | - | ❔ |
|
||||
| **Autoregressive FIR MA** | Afirma | - | - | - | - |
|
||||
| **Average Daily Range** | Adr | - | - | - | - |
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Release'">net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFramework Condition="'$(Configuration)' == 'Debug'">net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
+10
-11
@@ -1,7 +1,7 @@
|
||||
|
||||
namespace QuanTAlib.Tests
|
||||
{
|
||||
public class TBarTests
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class TBarTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsPropertiesCorrectly()
|
||||
@@ -195,13 +195,13 @@ namespace QuanTAlib.Tests
|
||||
|
||||
string result = bar.ToString();
|
||||
|
||||
Assert.Contains("2024-06-15", result);
|
||||
Assert.Contains("10:30:00", result);
|
||||
Assert.Contains("O=100.50", result);
|
||||
Assert.Contains("H=110.25", result);
|
||||
Assert.Contains("L=90.75", result);
|
||||
Assert.Contains("C=105.00", result);
|
||||
Assert.Contains("V=1000.00", result);
|
||||
Assert.Contains("2024-06-15", result, StringComparison.Ordinal);
|
||||
Assert.Contains("10:30:00", result, StringComparison.Ordinal);
|
||||
Assert.Contains("O=100.50", result, StringComparison.Ordinal);
|
||||
Assert.Contains("H=110.25", result, StringComparison.Ordinal);
|
||||
Assert.Contains("L=90.75", result, StringComparison.Ordinal);
|
||||
Assert.Contains("C=105.00", result, StringComparison.Ordinal);
|
||||
Assert.Contains("V=1000.00", result, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -345,6 +345,5 @@ namespace QuanTAlib.Tests
|
||||
var bar2 = new TBar(12346, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.True(bar1 != bar2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace QuanTAlib.Tests
|
||||
|
||||
string result = tValue.ToString();
|
||||
|
||||
Assert.Contains("2023-01-01", result);
|
||||
Assert.Contains("12:00:00", result);
|
||||
Assert.Contains("123.46", result);
|
||||
Assert.Contains("2023-01-01", result, StringComparison.Ordinal);
|
||||
Assert.Contains("12:00:00", result, StringComparison.Ordinal);
|
||||
Assert.Contains("123.46", result, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -49,7 +49,7 @@ public static class ValidationHelper
|
||||
}
|
||||
}
|
||||
|
||||
public static void VerifyData<TResult>(double[] qOutput, List<TResult> sSeries, Func<TResult, double?> selector, int skip = 100, double tolerance = DefaultTolerance)
|
||||
public static void VerifyData<TResult>(double[] qOutput, IReadOnlyList<TResult> sSeries, Func<TResult, double?> selector, int skip = 100, double tolerance = DefaultTolerance)
|
||||
{
|
||||
Assert.Equal(qOutput.Length, sSeries.Count);
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ public class AoIndicatorTests
|
||||
var indicator = new AoIndicator { FastPeriod = 10, SlowPeriod = 40 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("AO", indicator.ShortName);
|
||||
Assert.Contains("10", indicator.ShortName);
|
||||
Assert.Contains("40", indicator.ShortName);
|
||||
Assert.Contains("AO", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("10", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("40", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,8 +45,8 @@ public class AoIndicatorTests
|
||||
{
|
||||
var indicator = new AoIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Ao.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Ao.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -69,10 +69,10 @@ public sealed class AoValidationTests : IDisposable
|
||||
|
||||
var tulipIndicator = Tulip.Indicators.ao;
|
||||
double[][] inputs = { high, low };
|
||||
double[] options = { };
|
||||
double[] options = Array.Empty<double>();
|
||||
|
||||
int lookback = 33;
|
||||
double[][] outputs = { new double[_data.Bars.Count - lookback] };
|
||||
double[][] outputs = [new double[_data.Bars.Count - lookback]];
|
||||
|
||||
tulipIndicator.Run(inputs, options, outputs);
|
||||
var tulipResults = outputs[0];
|
||||
|
||||
@@ -35,9 +35,9 @@ public class ApoIndicatorTests
|
||||
var indicator = new ApoIndicator { FastPeriod = 10, SlowPeriod = 40 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("APO", indicator.ShortName);
|
||||
Assert.Contains("10", indicator.ShortName);
|
||||
Assert.Contains("40", indicator.ShortName);
|
||||
Assert.Contains("APO", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("10", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("40", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,8 +45,8 @@ public class ApoIndicatorTests
|
||||
{
|
||||
var indicator = new ApoIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Apo.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Apo.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class AroonIndicatorTests
|
||||
var indicator = new AroonIndicator { Period = 20 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("Aroon", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("Aroon", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class AroonIndicatorTests
|
||||
{
|
||||
var indicator = new AroonIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Aroon.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Aroon.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class AroonOscIndicatorTests
|
||||
var indicator = new AroonOscIndicator { Period = 20 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("AroonOsc", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("AroonOsc", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class AroonOscIndicatorTests
|
||||
{
|
||||
var indicator = new AroonOscIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("AroonOsc.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("AroonOsc.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -97,41 +97,4 @@ public sealed class AroonOscValidationTests : IDisposable
|
||||
ValidationHelper.VerifyData(results, tulipOsc, lookback: 14);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Ooples implementation deviates significantly from standard (TA-Lib, Tulip, Skender, QuanTAlib)")]
|
||||
public void MatchesOoples()
|
||||
{
|
||||
// Note: OoplesFinance implementation of Aroon Oscillator is an outlier.
|
||||
// It deviates from the consensus of TA-Lib, Tulip, Skender, and QuanTAlib.
|
||||
// The deviation is not a simple offset; it involves inconsistent steps and reversals,
|
||||
// likely due to differences in how the high/low window indices are tracked.
|
||||
|
||||
var aroon = new AroonOsc(14);
|
||||
var results = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
var res = aroon.Update(_data.Bars[i]);
|
||||
results.Add(res.Value);
|
||||
}
|
||||
|
||||
var ooplesData = _data.SkenderQuotes.Select(q => new TickerData
|
||||
{
|
||||
Date = q.Date,
|
||||
Open = (double)q.Open,
|
||||
High = (double)q.High,
|
||||
Low = (double)q.Low,
|
||||
Close = (double)q.Close,
|
||||
Volume = (double)q.Volume
|
||||
}).ToList();
|
||||
|
||||
var stockData = new StockData(ooplesData);
|
||||
|
||||
// Ooples only provides CalculateAroonOscillator
|
||||
var aroonOscResults = stockData.CalculateAroonOscillator(14);
|
||||
var ooplesOsc = aroonOscResults.OutputValues["Aroon"]
|
||||
.ToArray();
|
||||
|
||||
// Verify Oscillator
|
||||
ValidationHelper.VerifyData(results, ooplesOsc, lookback: 14, tolerance: ValidationHelper.OoplesTolerance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class BopIndicatorTests
|
||||
{
|
||||
var indicator = new BopIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Bop.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Bop.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed class BopValidationTests : IDisposable
|
||||
var close = _data.Bars.Close.Values.ToArray();
|
||||
|
||||
double[][] inputs = { open, high, low, close };
|
||||
double[] options = { }; // No options for BOP
|
||||
double[] options = Array.Empty<double>(); // No options for BOP
|
||||
|
||||
var bopInd = Tulip.Indicators.bop;
|
||||
double[][] outputs = { new double[open.Length - bopInd.Start(options)] };
|
||||
|
||||
@@ -38,9 +38,9 @@ public class CfbIndicatorTests
|
||||
// Initialize to update SourceName
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("CFB", indicator.ShortName);
|
||||
Assert.Contains("5-20", indicator.ShortName);
|
||||
Assert.Contains("Close", indicator.ShortName);
|
||||
Assert.Contains("CFB", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("5-20", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("Close", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -48,8 +48,8 @@ public class CfbIndicatorTests
|
||||
{
|
||||
var indicator = new CfbIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Cfb.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Cfb.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -35,8 +35,8 @@ public class DmxIndicatorTests
|
||||
// Initialize to update SourceName (though DMX doesn't use SourceName)
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("DMX", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("DMX", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -44,8 +44,8 @@ public class DmxIndicatorTests
|
||||
{
|
||||
var indicator = new DmxIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Dmx.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Dmx.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -49,8 +49,8 @@ public class MacdIndicatorTests
|
||||
{
|
||||
var indicator = new MacdIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Macd.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Macd.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -39,7 +39,7 @@ public class RsiIndicatorTests
|
||||
};
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("RSI(20)", indicator.ShortName);
|
||||
Assert.Contains("RSI(20)", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -47,8 +47,8 @@ public class RsiIndicatorTests
|
||||
{
|
||||
var indicator = new RsiIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Rsi.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Rsi.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+394
-50
@@ -1,63 +1,261 @@
|
||||
using Xunit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
namespace QuanTAlib;
|
||||
|
||||
public class RsiTests
|
||||
{
|
||||
[Fact]
|
||||
public void BasicCalculation()
|
||||
public void Constructor_InvalidParameters_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new Rsi(0));
|
||||
Assert.Throws<ArgumentException>(() => new Rsi(-1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BasicCalculation_DoesNotCrash()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
// RSI requires a period of data to be valid
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(rsi.Last.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Properties_Accessible()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
Assert.Equal("Rsi(14)", rsi.Name);
|
||||
Assert.False(rsi.IsHot);
|
||||
Assert.Equal(0, rsi.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchMatchesStreaming()
|
||||
public void IsHot_BecomesTrueAfterWarmup()
|
||||
{
|
||||
var rsi = new Rsi(5);
|
||||
var series = new TSeries();
|
||||
// Generate some data
|
||||
for (int i = 0; i < 20; i++)
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
Assert.False(rsi.IsHot);
|
||||
|
||||
// RSI wraps two RMA indicators (gain and loss)
|
||||
// RMA wraps EMA with alpha = 1/period
|
||||
// EMA becomes hot when E <= 0.05, which occurs after ~-ln(0.05)/ln(1-alpha) values
|
||||
// For period=14: alpha=1/14, needs ~40 values to become hot
|
||||
// Both RMAs must be hot for RSI to be hot
|
||||
for (int i = 0; i < 45; i++)
|
||||
{
|
||||
series.Add(new TValue(DateTime.UtcNow.AddMinutes(i), 100 + Math.Sin(i) * 10));
|
||||
rsi.Update(series.Close[i]);
|
||||
if (i < 40)
|
||||
{
|
||||
Assert.False(rsi.IsHot, $"Should not be hot at index {i}");
|
||||
}
|
||||
}
|
||||
|
||||
var batchResult = rsi.Update(series);
|
||||
|
||||
// After sufficient warmup, IsHot should be true
|
||||
Assert.True(rsi.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_True_AdvancesState()
|
||||
{
|
||||
var rsi = new Rsi(5);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i], isNew: true);
|
||||
}
|
||||
|
||||
var val1 = rsi.Update(series.Close[9], isNew: true);
|
||||
var nextTime = series.Close[9].Time + TimeSpan.FromMinutes(1).Ticks;
|
||||
var val2 = rsi.Update(new TValue(nextTime, series.Close[9].Value + 1), isNew: true);
|
||||
|
||||
Assert.NotEqual(val1.Value, val2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_False_UpdatesCurrentBar()
|
||||
{
|
||||
var rsi = new Rsi(5);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
var val1 = rsi.Update(series.Close[9], isNew: true);
|
||||
var modifiedValue = new TValue(series.Close[9].Time, series.Close[9].Value + 5);
|
||||
var val2 = rsi.Update(modifiedValue, isNew: false);
|
||||
|
||||
// Should update the same bar
|
||||
Assert.Equal(val1.Time, val2.Time);
|
||||
Assert.NotEqual(val1.Value, val2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNew_Consistency()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Feed first 99
|
||||
for (int i = 0; i < 99; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
// Update with 100th point (isNew=true)
|
||||
rsi.Update(series.Close[99], true);
|
||||
|
||||
// Update with modified 100th point (isNew=false)
|
||||
var modifiedValue = new TValue(series.Close[99].Time, series.Close[99].Value + 2.0);
|
||||
var val2 = rsi.Update(modifiedValue, false);
|
||||
|
||||
// Create new instance and feed up to modified
|
||||
var rsi2 = new Rsi(14);
|
||||
for (int i = 0; i < 99; i++)
|
||||
{
|
||||
rsi2.Update(series.Close[i]);
|
||||
}
|
||||
var val3 = rsi2.Update(modifiedValue, true);
|
||||
|
||||
Assert.Equal(val3.Value, val2.Value, 1e-9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IterativeCorrections_RestoreToOriginalState()
|
||||
{
|
||||
var rsi = new Rsi(10);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Feed N values
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
var originalValue = rsi.Last;
|
||||
|
||||
// Make M updates with isNew=false
|
||||
for (int m = 0; m < 5; m++)
|
||||
{
|
||||
var modifiedValue = new TValue(series.Close[29].Time, series.Close[29].Value + m);
|
||||
rsi.Update(modifiedValue, isNew: false);
|
||||
}
|
||||
|
||||
// Restore with original 30th value
|
||||
var restoredValue = rsi.Update(series.Close[29], isNew: false);
|
||||
|
||||
Assert.Equal(originalValue.Value, restoredValue.Value, 1e-9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ClearsState()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
Assert.True(rsi.IsHot);
|
||||
var valueBefore = rsi.Last.Value;
|
||||
|
||||
rsi.Reset();
|
||||
var streamResults = new System.Collections.Generic.List<double>();
|
||||
foreach (var item in series)
|
||||
|
||||
Assert.False(rsi.IsHot);
|
||||
Assert.Equal(0, rsi.Last.Value);
|
||||
|
||||
// Feed again
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamResults.Add(rsi.Update(item).Value);
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
Assert.Equal(batchResult[i].Value, streamResults[i], 8);
|
||||
}
|
||||
Assert.True(rsi.IsHot);
|
||||
Assert.Equal(valueBefore, rsi.Last.Value, 1e-9);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void SpanMatchesBatch()
|
||||
public void NaN_Input_DoesNotCrash()
|
||||
{
|
||||
var rsi = new Rsi(5);
|
||||
var series = new TSeries();
|
||||
// Generate some data
|
||||
var rsi = new Rsi(10);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
// Feed NaN
|
||||
var nanValue = new TValue(DateTime.UtcNow, double.NaN);
|
||||
var result = rsi.Update(nanValue);
|
||||
|
||||
// Should not crash and should return finite value
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Infinity_Input_DoesNotCrash()
|
||||
{
|
||||
var rsi = new Rsi(10);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
var infValue = new TValue(DateTime.UtcNow, double.PositiveInfinity);
|
||||
var result = rsi.Update(infValue);
|
||||
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleNaN_ContinuesCorrectly()
|
||||
{
|
||||
var rsi = new Rsi(10);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(30, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
series.Add(new TValue(DateTime.UtcNow.AddMinutes(i), 100 + Math.Sin(i) * 10));
|
||||
rsi.Update(series.Close[i]);
|
||||
}
|
||||
|
||||
var batchResult = rsi.Update(series);
|
||||
|
||||
var output = new double[series.Count];
|
||||
Rsi.Calculate(series.Values, output, 5);
|
||||
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
|
||||
// Feed multiple NaN
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Assert.Equal(batchResult[i].Value, output[i], 8);
|
||||
var nanValue = new TValue(DateTime.UtcNow.AddMinutes(i), double.NaN);
|
||||
var result = rsi.Update(nanValue);
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
|
||||
// Continue with valid data
|
||||
for (int i = 20; i < 30; i++)
|
||||
{
|
||||
var result = rsi.Update(series.Close[i]);
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,24 +270,170 @@ public class RsiTests
|
||||
}
|
||||
|
||||
var result = rsi.Update(series);
|
||||
// Flat line means no gains or losses, RSI should be 50 (or 0/100 depending on implementation details, but typically 50 or 0 if no moves)
|
||||
// Actually, if AvgGain=0 and AvgLoss=0, RSI is typically defined as 50 or 0.
|
||||
// Our implementation: RS = 0/0 -> NaN?
|
||||
// Let's check implementation.
|
||||
// If AvgLoss is 0, RSI is 100.
|
||||
// If AvgGain is 0, RSI is 0.
|
||||
// If both are 0?
|
||||
// In Rma: if all inputs are 0, Rma is 0.
|
||||
// So AvgGain=0, AvgLoss=0.
|
||||
// RS = 0/0 = NaN.
|
||||
// RSI = 100 - 100/(1+NaN) = NaN.
|
||||
// Let's see what happens.
|
||||
|
||||
// Actually, standard behavior for flat line is often 50 or 0.
|
||||
// Let's verify what our implementation does.
|
||||
// If we look at Rsi.cs:
|
||||
// if (avgLoss == 0) return avgGain == 0 ? 50 : 100;
|
||||
|
||||
// Flat line: no gains or losses, RSI = 50
|
||||
Assert.Equal(50, result.Last.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TSeries_Update_Matches_Streaming()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var streamingResults = new List<double>();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamingResults.Add(rsi.Update(series.Close[i]).Value);
|
||||
}
|
||||
|
||||
var rsi2 = new Rsi(14);
|
||||
var seriesResults = rsi2.Update(series.Close);
|
||||
|
||||
Assert.Equal(streamingResults.Count, seriesResults.Count);
|
||||
for (int i = 0; i < seriesResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], seriesResults.Values[i], 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticBatch_Matches_Streaming()
|
||||
{
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var rsi = new Rsi(14);
|
||||
var streamingResults = new List<double>();
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamingResults.Add(rsi.Update(series.Close[i]).Value);
|
||||
}
|
||||
|
||||
var batchResults = Rsi.Batch(series.Close, 14);
|
||||
|
||||
Assert.Equal(streamingResults.Count, batchResults.Count);
|
||||
for (int i = 0; i < batchResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], batchResults.Values[i], 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalc_ValidatesInput()
|
||||
{
|
||||
var source = new double[10];
|
||||
var output = new double[5];
|
||||
|
||||
Assert.Throws<ArgumentException>(() => Rsi.Calculate(source, output, 14));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalc_InvalidPeriod_Throws()
|
||||
{
|
||||
var source = new double[10];
|
||||
var output = new double[10];
|
||||
|
||||
Assert.Throws<ArgumentException>(() => Rsi.Calculate(source, output, 0));
|
||||
Assert.Throws<ArgumentException>(() => Rsi.Calculate(source, output, -1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalc_MatchesTSeriesCalc()
|
||||
{
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var batchResults = Rsi.Batch(series.Close, 14);
|
||||
|
||||
var output = new double[series.Count];
|
||||
Rsi.Calculate(series.Close.Values, output, 14);
|
||||
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
{
|
||||
Assert.Equal(batchResults.Values[i], output[i], 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpanCalc_HandlesNaN()
|
||||
{
|
||||
var source = new double[20];
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(20, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
source[i] = series.Close[i].Value;
|
||||
}
|
||||
|
||||
source[10] = double.NaN;
|
||||
source[15] = double.NaN;
|
||||
|
||||
var output = new double[20];
|
||||
Rsi.Calculate(source, output, 10);
|
||||
|
||||
// Should not crash and produce finite results
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
{
|
||||
Assert.True(double.IsFinite(output[i]) || output[i] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllModes_ProduceSameResult()
|
||||
{
|
||||
int period = 14;
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// 1. Batch Mode
|
||||
var batchSeries = Rsi.Batch(series.Close, period);
|
||||
double expected = batchSeries.Last.Value;
|
||||
|
||||
// 2. Span Mode
|
||||
var spanOutput = new double[series.Count];
|
||||
Rsi.Calculate(series.Close.Values, spanOutput, period);
|
||||
double spanResult = spanOutput[^1];
|
||||
|
||||
// 3. Streaming Mode
|
||||
var streamingRsi = new Rsi(period);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
streamingRsi.Update(series.Close[i]);
|
||||
}
|
||||
double streamingResult = streamingRsi.Last.Value;
|
||||
|
||||
// 4. Eventing Mode
|
||||
var pubSource = new TSeries();
|
||||
var eventingRsi = new Rsi(pubSource, period);
|
||||
for (int i = 0; i < series.Count; i++)
|
||||
{
|
||||
pubSource.Add(series.Close[i]);
|
||||
}
|
||||
double eventingResult = eventingRsi.Last.Value;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, spanResult, 9);
|
||||
Assert.Equal(expected, streamingResult, 9);
|
||||
Assert.Equal(expected, eventingResult, 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Chainability_Works()
|
||||
{
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM();
|
||||
var series = gbm.Fetch(10, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// Test TSeries chain
|
||||
var result = rsi.Update(series.Close);
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<TSeries>(result);
|
||||
|
||||
// Test TValue chain (returns TValue)
|
||||
var result2 = rsi.Update(series.Close[0]);
|
||||
Assert.IsType<TValue>(result2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ public class RsxIndicatorTests
|
||||
{
|
||||
var indicator = new RsxIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("RSX", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("RSX", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class RsxIndicatorTests
|
||||
{
|
||||
var indicator = new RsxIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Rsx.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Rsx.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class VelIndicatorTests
|
||||
{
|
||||
var indicator = new VelIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("VEL", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("VEL", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class VelIndicatorTests
|
||||
{
|
||||
var indicator = new VelIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Vel.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Vel.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+2
-26
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Release'">net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFramework Condition="'$(Configuration)' == 'Debug'">net10.0</TargetFramework>
|
||||
<Title>QuanTAlib</Title>
|
||||
<Product>Library of TA Calculations, Charts and Strategies for Quantower</Product>
|
||||
<Description>Quantitative Technical Analysis Library in C# for Quantower</Description>
|
||||
@@ -42,31 +43,6 @@
|
||||
<Compile Include="**/*.cs" Exclude="**/*.Tests.cs;**/*.Quantower.cs;**/obj/**/*.cs;**/bin/**/*.cs;feeds/gbm/ValidationHelper.cs;feeds/gbm/ValidationTestData.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="GitVersion.MsBuild" Version="6.5.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4" />
|
||||
|
||||
<PackageReference Include="Roslynator.Analyzers" Version="4.12.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.183">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.*">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\.github\QuanTAlib2.png" Pack="true" Visible="false" PackagePath="" />
|
||||
|
||||
@@ -34,8 +34,8 @@ public class AlmaIndicatorTests
|
||||
{
|
||||
var indicator = new AlmaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("ALMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("ALMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,8 +32,8 @@ public class BesselIndicatorTests
|
||||
{
|
||||
var indicator = new BesselIndicator { Length = 15 };
|
||||
|
||||
Assert.Contains("BESSEL", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("BESSEL", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class BilateralIndicatorTests
|
||||
{
|
||||
var indicator = new BilateralIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("Bilateral", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("Bilateral", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class BilateralIndicatorTests
|
||||
{
|
||||
var indicator = new BilateralIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Bilateral.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Bilateral.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class BlmaIndicatorTests
|
||||
var indicator = new BlmaIndicator { Period = 20 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("BLMA", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("BLMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class BlmaIndicatorTests
|
||||
{
|
||||
var indicator = new BlmaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Blma.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Blma.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -35,8 +35,8 @@ public class ButterIndicatorTests
|
||||
var indicator = new ButterIndicator { Period = 20 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("BUTTER", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("BUTTER", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -44,8 +44,8 @@ public class ButterIndicatorTests
|
||||
{
|
||||
var indicator = new ButterIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Butter.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Butter.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -33,8 +33,8 @@ public class ConvIndicatorTests
|
||||
{
|
||||
var indicator = new ConvIndicator();
|
||||
|
||||
Assert.Contains("CONV", indicator.ShortName);
|
||||
Assert.Contains("Close", indicator.ShortName);
|
||||
Assert.Contains("CONV", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("Close", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -42,8 +42,8 @@ public class ConvIndicatorTests
|
||||
{
|
||||
var indicator = new ConvIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Conv.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Conv.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -172,6 +172,6 @@ public class ConvIndicatorTests
|
||||
{
|
||||
var indicator = new ConvIndicator();
|
||||
|
||||
Assert.Contains("Convolution", indicator.Description);
|
||||
Assert.Contains("Convolution", indicator.Description, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,10 @@ public class ConvIndicator : Indicator, IWatchlistIndicator
|
||||
try
|
||||
{
|
||||
var weights = WeightsInput.Split(',')
|
||||
.Select(s => double.Parse(s.Trim()))
|
||||
.Select(s => double.Parse(s.Trim(), System.Globalization.CultureInfo.InvariantCulture))
|
||||
.ToArray();
|
||||
|
||||
if (weights.Length == 0)
|
||||
throw new ArgumentException("Weights cannot be empty");
|
||||
|
||||
_conv = new Conv(weights);
|
||||
_conv = new Conv(weights.Length == 0 ? [1.0] : weights);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
|
||||
@@ -32,8 +32,8 @@ public class DemaIndicatorTests
|
||||
{
|
||||
var indicator = new DemaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("DEMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("DEMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,8 +41,8 @@ public class DemaIndicatorTests
|
||||
{
|
||||
var indicator = new DemaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Dema.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Dema.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,8 +32,8 @@ public class DwmaIndicatorTests
|
||||
{
|
||||
var indicator = new DwmaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("DWMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("DWMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,8 +41,8 @@ public class DwmaIndicatorTests
|
||||
{
|
||||
var indicator = new DwmaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Dwma.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Dwma.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,8 +32,8 @@ public class EmaIndicatorTests
|
||||
{
|
||||
var indicator = new EmaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("EMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("EMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -33,8 +33,8 @@ public class HmaIndicatorTests
|
||||
{
|
||||
var indicator = new HmaIndicator { Period = 21 };
|
||||
|
||||
Assert.Contains("HMA", indicator.ShortName);
|
||||
Assert.Contains("21", indicator.ShortName);
|
||||
Assert.Contains("HMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("21", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -42,8 +42,8 @@ public class HmaIndicatorTests
|
||||
{
|
||||
var indicator = new HmaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Hma.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Hma.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -13,8 +13,8 @@ public class HtitIndicatorTests
|
||||
var indicator = new HtitIndicator();
|
||||
indicator.Initialize();
|
||||
Assert.Equal("HTIT - Ehlers Hilbert Transform Instantaneous Trend", indicator.Name);
|
||||
Assert.StartsWith("HTIT", indicator.ShortName);
|
||||
Assert.Contains("Close", indicator.ShortName);
|
||||
Assert.StartsWith("HTIT", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("Close", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Equal(0, HtitIndicator.MinHistoryDepths);
|
||||
Assert.Single(indicator.LinesSeries);
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ public class JmaIndicatorTests
|
||||
{
|
||||
var indicator = new JmaIndicator { Period = 15, Phase = 50 };
|
||||
|
||||
Assert.Contains("JMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("50", indicator.ShortName);
|
||||
Assert.Contains("JMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("50", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class JmaIndicatorTests
|
||||
{
|
||||
var indicator = new JmaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Jma.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Jma.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class KamaIndicatorTests
|
||||
{
|
||||
var indicator = new KamaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("KAMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("KAMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -33,8 +33,8 @@ public class LsmaIndicatorTests
|
||||
{
|
||||
var indicator = new LsmaIndicator { Period = 15, Offset = 2 };
|
||||
|
||||
Assert.Contains("LSMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("LSMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -42,8 +42,8 @@ public class LsmaIndicatorTests
|
||||
{
|
||||
var indicator = new LsmaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Lsma.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Lsma.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class MamaIndicatorTests
|
||||
var indicator = new MamaIndicator { FastLimit = 0.5, SlowLimit = 0.05 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("MAMA", indicator.ShortName);
|
||||
Assert.Contains("Close", indicator.ShortName);
|
||||
Assert.Contains("MAMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("Close", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,8 +34,8 @@ public class PwmaIndicatorTests
|
||||
{
|
||||
var indicator = new PwmaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("PWMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("PWMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class PwmaIndicatorTests
|
||||
{
|
||||
var indicator = new PwmaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Pwma.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Pwma.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,8 +32,8 @@ public class RmaIndicatorTests
|
||||
{
|
||||
var indicator = new RmaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("RMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("RMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,8 +32,8 @@ public class SmaIndicatorTests
|
||||
{
|
||||
var indicator = new SmaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("SMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("SMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ public class SsfIndicatorTests
|
||||
{
|
||||
var indicator = new SsfIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("SSF", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("SSF", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -35,9 +35,9 @@ public class SuperIndicatorTests
|
||||
var indicator = new SuperIndicator { Period = 20, Multiplier = 2.5 };
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("Super", indicator.ShortName);
|
||||
Assert.Contains("20", indicator.ShortName);
|
||||
Assert.Contains("2.5", indicator.ShortName);
|
||||
Assert.Contains("Super", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("20", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("2.5", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,8 +45,8 @@ public class SuperIndicatorTests
|
||||
{
|
||||
var indicator = new SuperIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Super.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Super.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -34,9 +34,9 @@ public class T3IndicatorTests
|
||||
{
|
||||
var indicator = new T3Indicator { Period = 15, VolumeFactor = 0.618 };
|
||||
|
||||
Assert.Contains("T3", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("0.62", indicator.ShortName); // F2 formatting
|
||||
Assert.Contains("T3", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("0.62", indicator.ShortName, StringComparison.Ordinal); // F2 formatting
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,8 +32,8 @@ public class TemaIndicatorTests
|
||||
{
|
||||
var indicator = new TemaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("TEMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("TEMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,8 +41,8 @@ public class TemaIndicatorTests
|
||||
{
|
||||
var indicator = new TemaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Tema.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Tema.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,8 +32,8 @@ public class TrimaIndicatorTests
|
||||
{
|
||||
var indicator = new TrimaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("TRIMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("TRIMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,8 +41,8 @@ public class TrimaIndicatorTests
|
||||
{
|
||||
var indicator = new TrimaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Trima.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Trima.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -179,6 +179,6 @@ public class TrimaIndicatorTests
|
||||
{
|
||||
var indicator = new TrimaIndicator();
|
||||
|
||||
Assert.Contains("Triangular", indicator.Description);
|
||||
Assert.Contains("Triangular", indicator.Description, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ public class VidyaIndicatorTests
|
||||
{
|
||||
var indicator = new VidyaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("VIDYA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("VIDYA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,8 +32,8 @@ public class WmaIndicatorTests
|
||||
{
|
||||
var indicator = new WmaIndicator { Period = 15 };
|
||||
|
||||
Assert.Contains("WMA", indicator.ShortName);
|
||||
Assert.Contains("15", indicator.ShortName);
|
||||
Assert.Contains("WMA", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,8 +41,8 @@ public class WmaIndicatorTests
|
||||
{
|
||||
var indicator = new WmaIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Wma.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Wma.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -180,6 +180,6 @@ public class WmaIndicatorTests
|
||||
{
|
||||
var indicator = new WmaIndicator();
|
||||
|
||||
Assert.Contains("Weighted", indicator.Description);
|
||||
Assert.Contains("Weighted", indicator.Description, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ public class AdoscIndicatorTests
|
||||
{
|
||||
var indicator = new AdoscIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink);
|
||||
Assert.Contains("Adosc.Quantower.cs", indicator.SourceCodeLink);
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Adosc.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Release'">net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFramework Condition="'$(Configuration)' == 'Debug'">net10.0</TargetFramework>
|
||||
<AssemblyName>Momentum</AssemblyName>
|
||||
<AlgoType>Indicator</AlgoType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Release'">net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFramework Condition="'$(Configuration)' == 'Debug'">net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Release'">net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFramework Condition="'$(Configuration)' == 'Debug'">net10.0</TargetFramework>
|
||||
<AssemblyName>Statistics</AssemblyName>
|
||||
<AlgoType>Indicator</AlgoType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Release'">net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFramework Condition="'$(Configuration)' == 'Debug'">net10.0</TargetFramework>
|
||||
<AssemblyName>Trends</AssemblyName>
|
||||
<AlgoType>Indicator</AlgoType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Release'">net10.0;net8.0</TargetFrameworks>
|
||||
<TargetFramework Condition="'$(Configuration)' == 'Debug'">net10.0</TargetFramework>
|
||||
<AssemblyName>Volume</AssemblyName>
|
||||
<AlgoType>Indicator</AlgoType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
|
||||
Reference in New Issue
Block a user