# Architecture

QuanTAlib is built on specific architectural decisions designed to maximize performance on modern hardware while maintaining mathematical correctness. These decisions involve trade-offs. Understanding the trade-offs helps determine whether QuanTAlib fits a particular use case.

## Three Core Decisions

### 1. Structure of Arrays (SoA) Memory Layout

Traditional object-oriented design stores data as arrays of objects: `List<PriceBar>` where each `PriceBar` contains timestamp, open, high, low, close, volume. This layout is intuitive for humans. It is terrible for CPUs.

QuanTAlib stores timestamps and values in separate contiguous arrays. When calculating an average, the CPU loads a cache line filled entirely with price values, without wasting space on interleaved timestamps or object headers.

The performance difference is measurable:

| Operation | SoA Layout | AoS Layout | Improvement |
| :-------- | ---------: | ---------: | ----------: |
| Average 10,000 values | 2.4 μs | 18.7 μs | 7.8× |
| Sum 100,000 values | 12.1 μs | 89.3 μs | 7.4× |
| Min/Max 500,000 values | 48.2 μs | 412.6 μs | 8.6× |

The gains come from two sources: cache efficiency (no wasted bytes) and SIMD vectorization (CPU processes 4-8 values per instruction). Both require contiguous memory.

### 2. O(1) Streaming Algorithms

A 14-period RSI and a 200-period RSI both process new bars in approximately 0.4 μs. The lookback period does not affect per-bar processing time.

Traditional batch approaches recalculate from scratch, scaling linearly with period. A 200-period indicator takes 14× longer than a 14-period indicator. This variable latency makes real-time processing unpredictable.

QuanTAlib maintains running state: partial sums, ring buffers, recursive coefficients. The cost is memory (40-60 bytes per indicator instance). The benefit is guaranteed constant-time updates regardless of period.

| Approach | 14-period | 200-period | 1000-period |
| :------- | --------: | ---------: | ----------: |
| Batch recalculation | 0.4 μs | 5.7 μs | 28.5 μs |
| O(1) streaming | 0.4 μs | 0.4 μs | 0.4 μs |

For single-symbol analysis, batch recalculation is fast enough. For 500 symbols updating simultaneously, O(1) streaming prevents the latency spikes that cause missed fills.

### 3. Explicit Initialization Handling

A 14-period SMA cannot produce a mathematically correct value until 14 bars have accumulated. Most libraries handle this in one of two ways:

1. **Return nothing**: NaN, null, or skip the first N-1 values entirely
2. **Return something**: Output numbers without indicating they are preliminary

QuanTAlib takes a third approach: return usable values immediately and expose confidence through the `IsHot` property.

```csharp
var sma = new Sma(14);
var result = sma.Update(new TValue(time, price));

// result.Value is always a number (the best estimate given available data)
// result.IsHot indicates whether enough data has accumulated
if (result.IsHot)
{
    // Full confidence: at least 14 bars processed
}
```

The math to calculate averages works with limited history. A 14-period SMA with 5 bars returns the average of those 5 bars. Not the 14-period average (that would require prescience), but a reasonable estimate that improves as data accumulates.

## Four Operating Modes

Trading systems have different data flow patterns. Backtesting engines process years of historical data in batch. Real-time systems update indicators bar-by-bar. Event-driven architectures react to changes asynchronously. QuanTAlib provides four modes optimized for these patterns.

### Span Mode

Operates directly on `Span<double>` without allocating objects. Raw arrays in, calculated arrays out. Zero garbage collection pressure, maximum speed, minimal abstraction.

```csharp
ReadOnlySpan<double> prices = GetPrices();
Span<double> output = stackalloc double[prices.Length];
Sma.Calculate(prices, output, period: 14);
```

**Use case:** Batch processing historical data, backtesting engines, research environments processing thousands of indicators across years of data.

**Trade-off:** No timestamps, no metadata, no safety rails. The caller manages array bounds, alignment, and interpretation.

### Batch Mode

Wraps calculations in TSeries objects that maintain timestamps, handle array resizing, and provide time-based indexing. Span mode with a protective wrapper.

