Skip to content

Migration from v2 to v3

This guide provides a comprehensive migration path from v2 to v3 of the Stock Indicators library. It includes all technical changes to the public API, syntax changes, and specific examples of deprecated and breaking changes.

Yes, there are minor breaking changes, but ...

Most of the deprecated v2 syntax has been shimmed in library version 3.0 with [Obsolete] code analysis warning flags to aid migrations. These shims are (or will be) removed in version 3.1, so start with any 3.0.x version before upgrading further.

Summary of breaking changes

We've renamed the package from Skender.Stock.Indicators to FacioQuo.Stock.Indicators, so you'll have to search and replace your usings statements.

API method naming

All static time-series API methods: Renamed from GetX() to ToX()

Example: GetSma(...)ToSma(...)

Market-data type renames (QuoteBar)

To align with industry-standard terminology (an OHLCV aggregate is universally a bar; a quote is a bid/ask snapshot), the core market-data types were renamed in v3. The old names remain as deprecated aliases during a migration window, so existing code keeps working — update to the new names as the deprecation warnings guide you:

  • QuoteBar (and the built-in Quote record → Bar record)
  • IQuoteIBar — custom market-data types now implement IBar
  • PeriodSizeBarInterval (aggregation interval enum; member names unchanged)

The old Quote, IQuote, PeriodSize, IReusableResult, and BasicData names remain as warning-level [Obsolete] aliases of the new types, so existing code keeps compiling and running while deprecation warnings guide each rename. Quote/IQuote flow through the new generic API directly, and PeriodSize keeps working via obsolete Aggregate(PeriodSize)/GetPivotPoints(PeriodSize) forwarding overloads. Migrate at your own pace — and finish related member renames such as DateTimestamp to clear all warnings. These shims will be removed in a future major version.

New: BarInterval now has a bidirectional string-code map — interval.ToCode() (e.g. BarInterval.FiveMinutes"5m") and "5m".ToBarInterval() (case-insensitive, with aliases like "5min"/"1day").

Quote/Bar interface details

  • Bar type (formerly Quote): Immutable record type
  • IBar.Date property: Renamed to IBar.Timestamp. Date remains as an [Obsolete] alias in v3.x for backward compatibility and will be removed in v3.1 — update consumers to Timestamp now.
  • IBar interface (formerly IQuote): Now a reusable (chainable) type
  • Custom bar types: Must implement the IReusable interface
  • IReusableResult: Renamed to IReusable
  • IReusable.Value property: Changed to non-nullable, returns double.NaN instead of null

Indicator return types

  • All indicator results: Changed from sealed class to immutable record types
  • BasicData type: Renamed to TimeValue
  • AtrStopResult values: Changed from decimal to double
  • UlcerIndexResult.UI property: Renamed to UlcerIndex
  • SmaAnalysis model: Renamed to SmaAnalysisResult

Removed features

  • GetBaseQuote() indicator: Removed — use the Use(CandlePart) utility instead
  • SyncSeries() utility: Removed along with SyncType enum
  • Find() and FindIndex() utilities: Deprecated
  • ToTupleCollection() utility: Deprecated
  • ToCollection() utility: Deprecated

Other changes

  • Indicator method parameters: v2 generic signatures like GetSma<TQuote>(this IEnumerable<TQuote>) are now interface-typed, like ToSma(this IReadOnlyList<IReusable>) and ToFractal(this IReadOnlyList<IBar>). Concrete lists (e.g. List<Bar>) convert automatically; your own generic wrapper methods need a class constraint (see Step 4)
  • Use() method: candlePart parameter now required (no default)
  • Use() return type: Now returns chainable TimeValue instead of tuple
  • Numerix class: Renamed to Numerical
  • Internal signals: Deprecated for several indicators
  • GetX tuple interfaces: Deprecated
  • Minor ADXR calculation correction to fix a one-period shift in the lookback period

Quick reference table

v2 APIv3 APINotes
quotes.GetSma(20)quotes.ToSma(20)Method prefix changed
QuoteBarType renamed (OHLCV bar)
IQuoteIBarInterface renamed
PeriodSizeBarIntervalEnum renamed
IBar.DateIBar.TimestampProperty renamed
quotes.Use()quotes.Use(CandlePart.Close)Parameter now required
result.Value == nulldouble.IsNaN(result.Value)Null handling changed
NumerixNumericalClass renamed
BasicDataTimeValueType renamed
SmaAnalysisSmaAnalysisResultType renamed
UlcerIndexResult.UIUlcerIndexResult.UlcerIndexProperty renamed
SyncSeries()(removed)Use manual alignment
Find() / FindIndex()LINQ methodsUse .FirstOrDefault() etc.
GetBaseQuote()Use(CandlePart)Use utility instead

Migration steps

This section is for those of you who need a more detailed walk-through for migration.

Step 1: Update method names

Replace all GetX() method calls with ToX():

csharp
var smaResults = quotes.GetSma(20); 
var smaResults = quotes.ToSma(20);  

Step 2: Update IBar property names

Rename Date to Timestamp in all custom bar classes (and implement IBar in place of the obsolete IQuote):

csharp
public class MyBar : IQuote
public class MyBar : IBar
{
    public DateTime Date { get; set; }      
    public DateTime Timestamp { get; set; } 
    public decimal Open { get; set; }
    public decimal High { get; set; }
    public decimal Low { get; set; }
    public decimal Close { get; set; }
    public decimal Volume { get; set; }
}

Step 3: Update custom bar types

If you had a custom quote type, change it to a record type and derive from IBar to replace IQuote.

csharp
// v2
public class MyQuote : IQuote
{
    // properties...

