diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..e9345c0b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,222 @@ +# QuanTAlib AI Coding Agent Instructions + +## Project Overview +QuanTAlib is a high-performance C# library for quantitative technical analysis, targeting .NET 8.0 with real-time streaming data processing. The library provides 50+ technical indicators optimized for sub-millisecond calculations using circular buffers, SIMD operations, and event-driven architecture. + +## Critical Architecture Patterns + +### Core Data Flow +All indicators inherit from `AbstractBase` (in `lib/core/abstractBase.cs`) which implements `ITValue`: +```csharp +// Standard indicator lifecycle: +Input → Calc() → ManageState(isNew) → Calculation() → Process() → Pub event +``` + +**Key insight**: The `isNew` parameter distinguishes between new bars and updates to the last bar. Indicators must support both modes - this is tested extensively in `Tests/test_updates_*.cs`. + +### Circular Buffer Pattern +`CircularBuffer` (in `lib/core/circularbuffer.cs`) is the foundation for memory-efficient fixed-capacity storage: +- Never grows beyond initial capacity +- O(1) add/access operations +- SIMD-optimized aggregations (Sum, Min, Max, Average) +- **Critical**: Always use `Add(item, isNew)` - the `isNew` flag controls whether to append or update + +### State Management in Indicators +Every indicator must implement: +```csharp +protected override void ManageState(bool isNew) +{ + if (isNew) { + _index++; + _p_prevValue = _prevValue; // Backup state + } else { + _prevValue = _p_prevValue; // Restore state + } +} +``` +This allows bar updates without corrupting historical calculations. + +## Development Workflow + +### MCP-Orchestrated Process +**Research Gate**: Before implementing non-trivial indicators, use Context7 to retrieve authoritative formulas/references. Embed citation tags in PR descriptions. + +**Decomposition**: Use Sequential-Thinking for complex multi-stage work (SIMD refactors, multi-timeframe logic). + +**Task Tracking**: Taskmaster holds the canonical task graph. Feature branches follow pattern: `feature/{taskId}-{slug}`. + +**Quality Gates**: +1. Formula citation required for non-trivial indicators (Context7 tag) +2. Benchmark data required for performance-related changes +3. Taskmaster task IDs must be referenced in PRs +4. Update `memory-bank/progress.md` after merge when threshold met + +### Build & Test Commands +```powershell +# Build solution +dotnet build QuanTAlib.sln + +# Run all tests +dotnet test --no-build + +# Run with coverage +dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov + +# Build using tasks.json +# Use Run Task: "build" or "test" +``` + +### Adding a New Indicator +1. **Research**: Get formula/specification (Context7 if needed) +2. **Location**: Place in appropriate `lib/` subdirectory (averages, oscillators, momentum, volatility, volume, statistics) +3. **Template structure**: +```csharp +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +[SkipLocalsInit] +public sealed class MyIndicator : AbstractBase +{ + private CircularBuffer _buffer; + private double _prevValue, _p_prevValue; // State + backup + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MyIndicator(int period) + { + ArgumentOutOfRangeException.ThrowIfLessThan(period, 1); + _buffer = new(period); + WarmupPeriod = period; // Set when indicator stabilizes + Name = $"MyIndicator({period})"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) { + _index++; + _p_prevValue = _prevValue; + } else { + _prevValue = _p_prevValue; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + _buffer.Add(Input.Value, Input.IsNew); + // Implement calculation logic + return result; + } +} +``` + +4. **Testing**: Create update test in `Tests/test_updates_*.cs`: +```csharp +[Fact] +public void MyIndicator_Update() +{ + var indicator = new MyIndicator(period: 14); + TestTValueUpdate(indicator, indicator.Calc); +} +``` + +### Quantower Integration +For platform indicators in `quantower/`, create wrapper classes inheriting from Quantower's `Indicator`: +- Use private `lib/` indicator instances +- Map `OnUpdate()` to indicator's `Calc()` method +- Extract output fields (e.g., `ma`, `jmaUp`, `jmaLo`) from indicator state + +## Code Style Requirements + +### Performance First +- Use `[MethodImpl(MethodImplOptions.AggressiveInlining)]` for hot paths +- Use `[MethodImpl(MethodImplOptions.AggressiveOptimization)]` for calculation methods +- Apply `[SkipLocalsInit]` to indicator classes +- Prefer SIMD operations in `CircularBuffer` for aggregations +- Minimize allocations in `Calculation()` methods + +### C# Conventions +- **No inline comments** within methods - code should be self-documenting +- Use XML doc comments for public APIs only +- PascalCase for public members, _camelCase for private fields +- Compact code - minimal whitespace between logical blocks +- Latest C# features: `ArgumentOutOfRangeException.ThrowIfLessThan`, pattern matching, etc. + +### Project Settings +- `LangVersion: preview` - use cutting-edge C# features +- `AllowUnsafeBlocks: true` - SIMD and unsafe operations permitted +- `Nullable: enable` - strict nullability checking +- Target: `net8.0` + +## Key Files & Directories + +### Core Library Structure +``` +lib/ +├── core/ # AbstractBase, CircularBuffer, TSeries, TBar, TValue +├── averages/ # Moving averages (SMA, EMA, DEMA, TEMA, JMA, etc.) +├── oscillators/ # RSI, Stochastic, Williams %R, CCI, Fisher +├── momentum/ # MACD, ADX, ROC, Vortex +├── volatility/ # ATR, Bollinger Bands, volatility measures +├── volume/ # Volume-based indicators +└── statistics/ # Statistical measures, correlations +``` + +### Critical Reference Files +- `lib/core/abstractBase.cs` - Base class for all indicators +- `lib/core/circularbuffer.cs` - Memory-efficient storage with SIMD +- `Directory.Build.props` - Solution-wide MSBuild properties +- `memory-bank/systemPatterns.md` - Architecture patterns +- `memory-bank/activeContext.md` - Current work focus and MCP policies +- `memory-bank/progress.md` - Completed features and roadmap + +### Testing Reference +- `Tests/test_updates_*.cs` - Update behavior validation (IsNew handling) +- `Tests/test_quantower.cs` - Quantower integration validation +- `Tests/test_talib.cs`, `test_Trady.cs` - Cross-validation against reference libraries + +## Common Patterns + +### Multi-Stage Smoothing +Many indicators (DEMA, TEMA, MACD) use cascaded smoothing: +```csharp +private readonly Ema _ema1; +private readonly Ema _ema2; + +_ema1.Calc(Input.Value, Input.IsNew); +_ema2.Calc(_ema1.Value, Input.IsNew); +``` + +### Bar-Based vs Value-Based +- **Value-based**: Accept `TValue`, process single values (most indicators) +- **Bar-based**: Accept `TBar` (OHLCV), process bar data (ATR, Stochastic, volume indicators) + +Override appropriate `Calc()` method: +```csharp +public override TValue Calc(TBar barInput) { /* ... */ } +``` + +### WarmupPeriod Calculation +Set `WarmupPeriod` to indicate when the indicator reaches 95% accuracy: +```csharp +WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - alpha)); +``` + +## Validation Strategy +1. **Update tests**: Verify `isNew=false` behavior converges to `isNew=true` with same final value +2. **Reference comparison**: Validate against TALib, Trady, or Skender implementations +3. **Edge cases**: Test with insufficient data (< period), NaN/Infinity, extreme values +4. **Performance**: Benchmark calculation time - target < 0.5ms per update + +## Documentation Requirements +- XML docs on public classes/methods describing purpose, formula, and sources +- Mathematical formulas in doc comments with source citations +- No internal comments - let code structure communicate intent +- Update `memory-bank/progress.md` after significant feature completion + +## GitVersion & Releases +- Semantic versioning via GitVersion.yml +- Version properties auto-injected: `$(GitVersion_MajorMinorPatch)` +- Commit messages influence version bumps (conventional commits) +- Build creates NuGet package with embedded version metadata diff --git a/lib/averages/Sma.cs b/lib/averages/Sma.cs index fb267e31..684d4445 100644 --- a/lib/averages/Sma.cs +++ b/lib/averages/Sma.cs @@ -1,75 +1,99 @@ -using System.Runtime.CompilerServices; -namespace QuanTAlib; - -/// -/// SMA: Simple Moving Average -/// The most basic form of moving average, calculating the arithmetic mean over a -/// specified period. Each data point in the period has equal weight in the -/// calculation. -/// -/// -/// The SMA calculation process: -/// 1. Maintains a buffer of the last 'period' values -/// 2. Calculates arithmetic mean of all values in the buffer -/// 3. Updates buffer with new values in FIFO manner -/// -/// Key characteristics: -/// - Equal weight for all values in the period -/// - Simple and straightforward calculation -/// - Significant lag due to equal weighting -/// - Smooth output with good noise reduction -/// - Most basic form of trend following -/// -/// Sources: -/// https://www.investopedia.com/terms/s/sma.asp -/// https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages -/// -public class Sma : AbstractBase -{ - private readonly CircularBuffer _buffer; - - /// The number of data points used in the SMA calculation. - /// Thrown when period is less than 1. - public Sma(int period) - { - if (period < 1) - { - throw new System.ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); - } - _buffer = new CircularBuffer(period); - Name = "Sma"; - WarmupPeriod = period; - Init(); - } - - /// The data source object that publishes updates. - /// The number of data points used in the SMA calculation. - public Sma(object source, int period) : this(period) - { - var pubEvent = source.GetType().GetEvent("Pub"); - pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override void ManageState(bool isNew) - { - if (isNew) - { - _lastValidValue = Input.Value; - _index++; - } - } - - /// - /// Performs the core SMA calculation using the circular buffer's average. - /// - /// The calculated SMA value. - protected override double Calculation() - { - ManageState(IsNew); - _buffer.Add(Input.Value, Input.IsNew); - - IsHot = _index >= WarmupPeriod; - return _buffer.Average(); - } -} +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// SMA: Simple Moving Average +/// The most basic form of moving average, calculating the arithmetic mean over a +/// specified period. Each data point in the period has equal weight in the +/// calculation. +/// +/// +/// The SMA calculation process: +/// 1. Maintains a circular buffer of the last 'period' values +/// 2. Maintains a running sum for O(1) calculation +/// 3. Updates: sum = sum - oldest + newest +/// 4. Returns sum / count for the average +/// +/// Key characteristics: +/// - Equal weight for all values in the period +/// - O(1) time complexity using running sum +/// - Simple and straightforward calculation +/// - Significant lag due to equal weighting +/// - Smooth output with good noise reduction +/// - Most basic form of trend following +/// +/// Sources: +/// https://www.investopedia.com/terms/s/sma.asp +/// https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages +/// +[SkipLocalsInit] +public sealed class Sma : AbstractBase +{ + private readonly CircularBuffer _buffer; + private double _sum, _p_sum; + private double _lastValue, _p_lastValue; + + /// The number of data points used in the SMA calculation. + /// Thrown when period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Sma(int period) + { + ArgumentOutOfRangeException.ThrowIfLessThan(period, 1); + _buffer = new CircularBuffer(period); + Name = $"Sma({period})"; + WarmupPeriod = period; + Init(); + } + + /// The data source object that publishes updates. + /// The number of data points used in the SMA calculation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Sma(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_sum = _sum; + _p_lastValue = _lastValue; + } + else + { + _sum = _p_sum; + _lastValue = _p_lastValue; + } + } + + /// + /// Performs the core SMA calculation using O(1) running sum algorithm. + /// + /// The calculated SMA value. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + double oldValue; + if (Input.IsNew) + { + oldValue = _buffer.Count == _buffer.Capacity ? _buffer.Oldest() : 0.0; + _lastValue = Input.Value; + } + else + { + oldValue = _lastValue; + } + + _sum = _sum - oldValue + Input.Value; + _buffer.Add(Input.Value, Input.IsNew); + + IsHot = _index >= WarmupPeriod; + return _sum / _buffer.Count; + } +}