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 3.0.2 or later before upgrading further.
Why 3.0.2 and not 3.0.0? — defects in the v2 shims
3.0.0 shipped several [Obsolete] shims that did not faithfully reproduce the v2 methods they stand in for. All are corrected in 3.0.2. None of them affect the ToX() methods, only the deprecated GetX() names.
Two returned different values than v2 did. If you ran either on 3.0.0, stored results — and any thresholds tuned against them — need revalidating:
GetPrs()returned the inverted ratio. It passed its two series in the wrong order, returningbase / eval, the reciprocal of the ratio documented for PRS.3.0.2returnseval / base, matchingToPrs()and v2.GetPvo()used the wrong default periods. It declaredfastPeriods: 9, slowPeriods: 12where v2 andToPvo()declare12and26, so an argument-less call silently computed a differently-parameterized PVO. Callers who passed explicit periods were unaffected.
The rest rejected calls that v2 accepted — these failed loudly at build or run time, never with quietly wrong values:
GetKeltner()rejected a fractional multiplier. It declaredint multiplierwhere v2 declaresdouble, soquotes.GetKeltner(20, 2.5, 10)would not compile. Do not round the multiplier to work around it; that silently changes your channel widths. Upgrade to3.0.2, or move toToKeltner(), which always accepted these values.GetStdDevChannels()rejected a null lookback through its tuple overload, declaringint lookbackPeriodswhere v2 declaresint?, whosenullmeans "use the whole series". Do not substitute a fixed lookback; that computes a different channel. The bar overload of the same method still acceptsnull— and its own deprecation message points you there — so the two forms disagreed. Upgrade to3.0.2, or move toToStdDevChannels().GetPrs()with nolookbackPeriodsthrew. The unspecified lookback mapped to0, which validation rejects, so the shim's own default raisedArgumentOutOfRangeException.3.0.2computes with a nullPrsPercent, as v2 did.GetPrs()andGetRoc()tuple overloads silently ignoredsmaPeriods. They still accept and discard the argument — v3 has noPrsSma/RocSmafield — but as of3.0.2their deprecation messages say so.
None of these fixes reach you by dropping the 3.0.2 assembly next to code compiled against 3.0.0 — recompile. GetKeltner() and GetStdDevChannels() changed parameter types, so existing call sites no longer bind to a method that exists. And GetPvo()'s corrected defaults are baked into the caller's IL at compile time, so an argument-less call keeps computing 9/12 until it is rebuilt. Recompiling from source requires no edits.
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: DeprecatedPrsResult.PrsSmaandRocResult.RocSma: Removed. v2 returned a moving average alongside these indicators; v3 has no such field. Chainresults.ToSma(smaPeriods)ontoToPrs()orToRoc()instead.
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, but anIEnumerable<IBar>does not: pass the list you already hold, or type your own wrapper methods asIReadOnlyList<TBar>with aclassconstraint (see Step 4). Call.ToSortedList()only on a source that is truly anIEnumerable(see Step 1) 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 |
|---|---|
quotes.GetSma(20) | quotes.ToSma(20)Method prefix changed |
Quote | BarType renamed (OHLCV bar) |
IQuote | IBarInterface renamed |
PeriodSize | BarIntervalEnum renamed |
IBar.Date | IBar.TimestampProperty renamed |
quotes.Use() | quotes.Use(CandlePart.Close)Parameter now required |
result.Value == null | double.IsNaN(result.Value)Null handling changed |
Numerix | NumericalClass renamed |
BasicData | TimeValueType renamed |
SmaAnalysis | SmaAnalysisResultType renamed |
UlcerIndexResult.UI | UlcerIndexResult.UlcerIndexProperty 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); v3 methods take a time-sorted IReadOnlyList and no longer sort or copy their input. v2 did both on every call, which was redundant work for the many callers who already hold a sorted list. Where an IEnumerable<IBar> no longer compiles, prefer these fixes in order:
- Pass the sorted
Listyou already hold. AList<T>passes straight in with no conversion. - Change your own methods that type bars as
IEnumerableto takeIReadOnlyList<TBar>with aclassconstraint, as in Step 4, so the list flows through unconverted. - Call
.ToSortedList()only on a source that is truly anIEnumerable, such as a LINQ or Entity Framework query. It copies and sorts on each call, so call it once and reuse the result.
List<Bar> bars = LoadSortedBars();
var vwapResults = bars.GetVwap();
var vwapResults = bars.ToVwap(); When the source is a query, sort it once:
IEnumerable<IBar> query = GetBarsFromDatabase();
var vwapResults = query.GetVwap();
var vwapResults = query.ToSortedList().ToVwap(); 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, type the source as IReadOnlyList<TBar> rather than IEnumerable<TBar>, and 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 IEnumerable<TBar> bars,
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 nullBehavior changes since 3.0.0
Some indicators now reject inputs that previously produced invalid output, and one buffer list now matches its Series result. Code that passes values in the ranges below needs a different argument.
Rejected arguments
Each case throws ArgumentOutOfRangeException that names the failing parameter.
| indicator | argument | previously | now |
|---|---|---|---|
| MACD and PVO | signalPeriods of 0 | Accepted; results contained Infinity | Must be greater than 0 |
| STC | cyclePeriods of 0 | Accepted; the stochastic step had no window | Must be greater than 0 |
| EPMA buffer list | lookbackPeriods of 1 | Accepted by the buffer list only | Must be greater than 1 in every style |
| Chandelier Exit stream hub | lookbackPeriods of 1 | Accepted by the hub only | Must be greater than 1 in every style |
STC buffer list
StcList now matches ToStc and StcHub at short cycles. It previously started its stochastic window at the first bar that had a MACD value, so short cycle and period combinations returned null where the Series and hub return values (for example cyclePeriods: 1, fastPeriods: 1, slowPeriods: 2 at indices 3 to 5). Positions that were null there now carry the values ToStc returns.
Buffer list names
The Name of the buffer lists for ALMA, ATR Stop, Bollinger Bands, Chandelier, Dynamic, Fractal, Renko, Rolling Pivots, Stochastic, StochRSI, and T3 now reports the arguments the list was built with. It previously reported the defaults.
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:
// v2 approach (inefficient for incremental updates)
List<Quote> quotes = new();
void OnQuoteReceived(Quote newQuote) // your feed's message handler
{
quotes.Add(newQuote);
var results = quotes.ToSma(20); // Recalculates everything!
// Use results...
}
// v3 BufferList (efficient incremental updates)
SmaList smaList = new(20);
void OnBarReceived(Bar newBar) // your feed's message handler
{
smaList.Add(newBar);
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();
void OnQuoteReceived(Quote newQuote) // your feed's message handler
{
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();
void OnBarReceived(Bar newBar) // your feed's message handler
{
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