Ozakboy.TradeKit.Indicators 0.1.1

dotnet add package Ozakboy.TradeKit.Indicators --version 0.1.1
                    
NuGet\Install-Package Ozakboy.TradeKit.Indicators -Version 0.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ozakboy.TradeKit.Indicators" Version="0.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ozakboy.TradeKit.Indicators" Version="0.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Ozakboy.TradeKit.Indicators" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ozakboy.TradeKit.Indicators --version 0.1.1
                    
#r "nuget: Ozakboy.TradeKit.Indicators, 0.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ozakboy.TradeKit.Indicators@0.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ozakboy.TradeKit.Indicators&version=0.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Ozakboy.TradeKit.Indicators&version=0.1.1
                    
Install as a Cake Tool

Ozakboy.TradeKit.Indicators

Technical indicators for .NET, with a batch implementation and a constant-memory streaming implementation of every indicator.

Zero third-party dependencies. All arithmetic in decimal.

繁體中文說明

Why this exists

Most indicator libraries are built for backtesting: you hand them an array and they hand back an array. That breaks down in a live trading engine, where a new candle closes every minute and recomputing the whole history each time makes cost grow with uptime.

Every indicator here comes in two forms that produce identical results:

  • Batch — Rsi.Calculate(prices, 14) returns a list the same length as the input, index-aligned, with null in the warm-up positions.
  • Streaming — new RsiIndicator(14).Update(price) keeps only the state the recursion actually needs and returns the current value.

Three things follow from that split, and they are the reason to prefer this over rolling your own:

  • Warm-up is explicit. Until an indicator has enough data it returns null — never 0, never an undefined number. A strategy cannot accidentally trade on a garbage reading during start-up.
  • Memory is constant. A streaming indicator's footprint depends on its period, not on how long the process has been running. EMA-family indicators keep a single previous value; window indicators keep a fixed ring buffer.
  • decimal throughout. No double, no float, including the square root behind Bollinger's standard deviation, which is implemented here because the BCL only ships a binary-floating-point one.

Install

dotnet add package Ozakboy.TradeKit.Indicators

Targets net10.0. The library has no package reference of any kind — nothing is added to your dependency tree.

Indicators

Indicator Batch Streaming Output Inputs needed before first value
Simple Moving Average Sma.Calculate SmaIndicator decimal n
Exponential Moving Average Ema.Calculate EmaIndicator decimal n
Relative Strength Index Rsi.Calculate RsiIndicator decimal n + 1
Average True Range Atr.Calculate AtrIndicator decimal n
MACD Macd.Calculate MacdIndicator MacdResult slow + signal - 1
Bollinger Bands BollingerBands.Calculate BollingerBandsIndicator BollingerBandsResult n
Donchian Channel DonchianChannel.Calculate DonchianChannelIndicator DonchianChannelResult n
Average Directional Index Adx.Calculate AdxIndicator AdxResult 2n

RSI needs n + 1 prices because n price changes require n + 1 prices. ADX needs 2n candles because two Wilder smoothing stages stack: the first produces smoothed directional movement, the second averages the resulting DX readings.

Indicators that need highs and lows (ATR, Donchian, ADX) take Candle. The rest take a decimal price, and also accept Candle as a convenience — they use its close.

Batch example

using Ozakboy.TradeKit.Indicators;

decimal[] closes =
[
    44.34m, 44.09m, 44.15m, 43.61m, 44.33m, 44.83m, 45.10m, 45.42m,
    45.84m, 46.08m, 45.89m, 46.03m, 45.61m, 46.28m, 46.28m, 46.00m,
    46.03m, 46.41m, 46.22m, 45.64m, 46.21m, 46.25m, 45.71m, 46.45m,
];

IReadOnlyList<decimal?> rsi = Rsi.Calculate(closes, 14);
IReadOnlyList<decimal?> sma = Sma.Calculate(closes, 10);

// Results are index-aligned with the input; warm-up positions are null.
for (int i = 0; i < closes.Length; i++)
{
    string rsiText = rsi[i] is decimal r ? r.ToString("F2") : "--";
    string smaText = sma[i] is decimal s ? s.ToString("F2") : "--";
    Console.WriteLine($"[{i,2}] close={closes[i],7} sma={smaText,6} rsi={rsiText,6}");
}

