diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml
new file mode 100644
index 00000000..e69f298e
--- /dev/null
+++ b/.github/codeql/codeql-config.yml
@@ -0,0 +1,22 @@
+name: "CodeQL Config"
+
+queries:
+ - uses: security-and-quality
+ - uses: security-extended
+
+paths-ignore:
+ - '**/test/**'
+ - '**/tests/**'
+ - '**/*.test.cs'
+ - '**/obj/**'
+ - '**/bin/**'
+ - '**/docs/**'
+
+query-filters:
+ - exclude:
+ problem.severity:
+ - warning
+ - recommendation
+
+paths:
+ - src
\ No newline at end of file
diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml
index bab332b0..4293fa8b 100644
--- a/.github/workflows/Publish.yml
+++ b/.github/workflows/Publish.yml
@@ -1,17 +1,37 @@
name: Publish Workflow
on:
- push: # Triggers on push events to any branch
- pull_request: # Triggers on pull request events targeting any branch
- workflow_dispatch: # Allows manual triggering of the workflow
+ push:
+ paths-ignore:
+ - '**.md'
+ - 'docs/**'
+ - '.gitignore'
+ - 'LICENSE'
+ pull_request:
+ paths-ignore:
+ - '**.md'
+ - 'docs/**'
+ - '.gitignore'
+ - 'LICENSE'
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
permissions:
contents: write
pull-requests: read # Allows SonarCloud to decorate PRs with analysis results
security-events: write # Required for CodeQL analysis and uploading SARIF results
+env:
+ DOTNET_VERSION: '8.x'
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+
jobs:
Code_Coverage:
+ timeout-minutes: 30
runs-on: windows-latest
steps:
- name: Checkout repository
@@ -22,7 +42,20 @@ jobs:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.x'
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: ${{ runner.os }}-nuget-
+
+ - name: Cache dotnet tools
+ uses: actions/cache@v4
+ with:
+ path: ~/.dotnet/tools
+ key: ${{ runner.os }}-dotnet-tools-${{ hashFiles('**/*.csproj') }}
- name: Set up JDK 17
uses: actions/setup-java@v4
@@ -61,6 +94,7 @@ jobs:
/d:sonar.exclusions="**/TestResults/**/*,**/bin/**/*,**/obj/**/*,**/*.html,**/coverage/**/*,**/CoverageReport/**/*,**/*.md,**/*.css,**/docs/**/*,**/archive/**/*,**/notebooks/**/*" `
/d:sonar.test.exclusions="**Tests.cs,**/obj/**/*,**/bin/**/*" `
/d:sonar.cpd.exclusions="**Tests.cs" `
+ /d:sonar.scanner.scanAll="false" `
/d:sonar.cs.roslyn.ignoreIssues="false" `
/d:sonar.issue.ignore.multicriteria="e1" `
/d:sonar.issue.ignore.multicriteria.e1.ruleKey="csharpsquid:S1944,csharpsquid:S2053,csharpsquid:S2222,csharpsquid:S2259,csharpsquid:S2583,csharpsquid:S2589,csharpsquid:S3329,csharpsquid:S3655,csharpsquid:S3900,csharpsquid:S3949,csharpsquid:S3966,csharpsquid:S4158,csharpsquid:S4347,csharpsquid:S5773,csharpsquid:S6781" `
@@ -68,6 +102,8 @@ jobs:
/d:sonar.verbose="true"
- name: Build Projects
+ id: build
+ continue-on-error: true
run: |
dotnet build --no-restore --configuration Debug
dotnet build ./lib/quantalib.csproj --configuration Release --nologo
@@ -75,8 +111,15 @@ jobs:
dotnet build ./quantower/Statistics/_Statistics.csproj --configuration Release --nologo
dotnet build ./quantower/Volatility/_Volatility.csproj --configuration Release --nologo
dotnet build ./SyntheticVendor/SyntheticVendor.csproj --configuration Release --nologo
+ if ($LASTEXITCODE -ne 0) { Write-Error "Build failed" }
+
+ - name: Check Build Status
+ if: steps.build.outcome == 'failure'
+ run: exit 1
- name: Run Tests with Coverage
+ id: tests
+ continue-on-error: true
run: |
dotnet test --no-build --configuration Debug /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
dotnet-coverage collect "dotnet test" -f xml -o "coverage.xml"
@@ -86,9 +129,21 @@ jobs:
- name: Generate Coverage Report
run: |
- reportgenerator -reports:*cover*.xml -targetdir:.
+ reportgenerator -reports:*cover*.xml -targetdir:./coverage-report
+
+ - name: Upload Coverage Reports
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-reports
+ path: |
+ **/TestResults
+ **/coverage-report
+ **/*cover*.xml
+ **/dotcover.*
- name: End SonarCloud Analysis
+ if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -102,13 +157,19 @@ jobs:
coverage-reports: '*cover*.xml'
- name: Upload Coverage to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
with:
files: 'cover*'
verbose: true
CodeQL:
+ timeout-minutes: 30
runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ actions: read
+ contents: read
+
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -118,12 +179,22 @@ jobs:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.x'
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: ${{ runner.os }}-nuget-
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: 'csharp'
+ queries: security-and-quality
+ config-file: ./.github/codeql/codeql-config.yml
+ tools: latest
- name: Restore dependencies
run: dotnet restore
@@ -131,63 +202,51 @@ jobs:
- name: Build
run: dotnet build --no-restore --configuration Debug
- - name: Run Codacy Analysis CLI
- uses: codacy/codacy-analysis-cli-action@v4
- with:
- project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
- verbose: true
- output: results.sarif
- format: sarif
- gh-code-scanning-compat: true
- max-allowed-issues: 2147483647
-
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
-
- - name: Upload SARIF results file
- uses: github/codeql-action/upload-sarif@v3
with:
- sarif_file: results.sarif
+ output: results
+ upload: true
- SecurityCodeScan:
- runs-on: windows-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
+ - name: Run Snyk to check for vulnerabilities
+ uses: snyk/actions/dotnet@master
+ continue-on-error: true
+ env:
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
+ LD_PRELOAD: '' # Clear the LD_PRELOAD to avoid CodeQL conflicts
with:
- fetch-depth: 0
+ args: |
+ --file=./lib/quantalib.csproj
+ --severity-threshold=low
+ --detection-depth=4
+ --package-manager=nuget
- - name: Setup NuGet
- uses: nuget/setup-nuget@v1
-
- - name: Setup MSBuild
- uses: microsoft/setup-msbuild@v1
-
- - name: Setup .NET SDK
- uses: actions/setup-dotnet@v4
+ - name: Run Snyk on Solution
+ uses: snyk/actions/dotnet@master
+ if: always()
+ continue-on-error: true
+ env:
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
+ LD_PRELOAD: ''
with:
- dotnet-version: |
- 8.x
- 3.1.x
- dotnet-quality: 'preview'
-
- - name: Set up projects for analysis
- uses: security-code-scan/security-code-scan-add-action@v1
-
- - name: Build
- run: |
- dotnet restore
- dotnet build --no-restore --configuration Debug
-
- - name: Convert SARIF for uploading to GitHub
- uses: security-code-scan/security-code-scan-results-action@v1
-
- - name: Upload SARIF
- uses: github/codeql-action/upload-sarif@v3
+ args: |
+ --file=QuanTAlib.sln
+ --all-projects
+ --detection-depth=4
+ - name: Run Snyk IaC
+ uses: snyk/actions/iac@master
+ continue-on-error: true
+ env:
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
+ LD_PRELOAD: ''
+ with:
+ args: |
+ --severity-threshold=low
build_publish:
- needs: [Code_Coverage, CodeQL, SecurityCodeScan]
+ timeout-minutes: 20
+ needs: [Code_Coverage, CodeQL]
if: |
success() &&
(github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')) ||
@@ -202,7 +261,14 @@ jobs:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.x'
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: ${{ runner.os }}-nuget-
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0
@@ -226,8 +292,6 @@ jobs:
dotnet build ./quantower/Volatility/_Volatility.csproj --configuration Release --nologo
dotnet build ./SyntheticVendor/SyntheticVendor.csproj --configuration Release --nologo
-############# Publish dev release
-
- name: Create or Update Development Release
if: github.ref == 'refs/heads/dev'
env:
@@ -247,16 +311,16 @@ jobs:
- name: Push prerelease package to myget.org
if: github.ref == 'refs/heads/dev'
+ continue-on-error: true
+ id: myget-push
run: |
dotnet nuget push 'lib/bin/Release/QuanTAlib.*.nupkg' \
- --source https://www.myget.org/F/quantalib/api/v3/index.json \
- --force-english-output \
- --api-key ${{ secrets.MYGET_DEPLOY_KEY_QUANTALIB }}
-
-############## Publish main release
+ --source https://www.myget.org/F/quantalib/api/v3/index.json \
+ --force-english-output \
+ --api-key ${{ secrets.MYGET_DEPLOY_KEY_QUANTALIB }}
- name: Create GitHub Release
- if: ${{ github.ref == 'refs/heads/main' }}
+ if: github.ref == 'refs/heads/main'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
diff --git a/Directory.Build.props b/Directory.Build.props
index a5cfd53c..a6c4ab71 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,7 +8,6 @@
true
en-US
false
- false
true
AnyCPU
True
@@ -21,6 +20,9 @@
snupkg
AnyCPU
true
+ true
+ true
+ true
@@ -49,6 +51,7 @@
+
diff --git a/SyntheticVendor/SyntheticVendor.cs b/SyntheticVendor/SyntheticVendor.cs
index 90898bae..89033c94 100644
--- a/SyntheticVendor/SyntheticVendor.cs
+++ b/SyntheticVendor/SyntheticVendor.cs
@@ -597,25 +597,6 @@ public class SyntheticVendor : Vendor
};
}
- private HistoryItemBar GeneratePulseWave(DateTime time, TimeSpan slice)
- {
- double hours = (time - DateTime.UnixEpoch).TotalHours;
- double period = 24; // 24-hour period
- double position = hours % period;
- double value = position < period / 5 ? 100 : -100; // 20% duty cycle
-
- return new HistoryItemBar
- {
- TicksLeft = time.Ticks,
- TicksRight = time.Add(slice).Ticks - 1,
- Open = value,
- High = 100,
- Low = -100,
- Close = value,
- Volume = 100,
- Ticks = 100
- };
- }
private HistoryItemBar GenerateTriangleWave(DateTime time, TimeSpan slice)
{
diff --git a/Tests/test_updates_statistics.cs b/Tests/test_updates_statistics.cs
index d1820474..a5d901fc 100644
--- a/Tests/test_updates_statistics.cs
+++ b/Tests/test_updates_statistics.cs
@@ -17,6 +17,24 @@ public class StatisticsUpdateTests
return ((double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue * 200) - 100; // Range: -100 to 100
}
+ private TBar GetRandomBar(bool IsNew)
+ {
+ double open = GetRandomDouble();
+ double high = open + Math.Abs(GetRandomDouble());
+ double low = open - Math.Abs(GetRandomDouble());
+ double close = low + (high - low) * GetRandomDouble();
+ return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew);
+ }
+
+ private TBar GetRandomBar(bool IsNew)
+ {
+ double open = GetRandomDouble();
+ double high = open + Math.Abs(GetRandomDouble());
+ double low = open - Math.Abs(GetRandomDouble());
+ double close = low + (high - low) * GetRandomDouble();
+ return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew);
+ }
+
[Fact]
public void Curvature_Update()
{
@@ -47,6 +65,22 @@ public class StatisticsUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+ [Fact]
+ public void Hurst_Update()
+ {
+ var indicator = new Hurst(period: 100, minLength: 10);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Kurtosis_Update()
{
diff --git a/Tests/test_updates_volatility.cs b/Tests/test_updates_volatility.cs
index c48a2d9e..26b6a5f9 100644
--- a/Tests/test_updates_volatility.cs
+++ b/Tests/test_updates_volatility.cs
@@ -26,6 +26,22 @@ public class VolatilityUpdateTests
return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew);
}
+ [Fact]
+ public void Adr_Update()
+ {
+ var indicator = new Adr(period: 14);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Atr_Update()
{
@@ -42,6 +58,166 @@ public class VolatilityUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+ [Fact]
+ public void Ap_Update()
+ {
+ var indicator = new Ap(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Atrp_Update()
+ {
+ var indicator = new Atrp(period: 14);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Bband_Update()
+ {
+ var indicator = new Bband(period: 20, multiplier: 2.0);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Ccv_Update()
+ {
+ var indicator = new Ccv(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Ce_Update()
+ {
+ var indicator = new Ce(period: 22, multiplier: 3.0);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Cv_Update()
+ {
+ var indicator = new Cv(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Cvi_Update()
+ {
+ var indicator = new Cvi(period: 10, smoothPeriod: 10);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Ewma_Update()
+ {
+ var indicator = new Ewma(period: 20, lambda: 0.94);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Fcb_Update()
+ {
+ var indicator = new Fcb(period: 20, smoothing: 0.5);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Gkv_Update()
+ {
+ var indicator = new Gkv(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Historical_Update()
{
@@ -57,6 +233,22 @@ public class VolatilityUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+ [Fact]
+ public void Hlv_Update()
+ {
+ var indicator = new Hlv(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Jvolty_Update()
{
@@ -102,22 +294,6 @@ public class VolatilityUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
- [Fact]
- public void Cvi_Update()
- {
- var indicator = new Cvi(period: 14);
- TBar r = GetRandomBar(true);
- double initialValue = indicator.Calc(r);
-
- for (int i = 0; i < RandomUpdates; i++)
- {
- indicator.Calc(GetRandomBar(IsNew: false));
- }
- double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
-
- Assert.Equal(initialValue, finalValue, precision);
- }
-
[Fact]
public void Tr_Update()
{
diff --git a/Tests/test_updates_volume.cs b/Tests/test_updates_volume.cs
index 166a5326..c85a619b 100644
--- a/Tests/test_updates_volume.cs
+++ b/Tests/test_updates_volume.cs
@@ -319,4 +319,68 @@ public class VolumeUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+
+ [Fact]
+ public void Vf_Update()
+ {
+ var indicator = new Vf(period: 13);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Vp_Update()
+ {
+ var indicator = new Vp(period: 14);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Vwap_Update()
+ {
+ var indicator = new Vwap();
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Vwma_Update()
+ {
+ var indicator = new Vwma(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
}
diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md
index 4d97d32c..0aecedff 100644
--- a/docs/indicators/indicators.md
+++ b/docs/indicators/indicators.md
@@ -1,16 +1,18 @@
# Indicators in QuanTAlib
+
+
| **Category** | **Status** | **Completion** |
|--------------|:----------:|:--------------:|
| Basic Transforms | 6 of 6 | 100% |
| Averages & Trends | 33 of 33 | 100% |
| Momentum | 17 of 17 | 100% |
-| Oscillators | 12 of 29 | 41% |
-| Volatility | 15 of 35 | 43% |
-| Volume | 19 of 19 | 100% |
-| Numerical Analysis | 13 of 20 | 65% |
+| Oscillators | 11 of 29 | 38% |
+| Volatility | 24 of 35 | 69% |
+| Volume | 15 of 19 | 79% |
+| Numerical Analysis | 13 of 19 | 68% |
| Errors | 16 of 16 | 100% |
-| **Total** | **131 of 175** | **75%** |
+| **Total** | **135 of 174** | **78%** |
|Technical Indicator Name| Class Name|
|-----------|:----------:|
@@ -109,16 +111,16 @@
|ATR - Average True Range|`Atr`|
|ATRP - Average True Range Percent|`Atrp`|
|ATRS - ATR Trailing Stop|`Atrs`|
-|🚧 BB* - Bollinger Bands® (Upper, Middle, Lower)|`Bb`|
-|🚧 CCV - Close-to-Close Volatility|`Ccv`|
-|🚧 CE - Chandelier Exit|`Ce`|
-|🚧 CV - Conditional Volatility (ARCH/GARCH)|`Cv`|
-|🚧 CVI - Chaikin's Volatility|`Cvi`|
+|BB* - Bollinger Bands® (Upper, Middle, Lower)|`Bb`|
+|CCV - Close-to-Close Volatility|`Ccv`|
+|CE - Chandelier Exit|`Ce`|
+|CV - Conditional Volatility (ARCH/GARCH)|`Cv`|
+|CVI - Chaikin's Volatility|`Cvi`|
|🚧 DC* - Donchian Channels (Upper, Middle, Lower)|`Dc`|
-|🚧 EWMA - Exponential Weighted Moving Average Volatility|`Ewma`|
-|🚧 FCB - Fractal Chaos Bands|`Fcb`|
-|🚧 GKV - Garman-Klass Volatility|`Gkv`|
-|🚧 HLV - High-Low Volatility|`Hlv`|
+|EWMA - Exponential Weighted Moving Average Volatility|`Ewma`|
+|FCB - Fractal Chaos Bands|`Fcb`|
+|GKV - Garman-Klass Volatility|`Gkv`|
+|HLV - High-Low Volatility|`Hlv`|
|HV - Historical Volatility|`Hv`|
|🚧 ICH* - Ichimoku Cloud (Conversion, Base, Leading Span A, Leading Span B, Lagging Span)|`Ich`|
|JVOLTY - Jurik Volatility|`Jvolty`|
diff --git a/lib/averages/Dema.cs b/lib/averages/Dema.cs
index f70ae547..3f543649 100644
--- a/lib/averages/Dema.cs
+++ b/lib/averages/Dema.cs
@@ -18,7 +18,6 @@ namespace QuanTAlib;
///
public class Dema : AbstractBase
{
- private readonly int _period;
private readonly double _k;
private readonly double _epsilon = 1e-10;
private double _lastEma1, _p_lastEma1;
@@ -31,8 +30,7 @@ public class Dema : AbstractBase
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
- _period = period;
- _k = 2.0 / (_period + 1);
+ _k = 2.0 / (period + 1);
Name = "Dema";
double percentile = 0.85; //targeting 85th percentile of correctness of converging EMA
WarmupPeriod = (int)System.Math.Ceiling(-period * System.Math.Log(1 - percentile));
diff --git a/lib/averages/Dsma.cs b/lib/averages/Dsma.cs
index 7720fb7b..238a78f7 100644
--- a/lib/averages/Dsma.cs
+++ b/lib/averages/Dsma.cs
@@ -21,9 +21,8 @@ namespace QuanTAlib;
///
public class Dsma : AbstractBase
{
- private readonly int _period;
private readonly CircularBuffer _buffer;
- private readonly double _c1, _c2, _c3;
+ private readonly double _c2, _c3;
private readonly double _scaleFactor;
private readonly double _periodRecip; // 1/_period
private readonly double _scaleByPeriod; // 5/_period
@@ -49,7 +48,6 @@ public class Dsma : AbstractBase
{
throw new ArgumentOutOfRangeException(nameof(scaleFactor), "Scale factor must be between 0 and 1 (exclusive).");
}
- _period = period;
_periodRecip = 1.0 / period;
_scaleFactor = scaleFactor;
_buffer = new CircularBuffer(period);
@@ -61,7 +59,7 @@ public class Dsma : AbstractBase
_c2 = b1;
_c3 = -a1 * a1;
- _c1 = 1.0 - _c2 - _c3;
+ double _c1 = 1.0 - _c2 - _c3;
_c1Half = _c1 * 0.5;
_scaleByPeriod = 5.0 / period;
diff --git a/lib/averages/Epma.cs b/lib/averages/Epma.cs
index 9fca0906..6cad88c7 100644
--- a/lib/averages/Epma.cs
+++ b/lib/averages/Epma.cs
@@ -26,7 +26,6 @@ public class Epma : AbstractBase
{
private readonly int _period;
private readonly Convolution _convolution;
- private readonly double[] _baseKernel;
/// The number of data points used in the EPMA calculation.
/// Thrown when period is less than 1.
@@ -37,7 +36,7 @@ public class Epma : AbstractBase
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
- _baseKernel = GenerateKernel(_period);
+ double[] _baseKernel = GenerateKernel(_period);
_convolution = new Convolution(_baseKernel);
Name = "Epma";
WarmupPeriod = period;
diff --git a/lib/averages/Fwma.cs b/lib/averages/Fwma.cs
index b1a7a8c0..eb8f2618 100644
--- a/lib/averages/Fwma.cs
+++ b/lib/averages/Fwma.cs
@@ -26,7 +26,6 @@ namespace QuanTAlib;
public class Fwma : AbstractBase
{
private readonly Convolution _convolution;
- private readonly double[] _kernel;
/// The number of data points used in the FWMA calculation.
/// Thrown when period is less than 1.
@@ -36,7 +35,7 @@ public class Fwma : AbstractBase
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
- _kernel = GenerateKernel(period);
+ double[] _kernel = GenerateKernel(period);
_convolution = new Convolution(_kernel);
Name = "Fwma";
WarmupPeriod = period;
diff --git a/lib/averages/Gma.cs b/lib/averages/Gma.cs
index 4924ae2c..c0828318 100644
--- a/lib/averages/Gma.cs
+++ b/lib/averages/Gma.cs
@@ -26,7 +26,6 @@ namespace QuanTAlib;
public class Gma : AbstractBase
{
private readonly Convolution _convolution;
- private readonly double[] _kernel;
/// The number of data points used in the GMA calculation.
/// Thrown when period is less than 1.
@@ -36,7 +35,7 @@ public class Gma : AbstractBase
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
- _kernel = GenerateKernel(period);
+ double[] _kernel = GenerateKernel(period);
_convolution = new Convolution(_kernel);
Name = "Gma";
WarmupPeriod = period;
diff --git a/lib/averages/Hma.cs b/lib/averages/Hma.cs
index 1fe049e7..d36c9f34 100644
--- a/lib/averages/Hma.cs
+++ b/lib/averages/Hma.cs
@@ -28,11 +28,6 @@ namespace QuanTAlib;
public class Hma : AbstractBase
{
private readonly Convolution _wmaHalf, _wmaFull, _wmaFinal;
- private readonly int _period;
- private readonly int _sqrtPeriod;
- private readonly double[] _kernelHalf;
- private readonly double[] _kernelFull;
- private readonly double[] _kernelFinal;
/// The number of data points used in the HMA calculation. Must be at least 2.
/// Thrown when period is less than 2.
@@ -42,13 +37,12 @@ public class Hma : AbstractBase
{
throw new System.ArgumentException("Period must be greater than or equal to 2.", nameof(period));
}
- _period = period;
- _sqrtPeriod = (int)System.Math.Sqrt(period);
+ int _sqrtPeriod = (int)System.Math.Sqrt(period);
// Generate all kernels once
- _kernelHalf = GenerateWmaKernel(period / 2);
- _kernelFull = GenerateWmaKernel(period);
- _kernelFinal = GenerateWmaKernel(_sqrtPeriod);
+ double[] _kernelHalf = GenerateWmaKernel(period / 2);
+ double[] _kernelFull = GenerateWmaKernel(period);
+ double[] _kernelFinal = GenerateWmaKernel(_sqrtPeriod);
// Initialize convolutions with pre-generated kernels
_wmaHalf = new Convolution(_kernelHalf);
diff --git a/lib/averages/Sinema.cs b/lib/averages/Sinema.cs
index 34fa800e..10f0f4ab 100644
--- a/lib/averages/Sinema.cs
+++ b/lib/averages/Sinema.cs
@@ -28,7 +28,6 @@ namespace QuanTAlib;
public class Sinema : AbstractBase
{
private readonly Convolution _convolution;
- private readonly double[] _kernel;
/// The number of data points used in the SINEMA calculation.
/// Thrown when period is less than 1.
@@ -38,7 +37,7 @@ public class Sinema : AbstractBase
{
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
- _kernel = GenerateKernel(period);
+ double[] _kernel = GenerateKernel(period);
_convolution = new Convolution(_kernel);
Name = "Sinema";
WarmupPeriod = period;
diff --git a/lib/averages/Sma.cs b/lib/averages/Sma.cs
index f6dec4bb..fb267e31 100644
--- a/lib/averages/Sma.cs
+++ b/lib/averages/Sma.cs
@@ -27,7 +27,6 @@ namespace QuanTAlib;
public class Sma : AbstractBase
{
private readonly CircularBuffer _buffer;
- private readonly int _period;
/// The number of data points used in the SMA calculation.
/// Thrown when period is less than 1.
@@ -37,7 +36,6 @@ public class Sma : AbstractBase
{
throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
- _period = period;
_buffer = new CircularBuffer(period);
Name = "Sma";
WarmupPeriod = period;
diff --git a/lib/averages/T3.cs b/lib/averages/T3.cs
index 7f65090e..e7db51bf 100644
--- a/lib/averages/T3.cs
+++ b/lib/averages/T3.cs
@@ -28,7 +28,6 @@ namespace QuanTAlib;
public class T3 : AbstractBase
{
private readonly int _period;
- private readonly double _vfactor;
private readonly bool _useSma;
private readonly double _k;
private readonly double _c1, _c2, _c3, _c4;
@@ -48,7 +47,6 @@ public class T3 : AbstractBase
throw new System.ArgumentException("Period must be greater than or equal to 1.", nameof(period));
}
_period = period;
- _vfactor = vfactor;
_useSma = useSma;
WarmupPeriod = period;
@@ -69,7 +67,7 @@ public class T3 : AbstractBase
_buffer5 = new(period);
_buffer6 = new(period);
- Name = $"T3({_period}, {_vfactor})";
+ Name = $"T3({_period}, {vfactor})";
Init();
}
diff --git a/lib/quantalib.csproj b/lib/quantalib.csproj
index 5b87dfa0..a5bae0db 100644
--- a/lib/quantalib.csproj
+++ b/lib/quantalib.csproj
@@ -1,36 +1,27 @@
-
QuanTAlib
Library of TA Calculations, Charts and Strategies for Quantower
Quantitative Technical Analysis Library in C# for Quantower
git
https://github.com/mihakralj/QuanTAlib
- true
Miha Kralj
Miha Kralj
readme.md
QuanTAlib
QuanTAlib
- 0.0.0.0
True
- AnyCPU
- full
- True
True
QuanTAlib2.png
- readme.md
Apache-2.0
Indicators;Stock;Market;Technical;Analysis;Algorithmic;Trading;Trade;Trend;Momentum;Finance;Algorithm;Algo;
AlgoTrading;Financial;Strategy;Chart;Charting;Oscillator;Overlay;Equity;Bitcoin;Crypto;Cryptocurrency;Forex;
Quantitative;Historical;Quotes;
- QuanTAlib2.png
https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png
True
false
- $(NoWarn);NU1903;NU5104
@@ -51,4 +42,4 @@
-
+
\ No newline at end of file
diff --git a/lib/statistics/Hurst.cs b/lib/statistics/Hurst.cs
new file mode 100644
index 00000000..4ec62254
--- /dev/null
+++ b/lib/statistics/Hurst.cs
@@ -0,0 +1,194 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// HURST: Hurst Exponent
+/// A measure of long-term memory of time series that relates to the
+/// autocorrelations of the time series, and the rate at which these
+/// decrease as the lag between pairs of values increases.
+///
+///
+/// The Hurst Exponent calculation process:
+/// 1. Calculate log returns of the series
+/// 2. Create subsequences of different lengths
+/// 3. For each length:
+/// - Calculate range (max-min) of cumulative deviations
+/// - Calculate standard deviation
+/// - Calculate R/S ratio
+/// 4. Fit log(R/S) vs log(length) to find H
+///
+/// Key characteristics:
+/// - H = 0.5: Random walk (Brownian motion)
+/// - 0.5 < H ≤ 1.0: Trending (persistent) series
+/// - 0 ≤ H < 0.5: Mean-reverting (anti-persistent) series
+/// - Default minimum length is 10
+/// - Default maximum length is period/2
+///
+/// Formula:
+/// R(n)/S(n) = c * n^H
+/// where:
+/// R(n) = range of cumulative deviations
+/// S(n) = standard deviation
+/// n = subsequence length
+/// H = Hurst exponent
+///
+/// Market Applications:
+/// - Market efficiency analysis
+/// - Trend strength measurement
+/// - Trading strategy development
+/// - Risk assessment
+/// - Market regime identification
+///
+/// Sources:
+/// H.E. Hurst (1951)
+/// "Long-term Storage Capacity of Reservoirs"
+/// Transactions of the American Society of Civil Engineers, 116, 770-799
+///
+/// Note: Returns a value between 0 and 1
+///
+
+[SkipLocalsInit]
+public sealed class Hurst : AbstractBase
+{
+ private readonly int _period;
+ private readonly int _minLength;
+ private readonly CircularBuffer _prices;
+ private readonly CircularBuffer _logReturns;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Hurst(int period = 100, int minLength = 10)
+ {
+ if (minLength < 10)
+ {
+ throw new ArgumentOutOfRangeException(nameof(minLength), "Minimum length must be at least 10.");
+ }
+ if (period <= minLength * 2)
+ {
+ throw new ArgumentOutOfRangeException(nameof(period), "Period must be at least twice the minimum length.");
+ }
+
+ _period = period;
+ _minLength = minLength;
+ WarmupPeriod = period + 1; // Need one extra period for returns
+ Name = $"HURST({_period})";
+ _prices = new CircularBuffer(period);
+ _logReturns = new CircularBuffer(period);
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Hurst(object source, int period = 100, int minLength = 10) : this(period, minLength)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _prices.Clear();
+ _logReturns.Clear();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private static (double range, double stdDev) CalculateRangeAndStdDev(ReadOnlySpan data)
+ {
+ int n = data.Length;
+ if (n == 0) return (0, 0);
+
+ // Calculate mean
+ double mean = 0;
+ for (int i = 0; i < n; i++)
+ {
+ mean += data[i];
+ }
+ mean /= n;
+
+ // Calculate cumulative deviations and std dev
+ double max = double.MinValue;
+ double min = double.MaxValue;
+ double sumSquaredDev = 0;
+ double cumDev = 0;
+
+ for (int i = 0; i < n; i++)
+ {
+ double dev = data[i] - mean;
+ cumDev += dev;
+ max = Math.Max(max, cumDev);
+ min = Math.Min(min, cumDev);
+ sumSquaredDev += dev * dev;
+ }
+
+ double range = max - min;
+ double stdDev = Math.Sqrt(sumSquaredDev / n);
+
+ return (range, stdDev);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Add price and calculate log return
+ _prices.Add(BarInput.Close);
+ if (_index > 1)
+ {
+ double logReturn = Math.Log(BarInput.Close / _prices[1]);
+ _logReturns.Add(logReturn);
+ }
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0.5; // Return random walk value until we have enough data
+ }
+
+ // Calculate R/S values for different lengths
+ int maxLength = _period / 2;
+ int numPoints = 0;
+ double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
+
+ for (int length = _minLength; length <= maxLength; length *= 2)
+ {
+ var (range, stdDev) = CalculateRangeAndStdDev(_logReturns.GetSpan()[..length]);
+ if (stdDev > 0)
+ {
+ double rs = range / stdDev;
+ if (rs > 0)
+ {
+ double x = Math.Log(length);
+ double y = Math.Log(rs);
+ sumX += x;
+ sumY += y;
+ sumXY += x * y;
+ sumX2 += x * x;
+ numPoints++;
+ }
+ }
+ }
+
+ // Calculate Hurst exponent using linear regression
+ double hurst = 0.5; // Default to random walk
+ if (numPoints > 1)
+ {
+ double slope = (numPoints * sumXY - sumX * sumY) / (numPoints * sumX2 - sumX * sumX);
+ hurst = Math.Max(0, Math.Min(1, slope)); // Clamp between 0 and 1
+ }
+
+ IsHot = _index >= WarmupPeriod;
+ return hurst;
+ }
+}
diff --git a/lib/volatility/Bband.cs b/lib/volatility/Bband.cs
new file mode 100644
index 00000000..a135b71b
--- /dev/null
+++ b/lib/volatility/Bband.cs
@@ -0,0 +1,143 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// BBAND: Bollinger Bands®
+/// A technical analysis tool that creates a band of three lines:
+/// - Middle Band: n-period simple moving average (SMA)
+/// - Upper Band: Middle Band + (standard deviation * multiplier)
+/// - Lower Band: Middle Band - (standard deviation * multiplier)
+///
+///
+/// The Bollinger Bands calculation process:
+/// 1. Calculate the middle band (SMA of closing prices)
+/// 2. Calculate the standard deviation of prices
+/// 3. Upper and lower bands are the middle band +/- standard deviation * multiplier
+///
+/// Key characteristics:
+/// - Adapts to volatility
+/// - Default period is 20 days
+/// - Default multiplier is 2.0
+/// - Returns three bands (upper, middle, lower)
+/// - Wider bands indicate higher volatility
+/// - Narrower bands indicate lower volatility
+///
+/// Formula:
+/// Middle Band = SMA(Close, period)
+/// Standard Deviation = SQRT(SUM((Close - Middle Band)^2) / period)
+/// Upper Band = Middle Band + (multiplier * Standard Deviation)
+/// Lower Band = Middle Band - (multiplier * Standard Deviation)
+///
+/// Market Applications:
+/// - Volatility measurement
+/// - Overbought/oversold identification
+/// - Price breakout detection
+/// - Trend strength analysis
+/// - Dynamic support/resistance levels
+///
+/// Sources:
+/// John Bollinger (1980s)
+/// https://www.bollingerbands.com
+///
+/// Note: Returns three values: upper, middle, and lower bands
+///
+
+[SkipLocalsInit]
+public sealed class Bband : AbstractBase
+{
+ private readonly int _period;
+ private readonly double _multiplier;
+ private readonly CircularBuffer _prices;
+ private double _middleBand;
+ private double _upperBand;
+ private double _lowerBand;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Bband(int period = 20, double multiplier = 2.0)
+ {
+ _period = period;
+ _multiplier = multiplier;
+ WarmupPeriod = period;
+ Name = $"BBAND({_period},{_multiplier})";
+ _prices = new CircularBuffer(period);
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Bband(object source, int period = 20, double multiplier = 2.0) : this(period, multiplier)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _middleBand = 0;
+ _upperBand = 0;
+ _lowerBand = 0;
+ _prices.Clear();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Add current price to buffer
+ _prices.Add(BarInput.Close);
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0;
+ }
+
+ // Calculate middle band (SMA)
+ _middleBand = _prices.Average();
+
+ // Calculate standard deviation
+ double sumSquaredDeviations = 0;
+ for (int i = 0; i < _period; i++)
+ {
+ double deviation = _prices[i] - _middleBand;
+ sumSquaredDeviations += deviation * deviation;
+ }
+ double standardDeviation = Math.Sqrt(sumSquaredDeviations / _period);
+
+ // Calculate bands
+ double bandWidth = _multiplier * standardDeviation;
+ _upperBand = _middleBand + bandWidth;
+ _lowerBand = _middleBand - bandWidth;
+
+ IsHot = _index >= WarmupPeriod;
+ return _middleBand; // Return middle band as primary value
+ }
+
+ ///
+ /// Gets the upper band value
+ ///
+ public double UpperBand => _upperBand;
+
+ ///
+ /// Gets the middle band value (SMA)
+ ///
+ public double MiddleBand => _middleBand;
+
+ ///
+ /// Gets the lower band value
+ ///
+ public double LowerBand => _lowerBand;
+}
diff --git a/lib/volatility/Ccv.cs b/lib/volatility/Ccv.cs
new file mode 100644
index 00000000..cfe6e7ba
--- /dev/null
+++ b/lib/volatility/Ccv.cs
@@ -0,0 +1,130 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// CCV: Close-to-Close Volatility
+/// A measure of price volatility that uses only closing prices,
+/// calculated as the standard deviation of logarithmic returns.
+///
+///
+/// The CCV calculation process:
+/// 1. Calculate logarithmic returns: ln(Close[t]/Close[t-1])
+/// 2. Calculate standard deviation of returns over the period
+/// 3. Annualize by multiplying by sqrt(trading days per year)
+///
+/// Key characteristics:
+/// - Uses only closing prices
+/// - Based on logarithmic returns
+/// - Default period is 20 days
+/// - Annualized by default (multiply by sqrt(252))
+/// - Expressed as a percentage
+///
+/// Formula:
+/// Returns = ln(Close[t]/Close[t-1])
+/// CCV = StdDev(Returns, period) * sqrt(252) * 100
+///
+/// Market Applications:
+/// - Volatility measurement
+/// - Risk assessment
+/// - Option pricing
+/// - Trading strategy development
+/// - Portfolio management
+///
+/// Sources:
+/// Close-to-Close Volatility concept
+/// https://www.investopedia.com/terms/v/volatility.asp
+///
+/// Note: Returns annualized volatility as a percentage
+///
+
+[SkipLocalsInit]
+public sealed class Ccv : AbstractBase
+{
+ private readonly int _period;
+ private readonly bool _annualize;
+ private readonly CircularBuffer _returns;
+ private double _prevClose;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Ccv(int period = 20, bool annualize = true)
+ {
+ _period = period;
+ _annualize = annualize;
+ WarmupPeriod = period + 1; // Need one extra period for returns calculation
+ Name = $"CCV({_period})";
+ _returns = new CircularBuffer(period);
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Ccv(object source, int period = 20, bool annualize = true) : this(period, annualize)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _prevClose = 0;
+ _returns.Clear();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Skip first period to establish previous close
+ if (_index == 1)
+ {
+ _prevClose = BarInput.Close;
+ return 0;
+ }
+
+ // Calculate logarithmic return
+ double logReturn = Math.Log(BarInput.Close / _prevClose);
+ _returns.Add(logReturn);
+ _prevClose = BarInput.Close;
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0;
+ }
+
+ // Calculate standard deviation
+ double mean = _returns.Average();
+ double sumSquaredDeviations = 0;
+ for (int i = 0; i < _period; i++)
+ {
+ double deviation = _returns[i] - mean;
+ sumSquaredDeviations += deviation * deviation;
+ }
+ double stdDev = Math.Sqrt(sumSquaredDeviations / _period);
+
+ // Annualize if requested (sqrt(252) for trading days in a year)
+ if (_annualize)
+ {
+ stdDev *= Math.Sqrt(252);
+ }
+
+ // Convert to percentage
+ double volatility = stdDev * 100;
+
+ IsHot = _index >= WarmupPeriod;
+ return volatility;
+ }
+}
diff --git a/lib/volatility/Ce.cs b/lib/volatility/Ce.cs
new file mode 100644
index 00000000..225c2c22
--- /dev/null
+++ b/lib/volatility/Ce.cs
@@ -0,0 +1,157 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// CE: Chandelier Exit
+/// A volatility-based stop-loss indicator that adapts to market conditions,
+/// using ATR to set stop levels above/below recent price extremes.
+///
+///
+/// The CE calculation process:
+/// 1. Calculate highest high and lowest low over the period
+/// 2. Calculate ATR over the period
+/// 3. Long Exit = Highest High - (ATR * multiplier)
+/// 4. Short Exit = Lowest Low + (ATR * multiplier)
+///
+/// Key characteristics:
+/// - Adapts to market volatility
+/// - Default period is 22 days
+/// - Default multiplier is 3.0
+/// - Returns both long and short exit levels
+/// - Based on ATR and price extremes
+///
+/// Formula:
+/// ATR = Average(TR, period)
+/// Long Exit = Highest High[period] - (multiplier * ATR)
+/// Short Exit = Lowest Low[period] + (multiplier * ATR)
+///
+/// Market Applications:
+/// - Stop loss placement
+/// - Position management
+/// - Trend following
+/// - Risk control
+/// - Exit strategy
+///
+/// Sources:
+/// Chuck LeBeau
+/// https://www.investopedia.com/terms/c/chandelier-exit.asp
+///
+/// Note: Returns two values: long exit and short exit levels
+///
+
+[SkipLocalsInit]
+public sealed class Ce : AbstractBase
+{
+ private readonly int _period;
+ private readonly double _multiplier;
+ private readonly CircularBuffer _tr;
+ private readonly CircularBuffer _highs;
+ private readonly CircularBuffer _lows;
+ private double _prevClose;
+ private double _longExit;
+ private double _shortExit;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Ce(int period = 22, double multiplier = 3.0)
+ {
+ _period = period;
+ _multiplier = multiplier;
+ WarmupPeriod = period + 1; // Need one extra period for TR
+ Name = $"CE({_period},{_multiplier})";
+ _tr = new CircularBuffer(period);
+ _highs = new CircularBuffer(period);
+ _lows = new CircularBuffer(period);
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Ce(object source, int period = 22, double multiplier = 3.0) : this(period, multiplier)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _prevClose = 0;
+ _longExit = 0;
+ _shortExit = 0;
+ _tr.Clear();
+ _highs.Clear();
+ _lows.Clear();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Skip first period to establish previous close
+ if (_index == 1)
+ {
+ _prevClose = BarInput.Close;
+ return 0;
+ }
+
+ // Calculate True Range
+ double tr = Math.Max(BarInput.High - BarInput.Low,
+ Math.Max(Math.Abs(BarInput.High - _prevClose),
+ Math.Abs(BarInput.Low - _prevClose)));
+
+ // Add values to buffers
+ _tr.Add(tr);
+ _highs.Add(BarInput.High);
+ _lows.Add(BarInput.Low);
+
+ // Store current close for next calculation
+ _prevClose = BarInput.Close;
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0;
+ }
+
+ // Calculate ATR
+ double atr = _tr.Average();
+
+ // Find highest high and lowest low
+ double highestHigh = double.MinValue;
+ double lowestLow = double.MaxValue;
+ for (int i = 0; i < _period; i++)
+ {
+ highestHigh = Math.Max(highestHigh, _highs[i]);
+ lowestLow = Math.Min(lowestLow, _lows[i]);
+ }
+
+ // Calculate exit levels
+ _longExit = highestHigh - (_multiplier * atr);
+ _shortExit = lowestLow + (_multiplier * atr);
+
+ IsHot = _index >= WarmupPeriod;
+ return _longExit; // Return long exit as primary value
+ }
+
+ ///
+ /// Gets the long exit level
+ ///
+ public double LongExit => _longExit;
+
+ ///
+ /// Gets the short exit level
+ ///
+ public double ShortExit => _shortExit;
+}
diff --git a/lib/volatility/Cv.cs b/lib/volatility/Cv.cs
new file mode 100644
index 00000000..77dd3fff
--- /dev/null
+++ b/lib/volatility/Cv.cs
@@ -0,0 +1,138 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// CV: Conditional Volatility (GARCH)
+/// Implements the GARCH(1,1) model for estimating conditional volatility,
+/// which captures volatility clustering and mean reversion in financial markets.
+///
+///
+/// The CV (GARCH) calculation process:
+/// 1. Calculate returns: (Close[t] - Close[t-1])/Close[t-1]
+/// 2. Update variance estimate using GARCH(1,1) formula:
+/// σ²[t] = ω + α*r²[t-1] + β*σ²[t-1]
+/// 3. Take square root to get volatility
+///
+/// Key characteristics:
+/// - Captures volatility clustering
+/// - Mean-reverting behavior
+/// - Responds to market shocks
+/// - Default period is 20 days
+/// - Returns annualized volatility
+///
+/// Formula:
+/// Returns[t] = (Close[t] - Close[t-1])/Close[t-1]
+/// σ²[t] = ω + α*Returns²[t-1] + β*σ²[t-1]
+/// CV[t] = sqrt(σ²[t]) * sqrt(252) * 100
+///
+/// Where:
+/// ω (omega) = long-term variance * (1 - α - β)
+/// α (alpha) = weight of recent squared return
+/// β (beta) = weight of previous variance
+///
+/// Market Applications:
+/// - Risk measurement
+/// - Option pricing
+/// - Value at Risk (VaR)
+/// - Portfolio optimization
+/// - Volatility forecasting
+///
+/// Sources:
+/// Bollerslev (1986)
+/// https://en.wikipedia.org/wiki/GARCH
+///
+/// Note: Returns annualized volatility as a percentage
+///
+
+[SkipLocalsInit]
+public sealed class Cv : AbstractBase
+{
+ private readonly int _period;
+ private readonly double _alpha;
+ private readonly double _beta;
+ private readonly double _omega;
+ private double _prevClose;
+ private double _prevVariance;
+ private bool _isInitialized;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Cv(int period = 20, double alpha = 0.1, double beta = 0.8)
+ {
+ _period = period;
+ _alpha = alpha;
+ _beta = beta;
+ _omega = 0.001 * (1 - alpha - beta); // Initial estimate, will be updated with actual data
+ WarmupPeriod = period + 1; // Need one extra period for returns
+ Name = $"CV({_period})";
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Cv(object source, int period = 20, double alpha = 0.1, double beta = 0.8) : this(period, alpha, beta)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _prevClose = 0;
+ _prevVariance = 0;
+ _isInitialized = false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Skip first period to establish previous close
+ if (_index == 1)
+ {
+ _prevClose = BarInput.Close;
+ return 0;
+ }
+
+ // Calculate return
+ double return_ = (BarInput.Close - _prevClose) / _prevClose;
+ double squaredReturn = return_ * return_;
+ _prevClose = BarInput.Close;
+
+ // Initialize with first available data if not done
+ if (!_isInitialized && _index > _period)
+ {
+ double _longTermVariance = squaredReturn; // Use current squared return as initial estimate
+ _prevVariance = _longTermVariance;
+ _isInitialized = true;
+ }
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0;
+ }
+
+ // Update variance estimate using GARCH(1,1)
+ double variance = _omega + _alpha * squaredReturn + _beta * _prevVariance;
+ _prevVariance = variance;
+
+ // Calculate annualized volatility as percentage
+ double volatility = Math.Sqrt(variance) * Math.Sqrt(252) * 100;
+
+ IsHot = _index >= WarmupPeriod;
+ return volatility;
+ }
+}
diff --git a/lib/volatility/Cvi.cs b/lib/volatility/Cvi.cs
index bc16cbbb..25c3912d 100644
--- a/lib/volatility/Cvi.cs
+++ b/lib/volatility/Cvi.cs
@@ -2,65 +2,63 @@ using System.Runtime.CompilerServices;
namespace QuanTAlib;
///
-/// CVI: Chaikin's Volatility
-/// A technical indicator developed by Marc Chaikin that measures the volatility of a financial instrument by comparing the spread between the high and low prices.
+/// CVI: Chaikin's Volatility Index
+/// Measures the rate of change of a moving average of the difference
+/// between high and low prices, indicating volatility expansion/contraction.
///
///
/// The CVI calculation process:
-/// 1. Calculates the difference between the high and low prices.
-/// 2. Applies an exponential moving average (EMA) to the differences.
-/// 3. Computes the percentage change in the EMA over a specified period.
+/// 1. Calculate High-Low difference
+/// 2. Take EMA of High-Low difference
+/// 3. Calculate ROC of the EMA over specified period
///
/// Key characteristics:
-/// - Measures volatility
-/// - Uses high and low prices
-/// - Percentage-based
-/// - EMA smoothing
+/// - Measures volatility expansion/contraction
+/// - Default period is 10 days
+/// - Default smoothing period is 10 days
+/// - Positive values indicate expanding volatility
+/// - Negative values indicate contracting volatility
///
/// Formula:
-/// CVI = (EMA(high - low, period) - EMA(high - low, period, offset)) / EMA(high - low, period, offset) * 100
+/// HL = High - Low
+/// Smoothed = EMA(HL, smoothPeriod)
+/// CVI = ((Smoothed - Smoothed[period]) / Smoothed[period]) * 100
///
/// Market Applications:
-/// - Volatility assessment
-/// - Trend confirmation
-/// - Risk management
-/// - Entry/exit timing
+/// - Volatility measurement
+/// - Trend strength analysis
+/// - Market regime identification
+/// - Trading range analysis
+/// - Breakout confirmation
///
/// Sources:
-/// Marc Chaikin - Original development
-/// https://www.investopedia.com/terms/c/chaikins-volatility.asp
+/// Marc Chaikin
+/// https://www.investopedia.com/terms/c/chaikinvolatility.asp
///
-/// Note: Higher CVI values indicate higher volatility
+/// Note: Returns percentage change in volatility
///
[SkipLocalsInit]
public sealed class Cvi : AbstractBase
{
private readonly int _period;
- private readonly Ema _ema;
- private readonly CircularBuffer _buffer;
- private double _prevEma;
+ private readonly CircularBuffer _smoothed;
+ private readonly double _alpha;
+ private double _ema;
- /// The number of periods for CVI calculation.
- /// Thrown when period is less than 1.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Cvi(int period)
+ public Cvi(int period = 10, int smoothPeriod = 10)
{
- if (period < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period),
- "Period must be greater than or equal to 1.");
- }
_period = period;
- _ema = new Ema(period);
- _buffer = new CircularBuffer(period);
- WarmupPeriod = period;
- Name = $"CVI({period})";
+ _alpha = 2.0 / (smoothPeriod + 1);
+ WarmupPeriod = _period + smoothPeriod;
+ Name = $"CVI({_period},{smoothPeriod})";
+ _smoothed = new CircularBuffer(_period);
+ Init();
}
/// The data source object that publishes updates.
- /// The number of periods for CVI calculation.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Cvi(object source, int period) : this(period)
+ public Cvi(object source, int period = 10, int smoothPeriod = 10) : this(period, smoothPeriod)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
@@ -70,9 +68,8 @@ public sealed class Cvi : AbstractBase
public override void Init()
{
base.Init();
- _ema.Init();
- _buffer.Clear();
- _prevEma = 0;
+ _ema = 0;
+ _smoothed.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -80,6 +77,7 @@ public sealed class Cvi : AbstractBase
{
if (isNew)
{
+ _lastValidValue = Value;
_index++;
}
}
@@ -89,20 +87,32 @@ public sealed class Cvi : AbstractBase
{
ManageState(BarInput.IsNew);
- double highLowDiff = BarInput.High - BarInput.Low;
- _buffer.Add(highLowDiff, BarInput.IsNew);
+ // Calculate High-Low difference
+ double hl = BarInput.High - BarInput.Low;
- double ema = _ema.Calc(new TValue(Input.Time, highLowDiff, BarInput.IsNew)).Value;
-
- double cvi = 0;
- if (_index >= _period)
+ // Calculate EMA of High-Low difference
+ if (_index == 1)
{
- double prevEma = _buffer[_buffer.Count - _period];
- cvi = (ema - prevEma) / prevEma * 100;
+ _ema = hl;
+ }
+ else
+ {
+ _ema = (_alpha * hl) + ((1 - _alpha) * _ema);
}
- _prevEma = ema;
+ // Add smoothed value to buffer
+ _smoothed.Add(_ema);
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0;
+ }
+
+ // Calculate rate of change
+ double roc = ((_ema - _smoothed[_period - 1]) / _smoothed[_period - 1]) * 100;
+
IsHot = _index >= WarmupPeriod;
- return cvi;
+ return roc;
}
}
diff --git a/lib/volatility/Ewma.cs b/lib/volatility/Ewma.cs
new file mode 100644
index 00000000..52fb9443
--- /dev/null
+++ b/lib/volatility/Ewma.cs
@@ -0,0 +1,141 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// EWMA: Exponential Weighted Moving Average Volatility
+/// A volatility measure that gives more weight to recent observations,
+/// calculated using squared returns and exponential weighting.
+///
+///
+/// The EWMA calculation process:
+/// 1. Calculate returns: (Close[t] - Close[t-1])/Close[t-1]
+/// 2. Square returns
+/// 3. Apply exponential weighting to squared returns
+/// 4. Take square root and annualize
+///
+/// Key characteristics:
+/// - More responsive to recent volatility changes
+/// - Default decay factor (lambda) is 0.94
+/// - Default period is 20 days
+/// - Annualized by default (multiply by sqrt(252))
+/// - Expressed as a percentage
+///
+/// Formula:
+/// Returns[t] = (Close[t] - Close[t-1])/Close[t-1]
+/// EWMA[t] = λ * EWMA[t-1] + (1-λ) * Returns[t]²
+/// Volatility = sqrt(EWMA) * sqrt(252) * 100
+///
+/// Where:
+/// λ (lambda) = decay factor (typically 0.94)
+///
+/// Market Applications:
+/// - Risk measurement
+/// - Option pricing
+/// - Value at Risk (VaR)
+/// - Portfolio optimization
+/// - Volatility forecasting
+///
+/// Sources:
+/// RiskMetrics™ Technical Document (1996)
+/// https://www.msci.com/documents/10199/5915b101-4206-4ba0-aee2-3449d5c7e95a
+///
+/// Note: Returns annualized volatility as a percentage
+///
+
+[SkipLocalsInit]
+public sealed class Ewma : AbstractBase
+{
+ private readonly int _period;
+ private readonly double _lambda;
+ private readonly bool _annualize;
+ private double _prevClose;
+ private double _ewma;
+ private bool _isInitialized;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Ewma(int period = 20, double lambda = 0.94, bool annualize = true)
+ {
+ _period = period;
+ _lambda = lambda;
+ _annualize = annualize;
+ WarmupPeriod = period + 1; // Need one extra period for returns
+ Name = $"EWMA({_period},{_lambda})";
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Ewma(object source, int period = 20, double lambda = 0.94, bool annualize = true) : this(period, lambda, annualize)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _prevClose = 0;
+ _ewma = 0;
+ _isInitialized = false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Skip first period to establish previous close
+ if (_index == 1)
+ {
+ _prevClose = BarInput.Close;
+ return 0;
+ }
+
+ // Calculate return
+ double return_ = (BarInput.Close - _prevClose) / _prevClose;
+ double squaredReturn = return_ * return_;
+ _prevClose = BarInput.Close;
+
+ // Initialize EWMA if not done
+ if (!_isInitialized && _index > _period)
+ {
+ _ewma = squaredReturn;
+ _isInitialized = true;
+ }
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0;
+ }
+
+ // Update EWMA
+ _ewma = _lambda * _ewma + (1 - _lambda) * squaredReturn;
+
+ // Calculate volatility
+ double volatility = Math.Sqrt(_ewma);
+
+ // Annualize if requested
+ if (_annualize)
+ {
+ volatility *= Math.Sqrt(252);
+ }
+
+ // Convert to percentage
+ volatility *= 100;
+
+ IsHot = _index >= WarmupPeriod;
+ return volatility;
+ }
+}
diff --git a/lib/volatility/Fcb.cs b/lib/volatility/Fcb.cs
new file mode 100644
index 00000000..45c61aa9
--- /dev/null
+++ b/lib/volatility/Fcb.cs
@@ -0,0 +1,159 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// FCB: Fractal Chaos Bands
+/// Adaptive price bands based on fractal geometry concepts,
+/// identifying potential support and resistance levels.
+///
+///
+/// The FCB calculation process:
+/// 1. Identify fractal highs and lows over the period
+/// 2. Calculate high and low bands using fractal points
+/// 3. Smooth bands using exponential moving average
+///
+/// Key characteristics:
+/// - Adapts to market structure
+/// - Default period is 20 days
+/// - Default smoothing factor is 0.5
+/// - Returns upper and lower bands
+/// - Based on fractal geometry concepts
+///
+/// Formula:
+/// Fractal High = High[t] where High[t] > High[t±1,2]
+/// Fractal Low = Low[t] where Low[t] < Low[t±1,2]
+/// Upper Band = EMA(Fractal Highs, smoothing)
+/// Lower Band = EMA(Fractal Lows, smoothing)
+///
+/// Market Applications:
+/// - Support/resistance identification
+/// - Trend analysis
+/// - Volatility measurement
+/// - Breakout detection
+/// - Trading range analysis
+///
+/// Sources:
+/// Bill Williams' Chaos Theory
+/// Trading Chaos (2nd Edition) by Bill Williams
+///
+/// Note: Returns three values: upper, middle, and lower bands
+///
+
+[SkipLocalsInit]
+public sealed class Fcb : AbstractBase
+{
+ private readonly double _smoothing;
+ private readonly CircularBuffer _highs;
+ private readonly CircularBuffer _lows;
+ private double _upperBand;
+ private double _middleBand;
+ private double _lowerBand;
+ private double _upperEma;
+ private double _lowerEma;
+ private readonly double _alpha;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Fcb(int period = 20, double smoothing = 0.5)
+ {
+ _smoothing = smoothing;
+ _alpha = 2.0 / (period + 1);
+ WarmupPeriod = period + 4; // Need extra periods for fractal identification
+ Name = $"FCB({period},{_smoothing})";
+ _highs = new CircularBuffer(period);
+ _lows = new CircularBuffer(period);
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Fcb(object source, int period = 20, double smoothing = 0.5) : this(period, smoothing)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _upperBand = 0;
+ _middleBand = 0;
+ _lowerBand = 0;
+ _upperEma = 0;
+ _lowerEma = 0;
+ _highs.Clear();
+ _lows.Clear();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Add current high/low to buffers
+ _highs.Add(BarInput.High);
+ _lows.Add(BarInput.Low);
+
+ // Need enough values for calculation
+ if (_index <= 4)
+ {
+ return 0;
+ }
+
+ // Check for fractal patterns
+ bool isFractalHigh = false;
+ bool isFractalLow = false;
+
+ // Fractal high: current high is higher than 2 bars before and after
+ isFractalHigh = _highs[2] > _highs[0] && _highs[2] > _highs[1] &&
+ _highs[2] > _highs[3] && _highs[2] > _highs[4];
+
+ // Fractal low: current low is lower than 2 bars before and after
+ isFractalLow = _lows[2] < _lows[0] && _lows[2] < _lows[1] &&
+ _lows[2] < _lows[3] && _lows[2] < _lows[4];
+
+
+ // Update EMAs with fractal points
+ if (isFractalHigh)
+ {
+ _upperEma = (_alpha * _highs[2]) + ((1 - _alpha) * _upperEma);
+ }
+ if (isFractalLow)
+ {
+ _lowerEma = (_alpha * _lows[2]) + ((1 - _alpha) * _lowerEma);
+ }
+
+ // Apply smoothing to bands
+ _upperBand = _smoothing * _upperEma + (1 - _smoothing) * BarInput.High;
+ _lowerBand = _smoothing * _lowerEma + (1 - _smoothing) * BarInput.Low;
+ _middleBand = (_upperBand + _lowerBand) / 2;
+
+ IsHot = _index >= WarmupPeriod;
+ return _middleBand; // Return middle band as primary value
+ }
+
+ ///
+ /// Gets the upper band value
+ ///
+ public double UpperBand => _upperBand;
+
+ ///
+ /// Gets the middle band value
+ ///
+ public double MiddleBand => _middleBand;
+
+ ///
+ /// Gets the lower band value
+ ///
+ public double LowerBand => _lowerBand;
+}
diff --git a/lib/volatility/Gkv.cs b/lib/volatility/Gkv.cs
new file mode 100644
index 00000000..19baebdb
--- /dev/null
+++ b/lib/volatility/Gkv.cs
@@ -0,0 +1,127 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// GKV: Garman-Klass Volatility
+/// An efficient estimator of volatility that uses open, high, low,
+/// and close prices to capture intraday price movements.
+///
+///
+/// The GKV calculation process:
+/// 1. Calculate components using OHLC prices
+/// 2. Combine components using optimal weights
+/// 3. Take rolling average over period
+/// 4. Annualize and convert to percentage
+///
+/// Key characteristics:
+/// - More efficient than close-to-close volatility
+/// - Uses full OHLC price information
+/// - Default period is 20 days
+/// - Annualized by default
+/// - Expressed as a percentage
+///
+/// Formula:
+/// u = ln(High/Low)²/2
+/// c = ln(Close/Open)²
+/// GKV = sqrt(sum((0.5*u - (2*ln(2)-1)*c) / period) * 252) * 100
+///
+/// Market Applications:
+/// - Volatility estimation
+/// - Risk measurement
+/// - Option pricing
+/// - Trading strategy development
+/// - Market analysis
+///
+/// Sources:
+/// Garman and Klass (1980)
+/// Journal of Business 53(1): 67-78
+///
+/// Note: Returns annualized volatility as a percentage
+///
+
+[SkipLocalsInit]
+public sealed class Gkv : AbstractBase
+{
+ private readonly int _period;
+ private readonly bool _annualize;
+ private readonly CircularBuffer _components;
+ private readonly double _ln2;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Gkv(int period = 20, bool annualize = true)
+ {
+ _period = period;
+ _annualize = annualize;
+ WarmupPeriod = period;
+ Name = $"GKV({_period})";
+ _components = new CircularBuffer(period);
+ _ln2 = Math.Log(2);
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Gkv(object source, int period = 20, bool annualize = true) : this(period, annualize)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _components.Clear();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Calculate components
+ double u = Math.Log(BarInput.High / BarInput.Low);
+ u = u * u / 2;
+
+ double c = Math.Log(BarInput.Close / BarInput.Open);
+ c = c * c;
+
+ // Combine components with optimal weights
+ double component = 0.5 * u - (2 * _ln2 - 1) * c;
+ _components.Add(component);
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0;
+ }
+
+ // Calculate average component
+ double avgComponent = _components.Average();
+
+ // Calculate volatility
+ double volatility = Math.Sqrt(avgComponent);
+
+ // Annualize if requested
+ if (_annualize)
+ {
+ volatility *= Math.Sqrt(252);
+ }
+
+ // Convert to percentage
+ volatility *= 100;
+
+ IsHot = _index >= WarmupPeriod;
+ return volatility;
+ }
+}
diff --git a/lib/volatility/Hlv.cs b/lib/volatility/Hlv.cs
new file mode 100644
index 00000000..63a0b3ea
--- /dev/null
+++ b/lib/volatility/Hlv.cs
@@ -0,0 +1,130 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// HLV: High-Low Volatility
+/// A volatility measure based on the high-low range relative
+/// to the previous close, capturing intraday price movements.
+///
+///
+/// The HLV calculation process:
+/// 1. Calculate normalized high-low range
+/// 2. Take rolling average over period
+/// 3. Convert to annualized volatility
+///
+/// Key characteristics:
+/// - Captures intraday price movements
+/// - Uses high, low, and previous close
+/// - Default period is 20 days
+/// - Annualized by default
+/// - Expressed as a percentage
+///
+/// Formula:
+/// Range = (High - Low) / PrevClose
+/// HLV = sqrt(sum(Range² / period) * 252) * 100
+///
+/// Market Applications:
+/// - Volatility measurement
+/// - Risk assessment
+/// - Trading range analysis
+/// - Market regime identification
+/// - Position sizing
+///
+/// Sources:
+/// Parkinson (1980) modified
+/// The Extreme Value Method for Estimating the Variance of the Rate of Return
+/// Journal of Business 53(1): 61-65
+///
+/// Note: Returns annualized volatility as a percentage
+///
+
+[SkipLocalsInit]
+public sealed class Hlv : AbstractBase
+{
+ private readonly int _period;
+ private readonly bool _annualize;
+ private readonly CircularBuffer _ranges;
+ private double _prevClose;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Hlv(int period = 20, bool annualize = true)
+ {
+ _period = period;
+ _annualize = annualize;
+ WarmupPeriod = period + 1; // Need one extra period for previous close
+ Name = $"HLV({_period})";
+ _ranges = new CircularBuffer(period);
+ Init();
+ }
+
+ /// The data source object that publishes updates.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Hlv(object source, int period = 20, bool annualize = true) : this(period, annualize)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _prevClose = 0;
+ _ranges.Clear();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Skip first period to establish previous close
+ if (_index == 1)
+ {
+ _prevClose = BarInput.Close;
+ return 0;
+ }
+
+ // Calculate normalized range
+ double range = (BarInput.High - BarInput.Low) / _prevClose;
+ double squaredRange = range * range;
+ _ranges.Add(squaredRange);
+
+ // Store current close for next calculation
+ _prevClose = BarInput.Close;
+
+ // Need enough values for calculation
+ if (_index <= _period)
+ {
+ return 0;
+ }
+
+ // Calculate average squared range
+ double avgSquaredRange = _ranges.Average();
+
+ // Calculate volatility
+ double volatility = Math.Sqrt(avgSquaredRange);
+
+ // Annualize if requested
+ if (_annualize)
+ {
+ volatility *= Math.Sqrt(252);
+ }
+
+ // Convert to percentage
+ volatility *= 100;
+
+ IsHot = _index >= WarmupPeriod;
+ return volatility;
+ }
+}
diff --git a/lib/volatility/_list.md b/lib/volatility/_list.md
index 49854141..9e0f4094 100644
--- a/lib/volatility/_list.md
+++ b/lib/volatility/_list.md
@@ -1,21 +1,21 @@
# Volatility indicators
-Done: 15, Todo: 20
+Done: 24, Todo: 11
✔️ ADR - Average Daily Range
✔️ AP - Andrew's Pitchfork
✔️ ATR - Average True Range
✔️ ATRP - Average True Range Percent
✔️ ATRS - ATR Trailing Stop
-*BB - Bollinger Bands® (Upper, Middle, Lower)
-CCV - Close-to-Close Volatility
-CE - Chandelier Exit
-CV - Conditional Volatility (ARCH/GARCH)
-CVI - Chaikin's Volatility
+✔️ BBAND - Bollinger Bands® (Upper, Middle, Lower)
+✔️ CCV - Close-to-Close Volatility
+✔️ CE - Chandelier Exit
+✔️ CV - Conditional Volatility (ARCH/GARCH)
+✔️ CVI - Chaikin's Volatility
*DC - Donchian Channels (Upper, Middle, Lower)
-EWMA - Exponential Weighted Moving Average Volatility
-FCB - Fractal Chaos Bands
-GKV - Garman-Klass Volatility
-HLV - High-Low Volatility
+✔️ EWMA - Exponential Weighted Moving Average Volatility
+✔️ FCB - Fractal Chaos Bands
+✔️ GKV - Garman-Klass Volatility
+✔️ HLV - High-Low Volatility
✔️ HV - Historical Volatility
*ICH - Ichimoku Cloud (Conversion, Base, Leading Span A, Leading Span B, Lagging Span)
✔️ JVOLTY - Jurik Volatility
diff --git a/quantower/Averages/_Averages.csproj b/quantower/Averages/_Averages.csproj
index 8d946a96..ea03d5a8 100644
--- a/quantower/Averages/_Averages.csproj
+++ b/quantower/Averages/_Averages.csproj
@@ -2,13 +2,10 @@
Averages
Indicator
- 0.0.0.0
bin\$(Configuration)\
- true
- true
- true
false
+
@@ -16,7 +13,6 @@
-
..\..\.github\TradingPlatform.BusinessLayer.dll
@@ -32,4 +28,4 @@
DestinationFolder="$(QuantowerRoot)\Settings\Scripts\Indicators\QuanTAlib\Averages" />
-
+
\ No newline at end of file
diff --git a/quantower/Momentum/_Momentum.csproj b/quantower/Momentum/_Momentum.csproj
index 00cb1a71..29adbe09 100644
--- a/quantower/Momentum/_Momentum.csproj
+++ b/quantower/Momentum/_Momentum.csproj
@@ -2,13 +2,10 @@
Momentum
Indicator
- 0.0.0.0
bin\$(Configuration)\
- true
- true
- true
false
+
@@ -30,4 +27,4 @@
-
+
\ No newline at end of file
diff --git a/quantower/Oscillators/_Oscillators.csproj b/quantower/Oscillators/_Oscillators.csproj
index ed3a39fe..11c13ec8 100644
--- a/quantower/Oscillators/_Oscillators.csproj
+++ b/quantower/Oscillators/_Oscillators.csproj
@@ -2,13 +2,10 @@
Oscillators
Indicator
- 0.0.0.0
bin\$(Configuration)\
- true
- true
- true
false
+
@@ -30,4 +27,4 @@
-
+
\ No newline at end of file
diff --git a/quantower/Statistics/_Statistics.csproj b/quantower/Statistics/_Statistics.csproj
index 4904fae6..f67a5d55 100644
--- a/quantower/Statistics/_Statistics.csproj
+++ b/quantower/Statistics/_Statistics.csproj
@@ -2,13 +2,10 @@
Statistics
Indicator
- 0.0.0.0
bin\$(Configuration)\
- true
- true
- true
false
+
diff --git a/quantower/Volatility/_Volatility.csproj b/quantower/Volatility/_Volatility.csproj
index 0a8f58d8..2612c385 100644
--- a/quantower/Volatility/_Volatility.csproj
+++ b/quantower/Volatility/_Volatility.csproj
@@ -2,13 +2,10 @@
Volatility
Indicator
- 0.0.0.0
bin\$(Configuration)\
- true
- true
- true
false
+
@@ -16,7 +13,6 @@
-
..\..\.github\TradingPlatform.BusinessLayer.dll
@@ -31,4 +27,4 @@
-
+
\ No newline at end of file
diff --git a/quantower/Volume/_Volume.csproj b/quantower/Volume/_Volume.csproj
index 13266f2a..db6a0d69 100644
--- a/quantower/Volume/_Volume.csproj
+++ b/quantower/Volume/_Volume.csproj
@@ -2,13 +2,10 @@
Volume
Indicator
- 0.0.0.0
bin\$(Configuration)\
- true
- true
- true
false
+
@@ -30,4 +27,4 @@
-
+
\ No newline at end of file