feat: enhance documentation for event flow patterns, SoA storage, and argument validation in AGENTS and MODELS

This commit is contained in:
Miha Kralj
2025-12-27 15:56:34 -08:00
parent d7dbd7078a
commit bf99f3caa9
4 changed files with 18 additions and 14 deletions
+3
View File
@@ -251,3 +251,6 @@ When creating a new indicator, you are **DONE** only when:
* **Math**: Use `System.Math` or `System.Numerics`. * **Math**: Use `System.Math` or `System.Numerics`.
* **Root Namespace**: `QuanTAlib`. * **Root Namespace**: `QuanTAlib`.
* **Patterns & Decisions**: When designing, refactoring, or fixing indicators or tests, first query `qdrant.mcp` for stored QuanTAlib patterns, architectural decisions, and benchmarks, and align new work with those references unless there is a documented reason to diverge. * **Patterns & Decisions**: When designing, refactoring, or fixing indicators or tests, first query `qdrant.mcp` for stored QuanTAlib patterns, architectural decisions, and benchmarks, and align new work with those references unless there is a documented reason to diverge.
* **Event Flow Pattern (Commit d7dbd70)**: For `ITValuePublisher`-based indicators, subscribe directly with `source.Pub += Handle;` in constructors instead of storing `source` or delegate fields solely for subscription; rely on struct-based event args (e.g., `TBarEventArgs`, `TValueEventArgs`) and, when Meziantou MA0046 flags the non-EventArgs signature, suppress it locally with a targeted pragma and comment explaining the performance trade-off.
* **SoA Backing Storage (Commit d7dbd70)**: Core series types (`TSeries`, `TBarSeries`) intentionally use concrete `List<T>` fields to support SoA layout and `CollectionsMarshal.AsSpan`; when analyzers suggest collection abstractions (MA0016), suppress them narrowly around those fields, as this is a deliberate performance design.
* **Argument Validation (Commit d7dbd70)**: All `Calculate`/`Batch` and span-based APIs must use `ArgumentException` (or derived) overloads that include the offending parameter name (e.g., `nameof(output)` or `nameof(sourceY)`) for length and range checks, matching the MA0015-compliant pattern adopted across indicators.
+2
View File
@@ -72,6 +72,8 @@ These tests compare the indicator's output against established external librarie
- **Compare against Python (pandas-ta/talib)**: If C# libs are unavailable. - **Compare against Python (pandas-ta/talib)**: If C# libs are unavailable.
- **Tolerance**: Typically `1e-6` to `1e-9`. - **Tolerance**: Typically `1e-6` to `1e-9`.
When asserting parameter validation behavior (e.g., invalid periods or mismatched buffer lengths), align with the MA0015-compliant pattern defined in `AGENTS.md` (commit d7dbd70): `ArgumentException` (or derived) overloads must include the offending `paramName` (for example `nameof(output)` or `nameof(sourceY)`), and tests should verify both the exception type and the parameter name where relevant.
## 3. Example Test Template ## 3. Example Test Template
```csharp ```csharp
+11 -12
View File
@@ -1,9 +1,9 @@
namespace QuanTAlib.Tests namespace QuanTAlib.Tests;
public class TBarSeriesTests
{ {
public class TBarSeriesTests [Fact]
{
[Fact]
public void Constructor_Default_CreatesEmptySeries() public void Constructor_Default_CreatesEmptySeries()
{ {
var series = new TBarSeries(); var series = new TBarSeries();
@@ -340,13 +340,13 @@ namespace QuanTAlib.Tests
public void GetEnumerator_NonGeneric_Works() public void GetEnumerator_NonGeneric_Works()
{ {
var series = new TBarSeries(); var series = new TBarSeries();
series.Add(100, 10, 15, 5, 12, 100); series.Add(100, 10, 15, 5, 12, 100, isNew: true);
series.Add(200, 20, 25, 15, 22, 200); series.Add(200, 20, 25, 15, 22, 200, isNew: true);
IEnumerable enumerable = series; var list = new List<object>();
var list = new List<object>();
foreach (var item in enumerable) foreach (var item in (IEnumerable)series)
{ {
list.Add(item); list.Add(item);
} }
@@ -406,7 +406,6 @@ namespace QuanTAlib.Tests
Assert.Equal(3, series.Count); Assert.Equal(3, series.Count);
Assert.Equal(100, series[0].Time); Assert.Equal(100, series[0].Time);
Assert.Equal(200, series[1].Time); Assert.Equal(200, series[1].Time);
Assert.Equal(300, series[2].Time); Assert.Equal(300, series[2].Time);
}
} }
} }
+1 -1
View File
@@ -21,7 +21,6 @@ public readonly struct TBarEventArgs
// We intentionally deviate from the standard EventArgs pattern here for perf. // We intentionally deviate from the standard EventArgs pattern here for perf.
#pragma warning disable MA0046 // The second parameter must be of type 'System.EventArgs' or a derived type #pragma warning disable MA0046 // The second parameter must be of type 'System.EventArgs' or a derived type
public delegate void TBarPublishedHandler(object? sender, in TBarEventArgs args); public delegate void TBarPublishedHandler(object? sender, in TBarEventArgs args);
#pragma warning restore MA0046
public class TBarSeries : IReadOnlyList<TBar> public class TBarSeries : IReadOnlyList<TBar>
{ {
@@ -36,6 +35,7 @@ public class TBarSeries : IReadOnlyList<TBar>
public string Name { get; set; } = "Bar"; public string Name { get; set; } = "Bar";
public event TBarPublishedHandler? Pub; public event TBarPublishedHandler? Pub;
#pragma warning restore MA0046
// Note: These views share underlying storage. Do not modify directly; use TBarSeries.Add() instead. // Note: These views share underlying storage. Do not modify directly; use TBarSeries.Add() instead.
public TSeries Open { get; } public TSeries Open { get; }