```csharp
TSeries prices = LoadHistoricalData();
TSeries smaValues = Sma.Calculate(prices, period: 14);
```

**Use case:** Historical analysis requiring time alignment. Research notebooks, strategy prototyping, exploratory analysis.

**Trade-off:** 10-15% overhead versus Span mode for the convenience of timestamp management.

### Streaming Mode

Processes one bar at a time, maintaining internal state between updates. Call `Update()` with each new price, receive the current indicator value.

```csharp
var sma = new Sma(14);
foreach (var bar in liveStream)
{
    var result = sma.Update(new TValue(bar.Time, bar.Close), isNew: true);
}
```

The `isNew` parameter distinguishes between new bars and updates to the current bar. When the last bar's values change as new ticks arrive, pass `isNew: false` to update without advancing state.

**Use case:** Live trading systems, real-time charting, tick-by-tick analysis.

**Trade-off:** Per-bar overhead is higher than batch processing (function call, state management). For historical analysis, batch mode is faster.

### Eventing Mode

Extends streaming mode with event infrastructure. Indicators raise events when values change, enabling reactive chains where one indicator's output triggers another's calculation.

```csharp
var source = new TSeries();
var sma = new Sma(source, 14);
var ema = new Ema(source, 14);

sma.Pub += (s, e) => HandleSmaUpdate(e.Value);
source.Add(new TValue(DateTime.UtcNow, 100.0));  // Both indicators update
```

**Use case:** Complex trading systems with conditional logic, risk management systems reacting to volatility changes, pipelines where indicators communicate state.

**Trade-off:** Event dispatch overhead. For simple calculations, direct calls are faster.

## Memory Layout Details

### TSeries Internal Structure

```csharp
internal List<long> _t;    // Timestamps (ticks since epoch)
internal List<double> _v;  // Values
```

Two separate `List<T>` collections rather than `List<(long, double)>`. Access is exposed via `ReadOnlySpan<double>` properties, allowing zero-copy access to underlying memory.

### Cache Line Efficiency

A typical CPU cache line is 64 bytes. With SoA layout:

| Layout | Values per cache line | Utilization |
| :----- | --------------------: | ----------: |
| SoA (doubles only) | 8 | 100% |
| AoS (timestamp + value) | 4 | 100% |
| AoS (full PriceBar object) | 1-2 | 30-60% |

When iterating through values for calculation, SoA loads 8 relevant values per cache miss. AoS with full objects loads 1-2 values plus irrelevant data.

## SIMD Implementation

QuanTAlib uses `System.Runtime.Intrinsics` to access hardware vector instructions.

### Vectorization Strategy

| Operation | Scalar | AVX2 (4 doubles) | AVX-512 (8 doubles) |
| :-------- | -----: | ---------------: | ------------------: |
| Sum | 1 value/cycle | 4 values/cycle | 8 values/cycle |
| Min/Max | 1 value/cycle | 4 values/cycle | 8 values/cycle |
| Element-wise arithmetic | 1 value/cycle | 4 values/cycle | 8 values/cycle |

### Runtime Detection

```csharp
if (Avx512F.IsSupported)
    CalculateAvx512(source, output);
else if (Avx2.IsSupported)
    CalculateAvx2(source, output);
else
    CalculateScalar(source, output);
```

The library checks hardware support at runtime. Systems without AVX2 fall back to scalar implementations. The code runs everywhere; speed varies with hardware capability.

### Allocation Discipline

SIMD operations are performed on `Span<T>` and `ReadOnlySpan<T>`. No heap allocations occur during calculation. This discipline extends throughout the hot path: no LINQ, no temporary objects, no closures that capture state.

## Design Philosophy

**Correctness first.** Validation against original research papers and established libraries. When sources disagree, trace to the original author or mathematical derivation.

**Performance by default.** Algorithms and data structures chosen for speed. The fast path is the default path.

**No hidden allocations.** Hot paths are allocation-free. The garbage collector stays asleep during trading hours.

**Transparency.** Internal state (`IsHot`, `WarmupPeriod`, `Last`) is exposed. No black boxes.