C# Core API
3.1 Batch Processing
3.2 Output Size and Lookback
3.3 Display Shift
3.4 Errors
4.1 Abstract API
4.2 Numerical Stability
4.3 Candlestick Settings
4.4 Input Type: float vs. double
4.5 Index Range
4.6 Threading
4.7 Trimming and NativeAOT
1.0 Introduction
The .NET library is a native port of TA-Lib in the TALib namespace — no P/Invoke, no native dependency, pure managed C# targeting net10.0. Every indicator is a method on a Core instance, takes its series as spans, and is bit-identical to the reference C library over the same inputs, with one exception beyond TA-Lib's control: where a calculation calls a transcendental math function (such as exp, log, sin or atan), .NET's math library can round the result differently from the C library in the last bit (rD1).
The Core API provides:
- The
Coretype and the builder that configures it. - The settings each
Corecarries: unstable period and candlestick settings. MultipleCoreinstances can safely co-exist (say for different settings). - Every TA function, each processing a whole array of data at once.
- An optional Abstract API for calling those functions dynamically.
To process a live feed one bar at a time instead, see the companion C# Streaming API.
There is no initialization step and nothing to shut down. Where C requires TA_Initialize before any call and TA_Shutdown at exit, C# has Core.Default (or a configured Core.Builder()...Build()) ready immediately; a Core owns only managed state, so an unreferenced one is simply garbage-collected.
2.0 Add it to your project
<ItemGroup>
<PackageReference Include="TA-Lib" Version="0.8.3" />
</ItemGroup>Or run dotnet add package TA-Lib. It targets net10.0, so it needs the .NET 10 SDK or later.
Every released version is listed on NuGet.
3.0 Calling into TA-Lib
Every indicator is exposed as a method on Core, taking the same startIdx/endIdx/inputs/optional-parameters/outputs shape as the C function it mirrors.
3.1 Batch Processing
Every function follows the same simple pattern: it reads its inputs from spans you pass in and writes its results into spans you allocate.
A function never writes more elements than you request, so the output span only needs to cover the startIdx-to-endIdx range.
As an example, let's walk through SMA, a method to calculate a moving average.
public OutRange Sma( int startIdx, int endIdx, ReadOnlySpan<double> inReal, int optInTimePeriod, Span<double> outReal )
All TA methods use the same calling pattern, divided into four groups:
- The output will be calculated only for the range specified by startIdx and endIdx. These are zero-based indices into the input spans.
- One or more input spans are then specified. Typically, these are the "price" data. In this example there is only one input. All input parameter names start with "in".
- Zero or more optional inputs are then specified. In this example there is one optional input. These parameters give finer control specific to each function. If you do not care about a particular optIn, just pass
Core.IntegerDefaultorCore.RealDefault(depending on the type). For a moving-average type, useMAType.DEFAULT. - One or more output spans come last. In this example there is only one output (outReal). Where the values landed is the return value, not a parameter: on success you get an
OutRange.
This calling pattern takes some getting used to, but it lets your app spend time and memory only on the data it actually needs.
For example, here is how to calculate a 30-day simple moving average (SMA) of daily closing prices:
using TALib;
var core = Core.Default;
double[] close = [ /* ...your closing prices... */ ];
var outReal = new double[close.Length];
OutRange r = core.Sma(
0, close.Length - 1,
close,
30,
outReal );
// outReal[0 .. r.Count - 1] holds the SMA; outReal[i] is input bar r.BegIdx + i.
for (int i = 0; i < r.Count; i++)
{
Console.WriteLine($"bar {r.BegIdx + i} = {outReal[i]}");
}
After the call, read r to learn what was produced. Even though we requested the whole range (0 to close.Length - 1), a 30-day average is not defined until the 30th day. Consequently r.BegIdx will be 29 (zero-based) and r.Count will be close.Length - 29. In other words, only that many elements of outReal are written, corresponding to input elements 29 through the end.
Arrays convert to spans implicitly, so you can also pass a slice of a larger buffer (close.AsSpan(start, count)) without copying it. startIdx and endIdx then index the slice, not the array it came from: a slice holds no bars before its first element (rP1).
If you do not provide enough data to calculate even one value, the call still succeeds and r.Count is 0 (r.IsEmpty).
OutRange is a readonly record struct with two components, BegIdx and Count (C's outBegIdx and outNBElement), plus the conveniences IsEmpty and Empty.
3.2 Output Size and Lookback
Size the output span to at least endIdx - startIdx + 1, or exactly with the lookback (see the three sizing methods):
int lookback = core.SmaLookback(30); // 29 for a 30-period SMAEach TA method has a matching <Name>Lookback method, taking the same optional parameters as the method itself. The lookback is how many inputs are consumed before the first output.
3.3 Display Shift
A few indicators are conventionally drawn at another bar than the one that computes them. The values are never moved: the matching <Name>DisplayShift method gives the number of bars to shift one output when charting it, positive ahead and negative behind. It is 0 for almost every function.
int shift = core.DpoDisplayShift(20, 0); // period 20, first output: -11The same answer comes from the Abstract API. The rules are in the specification.
3.4 Errors
The public methods throw rather than return a status code:
| Condition | Exception |
|---|---|
startIdx/endIdx negative, above Core.IndexMax, or endIdx < startIdx | TALibArgumentOutOfRangeException |
| An optional parameter outside its documented range | TALibArgumentException |
An input span that does not reach endIdx, or an output span shorter than the values produced | TALibArgumentException naming the span |
| Two outputs that are the same buffer (rB6) | TALibArgumentException |
Each extends the framework type you would reach for, so catch (ArgumentException) still works, and each carries its RetCode.
You can pass the same buffer as an input and an output to compute in place. Avoid a partial overlap: its result is unspecified.
A NaN or ±Inf inside an input series is not detected. Clean the series before calling.
4.0 Advanced Features
4.1 Abstract API
TALib.Metadata.FunctionCatalog describes every function at run time and calls it without naming it at compile time. See the Abstract API page.
Core.Functions is an IReadOnlyList<FuncInfo>, so it is enumerable, LINQ-able, and indexable by position or by name.
4.2 Numerical Stability
Your value changed when you fed the same bar more history? That is by design: recursive functions converge as history accumulates. See Unstable Period for how to mitigate that.
Rounding is a separate axis: floating-point error accumulates over a very long series, which is one reason a call is capped at Core.IndexMax.
Every function documentation page carries a numerical-stability property: how much the value at a given bar depends on where the series you passed in begins.
4.3 Candlestick Settings
The CDL* pattern methods judge each candle against tunable thresholds. See candlestick settings for the full list and defaults; the builder sets them the same way:
var core = Core.Builder()
.CandleSetting(CandleSettingType.BodyDoji, RangeType.HighLow, 10, 0.1)
.Build();Each setter throws ArgumentOutOfRangeException at once if an argument is out of range, so Build() cannot fail.
4.4 Input Type: float vs. double
Every indicator also has a ReadOnlySpan<float> overload (float[] converts implicitly). The overload converts each element to double when read, so both overloads produce the same output, bit-for-bit. Use it to feed price data already stored as float without a conversion copy.
4.5 Index Range
Core.IndexMax is the largest value startIdx or endIdx may take: 100,000,000. It's a sanity bound. Past it, a call is more likely a caller bug than a real need, and it's also untested territory for overflow and rounding error.
4.6 Threading
A Core is immutable once built, so one instance is safe to share across any number of threads with no synchronization. Core.Default is the all-defaults instance. To change a setting, build another Core with Core.Builder().
4.7 Trimming and NativeAOT
The library is annotated IsAotCompatible, uses no reflection, and publishes clean under PublishAot with TrimMode=full.
At ILC's default instruction-set baseline, some indicators (EMA, DEMA, TRIX among them) run slower than under the JIT; values are unaffected. If you publish AOT and care about throughput, raise the baseline:
<IlcInstructionSet>x86-64-v3</IlcInstructionSet>5.0 Documentation
Every function ships XML doc comments, so your IDE's tooltips and IntelliSense are populated without a separate doc build.