diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 5c462ec8..dabdd4cc 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -2,7 +2,7 @@ name: CI/CD
on:
push:
- branches: [ main, develop ]
+ branches: [ main, dev ]
pull_request:
workflow_dispatch:
@@ -76,7 +76,7 @@ jobs:
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
release_branches: main
- pre_release_branches: develop
+ pre_release_branches: dev
dry_run: true
default_bump: ${{ steps.check_tags.outputs.has_tags == 'false' && 'major' || 'false' }}
custom_release_rules: |
diff --git a/.gitignore b/.gitignore
index fbf58e72..df916e3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -287,3 +287,12 @@ __pycache__/
*.odx.cs
*.xsd.cs
.DS_Store
+
+# Agent tooling scratch directories and generated state
+.auto-claude/
+.auto-claude-security.json
+.auto-claude-status
+.claude_settings.json
+.worktrees/
+.security-key
+logs/security/
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 79f2303e..124576de 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -29,5 +29,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index deabc41d..43614355 100644
--- a/README.md
+++ b/README.md
@@ -34,12 +34,16 @@ A modern and robust C# Technical Analysis library based on the original open-sou
* [π Features](#-features)
* [Roadmap (next features)](#roadmap-next-features)
* [π Documentation](#-documentation)
+ * [π Guides](#-guides)
* [π₯ Installation](#-installation)
* [π Prerequisites](#-prerequisites)
* [π We use the latest C# features](#-we-use-the-latest-c-features)
* [π¦ NuGet Packages](#-nuget-packages)
* [π§ͺ Tests Specifications](#-tests-specifications)
* [πΎ Installation](#-installation-1)
+ * [π§βπ» Usage](#-usage)
+ * [π§© Samples](#-samples)
+ * [β‘ Benchmarks](#-benchmarks)
* [π Code Quality](#-code-quality)
* [β Issues and Feature Requests](#-issues-and-feature-requests)
* [π€ Contributing](#-contributing)
@@ -59,7 +63,12 @@ The primary objective of TaLibStandard is to provide a comprehensive, feature-ri
## π Getting started
-To get started with TaLibStandard, you can clone the repository and explore the examples provided in the `examples` directory. You can also refer to the list of [available functions](./docs/functions.md) in the documentation for a comprehensive overview of the library's capabilities.
+To get started with TaLibStandard, read the [getting started guide](./docs/guides/getting-started.md) β
+it covers installation, your first indicator, and the output-alignment rule that everything else depends
+on. Then clone the repository and explore the runnable projects in the [`samples`](./samples) directory
+(see [Samples](#-samples)). For a comprehensive overview of the library's capabilities, refer to the
+[indicator catalog](./docs/indicators/README.md) or the flat list of
+[available functions](./docs/functions.md).
## π Features
@@ -70,12 +79,12 @@ To get started with TaLibStandard, you can clone the repository and explore the
### Roadmap (next features)
* [ ] Comprehensive API documentation that is easy to understand
-* [ ] High-Level API for common use cases
+* [x] High-Level API for common use cases β see the [fluent API guide](./docs/guides/fluent-api.md)
* [ ] Support for more data types
* [ ] Support for more functions
* [ ] More tests
-* [ ] More examples
-* [ ] Add a Benchmark project
+* [x] More examples β see [Samples](#-samples)
+* [x] Add a Benchmark project β see [Benchmarks](#-benchmarks)
* [ ] Create a gRPC server to expose the library as a service
## π Documentation
@@ -87,6 +96,21 @@ discuss it.
> **Note:** The documentation is generated using [Doraku/DefaultDocumentation]() tool. It is generated automatically when the project is built.
+## π Guides
+
+Hand-written guides live in [`docs/guides`](./docs/guides), and every public entry point is catalogued in
+[`docs/indicators`](./docs/indicators/README.md).
+
+| Guide | What it covers |
+|-------|----------------|
+| [π Getting started](./docs/guides/getting-started.md) | Installation, your first indicator, and the three things that trip everyone up: `RetCode`, `BegIdx`/`NBElement` output alignment, and the `double` / `float` / `decimal` story. **Start here.** |
+| [β¨ Fluent API](./docs/guides/fluent-api.md) | `PriceSeries` in, bar-indexed `IndicatorSeries` out β the layer that does the `BegIdx`/`NBElement` arithmetic for you, with `null` for a bar that has not warmed up. Warm-up semantics, crossings, `AsOf`, the nine shipped indicators and the `Align` escape hatch to the rest. |
+| [π Indicator catalog](./docs/indicators/README.md) | Every `TAMath` and `TACandle` entry point, grouped by category, with signatures, defaults, outputs and links to the generated API pages. |
+| [π‘ Real-time streaming](./docs/guides/real-time-streaming.md) | Ticks β bars β indicators over SignalR and raw WebSocket: architecture, message contracts, warm-up semantics and production notes. |
+| [π Backtesting](./docs/guides/backtesting.md) | The engine model, the structurally enforced no-look-ahead guarantee, the cost model, every metric with its formula, and how to write your own strategy. |
+| [π TradingView integration](./docs/guides/tradingview-integration.md) | Pine Script `ta.*` β `TAMath` mapping, parity caveats, UDF datafeed and Lightweight Charts wiring, alert-webhook security. |
+| [β‘ Benchmarks](./docs/guides/benchmarks.md) | What the benchmark suite measures, how to run it, how to read BenchmarkDotNet output, and the measured results. |
+
## π₯ Installation
### π Prerequisites
@@ -130,11 +154,31 @@ dotnet add package Atypical.TechnicalAnalysis.Functions
## π§βπ» Usage
-TaLibStandard exposes two APIs on the same indicator: a low-level `TAFunc` API that mirrors the
-original TA-Lib C signature (`ref`/`in` parameters, pre-allocated output arrays), and a higher-level
-`TAMath` API that wraps it and returns a strongly-typed result record.
+TaLibStandard exposes three APIs over the same indicators: a **fluent** API (`PriceSeries` /
+`IndicatorSeries`) that hands you values addressed by bar index, a **`TAMath`** API that returns a
+strongly-typed result record carrying TA-Lib's raw output array and its alignment metadata, and a
+low-level **`TAFunc`** API that mirrors the original TA-Lib C signature (`ref`/`in` parameters,
+pre-allocated output arrays).
-### High-level API (`TAMath`)
+### Fluent API (`PriceSeries` β `IndicatorSeries`)
+
+```csharp
+using TechnicalAnalysis.Functions;
+
+PriceSeries prices = PriceSeries.FromHlc(highs, lows, closes);
+
+double? rsi = prices.Rsi(14).Latest; // null until the indicator has warmed up
+double? atr = prices.Atr(14).Latest;
+
+IndicatorSeries fast = prices.Sma(5);
+IndicatorSeries slow = prices.Sma(20);
+bool goldenCross = fast.CrossedAbove(slow, bar: prices.BarCount - 1);
+```
+
+Every index is a **bar** index, and a bar the indicator has not reached yet is `null` β never `0.0`.
+See the [fluent API guide](./docs/guides/fluent-api.md).
+
+### `TAMath` β the raw result record
```csharp
using TechnicalAnalysis.Functions;
@@ -144,9 +188,11 @@ double[] closingPrices = [.. /* your OHLCV data */];
// RsiResult exposes RetCode, BegIdx, NBElement and the Real[] output array
RsiResult rsi = TAMath.Rsi(0, closingPrices.Length - 1, closingPrices, timePeriod: 14);
-if (rsi.RetCode == RetCode.Success)
+if (rsi.RetCode == RetCode.Success && rsi.NBElement > 0)
{
- double latestRsi = rsi.Real[^1]; // most recent RSI value
+ // The newest value is at array index NBElement - 1, and it describes
+ // bar BegIdx + NBElement - 1. Those are two different numbers.
+ double latestRsi = rsi.Real[rsi.NBElement - 1];
}
```
@@ -187,6 +233,54 @@ inputs. See the [full function list](./docs/functions.md) for every available in
candlestick pattern, and the [Demo.BlazorWasm](./Demo.BlazorWasm) project for a working end-to-end
example that charts these indicators.
+> **One rule to internalise before anything else.** `TAMath` fills its output array from index `0`, not
+> from the input index it corresponds to. Output element `k` describes **input index `BegIdx + k`**, for
+> `k` in `[0, NBElement)`; everything from `NBElement` onwards is a meaningless zero. Getting this wrong
+> shifts every signal in time, silently. The [getting started guide](./docs/guides/getting-started.md)
+> works through it with a hand-checkable example, and the
+> [fluent API](./docs/guides/fluent-api.md) does the arithmetic for you in one tested place.
+
+## π§© Samples
+
+Runnable projects, all completely offline β no market data provider, no API key, no network calls.
+
+| Sample | Run it | Guide |
+|--------|--------|-------|
+| [**Real-time streaming**](./samples/TechnicalAnalysis.Samples.RealTime)
ASP.NET Core server: synthetic tick feed β OHLCV bars β seven indicators (eleven series) per closed bar, published over a SignalR hub *and* a raw WebSocket, plus a zero-dependency browser dashboard. | `dotnet run --project samples/TechnicalAnalysis.Samples.RealTime -c Release`
then open | [π‘ Real-time streaming](./docs/guides/real-time-streaming.md) |
+| [**Real-time console client**](./samples/TechnicalAnalysis.Samples.RealTime.Client)
SignalR client for the server above; exercises both the group-push and the server-streaming paths. | `dotnet run --project samples/TechnicalAnalysis.Samples.RealTime.Client -c Release -- --symbol GLOBEX` | [π‘ Real-time streaming](./docs/guides/real-time-streaming.md) |
+| [**Backtesting**](./samples/TechnicalAnalysis.Samples.Backtesting)
Bar-by-bar engine with a structurally enforced no-look-ahead guarantee, a commission/slippage cost model, a full metrics suite and five strategies compared side by side. | `dotnet run --project samples/TechnicalAnalysis.Samples.Backtesting -c Release` | [π Backtesting](./docs/guides/backtesting.md) |
+| [**Blazor WebAssembly demo**](./Demo.BlazorWasm)
Interactive browser demo charting the indicators. | `dotnet run --project Demo.BlazorWasm` | β |
+
+## β‘ Benchmarks
+
+[`benchmarks/TechnicalAnalysis.Benchmarks`](./benchmarks/TechnicalAnalysis.Benchmarks) is a
+BenchmarkDotNet suite of **119 benchmarks** over deterministic synthetic market data at three series
+lengths (1 000 / 10 000 / 100 000), all with `[MemoryDiagnoser]`. Every indicator in the overlap,
+momentum and volatility/volume suites is measured **twice** β once through the allocation-free `TAFunc`
+API and once through the ergonomic `TAMath` API β so the cost of convenience is a number rather than a
+guess. Candlestick patterns are measured on `double`, `float` **and** `decimal` to price the
+generic-math design.
+
+```shell
+# see what is there, without running anything
+dotnet run --project benchmarks/TechnicalAnalysis.Benchmarks -c Release -- --list flat
+
+# prove every benchmark computes something valid (fast; not a measurement)
+dotnet run --project benchmarks/TechnicalAnalysis.Benchmarks -c Release -- --selfcheck
+
+# one suite
+dotnet run --project benchmarks/TechnicalAnalysis.Benchmarks -c Release -- --anyCategories Momentum
+```
+
+An optional sixth suite compares the managed port head to head against the original TA-Lib C library
+through P/Invoke, with an equivalence assertion that runs *before* anything is timed. It is enabled
+automatically when the native library is found and silently skipped when it is not, so the suite has no
+native dependency.
+
+See the [benchmarks guide](./docs/guides/benchmarks.md) for the full switch reference, the native
+install instructions per platform, how to read every output column, the measured results and the
+methodology caveats.
+
## π Code Quality
We strive for the highest code quality in TaLibStandard, leveraging Codacyβan automated code analysis/quality tool. Codacy provides static analysis, cyclomatic complexity measures, duplication identification, and code unit test coverage changes for every commit and pull request.
diff --git a/TaLibStandard.sln b/TaLibStandard.sln
index 00b78621..374e7497 100644
--- a/TaLibStandard.sln
+++ b/TaLibStandard.sln
@@ -37,77 +37,178 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo.BlazorWasm", "Demo.Bla
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{A5E5C4A5-0E5C-4F68-B5E5-E5C5F5E5C5F5}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{66320409-64EC-F7C5-3DEF-65E7510DAAD1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnicalAnalysis.Benchmarks", "benchmarks\TechnicalAnalysis.Benchmarks\TechnicalAnalysis.Benchmarks.csproj", "{5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnicalAnalysis.Samples.Backtesting", "samples\TechnicalAnalysis.Samples.Backtesting\TechnicalAnalysis.Samples.Backtesting.csproj", "{4648FCAC-48E5-40BC-A6D2-01DA9E671533}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnicalAnalysis.Samples.RealTime", "samples\TechnicalAnalysis.Samples.RealTime\TechnicalAnalysis.Samples.RealTime.csproj", "{B95318E3-0681-4D0B-A99E-EFC4592A2916}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnicalAnalysis.Samples.RealTime.Client", "samples\TechnicalAnalysis.Samples.RealTime.Client\TechnicalAnalysis.Samples.RealTime.Client.csproj", "{81CA190F-CC16-4A06-8B21-410039E9F04E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnicalAnalysis.Samples.Backtesting.UnitTests", "tests\TechnicalAnalysis.Samples.Backtesting.UnitTests\TechnicalAnalysis.Samples.Backtesting.UnitTests.csproj", "{590EA42C-9B12-472B-9D1F-1B7ADA156109}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x86 = Debug|x86
+ Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x86 = Release|x86
+ Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AE28059B-2495-4229-A90E-5CE9D70334D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE28059B-2495-4229-A90E-5CE9D70334D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE28059B-2495-4229-A90E-5CE9D70334D3}.Debug|x86.ActiveCfg = Debug|Any CPU
{AE28059B-2495-4229-A90E-5CE9D70334D3}.Debug|x86.Build.0 = Debug|Any CPU
+ {AE28059B-2495-4229-A90E-5CE9D70334D3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AE28059B-2495-4229-A90E-5CE9D70334D3}.Debug|x64.Build.0 = Debug|Any CPU
{AE28059B-2495-4229-A90E-5CE9D70334D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE28059B-2495-4229-A90E-5CE9D70334D3}.Release|Any CPU.Build.0 = Release|Any CPU
{AE28059B-2495-4229-A90E-5CE9D70334D3}.Release|x86.ActiveCfg = Release|Any CPU
{AE28059B-2495-4229-A90E-5CE9D70334D3}.Release|x86.Build.0 = Release|Any CPU
+ {AE28059B-2495-4229-A90E-5CE9D70334D3}.Release|x64.ActiveCfg = Release|Any CPU
+ {AE28059B-2495-4229-A90E-5CE9D70334D3}.Release|x64.Build.0 = Release|Any CPU
{552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Debug|x86.ActiveCfg = Debug|Any CPU
{552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Debug|x86.Build.0 = Debug|Any CPU
+ {552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Debug|x64.Build.0 = Debug|Any CPU
{552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Release|Any CPU.ActiveCfg = Release|Any CPU
{552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Release|Any CPU.Build.0 = Release|Any CPU
{552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Release|x86.ActiveCfg = Release|Any CPU
{552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Release|x86.Build.0 = Release|Any CPU
+ {552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Release|x64.ActiveCfg = Release|Any CPU
+ {552FF382-7E2F-4566-8DF8-2BA0F752DA36}.Release|x64.Build.0 = Release|Any CPU
{70E407F2-1332-408C-B11F-A64E2A3FFF99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70E407F2-1332-408C-B11F-A64E2A3FFF99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70E407F2-1332-408C-B11F-A64E2A3FFF99}.Debug|x86.ActiveCfg = Debug|Any CPU
{70E407F2-1332-408C-B11F-A64E2A3FFF99}.Debug|x86.Build.0 = Debug|Any CPU
+ {70E407F2-1332-408C-B11F-A64E2A3FFF99}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {70E407F2-1332-408C-B11F-A64E2A3FFF99}.Debug|x64.Build.0 = Debug|Any CPU
{70E407F2-1332-408C-B11F-A64E2A3FFF99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70E407F2-1332-408C-B11F-A64E2A3FFF99}.Release|Any CPU.Build.0 = Release|Any CPU
{70E407F2-1332-408C-B11F-A64E2A3FFF99}.Release|x86.ActiveCfg = Release|Any CPU
{70E407F2-1332-408C-B11F-A64E2A3FFF99}.Release|x86.Build.0 = Release|Any CPU
+ {70E407F2-1332-408C-B11F-A64E2A3FFF99}.Release|x64.ActiveCfg = Release|Any CPU
+ {70E407F2-1332-408C-B11F-A64E2A3FFF99}.Release|x64.Build.0 = Release|Any CPU
{9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Debug|x86.ActiveCfg = Debug|Any CPU
{9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Debug|x86.Build.0 = Debug|Any CPU
+ {9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Debug|x64.Build.0 = Debug|Any CPU
{9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Release|Any CPU.Build.0 = Release|Any CPU
{9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Release|x86.ActiveCfg = Release|Any CPU
{9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Release|x86.Build.0 = Release|Any CPU
+ {9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Release|x64.ActiveCfg = Release|Any CPU
+ {9F038FE8-EB6F-45D2-A6B4-BBFED44CFBB0}.Release|x64.Build.0 = Release|Any CPU
{EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Debug|x86.ActiveCfg = Debug|Any CPU
{EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Debug|x86.Build.0 = Debug|Any CPU
+ {EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Debug|x64.Build.0 = Debug|Any CPU
{EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Release|Any CPU.Build.0 = Release|Any CPU
{EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Release|x86.ActiveCfg = Release|Any CPU
{EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Release|x86.Build.0 = Release|Any CPU
+ {EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Release|x64.ActiveCfg = Release|Any CPU
+ {EDB2827E-1163-49E6-9120-FED9E1ABDB62}.Release|x64.Build.0 = Release|Any CPU
{B1B33276-DEFE-4183-8DEC-D068356BD02A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1B33276-DEFE-4183-8DEC-D068356BD02A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1B33276-DEFE-4183-8DEC-D068356BD02A}.Debug|x86.ActiveCfg = Debug|Any CPU
{B1B33276-DEFE-4183-8DEC-D068356BD02A}.Debug|x86.Build.0 = Debug|Any CPU
+ {B1B33276-DEFE-4183-8DEC-D068356BD02A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B1B33276-DEFE-4183-8DEC-D068356BD02A}.Debug|x64.Build.0 = Debug|Any CPU
{B1B33276-DEFE-4183-8DEC-D068356BD02A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1B33276-DEFE-4183-8DEC-D068356BD02A}.Release|Any CPU.Build.0 = Release|Any CPU
{B1B33276-DEFE-4183-8DEC-D068356BD02A}.Release|x86.ActiveCfg = Release|Any CPU
{B1B33276-DEFE-4183-8DEC-D068356BD02A}.Release|x86.Build.0 = Release|Any CPU
+ {B1B33276-DEFE-4183-8DEC-D068356BD02A}.Release|x64.ActiveCfg = Release|Any CPU
+ {B1B33276-DEFE-4183-8DEC-D068356BD02A}.Release|x64.Build.0 = Release|Any CPU
{F07C261D-D025-4E12-B260-DDD09E83DC0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F07C261D-D025-4E12-B260-DDD09E83DC0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F07C261D-D025-4E12-B260-DDD09E83DC0C}.Debug|x86.ActiveCfg = Debug|Any CPU
{F07C261D-D025-4E12-B260-DDD09E83DC0C}.Debug|x86.Build.0 = Debug|Any CPU
+ {F07C261D-D025-4E12-B260-DDD09E83DC0C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F07C261D-D025-4E12-B260-DDD09E83DC0C}.Debug|x64.Build.0 = Debug|Any CPU
{F07C261D-D025-4E12-B260-DDD09E83DC0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F07C261D-D025-4E12-B260-DDD09E83DC0C}.Release|Any CPU.Build.0 = Release|Any CPU
{F07C261D-D025-4E12-B260-DDD09E83DC0C}.Release|x86.ActiveCfg = Release|Any CPU
{F07C261D-D025-4E12-B260-DDD09E83DC0C}.Release|x86.Build.0 = Release|Any CPU
+ {F07C261D-D025-4E12-B260-DDD09E83DC0C}.Release|x64.ActiveCfg = Release|Any CPU
+ {F07C261D-D025-4E12-B260-DDD09E83DC0C}.Release|x64.Build.0 = Release|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Debug|x86.Build.0 = Debug|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Debug|x64.Build.0 = Debug|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Release|x86.ActiveCfg = Release|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Release|x86.Build.0 = Release|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Release|x64.ActiveCfg = Release|Any CPU
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80}.Release|x64.Build.0 = Release|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Debug|x86.Build.0 = Debug|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Debug|x64.Build.0 = Debug|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Release|x86.ActiveCfg = Release|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Release|x86.Build.0 = Release|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Release|x64.ActiveCfg = Release|Any CPU
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533}.Release|x64.Build.0 = Release|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Debug|x86.Build.0 = Debug|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Debug|x64.Build.0 = Debug|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Release|x86.ActiveCfg = Release|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Release|x86.Build.0 = Release|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Release|x64.ActiveCfg = Release|Any CPU
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916}.Release|x64.Build.0 = Release|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Debug|x86.Build.0 = Debug|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Debug|x64.Build.0 = Debug|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Release|x86.ActiveCfg = Release|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Release|x86.Build.0 = Release|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Release|x64.ActiveCfg = Release|Any CPU
+ {81CA190F-CC16-4A06-8B21-410039E9F04E}.Release|x64.Build.0 = Release|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Debug|x86.Build.0 = Debug|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Debug|x64.Build.0 = Debug|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Release|Any CPU.Build.0 = Release|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Release|x86.ActiveCfg = Release|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Release|x86.Build.0 = Release|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Release|x64.ActiveCfg = Release|Any CPU
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {1122291B-5129-4C67-94CF-1B89AB8C804A}
- EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{AE28059B-2495-4229-A90E-5CE9D70334D3} = {5AEBD15B-4E7E-47F2-B8D9-FEA28EBE87EA}
{552FF382-7E2F-4566-8DF8-2BA0F752DA36} = {5AEBD15B-4E7E-47F2-B8D9-FEA28EBE87EA}
@@ -116,5 +217,13 @@ Global
{EDB2827E-1163-49E6-9120-FED9E1ABDB62} = {CC261FDF-BD3D-46D8-9F87-326E86BDEEF2}
{B1B33276-DEFE-4183-8DEC-D068356BD02A} = {CC261FDF-BD3D-46D8-9F87-326E86BDEEF2}
{F07C261D-D025-4E12-B260-DDD09E83DC0C} = {A5E5C4A5-0E5C-4F68-B5E5-E5C5F5E5C5F5}
+ {5471EED6-09D0-42C5-9D93-0B1E7B2DDB80} = {66320409-64EC-F7C5-3DEF-65E7510DAAD1}
+ {4648FCAC-48E5-40BC-A6D2-01DA9E671533} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
+ {B95318E3-0681-4D0B-A99E-EFC4592A2916} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
+ {81CA190F-CC16-4A06-8B21-410039E9F04E} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
+ {590EA42C-9B12-472B-9D1F-1B7ADA156109} = {CC261FDF-BD3D-46D8-9F87-326E86BDEEF2}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {1122291B-5129-4C67-94CF-1B89AB8C804A}
EndGlobalSection
EndGlobal
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/BenchmarkCategories.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/BenchmarkCategories.cs
new file mode 100644
index 00000000..caa32d5b
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/BenchmarkCategories.cs
@@ -0,0 +1,79 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+namespace TechnicalAnalysis.Benchmarks.Benchmarks;
+
+///
+/// The category names used by [BenchmarkCategory], so that --anyCategories and
+/// --allCategories filters can be written without guessing at spelling.
+///
+public static class BenchmarkCategories
+{
+ ///
+ /// Moving averages, envelopes and other overlap studies.
+ ///
+ public const string OverlapStudies = "OverlapStudies";
+
+ ///
+ /// Momentum oscillators.
+ ///
+ public const string Momentum = "Momentum";
+
+ ///
+ /// Volatility and volume indicators plus the statistic functions.
+ ///
+ public const string VolatilityVolume = "VolatilityVolume";
+
+ ///
+ /// Candlestick pattern recognisers.
+ ///
+ public const string CandlePatterns = "CandlePatterns";
+
+ ///
+ /// Numeric precision comparisons.
+ ///
+ public const string Precision = "Precision";
+
+ ///
+ /// Managed versus native TA-Lib C head-to-head comparisons.
+ ///
+ public const string NativeComparison = "NativeComparison";
+
+ ///
+ /// The low level, allocation-free TAFunc API with caller-supplied output buffers.
+ ///
+ public const string TaFunc = "TAFunc";
+
+ ///
+ /// The ergonomic TAMath API that allocates its output arrays and a result record per call.
+ ///
+ public const string TaMath = "TAMath";
+
+ ///
+ /// Benchmarks operating on inputs.
+ ///
+ public const string DoublePrecision = "double";
+
+ ///
+ /// Benchmarks operating on inputs.
+ ///
+ public const string SinglePrecision = "float";
+
+ ///
+ /// Benchmarks operating on inputs.
+ ///
+ public const string DecimalPrecision = "decimal";
+
+ ///
+ /// The managed TaLibStandard implementation.
+ ///
+ public const string Managed = "Managed";
+
+ ///
+ /// The original TA-Lib C implementation reached through P/Invoke.
+ ///
+ public const string Native = "Native";
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/CandlePatternBenchmarks.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/CandlePatternBenchmarks.cs
new file mode 100644
index 00000000..0c50d56c
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/CandlePatternBenchmarks.cs
@@ -0,0 +1,708 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using BenchmarkDotNet.Attributes;
+using TechnicalAnalysis.Candles;
+using TechnicalAnalysis.Common;
+
+namespace TechnicalAnalysis.Benchmarks.Benchmarks;
+
+///
+/// A representative dozen candlestick pattern recognisers, each exercised over ,
+/// and inputs.
+///
+///
+///
+/// TACandle is generic over T : IFloatingPoint<T>, so the JIT produces a dedicated, fully
+/// devirtualised body for each value type. The three variants of every benchmark expose the real cost of that
+/// generic-math design: double and float compile down to hardware floating point, whereas
+/// decimal falls back to the software 128-bit decimal implementation, which is typically one to two orders
+/// of magnitude slower and is the reason a decimal-based pipeline should be a deliberate choice.
+///
+///
+/// There is no allocation-free path here: TACandle always allocates the int[] output and a
+/// record. The memory columns therefore measure the ergonomic API only.
+///
+///
+/// There is deliberately no Ratio column here. A single Baseline = true would
+/// ratio every row against one method β the default logical group is (Job, Params), not (indicator) β so a
+/// float row would appear to be compared against its own double counterpart when it was in fact
+/// compared against Sma_Double. Compare the Mean and Allocated columns of the two rows
+/// of the same indicator instead.
+///
+///
+[MemoryDiagnoser]
+[CategoriesColumn]
+[BenchmarkCategory(BenchmarkCategories.CandlePatterns)]
+public class CandlePatternBenchmarks : MarketDataBenchmarkBase
+{
+ private const double PenetrationDouble = 0.3;
+ private const float PenetrationSingle = 0.3f;
+ private const decimal PenetrationDecimal = 0.3m;
+
+ ///
+ /// Generates the market data.
+ ///
+ [GlobalSetup]
+ public void Setup()
+ {
+ InitializeMarketData();
+ }
+
+ ///
+ /// Doji over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult Doji_Double()
+ {
+ return TACandle.CdlDoji(StartIdx, EndIdx, Series.Doubles.Open, Series.Doubles.High, Series.Doubles.Low, Series.Doubles.Close);
+ }
+
+ ///
+ /// Doji over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult Doji_Float()
+ {
+ return TACandle.CdlDoji(StartIdx, EndIdx, Series.Singles.Open, Series.Singles.High, Series.Singles.Low, Series.Singles.Close);
+ }
+
+ ///
+ /// Doji over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult Doji_Decimal()
+ {
+ return TACandle.CdlDoji(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Engulfing pattern over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult Engulfing_Double()
+ {
+ return TACandle.CdlEngulfing(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Engulfing pattern over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult Engulfing_Float()
+ {
+ return TACandle.CdlEngulfing(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Engulfing pattern over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult Engulfing_Decimal()
+ {
+ return TACandle.CdlEngulfing(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Hammer over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult Hammer_Double()
+ {
+ return TACandle.CdlHammer(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Hammer over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult Hammer_Float()
+ {
+ return TACandle.CdlHammer(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Hammer over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult Hammer_Decimal()
+ {
+ return TACandle.CdlHammer(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Hanging man over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult HangingMan_Double()
+ {
+ return TACandle.CdlHangingMan(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Hanging man over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult HangingMan_Float()
+ {
+ return TACandle.CdlHangingMan(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Hanging man over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult HangingMan_Decimal()
+ {
+ return TACandle.CdlHangingMan(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Harami over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult Harami_Double()
+ {
+ return TACandle.CdlHarami(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Harami over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult Harami_Float()
+ {
+ return TACandle.CdlHarami(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Harami over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult Harami_Decimal()
+ {
+ return TACandle.CdlHarami(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Marubozu over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult Marubozu_Double()
+ {
+ return TACandle.CdlMarubozu(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Marubozu over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult Marubozu_Float()
+ {
+ return TACandle.CdlMarubozu(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Marubozu over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult Marubozu_Decimal()
+ {
+ return TACandle.CdlMarubozu(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Spinning top over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult SpinningTop_Double()
+ {
+ return TACandle.CdlSpinningTop(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Spinning top over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult SpinningTop_Float()
+ {
+ return TACandle.CdlSpinningTop(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Spinning top over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult SpinningTop_Decimal()
+ {
+ return TACandle.CdlSpinningTop(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Shooting star over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult ShootingStar_Double()
+ {
+ return TACandle.CdlShootingStar(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Shooting star over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult ShootingStar_Float()
+ {
+ return TACandle.CdlShootingStar(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Shooting star over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult ShootingStar_Decimal()
+ {
+ return TACandle.CdlShootingStar(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Three white soldiers over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult ThreeWhiteSoldiers_Double()
+ {
+ return TACandle.Cdl3WhiteSoldiers(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Three white soldiers over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult ThreeWhiteSoldiers_Float()
+ {
+ return TACandle.Cdl3WhiteSoldiers(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Three white soldiers over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult ThreeWhiteSoldiers_Decimal()
+ {
+ return TACandle.Cdl3WhiteSoldiers(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Three black crows over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult ThreeBlackCrows_Double()
+ {
+ return TACandle.Cdl3BlackCrows(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Three black crows over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult ThreeBlackCrows_Float()
+ {
+ return TACandle.Cdl3BlackCrows(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Three black crows over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult ThreeBlackCrows_Decimal()
+ {
+ return TACandle.Cdl3BlackCrows(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Piercing pattern over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult Piercing_Double()
+ {
+ return TACandle.CdlPiercing(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// Piercing pattern over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult Piercing_Float()
+ {
+ return TACandle.CdlPiercing(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// Piercing pattern over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult Piercing_Decimal()
+ {
+ return TACandle.CdlPiercing(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// High wave candle over double inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult HighWave_Double()
+ {
+ return TACandle.CdlHighWave(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close);
+ }
+
+ ///
+ /// High wave candle over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult HighWave_Float()
+ {
+ return TACandle.CdlHighWave(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close);
+ }
+
+ ///
+ /// High wave candle over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult HighWave_Decimal()
+ {
+ return TACandle.CdlHighWave(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close);
+ }
+
+ ///
+ /// Morning star over double inputs. This is the one pattern in the set that takes a penetration argument.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CandleIndicatorResult MorningStar_Double()
+ {
+ return TACandle.CdlMorningStar(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Open,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ PenetrationDouble);
+ }
+
+ ///
+ /// Morning star over float inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CandleIndicatorResult MorningStar_Float()
+ {
+ return TACandle.CdlMorningStar(
+ StartIdx,
+ EndIdx,
+ Series.Singles.Open,
+ Series.Singles.High,
+ Series.Singles.Low,
+ Series.Singles.Close,
+ PenetrationSingle);
+ }
+
+ ///
+ /// Morning star over decimal inputs.
+ ///
+ /// The pattern result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DecimalPrecision)]
+ public CandleIndicatorResult MorningStar_Decimal()
+ {
+ return TACandle.CdlMorningStar(
+ StartIdx,
+ EndIdx,
+ Series.Decimals.Open,
+ Series.Decimals.High,
+ Series.Decimals.Low,
+ Series.Decimals.Close,
+ PenetrationDecimal);
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/MarketDataBenchmarkBase.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/MarketDataBenchmarkBase.cs
new file mode 100644
index 00000000..a6065bd0
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/MarketDataBenchmarkBase.cs
@@ -0,0 +1,75 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using BenchmarkDotNet.Attributes;
+using TechnicalAnalysis.Benchmarks.Data;
+
+namespace TechnicalAnalysis.Benchmarks.Benchmarks;
+
+///
+/// Shared plumbing for every indicator benchmark: the series length parameter, the generated market data and the
+/// pre-allocated output buffers used by the allocation-free TAFunc path.
+///
+///
+///
+/// Three output buffers are provided because no bound indicator writes more than three output series
+/// (MACD and Bollinger Bands are the widest at three).
+///
+///
+/// The buffers are allocated once in [GlobalSetup], i.e. outside the measured region. A benchmark named
+/// *_TAFunc therefore measures the algorithm only; a benchmark named *_TAMath measures the algorithm
+/// plus the result-object and output-array allocations that the ergonomic API performs on every call.
+///
+///
+public abstract class MarketDataBenchmarkBase
+{
+ ///
+ /// Gets or sets the number of bars fed to the indicator.
+ ///
+ [Params(1_000, 10_000, 100_000)]
+ public int Length { get; set; }
+
+ ///
+ /// Gets the generated market data for the current .
+ ///
+ protected MarketSeries Series { get; private set; } = MarketDataGenerator.Generate(2);
+
+ ///
+ /// The first index handed to the indicator. Always zero: benchmarks always run the whole series.
+ ///
+ protected const int StartIdx = 0;
+
+ ///
+ /// Gets the last index handed to the indicator, i.e. minus one.
+ ///
+ protected int EndIdx => Length - 1;
+
+ ///
+ /// Gets the first pre-allocated output buffer.
+ ///
+ protected double[] Output0 { get; private set; } = [];
+
+ ///
+ /// Gets the second pre-allocated output buffer.
+ ///
+ protected double[] Output1 { get; private set; } = [];
+
+ ///
+ /// Gets the third pre-allocated output buffer.
+ ///
+ protected double[] Output2 { get; private set; } = [];
+
+ ///
+ /// Generates the market data and allocates the output buffers. Call this from a [GlobalSetup] method.
+ ///
+ protected void InitializeMarketData()
+ {
+ Series = MarketDataGenerator.Generate(Length);
+ Output0 = new double[Length];
+ Output1 = new double[Length];
+ Output2 = new double[Length];
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/MomentumBenchmarks.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/MomentumBenchmarks.cs
new file mode 100644
index 00000000..bb5c5372
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/MomentumBenchmarks.cs
@@ -0,0 +1,505 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using BenchmarkDotNet.Attributes;
+using TechnicalAnalysis.Common;
+using TechnicalAnalysis.Functions;
+
+namespace TechnicalAnalysis.Benchmarks.Benchmarks;
+
+///
+/// Momentum oscillators.
+///
+///
+///
+/// As in the other suites, _TAFunc measures the algorithm with caller-supplied output buffers and
+/// _TAMath measures the ergonomic API including its per-call allocations. Several of these indicators are
+/// composites (MACD, StochRsi, Ppo, UltOsc) and allocate internal scratch arrays even on the TAFunc path;
+/// the memory columns make that visible.
+///
+///
+/// There is deliberately no Ratio column here. BenchmarkDotNet's default logical group
+/// is (Job, Params), so a single Baseline = true would ratio every method in the class against
+/// that one method β an EMA row would read as "3.7x slower" when what it measured was EMA against SMA, not
+/// TAMath against TAFunc. Grouping per indicator cannot fix it either, because
+/// BenchmarkLogicalGroupRule.ByCategory keys on the whole category set and the TAFunc /
+/// TAMath categories put the two halves of a pair in different groups. Read the two rows of the same
+/// indicator and divide the Mean column yourself; is the one
+/// suite whose categories do line up, and it is the one that carries baselines.
+///
+///
+[MemoryDiagnoser]
+[CategoriesColumn]
+[BenchmarkCategory(BenchmarkCategories.Momentum)]
+public class MomentumBenchmarks : MarketDataBenchmarkBase
+{
+ private const int RsiPeriod = 14;
+ private const int MacdFast = 12;
+ private const int MacdSlow = 26;
+ private const int MacdSignal = 9;
+ private const int StochFastK = 5;
+ private const int StochSlowK = 3;
+ private const int StochSlowD = 3;
+ private const int StochRsiPeriod = 14;
+ private const int StochRsiFastK = 5;
+ private const int StochRsiFastD = 3;
+ private const int AdxPeriod = 14;
+ private const int CciPeriod = 14;
+ private const int MfiPeriod = 14;
+ private const int WillRPeriod = 14;
+ private const int PpoFast = 12;
+ private const int PpoSlow = 26;
+ private const int RocPeriod = 10;
+ private const int UltOscPeriod1 = 7;
+ private const int UltOscPeriod2 = 14;
+ private const int UltOscPeriod3 = 28;
+ private const int AroonPeriod = 14;
+
+ ///
+ /// Generates the market data and allocates the output buffers.
+ ///
+ [GlobalSetup]
+ public void Setup()
+ {
+ InitializeMarketData();
+ }
+
+ ///
+ /// Relative strength index, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Rsi_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Rsi(StartIdx, EndIdx, Series.Doubles.Close, RsiPeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Relative strength index, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public RsiResult Rsi_TAMath()
+ {
+ return TAMath.Rsi(StartIdx, EndIdx, Series.Doubles.Close, RsiPeriod);
+ }
+
+ ///
+ /// Moving average convergence divergence, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Macd_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] macd = Output0;
+ double[] signal = Output1;
+ double[] histogram = Output2;
+
+ return TAFunc.Macd(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ MacdFast,
+ MacdSlow,
+ MacdSignal,
+ ref begIdx,
+ ref nbElement,
+ ref macd,
+ ref signal,
+ ref histogram);
+ }
+
+ ///
+ /// Moving average convergence divergence, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public MacdResult Macd_TAMath()
+ {
+ return TAMath.Macd(StartIdx, EndIdx, Series.Doubles.Close, MacdFast, MacdSlow, MacdSignal);
+ }
+
+ ///
+ /// Slow stochastic, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Stoch_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] slowK = Output0;
+ double[] slowD = Output1;
+
+ return TAFunc.Stoch(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ StochFastK,
+ StochSlowK,
+ MAType.Sma,
+ StochSlowD,
+ MAType.Sma,
+ ref begIdx,
+ ref nbElement,
+ ref slowK,
+ ref slowD);
+ }
+
+ ///
+ /// Slow stochastic, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public StochResult Stoch_TAMath()
+ {
+ return TAMath.Stoch(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ StochFastK,
+ StochSlowK,
+ MAType.Sma,
+ StochSlowD,
+ MAType.Sma);
+ }
+
+ ///
+ /// Stochastic RSI, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode StochRsi_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] fastK = Output0;
+ double[] fastD = Output1;
+
+ return TAFunc.StochRsi(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ StochRsiPeriod,
+ StochRsiFastK,
+ StochRsiFastD,
+ MAType.Sma,
+ ref begIdx,
+ ref nbElement,
+ ref fastK,
+ ref fastD);
+ }
+
+ ///
+ /// Stochastic RSI, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public StochRsiResult StochRsi_TAMath()
+ {
+ return TAMath.StochRsi(StartIdx, EndIdx, Series.Doubles.Close, StochRsiPeriod, StochRsiFastK, StochRsiFastD);
+ }
+
+ ///
+ /// Average directional movement index, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Adx_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Adx(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AdxPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Average directional movement index, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public AdxResult Adx_TAMath()
+ {
+ return TAMath.Adx(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low, Series.Doubles.Close, AdxPeriod);
+ }
+
+ ///
+ /// Commodity channel index, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Cci_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Cci(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ CciPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Commodity channel index, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public CciResult Cci_TAMath()
+ {
+ return TAMath.Cci(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low, Series.Doubles.Close, CciPeriod);
+ }
+
+ ///
+ /// Money flow index, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Mfi_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Mfi(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ Series.Doubles.Volume,
+ MfiPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Money flow index, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public MfiResult Mfi_TAMath()
+ {
+ return TAMath.Mfi(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ Series.Doubles.Volume,
+ MfiPeriod);
+ }
+
+ ///
+ /// Williams %R, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode WillR_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.WillR(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ WillRPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Williams %R, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public WillRResult WillR_TAMath()
+ {
+ return TAMath.WillR(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low, Series.Doubles.Close, WillRPeriod);
+ }
+
+ ///
+ /// Percentage price oscillator, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Ppo_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Ppo(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ PpoFast,
+ PpoSlow,
+ MAType.Sma,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Percentage price oscillator, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public PpoResult Ppo_TAMath()
+ {
+ return TAMath.Ppo(StartIdx, EndIdx, Series.Doubles.Close, PpoFast, PpoSlow);
+ }
+
+ ///
+ /// Rate of change, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Roc_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Roc(StartIdx, EndIdx, Series.Doubles.Close, RocPeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Rate of change, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public RocResult Roc_TAMath()
+ {
+ return TAMath.Roc(StartIdx, EndIdx, Series.Doubles.Close, RocPeriod);
+ }
+
+ ///
+ /// Ultimate oscillator, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode UltOsc_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.UltOsc(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ UltOscPeriod1,
+ UltOscPeriod2,
+ UltOscPeriod3,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Ultimate oscillator, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public UltOscResult UltOsc_TAMath()
+ {
+ return TAMath.UltOsc(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ UltOscPeriod1,
+ UltOscPeriod2,
+ UltOscPeriod3);
+ }
+
+ ///
+ /// Aroon, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Aroon_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] aroonDown = Output0;
+ double[] aroonUp = Output1;
+
+ return TAFunc.Aroon(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ AroonPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref aroonDown,
+ ref aroonUp);
+ }
+
+ ///
+ /// Aroon, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public AroonResult Aroon_TAMath()
+ {
+ return TAMath.Aroon(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low, AroonPeriod);
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/NativeComparisonBenchmarks.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/NativeComparisonBenchmarks.cs
new file mode 100644
index 00000000..161e8392
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/NativeComparisonBenchmarks.cs
@@ -0,0 +1,750 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using TechnicalAnalysis.Benchmarks.Interop;
+using TechnicalAnalysis.Common;
+using TechnicalAnalysis.Functions;
+
+namespace TechnicalAnalysis.Benchmarks.Benchmarks;
+
+///
+/// Head-to-head comparison between the managed TaLibStandard kernels and the original TA-Lib C library.
+///
+///
+///
+/// This class only runs when is . Program
+/// removes it from the runnable set otherwise, so the suite has no native dependency by default.
+///
+///
+/// Both sides use caller-supplied output buffers allocated in [GlobalSetup], so the comparison is
+/// algorithm against algorithm with no allocation noise on either side. The managed side deliberately uses
+/// TAFunc rather than TAMath for exactly that reason.
+///
+///
+/// [GlobalSetup] runs both implementations once and asserts they agree, so a "faster" result can never come
+/// from computing the wrong thing. Benchmarks are grouped per indicator with the managed implementation as the
+/// baseline, so the Ratio column reads directly as "native time / managed time".
+///
+///
+[MemoryDiagnoser]
+[CategoriesColumn]
+[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
+[BenchmarkCategory(BenchmarkCategories.NativeComparison)]
+public class NativeComparisonBenchmarks : MarketDataBenchmarkBase
+{
+ private const int SmaPeriod = 30;
+ private const int EmaPeriod = 30;
+ private const int RsiPeriod = 14;
+ private const int MacdFast = 12;
+ private const int MacdSlow = 26;
+ private const int MacdSignal = 9;
+ private const int BbandsPeriod = 20;
+ private const int AtrPeriod = 14;
+ private const int AdxPeriod = 14;
+ private const int StochFastK = 5;
+ private const int StochSlowK = 3;
+ private const int StochSlowD = 3;
+
+ private double[] _native0 = [];
+ private double[] _native1 = [];
+ private double[] _native2 = [];
+
+ ///
+ /// Generates the market data, allocates every output buffer and proves managed and native agree.
+ ///
+ ///
+ /// Thrown when the native library is unavailable, when either implementation reports a failure, or when the two
+ /// implementations disagree.
+ ///
+ [GlobalSetup]
+ public void Setup()
+ {
+ if (!NativeTaLib.IsAvailable)
+ {
+ throw new InvalidOperationException(
+ "Native TA-Lib is not available. NativeComparisonBenchmarks must not be scheduled in that case. " +
+ NativeTaLib.Diagnostics);
+ }
+
+ InitializeMarketData();
+
+ _native0 = new double[Length];
+ _native1 = new double[Length];
+ _native2 = new double[Length];
+
+ VerifyEquivalence();
+ }
+
+ ///
+ /// Simple moving average, managed kernel.
+ ///
+ /// The return code of the calculation.
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Sma")]
+ public RetCode Sma_Managed()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Sma(StartIdx, EndIdx, Series.Doubles.Close, SmaPeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Simple moving average, native TA-Lib C.
+ ///
+ /// The native return code.
+ [Benchmark]
+ [BenchmarkCategory("Sma")]
+ public int Sma_Native()
+ {
+ return NativeTaLib.Sma(StartIdx, EndIdx, Series.Doubles.Close, SmaPeriod, out _, out _, _native0);
+ }
+
+ ///
+ /// Exponential moving average, managed kernel.
+ ///
+ /// The return code of the calculation.
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Ema")]
+ public RetCode Ema_Managed()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Ema(StartIdx, EndIdx, Series.Doubles.Close, EmaPeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Exponential moving average, native TA-Lib C.
+ ///
+ /// The native return code.
+ [Benchmark]
+ [BenchmarkCategory("Ema")]
+ public int Ema_Native()
+ {
+ return NativeTaLib.Ema(StartIdx, EndIdx, Series.Doubles.Close, EmaPeriod, out _, out _, _native0);
+ }
+
+ ///
+ /// Relative strength index, managed kernel.
+ ///
+ /// The return code of the calculation.
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Rsi")]
+ public RetCode Rsi_Managed()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Rsi(StartIdx, EndIdx, Series.Doubles.Close, RsiPeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Relative strength index, native TA-Lib C.
+ ///
+ /// The native return code.
+ [Benchmark]
+ [BenchmarkCategory("Rsi")]
+ public int Rsi_Native()
+ {
+ return NativeTaLib.Rsi(StartIdx, EndIdx, Series.Doubles.Close, RsiPeriod, out _, out _, _native0);
+ }
+
+ ///
+ /// MACD, managed kernel.
+ ///
+ /// The return code of the calculation.
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Macd")]
+ public RetCode Macd_Managed()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] macd = Output0;
+ double[] signal = Output1;
+ double[] histogram = Output2;
+
+ return TAFunc.Macd(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ MacdFast,
+ MacdSlow,
+ MacdSignal,
+ ref begIdx,
+ ref nbElement,
+ ref macd,
+ ref signal,
+ ref histogram);
+ }
+
+ ///
+ /// MACD, native TA-Lib C.
+ ///
+ /// The native return code.
+ [Benchmark]
+ [BenchmarkCategory("Macd")]
+ public int Macd_Native()
+ {
+ return NativeTaLib.Macd(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ MacdFast,
+ MacdSlow,
+ MacdSignal,
+ out _,
+ out _,
+ _native0,
+ _native1,
+ _native2);
+ }
+
+ ///
+ /// Bollinger Bands, managed kernel.
+ ///
+ /// The return code of the calculation.
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Bbands")]
+ public RetCode Bbands_Managed()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] upper = Output0;
+ double[] middle = Output1;
+ double[] lower = Output2;
+
+ return TAFunc.BollingerBands(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ BbandsPeriod,
+ 2.0,
+ 2.0,
+ MAType.Sma,
+ ref begIdx,
+ ref nbElement,
+ ref upper,
+ ref middle,
+ ref lower);
+ }
+
+ ///
+ /// Bollinger Bands, native TA-Lib C.
+ ///
+ /// The native return code.
+ [Benchmark]
+ [BenchmarkCategory("Bbands")]
+ public int Bbands_Native()
+ {
+ return NativeTaLib.Bbands(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ BbandsPeriod,
+ 2.0,
+ 2.0,
+ (int)MAType.Sma,
+ out _,
+ out _,
+ _native0,
+ _native1,
+ _native2);
+ }
+
+ ///
+ /// Average true range, managed kernel.
+ ///
+ /// The return code of the calculation.
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Atr")]
+ public RetCode Atr_Managed()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Atr(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AtrPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Average true range, native TA-Lib C.
+ ///
+ /// The native return code.
+ [Benchmark]
+ [BenchmarkCategory("Atr")]
+ public int Atr_Native()
+ {
+ return NativeTaLib.Atr(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AtrPeriod,
+ out _,
+ out _,
+ _native0);
+ }
+
+ ///
+ /// Average directional movement index, managed kernel.
+ ///
+ /// The return code of the calculation.
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Adx")]
+ public RetCode Adx_Managed()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Adx(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AdxPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Average directional movement index, native TA-Lib C.
+ ///
+ /// The native return code.
+ [Benchmark]
+ [BenchmarkCategory("Adx")]
+ public int Adx_Native()
+ {
+ return NativeTaLib.Adx(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AdxPeriod,
+ out _,
+ out _,
+ _native0);
+ }
+
+ ///
+ /// Slow stochastic, managed kernel.
+ ///
+ /// The return code of the calculation.
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory("Stoch")]
+ public RetCode Stoch_Managed()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] slowK = Output0;
+ double[] slowD = Output1;
+
+ return TAFunc.Stoch(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ StochFastK,
+ StochSlowK,
+ MAType.Sma,
+ StochSlowD,
+ MAType.Sma,
+ ref begIdx,
+ ref nbElement,
+ ref slowK,
+ ref slowD);
+ }
+
+ ///
+ /// Slow stochastic, native TA-Lib C.
+ ///
+ /// The native return code.
+ [Benchmark]
+ [BenchmarkCategory("Stoch")]
+ public int Stoch_Native()
+ {
+ return NativeTaLib.Stoch(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ StochFastK,
+ StochSlowK,
+ (int)MAType.Sma,
+ StochSlowD,
+ (int)MAType.Sma,
+ out _,
+ out _,
+ _native0,
+ _native1);
+ }
+
+ private void VerifyEquivalence()
+ {
+ VerifySingleOutput(
+ "SMA",
+ (out int beg, out int count, double[] buffer) =>
+ {
+ int b = 0;
+ int n = 0;
+ double[] local = buffer;
+ RetCode code = TAFunc.Sma(StartIdx, EndIdx, Series.Doubles.Close, SmaPeriod, ref b, ref n, ref local);
+ beg = b;
+ count = n;
+ return code == RetCode.Success;
+ },
+ (out int beg, out int count, double[] buffer) =>
+ NativeTaLib.Sma(StartIdx, EndIdx, Series.Doubles.Close, SmaPeriod, out beg, out count, buffer)
+ == NativeTaLib.Success);
+
+ VerifySingleOutput(
+ "EMA",
+ (out int beg, out int count, double[] buffer) =>
+ {
+ int b = 0;
+ int n = 0;
+ double[] local = buffer;
+ RetCode code = TAFunc.Ema(StartIdx, EndIdx, Series.Doubles.Close, EmaPeriod, ref b, ref n, ref local);
+ beg = b;
+ count = n;
+ return code == RetCode.Success;
+ },
+ (out int beg, out int count, double[] buffer) =>
+ NativeTaLib.Ema(StartIdx, EndIdx, Series.Doubles.Close, EmaPeriod, out beg, out count, buffer)
+ == NativeTaLib.Success);
+
+ VerifySingleOutput(
+ "RSI",
+ (out int beg, out int count, double[] buffer) =>
+ {
+ int b = 0;
+ int n = 0;
+ double[] local = buffer;
+ RetCode code = TAFunc.Rsi(StartIdx, EndIdx, Series.Doubles.Close, RsiPeriod, ref b, ref n, ref local);
+ beg = b;
+ count = n;
+ return code == RetCode.Success;
+ },
+ (out int beg, out int count, double[] buffer) =>
+ NativeTaLib.Rsi(StartIdx, EndIdx, Series.Doubles.Close, RsiPeriod, out beg, out count, buffer)
+ == NativeTaLib.Success);
+
+ VerifySingleOutput(
+ "ATR",
+ (out int beg, out int count, double[] buffer) =>
+ {
+ int b = 0;
+ int n = 0;
+ double[] local = buffer;
+ RetCode code = TAFunc.Atr(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AtrPeriod,
+ ref b,
+ ref n,
+ ref local);
+ beg = b;
+ count = n;
+ return code == RetCode.Success;
+ },
+ (out int beg, out int count, double[] buffer) =>
+ NativeTaLib.Atr(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AtrPeriod,
+ out beg,
+ out count,
+ buffer)
+ == NativeTaLib.Success);
+
+ VerifySingleOutput(
+ "ADX",
+ (out int beg, out int count, double[] buffer) =>
+ {
+ int b = 0;
+ int n = 0;
+ double[] local = buffer;
+ RetCode code = TAFunc.Adx(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AdxPeriod,
+ ref b,
+ ref n,
+ ref local);
+ beg = b;
+ count = n;
+ return code == RetCode.Success;
+ },
+ (out int beg, out int count, double[] buffer) =>
+ NativeTaLib.Adx(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AdxPeriod,
+ out beg,
+ out count,
+ buffer)
+ == NativeTaLib.Success);
+
+ VerifyMacd();
+ VerifyBbands();
+ VerifyStoch();
+ }
+
+ private void VerifyMacd()
+ {
+ int managedBeg = 0;
+ int managedCount = 0;
+ double[] managedMacd = new double[Length];
+ double[] managedSignal = new double[Length];
+ double[] managedHist = new double[Length];
+
+ RetCode managedCode = TAFunc.Macd(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ MacdFast,
+ MacdSlow,
+ MacdSignal,
+ ref managedBeg,
+ ref managedCount,
+ ref managedMacd,
+ ref managedSignal,
+ ref managedHist);
+
+ EnsureSucceeded("MACD", managedCode == RetCode.Success, isManaged: true);
+
+ double[] nativeMacd = new double[Length];
+ double[] nativeSignal = new double[Length];
+ double[] nativeHist = new double[Length];
+
+ int nativeCode = NativeTaLib.Macd(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ MacdFast,
+ MacdSlow,
+ MacdSignal,
+ out int nativeBeg,
+ out int nativeCount,
+ nativeMacd,
+ nativeSignal,
+ nativeHist);
+
+ EnsureSucceeded("MACD", nativeCode == NativeTaLib.Success, isManaged: false);
+
+ NativeEquivalence.AssertEquivalent("MACD (line)", managedBeg, managedCount, managedMacd, nativeBeg, nativeCount, nativeMacd);
+ NativeEquivalence.AssertEquivalent(
+ "MACD (signal)",
+ managedBeg,
+ managedCount,
+ managedSignal,
+ nativeBeg,
+ nativeCount,
+ nativeSignal);
+ NativeEquivalence.AssertEquivalent("MACD (hist)", managedBeg, managedCount, managedHist, nativeBeg, nativeCount, nativeHist);
+ }
+
+ private void VerifyBbands()
+ {
+ int managedBeg = 0;
+ int managedCount = 0;
+ double[] managedUpper = new double[Length];
+ double[] managedMiddle = new double[Length];
+ double[] managedLower = new double[Length];
+
+ RetCode managedCode = TAFunc.BollingerBands(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ BbandsPeriod,
+ 2.0,
+ 2.0,
+ MAType.Sma,
+ ref managedBeg,
+ ref managedCount,
+ ref managedUpper,
+ ref managedMiddle,
+ ref managedLower);
+
+ EnsureSucceeded("BBANDS", managedCode == RetCode.Success, isManaged: true);
+
+ double[] nativeUpper = new double[Length];
+ double[] nativeMiddle = new double[Length];
+ double[] nativeLower = new double[Length];
+
+ int nativeCode = NativeTaLib.Bbands(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ BbandsPeriod,
+ 2.0,
+ 2.0,
+ (int)MAType.Sma,
+ out int nativeBeg,
+ out int nativeCount,
+ nativeUpper,
+ nativeMiddle,
+ nativeLower);
+
+ EnsureSucceeded("BBANDS", nativeCode == NativeTaLib.Success, isManaged: false);
+
+ NativeEquivalence.AssertEquivalent(
+ "BBANDS (upper)",
+ managedBeg,
+ managedCount,
+ managedUpper,
+ nativeBeg,
+ nativeCount,
+ nativeUpper);
+ NativeEquivalence.AssertEquivalent(
+ "BBANDS (middle)",
+ managedBeg,
+ managedCount,
+ managedMiddle,
+ nativeBeg,
+ nativeCount,
+ nativeMiddle);
+ NativeEquivalence.AssertEquivalent(
+ "BBANDS (lower)",
+ managedBeg,
+ managedCount,
+ managedLower,
+ nativeBeg,
+ nativeCount,
+ nativeLower);
+ }
+
+ private void VerifyStoch()
+ {
+ int managedBeg = 0;
+ int managedCount = 0;
+ double[] managedSlowK = new double[Length];
+ double[] managedSlowD = new double[Length];
+
+ RetCode managedCode = TAFunc.Stoch(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ StochFastK,
+ StochSlowK,
+ MAType.Sma,
+ StochSlowD,
+ MAType.Sma,
+ ref managedBeg,
+ ref managedCount,
+ ref managedSlowK,
+ ref managedSlowD);
+
+ EnsureSucceeded("STOCH", managedCode == RetCode.Success, isManaged: true);
+
+ double[] nativeSlowK = new double[Length];
+ double[] nativeSlowD = new double[Length];
+
+ int nativeCode = NativeTaLib.Stoch(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ StochFastK,
+ StochSlowK,
+ (int)MAType.Sma,
+ StochSlowD,
+ (int)MAType.Sma,
+ out int nativeBeg,
+ out int nativeCount,
+ nativeSlowK,
+ nativeSlowD);
+
+ EnsureSucceeded("STOCH", nativeCode == NativeTaLib.Success, isManaged: false);
+
+ NativeEquivalence.AssertEquivalent(
+ "STOCH (slowK)",
+ managedBeg,
+ managedCount,
+ managedSlowK,
+ nativeBeg,
+ nativeCount,
+ nativeSlowK);
+ NativeEquivalence.AssertEquivalent(
+ "STOCH (slowD)",
+ managedBeg,
+ managedCount,
+ managedSlowD,
+ nativeBeg,
+ nativeCount,
+ nativeSlowD);
+ }
+
+ private void VerifySingleOutput(string indicator, SingleOutputInvoker managed, SingleOutputInvoker @native)
+ {
+ double[] managedBuffer = new double[Length];
+ double[] nativeBuffer = new double[Length];
+
+ EnsureSucceeded(indicator, managed(out int managedBeg, out int managedCount, managedBuffer), isManaged: true);
+ EnsureSucceeded(indicator, @native(out int nativeBeg, out int nativeCount, nativeBuffer), isManaged: false);
+
+ NativeEquivalence.AssertEquivalent(
+ indicator,
+ managedBeg,
+ managedCount,
+ managedBuffer,
+ nativeBeg,
+ nativeCount,
+ nativeBuffer);
+ }
+
+ private static void EnsureSucceeded(string indicator, bool succeeded, bool isManaged)
+ {
+ if (succeeded)
+ {
+ return;
+ }
+
+ throw new InvalidOperationException(
+ $"{indicator}: the {(isManaged ? "managed" : "native")} implementation reported a failure during the " +
+ "equivalence check, so no timing can be trusted.");
+ }
+
+ ///
+ /// Invokes one single-output indicator into the supplied buffer.
+ ///
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated output buffer.
+ /// when the call succeeded.
+ private delegate bool SingleOutputInvoker(out int outBegIdx, out int outNbElement, double[] buffer);
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/OverlapStudiesBenchmarks.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/OverlapStudiesBenchmarks.cs
new file mode 100644
index 00000000..45b04ac0
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/OverlapStudiesBenchmarks.cs
@@ -0,0 +1,353 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using BenchmarkDotNet.Attributes;
+using TechnicalAnalysis.Common;
+using TechnicalAnalysis.Functions;
+
+namespace TechnicalAnalysis.Benchmarks.Benchmarks;
+
+///
+/// Overlap studies: moving averages, envelopes and the parabolic SAR.
+///
+///
+///
+/// Every indicator appears twice. The _TAFunc variant writes into buffers allocated in
+/// [GlobalSetup] and therefore reports the pure algorithm cost with zero managed allocation. The
+/// _TAMath variant calls the ergonomic API, which allocates one output array per output series plus one
+/// result record per call; the delta between the two is the price of the convenient API.
+///
+///
+/// There is deliberately no Ratio column here. BenchmarkDotNet's default logical group
+/// is (Job, Params), so a single Baseline = true would ratio every method in the class against
+/// that one method β an EMA row would read as "3.7x slower" when what it measured was EMA against SMA, not
+/// TAMath against TAFunc. Grouping per indicator cannot fix it either, because
+/// BenchmarkLogicalGroupRule.ByCategory keys on the whole category set and the TAFunc /
+/// TAMath categories put the two halves of a pair in different groups. Read the two rows of the same
+/// indicator and divide the Mean column yourself; is the one
+/// suite whose categories do line up, and it is the one that carries baselines.
+///
+///
+[MemoryDiagnoser]
+[CategoriesColumn]
+[BenchmarkCategory(BenchmarkCategories.OverlapStudies)]
+public class OverlapStudiesBenchmarks : MarketDataBenchmarkBase
+{
+ private const int TimePeriod = 30;
+ private const int BollingerPeriod = 20;
+ private const int MidPointPeriod = 14;
+ private const int T3Period = 5;
+ private const double T3VFactor = 0.7;
+
+ ///
+ /// Generates the market data and allocates the output buffers.
+ ///
+ [GlobalSetup]
+ public void Setup()
+ {
+ InitializeMarketData();
+ }
+
+ ///
+ /// Simple moving average, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Sma_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Sma(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Simple moving average, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public SmaResult Sma_TAMath()
+ {
+ return TAMath.Sma(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod);
+ }
+
+ ///
+ /// Exponential moving average, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Ema_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Ema(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Exponential moving average, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public EmaResult Ema_TAMath()
+ {
+ return TAMath.Ema(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod);
+ }
+
+ ///
+ /// Weighted moving average, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Wma_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Wma(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Weighted moving average, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public WmaResult Wma_TAMath()
+ {
+ return TAMath.Wma(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod);
+ }
+
+ ///
+ /// Double exponential moving average, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Dema_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Dema(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Double exponential moving average, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public DemaResult Dema_TAMath()
+ {
+ return TAMath.Dema(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod);
+ }
+
+ ///
+ /// Triple exponential moving average, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Tema_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Tema(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Triple exponential moving average, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public TemaResult Tema_TAMath()
+ {
+ return TAMath.Tema(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod);
+ }
+
+ ///
+ /// Triangular moving average, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Trima_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Trima(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Triangular moving average, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public TrimaResult Trima_TAMath()
+ {
+ return TAMath.Trima(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod);
+ }
+
+ ///
+ /// Kaufman adaptive moving average, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Kama_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Kama(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Kaufman adaptive moving average, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public KamaResult Kama_TAMath()
+ {
+ return TAMath.Kama(StartIdx, EndIdx, Series.Doubles.Close, TimePeriod);
+ }
+
+ ///
+ /// Tillson T3 moving average, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode T3_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.T3(StartIdx, EndIdx, Series.Doubles.Close, T3Period, T3VFactor, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Tillson T3 moving average, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public T3Result T3_TAMath()
+ {
+ return TAMath.T3(StartIdx, EndIdx, Series.Doubles.Close, T3Period, T3VFactor);
+ }
+
+ ///
+ /// Bollinger Bands, allocation-free path. Three output series are written into pre-allocated buffers.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode BollingerBands_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] upper = Output0;
+ double[] middle = Output1;
+ double[] lower = Output2;
+
+ return TAFunc.BollingerBands(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ BollingerPeriod,
+ 2.0,
+ 2.0,
+ MAType.Sma,
+ ref begIdx,
+ ref nbElement,
+ ref upper,
+ ref middle,
+ ref lower);
+ }
+
+ ///
+ /// Bollinger Bands, ergonomic path. Allocates three output arrays plus a result record.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public BollingerBandsResult BollingerBands_TAMath()
+ {
+ return TAMath.BollingerBands(StartIdx, EndIdx, Series.Doubles.Close, BollingerPeriod);
+ }
+
+ ///
+ /// MidPoint over a period, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode MidPoint_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.MidPoint(StartIdx, EndIdx, Series.Doubles.Close, MidPointPeriod, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// MidPoint over a period, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public MidPointResult MidPoint_TAMath()
+ {
+ return TAMath.MidPoint(StartIdx, EndIdx, Series.Doubles.Close, MidPointPeriod);
+ }
+
+ ///
+ /// Parabolic SAR, allocation-free path. Note that the SAR implementation itself allocates a few tiny scratch
+ /// arrays internally, so this variant is not literally zero-allocation.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Sar_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Sar(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ 0.02,
+ 0.2,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Parabolic SAR, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public SarResult Sar_TAMath()
+ {
+ return TAMath.Sar(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low);
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/PrecisionBenchmarks.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/PrecisionBenchmarks.cs
new file mode 100644
index 00000000..b53fb5b8
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/PrecisionBenchmarks.cs
@@ -0,0 +1,208 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using BenchmarkDotNet.Attributes;
+using TechnicalAnalysis.Functions;
+
+namespace TechnicalAnalysis.Benchmarks.Benchmarks;
+
+///
+/// Double versus float on the same indicators, so the cost of the overloads is visible.
+///
+///
+///
+/// The TAFunc kernels are written for only. Every float overload on
+/// TAMath therefore widens its inputs into freshly allocated double[] arrays and then calls the same
+/// kernel. A float benchmark consequently measures the double kernel plus one widening pass and one array
+/// allocation per input series; it can never be faster than its double counterpart, and the memory columns
+/// show exactly how much extra it costs.
+///
+///
+/// This suite intentionally uses the ergonomic TAMath API for both precisions, because that is the only API
+/// where a float entry point exists at all. Use and friends for the
+/// allocation-free comparison.
+///
+///
+/// There is deliberately no Ratio column here. A single Baseline = true would
+/// ratio every row against one method β the default logical group is (Job, Params), not (indicator) β so a
+/// float row would appear to be compared against its own double counterpart when it was in fact
+/// compared against Sma_Double. Compare the Mean and Allocated columns of the two rows
+/// of the same indicator instead.
+///
+///
+[MemoryDiagnoser]
+[CategoriesColumn]
+[BenchmarkCategory(BenchmarkCategories.Precision)]
+public class PrecisionBenchmarks : MarketDataBenchmarkBase
+{
+ private const int MaPeriod = 30;
+ private const int RsiPeriod = 14;
+ private const int AtrPeriod = 14;
+ private const int BollingerPeriod = 20;
+
+ ///
+ /// Generates the market data.
+ ///
+ [GlobalSetup]
+ public void Setup()
+ {
+ InitializeMarketData();
+ }
+
+ ///
+ /// Simple moving average over double inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public SmaResult Sma_Double()
+ {
+ return TAMath.Sma(StartIdx, EndIdx, Series.Doubles.Close, MaPeriod);
+ }
+
+ ///
+ /// Simple moving average over float inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public SmaResult Sma_Float()
+ {
+ return TAMath.Sma(StartIdx, EndIdx, Series.Singles.Close, MaPeriod);
+ }
+
+ ///
+ /// Exponential moving average over double inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public EmaResult Ema_Double()
+ {
+ return TAMath.Ema(StartIdx, EndIdx, Series.Doubles.Close, MaPeriod);
+ }
+
+ ///
+ /// Exponential moving average over float inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public EmaResult Ema_Float()
+ {
+ return TAMath.Ema(StartIdx, EndIdx, Series.Singles.Close, MaPeriod);
+ }
+
+ ///
+ /// Relative strength index over double inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public RsiResult Rsi_Double()
+ {
+ return TAMath.Rsi(StartIdx, EndIdx, Series.Doubles.Close, RsiPeriod);
+ }
+
+ ///
+ /// Relative strength index over float inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public RsiResult Rsi_Float()
+ {
+ return TAMath.Rsi(StartIdx, EndIdx, Series.Singles.Close, RsiPeriod);
+ }
+
+ ///
+ /// MACD over double inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public MacdResult Macd_Double()
+ {
+ return TAMath.Macd(StartIdx, EndIdx, Series.Doubles.Close);
+ }
+
+ ///
+ /// MACD over float inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public MacdResult Macd_Float()
+ {
+ return TAMath.Macd(StartIdx, EndIdx, Series.Singles.Close);
+ }
+
+ ///
+ /// Bollinger Bands over double inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public BollingerBandsResult BollingerBands_Double()
+ {
+ return TAMath.BollingerBands(StartIdx, EndIdx, Series.Doubles.Close, BollingerPeriod);
+ }
+
+ ///
+ /// Bollinger Bands over float inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public BollingerBandsResult BollingerBands_Float()
+ {
+ return TAMath.BollingerBands(StartIdx, EndIdx, Series.Singles.Close, BollingerPeriod);
+ }
+
+ ///
+ /// Average true range over double inputs. Three input series must be widened on the float path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public AtrResult Atr_Double()
+ {
+ return TAMath.Atr(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low, Series.Doubles.Close, AtrPeriod);
+ }
+
+ ///
+ /// Average true range over float inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public AtrResult Atr_Float()
+ {
+ return TAMath.Atr(StartIdx, EndIdx, Series.Singles.High, Series.Singles.Low, Series.Singles.Close, AtrPeriod);
+ }
+
+ ///
+ /// Correlation over double inputs. Two input series must be widened on the float path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.DoublePrecision)]
+ public CorrelResult Correl_Double()
+ {
+ return TAMath.Correl(StartIdx, EndIdx, Series.Doubles.Close, Series.ReferenceDoubles.Close, MaPeriod);
+ }
+
+ ///
+ /// Correlation over float inputs.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.SinglePrecision)]
+ public CorrelResult Correl_Float()
+ {
+ return TAMath.Correl(StartIdx, EndIdx, Series.Singles.Close, Series.ReferenceSingles.Close, MaPeriod);
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/VolatilityVolumeBenchmarks.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/VolatilityVolumeBenchmarks.cs
new file mode 100644
index 00000000..03b1341b
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Benchmarks/VolatilityVolumeBenchmarks.cs
@@ -0,0 +1,396 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using BenchmarkDotNet.Attributes;
+using TechnicalAnalysis.Common;
+using TechnicalAnalysis.Functions;
+
+namespace TechnicalAnalysis.Benchmarks.Benchmarks;
+
+///
+/// Volatility indicators, volume indicators and the two-series statistic functions.
+///
+///
+///
+/// Correl and Beta consume the primary close series and the correlated reference close series produced by
+/// , so the statistics they compute are meaningful rather than degenerate.
+///
+///
+/// There is deliberately no Ratio column here. BenchmarkDotNet's default logical group
+/// is (Job, Params), so a single Baseline = true would ratio every method in the class against
+/// that one method β an EMA row would read as "3.7x slower" when what it measured was EMA against SMA, not
+/// TAMath against TAFunc. Grouping per indicator cannot fix it either, because
+/// BenchmarkLogicalGroupRule.ByCategory keys on the whole category set and the TAFunc /
+/// TAMath categories put the two halves of a pair in different groups. Read the two rows of the same
+/// indicator and divide the Mean column yourself; is the one
+/// suite whose categories do line up, and it is the one that carries baselines.
+///
+///
+[MemoryDiagnoser]
+[CategoriesColumn]
+[BenchmarkCategory(BenchmarkCategories.VolatilityVolume)]
+public class VolatilityVolumeBenchmarks : MarketDataBenchmarkBase
+{
+ private const int AtrPeriod = 14;
+ private const int NatrPeriod = 14;
+ private const int AdOscFast = 3;
+ private const int AdOscSlow = 10;
+ private const int StdDevPeriod = 20;
+ private const int VariancePeriod = 20;
+ private const int CorrelPeriod = 30;
+ private const int BetaPeriod = 5;
+ private const double NbDev = 1.0;
+
+ ///
+ /// Generates the market data and allocates the output buffers.
+ ///
+ [GlobalSetup]
+ public void Setup()
+ {
+ InitializeMarketData();
+ }
+
+ ///
+ /// Average true range, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Atr_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Atr(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ AtrPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Average true range, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public AtrResult Atr_TAMath()
+ {
+ return TAMath.Atr(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low, Series.Doubles.Close, AtrPeriod);
+ }
+
+ ///
+ /// Normalized average true range, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Natr_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Natr(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ NatrPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Normalized average true range, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public NatrResult Natr_TAMath()
+ {
+ return TAMath.Natr(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low, Series.Doubles.Close, NatrPeriod);
+ }
+
+ ///
+ /// True range, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode TrueRange_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.TrueRange(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// True range, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public TrueRangeResult TrueRange_TAMath()
+ {
+ return TAMath.TrueRange(StartIdx, EndIdx, Series.Doubles.High, Series.Doubles.Low, Series.Doubles.Close);
+ }
+
+ ///
+ /// On balance volume, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Obv_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Obv(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ Series.Doubles.Volume,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// On balance volume, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public ObvResult Obv_TAMath()
+ {
+ return TAMath.Obv(StartIdx, EndIdx, Series.Doubles.Close, Series.Doubles.Volume);
+ }
+
+ ///
+ /// Chaikin accumulation / distribution line, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Ad_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Ad(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ Series.Doubles.Volume,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Chaikin accumulation / distribution line, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public AdResult Ad_TAMath()
+ {
+ return TAMath.Ad(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ Series.Doubles.Volume);
+ }
+
+ ///
+ /// Chaikin accumulation / distribution oscillator, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode AdOsc_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.AdOsc(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ Series.Doubles.Volume,
+ AdOscFast,
+ AdOscSlow,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Chaikin accumulation / distribution oscillator, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public AdOscResult AdOsc_TAMath()
+ {
+ return TAMath.AdOsc(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.High,
+ Series.Doubles.Low,
+ Series.Doubles.Close,
+ Series.Doubles.Volume,
+ AdOscFast,
+ AdOscSlow);
+ }
+
+ ///
+ /// Rolling standard deviation, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode StdDev_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.StdDev(StartIdx, EndIdx, Series.Doubles.Close, StdDevPeriod, NbDev, ref begIdx, ref nbElement, ref output);
+ }
+
+ ///
+ /// Rolling standard deviation, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public StdDevResult StdDev_TAMath()
+ {
+ return TAMath.StdDev(StartIdx, EndIdx, Series.Doubles.Close, StdDevPeriod, NbDev);
+ }
+
+ ///
+ /// Rolling variance, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Variance_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Variance(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ VariancePeriod,
+ NbDev,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Rolling variance, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public VarianceResult Variance_TAMath()
+ {
+ return TAMath.Variance(StartIdx, EndIdx, Series.Doubles.Close, VariancePeriod, NbDev);
+ }
+
+ ///
+ /// Pearson correlation between the primary and the reference instrument, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Correl_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Correl(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ Series.ReferenceDoubles.Close,
+ CorrelPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Pearson correlation between the primary and the reference instrument, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public CorrelResult Correl_TAMath()
+ {
+ return TAMath.Correl(StartIdx, EndIdx, Series.Doubles.Close, Series.ReferenceDoubles.Close, CorrelPeriod);
+ }
+
+ ///
+ /// Beta of the primary instrument against the reference instrument, allocation-free path.
+ ///
+ /// The return code of the calculation.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaFunc)]
+ public RetCode Beta_TAFunc()
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ double[] output = Output0;
+ return TAFunc.Beta(
+ StartIdx,
+ EndIdx,
+ Series.Doubles.Close,
+ Series.ReferenceDoubles.Close,
+ BetaPeriod,
+ ref begIdx,
+ ref nbElement,
+ ref output);
+ }
+
+ ///
+ /// Beta of the primary instrument against the reference instrument, ergonomic path.
+ ///
+ /// The calculated result.
+ [Benchmark]
+ [BenchmarkCategory(BenchmarkCategories.TaMath)]
+ public BetaResult Beta_TAMath()
+ {
+ return TAMath.Beta(StartIdx, EndIdx, Series.Doubles.Close, Series.ReferenceDoubles.Close, BetaPeriod);
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Configuration/TaLibBenchmarkConfig.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Configuration/TaLibBenchmarkConfig.cs
new file mode 100644
index 00000000..e2f5a4a3
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Configuration/TaLibBenchmarkConfig.cs
@@ -0,0 +1,62 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using BenchmarkDotNet.Columns;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Exporters;
+using BenchmarkDotNet.Exporters.Json;
+using BenchmarkDotNet.Loggers;
+using BenchmarkDotNet.Order;
+using BenchmarkDotNet.Reports;
+
+namespace TechnicalAnalysis.Benchmarks.Configuration;
+
+///
+/// The shared BenchmarkDotNet configuration for the TaLibStandard performance suite.
+///
+///
+///
+/// The configuration deliberately declares no job. BenchmarkDotNet then falls back to Job.Default, which
+/// means the standard command line switches (--job Dry, --job Short, --runtimes, ...) add
+/// exactly one job instead of multiplying an already-declared one.
+///
+///
+/// Exporters: GitHub-flavoured markdown (paste straight into an issue or a release note) and full JSON
+/// (machine readable, for tracking regressions between releases). Both land in
+/// BenchmarkDotNet.Artifacts/results next to the executable.
+///
+///
+public sealed class TaLibBenchmarkConfig : ManualConfig
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TaLibBenchmarkConfig()
+ {
+ AddLogger(ConsoleLogger.Default);
+ AddColumnProvider(DefaultColumnProviders.Instance);
+ AddColumn(StatisticColumn.OperationsPerSecond);
+
+ // Also declared per class via [MemoryDiagnoser]; BenchmarkDotNet de-duplicates the singleton instance.
+ AddDiagnoser(MemoryDiagnoser.Default);
+
+ AddExporter(MarkdownExporter.GitHub);
+ AddExporter(JsonExporter.Full);
+
+ // Declared order keeps every "_TAFunc" / "_TAMath" and "_Managed" / "_Native" pair adjacent in the summary,
+ // which is what a reader of this suite actually wants to compare.
+ WithOrderer(new DefaultOrderer(SummaryOrderPolicy.Declared, MethodOrderPolicy.Declared));
+
+ WithSummaryStyle(SummaryStyle.Default
+ .WithRatioStyle(RatioStyle.Trend)
+ .WithMaxParameterColumnWidth(24));
+
+ // Anchored to the executable rather than the current directory, so running the suite from the repository
+ // root does not drop a BenchmarkDotNet.Artifacts folder there. Overridable with --artifacts.
+ WithArtifactsPath(Path.Combine(AppContext.BaseDirectory, "BenchmarkDotNet.Artifacts"));
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Data/DeterministicRandom.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Data/DeterministicRandom.cs
new file mode 100644
index 00000000..37b79f3d
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Data/DeterministicRandom.cs
@@ -0,0 +1,120 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using System.Numerics;
+
+namespace TechnicalAnalysis.Benchmarks.Data;
+
+///
+/// A self-contained, fully deterministic pseudo random number generator (xoshiro256** seeded through SplitMix64).
+///
+///
+///
+/// The BCL does not guarantee that a given seed produces the same sequence across
+/// runtime versions. Benchmarks must be comparable across machines and across .NET releases, so the generator is
+/// implemented here instead of being taken from the BCL. The algorithm is deliberately simple and allocation free.
+///
+///
+/// This type is not thread safe. Each generated series creates its own instance.
+///
+///
+public sealed class DeterministicRandom
+{
+ private ulong _s0;
+ private ulong _s1;
+ private ulong _s2;
+ private ulong _s3;
+ private double _spareGaussian;
+ private bool _hasSpareGaussian;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The seed. The same seed always yields the same sequence.
+ public DeterministicRandom(int seed)
+ {
+ ulong state = unchecked((ulong)seed + 0x9E3779B97F4A7C15UL);
+ _s0 = SplitMix64(ref state);
+ _s1 = SplitMix64(ref state);
+ _s2 = SplitMix64(ref state);
+ _s3 = SplitMix64(ref state);
+ }
+
+ ///
+ /// Returns the next raw 64 bit sample of the generator.
+ ///
+ /// A uniformly distributed unsigned 64 bit integer.
+ public ulong NextUInt64()
+ {
+ unchecked
+ {
+ ulong result = BitOperations.RotateLeft(_s1 * 5UL, 7) * 9UL;
+ ulong t = _s1 << 17;
+
+ _s2 ^= _s0;
+ _s3 ^= _s1;
+ _s1 ^= _s2;
+ _s0 ^= _s3;
+ _s2 ^= t;
+ _s3 = BitOperations.RotateLeft(_s3, 45);
+
+ return result;
+ }
+ }
+
+ ///
+ /// Returns the next uniformly distributed sample in the half open interval [0, 1).
+ ///
+ /// A uniformly distributed double in [0, 1).
+ public double NextDouble()
+ {
+ // 53 significant bits, the exact precision of a double mantissa.
+ return (NextUInt64() >> 11) * (1.0 / 9007199254740992.0);
+ }
+
+ ///
+ /// Returns the next standard normal sample (mean 0, standard deviation 1) using the Marsaglia polar method.
+ ///
+ /// A normally distributed double.
+ public double NextGaussian()
+ {
+ if (_hasSpareGaussian)
+ {
+ _hasSpareGaussian = false;
+ return _spareGaussian;
+ }
+
+ double u;
+ double v;
+ double s;
+
+ do
+ {
+ u = (2.0 * NextDouble()) - 1.0;
+ v = (2.0 * NextDouble()) - 1.0;
+ s = (u * u) + (v * v);
+ }
+ while (s is <= 0.0 or >= 1.0);
+
+ double factor = Math.Sqrt(-2.0 * Math.Log(s) / s);
+ _spareGaussian = v * factor;
+ _hasSpareGaussian = true;
+
+ return u * factor;
+ }
+
+ private static ulong SplitMix64(ref ulong state)
+ {
+ unchecked
+ {
+ state += 0x9E3779B97F4A7C15UL;
+ ulong z = state;
+ z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL;
+ z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL;
+ return z ^ (z >> 31);
+ }
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Data/MarketDataGenerator.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Data/MarketDataGenerator.cs
new file mode 100644
index 00000000..0e6fdcef
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Data/MarketDataGenerator.cs
@@ -0,0 +1,243 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using System.Collections.Concurrent;
+
+namespace TechnicalAnalysis.Benchmarks.Data;
+
+///
+/// Produces deterministic, fully offline OHLCV series for benchmarking.
+///
+///
+///
+/// The close price follows a discretised geometric Brownian motion
+/// C[i] = C[i-1] * exp((mu - sigma^2 / 2) * dt + sigma * sqrt(dt) * Z) with a trading-day time step.
+/// The open gaps away from the previous close, and the high / low extend beyond the candle body by an exponential
+/// wick whose size is itself a random draw, so candlestick pattern recognisers see realistic bodies and shadows
+/// instead of degenerate bars. Volume is log-normal and correlated with the absolute return of the bar.
+///
+///
+/// Everything is driven by with a fixed default seed, so two runs on two machines
+/// see byte-identical inputs. No network access, no market data provider, no files.
+///
+///
+public static class MarketDataGenerator
+{
+ ///
+ /// The default seed. Change it only if every published benchmark number is regenerated at the same time.
+ ///
+ public const int DefaultSeed = 20240217;
+
+ ///
+ /// The price the primary instrument starts from.
+ ///
+ private const double InitialPrice = 100.0;
+
+ ///
+ /// The price the correlated reference instrument starts from.
+ ///
+ private const double ReferenceInitialPrice = 250.0;
+
+ ///
+ /// The annualised drift of the geometric Brownian motion.
+ ///
+ private const double Drift = 0.08;
+
+ ///
+ /// The annualised volatility of the geometric Brownian motion.
+ ///
+ private const double Volatility = 0.25;
+
+ ///
+ /// The number of trading days per year, i.e. the inverse of the time step.
+ ///
+ private const double TradingDaysPerYear = 252.0;
+
+ ///
+ /// The instantaneous correlation between the primary and the reference instrument.
+ ///
+ private const double ReferenceCorrelation = 0.65;
+
+ ///
+ /// The median traded volume of a bar.
+ ///
+ private const double MedianVolume = 1_000_000.0;
+
+ ///
+ /// Number of decimal places every generated price is rounded to, so the decimal projection is lossless.
+ ///
+ private const int PriceDecimals = 4;
+
+ private static readonly ConcurrentDictionary<(int Length, int Seed), MarketSeries> Cache = new();
+
+ ///
+ /// Generates (or returns a cached) market series of the requested length.
+ ///
+ /// The number of bars to generate. Must be at least two.
+ /// The seed. Defaults to .
+ /// A deterministic .
+ /// Thrown when is less than two.
+ ///
+ /// Results are cached per (length, seed) pair. Generation happens in [GlobalSetup], never inside a measured
+ /// method, so caching does not influence any timing; it only keeps repeated setups cheap.
+ ///
+ public static MarketSeries Generate(int length, int seed = DefaultSeed)
+ {
+ ArgumentOutOfRangeException.ThrowIfLessThan(length, 2);
+
+ return Cache.GetOrAdd((length, seed), static key => GenerateCore(key.Length, key.Seed));
+ }
+
+ private static MarketSeries GenerateCore(int length, int seed)
+ {
+ DeterministicRandom random = new(seed);
+
+ double dt = 1.0 / TradingDaysPerYear;
+ double barDrift = (Drift - (0.5 * Volatility * Volatility)) * dt;
+ double barVolatility = Volatility * Math.Sqrt(dt);
+ double crossVolatility = Math.Sqrt(1.0 - (ReferenceCorrelation * ReferenceCorrelation));
+
+ double[] open = new double[length];
+ double[] high = new double[length];
+ double[] low = new double[length];
+ double[] close = new double[length];
+ double[] volume = new double[length];
+
+ double[] refOpen = new double[length];
+ double[] refHigh = new double[length];
+ double[] refLow = new double[length];
+ double[] refClose = new double[length];
+ double[] refVolume = new double[length];
+
+ double price = InitialPrice;
+ double referencePrice = ReferenceInitialPrice;
+
+ for (int i = 0; i < length; i++)
+ {
+ double shock = random.NextGaussian();
+ double referenceShock = (ReferenceCorrelation * shock) + (crossVolatility * random.NextGaussian());
+
+ BuildBar(random, ref price, barDrift, barVolatility, shock, i, open, high, low, close, volume);
+ BuildBar(
+ random,
+ ref referencePrice,
+ barDrift,
+ barVolatility * 0.8,
+ referenceShock,
+ i,
+ refOpen,
+ refHigh,
+ refLow,
+ refClose,
+ refVolume);
+ }
+
+ OhlcvSeries doubles = new(open, high, low, close, volume);
+ OhlcvSeries referenceDoubles = new(refOpen, refHigh, refLow, refClose, refVolume);
+
+ return new MarketSeries(
+ seed,
+ doubles,
+ ToSingle(doubles),
+ ToDecimal(doubles),
+ referenceDoubles,
+ ToSingle(referenceDoubles));
+ }
+
+ private static void BuildBar(
+ DeterministicRandom random,
+ ref double price,
+ double barDrift,
+ double barVolatility,
+ double shock,
+ int index,
+ double[] open,
+ double[] high,
+ double[] low,
+ double[] close,
+ double[] volume)
+ {
+ double previousClose = price;
+
+ // Overnight gap: a fraction of a daily move, so the open is close to but rarely equal to the previous close.
+ double gap = 0.20 * barVolatility * random.NextGaussian();
+ double barOpen = previousClose * Math.Exp(gap);
+ double barClose = previousClose * Math.Exp(barDrift + (barVolatility * shock));
+
+ double bodyHigh = Math.Max(barOpen, barClose);
+ double bodyLow = Math.Min(barOpen, barClose);
+
+ // Wicks are always non-negative and independent of the body direction.
+ double upperWick = Math.Abs(random.NextGaussian()) * barVolatility * 0.6;
+ double lowerWick = Math.Abs(random.NextGaussian()) * barVolatility * 0.6;
+
+ double barHigh = bodyHigh * Math.Exp(upperWick);
+ double barLow = bodyLow * Math.Exp(-lowerWick);
+
+ // Volume is log-normal and grows with the magnitude of the bar's return.
+ double relativeMove = Math.Abs((barClose / previousClose) - 1.0);
+ double barVolume = MedianVolume * Math.Exp(0.35 * random.NextGaussian()) * (1.0 + (8.0 * relativeMove));
+
+ barOpen = Math.Round(barOpen, PriceDecimals, MidpointRounding.AwayFromZero);
+ barClose = Math.Round(barClose, PriceDecimals, MidpointRounding.AwayFromZero);
+ barHigh = Math.Round(barHigh, PriceDecimals, MidpointRounding.AwayFromZero);
+ barLow = Math.Round(barLow, PriceDecimals, MidpointRounding.AwayFromZero);
+
+ // Rounding must never break the OHLC invariants that every candlestick recogniser relies upon.
+ barHigh = Math.Max(barHigh, Math.Max(barOpen, barClose));
+ barLow = Math.Min(barLow, Math.Min(barOpen, barClose));
+
+ open[index] = barOpen;
+ high[index] = barHigh;
+ low[index] = barLow;
+ close[index] = barClose;
+ volume[index] = Math.Round(barVolume / 100.0, MidpointRounding.AwayFromZero) * 100.0;
+
+ price = barClose;
+ }
+
+ private static OhlcvSeries ToSingle(OhlcvSeries source)
+ {
+ return new OhlcvSeries(
+ ToSingle(source.Open),
+ ToSingle(source.High),
+ ToSingle(source.Low),
+ ToSingle(source.Close),
+ ToSingle(source.Volume));
+ }
+
+ private static OhlcvSeries ToDecimal(OhlcvSeries source)
+ {
+ return new OhlcvSeries(
+ ToDecimal(source.Open),
+ ToDecimal(source.High),
+ ToDecimal(source.Low),
+ ToDecimal(source.Close),
+ ToDecimal(source.Volume));
+ }
+
+ private static float[] ToSingle(double[] source)
+ {
+ float[] result = new float[source.Length];
+ for (int i = 0; i < source.Length; i++)
+ {
+ result[i] = (float)source[i];
+ }
+
+ return result;
+ }
+
+ private static decimal[] ToDecimal(double[] source)
+ {
+ decimal[] result = new decimal[source.Length];
+ for (int i = 0; i < source.Length; i++)
+ {
+ result[i] = (decimal)source[i];
+ }
+
+ return result;
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Data/MarketSeries.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Data/MarketSeries.cs
new file mode 100644
index 00000000..998de135
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Data/MarketSeries.cs
@@ -0,0 +1,82 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+namespace TechnicalAnalysis.Benchmarks.Data;
+
+///
+/// One synthetic market data set, exposed in the three numeric precisions the library supports plus a second,
+/// correlated instrument used by the two-input indicators (Correl, Beta).
+///
+///
+///
+/// , and are projections of the exact same
+/// underlying series. The generator rounds every price to four decimal places so that the decimal projection is an
+/// exact representation of the double projection; only the float projection loses information. That makes a
+/// double / float / decimal comparison a pure cost comparison rather than a "different data" comparison.
+///
+///
+public sealed class MarketSeries
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The seed the series was generated from.
+ /// The primary instrument as doubles.
+ /// The primary instrument as floats.
+ /// The primary instrument as decimals.
+ /// The correlated reference instrument as doubles.
+ /// The correlated reference instrument as floats.
+ public MarketSeries(
+ int seed,
+ OhlcvSeries doubles,
+ OhlcvSeries singles,
+ OhlcvSeries decimals,
+ OhlcvSeries referenceDoubles,
+ OhlcvSeries referenceSingles)
+ {
+ Seed = seed;
+ Doubles = doubles;
+ Singles = singles;
+ Decimals = decimals;
+ ReferenceDoubles = referenceDoubles;
+ ReferenceSingles = referenceSingles;
+ }
+
+ ///
+ /// Gets the seed the series was generated from.
+ ///
+ public int Seed { get; }
+
+ ///
+ /// Gets the number of bars in the series.
+ ///
+ public int Length => Doubles.Length;
+
+ ///
+ /// Gets the primary instrument projected onto .
+ ///
+ public OhlcvSeries Doubles { get; }
+
+ ///
+ /// Gets the primary instrument projected onto .
+ ///
+ public OhlcvSeries Singles { get; }
+
+ ///
+ /// Gets the primary instrument projected onto .
+ ///
+ public OhlcvSeries Decimals { get; }
+
+ ///
+ /// Gets a second, correlated instrument projected onto , used by Correl and Beta.
+ ///
+ public OhlcvSeries ReferenceDoubles { get; }
+
+ ///
+ /// Gets a second, correlated instrument projected onto , used by Correl and Beta.
+ ///
+ public OhlcvSeries ReferenceSingles { get; }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Data/OhlcvSeries.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Data/OhlcvSeries.cs
new file mode 100644
index 00000000..9c697e25
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Data/OhlcvSeries.cs
@@ -0,0 +1,65 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+namespace TechnicalAnalysis.Benchmarks.Data;
+
+///
+/// An open / high / low / close / volume series projected onto a single numeric type.
+///
+/// The numeric type the series is projected onto (double, float or decimal).
+///
+/// All five arrays always have the same length. Instances are produced by and are
+/// treated as immutable by the benchmarks: an indicator must never write into its input.
+///
+public sealed class OhlcvSeries
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The open prices.
+ /// The high prices.
+ /// The low prices.
+ /// The close prices.
+ /// The traded volumes.
+ public OhlcvSeries(T[] open, T[] high, T[] low, T[] close, T[] volume)
+ {
+ Open = open;
+ High = high;
+ Low = low;
+ Close = close;
+ Volume = volume;
+ }
+
+ ///
+ /// Gets the open prices.
+ ///
+ public T[] Open { get; }
+
+ ///
+ /// Gets the high prices.
+ ///
+ public T[] High { get; }
+
+ ///
+ /// Gets the low prices.
+ ///
+ public T[] Low { get; }
+
+ ///
+ /// Gets the close prices.
+ ///
+ public T[] Close { get; }
+
+ ///
+ /// Gets the traded volumes.
+ ///
+ public T[] Volume { get; }
+
+ ///
+ /// Gets the number of bars in the series.
+ ///
+ public int Length => Close.Length;
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Diagnostics/BenchmarkSelfCheck.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Diagnostics/BenchmarkSelfCheck.cs
new file mode 100644
index 00000000..89087e0e
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Diagnostics/BenchmarkSelfCheck.cs
@@ -0,0 +1,186 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using System.Globalization;
+using System.Reflection;
+using BenchmarkDotNet.Attributes;
+using TechnicalAnalysis.Common;
+
+namespace TechnicalAnalysis.Benchmarks.Diagnostics;
+
+///
+/// Runs every benchmark method exactly once, outside BenchmarkDotNet, and checks that it actually succeeds.
+///
+///
+///
+/// A benchmark that silently measures a validation failure looks fast and means nothing. This self check invokes
+/// each [Benchmark] method once at the smallest configured series length and inspects the returned value:
+/// a , an or a native return code must all report success, and
+/// any thrown exception is reported.
+///
+///
+/// Invoke it with -- --selfcheck. It is a correctness gate, never a measurement: no timing is produced or
+/// implied.
+///
+///
+public static class BenchmarkSelfCheck
+{
+ ///
+ /// The command line flag that triggers the self check.
+ ///
+ public const string Flag = "--selfcheck";
+
+ ///
+ /// The series length used by the self check. The smallest configured [Params] value keeps it instant.
+ ///
+ private const int SelfCheckLength = 1_000;
+
+ ///
+ /// Runs the self check over the supplied benchmark types.
+ ///
+ /// The benchmark classes to check.
+ /// Zero when every benchmark succeeded, one otherwise.
+ public static int Run(IReadOnlyList benchmarkTypes)
+ {
+ ArgumentNullException.ThrowIfNull(benchmarkTypes);
+
+ int checkedCount = 0;
+ List failures = [];
+
+ Console.WriteLine(string.Format(
+ CultureInfo.InvariantCulture,
+ "Self check: invoking every benchmark once with Length = {0}.",
+ SelfCheckLength));
+ Console.WriteLine();
+
+ foreach (Type type in benchmarkTypes)
+ {
+ int typeFailures = failures.Count;
+ CheckType(type, ref checkedCount, failures);
+
+ Console.WriteLine(string.Format(
+ CultureInfo.InvariantCulture,
+ " {0,-32} {1}",
+ type.Name,
+ failures.Count == typeFailures ? "OK" : "FAILED"));
+ }
+
+ Console.WriteLine();
+
+ if (failures.Count == 0)
+ {
+ Console.WriteLine(string.Format(
+ CultureInfo.InvariantCulture,
+ "Self check passed: {0} benchmark method(s) all reported success.",
+ checkedCount));
+ return 0;
+ }
+
+ Console.WriteLine(string.Format(
+ CultureInfo.InvariantCulture,
+ "Self check FAILED: {0} of {1} benchmark method(s) did not report success.",
+ failures.Count,
+ checkedCount));
+
+ foreach (string failure in failures)
+ {
+ Console.WriteLine(" " + failure);
+ }
+
+ return 1;
+ }
+
+ private static void CheckType(Type type, ref int checkedCount, List failures)
+ {
+ object? instance;
+
+ try
+ {
+ instance = Activator.CreateInstance(type);
+ }
+ catch (Exception ex)
+ {
+ failures.Add($"{type.Name}: could not be instantiated: {Describe(ex)}");
+ return;
+ }
+
+ if (instance is null)
+ {
+ failures.Add($"{type.Name}: could not be instantiated.");
+ return;
+ }
+
+ PropertyInfo? lengthProperty = type.GetProperty("Length", BindingFlags.Public | BindingFlags.Instance);
+ lengthProperty?.SetValue(instance, SelfCheckLength);
+
+ MethodInfo? setup = type
+ .GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .FirstOrDefault(static method => method.GetCustomAttribute() is not null);
+
+ try
+ {
+ setup?.Invoke(instance, null);
+ }
+ catch (Exception ex)
+ {
+ failures.Add($"{type.Name}: [GlobalSetup] threw {Describe(ex)}");
+ return;
+ }
+
+ foreach (MethodInfo method in type
+ .GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(static method => method.GetCustomAttribute() is not null)
+ .Where(static method => method.GetParameters().Length == 0))
+ {
+ checkedCount++;
+
+ try
+ {
+ object? result = method.Invoke(instance, null);
+ string? problem = Validate(result);
+
+ if (problem is not null)
+ {
+ failures.Add($"{type.Name}.{method.Name}: {problem}");
+ }
+ }
+ catch (Exception ex)
+ {
+ failures.Add($"{type.Name}.{method.Name}: threw {Describe(ex)}");
+ }
+ }
+ }
+
+ private static string? Validate(object? result)
+ {
+ return result switch
+ {
+ null => "returned null",
+ RetCode code when code != RetCode.Success => $"returned RetCode.{code}",
+ RetCode => null,
+ IndicatorResult indicator when indicator.RetCode != RetCode.Success =>
+ $"result carries RetCode.{indicator.RetCode}",
+ IndicatorResult indicator when indicator.NBElement <= 0 =>
+ $"result carries NBElement = {indicator.NBElement.ToString(CultureInfo.InvariantCulture)}",
+ IndicatorResult => null,
+ int nativeCode when nativeCode != 0 =>
+ $"native call returned TA_RetCode {nativeCode.ToString(CultureInfo.InvariantCulture)}",
+ _ => null
+ };
+ }
+
+ private static string Describe(Exception exception)
+ {
+ Exception effective = exception;
+
+ if (exception is TargetInvocationException { InnerException: { } inner })
+ {
+ effective = inner;
+ }
+
+ return $"{effective.GetType().Name}: {effective.Message}";
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Interop/NativeEquivalence.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Interop/NativeEquivalence.cs
new file mode 100644
index 00000000..2ebc25ca
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Interop/NativeEquivalence.cs
@@ -0,0 +1,126 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using System.Globalization;
+
+namespace TechnicalAnalysis.Benchmarks.Interop;
+
+///
+/// Verifies that the managed TaLibStandard implementation and the native TA-Lib C implementation compute the same
+/// thing before either of them is timed.
+///
+///
+///
+/// A benchmark that is "faster" because it computes the wrong answer is worse than no benchmark at all. Every
+/// head-to-head pair in NativeComparisonBenchmarks is therefore validated in [GlobalSetup]: the
+/// alignment metadata (outBegIdx and outNBElement) must match exactly and every produced value must
+/// agree within tolerance. A mismatch throws, which BenchmarkDotNet surfaces as a failed benchmark.
+///
+///
+/// Both APIs use the same output convention: the buffer is filled from index 0, and output element k
+/// corresponds to input index outBegIdx + k for k in [0, outNBElement). Elements at or beyond
+/// outNBElement are meaningless and are never compared.
+///
+///
+public static class NativeEquivalence
+{
+ ///
+ /// The default relative tolerance. TA-Lib C and TaLibStandard run the same recurrences in the same order, so
+ /// results normally agree to the last few bits; this leaves room for compiler-level reassociation only.
+ ///
+ public const double DefaultTolerance = 1e-9;
+
+ ///
+ /// Asserts that two implementations produced the same aligned output series.
+ ///
+ /// The indicator name, used in the failure message.
+ /// The managed outBegIdx.
+ /// The managed outNBElement.
+ /// The managed output buffer, filled from index zero.
+ /// The native outBegIdx.
+ /// The native outNBElement.
+ /// The native output buffer, filled from index zero.
+ /// The relative tolerance. Defaults to .
+ /// Thrown when the two implementations disagree.
+ public static void AssertEquivalent(
+ string indicator,
+ int managedBegIdx,
+ int managedCount,
+ double[] managedValues,
+ int nativeBegIdx,
+ int nativeCount,
+ double[] nativeValues,
+ double tolerance = DefaultTolerance)
+ {
+ ArgumentNullException.ThrowIfNull(managedValues);
+ ArgumentNullException.ThrowIfNull(nativeValues);
+
+ if (managedBegIdx != nativeBegIdx)
+ {
+ throw new InvalidOperationException(string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}: output alignment differs. Managed outBegIdx = {1}, native outBegIdx = {2}. " +
+ "Comparing the timings would be meaningless.",
+ indicator,
+ managedBegIdx,
+ nativeBegIdx));
+ }
+
+ if (managedCount != nativeCount)
+ {
+ throw new InvalidOperationException(string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}: output length differs. Managed outNBElement = {1}, native outNBElement = {2}.",
+ indicator,
+ managedCount,
+ nativeCount));
+ }
+
+ for (int i = 0; i < managedCount; i++)
+ {
+ double managed = managedValues[i];
+ double @native = nativeValues[i];
+
+ if (AreClose(managed, @native, tolerance))
+ {
+ continue;
+ }
+
+ throw new InvalidOperationException(string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}: values diverge at output index {1} (input index {2}). Managed = {3:R}, native = {4:R}, " +
+ "absolute difference = {5:R}, tolerance = {6:R}.",
+ indicator,
+ i,
+ managedBegIdx + i,
+ managed,
+ @native,
+ Math.Abs(managed - @native),
+ tolerance));
+ }
+ }
+
+ private static bool AreClose(double left, double right, double tolerance)
+ {
+ if (double.IsNaN(left) && double.IsNaN(right))
+ {
+ return true;
+ }
+
+ if (double.IsNaN(left) || double.IsNaN(right))
+ {
+ return false;
+ }
+
+ if (left.Equals(right))
+ {
+ return true;
+ }
+
+ double scale = Math.Max(1.0, Math.Max(Math.Abs(left), Math.Abs(right)));
+ return Math.Abs(left - right) <= tolerance * scale;
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Interop/NativeTaLib.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Interop/NativeTaLib.cs
new file mode 100644
index 00000000..6cdf0725
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Interop/NativeTaLib.cs
@@ -0,0 +1,772 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using System.Globalization;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace TechnicalAnalysis.Benchmarks.Interop;
+
+///
+/// An opt-in P/Invoke bridge to the original TA-Lib C library.
+///
+///
+///
+/// The bridge is entirely optional. probes for the native library once, never throws and
+/// returns when nothing suitable is found; the rest of the benchmark suite then runs
+/// unchanged with zero native dependencies.
+///
+///
+/// Only functions whose C signature is certain are bound here. Every one of them is declared in
+/// ta_func.h of the upstream TA-Lib distribution with the same shape: leading startIdx /
+/// endIdx, then the const double[] inputs, then the optIn* parameters, then
+/// int *outBegIdx and int *outNBElement, then the double[] output buffers. The return type is
+/// the C enum TA_RetCode, which is marshalled as (TA_SUCCESS is 0). The calling
+/// convention is cdecl on every supported platform.
+///
+///
+/// The TA_MAType C enum uses the same ordinal order as TechnicalAnalysis.Common.MAType
+/// (SMA, EMA, WMA, DEMA, TEMA, TRIMA, KAMA, MAMA, T3), so a plain cast to is correct.
+///
+///
+public static class NativeTaLib
+{
+ ///
+ /// The value of TA_SUCCESS in the C TA_RetCode enum.
+ ///
+ public const int Success = 0;
+
+ ///
+ /// The logical name used by every [DllImport] below. It is never resolved by the default loader: the
+ /// registered always answers with the handle discovered by .
+ ///
+ private const string LogicalLibraryName = "talib-native";
+
+ ///
+ /// Environment variable that overrides discovery with an explicit file name or absolute path.
+ ///
+ private const string OverrideEnvironmentVariable = "TALIB_NATIVE_LIBRARY";
+
+ ///
+ /// Library names handed to the platform loader, which applies the usual lib prefix and
+ /// .so / .dylib / .dll suffix conventions itself.
+ ///
+ private static readonly string[] CandidateNames =
+ [
+ "ta-lib",
+ "ta_lib",
+ "libta-lib",
+ "libta_lib",
+ "ta_libc",
+ "ta_libc_cdr",
+ "libta-lib.so.0",
+ "libta_lib.so.0"
+ ];
+
+ ///
+ /// Absolute paths tried after the plain names, covering the default Homebrew, MacPorts and autotools prefixes.
+ ///
+ private static readonly string[] CandidatePaths =
+ [
+ "/opt/homebrew/lib/libta-lib.dylib",
+ "/opt/homebrew/lib/libta_lib.dylib",
+ "/usr/local/lib/libta-lib.dylib",
+ "/usr/local/lib/libta_lib.dylib",
+ "/opt/local/lib/libta-lib.dylib",
+ "/usr/local/lib/libta-lib.so",
+ "/usr/local/lib/libta_lib.so",
+ "/usr/lib/libta-lib.so",
+ "/usr/lib/libta_lib.so",
+ "/usr/lib/x86_64-linux-gnu/libta-lib.so",
+ "/usr/lib/aarch64-linux-gnu/libta-lib.so"
+ ];
+
+ private static readonly Lock SyncRoot = new();
+
+ private static bool _probed;
+ private static bool _available;
+ private static IntPtr _handle;
+ private static string _resolvedName = "";
+ private static string _diagnostics = "not probed yet";
+
+ static NativeTaLib()
+ {
+ // Registering the resolver here (rather than lazily) guarantees it is in place before the CLR resolves any
+ // of the [DllImport] entries below, because touching any static member runs this constructor first.
+ try
+ {
+ NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), ResolveLibrary);
+ }
+ catch (InvalidOperationException)
+ {
+ // A resolver was already registered for this assembly. Harmless: discovery still works through it or
+ // through the default loader, and a failure to bind simply leaves IsAvailable false.
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the native TA-Lib C library was found, loaded and successfully initialised.
+ ///
+ ///
+ /// This property never throws. The first access performs the probe; subsequent accesses are a field read.
+ ///
+ public static bool IsAvailable
+ {
+ get
+ {
+ Probe();
+ return _available;
+ }
+ }
+
+ ///
+ /// Gets the name or path the native library was resolved from, or <none> when it was not found.
+ ///
+ public static string ResolvedName
+ {
+ get
+ {
+ Probe();
+ return _resolvedName;
+ }
+ }
+
+ ///
+ /// Gets a human readable description of what the probe tried and what happened, for the startup banner.
+ ///
+ public static string Diagnostics
+ {
+ get
+ {
+ Probe();
+ return _diagnostics;
+ }
+ }
+
+ ///
+ /// Calls TA_SMA.
+ ///
+ /// The first index of the input to process.
+ /// The last index of the input to process.
+ /// The input series.
+ /// The averaging period.
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated output buffer.
+ /// The C TA_RetCode; means success.
+ public static unsafe int Sma(
+ int startIdx,
+ int endIdx,
+ double[] inReal,
+ int optInTimePeriod,
+ out int outBegIdx,
+ out int outNbElement,
+ double[] outReal)
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ int retCode;
+
+ fixed (double* pIn = inReal)
+ fixed (double* pOut = outReal)
+ {
+ retCode = TA_SMA(startIdx, endIdx, pIn, optInTimePeriod, &begIdx, &nbElement, pOut);
+ }
+
+ outBegIdx = begIdx;
+ outNbElement = nbElement;
+ return retCode;
+ }
+
+ ///
+ /// Calls TA_EMA.
+ ///
+ /// The first index of the input to process.
+ /// The last index of the input to process.
+ /// The input series.
+ /// The averaging period.
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated output buffer.
+ /// The C TA_RetCode; means success.
+ public static unsafe int Ema(
+ int startIdx,
+ int endIdx,
+ double[] inReal,
+ int optInTimePeriod,
+ out int outBegIdx,
+ out int outNbElement,
+ double[] outReal)
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ int retCode;
+
+ fixed (double* pIn = inReal)
+ fixed (double* pOut = outReal)
+ {
+ retCode = TA_EMA(startIdx, endIdx, pIn, optInTimePeriod, &begIdx, &nbElement, pOut);
+ }
+
+ outBegIdx = begIdx;
+ outNbElement = nbElement;
+ return retCode;
+ }
+
+ ///
+ /// Calls TA_RSI.
+ ///
+ /// The first index of the input to process.
+ /// The last index of the input to process.
+ /// The input series.
+ /// The RSI period.
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated output buffer.
+ /// The C TA_RetCode; means success.
+ public static unsafe int Rsi(
+ int startIdx,
+ int endIdx,
+ double[] inReal,
+ int optInTimePeriod,
+ out int outBegIdx,
+ out int outNbElement,
+ double[] outReal)
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ int retCode;
+
+ fixed (double* pIn = inReal)
+ fixed (double* pOut = outReal)
+ {
+ retCode = TA_RSI(startIdx, endIdx, pIn, optInTimePeriod, &begIdx, &nbElement, pOut);
+ }
+
+ outBegIdx = begIdx;
+ outNbElement = nbElement;
+ return retCode;
+ }
+
+ ///
+ /// Calls TA_MACD.
+ ///
+ /// The first index of the input to process.
+ /// The last index of the input to process.
+ /// The input series.
+ /// The fast EMA period.
+ /// The slow EMA period.
+ /// The signal EMA period.
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated MACD line buffer.
+ /// The caller-allocated signal line buffer.
+ /// The caller-allocated histogram buffer.
+ /// The C TA_RetCode; means success.
+ public static unsafe int Macd(
+ int startIdx,
+ int endIdx,
+ double[] inReal,
+ int optInFastPeriod,
+ int optInSlowPeriod,
+ int optInSignalPeriod,
+ out int outBegIdx,
+ out int outNbElement,
+ double[] outMacd,
+ double[] outMacdSignal,
+ double[] outMacdHist)
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ int retCode;
+
+ fixed (double* pIn = inReal)
+ fixed (double* pMacd = outMacd)
+ fixed (double* pSignal = outMacdSignal)
+ fixed (double* pHist = outMacdHist)
+ {
+ retCode = TA_MACD(
+ startIdx,
+ endIdx,
+ pIn,
+ optInFastPeriod,
+ optInSlowPeriod,
+ optInSignalPeriod,
+ &begIdx,
+ &nbElement,
+ pMacd,
+ pSignal,
+ pHist);
+ }
+
+ outBegIdx = begIdx;
+ outNbElement = nbElement;
+ return retCode;
+ }
+
+ ///
+ /// Calls TA_BBANDS.
+ ///
+ /// The first index of the input to process.
+ /// The last index of the input to process.
+ /// The input series.
+ /// The averaging period.
+ /// The number of standard deviations for the upper band.
+ /// The number of standard deviations for the lower band.
+ /// The moving average type, ordinal-compatible with MAType.
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated upper band buffer.
+ /// The caller-allocated middle band buffer.
+ /// The caller-allocated lower band buffer.
+ /// The C TA_RetCode; means success.
+ public static unsafe int Bbands(
+ int startIdx,
+ int endIdx,
+ double[] inReal,
+ int optInTimePeriod,
+ double optInNbDevUp,
+ double optInNbDevDn,
+ int optInMaType,
+ out int outBegIdx,
+ out int outNbElement,
+ double[] outUpper,
+ double[] outMiddle,
+ double[] outLower)
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ int retCode;
+
+ fixed (double* pIn = inReal)
+ fixed (double* pUpper = outUpper)
+ fixed (double* pMiddle = outMiddle)
+ fixed (double* pLower = outLower)
+ {
+ retCode = TA_BBANDS(
+ startIdx,
+ endIdx,
+ pIn,
+ optInTimePeriod,
+ optInNbDevUp,
+ optInNbDevDn,
+ optInMaType,
+ &begIdx,
+ &nbElement,
+ pUpper,
+ pMiddle,
+ pLower);
+ }
+
+ outBegIdx = begIdx;
+ outNbElement = nbElement;
+ return retCode;
+ }
+
+ ///
+ /// Calls TA_ATR.
+ ///
+ /// The first index of the input to process.
+ /// The last index of the input to process.
+ /// The high price series.
+ /// The low price series.
+ /// The close price series.
+ /// The averaging period.
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated output buffer.
+ /// The C TA_RetCode; means success.
+ public static unsafe int Atr(
+ int startIdx,
+ int endIdx,
+ double[] inHigh,
+ double[] inLow,
+ double[] inClose,
+ int optInTimePeriod,
+ out int outBegIdx,
+ out int outNbElement,
+ double[] outReal)
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ int retCode;
+
+ fixed (double* pHigh = inHigh)
+ fixed (double* pLow = inLow)
+ fixed (double* pClose = inClose)
+ fixed (double* pOut = outReal)
+ {
+ retCode = TA_ATR(startIdx, endIdx, pHigh, pLow, pClose, optInTimePeriod, &begIdx, &nbElement, pOut);
+ }
+
+ outBegIdx = begIdx;
+ outNbElement = nbElement;
+ return retCode;
+ }
+
+ ///
+ /// Calls TA_ADX.
+ ///
+ /// The first index of the input to process.
+ /// The last index of the input to process.
+ /// The high price series.
+ /// The low price series.
+ /// The close price series.
+ /// The averaging period.
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated output buffer.
+ /// The C TA_RetCode; means success.
+ public static unsafe int Adx(
+ int startIdx,
+ int endIdx,
+ double[] inHigh,
+ double[] inLow,
+ double[] inClose,
+ int optInTimePeriod,
+ out int outBegIdx,
+ out int outNbElement,
+ double[] outReal)
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ int retCode;
+
+ fixed (double* pHigh = inHigh)
+ fixed (double* pLow = inLow)
+ fixed (double* pClose = inClose)
+ fixed (double* pOut = outReal)
+ {
+ retCode = TA_ADX(startIdx, endIdx, pHigh, pLow, pClose, optInTimePeriod, &begIdx, &nbElement, pOut);
+ }
+
+ outBegIdx = begIdx;
+ outNbElement = nbElement;
+ return retCode;
+ }
+
+ ///
+ /// Calls TA_STOCH.
+ ///
+ /// The first index of the input to process.
+ /// The last index of the input to process.
+ /// The high price series.
+ /// The low price series.
+ /// The close price series.
+ /// The fast %K period.
+ /// The slow %K smoothing period.
+ /// The slow %K moving average type.
+ /// The slow %D smoothing period.
+ /// The slow %D moving average type.
+ /// Receives the input index the first output element corresponds to.
+ /// Receives the number of valid output elements.
+ /// The caller-allocated slow %K buffer.
+ /// The caller-allocated slow %D buffer.
+ /// The C TA_RetCode; means success.
+ public static unsafe int Stoch(
+ int startIdx,
+ int endIdx,
+ double[] inHigh,
+ double[] inLow,
+ double[] inClose,
+ int optInFastKPeriod,
+ int optInSlowKPeriod,
+ int optInSlowKMaType,
+ int optInSlowDPeriod,
+ int optInSlowDMaType,
+ out int outBegIdx,
+ out int outNbElement,
+ double[] outSlowK,
+ double[] outSlowD)
+ {
+ int begIdx = 0;
+ int nbElement = 0;
+ int retCode;
+
+ fixed (double* pHigh = inHigh)
+ fixed (double* pLow = inLow)
+ fixed (double* pClose = inClose)
+ fixed (double* pSlowK = outSlowK)
+ fixed (double* pSlowD = outSlowD)
+ {
+ retCode = TA_STOCH(
+ startIdx,
+ endIdx,
+ pHigh,
+ pLow,
+ pClose,
+ optInFastKPeriod,
+ optInSlowKPeriod,
+ optInSlowKMaType,
+ optInSlowDPeriod,
+ optInSlowDMaType,
+ &begIdx,
+ &nbElement,
+ pSlowK,
+ pSlowD);
+ }
+
+ outBegIdx = begIdx;
+ outNbElement = nbElement;
+ return retCode;
+ }
+
+ private static IntPtr ResolveLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
+ {
+ return string.Equals(libraryName, LogicalLibraryName, StringComparison.Ordinal) ? _handle : IntPtr.Zero;
+ }
+
+ private static void Probe()
+ {
+ if (Volatile.Read(ref _probed))
+ {
+ return;
+ }
+
+ lock (SyncRoot)
+ {
+ if (_probed)
+ {
+ return;
+ }
+
+ StringBuilder log = new();
+
+ try
+ {
+ ProbeCore(log);
+ }
+#pragma warning disable CA1031 // Discovery must never propagate: the whole point is that the suite degrades gracefully.
+ catch (Exception ex)
+#pragma warning restore CA1031
+ {
+ _available = false;
+ _handle = IntPtr.Zero;
+ log.Append(CultureInfo.InvariantCulture, $"unexpected failure: {ex.GetType().Name}: {ex.Message}");
+ }
+
+ _diagnostics = log.ToString();
+ Volatile.Write(ref _probed, true);
+ }
+ }
+
+ private static void ProbeCore(StringBuilder log)
+ {
+ string? overridden = Environment.GetEnvironmentVariable(OverrideEnvironmentVariable);
+ List attempted = [];
+
+ if (!string.IsNullOrWhiteSpace(overridden))
+ {
+ attempted.Add(overridden);
+ }
+
+ attempted.AddRange(CandidateNames);
+ attempted.AddRange(CandidatePaths);
+
+ foreach (string candidate in attempted)
+ {
+ if (!TryLoad(candidate, out IntPtr handle))
+ {
+ continue;
+ }
+
+ // A library that loads but has no TA_Initialize export is not TA-Lib.
+ if (!NativeLibrary.TryGetExport(handle, "TA_Initialize", out IntPtr initialize))
+ {
+ NativeLibrary.Free(handle);
+ log.Append(CultureInfo.InvariantCulture, $"'{candidate}' loaded but exports no TA_Initialize; ignored. ");
+ continue;
+ }
+
+ // Called through the export pointer of *this* candidate, never through the [DllImport] stub.
+ // Invoking the stub would make the CLR run the resolver once and cache the resolved module and
+ // function pointer for the lifetime of the process; the next candidate would then jump to that
+ // cached address, which by then points into a library this loop has already freed. Calling the
+ // pointer directly keeps each candidate self-contained, so a library that loads but fails
+ // TA_Initialize costs an ignored candidate rather than an access violation.
+ int retCode = InvokeInitialize(initialize);
+ if (retCode != Success)
+ {
+ NativeLibrary.Free(handle);
+ log.Append(CultureInfo.InvariantCulture, $"'{candidate}': TA_Initialize returned {retCode}; ignored. ");
+ continue;
+ }
+
+ // Published only now that the candidate is known good, so the resolver can never hand a
+ // [DllImport] a handle that is about to be freed.
+ _handle = handle;
+ _resolvedName = candidate;
+
+ AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
+ _available = true;
+ log.Append(CultureInfo.InvariantCulture, $"resolved '{candidate}', TA_Initialize succeeded.");
+ return;
+ }
+
+ _available = false;
+ _handle = IntPtr.Zero;
+ log.Append(CultureInfo.InvariantCulture, $"probed {attempted.Count} candidate name(s)/path(s), none loaded.");
+ }
+
+ ///
+ /// Calls a resolved TA_Initialize export through its address, bypassing the P/Invoke stub and the
+ /// per-process caching that comes with it.
+ ///
+ /// The address of TA_Initialize in the candidate library.
+ /// The C TA_RetCode.
+ private static unsafe int InvokeInitialize(IntPtr entryPoint)
+ {
+ return ((delegate* unmanaged[Cdecl])entryPoint)();
+ }
+
+ private static bool TryLoad(string candidate, out IntPtr handle)
+ {
+ handle = IntPtr.Zero;
+
+ try
+ {
+ if (Path.IsPathRooted(candidate))
+ {
+ return File.Exists(candidate) && NativeLibrary.TryLoad(candidate, out handle);
+ }
+
+ return NativeLibrary.TryLoad(
+ candidate,
+ Assembly.GetExecutingAssembly(),
+ DllImportSearchPath.SafeDirectories | DllImportSearchPath.UserDirectories,
+ out handle);
+ }
+#pragma warning disable CA1031 // A malformed candidate must not abort the probe.
+ catch (Exception)
+#pragma warning restore CA1031
+ {
+ handle = IntPtr.Zero;
+ return false;
+ }
+ }
+
+ private static void OnProcessExit(object? sender, EventArgs e)
+ {
+ try
+ {
+ if (_available)
+ {
+ _available = false;
+ _ = TA_Shutdown();
+ }
+ }
+#pragma warning disable CA1031 // Nothing useful can be done at process exit.
+ catch (Exception)
+#pragma warning restore CA1031
+ {
+ // Ignored.
+ }
+ }
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_Shutdown", CallingConvention = CallingConvention.Cdecl)]
+ private static extern int TA_Shutdown();
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_SMA", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe int TA_SMA(
+ int startIdx,
+ int endIdx,
+ double* inReal,
+ int optInTimePeriod,
+ int* outBegIdx,
+ int* outNBElement,
+ double* outReal);
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_EMA", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe int TA_EMA(
+ int startIdx,
+ int endIdx,
+ double* inReal,
+ int optInTimePeriod,
+ int* outBegIdx,
+ int* outNBElement,
+ double* outReal);
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_RSI", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe int TA_RSI(
+ int startIdx,
+ int endIdx,
+ double* inReal,
+ int optInTimePeriod,
+ int* outBegIdx,
+ int* outNBElement,
+ double* outReal);
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_MACD", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe int TA_MACD(
+ int startIdx,
+ int endIdx,
+ double* inReal,
+ int optInFastPeriod,
+ int optInSlowPeriod,
+ int optInSignalPeriod,
+ int* outBegIdx,
+ int* outNBElement,
+ double* outMACD,
+ double* outMACDSignal,
+ double* outMACDHist);
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_BBANDS", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe int TA_BBANDS(
+ int startIdx,
+ int endIdx,
+ double* inReal,
+ int optInTimePeriod,
+ double optInNbDevUp,
+ double optInNbDevDn,
+ int optInMAType,
+ int* outBegIdx,
+ int* outNBElement,
+ double* outRealUpperBand,
+ double* outRealMiddleBand,
+ double* outRealLowerBand);
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_ATR", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe int TA_ATR(
+ int startIdx,
+ int endIdx,
+ double* inHigh,
+ double* inLow,
+ double* inClose,
+ int optInTimePeriod,
+ int* outBegIdx,
+ int* outNBElement,
+ double* outReal);
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_ADX", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe int TA_ADX(
+ int startIdx,
+ int endIdx,
+ double* inHigh,
+ double* inLow,
+ double* inClose,
+ int optInTimePeriod,
+ int* outBegIdx,
+ int* outNBElement,
+ double* outReal);
+
+ [DllImport(LogicalLibraryName, EntryPoint = "TA_STOCH", CallingConvention = CallingConvention.Cdecl)]
+ private static extern unsafe int TA_STOCH(
+ int startIdx,
+ int endIdx,
+ double* inHigh,
+ double* inLow,
+ double* inClose,
+ int optInFastK_Period,
+ int optInSlowK_Period,
+ int optInSlowK_MAType,
+ int optInSlowD_Period,
+ int optInSlowD_MAType,
+ int* outBegIdx,
+ int* outNBElement,
+ double* outSlowK,
+ double* outSlowD);
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/Program.cs b/benchmarks/TechnicalAnalysis.Benchmarks/Program.cs
new file mode 100644
index 00000000..ffd95b6b
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/Program.cs
@@ -0,0 +1,144 @@
+// Copyright (c) 2023 Philippe Matray. All rights reserved.
+// This file is part of TaLibStandard.
+// TaLibStandard is licensed under the GNU General Public License v3.0.
+// See the LICENSE file in the project root for the full license text.
+// For more information, visit https://github.com/phmatray/TaLibStandard.
+
+using System.Globalization;
+using System.Reflection;
+using System.Runtime;
+using System.Runtime.InteropServices;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Running;
+using TechnicalAnalysis.Benchmarks.Benchmarks;
+using TechnicalAnalysis.Benchmarks.Configuration;
+using TechnicalAnalysis.Benchmarks.Diagnostics;
+using TechnicalAnalysis.Benchmarks.Interop;
+
+namespace TechnicalAnalysis.Benchmarks;
+
+///
+/// Entry point of the TaLibStandard performance benchmark suite.
+///
+///
+///
+/// All standard BenchmarkDotNet command line arguments are passed straight through to
+/// , so --list flat, --filter, --anyCategories, --job,
+/// --exporters and friends all work as documented upstream.
+///
+///
+/// One project-specific flag is recognised before the switcher runs: --selfcheck invokes every benchmark
+/// once and asserts it reports success, without producing any timing.
+///
+///
+public static class Program
+{
+ ///
+ /// Runs the suite.
+ ///
+ /// The BenchmarkDotNet command line arguments.
+ /// Zero on success.
+ public static int Main(string[] args)
+ {
+ args ??= [];
+
+ bool nativeAvailable = NativeTaLib.IsAvailable;
+ PrintBanner(nativeAvailable);
+
+ Type[] runnableTypes = GetRunnableBenchmarkTypes(nativeAvailable);
+ if (runnableTypes.Length == 0)
+ {
+ Console.WriteLine("No benchmark types were discovered. Nothing to do.");
+ return 0;
+ }
+
+ if (args.Contains(BenchmarkSelfCheck.Flag, StringComparer.OrdinalIgnoreCase))
+ {
+ return BenchmarkSelfCheck.Run(runnableTypes);
+ }
+
+ IConfig config = new TaLibBenchmarkConfig();
+ BenchmarkSwitcher.FromTypes(runnableTypes).Run(args, config);
+
+ return 0;
+ }
+
+ ///
+ /// Returns every benchmark class that should be offered to .
+ ///
+ /// Whether the native TA-Lib C library was found.
+ /// The runnable benchmark types, in a stable alphabetical order.
+ ///
+ /// is excluded when the native library is missing, so that
+ /// --list, --filter * and an unattended full run never attempt something that cannot work.
+ ///
+ private static Type[] GetRunnableBenchmarkTypes(bool nativeAvailable)
+ {
+ IEnumerable candidates = Assembly.GetExecutingAssembly()
+ .GetExportedTypes()
+ .Where(static type => type is { IsClass: true, IsAbstract: false, IsGenericTypeDefinition: false })
+ .Where(static type => type
+ .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
+ .Any(static method => method.GetCustomAttribute() is not null));
+
+ if (!nativeAvailable)
+ {
+ candidates = candidates.Where(static type => type != typeof(NativeComparisonBenchmarks));
+ }
+
+ return [.. candidates.OrderBy(static type => type.Name, StringComparer.Ordinal)];
+ }
+
+ ///
+ /// The horizontal rule drawn around the startup banner.
+ ///
+ private const string Rule = "================================================================================";
+
+ private static void PrintBanner(bool nativeAvailable)
+ {
+ Console.WriteLine(Rule);
+ Console.WriteLine(" TaLibStandard performance benchmarks");
+ Console.WriteLine(Rule);
+ Console.WriteLine(string.Format(
+ CultureInfo.InvariantCulture,
+ " Runtime : {0}",
+ RuntimeInformation.FrameworkDescription));
+ Console.WriteLine(string.Format(
+ CultureInfo.InvariantCulture,
+ " OS / arch : {0} / {1}",
+ RuntimeInformation.OSDescription.Trim(),
+ RuntimeInformation.OSArchitecture));
+ Console.WriteLine(string.Format(
+ CultureInfo.InvariantCulture,
+ " Server GC : {0} GC latency mode: {1} Logical cores: {2}",
+ GCSettings.IsServerGC,
+ GCSettings.LatencyMode,
+ Environment.ProcessorCount));
+ Console.WriteLine(Rule);
+
+ if (nativeAvailable)
+ {
+ Console.WriteLine(string.Format(
+ CultureInfo.InvariantCulture,
+ " NATIVE TA-LIB: AVAILABLE (resolved from '{0}')",
+ NativeTaLib.ResolvedName));
+ Console.WriteLine(" NativeComparisonBenchmarks is included. Managed and native outputs are checked for");
+ Console.WriteLine(" equivalence in [GlobalSetup] before anything is timed.");
+ }
+ else
+ {
+ Console.WriteLine(" NATIVE TA-LIB: NOT AVAILABLE - the managed-versus-C comparison is DISABLED.");
+ Console.WriteLine(string.Format(CultureInfo.InvariantCulture, " Probe result : {0}", NativeTaLib.Diagnostics));
+ Console.WriteLine(" Everything else runs normally; the suite has no native dependency by design.");
+ Console.WriteLine(" To enable the comparison, install the TA-Lib C library and re-run:");
+ Console.WriteLine(" macOS : brew install ta-lib");
+ Console.WriteLine(" Debian : apt-get install libta-lib0 libta-lib-dev (or build from source)");
+ Console.WriteLine(" Windows : put ta-lib.dll (or ta_libc_cdr.dll) on PATH or next to the executable");
+ Console.WriteLine(" Any OS : set TALIB_NATIVE_LIBRARY to the full path of the shared library");
+ }
+
+ Console.WriteLine(Rule);
+ Console.WriteLine();
+ }
+}
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/README.md b/benchmarks/TechnicalAnalysis.Benchmarks/README.md
new file mode 100644
index 00000000..000a432c
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/README.md
@@ -0,0 +1,320 @@
+# TaLibStandard performance benchmarks
+
+A [BenchmarkDotNet](https://benchmarkdotnet.org) suite that measures the TaLibStandard indicator kernels and,
+optionally, compares them head to head against the original TA-Lib C library.
+
+The suite is **fully offline and deterministic**. It never touches the network, never reads a market data provider
+and needs no API key: all inputs come from a seeded synthetic OHLCV generator, so a run on your machine and a run on
+CI see byte-identical data.
+
+Native TA-Lib is **entirely optional**. When it is not installed the suite prints a banner saying so and simply
+drops the comparison benchmarks from the runnable set.
+
+---
+
+## Quick start
+
+```bash
+# from the repository root
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --list flat # see what exists
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --selfcheck # correctness gate, instant
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks # interactive menu
+```
+
+> Always run in `Release`. BenchmarkDotNet refuses to produce numbers from a `Debug` build, and rightly so.
+
+### Run everything
+
+```bash
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --filter '*'
+```
+
+This is a *long* run: 119 benchmark methods times three series lengths. Budget an hour or more. For day-to-day work,
+filter.
+
+### Run one category
+
+Categories are `OverlapStudies`, `Momentum`, `VolatilityVolume`, `CandlePatterns`, `Precision`, `NativeComparison`,
+plus the cross-cutting tags `TAFunc`, `TAMath`, `double`, `float` and `decimal`.
+
+```bash
+# one suite
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --anyCategories Momentum
+
+# only the allocation-free kernels, across every suite
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --anyCategories TAFunc
+
+# the decimal candlestick benchmarks only (both tags must match)
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --allCategories CandlePatterns decimal
+```
+
+### Run one filter
+
+`--filter` takes glob patterns against the fully qualified method name.
+
+```bash
+# every RSI benchmark anywhere in the suite
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --filter '*Rsi*'
+
+# one class
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --filter '*OverlapStudiesBenchmarks*'
+
+# one method, one length, quickly
+dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- \
+ --filter '*OverlapStudiesBenchmarks.Sma*' --job Short
+```
+
+### Useful switches
+
+Every standard BenchmarkDotNet argument is passed through.
+
+| Switch | Effect |
+| --- | --- |
+| `--list flat` / `--list tree` | Enumerate benchmarks without running them |
+| `--filter ` | Select benchmarks by fully qualified name |
+| `--anyCategories` / `--allCategories` | Select by `[BenchmarkCategory]` |
+| `--job Dry` | One cold-start iteration. Smoke test only β **the timings are meaningless** |
+| `--job Short` | Fewer iterations, roughly 5x faster, wider error bars |
+| `--job Default` | The standard statistically rigorous job |
+| `--runtimes net10.0` | Pick the target runtime explicitly |
+| `--exporters github json` | Choose exporters at the command line |
+| `--selfcheck` | Project-specific: invoke every benchmark once and assert it succeeds. No timing |
+
+Results are written to `BenchmarkDotNet.Artifacts/results/` next to the executable, as GitHub-flavoured markdown
+(paste straight into an issue or release note) and as full JSON (machine readable, for tracking regressions between
+releases).
+
+---
+
+## What the suite measures
+
+### The two API paths, benchmarked separately
+
+Nearly every indicator appears **twice**, and the distinction is the single most important thing to understand when
+reading the output.
+
+| Suffix | API | Output buffers | What the number means |
+| --- | --- | --- | --- |
+| `_TAFunc` | `TAFunc.Sma(..., ref outReal)` | Allocated once in `[GlobalSetup]`, i.e. **outside** the measured method | Pure algorithm cost. Allocation shows as ~0 B (a few bytes of boxing noise from the benchmark harness itself) |
+| `_TAMath` | `TAMath.Sma(...) -> SmaResult` | Allocated **inside** the call, one array per output series plus one result record | Algorithm cost **plus** the price of the ergonomic API |
+
+The delta between the two is exactly what the convenience of `TAMath` costs, and the `Allocated` column quantifies
+it: an `SmaResult` over 100 000 bars allocates ~800 KB per call while the `TAFunc` path allocates nothing.
+
+Some kernels allocate internally regardless of which entry point you use (`Sar`, `Natr`, `StochRsi`, `Ppo`, `Macd`
+and the other composites build scratch arrays). The memory columns make that visible rather than hiding it.
+
+### The suites
+
+| Class | Indicators | Notes |
+| --- | --- | --- |
+| `OverlapStudiesBenchmarks` | Sma, Ema, Wma, Dema, Tema, Trima, Kama, T3, BollingerBands, MidPoint, Sar | Both API paths |
+| `MomentumBenchmarks` | Rsi, Macd, Stoch, StochRsi, Adx, Cci, Mfi, WillR, Ppo, Roc, UltOsc, Aroon | Both API paths |
+| `VolatilityVolumeBenchmarks` | Atr, Natr, TrueRange, Obv, Ad, AdOsc, StdDev, Variance, Correl, Beta | Both API paths. Correl and Beta consume a second, correlated instrument |
+| `CandlePatternBenchmarks` | Doji, Engulfing, Hammer, HangingMan, Harami, Marubozu, SpinningTop, ShootingStar, 3WhiteSoldiers, 3BlackCrows, Piercing, HighWave, MorningStar | Each over `double`, `float` **and** `decimal`, to price the generic-math design |
+| `PrecisionBenchmarks` | Sma, Ema, Rsi, Macd, BollingerBands, Atr, Correl | `double` vs `float` on the ergonomic API |
+| `NativeComparisonBenchmarks` | Sma, Ema, Rsi, Macd, BBands, Atr, Adx, Stoch | Managed vs TA-Lib C. Only runs when the native library is present |
+
+Every class is a `[MemoryDiagnoser]` and parameterised over series length `1 000`, `10 000` and `100 000`.
+
+### What the precision benchmarks actually show
+
+`TAFunc` is written for `double` only. The `float` overloads on `TAMath` widen their inputs into freshly allocated
+`double[]` arrays and then call the same kernel. A `float` benchmark therefore measures *the double kernel plus one
+widening pass and one array allocation per input series*. It can never be faster than its `double` counterpart, and
+`PrecisionBenchmarks` exists to put a number on the overhead rather than leave it to intuition.
+
+The candlestick suite is different: `TACandle` is generic over `T : IFloatingPoint`, so the JIT emits a dedicated
+body per value type. `double` and `float` compile to hardware floating point; `decimal` falls back to the software
+128-bit implementation. The three variants of each pattern price that choice directly.
+
+### The synthetic data
+
+`Data/MarketDataGenerator.cs` builds a discretised geometric Brownian motion,
+`C[i] = C[i-1] * exp((mu - sigma^2/2) * dt + sigma * sqrt(dt) * Z)`, with `mu = 8%`, `sigma = 25%` and a trading-day
+time step. On top of the close path it adds an overnight gap for the open and independent exponential wicks for the
+high and low, so pattern recognisers see realistic bodies and shadows instead of degenerate bars. Volume is
+log-normal and grows with the absolute return of the bar. A second, correlated instrument (rho = 0.65) feeds Correl
+and Beta.
+
+Randomness comes from `DeterministicRandom` (xoshiro256\*\* seeded through SplitMix64), implemented in this project
+rather than taken from `System.Random`, because the BCL does not guarantee that a seed produces the same sequence
+across runtime versions. The default seed is `20240217`; change it only if every published number is regenerated at
+the same time.
+
+Every price is rounded to four decimal places at generation time, which makes the `decimal` projection an **exact**
+representation of the `double` projection. Only the `float` projection loses information. A precision comparison is
+therefore a pure cost comparison, not a "different data" comparison.
+
+---
+
+## Enabling the native TA-Lib comparison
+
+`NativeComparisonBenchmarks` runs the managed kernels against the original TA-Lib C library through P/Invoke. It is
+skipped automatically when the library is missing, so nothing below is required to use the rest of the suite.
+
+### Install the native library
+
+**macOS**
+
+```bash
+brew install ta-lib
+# installs /opt/homebrew/lib/libta-lib.dylib on Apple silicon, /usr/local/lib/... on Intel
+```
+
+**Linux (Debian / Ubuntu)**
+
+```bash
+# from the distribution, when packaged
+sudo apt-get install libta-lib0 libta-lib-dev
+
+# or from source
+curl -L -O https://github.com/TA-Lib/ta-lib/releases/download/v0.6.4/ta-lib-0.6.4-src.tar.gz
+tar xzf ta-lib-0.6.4-src.tar.gz && cd ta-lib-0.6.4
+./configure --prefix=/usr/local && make && sudo make install && sudo ldconfig
+```
+
+**Windows**
+
+Install the official MSI, or download the prebuilt `ta-lib` archive, then make sure `ta-lib.dll` (older builds:
+`ta_libc_cdr.dll`) is on `PATH` or sits next to the benchmark executable.
+
+### How discovery works
+
+`Interop/NativeTaLib.cs` registers a `DllImportResolver` and probes, in order:
+
+1. the path or name in the `TALIB_NATIVE_LIBRARY` environment variable, if set;
+2. the plain names `ta-lib`, `ta_lib`, `libta-lib`, `libta_lib`, `ta_libc`, `ta_libc_cdr`, `libta-lib.so.0`,
+ `libta_lib.so.0`, letting the platform loader apply its own `lib` prefix and `.dylib` / `.so` / `.dll` suffix
+ conventions;
+3. a list of absolute paths covering the Homebrew, MacPorts and autotools defaults.
+
+A candidate that loads but exports no `TA_Initialize` is rejected as "not TA-Lib". `TA_Initialize` is called once on
+the first successful load and `TA_Shutdown` is registered on process exit. **The probe never throws**: on any
+failure `NativeTaLib.IsAvailable` is simply `false` and `NativeTaLib.Diagnostics` explains why.
+
+If discovery fails for a library you know is installed:
+
+```bash
+TALIB_NATIVE_LIBRARY=/opt/homebrew/lib/libta-lib.dylib \
+ dotnet run -c Release --project benchmarks/TechnicalAnalysis.Benchmarks -- --anyCategories NativeComparison
+```
+
+### Correctness before speed
+
+A "faster" result that computes the wrong thing is worse than no result. `NativeComparisonBenchmarks.Setup()` runs
+both implementations once and asserts, via `Interop/NativeEquivalence.cs`, that:
+
+* `outBegIdx` matches exactly (a shifted series would silently misalign every signal in time);
+* `outNBElement` matches exactly;
+* every produced value agrees within a relative tolerance of `1e-9`.
+
+A mismatch throws in `[GlobalSetup]`, which BenchmarkDotNet reports as a failed benchmark. No timing is ever
+published for a pair that disagrees.
+
+Both sides use caller-supplied output buffers, so the comparison is algorithm against algorithm with no allocation
+noise on either side. The managed side deliberately uses `TAFunc`, not `TAMath`, for that reason. The managed
+implementation is the baseline of each group, so the `Ratio` column reads directly as *native time / managed time*.
+
+### Which C signatures are bound
+
+Bound and verified against the upstream `ta_func.h` / `ta_libc.h`: `TA_Initialize`, `TA_Shutdown`, `TA_SMA`,
+`TA_EMA`, `TA_RSI`, `TA_MACD`, `TA_BBANDS`, `TA_ATR`, `TA_ADX`, `TA_STOCH`. All of them share the same shape β
+`startIdx`, `endIdx`, the `const double[]` inputs, the `optIn*` parameters, `int *outBegIdx`, `int *outNBElement`,
+then the `double[]` output buffers β return the C enum `TA_RetCode` (marshalled as `int`, `TA_SUCCESS == 0`) and use
+the cdecl calling convention. The `TA_MAType` enum has the same ordinal order as `TechnicalAnalysis.Common.MAType`
+(SMA, EMA, WMA, DEMA, TEMA, TRIMA, KAMA, MAMA, T3), so a plain cast is correct.
+
+Nothing else is bound. A wrong P/Invoke signature corrupts the stack and can produce plausible-looking but wrong
+numbers, so the rule for this file is: bind only what is certain.
+
+---
+
+## Reading the output
+
+```
+| Method | Categories | Length | Mean | Error | Op/s | Allocated |
+|----------- |---------------------- |------- |-----------:|---------:|-------:|----------:|
+| Sma_TAFunc | TAFunc,OverlapStudies | 100000 | xxx.x us | x.x us | xx,xxx | 0 B |
+| Sma_TAMath | TAMath,OverlapStudies | 100000 | xxx.x us | x.x us | xx,xxx | 800,104 B|
+```
+
+* **Length** β the `[Params]` value: number of bars fed to the indicator.
+* **Mean** β arithmetic mean per operation. One operation is one full pass over the whole series, not one bar.
+ Divide by `Length` for a per-bar figure.
+* **Error** β half of the 99.9% confidence interval. If two means differ by less than the sum of their errors, treat
+ them as indistinguishable.
+* **StdDev / Median** β appear when the distribution is noisy; a large `StdDev` relative to `Mean` means the
+ measurement is unstable and should not be quoted.
+* **Op/s** β full-series operations per second, the reciprocal of `Mean`.
+* **Ratio / Alloc Ratio** β versus the baseline of the same logical group. **Only `NativeComparisonBenchmarks`
+ declares baselines**, where `_Managed` is the baseline of each per-indicator group and the ratio reads as
+ *native Γ· managed*. The indicator suites declare none, because BenchmarkDotNet's default logical group is
+ (Job, Params): one baseline would ratio every row in the class against a single method, so `Ema_TAMath` would
+ be compared against `Sma_TAFunc` rather than against `Ema_TAFunc`. Compare the two rows of the same indicator
+ by hand instead.
+* **Allocated** β managed bytes per operation, inclusive. This is the column that separates the two API paths.
+
+Scaling is the other thing worth reading. A well-behaved O(n) kernel should show `Mean` growing roughly 10x when
+`Length` grows 10x. A super-linear jump between `10 000` and `100 000` usually means the working set stopped fitting
+in cache, not that the algorithm changed.
+
+---
+
+## Caveats
+
+Read these before quoting a number anywhere.
+
+* **`--job Dry` numbers are not measurements.** Dry runs a single cold-start iteration with no warmup, so it is
+ dominated by JIT compilation and first-touch page faults. It exists to prove the plumbing works. Use `--job Short`
+ at minimum and `--job Default` for anything you publish.
+* **JIT warmup matters.** BenchmarkDotNet's default job includes a pilot phase, warmup iterations and overhead
+ subtraction precisely because the first few calls into a freshly JIT-compiled method are not representative. Do not
+ reduce the iteration counts to make a run finish faster and then quote the result.
+* **ServerGC is on** (`true` in the csproj) with concurrent GC.
+ That is a deliberate choice matching a server-side analytics workload, and it changes the allocation-heavy
+ `_TAMath` numbers relative to a workstation-GC client. If your application runs workstation GC, re-run with
+ `--runtimes` and an appropriate job, or expect the ergonomic-path figures to differ.
+* **Machine variance is real.** Absolute timings depend on CPU model, core count, memory bandwidth, thermal state,
+ power profile and what else is running. Laptops on battery throttle. CI runners are shared and noisy. Only compare
+ numbers produced on the same machine in the same session; treat cross-machine comparisons as qualitative.
+* **Compare ratios, not absolutes, across time.** When tracking regressions between releases, the stable signal is
+ the ratio between two benchmarks measured together, not the microseconds.
+* **The data is synthetic.** It is realistic enough to exercise the branches of every kernel and every candlestick
+ recogniser, but real market data has different volatility clustering and gap statistics. Branch-heavy indicators
+ such as the candlestick patterns may behave slightly differently on real series.
+* **Native comparison is single-threaded, in-process P/Invoke.** The measured native time includes the managed to
+ native transition and the array pinning, which is exactly what a .NET consumer of the C library would pay, but it
+ is not the cost you would measure from a C program.
+* **`--selfcheck` is a correctness gate, not a benchmark.** It invokes each method once at `Length = 1 000` and
+ asserts a successful `RetCode`; it deliberately reports no timing at all.
+
+---
+
+## Project layout
+
+```
+benchmarks/TechnicalAnalysis.Benchmarks/
+βββ Benchmarks/
+β βββ BenchmarkCategories.cs category name constants
+β βββ MarketDataBenchmarkBase.cs [Params] length, generated data, pre-allocated output buffers
+β βββ OverlapStudiesBenchmarks.cs
+β βββ MomentumBenchmarks.cs
+β βββ VolatilityVolumeBenchmarks.cs
+β βββ CandlePatternBenchmarks.cs
+β βββ PrecisionBenchmarks.cs
+β βββ NativeComparisonBenchmarks.cs managed vs TA-Lib C, with equivalence assertion in [GlobalSetup]
+βββ Configuration/
+β βββ TaLibBenchmarkConfig.cs exporters, columns, ordering, summary style
+βββ Data/
+β βββ DeterministicRandom.cs xoshiro256** + SplitMix64 + Box-Muller
+β βββ MarketDataGenerator.cs seeded GBM OHLCV generator
+β βββ MarketSeries.cs double / float / decimal projections + correlated reference series
+β βββ OhlcvSeries.cs
+βββ Diagnostics/
+β βββ BenchmarkSelfCheck.cs --selfcheck correctness gate
+βββ Interop/
+β βββ NativeTaLib.cs opt-in P/Invoke bridge, never throws
+β βββ NativeEquivalence.cs managed vs native tolerance assertions
+βββ Program.cs banner, native detection, BenchmarkSwitcher
+```
diff --git a/benchmarks/TechnicalAnalysis.Benchmarks/TechnicalAnalysis.Benchmarks.csproj b/benchmarks/TechnicalAnalysis.Benchmarks/TechnicalAnalysis.Benchmarks.csproj
new file mode 100644
index 00000000..ec664a24
--- /dev/null
+++ b/benchmarks/TechnicalAnalysis.Benchmarks/TechnicalAnalysis.Benchmarks.csproj
@@ -0,0 +1,44 @@
+
+
+
+ Exe
+ false
+ true
+ TechnicalAnalysis.Benchmarks
+
+ true
+
+ true
+ true
+ true
+
+
+
+
+ $(NoWarn);CA1031;CA1051;CA1303;CA1307;CA1310;CA1707;CA1819;CA1812;CA5394;SYSLIB1054;IDE0032;IDE0290;IDE0350
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/README.md b/docs/README.md
index c61bc586..f72a343a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,23 +1,97 @@
# π TaLibStandard Documentation
-## π Read the documentation
+Everything written about TaLibStandard lives here. There are three kinds of page, and it helps to know
+which one you want:
-### ...on GitHub (recommended)
+| I want to⦠| Go to |
+|------------|-------|
+| Learn the library from scratch | [Guides](#-guides) β hand-written, worked examples, prose |
+| Find an indicator and its signature | [Indicator catalog](#-indicator-catalog) β generated from the source, one table per category |
+| Look up one type or member in detail | [API reference](#-api-reference) β generated from the XML docs |
+| Never do `BegIdx` / `NBElement` arithmetic by hand | [Fluent API](guides/fluent-api.md) β `PriceSeries` in, bar-indexed `IndicatorSeries` out |
-The main documentation for TaLibStandard is located in the following files. They are written in Markdown and can be read directly from GitHub:
+---
-- **Candles**: [TechnicalAnalysis.Candles](https://github.com/phmatray/TaLibStandard/blob/main/docs/candles/Atypical.TechnicalAnalysis.Candles.md)
-- **Functions**: [TechnicalAnalysis.Functions](https://github.com/phmatray/TaLibStandard/blob/main/docs/functions/Atypical.TechnicalAnalysis.Functions.md)
-- **Common**: [TechnicalAnalysis.Common](https://github.com/phmatray/TaLibStandard/blob/main/docs/common/Atypical.TechnicalAnalysis.Common.md)
+## π Guides
-### ...in your IDE
+Hand-written and kept in `docs/guides/`. Start at the top.
-If you are using an IDE that supports Markdown, you can open the following files directly in your IDE:
+| Guide | What it covers |
+|-------|----------------|
+| [**Getting started**](guides/getting-started.md) | Installation, your first indicator, and the three things that trip everyone up: `RetCode`, `BegIdx`/`NBElement` output alignment, and the `double` / `float` / `decimal` story. Includes a hand-checkable worked SMA example, a lookback table, `TACore.Globals`, the low-level `TAFunc` API and thirteen common pitfalls. **Read this one first.** |
+| [**Fluent API**](guides/fluent-api.md) | The bar-indexed layer over `TAMath`: `PriceSeries` factories, `IndicatorSeries` and its warm-up-is-`null` contract, crossings, `AsOf`, the nine shipped indicators with a worked example each, the `Align` escape hatch to the other 89, and error handling. |
+| [**Indicator catalog**](indicators/README.md) | Every public entry point in one place β see below. |
+| [**Real-time streaming**](guides/real-time-streaming.md) | Turning a tick feed into bars and indicators: architecture, both transports (SignalR hub and raw WebSocket), the message contracts, warm-up and null semantics, and production notes on Redis scale-out, backpressure, cancellation and when to abandon window-recompute for incremental state. |
+| [**Backtesting**](guides/backtesting.md) | The engine model and its execution timeline, the structurally enforced no-look-ahead guarantee, the commission/slippage cost model, every performance metric with its formula and annualisation assumption, a complete worked `IStrategy`, CSV input, and an honest limitations section. |
+| [**TradingView integration**](guides/tradingview-integration.md) | Mapping ~50 Pine `ta.*` functions to `TAMath`/`TACandle`, the three library defects that make some outputs impossible to reconcile at any tolerance, nine parity caveats (Wilder smoothing, EMA seeding, repainting, session alignment, dividend adjustment, β¦), UDF datafeed and Lightweight Charts wiring, and alert-webhook security. |
+| [**Benchmarks**](guides/benchmarks.md) | What the BenchmarkDotNet suite measures and why, how to run one category or one filter, how to enable the native TA-Lib C comparison per platform, how to read every output column, the measured results, and the methodology caveats. |
-- **Candles**: [./docs/candles/TechnicalAnalysis.Candles.md](./candles/Atypical.TechnicalAnalysis.Candles.md)
-- **Functions**: [./docs/functions/TechnicalAnalysis.Functions.md](./functions/Atypical.TechnicalAnalysis.Functions.md)
-- **Common**: [./docs/common/TechnicalAnalysis.Common.md](./common/Atypical.TechnicalAnalysis.Common.md)
+---
-## π€ Contributing
+## π Indicator catalog
-If you want to contribute to the documentation, just edit the summaries in the source code and build the solution. The documentation will be automatically generated in the `docs` folder.
+[**docs/indicators/README.md**](indicators/README.md) lists every public entry point grouped into ten
+categories, with its full signature, parameters and defaults, output property names, a one-line
+description and a link to its generated API page.
+
+It is **generated** by [`tools/generate-indicator-catalog.py`](../tools/generate-indicator-catalog.py)
+directly from `src/`. Do not hand-edit it. To regenerate:
+
+```shell
+python3 tools/generate-indicator-catalog.py
+
+# or, to fail if the committed file is stale (suitable for CI)
+python3 tools/generate-indicator-catalog.py --check
+```
+
+The generator exits non-zero β with a named list β if an indicator exists in the source but is missing
+from its category table, or vice versa, so a newly added indicator cannot silently escape the catalog.
+
+---
+
+## π API reference
+
+Generated from the XML documentation comments by
+[Doraku/DefaultDocumentation](https://github.com/Doraku/DefaultDocumentation) every time the solution is
+built. One page per public type and member.
+
+### β¦on GitHub (recommended)
+
+- **Functions**: [Atypical.TechnicalAnalysis.Functions](https://github.com/phmatray/TaLibStandard/blob/main/docs/functions/Atypical.TechnicalAnalysis.Functions.md)
+- **Candles**: [Atypical.TechnicalAnalysis.Candles](https://github.com/phmatray/TaLibStandard/blob/main/docs/candles/Atypical.TechnicalAnalysis.Candles.md)
+- **Common**: [Atypical.TechnicalAnalysis.Common](https://github.com/phmatray/TaLibStandard/blob/main/docs/common/Atypical.TechnicalAnalysis.Common.md)
+
+### β¦in your IDE
+
+- **Functions**: [./functions/Atypical.TechnicalAnalysis.Functions.md](./functions/Atypical.TechnicalAnalysis.Functions.md)
+- **Candles**: [./candles/Atypical.TechnicalAnalysis.Candles.md](./candles/Atypical.TechnicalAnalysis.Candles.md)
+- **Common**: [./common/Atypical.TechnicalAnalysis.Common.md](./common/Atypical.TechnicalAnalysis.Common.md)
+
+There is also a flat [function list](./functions.md) if you only need the names.
+
+---
+
+## π§ͺ Runnable code
+
+The guides above describe these; the code itself is in the repository.
+
+| Project | Guide |
+|---------|-------|
+| [`samples/TechnicalAnalysis.Samples.RealTime`](../samples/TechnicalAnalysis.Samples.RealTime) + [`.Client`](../samples/TechnicalAnalysis.Samples.RealTime.Client) | [Real-time streaming](guides/real-time-streaming.md) |
+| [`samples/TechnicalAnalysis.Samples.Backtesting`](../samples/TechnicalAnalysis.Samples.Backtesting) | [Backtesting](guides/backtesting.md) |
+| [`benchmarks/TechnicalAnalysis.Benchmarks`](../benchmarks/TechnicalAnalysis.Benchmarks) | [Benchmarks](guides/benchmarks.md) |
+| [`Demo.BlazorWasm`](../Demo.BlazorWasm) | β charts indicators in the browser |
+
+---
+
+## π€ Contributing to the docs
+
+* **API reference** (`docs/functions/`, `docs/candles/`, `docs/common/`, `docs/links`): edit the XML
+ summaries in the source and build the solution. These folders are regenerated on every build; hand
+ edits are lost.
+* **Indicator catalog** (`docs/indicators/README.md`): edit the `CATEGORIES` table in
+ `tools/generate-indicator-catalog.py`, then re-run the script.
+* **Guides** (`docs/guides/*.md`): ordinary hand-written Markdown β edit directly.
+
+All summaries are written in English. If you would like to help translate the documentation, please open
+an issue to discuss it.
diff --git a/docs/candles/Candle2Crows_T_.md b/docs/candles/Candle2Crows_T_.md
index ebc315de..fb216f9d 100644
--- a/docs/candles/Candle2Crows_T_.md
+++ b/docs/candles/Candle2Crows_T_.md
@@ -17,7 +17,7 @@ public class Candle2Crows : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle2Crows_T_.md#TechnicalAnalysis.Candles.Candle2Crows_T_.T 'TechnicalAnalysis\.Candles\.Candle2Crows\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 Candle2Crows\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle2Crows_T_.md#TechnicalAnalysis.Candles.Candle2Crows_T_.T 'TechnicalAnalysis\.Candles\.Candle2Crows\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β Candle2Crows\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/Candle3BlackCrows_T_.md b/docs/candles/Candle3BlackCrows_T_.md
index 61989dc0..29cbf959 100644
--- a/docs/candles/Candle3BlackCrows_T_.md
+++ b/docs/candles/Candle3BlackCrows_T_.md
@@ -17,7 +17,7 @@ public class Candle3BlackCrows : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3BlackCrows_T_.md#TechnicalAnalysis.Candles.Candle3BlackCrows_T_.T 'TechnicalAnalysis\.Candles\.Candle3BlackCrows\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 Candle3BlackCrows\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3BlackCrows_T_.md#TechnicalAnalysis.Candles.Candle3BlackCrows_T_.T 'TechnicalAnalysis\.Candles\.Candle3BlackCrows\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β Candle3BlackCrows\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/Candle3Inside_T_.md b/docs/candles/Candle3Inside_T_.md
index 47f43350..5ee64292 100644
--- a/docs/candles/Candle3Inside_T_.md
+++ b/docs/candles/Candle3Inside_T_.md
@@ -17,7 +17,7 @@ public class Candle3Inside : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3Inside_T_.md#TechnicalAnalysis.Candles.Candle3Inside_T_.T 'TechnicalAnalysis\.Candles\.Candle3Inside\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 Candle3Inside\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3Inside_T_.md#TechnicalAnalysis.Candles.Candle3Inside_T_.T 'TechnicalAnalysis\.Candles\.Candle3Inside\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β Candle3Inside\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/Candle3LineStrike_T_.md b/docs/candles/Candle3LineStrike_T_.md
index 35b6d355..f6154a48 100644
--- a/docs/candles/Candle3LineStrike_T_.md
+++ b/docs/candles/Candle3LineStrike_T_.md
@@ -17,7 +17,7 @@ public class Candle3LineStrike : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3LineStrike_T_.md#TechnicalAnalysis.Candles.Candle3LineStrike_T_.T 'TechnicalAnalysis\.Candles\.Candle3LineStrike\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 Candle3LineStrike\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3LineStrike_T_.md#TechnicalAnalysis.Candles.Candle3LineStrike_T_.T 'TechnicalAnalysis\.Candles\.Candle3LineStrike\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β Candle3LineStrike\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/Candle3Outside_T_.md b/docs/candles/Candle3Outside_T_.md
index 0f3a805f..d89517ec 100644
--- a/docs/candles/Candle3Outside_T_.md
+++ b/docs/candles/Candle3Outside_T_.md
@@ -17,7 +17,7 @@ public class Candle3Outside : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3Outside_T_.md#TechnicalAnalysis.Candles.Candle3Outside_T_.T 'TechnicalAnalysis\.Candles\.Candle3Outside\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 Candle3Outside\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3Outside_T_.md#TechnicalAnalysis.Candles.Candle3Outside_T_.T 'TechnicalAnalysis\.Candles\.Candle3Outside\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β Candle3Outside\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/Candle3StarsInSouth_T_.md b/docs/candles/Candle3StarsInSouth_T_.md
index 16745681..12ac1c71 100644
--- a/docs/candles/Candle3StarsInSouth_T_.md
+++ b/docs/candles/Candle3StarsInSouth_T_.md
@@ -17,7 +17,7 @@ public class Candle3StarsInSouth : TechnicalAnalysis.Common.CandleIndicator\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 Candle3StarsInSouth\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3StarsInSouth_T_.md#TechnicalAnalysis.Candles.Candle3StarsInSouth_T_.T 'TechnicalAnalysis\.Candles\.Candle3StarsInSouth\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β Candle3StarsInSouth\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/Candle3WhiteSoldiers_T_.md b/docs/candles/Candle3WhiteSoldiers_T_.md
index 1799f709..4dc59fcf 100644
--- a/docs/candles/Candle3WhiteSoldiers_T_.md
+++ b/docs/candles/Candle3WhiteSoldiers_T_.md
@@ -17,7 +17,7 @@ public class Candle3WhiteSoldiers : TechnicalAnalysis.Common.CandleIndicator<
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3WhiteSoldiers_T_.md#TechnicalAnalysis.Candles.Candle3WhiteSoldiers_T_.T 'TechnicalAnalysis\.Candles\.Candle3WhiteSoldiers\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 Candle3WhiteSoldiers\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](Candle3WhiteSoldiers_T_.md#TechnicalAnalysis.Candles.Candle3WhiteSoldiers_T_.T 'TechnicalAnalysis\.Candles\.Candle3WhiteSoldiers\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β Candle3WhiteSoldiers\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleAbandonedBaby_T_.md b/docs/candles/CandleAbandonedBaby_T_.md
index 37295d53..be881890 100644
--- a/docs/candles/CandleAbandonedBaby_T_.md
+++ b/docs/candles/CandleAbandonedBaby_T_.md
@@ -17,7 +17,7 @@ public class CandleAbandonedBaby : TechnicalAnalysis.Common.CandleIndicator\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleAbandonedBaby\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleAbandonedBaby_T_.md#TechnicalAnalysis.Candles.CandleAbandonedBaby_T_.T 'TechnicalAnalysis\.Candles\.CandleAbandonedBaby\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleAbandonedBaby\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleAdvanceBlock_T_.md b/docs/candles/CandleAdvanceBlock_T_.md
index 321c1499..b9d10b39 100644
--- a/docs/candles/CandleAdvanceBlock_T_.md
+++ b/docs/candles/CandleAdvanceBlock_T_.md
@@ -17,7 +17,7 @@ public class CandleAdvanceBlock : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleAdvanceBlock_T_.md#TechnicalAnalysis.Candles.CandleAdvanceBlock_T_.T 'TechnicalAnalysis\.Candles\.CandleAdvanceBlock\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleAdvanceBlock\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleAdvanceBlock_T_.md#TechnicalAnalysis.Candles.CandleAdvanceBlock_T_.T 'TechnicalAnalysis\.Candles\.CandleAdvanceBlock\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleAdvanceBlock\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleBeltHold_T_.md b/docs/candles/CandleBeltHold_T_.md
index 69c27ae6..8788ebef 100644
--- a/docs/candles/CandleBeltHold_T_.md
+++ b/docs/candles/CandleBeltHold_T_.md
@@ -17,7 +17,7 @@ public class CandleBeltHold : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleBeltHold_T_.md#TechnicalAnalysis.Candles.CandleBeltHold_T_.T 'TechnicalAnalysis\.Candles\.CandleBeltHold\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleBeltHold\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleBeltHold_T_.md#TechnicalAnalysis.Candles.CandleBeltHold_T_.T 'TechnicalAnalysis\.Candles\.CandleBeltHold\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleBeltHold\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleBreakaway_T_.md b/docs/candles/CandleBreakaway_T_.md
index d5973a77..a4cbad97 100644
--- a/docs/candles/CandleBreakaway_T_.md
+++ b/docs/candles/CandleBreakaway_T_.md
@@ -17,7 +17,7 @@ public class CandleBreakaway : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleBreakaway_T_.md#TechnicalAnalysis.Candles.CandleBreakaway_T_.T 'TechnicalAnalysis\.Candles\.CandleBreakaway\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleBreakaway\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleBreakaway_T_.md#TechnicalAnalysis.Candles.CandleBreakaway_T_.T 'TechnicalAnalysis\.Candles\.CandleBreakaway\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleBreakaway\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleClosingMarubozu_T_.md b/docs/candles/CandleClosingMarubozu_T_.md
index 62df74ed..1595e82b 100644
--- a/docs/candles/CandleClosingMarubozu_T_.md
+++ b/docs/candles/CandleClosingMarubozu_T_.md
@@ -17,7 +17,7 @@ public class CandleClosingMarubozu : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleClosingMarubozu_T_.md#TechnicalAnalysis.Candles.CandleClosingMarubozu_T_.T 'TechnicalAnalysis\.Candles\.CandleClosingMarubozu\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleClosingMarubozu\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleClosingMarubozu_T_.md#TechnicalAnalysis.Candles.CandleClosingMarubozu_T_.T 'TechnicalAnalysis\.Candles\.CandleClosingMarubozu\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleClosingMarubozu\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleConcealBabySwallow_T_.md b/docs/candles/CandleConcealBabySwallow_T_.md
index d726ea1b..078b0f0d 100644
--- a/docs/candles/CandleConcealBabySwallow_T_.md
+++ b/docs/candles/CandleConcealBabySwallow_T_.md
@@ -17,7 +17,7 @@ public class CandleConcealBabySwallow : TechnicalAnalysis.Common.CandleIndica
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleConcealBabySwallow_T_.md#TechnicalAnalysis.Candles.CandleConcealBabySwallow_T_.T 'TechnicalAnalysis\.Candles\.CandleConcealBabySwallow\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleConcealBabySwallow\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleConcealBabySwallow_T_.md#TechnicalAnalysis.Candles.CandleConcealBabySwallow_T_.T 'TechnicalAnalysis\.Candles\.CandleConcealBabySwallow\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleConcealBabySwallow\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleCounterAttack_T_.md b/docs/candles/CandleCounterAttack_T_.md
index 1964454b..d7a532d5 100644
--- a/docs/candles/CandleCounterAttack_T_.md
+++ b/docs/candles/CandleCounterAttack_T_.md
@@ -17,7 +17,7 @@ public class CandleCounterAttack : TechnicalAnalysis.Common.CandleIndicator\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleCounterAttack\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleCounterAttack_T_.md#TechnicalAnalysis.Candles.CandleCounterAttack_T_.T 'TechnicalAnalysis\.Candles\.CandleCounterAttack\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleCounterAttack\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleDarkCloudCover_T_.md b/docs/candles/CandleDarkCloudCover_T_.md
index b23aac6a..ebdb7153 100644
--- a/docs/candles/CandleDarkCloudCover_T_.md
+++ b/docs/candles/CandleDarkCloudCover_T_.md
@@ -17,7 +17,7 @@ public class CandleDarkCloudCover : TechnicalAnalysis.Common.CandleIndicator<
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleDarkCloudCover_T_.md#TechnicalAnalysis.Candles.CandleDarkCloudCover_T_.T 'TechnicalAnalysis\.Candles\.CandleDarkCloudCover\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleDarkCloudCover\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleDarkCloudCover_T_.md#TechnicalAnalysis.Candles.CandleDarkCloudCover_T_.T 'TechnicalAnalysis\.Candles\.CandleDarkCloudCover\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleDarkCloudCover\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleDojiStar_T_.md b/docs/candles/CandleDojiStar_T_.md
index ea0a658f..95195138 100644
--- a/docs/candles/CandleDojiStar_T_.md
+++ b/docs/candles/CandleDojiStar_T_.md
@@ -17,7 +17,7 @@ public class CandleDojiStar : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleDojiStar_T_.md#TechnicalAnalysis.Candles.CandleDojiStar_T_.T 'TechnicalAnalysis\.Candles\.CandleDojiStar\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleDojiStar\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleDojiStar_T_.md#TechnicalAnalysis.Candles.CandleDojiStar_T_.T 'TechnicalAnalysis\.Candles\.CandleDojiStar\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleDojiStar\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleDoji_T_.md b/docs/candles/CandleDoji_T_.md
index f9b77d6a..81c1f7eb 100644
--- a/docs/candles/CandleDoji_T_.md
+++ b/docs/candles/CandleDoji_T_.md
@@ -17,7 +17,7 @@ public class CandleDoji : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleDoji_T_.md#TechnicalAnalysis.Candles.CandleDoji_T_.T 'TechnicalAnalysis\.Candles\.CandleDoji\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleDoji\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleDoji_T_.md#TechnicalAnalysis.Candles.CandleDoji_T_.T 'TechnicalAnalysis\.Candles\.CandleDoji\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleDoji\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleDragonflyDoji_T_.md b/docs/candles/CandleDragonflyDoji_T_.md
index 15f63cbf..76cb24e4 100644
--- a/docs/candles/CandleDragonflyDoji_T_.md
+++ b/docs/candles/CandleDragonflyDoji_T_.md
@@ -17,7 +17,7 @@ public class CandleDragonflyDoji : TechnicalAnalysis.Common.CandleIndicator\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleDragonflyDoji\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleDragonflyDoji_T_.md#TechnicalAnalysis.Candles.CandleDragonflyDoji_T_.T 'TechnicalAnalysis\.Candles\.CandleDragonflyDoji\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleDragonflyDoji\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleEngulfing_T_.md b/docs/candles/CandleEngulfing_T_.md
index d07c4083..eb1cd8bf 100644
--- a/docs/candles/CandleEngulfing_T_.md
+++ b/docs/candles/CandleEngulfing_T_.md
@@ -17,7 +17,7 @@ public class CandleEngulfing : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleEngulfing_T_.md#TechnicalAnalysis.Candles.CandleEngulfing_T_.T 'TechnicalAnalysis\.Candles\.CandleEngulfing\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleEngulfing\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleEngulfing_T_.md#TechnicalAnalysis.Candles.CandleEngulfing_T_.T 'TechnicalAnalysis\.Candles\.CandleEngulfing\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleEngulfing\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleEveningDojiStar_T_.md b/docs/candles/CandleEveningDojiStar_T_.md
index 1d311a7b..b020ddc5 100644
--- a/docs/candles/CandleEveningDojiStar_T_.md
+++ b/docs/candles/CandleEveningDojiStar_T_.md
@@ -17,7 +17,7 @@ public class CandleEveningDojiStar : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleEveningDojiStar_T_.md#TechnicalAnalysis.Candles.CandleEveningDojiStar_T_.T 'TechnicalAnalysis\.Candles\.CandleEveningDojiStar\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleEveningDojiStar\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleEveningDojiStar_T_.md#TechnicalAnalysis.Candles.CandleEveningDojiStar_T_.T 'TechnicalAnalysis\.Candles\.CandleEveningDojiStar\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleEveningDojiStar\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleEveningStar_T_.md b/docs/candles/CandleEveningStar_T_.md
index c2407ebf..97ee6685 100644
--- a/docs/candles/CandleEveningStar_T_.md
+++ b/docs/candles/CandleEveningStar_T_.md
@@ -17,7 +17,7 @@ public class CandleEveningStar : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleEveningStar_T_.md#TechnicalAnalysis.Candles.CandleEveningStar_T_.T 'TechnicalAnalysis\.Candles\.CandleEveningStar\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleEveningStar\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleEveningStar_T_.md#TechnicalAnalysis.Candles.CandleEveningStar_T_.T 'TechnicalAnalysis\.Candles\.CandleEveningStar\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleEveningStar\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleGapSideSideWhite_T_.md b/docs/candles/CandleGapSideSideWhite_T_.md
index 6613a33b..2cf7d9f7 100644
--- a/docs/candles/CandleGapSideSideWhite_T_.md
+++ b/docs/candles/CandleGapSideSideWhite_T_.md
@@ -17,7 +17,7 @@ public class CandleGapSideSideWhite : TechnicalAnalysis.Common.CandleIndicato
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleGapSideSideWhite_T_.md#TechnicalAnalysis.Candles.CandleGapSideSideWhite_T_.T 'TechnicalAnalysis\.Candles\.CandleGapSideSideWhite\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleGapSideSideWhite\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleGapSideSideWhite_T_.md#TechnicalAnalysis.Candles.CandleGapSideSideWhite_T_.T 'TechnicalAnalysis\.Candles\.CandleGapSideSideWhite\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleGapSideSideWhite\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleGravestoneDoji_T_.md b/docs/candles/CandleGravestoneDoji_T_.md
index 523f109b..620230ad 100644
--- a/docs/candles/CandleGravestoneDoji_T_.md
+++ b/docs/candles/CandleGravestoneDoji_T_.md
@@ -17,7 +17,7 @@ public class CandleGravestoneDoji : TechnicalAnalysis.Common.CandleIndicator<
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleGravestoneDoji_T_.md#TechnicalAnalysis.Candles.CandleGravestoneDoji_T_.T 'TechnicalAnalysis\.Candles\.CandleGravestoneDoji\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleGravestoneDoji\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleGravestoneDoji_T_.md#TechnicalAnalysis.Candles.CandleGravestoneDoji_T_.T 'TechnicalAnalysis\.Candles\.CandleGravestoneDoji\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleGravestoneDoji\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleHammer_T_.md b/docs/candles/CandleHammer_T_.md
index e95b848d..e5cb7340 100644
--- a/docs/candles/CandleHammer_T_.md
+++ b/docs/candles/CandleHammer_T_.md
@@ -17,7 +17,7 @@ public class CandleHammer : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHammer_T_.md#TechnicalAnalysis.Candles.CandleHammer_T_.T 'TechnicalAnalysis\.Candles\.CandleHammer\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleHammer\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHammer_T_.md#TechnicalAnalysis.Candles.CandleHammer_T_.T 'TechnicalAnalysis\.Candles\.CandleHammer\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleHammer\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleHangingMan_T_.md b/docs/candles/CandleHangingMan_T_.md
index 20b4ac4d..d22aa791 100644
--- a/docs/candles/CandleHangingMan_T_.md
+++ b/docs/candles/CandleHangingMan_T_.md
@@ -17,7 +17,7 @@ public class CandleHangingMan : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHangingMan_T_.md#TechnicalAnalysis.Candles.CandleHangingMan_T_.T 'TechnicalAnalysis\.Candles\.CandleHangingMan\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleHangingMan\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHangingMan_T_.md#TechnicalAnalysis.Candles.CandleHangingMan_T_.T 'TechnicalAnalysis\.Candles\.CandleHangingMan\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleHangingMan\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleHaramiCross_T_.md b/docs/candles/CandleHaramiCross_T_.md
index 8cf6c2ed..c4b6eaf7 100644
--- a/docs/candles/CandleHaramiCross_T_.md
+++ b/docs/candles/CandleHaramiCross_T_.md
@@ -17,7 +17,7 @@ public class CandleHaramiCross : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHaramiCross_T_.md#TechnicalAnalysis.Candles.CandleHaramiCross_T_.T 'TechnicalAnalysis\.Candles\.CandleHaramiCross\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleHaramiCross\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHaramiCross_T_.md#TechnicalAnalysis.Candles.CandleHaramiCross_T_.T 'TechnicalAnalysis\.Candles\.CandleHaramiCross\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleHaramiCross\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleHarami_T_.md b/docs/candles/CandleHarami_T_.md
index 7b491937..f7937982 100644
--- a/docs/candles/CandleHarami_T_.md
+++ b/docs/candles/CandleHarami_T_.md
@@ -17,7 +17,7 @@ public class CandleHarami : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHarami_T_.md#TechnicalAnalysis.Candles.CandleHarami_T_.T 'TechnicalAnalysis\.Candles\.CandleHarami\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleHarami\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHarami_T_.md#TechnicalAnalysis.Candles.CandleHarami_T_.T 'TechnicalAnalysis\.Candles\.CandleHarami\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleHarami\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleHighWave_T_.md b/docs/candles/CandleHighWave_T_.md
index 4ffde793..159056f7 100644
--- a/docs/candles/CandleHighWave_T_.md
+++ b/docs/candles/CandleHighWave_T_.md
@@ -17,7 +17,7 @@ public class CandleHighWave : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHighWave_T_.md#TechnicalAnalysis.Candles.CandleHighWave_T_.T 'TechnicalAnalysis\.Candles\.CandleHighWave\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleHighWave\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHighWave_T_.md#TechnicalAnalysis.Candles.CandleHighWave_T_.T 'TechnicalAnalysis\.Candles\.CandleHighWave\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleHighWave\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleHikkakeMod_T_.md b/docs/candles/CandleHikkakeMod_T_.md
index d0265a6d..a4e557fa 100644
--- a/docs/candles/CandleHikkakeMod_T_.md
+++ b/docs/candles/CandleHikkakeMod_T_.md
@@ -17,7 +17,7 @@ public class CandleHikkakeMod : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHikkakeMod_T_.md#TechnicalAnalysis.Candles.CandleHikkakeMod_T_.T 'TechnicalAnalysis\.Candles\.CandleHikkakeMod\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleHikkakeMod\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHikkakeMod_T_.md#TechnicalAnalysis.Candles.CandleHikkakeMod_T_.T 'TechnicalAnalysis\.Candles\.CandleHikkakeMod\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleHikkakeMod\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleHikkake_T_.md b/docs/candles/CandleHikkake_T_.md
index 4b256b89..de82588d 100644
--- a/docs/candles/CandleHikkake_T_.md
+++ b/docs/candles/CandleHikkake_T_.md
@@ -17,7 +17,7 @@ public class CandleHikkake : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHikkake_T_.md#TechnicalAnalysis.Candles.CandleHikkake_T_.T 'TechnicalAnalysis\.Candles\.CandleHikkake\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleHikkake\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHikkake_T_.md#TechnicalAnalysis.Candles.CandleHikkake_T_.T 'TechnicalAnalysis\.Candles\.CandleHikkake\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleHikkake\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleHomingPigeon_T_.md b/docs/candles/CandleHomingPigeon_T_.md
index cc1b0564..5dd7d7e8 100644
--- a/docs/candles/CandleHomingPigeon_T_.md
+++ b/docs/candles/CandleHomingPigeon_T_.md
@@ -17,7 +17,7 @@ public class CandleHomingPigeon : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHomingPigeon_T_.md#TechnicalAnalysis.Candles.CandleHomingPigeon_T_.T 'TechnicalAnalysis\.Candles\.CandleHomingPigeon\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleHomingPigeon\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleHomingPigeon_T_.md#TechnicalAnalysis.Candles.CandleHomingPigeon_T_.T 'TechnicalAnalysis\.Candles\.CandleHomingPigeon\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleHomingPigeon\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleIdentical3Crows_T_.md b/docs/candles/CandleIdentical3Crows_T_.md
index 4b82e5e5..e58fdd3f 100644
--- a/docs/candles/CandleIdentical3Crows_T_.md
+++ b/docs/candles/CandleIdentical3Crows_T_.md
@@ -17,7 +17,7 @@ public class CandleIdentical3Crows : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleIdentical3Crows_T_.md#TechnicalAnalysis.Candles.CandleIdentical3Crows_T_.T 'TechnicalAnalysis\.Candles\.CandleIdentical3Crows\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleIdentical3Crows\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleIdentical3Crows_T_.md#TechnicalAnalysis.Candles.CandleIdentical3Crows_T_.T 'TechnicalAnalysis\.Candles\.CandleIdentical3Crows\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleIdentical3Crows\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleInNeck_T_.md b/docs/candles/CandleInNeck_T_.md
index 48336d11..94cd7eec 100644
--- a/docs/candles/CandleInNeck_T_.md
+++ b/docs/candles/CandleInNeck_T_.md
@@ -17,7 +17,7 @@ public class CandleInNeck : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleInNeck_T_.md#TechnicalAnalysis.Candles.CandleInNeck_T_.T 'TechnicalAnalysis\.Candles\.CandleInNeck\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleInNeck\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleInNeck_T_.md#TechnicalAnalysis.Candles.CandleInNeck_T_.T 'TechnicalAnalysis\.Candles\.CandleInNeck\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleInNeck\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleInvertedHammer_T_.md b/docs/candles/CandleInvertedHammer_T_.md
index 5ec48f09..4595c9fb 100644
--- a/docs/candles/CandleInvertedHammer_T_.md
+++ b/docs/candles/CandleInvertedHammer_T_.md
@@ -17,7 +17,7 @@ public class CandleInvertedHammer : TechnicalAnalysis.Common.CandleIndicator<
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleInvertedHammer_T_.md#TechnicalAnalysis.Candles.CandleInvertedHammer_T_.T 'TechnicalAnalysis\.Candles\.CandleInvertedHammer\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleInvertedHammer\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleInvertedHammer_T_.md#TechnicalAnalysis.Candles.CandleInvertedHammer_T_.T 'TechnicalAnalysis\.Candles\.CandleInvertedHammer\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleInvertedHammer\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleKickingByLength_T_.md b/docs/candles/CandleKickingByLength_T_.md
index e09f4890..867cafa0 100644
--- a/docs/candles/CandleKickingByLength_T_.md
+++ b/docs/candles/CandleKickingByLength_T_.md
@@ -17,7 +17,7 @@ public class CandleKickingByLength : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleKickingByLength_T_.md#TechnicalAnalysis.Candles.CandleKickingByLength_T_.T 'TechnicalAnalysis\.Candles\.CandleKickingByLength\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleKickingByLength\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleKickingByLength_T_.md#TechnicalAnalysis.Candles.CandleKickingByLength_T_.T 'TechnicalAnalysis\.Candles\.CandleKickingByLength\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleKickingByLength\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleKicking_T_.md b/docs/candles/CandleKicking_T_.md
index eaa1de05..35ed4765 100644
--- a/docs/candles/CandleKicking_T_.md
+++ b/docs/candles/CandleKicking_T_.md
@@ -17,7 +17,7 @@ public class CandleKicking : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleKicking_T_.md#TechnicalAnalysis.Candles.CandleKicking_T_.T 'TechnicalAnalysis\.Candles\.CandleKicking\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleKicking\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleKicking_T_.md#TechnicalAnalysis.Candles.CandleKicking_T_.T 'TechnicalAnalysis\.Candles\.CandleKicking\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleKicking\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleLadderBottom_T_.md b/docs/candles/CandleLadderBottom_T_.md
index c97f4575..badaab0a 100644
--- a/docs/candles/CandleLadderBottom_T_.md
+++ b/docs/candles/CandleLadderBottom_T_.md
@@ -17,7 +17,7 @@ public class CandleLadderBottom : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleLadderBottom_T_.md#TechnicalAnalysis.Candles.CandleLadderBottom_T_.T 'TechnicalAnalysis\.Candles\.CandleLadderBottom\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleLadderBottom\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleLadderBottom_T_.md#TechnicalAnalysis.Candles.CandleLadderBottom_T_.T 'TechnicalAnalysis\.Candles\.CandleLadderBottom\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleLadderBottom\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleLongLeggedDoji_T_.md b/docs/candles/CandleLongLeggedDoji_T_.md
index 3d492e4c..45ecaf7b 100644
--- a/docs/candles/CandleLongLeggedDoji_T_.md
+++ b/docs/candles/CandleLongLeggedDoji_T_.md
@@ -17,7 +17,7 @@ public class CandleLongLeggedDoji : TechnicalAnalysis.Common.CandleIndicator<
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleLongLeggedDoji_T_.md#TechnicalAnalysis.Candles.CandleLongLeggedDoji_T_.T 'TechnicalAnalysis\.Candles\.CandleLongLeggedDoji\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleLongLeggedDoji\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleLongLeggedDoji_T_.md#TechnicalAnalysis.Candles.CandleLongLeggedDoji_T_.T 'TechnicalAnalysis\.Candles\.CandleLongLeggedDoji\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleLongLeggedDoji\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleLongLine_T_.md b/docs/candles/CandleLongLine_T_.md
index 8c897284..00b4bad4 100644
--- a/docs/candles/CandleLongLine_T_.md
+++ b/docs/candles/CandleLongLine_T_.md
@@ -17,7 +17,7 @@ public class CandleLongLine : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleLongLine_T_.md#TechnicalAnalysis.Candles.CandleLongLine_T_.T 'TechnicalAnalysis\.Candles\.CandleLongLine\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleLongLine\
+Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') β [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleLongLine_T_.md#TechnicalAnalysis.Candles.CandleLongLine_T_.T 'TechnicalAnalysis\.Candles\.CandleLongLine\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') β CandleLongLine\
| Constructors | |
| :--- | :--- |
diff --git a/docs/candles/CandleMarubozu_T_.md b/docs/candles/CandleMarubozu_T_.md
index 889c40c1..b64fbdb0 100644
--- a/docs/candles/CandleMarubozu_T_.md
+++ b/docs/candles/CandleMarubozu_T_.md
@@ -17,7 +17,7 @@ public class CandleMarubozu : TechnicalAnalysis.Common.CandleIndicator
The type of the array elements\.
-Inheritance [System\.Object](https://learn.microsoft.com/en-us/dotnet/api/system.object 'System\.Object') 🡒 [TechnicalAnalysis\.Common\.CandleIndicator<](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1')[T](CandleMarubozu_T_.md#TechnicalAnalysis.Candles.CandleMarubozu_T_.T 'TechnicalAnalysis\.Candles\.CandleMarubozu\\.T')[>](https://learn.microsoft.com/en-us/dotnet/api/technicalanalysis.common.candleindicator-1 'TechnicalAnalysis\.Common\.CandleIndicator\`1') 🡒 CandleMarubozu\