---
name: indicator-series
description: Implement Series-style batch indicators (`To{Name}()` in `{Name}.Series.cs`), the canonical numerical reference every BufferList and StreamHub must match exactly. Owns the full per-indicator file set and the completion checklist across all styles, tests, catalog, docs, regression baseline, and benchmark. Use when adding a new indicator, creating or editing a `src/Indicators/**/{Name}.Series.cs`, `{Name}Result.cs`, or `{Name}.Utilities.cs` file, choosing a result or input interface, optimizing a Series calculation, or checking whether an indicator is complete.
---

# Series indicator development

Series results are the source of truth. A BufferList or StreamHub that disagrees with Series is wrong unless the Series result is proven wrong against reference data.

Load [result interface selection](references/decision-tree.md) before writing a `{Name}Result.cs` record or choosing the `this` parameter type of `To{Name}()`.

## Per-indicator file set

Source files live in `src/Indicators/{folder}/{Name}/`, where `{folder}` is the alphabetical bucket `a-b`, `c-d`, `e-j`, `k-q`, `r-s`, or `t-z`:

| File | Contents |
| ---- | -------- |
| `{Name}.Series.cs` | `public static partial class {Name}` with the `To{Name}()` extension |
| `{Name}List.cs` | `{Name}List` BufferList class plus the `To{Name}List()` extension |
| `{Name}Hub.cs` | `{Name}Hub` class with an `internal` constructor plus the `To{Name}Hub()` extension |
| `{Name}.Catalog.cs` | `CommonListing`, `SeriesListing`, `BufferListing`, `StreamListing` |
| `{Name}Result.cs` | Positional result `record` |
| `{Name}.Utilities.cs` | `internal static void Validate(...)`, any shared `Increment(...)` kernel, and any `RemoveWarmupPeriods()` overload |
| `I{Name}.cs` | Public interface of configuration properties (`LookbackPeriods`, derived constants such as `K`); never result properties |

Add a `RemoveWarmupPeriods()` overload only when values keep converging after the first calculated one (see `Ema.Utilities.cs`) or the result is not `IReusable`; otherwise the generic `PruningExtensions.RemoveWarmupPeriods<T>()` covers it.

Tests live in `tests/Library/Indicators/{folder}/{Name}/`: `{Name}SeriesTests.cs`, `{Name}BufferListTests.cs`, `{Name}HubTests.cs`, `{Name}CatalogTests.cs`, `{Name}RegressionTests.cs`, and the `{Name}.Calc.xlsx` spreadsheet holding the manually calculated reference values.

## Completion checklist

An indicator is complete when every item holds. Series-only indicators skip the List, Hub, and their tests, listings, and benchmarks.

- [ ] Every applicable source file in the table above exists (Series-only indicators omit `{Name}List.cs` and `{Name}Hub.cs`); the indicator-buffer and indicator-stream skills own the List and Hub specifics.
- [ ] `{Name}SeriesTests` inherits `StaticSeriesTestBase` and asserts spot values from `{Name}.Calc.xlsx`; assert a documented value range with `IsBetween` where the indicator has one. The testing-standards skill owns base classes, required methods, and precision constants.
- [ ] `{Name}BufferListTests` and `{Name}HubTests` assert `IsExactly` parity with the Series output.
- [ ] Catalog listings are registered in `src/Common/Catalog/Catalog.Listings.cs`, the catalog shape snapshot is regenerated, and `{Name}CatalogTests` covers each listing; the indicator-catalog skill owns listing rules, registration order, and the catalog shape snapshot.
- [ ] `tests/Library/TestData/results/{uiid-lowercase}.standard.json` exists, generated by `dotnet run --project tools/baselining -- --indicator {UIID}`.
- [ ] `{Name}RegressionTests` inherits `RegressionTestBase<TResult>`, carries `[TestClass, TestCategory("Regression")]`, passes the baseline filename to the base constructor, and calls each style with the catalog default parameters, because the baseline is generated from those defaults.
- [ ] Benchmarks exist in `tools/performance/Perf.Series.cs`, `Perf.Buffer.cs`, and `Perf.Stream.cs`; the performance-testing skill owns their shape.
- [ ] `docs/indicators/{kebab-name}.md` exists or is updated; the documentation skill owns page structure.
- [ ] `docs/migration/v3.md` is updated when the indicator existed in v2 and its API or results changed.

## Implementation rules

- Validate arguments first: `ArgumentNullException.ThrowIfNull(source)`, then `Validate(...)`, which throws `ArgumentOutOfRangeException` naming the parameter and offending value.
- Calculate in `double`, use `double.NaN` for incalculable internal state, and convert with `.NaN2Null()` only when constructing the result.
- Accept NaN inputs and let them propagate. Guard every variable denominator with `denom != 0 ? num / denom : double.NaN`, comparing to exactly zero.
- Keep the calculation single-pass O(n) and allocate only the result array plus minimal working buffers.
- When the List or Hub repeats the per-step math, put it in an `Increment(...)` kernel in `{Name}.Utilities.cs` and call it from every style instead of re-deriving the formula (see `Ema.Increment`).

When the result count equals the input count, fill a preallocated array, then copy it into the returned list:

```csharp
TResult[] results = new TResult[length];
// results[i] = new TResult(...);
return new List<TResult>(results);
```

Benchmark this against `List<T>.Add()` before committing to it; some indicators, such as ADL, are faster with `Add()`.

## Reference examples

- Single value, chainable: `src/Indicators/r-s/Sma/Sma.Series.cs`
- Exponential smoothing with an SMA seed: `src/Indicators/e-j/Ema/Ema.Series.cs`
- Multi-stage from bars: `src/Indicators/a-b/Adx/Adx.Series.cs`
- Multi-value `ISeries` result: `src/Indicators/a-b/Alligator/Alligator.Series.cs`

## Do not do these

- Do not verify a formula against anything but an authoritative reference publication.
- Do not change a test's expected value or loosen its precision constant to make a failing calculation pass; fix the formula.
- Do not reject or filter NaN inputs; downstream chaining depends on propagation.
- Do not use epsilon comparisons for zero checks.
- Do not fix a BufferList or StreamHub mismatch by changing Series unless the Series value is shown wrong against `{Name}.Calc.xlsx` or the reference publication.