    [JsonIgnore]
    public double Value => (double)Close;
}

// v3 - option 1: use record
public record MyBar : IBar
{
    // properties...
}

// v3 - option 2: implement value-based equality
public class MyBar : IBar, IEquatable<MyBar>
{
    // properties...
    
    // implement value-based equality
    public override bool Equals(object obj) { /* ... */ }
    public override int GetHashCode() { /* ... */ }
}

Step 4: Update generic extension methods

v2 indicator methods were generic, e.g. GetSma<TQuote>(...) where TQuote : IQuote. In v3, they instead accept interface-typed lists — IReadOnlyList<IBar> for bar-based indicators and IReadOnlyList<IReusable> for chainable ones. Calls on concrete collections like List<Bar> work unchanged through covariance.

If you wrote your own generic extension methods over bars, add a class constraint. Covariance (IReadOnlyList<TBar>IReadOnlyList<IBar>) only applies when the compiler knows TBar is a reference type:

csharp
public static double MyVolumeSma<TBar>(
    this IReadOnlyList<TBar> bars,
    int lookbackPeriods)
    where TBar : IBar
    where TBar : class, IBar
{
    IReadOnlyList<SmaResult> results = bars
        .Use(CandlePart.Volume)
        .ToSma(lookbackPeriods);
    // ...
}

Additional interface constraints still compose normally, e.g. where TBar : class, IBar, IMyOther. Since all practical IBar implementations are reference types (record or class), the added constraint does not limit usage.

Step 5: Update Use() method calls

Add explicit candlePart parameter:

csharp
// v2 - candlePart defaulted to Close
var quoteParts = quotes.Use();

// v3 - candlePart required
var barParts = bars.Use(CandlePart.Close);

Handle new TimeValue return type:

csharp
// v2
var (timestamp, value) = quotes.Use(CandlePart.Close);

// v3
IReadOnlyList<TimeValue> barParts = bars.Use(CandlePart.Close);

Step 6: Value is now non-nullable

The chainable IReusable.Value property changed from double? to double, returning double.NaN instead of null. This property exists mainly for internal chaining, so most code is unaffected — named result properties such as SmaResult.Sma remain nullable double?. If you do read .Value directly, replace result.Value == null checks with double.IsNaN(result.Value).

Step 7: Update class references

  • NumerixNumerical
  • BasicDataTimeValue
  • SmaAnalysisSmaAnalysisResult
  • UlcerIndexResult.UIUlcerIndexResult.UlcerIndex

Step 8: Remove deprecated utilities

Replace or remove calls to:

  • SyncSeries() - manually align data instead
  • Find() - use LINQ .FirstOrDefault()
  • FindIndex() - use LINQ .Select((item, index) => ...) with .FirstOrDefault()
  • GetBaseQuote() - use Use(CandlePart) utility instead

Step 9: Update ADXR expectations

If using ADXR, expect slight changes in values and timing:

csharp
// v2 - first ADXR at index 40 (for lookbackPeriods=14)
var adxResults = quotes.GetAdx(14);
var firstAdxr = adxResults[40].Adxr; // not null

// v3 - first ADXR at index 41
var adxResults = bars.ToAdx(14);
var firstAdxr = adxResults[41].Adxr; // not null
// adxResults[40].Adxr is now null

New v3 feature: streaming capabilities

v3 adds incremental and real-time processing. Alongside the v2 Series (batch) style, most indicators now also support two streaming styles:

  • BufferList — self-managed incremental updates, ideal for growing datasets.
  • StreamHub — reactive, observable hubs with cascading updates for live feeds.

See the Indicator styles guide for a full feature comparison and detailed usage. The examples below show the v2→v3 transition for each style.

Optional migrations

These migrations to streaming style indicators are only appropriate if you have advanced incremental or live-streaming uses cases.

Our time series (batch) style indicators are still the best choice for processing of complete historical OHLCV aggregate price datasets, and are functionally unchanged from v2.

Migration examples

From v2 Series to v3 BufferList

If you were building up results incrementally in v2, you can now use BufferList for better performance:

csharp
// v2 approach (inefficient for incremental updates)
List<Quote> quotes = new();
foreach (Quote newQuote in stream)
{
    quotes.Add(newQuote);
    var results = quotes.ToSma(20);  // Recalculates everything!
    // Use results...
}

// v3 BufferList (efficient incremental updates)
SmaList smaList = new(20);
foreach (Bar newBar in stream)
{
    smaList.Add(newBar);
    
    // Note: smaList[^1] throws ArgumentOutOfRangeException if empty
    if (smaList.Count > 0)
    {
        SmaResult latest = smaList[^1];
        // Use latest...
    }
}

From v2 Series to v3 StreamHub

If you need to coordinate multiple indicators with live data:

csharp
// v2 approach (requires maintaining separate lists)
List<Quote> quotes = new();
foreach (Quote newQuote in stream)
{
    quotes.Add(newQuote);
    var smaResults = quotes.ToSma(20);
    var rsiResults = quotes.ToRsi(14);
    var macdResults = quotes.ToMacd();
    // Process results...
}

// v3 StreamHub (coordinated real-time updates)
BarHub barHub = new();
SmaHub smaHub = barHub.ToSmaHub(20);
RsiHub rsiHub = barHub.ToRsiHub(14);
MacdHub macdHub = barHub.ToMacdHub();

foreach (Bar newBar in stream)
{
    barHub.Add(newBar);  // Single update propagates to all observers
    // Access latest results from each hub
}

Streaming documentation

For indicator-specific streaming examples, see the documentation for each indicator. Indicators with streaming support include a "Streaming" section with BufferList and StreamHub examples.

Popular indicators with complete streaming documentation:

Need help?