Streaming example

using Ozakboy.TradeKit.Indicators;

RsiIndicator rsi = new(14);
AtrIndicator atr = new(14);
MacdIndicator macd = new();

foreach (Candle candle in ReadCandles())
{
    decimal? rsiValue = rsi.Update(candle);
    decimal? atrValue = atr.Update(candle);
    MacdResult? macdValue = macd.Update(candle);

    // Do not act while anything is still warming up; the nulls are deliberate, not omissions.
    if (rsiValue is not decimal r || atrValue is not decimal a || macdValue is not MacdResult m)
    {
        continue;
    }

    Console.WriteLine($"rsi={r:F2} atr={a:F4} macd={m.Macd:F4} hist={m.Histogram:F4}");
}

static IEnumerable<Candle> ReadCandles()
{
    // Replace with your candle source: exchange WebSocket, database, CSV, and so on.
    decimal price = 100m;
    for (int i = 0; i < 200; i++)
    {
        price += (i % 7) - 3;
        yield return Candle.Of(price, price + 1.5m, price - 1.5m, price + 0.4m);
    }
}

Candle.Of(open, high, low, close, volume) builds a candle without a timestamp. Use the full constructor when you want to carry the open time along: new Candle(openTime, open, high, low, close, volume).

Driving indicators uniformly

Every streaming indicator implements IIndicator, so a heterogeneous set can share one warm-up gate:

List<IIndicator> indicators = [rsi, atr, macd];

if (indicators.TrueForAll(indicator => indicator.IsReady))
{
    // Everything is ready; the strategy may run.
}

IIndicator also exposes Name (for example "MACD(12,26,9)"), WarmupPeriod, ProcessedCount and Reset(). Reset() returns an instance to its freshly-constructed state, which is what you want after a gap in the feed or a symbol switch.

Conventions worth knowing

Indicator definitions differ between charting packages. These are the choices this library makes, all of them documented on the types themselves:

  • EMA seeding is configurable via EmaSeedMode. The default, SimpleMovingAverage, seeds from the mean of the first n inputs, matching Wilder and StockCharts. FirstValue seeds from the first input, matching many live charting tools. MACD passes its choice down to all three of its EMAs.
  • Bollinger's standard deviation is the population form (divided by n, not n - 1), as in Bollinger's original work. The sample form would widen the bands by roughly 2.6% at n = 20.
  • Donchian includes the current candle. Breakout rules of the Turtle kind should compare price against the previous bar's channel — read Current before calling Update.
  • ATR's first true range is high - low, since the first candle has no previous close.

Division by zero

These are not theoretical corners; live markets produce them regularly.

  • RSI with an average loss of 0 and a positive average gain returns 100, the standard convention. Average gain of 0 returns 0.
  • RSI on a completely flat window, where both averages are 0, returns 100. The standard formula defines only the two single-sided zero cases, so this boundary has no agreed answer; the library deliberately follows the common convention, where the zero-denominator branch keys on the average loss alone and a flat series therefore falls into it. The cost is that a motionless market reads as extremely overbought, so a strategy that needs to tell the two apart should check whether the price moved at all — for example by watching ATR alongside the RSI.
  • ADX with a smoothed true range of 0, or with +DI + -DI equal to 0, reports 0 — no range, therefore no directionality.

Accuracy

Expected values in the test suite come from three independent layers: formulas quoted verbatim from StockCharts ChartSchool and Wilder's New Concepts in Technical Trading Systems (1978); the published 70.53 anchor for Wilder's own RSI example data; and a reference implementation written separately in another language directly from those formula texts. Each indicator additionally has a case small enough to check on a calculator. The full provenance, including the URLs, is in tests/Ozakboy.TradeKit.Indicators.Tests/GoldenVectorProvenance.cs.

Batch and streaming results are asserted exactly equal for every indicator across a range of periods — no tolerance. They are the same arithmetic in the same order, so a mismatch would be a bug, not a rounding artifact.

DecimalMath.Sqrt uses Newton–Raphson seeded from a power-of-ten table. Measured across roughly 340,000 inputs spanning every decimal scale, it converged within 8 iterations in every case, and returns perfect squares exactly.

License

MIT. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.1 92 9/11/2026
0.1.0 83 9/11/2026