diff --git a/.github/sonarscanner.ps1 b/.github/sonarscanner.ps1
new file mode 100644
index 00000000..2692cb29
--- /dev/null
+++ b/.github/sonarscanner.ps1
@@ -0,0 +1,85 @@
+#Requires -Version 7.0
+<#
+.SYNOPSIS
+ Static Analysis Scanner for QuanTAlib
+.DESCRIPTION
+ Runs SonarCloud and Qodana with code coverage on Windows
+#>
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+# Load tokens from User environment if not in process environment
+if (-not $env:SONAR_TOKEN) {
+ $env:SONAR_TOKEN = [System.Environment]::GetEnvironmentVariable("SONAR_TOKEN", "User")
+}
+if (-not $env:QODANA_TOKEN) {
+ $env:QODANA_TOKEN = [System.Environment]::GetEnvironmentVariable("QODANA_TOKEN", "User")
+}
+
+# Verify tokens are available
+if (-not $env:SONAR_TOKEN) {
+ Write-Error "SONAR_TOKEN environment variable not set"
+ exit 1
+}
+if (-not $env:QODANA_TOKEN) {
+ Write-Error "QODANA_TOKEN environment variable not set"
+ exit 1
+}
+
+# Coverage output directory for Qodana
+$CoverageDir = ".qodana/code-coverage"
+New-Item -ItemType Directory -Force -Path $CoverageDir | Out-Null
+
+# Start SonarScanner analysis context
+Write-Host "==> Starting SonarScanner analysis..." -ForegroundColor Cyan
+dotnet-sonarscanner begin `
+ /o:"mihakralj-quantalib" `
+ /k:"mihakralj_QuanTAlib" `
+ /d:sonar.token="$env:SONAR_TOKEN" `
+ /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml"
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+# Build solution (within SonarScanner context)
+Write-Host "==> Building solution..." -ForegroundColor Cyan
+dotnet build --no-incremental
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+# Run tests with coverage (opencover format works for both SonarCloud and can be converted)
+Write-Host "==> Running tests with coverage..." -ForegroundColor Cyan
+dotnet test --no-build --collect:"XPlat Code Coverage" `
+ -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover,lcov
+# Continue even if tests fail - we still want coverage data for analysis
+
+# Copy coverage files to Qodana directory with unique names
+Write-Host "==> Copying coverage files to Qodana directory..." -ForegroundColor Cyan
+$coverageFiles = Get-ChildItem -Recurse -Filter "coverage.info" -ErrorAction SilentlyContinue
+if ($coverageFiles) {
+ $index = 0
+ foreach ($file in $coverageFiles) {
+ $destName = "coverage_$index.info"
+ Copy-Item $file.FullName -Destination (Join-Path $CoverageDir $destName) -Force
+ Write-Host " Copied: $($file.FullName) -> $destName" -ForegroundColor Gray
+ $index++
+ }
+} else {
+ Write-Host " WARNING: No coverage.info files found" -ForegroundColor Yellow
+}
+
+# End SonarScanner analysis
+Write-Host "==> Completing SonarScanner analysis..." -ForegroundColor Cyan
+dotnet-sonarscanner end /d:sonar.token="$env:SONAR_TOKEN"
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+Write-Host "==> SonarCloud: https://sonarcloud.io/project/overview?id=mihakralj_QuanTAlib" -ForegroundColor Green
+
+# Run Qodana
+Write-Host "==> Starting Qodana analysis..." -ForegroundColor Cyan
+
+# Set CI environment to suppress interactive prompts
+$env:CI = "true"
+qodana scan --within-docker=false -l qodana-dotnet --coverage-dir $CoverageDir
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+Write-Host "==> Qodana analysis complete" -ForegroundColor Green
+Write-Host "==> Done!" -ForegroundColor Green
diff --git a/.gitignore b/.gitignore
index 917ca329..b79d48d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -413,3 +413,7 @@ ilspy/
#Ignore insiders AI rules
.github/instructions/codacy.instructions.md
+
+
+#Ignore vscode AI rules
+.github\instructions\codacy.instructions.md
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1eda022f..bc57a89e 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -170,7 +170,9 @@
"sonarlint.connectedMode.project": {
"connectionId": "mihakralj-quantalib",
"projectKey": "mihakralj_QuanTAlib"
- }
+ },
+ "qodana.projectId": "KbxmN"
+ // Note: Native mode is configured in qodana.yaml with withinDocker: false
// ???????????????????????????????????????????????????????????????????
// Keyboard Shortcuts Reference
diff --git a/Directory.Build.props b/Directory.Build.props
index f4ab4530..78ff3800 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -44,12 +44,11 @@
- S1144,S1944,S2053,S2222,S2259,S2583,S2589,S3329,S3655,S3776,S3900,S3949,S3966,S4158,S4347,S5773,S6781
+ S1144,S1944,S2053,S2222,S2245,S2259,S2583,S2589,S3329,S3655,S3776,S3900,S3949,S3966,S4158,S4347,S5773,S6781
-
diff --git a/GitVersion.yml b/GitVersion.yml
index 2ea209fe..171a6c3e 100644
--- a/GitVersion.yml
+++ b/GitVersion.yml
@@ -1,25 +1,31 @@
+workflow: GitHubFlow/v1
assembly-versioning-scheme: MajorMinorPatch
assembly-file-versioning-scheme: MajorMinorPatch
+tag-prefix: '[vV]'
major-version-bump-message: '\+semver:\s?(breaking|major)'
minor-version-bump-message: '\+semver:\s?(feature|minor)'
patch-version-bump-message: '\+semver:\s?(fix|patch)'
no-bump-message: '\+semver:\s?(none|skip)'
-tag-prefix: '[vV]'
branches:
main:
- regex: ^main$
mode: ContinuousDeployment
+ label: ''
increment: Patch
+ prevent-increment:
+ of-merged-branch: true
track-merge-target: false
is-release-branch: true
+ is-main-branch: true
pre-release-weight: 0
develop:
- regex: ^dev(elop)?(ment)?$
mode: ContinuousDelivery
+ label: alpha
increment: Patch
track-merge-target: true
- is-release-branch: false
- source-branches: ['main']
+ track-merge-message: true
+ regex: ^dev(elop)?(ment)?$
+ source-branches:
+ - main
pre-release-weight: 30000
ignore:
sha: []
diff --git a/lib/QuanTAlib.Tests.csproj b/lib/QuanTAlib.Tests.csproj
index b4cc5f7b..7350b73e 100644
--- a/lib/QuanTAlib.Tests.csproj
+++ b/lib/QuanTAlib.Tests.csproj
@@ -22,6 +22,8 @@
+
+
diff --git a/lib/averages/ema/Ema.Tests.cs b/lib/averages/ema/Ema.Tests.cs
index a3745376..56d9b439 100644
--- a/lib/averages/ema/Ema.Tests.cs
+++ b/lib/averages/ema/Ema.Tests.cs
@@ -1,7 +1,3 @@
-using System;
-using Xunit;
-using QuanTAlib;
-
namespace QuanTAlib.Tests;
public class EmaTests
diff --git a/lib/averages/ema/Ema.Validation.Tests.cs b/lib/averages/ema/Ema.Validation.Tests.cs
index 898c1d9d..3c56aa0e 100644
--- a/lib/averages/ema/Ema.Validation.Tests.cs
+++ b/lib/averages/ema/Ema.Validation.Tests.cs
@@ -4,9 +4,7 @@ using System.Linq;
using Skender.Stock.Indicators;
using TALib;
using Tulip;
-using Xunit;
using Xunit.Abstractions;
-using QuanTAlib;
namespace QuanTAlib.Tests;
@@ -213,7 +211,7 @@ public class EmaValidationTests
// Calculate Tulip EMA
var emaIndicator = Tulip.Indicators.ema;
double[][] inputs = { tData };
- double[] options = { (double)period };
+ double[] options = { period };
double[][] outputs = { new double[tData.Length] };
emaIndicator.Run(inputs, options, outputs);
@@ -246,7 +244,7 @@ public class EmaValidationTests
// Calculate Tulip EMA
var emaIndicator = Tulip.Indicators.ema;
double[][] inputs = { tData };
- double[] options = { (double)period };
+ double[] options = { period };
double[][] outputs = { new double[tData.Length] };
emaIndicator.Run(inputs, options, outputs);
@@ -275,7 +273,7 @@ public class EmaValidationTests
// Calculate Tulip EMA
var emaIndicator = Tulip.Indicators.ema;
double[][] inputs = { sourceData };
- double[] options = { (double)period };
+ double[] options = { period };
double[][] outputs = { new double[sourceData.Length] };
emaIndicator.Run(inputs, options, outputs);
diff --git a/lib/averages/ema/Ema.cs b/lib/averages/ema/Ema.cs
index ccb1f023..8b12123e 100644
--- a/lib/averages/ema/Ema.cs
+++ b/lib/averages/ema/Ema.cs
@@ -25,12 +25,25 @@ namespace QuanTAlib;
///
public class Ema
{
- private struct State
+ private struct State : IEquatable
{
public double Ema;
public double E;
public bool IsHot;
+
public static State New() => new() { Ema = 0, E = 1.0, IsHot = false };
+
+ public readonly bool Equals(State other) =>
+ Ema == other.Ema && E == other.E && IsHot == other.IsHot;
+
+ public override readonly bool Equals(object? obj) =>
+ obj is State other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ HashCode.Combine(Ema, E, IsHot);
+
+ public static bool operator ==(State left, State right) => left.Equals(right);
+ public static bool operator !=(State left, State right) => !left.Equals(right);
}
private readonly double _alpha;
@@ -60,7 +73,7 @@ public class Ema
///
/// Creates EMA with specified alpha smoothing factor.
///
- /// Smoothing factor (0 < alpha <= 1)
+ /// Smoothing factor (0 < alpha <= 1)
public Ema(double alpha)
{
if (alpha <= 0 || alpha > 1)
@@ -217,7 +230,7 @@ public class Ema
///
/// Input values
/// Output span (must be same length as source)
- /// Smoothing factor (0 < alpha <= 1)
+ /// Smoothing factor (0 < alpha <= 1)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan source, Span output, double alpha)
{
diff --git a/lib/averages/ema/EmaVector.Tests.cs b/lib/averages/ema/EmaVector.Tests.cs
index d635c1ca..ab466839 100644
--- a/lib/averages/ema/EmaVector.Tests.cs
+++ b/lib/averages/ema/EmaVector.Tests.cs
@@ -1,7 +1,3 @@
-using System;
-using System.Linq;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests;
diff --git a/lib/averages/sma/Sma.Tests.cs b/lib/averages/sma/Sma.Tests.cs
index 4fba1f6d..12334267 100644
--- a/lib/averages/sma/Sma.Tests.cs
+++ b/lib/averages/sma/Sma.Tests.cs
@@ -1,7 +1,3 @@
-using System;
-using Xunit;
-using QuanTAlib;
-
namespace QuanTAlib.Tests;
public class SmaTests
diff --git a/lib/averages/sma/Sma.Validation.Tests.cs b/lib/averages/sma/Sma.Validation.Tests.cs
index acebf2c7..d46fee42 100644
--- a/lib/averages/sma/Sma.Validation.Tests.cs
+++ b/lib/averages/sma/Sma.Validation.Tests.cs
@@ -4,9 +4,7 @@ using System.Linq;
using Skender.Stock.Indicators;
using TALib;
using Tulip;
-using Xunit;
using Xunit.Abstractions;
-using QuanTAlib;
namespace QuanTAlib.Tests;
@@ -213,7 +211,7 @@ public class SmaValidationTests
// Calculate Tulip SMA
var smaIndicator = Tulip.Indicators.sma;
double[][] inputs = { tData };
- double[] options = { (double)period };
+ double[] options = { period };
int lookback = period - 1;
double[][] outputs = { new double[tData.Length - lookback] };
@@ -247,7 +245,7 @@ public class SmaValidationTests
// Calculate Tulip SMA
var smaIndicator = Tulip.Indicators.sma;
double[][] inputs = { tData };
- double[] options = { (double)period };
+ double[] options = { period };
int lookback = period - 1;
double[][] outputs = { new double[tData.Length - lookback] };
@@ -277,7 +275,7 @@ public class SmaValidationTests
// Calculate Tulip SMA
var smaIndicator = Tulip.Indicators.sma;
double[][] inputs = { sourceData };
- double[] options = { (double)period };
+ double[] options = { period };
int lookback = period - 1;
double[][] outputs = { new double[sourceData.Length - lookback] };
diff --git a/lib/averages/sma/SmaVector.Tests.cs b/lib/averages/sma/SmaVector.Tests.cs
index 903bb9bb..99209d59 100644
--- a/lib/averages/sma/SmaVector.Tests.cs
+++ b/lib/averages/sma/SmaVector.Tests.cs
@@ -1,6 +1,3 @@
-using System.Linq;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests;
diff --git a/lib/averages/wma/Wma.Tests.cs b/lib/averages/wma/Wma.Tests.cs
index 0542eb39..414334e3 100644
--- a/lib/averages/wma/Wma.Tests.cs
+++ b/lib/averages/wma/Wma.Tests.cs
@@ -1,7 +1,3 @@
-using System;
-using Xunit;
-using QuanTAlib;
-
namespace QuanTAlib.Tests;
public class WmaTests
diff --git a/lib/averages/wma/Wma.Validation.Tests.cs b/lib/averages/wma/Wma.Validation.Tests.cs
index f686a300..633baed0 100644
--- a/lib/averages/wma/Wma.Validation.Tests.cs
+++ b/lib/averages/wma/Wma.Validation.Tests.cs
@@ -4,9 +4,7 @@ using System.Linq;
using Skender.Stock.Indicators;
using TALib;
using Tulip;
-using Xunit;
using Xunit.Abstractions;
-using QuanTAlib;
namespace QuanTAlib.Tests;
@@ -213,7 +211,7 @@ public class WmaValidationTests
// Calculate Tulip WMA
var wmaIndicator = Tulip.Indicators.wma;
double[][] inputs = { tData };
- double[] options = { (double)period };
+ double[] options = { period };
int lookback = period - 1;
double[][] outputs = { new double[tData.Length - lookback] };
@@ -247,7 +245,7 @@ public class WmaValidationTests
// Calculate Tulip WMA
var wmaIndicator = Tulip.Indicators.wma;
double[][] inputs = { tData };
- double[] options = { (double)period };
+ double[] options = { period };
int lookback = period - 1;
double[][] outputs = { new double[tData.Length - lookback] };
@@ -277,7 +275,7 @@ public class WmaValidationTests
// Calculate Tulip WMA
var wmaIndicator = Tulip.Indicators.wma;
double[][] inputs = { sourceData };
- double[] options = { (double)period };
+ double[] options = { period };
int lookback = period - 1;
double[][] outputs = { new double[sourceData.Length - lookback] };
diff --git a/lib/averages/wma/WmaVector.Tests.cs b/lib/averages/wma/WmaVector.Tests.cs
index 2cb70aaf..2805001d 100644
--- a/lib/averages/wma/WmaVector.Tests.cs
+++ b/lib/averages/wma/WmaVector.Tests.cs
@@ -1,6 +1,3 @@
-using System.Linq;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests;
diff --git a/lib/core/ringbuffer/RingBuffer.Tests.cs b/lib/core/ringbuffer/RingBuffer.Tests.cs
index 367d19b9..aa896951 100644
--- a/lib/core/ringbuffer/RingBuffer.Tests.cs
+++ b/lib/core/ringbuffer/RingBuffer.Tests.cs
@@ -1,7 +1,3 @@
-using System;
-using System.Collections;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests;
diff --git a/lib/core/ringbuffer/RingBuffer.cs b/lib/core/ringbuffer/RingBuffer.cs
index 0f92a190..6a6ba491 100644
--- a/lib/core/ringbuffer/RingBuffer.cs
+++ b/lib/core/ringbuffer/RingBuffer.cs
@@ -423,7 +423,7 @@ public sealed class RingBuffer : IEnumerable
///
/// High-performance enumerator for the RingBuffer.
///
- public struct Enumerator : IEnumerator
+ public struct Enumerator : IEnumerator, IEquatable
{
private readonly RingBuffer _buffer;
private readonly int _start;
@@ -453,8 +453,8 @@ public sealed class RingBuffer : IEnumerable
return true;
}
- public double Current => _current;
- object IEnumerator.Current => Current;
+ public readonly double Current => _current;
+ readonly object IEnumerator.Current => Current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
@@ -463,6 +463,22 @@ public sealed class RingBuffer : IEnumerable
_current = default;
}
- public void Dispose() { }
+ public readonly void Dispose() { }
+
+ public readonly bool Equals(Enumerator other) =>
+ ReferenceEquals(_buffer, other._buffer) &&
+ _start == other._start &&
+ _count == other._count &&
+ _index == other._index &&
+ _current == other._current;
+
+ public override readonly bool Equals(object? obj) =>
+ obj is Enumerator other && Equals(other);
+
+ public override readonly int GetHashCode() =>
+ HashCode.Combine(RuntimeHelpers.GetHashCode(_buffer), _start, _count, _index, _current);
+
+ public static bool operator ==(Enumerator left, Enumerator right) => left.Equals(right);
+ public static bool operator !=(Enumerator left, Enumerator right) => !left.Equals(right);
}
}
diff --git a/lib/core/simd/SimdExtensions.Tests.cs b/lib/core/simd/SimdExtensions.Tests.cs
index c9118924..67ba0ec0 100644
--- a/lib/core/simd/SimdExtensions.Tests.cs
+++ b/lib/core/simd/SimdExtensions.Tests.cs
@@ -1,6 +1,3 @@
-using System;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests;
@@ -550,11 +547,13 @@ public class SimdExtensionsTests
Assert.True(avg > 0);
Assert.True(min > 0);
Assert.True(max > min);
+ Assert.Equal(min, minAlt);
+ Assert.Equal(max, maxAlt);
Assert.True(variance > 0);
Assert.True(stdDev > 0);
- Assert.True(sw.ElapsedMilliseconds < 10,
- $"SIMD operations took {sw.ElapsedMilliseconds}ms, expected < 10ms");
+ Assert.True(sw.ElapsedMilliseconds < 50,
+ $"SIMD operations took {sw.ElapsedMilliseconds}ms, expected < 50ms");
}
[Fact]
diff --git a/lib/core/tbar/TBar.Tests.cs b/lib/core/tbar/TBar.Tests.cs
index 6d22c8c6..1f33fd3b 100644
--- a/lib/core/tbar/TBar.Tests.cs
+++ b/lib/core/tbar/TBar.Tests.cs
@@ -1,6 +1,3 @@
-using System;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests
{
diff --git a/lib/core/tbarseries/TBarSeries.Tests.cs b/lib/core/tbarseries/TBarSeries.Tests.cs
index e505ce3a..50e508a6 100644
--- a/lib/core/tbarseries/TBarSeries.Tests.cs
+++ b/lib/core/tbarseries/TBarSeries.Tests.cs
@@ -1,9 +1,3 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests
{
diff --git a/lib/core/tseries/TSeries.Tests.cs b/lib/core/tseries/TSeries.Tests.cs
index b29fe2b4..a6379dc6 100644
--- a/lib/core/tseries/TSeries.Tests.cs
+++ b/lib/core/tseries/TSeries.Tests.cs
@@ -1,9 +1,3 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests
{
diff --git a/lib/core/tvalue/TValue.Tests.cs b/lib/core/tvalue/TValue.Tests.cs
index 2eab88ab..cd2e8ccf 100644
--- a/lib/core/tvalue/TValue.Tests.cs
+++ b/lib/core/tvalue/TValue.Tests.cs
@@ -1,6 +1,3 @@
-using System;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests
{
diff --git a/lib/feeds/csv/CsvFeed.Tests.cs b/lib/feeds/csv/CsvFeed.Tests.cs
index e9656302..a6d26474 100644
--- a/lib/feeds/csv/CsvFeed.Tests.cs
+++ b/lib/feeds/csv/CsvFeed.Tests.cs
@@ -1,4 +1,3 @@
-using Xunit;
namespace QuanTAlib.Tests;
diff --git a/lib/feeds/gbm/Gbm.Tests.cs b/lib/feeds/gbm/Gbm.Tests.cs
index f79887d4..8837e93c 100644
--- a/lib/feeds/gbm/Gbm.Tests.cs
+++ b/lib/feeds/gbm/Gbm.Tests.cs
@@ -1,6 +1,3 @@
-using System;
-using Xunit;
-using QuanTAlib;
namespace QuanTAlib.Tests;
diff --git a/lib/quantalib.csproj b/lib/quantalib.csproj
index 257a4657..cc450824 100644
--- a/lib/quantalib.csproj
+++ b/lib/quantalib.csproj
@@ -32,17 +32,17 @@
6afc11a7-4355-4f5e-9fdf-22431e5b03cb
-
-
-
-
+
+
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/qodana.yaml b/qodana.yaml
new file mode 100644
index 00000000..bd636b87
--- /dev/null
+++ b/qodana.yaml
@@ -0,0 +1,96 @@
+#-------------------------------------------------------------------------------#
+# Qodana analysis is configured by qodana.yaml file #
+# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
+#-------------------------------------------------------------------------------#
+version: "1.0"
+
+#Specify inspection profile for code analysis
+profile:
+ name: qodana.starter
+
+#Enable inspections
+#include:
+# - name:
+
+#Disable inspections
+exclude:
+ # Disable XML documentation comment validation (allows unescaped < > in comments)
+ - name: XmlDocAnalyzer
+ - name: InvalidXmlDocComment
+ # Library project - public API properties are intentionally unused internally
+ - name: UnusedAutoPropertyAccessor.Global
+ # Style preference - fully qualified names used intentionally for clarity
+ - name: RedundantNameQualifier
+ - name: RCS1036 # Roslyn: Remove redundant empty line
+ - name: IDE0001 # Simplify name
+ - name: IDE0002 # Simplify member access
+
+ # HIGH-PERFORMANCE LIBRARY EXCLUSIONS
+ # ------------------------------------
+ # Flat namespace structure is intentional for this library
+ - name: CheckNamespace
+
+ # Float comparisons are intentional in financial calculations (checking 0.0, NaN, sentinel values)
+ - name: CompareOfFloatsByEqualityOperator
+
+ # Explicit default args improve code clarity and self-documentation
+ - name: RedundantArgumentDefaultValue
+
+ # Platform-specific optimizations (SIMD, intrinsics) are intentional
+ - name: CA1416 # Validate platform compatibility
+
+ # Public API unused internally - this is a library
+ - name: UnusedMember.Global
+ - name: MemberCanBePrivate.Global
+ - name: ClassNeverInstantiated.Global
+ - name: UnusedType.Global
+
+ # Redundant using directives - managed by IDE/build, not critical for library
+ - name: RedundantUsingDirective
+ - name: IDE0005 # Remove unnecessary using directives
+ - name: CS8019 # Unnecessary using directive
+
+ # Nullable warning suppressions - used intentionally for null safety patterns
+ - name: RedundantSuppressNullableWarningExpression
+
+ # Redundant type specifications - explicit types used for clarity/documentation
+ - name: RedundantTypeArgumentsOfMethod
+ - name: RedundantCast
+ - name: RedundantExplicitArrayCreation
+
+ # Unused local variables - often used in test setup or placeholder code (includes false positives for tuple deconstruction)
+ - name: UnusedVariable
+ - name: UnusedVariable.Compiler # False positive for tuple deconstruction
+ - name: CS0219 # Variable is assigned but never used
+
+ # Object initializer in using statement - false positive for simple property setters
+ - name: CA2000 # Dispose objects before losing scope (overly cautious for simple cases)
+ - name: UseObjectOrCollectionInitializerWhenPossible
+ - name: DoNotUseObjectInitializerForUsingVariable
+ - name: ObjectInitializerMightCauseException
+ - name: ObjectCreationAsStatement
+ - name: UseObjectOrCollectionInitializer
+ - name: UsingStatementResourceInitialization # "Do not use object initializer for 'using' variable"
+
+ # Private field can be local variable - test fixtures often use fields for clarity/organization
+ - name: PrivateFieldCanBeConvertedToLocalVariable
+ - name: ConvertToLocalFunction
+
+ # Code coverage checks - SonarCloud handles coverage, Qodana coverage unreliable
+ - name: CoverageCheck
+ - name: ClassCoverageCheck
+ - name: MethodCoverageCheck
+ - name: CodeCoverageCheck
+
+#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
+#bootstrap: sh ./prepare-qodana.sh
+
+#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
+#plugins:
+# - id: #(plugin id can be found at https://plugins.jetbrains.com)
+
+#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
+linter: qodana-dotnet
+
+# Use native mode instead of Docker (avoids Unix socket issues on Windows)
+withinDocker: false
diff --git a/quantower/Quantower.Tests.csproj b/quantower/Quantower.Tests.csproj
index d460e070..2e1ee149 100644
--- a/quantower/Quantower.Tests.csproj
+++ b/quantower/Quantower.Tests.csproj
@@ -10,6 +10,7 @@
+