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 835aafd1..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 @@ -69,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 @@ -76,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" @@ -87,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 }} @@ -109,7 +163,13 @@ jobs: 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 @@ -119,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 @@ -132,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@v2 - - - 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')) || @@ -203,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 @@ -227,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: @@ -248,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/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/lib/averages/Dsma.cs b/lib/averages/Dsma.cs index 31f5ba9d..238a78f7 100644 --- a/lib/averages/Dsma.cs +++ b/lib/averages/Dsma.cs @@ -22,7 +22,7 @@ namespace QuanTAlib; public class Dsma : AbstractBase { 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 @@ -59,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/volatility/Cv.cs b/lib/volatility/Cv.cs index 2d1f88c2..77dd3fff 100644 --- a/lib/volatility/Cv.cs +++ b/lib/volatility/Cv.cs @@ -53,7 +53,6 @@ public sealed class Cv : AbstractBase private readonly double _omega; private double _prevClose; private double _prevVariance; - private double _longTermVariance; private bool _isInitialized; [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -82,7 +81,6 @@ public sealed class Cv : AbstractBase base.Init(); _prevClose = 0; _prevVariance = 0; - _longTermVariance = 0; _isInitialized = false; } @@ -116,7 +114,7 @@ public sealed class Cv : AbstractBase // Initialize with first available data if not done if (!_isInitialized && _index > _period) { - _longTermVariance = squaredReturn; // Use current squared return as initial estimate + double _longTermVariance = squaredReturn; // Use current squared return as initial estimate _prevVariance = _longTermVariance; _isInitialized = true; } diff --git a/lib/volatility/Cvi.cs b/lib/volatility/Cvi.cs index c9f5c4d0..25c3912d 100644 --- a/lib/volatility/Cvi.cs +++ b/lib/volatility/Cvi.cs @@ -41,7 +41,6 @@ namespace QuanTAlib; public sealed class Cvi : AbstractBase { private readonly int _period; - private readonly int _smoothPeriod; private readonly CircularBuffer _smoothed; private readonly double _alpha; private double _ema; @@ -50,10 +49,9 @@ public sealed class Cvi : AbstractBase public Cvi(int period = 10, int smoothPeriod = 10) { _period = period; - _smoothPeriod = smoothPeriod; - _alpha = 2.0 / (_smoothPeriod + 1); - WarmupPeriod = _period + _smoothPeriod; - Name = $"CVI({_period},{_smoothPeriod})"; + _alpha = 2.0 / (smoothPeriod + 1); + WarmupPeriod = _period + smoothPeriod; + Name = $"CVI({_period},{smoothPeriod})"; _smoothed = new CircularBuffer(_period); Init(); } diff --git a/lib/volatility/Fcb.cs b/lib/volatility/Fcb.cs index 11f99dc1..45c61aa9 100644 --- a/lib/volatility/Fcb.cs +++ b/lib/volatility/Fcb.cs @@ -42,7 +42,6 @@ namespace QuanTAlib; [SkipLocalsInit] public sealed class Fcb : AbstractBase { - private readonly int _period; private readonly double _smoothing; private readonly CircularBuffer _highs; private readonly CircularBuffer _lows; @@ -56,11 +55,10 @@ public sealed class Fcb : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Fcb(int period = 20, double smoothing = 0.5) { - _period = period; _smoothing = smoothing; - _alpha = 2.0 / (_period + 1); + _alpha = 2.0 / (period + 1); WarmupPeriod = period + 4; // Need extra periods for fractal identification - Name = $"FCB({_period},{_smoothing})"; + Name = $"FCB({period},{_smoothing})"; _highs = new CircularBuffer(period); _lows = new CircularBuffer(period); Init(); @@ -116,16 +114,14 @@ public sealed class Fcb : AbstractBase bool isFractalHigh = false; bool isFractalLow = false; - if (_index >= 5) - { - // 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 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]; - // 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)