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 (Quote → Bar)
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:
Quote→Bar(and the built-inQuoterecord →Barrecord)IQuote→IBar— custom market-data types now implementIBarPeriodSize→BarInterval(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 Date → Timestamp to clear all warnings. These shims will be removed in a future major version.
New:
BarIntervalnow 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
Bartype (formerlyQuote): ImmutablerecordtypeIBar.Dateproperty: Renamed toIBar.Timestamp.Dateremains as an[Obsolete]alias in v3.x for backward compatibility and will be removed in v3.1 — update consumers toTimestampnow.IBarinterface (formerlyIQuote): Now a reusable (chainable) type- Custom bar types: Must implement the
IReusableinterface IReusableResult: Renamed toIReusableIReusable.Valueproperty: Changed to non-nullable, returnsdouble.NaNinstead ofnull
Indicator return types
- All indicator results: Changed from
sealed classto immutablerecordtypes BasicDatatype: Renamed toTimeValueAtrStopResultvalues: Changed fromdecimaltodoubleUlcerIndexResult.UIproperty: Renamed toUlcerIndexSmaAnalysismodel: Renamed toSmaAnalysisResult
Removed features
GetBaseQuote()indicator: Removed — use theUse(CandlePart)utility insteadSyncSeries()utility: Removed along withSyncTypeenumFind()andFindIndex()utilities: DeprecatedToTupleCollection()utility: DeprecatedToCollection()utility: Deprecated
Other changes
- Indicator method parameters: v2 generic signatures like
GetSma<TQuote>(this IEnumerable<TQuote>)are now interface-typed, likeToSma(this IReadOnlyList<IReusable>)andToFractal(this IReadOnlyList<IBar>). Concrete lists (e.g.List<Bar>) convert automatically; your own generic wrapper methods need aclassconstraint (see Step 4) Use()method:candlePartparameter now required (no default)Use()return type: Now returns chainableTimeValueinstead of tupleNumerixclass: Renamed toNumerical- 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 API | v3 API | Notes |
|---|---|---|
quotes.GetSma(20) | quotes.ToSma(20) | Method prefix changed |
Quote | Bar | Type renamed (OHLCV bar) |
IQuote | IBar | Interface renamed |
PeriodSize | BarInterval | Enum renamed |
IBar.Date | IBar.Timestamp | Property renamed |
quotes.Use() | quotes.Use(CandlePart.Close) | Parameter now required |
result.Value == null | double.IsNaN(result.Value) | Null handling changed |
Numerix | Numerical | Class renamed |
BasicData | TimeValue | Type renamed |
SmaAnalysis | SmaAnalysisResult | Type renamed |
UlcerIndexResult.UI | UlcerIndexResult.UlcerIndex | Property renamed |
SyncSeries() | (removed) | Use manual alignment |
Find() / FindIndex() | LINQ methods | Use .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():
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):
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.
// 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:
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:
// v2 - candlePart defaulted to Close
var quoteParts = quotes.Use();
// v3 - candlePart required
var barParts = bars.Use(CandlePart.Close);Handle new TimeValue return type:
// 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
Numerix→NumericalBasicData→TimeValueSmaAnalysis→SmaAnalysisResultUlcerIndexResult.UI→UlcerIndexResult.UlcerIndex
Step 8: Remove deprecated utilities
Replace or remove calls to:
SyncSeries()- manually align data insteadFind()- use LINQ.FirstOrDefault()FindIndex()- use LINQ.Select((item, index) => ...)with.FirstOrDefault()GetBaseQuote()- useUse(CandlePart)utility instead
Step 9: Update ADXR expectations
If using ADXR, expect slight changes in values and timing:
// 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 nullNew 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:
// 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:
// 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:
- Moving Averages: SMA, EMA, WMA
- Oscillators: RSI, MACD, Stochastic
- Channels: Bollinger Bands, Keltner
Need help?
- Guide and Pro tips - Getting started with v3
- Indicators - Indicator-specific documentation
- GitHub Discussions - Ask questions and share ideas
- GitHub Issues - Report bugs or request features