14 KiB
AGENTS.md - QuanTAlib Protocol
To all AI Agents: This file defines the laws, physics, and protocols of the QuanTAlib repository. Read this before writing a single line of code. Failure to adhere to these standards will result in rejected code.
1. Identity & Mission
QuanTAlib is a high-performance, zero-allocation C# library for quantitative technical analysis.
- Target: Quantower and custom C# trading engines.
- Core Philosophy: Speed, Correctness, and Memory Efficiency.
- Key Constraint: Hot paths must be allocation-free (GC pressure is the enemy).
2. Architecture & "Physics"
Memory Model: Structure of Arrays (SoA)
We do not store objects in lists. We store primitive arrays.
- TSeries: Internally uses
List<long> _t(timestamps) andList<double> _v(values). - Access: Expose data via
ReadOnlySpan<double>for SIMD operations.
Core Types
TValue: Struct (16 bytes).DateTime Time,double Value.TBar: Struct (48 bytes).DateTime Time,double Open, High, Low, Close, Volume.TSeries: The primary data structure for time series.ITValuePublisher: The interface for reactive data flow.
Design Principles
- Source Material: The algorithm and markdown documentation foundation should be sourced from https://github.com/mihakralj/pinescript/blob/main/indicators/.
- Zero Allocation: The core calculation loop must not allocate memory on the heap. Use
stackalloc,Span<T>, and pinned memory where possible. - O(1) Complexity: Streaming updates must be O(1) whenever mathematically possible. Use running sums/products or circular buffers to avoid re-iterating over history.
- Dual API: Provide both a stateful object-oriented API (
Update) and a stateless static vector API (Calculate). - Bar Correction: Support intra-bar updates via the
isNewparameter. The indicator must be able to rollback the last update and apply a new value for the same timestamp. - Robustness: Handle
NaNandInfinitygracefully using last-valid-value substitution. Never propagate invalid values. - Reactive: Implement
ITValuePublisherto support event-driven architectures. - Time Handling: Always use
DateTime.UtcNowinstead ofDateTime.Nowto ensure consistent time handling across timezones.
Performance Rules
- Zero Allocation: The
Updatemethod MUST NOT allocate memory on the heap. Usestackallocor pre-allocated buffers. - O(1) Complexity: Streaming updates must be constant time. Use circular buffers (
RingBuffer) or running sums. - SIMD: Batch operations (
Calculate) should useSystem.Runtime.Intrinsics(AVX2) orSystem.Numerics.Vector<T>where possible. UseVector.ConditionalSelectto handle edge cases (e.g., division by zero) without branching. If SIMD is not possible due to recursive dependencies, usestackallocfor internal buffers to avoid heap allocations. - Inlining: Use
[MethodImpl(MethodImplOptions.AggressiveInlining)]on hot methods. - Locals: Use
[SkipLocalsInit]to avoid zero-init costs in tight loops.
3. Indicator Implementation Standards
Every indicator must follow the Good Indicator Guidelines strictly.
File Structure
Directory: lib/[category]/[name]/ (e.g., lib/trends/sma/)
| File | Naming | Purpose |
|---|---|---|
| Source | [Name].cs |
Main implementation. public sealed class. |
| Tests | [Name].Tests.cs |
xUnit tests (correctness, edge cases). |
| Validation | [Name].Validation.Tests.cs |
Compare against TA-Lib, Skender, etc. |
| Docs | [Name].md |
User documentation with formulas. |
| Adapter | [Name].Quantower.cs |
Quantower platform integration. |
| Adapter Tests | [Name].Quantower.Tests.cs |
Tests for the adapter. |
Class Definition
- Namespace:
QuanTAlib - Attributes:
[SkipLocalsInit]for performance. - Modifiers:
public sealed class - Interface: Implements
ITValuePublisher
State Management
- Scalar State: Use a
private record struct Stateto group all scalar state variables. This ensures value semantics, automaticIEquatableimplementation, and cleaner rollback logic. - State Variables: Maintain
private State _state;(current) andprivate State _p_state;(previous valid state). - Buffers: Use
RingBufferfor sliding window data. - Resync: Implement a periodic full recalculation (e.g., every 1000 ticks) to prevent floating-point drift in running sums.
Constructor
- Validate all parameters (throw
ArgumentExceptionfor invalid values). - Initialize
Nameproperty (e.g.,$"Sma({period})"); - Support chaining:
public [Name](ITValuePublisher source, ...) - Event Subscription: Subscribe to events directly (
source.Pub += ...) when the source is passed as a non-nullable parameter. Do not use defensive null checks (if (_source != null)).
The Update Method Contract
The Update method is the heart of the indicator.
public TValue Update(TValue input, bool isNew = true)
- Attribute:
[MethodImpl(MethodImplOptions.AggressiveInlining)] - Logic:
-
State Rollback:
if (isNew) { _p_state = _state; // ... update state (e.g. counters) ... } else { _state = _p_state; // ... update state ... } -
Input Validation: Check
double.IsFinite. If not, use_lastValidValue(stored inState). -
Calculation: Perform the math.
-
Publish: Update
Lastproperty, invokePubevent, returnLast.
-
Update Method (TSeries)
- Signature:
public TSeries Update(TSeries source) - Placement: Must be adjacent to the
Update(TValue)method. - Logic:
- Create output series.
- Call static
Calculate(Span)for performance. - Restore internal state by replaying the last
Periodbars (or full series if recursive).
Static Calculate (TSeries)
- Create a new instance of the indicator.
- Iterate through the source series.
- Return the resulting
TSeries.
Static Calculate (Span) - Critical for Performance
- Signature:
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, ...) - Attribute:
[MethodImpl(MethodImplOptions.AggressiveInlining)] - Optimization:
- Check for SIMD support (
Avx2.IsSupported). - Use
stackallocfor small buffers (threshold ~256) and for internal state buffers in recursive algorithms where SIMD is not applicable. - Implement a scalar fallback path that handles
NaNsafely. - Implement a SIMD path for large, clean datasets (optional but recommended for simple averages).
- Check for SIMD support (
4. Testing Protocol
Unit Tests ([Name].Tests.cs)
- Framework: xUnit
- Data Generation: Use
GBM(Geometric Brownian Motion) for generating realistic test data. Avoid usingSystem.Randomdirectly. - Coverage:
- Constructor validation (invalid params).
- Basic calculation correctness (compare against manual calc).
isNew=truevsisNew=falsebehavior (bar correction).Reset()functionality.IsHotproperty behavior.NaN/Infinityhandling (must not crash, must return finite values).- Consistency between Object API, Static TSeries API, and Static Span API.
- Edge cases: Period=1, empty input, single input.
Validation Tests ([Name].Validation.Tests.cs)
- Mandatory: You MUST validate against at least one external authority (TA-Lib, Skender, Tulip, OoplesFinance, Python libs).
- Tolerance: Use explicit constants from
ValidationHelper(e.g.,ValidationHelper.SkenderTolerance,ValidationHelper.TalibTolerance) rather than relying on defaults. Typically1e-7. - Data: Use
ValidationTestDataclass which wrapsGBM(Geometric Brownian Motion) to generate realistic test data (default 5000 bars) and provides pre-calculated Skender quotes. - Coverage: Validate all 3 modes (Batch, Streaming, Span) against the external library.
- Verification: Use
ValidationHelper.VerifyDatawhich checks the last 100 bars to ensure convergence and correctness.
External Library Usage Guide
-
Skender.Stock.Indicators:
- Use
_data.SkenderQuotes.Get[Indicator](...). - Compare using
ValidationHelper.VerifyDatawithtolerance: ValidationHelper.SkenderTolerance.
- Use
-
TA-Lib (TALib.NETCore):
- Namespace:
using TALib; - Method:
TALib.Functions.[Indicator]<double>(...). - Check
Assert.Equal(Core.RetCode.Success, retCode). - Use
ValidationHelper.VerifyDatawithoutRangeandlookback.
- Namespace:
-
Tulip (Tulip.NETCore):
- Namespace:
using Tulip; - Method:
Tulip.Indicators.[indicator].Run(...). - Handle lookback/offset manually (Tulip output is shorter than input).
- Note: Be aware of potential 1-bar shifts due to different initialization strategies (e.g., Tulip often skips index 0). Use
lookbackparameter to align. - Use
ValidationHelper.VerifyDatawithlookback.
- Namespace:
-
OoplesFinance.StockIndicators:
- Namespace:
using OoplesFinance.StockIndicators; - Convert data:
_data.SkenderQuotes.Select(q => new TickerData { ... }).ToList(). - Use
new StockData(ooplesData).Calculate[Indicator](...). - Compare using
ValidationHelper.VerifyData.
- Namespace:
5. Documentation Standards
- Format: Markdown.
- Content: Title, Description, Parameters, Formula (LaTeX), C# Usage Examples.
- Style: Follow the guidelines in
.clinerules/techdocs.md(Bryson-Executive voice, architectural focus, evidence-based). - Pronouns: Avoid first-person plural in documentation; prefer explicit "QuanTAlib" subject or passive voice. Treat this as a persistent style rule (to be stored in qdrant style/pattern entries when available).
- Index & Links: Add the new indicator to:
- Category index (e.g.,
lib/trends/_index.md) - Main library index (
lib/_index.md) - Documentation sidebar (
docs/_sidebar.md) - Integration guide (
docs/integration.md) - Indicators list (
docs/indicators.md) - Validation table (
docs/validation.md)
- Category index (e.g.,
- Linting: Ensure that markdownlint shows no issues for the file.
- MD030: Ensure exactly one space after list markers.
- MD032: Ensure lists are surrounded by blank lines.
6. Quantower Adapter
- Implementation: Create a wrapper class in
[Name].Quantower.csthat adapts the QuanTAlib indicator for the Quantower platform. - Tests: Create unit tests in
[Name].Quantower.Tests.csto verify the adapter's functionality using mocks where necessary. - Project Inclusion: Ensure the adapter is included in the appropriate project (e.g.,
quantower/Statistics.csproj) and tests inquantower/Quantower.Tests.csproj.
7. Code Review
- Tool: Run CodeRabbit on the changes.
- Requirement: Address and fix ALL issues identified by the CodeRabbit review before considering the task complete.
8. Development Checklist
When creating a new indicator, you are DONE only when:
- Source algorithm is verified.
- Algorithm is fully optimized for modern C#.
- Algorithm runs in O(1) constant time wherever possible.
- Algorithm is SIMD-optimized wherever possible.
- All 6 required files exist.
UpdatehandlesisNewandNaNcorrectly.- No heap allocations in
Update. - Static
Calculate(Span)is implemented. - Unit tests are created for all key methods and attributes.
- Unit tests pass (including edge cases).
- Validation tests pass against external libs.
- Documentation is complete and linked in all required index/doc files:
docs/_sidebar.mddocs/indicators.mddocs/validation.md(Update validation status table)lib/_index.mdlib/[category]/_index.md
- Quantower adapter and tests are implemented.
- CodeRabbit review issues are resolved.
9. Forbidden Actions
- DO NOT use LINQ in hot paths (
UpdateorCalculate). - DO NOT use
newinsideUpdate. - DO NOT change
Directory.Build.propswithout explicit instruction. - DO NOT remove
[SkipLocalsInit]or[MethodImpl]attributes. - DO NOT ignore
NaNinputs; handle them safely. - DO NOT skip creating
[Name].Quantower.csand[Name].Quantower.Tests.cswhen implementing or modifying an indicator. - DO NOT forget to update
docs/validation.mdwith the validation status of the new indicator.
10. Context & Resources
- Time: Use
DateTime.UtcNow. - Math: Use
System.MathorSystem.Numerics. - Root Namespace:
QuanTAlib. - Patterns & Decisions: When designing, refactoring, or fixing indicators or tests, first query
qdrant.mcpfor 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): ForITValuePublisher-based indicators, subscribe directly withsource.Pub += Handle;in constructors instead of storingsourceor 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 concreteList<T>fields to support SoA layout andCollectionsMarshal.AsSpan; when analyzers suggest collection abstractions (MA0016), suppress them narrowly around those fields, as this is a deliberate performance design. - Argument Validation (Commit
d7dbd70): AllCalculate/Batchand span-based APIs must useArgumentException(or derived) overloads that include the offending parameter name (e.g.,nameof(output)ornameof(sourceY)) for length and range checks, matching the MA0015-compliant pattern adopted across indicators.