diff --git a/GitVersion.yml b/GitVersion.yml
index 1eba6ce9..ce39df9f 100644
--- a/GitVersion.yml
+++ b/GitVersion.yml
@@ -1,6 +1,6 @@
+mode: ContinuousDeployment
assembly-versioning-scheme: MajorMinorPatch
assembly-file-versioning-scheme: MajorMinorPatch
-mode: ContinuousDeployment
tag-prefix: '[vV]?'
major-version-bump-message: '\+semver:\s?(breaking|major)'
minor-version-bump-message: '\+semver:\s?(feature|minor)'
@@ -34,4 +34,4 @@ branches:
pre-release-weight: 0
ignore:
- sha: []
\ No newline at end of file
+ sha: []
diff --git a/docs/Progress.md b/docs/Progress.md
index 5847f8dc..793f4881 100644
--- a/docs/Progress.md
+++ b/docs/Progress.md
@@ -1,5 +1,5 @@
# Backlog and done
-|**QT**|**Cht**|Cmnt|Docs|isNew|Valid|
+|**QT**|**Chart**|Cmnt|Docs|isNew|Validation|
|--|:--:|:--:|:--:|:--:|:--:|
|AFIRMA|✔️|||||
\ No newline at end of file
diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md
index e26a8a65..d11d04e8 100644
--- a/docs/indicators/indicators.md
+++ b/docs/indicators/indicators.md
@@ -6,13 +6,47 @@
|**BASIC TRANSFORMS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|--|:--:|:--:|:--:|:--:|:--:|
-|OC2 - Midpoint price|️`.OC2`|`CandlePart.OC2`|`MidPoint`||
-|HL2 - Median Price|️`.HL2`|`CandlePart.HL2`|`MedPrice`||
-|HLC3 - Typical Price|️`.HLC3`|`CandlePart.HLC3`|`TypPrice`||
-|OHL3 - Mean Price|`️.OHL3`|`CandlePart.OHL3`|||
-|OHLC4 - Average Price|`️.OHLC4`|`CandlePart.OHLC4`|`AvgPrice`||
-|HLCC4 - Weighted Price|`️.HLCC4`||`WclPrice`||
+|OC2 - Midpoint price|️`.OC2`|CandlePart.OC2|MidPoint||
+|HL2 - Median Price|️`.HL2`|CandlePart.HL2|MedPrice||
+|HLC3 - Typical Price|️`.HLC3`|CandlePart.HLC3|TypPrice||
+|OHL3 - Mean Price|`️.OHL3`|CandlePart.OHL3`|||
+|OHLC4 - Average Price|`️.OHLC4`|CandlePart.OHLC4|AvgPrice||
+|HLCC4 - Weighted Price|`️.HLCC4`||WclPrice||
|
||||
+|**STATISTICS AND NUMERICAL ANALYSIS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
+|BETA - Beta coefficient|||||
+|CORR - Correlation Coefficient|||||
+|CURVATURE - Rate of Change in Direction or Slope|`Curvature`||||
+|ENTROPY - Measure of Uncertainty or Disorder|`Entropy`||||
+|KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`||||
+|HUBER - Huber Loss|||||
+|MAX - Maximum with exponential decay|`Max`||||
+|MAE - Mean Absolute Error|||||
+|MAPD - Mean Absolute Percentage Deviation|||||
+|MAPE - Mean Absolute Percentage Error|||||
+|MASE - Mean Absolute Scaled Error|||||
+|MDA - Mean Directional Accuracy|||||
+|ME - Mean Error|||||
+|MEDIAN - Middle value|`Median`||||
+|MIN - Minimum with exponential decay|`Min`||||
+|MODE - Most Frequent Value|`Mode`||||
+|MPE - Pean Percentage Error|||||
+|MSE - Mean Squared Error|||||
+|MSLE - Mean Squared Logarithmic Error|||||
+|PERCENTILE - Rank Order|`Percentile`||||
+|RSQUARED - Coefficient of Determination R-Squared|||||
+|RAE - Relative Absolute Error|||||
+|RMSE - Root Mean Squared Error|||||
+|RSE - Relateive Squared Error|||||
+|RMSLE - Root Mean Squared Logarithmic Error|||||
+|SKEW - Skewness, asymmetry of distribution|`Skew`||||
+|SLOPE - Rate of Change, Linear Regression|`Slope`||||
+|SMAPE - Symmetric Mean Absolute Percentage Error|||||
+|STDDEV - Standard Deviation, Measure of Spread|||||
+|THEIL - Theil's U Statistics|||||
+|VARIANCE - Average of Squared Deviations|`Variance`||||
+|ZSCORE - Standardized Score|`Zscore`||||
+|
|||||
|**AVERAGES & TRENDS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|AFIRMA - Autoregressive Finite Impulse Response Moving Average|`Afirma`||||
|ALMA - Arnaud Legoux Moving Average|`Alma`|`✔️`|||
@@ -57,84 +91,83 @@
|ZLEMA - Zero Lag EMA Average|`Zlema`|||`✔️`|
|
||||
|**VOLATILITY INDICATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
-|ADL - Chaikin Accumulation Distribution Line||`GetAdl`|`Ad`||
-|ADOSC - Chaikin Accumulation Distribution Oscillator||`GetChaikinOsc`|`AdOsc`||
-|ATR - Average True Range||`GetAtr`|`Atr`||
+|ADL - Chaikin Accumulation Distribution Line||GetAdl|Ad||
+|ADOSC - Chaikin Accumulation Distribution Oscillator||GetChaikinOsc|AdOsc||
+|ATR - Average True Range||GetAtr|Atr||
|ATRP - Average True Range Percent|||||
-|ATRSTOP - ATR Trailing Stop ||`GetAtrStop`|||
-|BETA - Beta coefficient|||||
-|BBANDS - Bollinger Bands®||`BollingerBands`|||
-|CHAND - Chandelier Exit||`GetChandelier`|||
-|CRSI - Connor RSI||`GetConnorsRsi`|||
+|ATRSTOP - ATR Trailing Stop ||GetAtrStop|||
+|BBANDS - Bollinger Bands®||BollingerBands|||
+|CHAND - Chandelier Exit||GetChandelier|||
+|CRSI - Connor RSI||GetConnorsRsi|||
|CVI - Chaikins Volatility|||||
-|DON - Donchian Channels||`GetDonchian`|||
-|FCB - Fractal Chaos Bands||`GetFcb`|||
+|DON - Donchian Channels||GetDonchian|||
+|FCB - Fractal Chaos Bands||GetFcb|||
|FISHER - Fisher Transform|||||
|HV - Historical Volatility|||||
-|ICH - Ichimoku Cloud||`GetIchimoku`|||
-|KEL - Keltner Channels||`GetKeltner`|||
-|NATR - Normalized Average True Range||`GetAtr`|||
+|ICH - Ichimoku Cloud||GetIchimoku|||
+|KEL - Keltner Channels||GetKeltner|||
+|NATR - Normalized Average True Range||GetAtr|||
|CHN - Price Channel Indicator|||||
-|RSI - Relative Strength Index||`GetRsi`|||
-|SAR - Parabolic Stop and Reverse||`GetParabolicSar`|||
-|SRSI - Stochastic RSI||`GetStochRsi`|||
-|STARC - Starc Bands||`GetStarcBands`|||
+|RSI - Relative Strength Index||GetRsi|||
+|SAR - Parabolic Stop and Reverse||GetParabolicSar|||
+|SRSI - Stochastic RSI||GetStochRsi|||
+|STARC - Starc Bands||GetStarcBands|||
|TR - True Range|||||
-|UI - Ulcer Index||`GetUlcerIndex`|||
-|VSTOP - Volatility Stop||`GetVolatilityStop`|||
+|UI - Ulcer Index||GetUlcerIndex|||
+|VSTOP - Volatility Stop||GetVolatilityStop|||
|
||||
|**MOMENTUM INDICATORS & OSCILLATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|AC - Acceleration Oscillator|||||
-|ADX - Average Directional Movement Index||`GetAdx`|`Adx`||
-|ADXR - Average Directional Movement Index|| `Rating`|`Adxr`||
-|AO - Awesome Oscillator||`GetAwesome`|||
-|APO - Absolute Price Oscillator||`Apo`|||
-|AROON - Aroon oscillator||`GetAroon`|`Aroon`||
-|BOP - Balance of Power||`GetBop`|`Bop`||
-|CCI - Commodity Channel Index||`GetCci`|`Cci`||
+|ADX - Average Directional Movement Index||GetAdx|Adx||
+|ADXR - Average Directional Movement Index||Rating|Adxr||
+|AO - Awesome Oscillator||GetAwesome|||
+|APO - Absolute Price Oscillator||Apo|||
+|AROON - Aroon oscillator||GetAroon|Aroon||
+|BOP - Balance of Power||GetBop|Bop||
+|CCI - Commodity Channel Index||GetCci|Cci||
|CFO - Chande Forcast Oscillator|||||
-|CMO - Chande Momentum Oscillator||`GetCmo`|`Cmo`||
-|CHOP - Choppiness Index||`GetChop`|||
+|CMO - Chande Momentum Oscillator||GetCmo|Cmo||
+|CHOP - Choppiness Index||GetChop|||
|COG - Center of Gravity|||||
|COPPOCK - Coppock Curve|||||
|CTI - Ehler's Correlation Trend Indicator|||||
-|DPO - Detrended Price Oscillator||`GetDpo`|||
-|DMI - Directional Movement Index||`GetDmi`|||
-|EFI - Elder Ray's Force Index||`GetElderRay`|||
+|DPO - Detrended Price Oscillator||GetDpo|||
+|DMI - Directional Movement Index||GetDmi|||
+|EFI - Elder Ray's Force Index||GetElderRay|||
|FOSC - Forecast oscillator||||||
-|GATOR - Gator oscillator||`GetGator`|||
-|HURST - Hurst Exponent||`GetHurst`|||
+|GATOR - Gator oscillator||GetGator|||
+|HURST - Hurst Exponent||GetHurst|||
|KRI - Kairi Relative Index|||||
-|KVO - Klinger Volume Oscillator||`GetKvo`||||
-|MFI - Money Flow Index||`GetMfi`|||
+|KVO - Klinger Volume Oscillator||GetKvo||||
+|MFI - Money Flow Index||GetMfi|||
|MOM - Momentum|||||
|NVI - Negative Volume Index|||||
|PO - Price Oscillator|||||
|PPO - Percentage Price Oscillator|||||
-|PMO - Price Momentum Oscillator||`GetPmo`|||
+|PMO - Price Momentum Oscillator||GetPmo|||
|PVI - Positive Volume Index|||||
|ROC - Rate of Change||GetRoc|||
|RVGI - Relative Vigor Index|||||
-|SMI - Stochastic Momentum Index||`GetSmi`|||
-|STC - Schaff Trend Cycle||`GetStc`|||
-|STOCH - Stochastic Oscillator||`GetStoch`|||
-|TRIX - 1-day ROC of TEMA||`GetTrix`||`trix.Run`|
-|TSI - True Strength Index||`GetTsi`|||
-|UO - Ultimate Oscillator||`GetUltimate`|||
-|WILLR - Larry Williams' %R||GetWillia`msR`|||
-|WGAT - Williams Alligator||`GetAlligator`|||
+|SMI - Stochastic Momentum Index||GetSmi|||
+|STC - Schaff Trend Cycle||GetStc|||
+|STOCH - Stochastic Oscillator||`GetStoch|||
+|TRIX - 1-day ROC of TEMA||GetTrix||trix.Run|
+|TSI - True Strength Index||GetTsi|||
+|UO - Ultimate Oscillator||GetUltimate|||
+|WILLR - Larry Williams' %R||GetWilliamsR|||
+|WGAT - Williams Alligator||GetAlligator|||
|
||||
|**VOLUME INDICATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|AOBV - Archer On-Balance Volume|||||
-|CMF - Chaikin Money Flow||`GetCmf`|||
+|CMF - Chaikin Money Flow||GetCmf|||
|EOM - Ease of Movement|||||
|KVO - Klinger Volume Oscilaltor|||||
-|OBV - On-Balance Volume||`GetObv`|||
-|PRS - Price Relative Strength||`GetPrs`|||
+|OBV - On-Balance Volume||GetObv|||
+|PRS - Price Relative Strength||`GetPrs|||
|PVOL - Price-Volume|||||
-|PVO - Percentage Volume Oscillator||`GetPvo`|||
+|PVO - Percentage Volume Oscillator||GetPvo|||
|PVR - Price Volume Rank|||||
|PVT - Price Volume Trend|||||
|VP - Volume Profile|||||
-|VWAP - Volume Weighted Average Price||`GetVwap`|||
-|VWMA - Volume Weighted Moving Average||`GetVwma`||||
+|VWAP - Volume Weighted Average Price||GetVwap|||
+|VWMA - Volume Weighted Moving Average||GetVwma||||
diff --git a/lib/core/AbstractBarBase.cs b/lib/core/AbstractBarBase.cs
index cd643345..9a4de1cf 100644
--- a/lib/core/AbstractBarBase.cs
+++ b/lib/core/AbstractBarBase.cs
@@ -1,56 +1,76 @@
namespace QuanTAlib;
///
-/// Provides a base implementation for financial indicators in the QuanTAlib library.
-/// This abstract class implements the iTValue interface and defines common properties
-/// and methods used by inheriting indicator types.
+/// Provides a base implementation for financial indicators that work with bar data in the QuanTAlib library.
///
-public abstract class AbstractBarBase : iTValue
-{
+///
+/// This abstract class implements the iTValue interface and defines common properties
+/// and methods used by inheriting indicator types. It handles the basic flow of
+/// receiving bar data, performing calculations, and publishing results.
+///
+public abstract class AbstractBarBase : iTValue {
public DateTime Time { get; set; }
public double Value { get; set; }
public bool IsNew { get; set; }
public bool IsHot { get; set; }
-
public TBar Input { get; set; }
public String Name { get; set; } = "";
public int WarmupPeriod { get; set; }
-
- public TValue Tick => new(Time, Value, IsNew, IsHot); // Stores the current value of indicator
- public event ValueSignal Pub = delegate { }; // Publisher of generated values
-
- protected int _index; //tracking the position of output
+ public TValue Tick => new(Time, Value, IsNew, IsHot);
+ public event ValueSignal Pub = delegate { };
+ protected int _index;
protected double _lastValidValue;
- // other _internal vars defined here
-
- protected AbstractBarBase()
- { //add parameters into constructor
+ protected AbstractBarBase() {
+ // Add parameters into constructor if needed
}
+ ///
+ /// Subscribes to bar data updates.
+ ///
+ /// The source of the bar data.
+ /// The event arguments containing the bar data.
public void Sub(object source, in TBarEventArgs args) => Calc(args.Bar);
- public virtual void Init()
- {
+ ///
+ /// Initializes the indicator's state.
+ ///
+ public virtual void Init() {
_index = 0;
_lastValidValue = 0;
}
- public virtual TValue Calc(TBar input)
- {
+ ///
+ /// Calculates the indicator value based on the input bar.
+ ///
+ /// The input bar data.
+ /// A TValue containing the calculated result.
+ public virtual TValue Calc(TBar input) {
Input = input;
- if (double.IsNaN(input.Close) || double.IsInfinity(input.Close))
- {
+ if (double.IsNaN(input.Close) || double.IsInfinity(input.Close)) {
return Process(new TValue(Time: input.Time, Value: GetLastValid(), IsNew: input.IsNew, IsHot: true));
}
this.Value = Calculation();
return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot));
}
- protected virtual double GetLastValid()
- {
+ ///
+ /// Retrieves the last valid calculated value.
+ ///
+ /// The last valid value of the indicator.
+ protected virtual double GetLastValid() {
return this.Value;
}
+
+ ///
+ /// Manages the state of the indicator based on whether a new bar is being processed.
+ ///
+ /// Indicates whether the current input is a new bar.
protected abstract void ManageState(bool isNew);
+
+ ///
+ /// Performs the actual calculation of the indicator value.
+ ///
+ /// The calculated indicator value.
protected abstract double Calculation();
///
@@ -59,8 +79,7 @@ public abstract class AbstractBarBase : iTValue
///
/// The calculated TValue to process.
/// The processed TValue.
- protected virtual TValue Process(TValue value)
- {
+ protected virtual TValue Process(TValue value) {
this.Time = value.Time;
this.Value = value.Value;
this.IsNew = value.IsNew;
diff --git a/lib/core/abstractBase.cs b/lib/core/abstractBase.cs
index 84dfc144..ee6ea0cb 100644
--- a/lib/core/abstractBase.cs
+++ b/lib/core/abstractBase.cs
@@ -2,29 +2,29 @@ namespace QuanTAlib;
///
/// Provides a base implementation for financial indicators in the QuanTAlib library.
-/// This abstract class implements the iTValue interface and defines common properties
-/// and methods used by inheriting indicator types.
///
+///
+/// This abstract class implements the iTValue interface and defines common properties
+/// and methods used by inheriting indicator types. It handles the basic flow of
+/// receiving data, performing calculations, and publishing results.
+///
public abstract class AbstractBase : iTValue
{
public DateTime Time { get; set; }
public double Value { get; set; }
public bool IsNew { get; set; }
public bool IsHot { get; set; }
-
public TValue Input { get; set; }
public String Name { get; set; } = "";
public int WarmupPeriod { get; set; }
-
- public TValue Tick => new(Time, Value, IsNew, IsHot); // Stores the current value of indicator
- public event ValueSignal Pub = delegate { }; // Publisher of generated values
-
- protected int _index; //tracking the position of output
+ public TValue Tick => new(Time, Value, IsNew, IsHot);
+ public event ValueSignal Pub = delegate { };
+ protected int _index;
protected double _lastValidValue;
- // other _internal vars defined here
protected AbstractBase()
- { //add parameters into constructor
+ {
+ // Add parameters into constructor if needed
}
///
@@ -34,6 +34,9 @@ public abstract class AbstractBase : iTValue
/// The argument containing the new data point.
public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick);
+ ///
+ /// Initializes the indicator's state.
+ ///
public virtual void Init()
{
_index = 0;
@@ -41,11 +44,14 @@ public abstract class AbstractBase : iTValue
}
///
- /// Calculates the indicator value based on the input; calls specific Calculation() method
- /// where implementation is
+ /// Calculates the indicator value based on the input.
///
/// The input value for the calculation.
/// A TValue representing the calculated indicator value.
+ ///
+ /// This method calls the specific Calculation() method where the actual implementation is.
+ /// If the input value is NaN or infinity, it returns the last valid value instead.
+ ///
public virtual TValue Calc(TValue input)
{
Input = input;
@@ -57,11 +63,25 @@ public abstract class AbstractBase : iTValue
return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot));
}
+ ///
+ /// Retrieves the last valid calculated value.
+ ///
+ /// The last valid value of the indicator.
protected virtual double GetLastValid()
{
return this.Value;
}
+
+ ///
+ /// Manages the state of the indicator based on whether a new data point is being processed.
+ ///
+ /// Indicates whether the current input is a new data point.
protected abstract void ManageState(bool isNew);
+
+ ///
+ /// Performs the actual calculation of the indicator value.
+ ///
+ /// The calculated indicator value.
protected abstract double Calculation();
///
diff --git a/lib/core/circularbuffer.cs b/lib/core/circularbuffer.cs
index 695c4da3..04af52a5 100644
--- a/lib/core/circularbuffer.cs
+++ b/lib/core/circularbuffer.cs
@@ -4,55 +4,72 @@ using System.Numerics;
namespace QuanTAlib;
-public class CircularBuffer : IEnumerable
-{
+///
+/// Represents a circular buffer of double values with fixed capacity.
+///
+///
+/// This class provides efficient operations for adding, accessing, and manipulating
+/// a fixed-size buffer of double values. It uses SIMD operations for improved performance
+/// on supported hardware.
+///
+public class CircularBuffer : IEnumerable {
private readonly double[] _buffer;
private int _start = 0;
private int _size = 0;
+ ///
+ /// Gets the maximum number of elements that can be contained in the buffer.
+ ///
public int Capacity { get; }
+
+ ///
+ /// Gets the number of elements currently contained in the buffer.
+ ///
public int Count => _size;
- public CircularBuffer(int capacity)
- {
+ ///
+ /// Initializes a new instance of the CircularBuffer class with the specified capacity.
+ ///
+ /// The maximum number of elements the buffer can hold.
+ public CircularBuffer(int capacity) {
Capacity = capacity;
_buffer = GC.AllocateArray(capacity, pinned: true);
}
+ ///
+ /// Adds an item to the buffer.
+ ///
+ /// The item to add to the buffer.
+ /// Indicates whether the item is a new value or an update to the last added value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Add(double item, bool isNew = true)
- {
- if (_size == 0 || isNew)
- {
- if (_size < Capacity)
- {
+ public void Add(double item, bool isNew = true) {
+ if (_size == 0 || isNew) {
+ if (_size < Capacity) {
_buffer[(_start + _size) % Capacity] = item;
_size++;
- }
- else
- {
+ } else {
_buffer[_start] = item;
_start = (_start + 1) % Capacity;
}
- }
- else
- {
+ } else {
_buffer[(_start + _size - 1) % Capacity] = item;
}
}
- public double this[Index index]
- {
+ ///
+ /// Gets or sets the element at the specified index.
+ ///
+ /// The zero-based index of the element to get or set.
+ /// The element at the specified index.
+ public double this[Index index] {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
+ get {
int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value;
actualIndex = Math.Clamp(actualIndex, 0, _size - 1);
return _buffer[(_start + actualIndex) % Capacity];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- set
- {
+ set {
int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value;
actualIndex = Math.Clamp(actualIndex, 0, _size - 1);
_buffer[(_start + actualIndex) % Capacity] = value;
@@ -60,54 +77,66 @@ public class CircularBuffer : IEnumerable
}
[MethodImpl(MethodImplOptions.NoInlining)]
- private static void ThrowArgumentOutOfRangeException()
- {
+ private static void ThrowArgumentOutOfRangeException() {
throw new ArgumentOutOfRangeException("index", "Index is out of range.");
}
+ ///
+ /// Gets the newest (most recently added) element in the buffer.
+ ///
+ /// The newest element in the buffer.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public double Newest()
- {
+ public double Newest() {
if (_size == 0)
return 0;
return _buffer[(_start + _size - 1) % Capacity];
}
+ ///
+ /// Gets the oldest element in the buffer.
+ ///
+ /// The oldest element in the buffer.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public double Oldest()
- {
+ public double Oldest() {
if (_size == 0)
ThrowInvalidOperationException();
return _buffer[_start];
}
[MethodImpl(MethodImplOptions.NoInlining)]
- private static void ThrowInvalidOperationException()
- {
+ private static void ThrowInvalidOperationException() {
throw new InvalidOperationException("Buffer is empty.");
}
+ ///
+ /// Returns an enumerator that iterates through the buffer.
+ ///
+ /// An enumerator for the buffer.
public Enumerator GetEnumerator() => new(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- public struct Enumerator : IEnumerator
- {
+ ///
+ /// Represents an enumerator for the CircularBuffer.
+ ///
+ public struct Enumerator : IEnumerator {
private readonly CircularBuffer _buffer;
private int _index;
private double _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal Enumerator(CircularBuffer buffer)
- {
+ internal Enumerator(CircularBuffer buffer) {
_buffer = buffer;
_index = -1;
_current = default;
}
+ ///
+ /// Advances the enumerator to the next element of the buffer.
+ ///
+ /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool MoveNext()
- {
+ public bool MoveNext() {
if (_index + 1 >= _buffer._size)
return false;
@@ -116,92 +145,122 @@ public class CircularBuffer : IEnumerable
return true;
}
+ ///
+ /// Gets the element in the buffer at the current position of the enumerator.
+ ///
public double Current => _current;
object IEnumerator.Current => Current;
- public void Reset()
- {
+ ///
+ /// Sets the enumerator to its initial position, which is before the first element in the buffer.
+ ///
+ public void Reset() {
_index = -1;
_current = default;
}
+ ///
+ /// Disposes the enumerator.
+ ///
public void Dispose() { }
}
+ ///
+ /// Copies the elements of the buffer to an array, starting at a particular array index.
+ ///
+ /// The one-dimensional array that is the destination of the elements copied from the buffer.
+ /// The zero-based index in array at which copying begins.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void CopyTo(double[] destination, int destinationIndex)
- {
+ public void CopyTo(double[] destination, int destinationIndex) {
if (_size == 0)
return;
- if (_start + _size <= Capacity)
- {
+ if (_start + _size <= Capacity) {
Array.Copy(_buffer, _start, destination, destinationIndex, _size);
- }
- else
- {
+ } else {
int firstPartLength = Capacity - _start;
Array.Copy(_buffer, _start, destination, destinationIndex, firstPartLength);
Array.Copy(_buffer, 0, destination, destinationIndex + firstPartLength, _size - firstPartLength);
}
}
+ ///
+ /// Returns a read-only span over the contents of the buffer.
+ ///
+ /// A read-only span over the buffer contents.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ReadOnlySpan GetSpan()
- {
+ public ReadOnlySpan GetSpan() {
if (_size == 0)
return ReadOnlySpan.Empty;
- if (_start + _size <= Capacity)
- {
+ if (_start + _size <= Capacity) {
return new ReadOnlySpan(_buffer, _start, _size);
- }
- else
- {
+ } else {
return new ReadOnlySpan(ToArray());
}
}
+ ///
+ /// Gets the internal buffer array.
+ ///
public double[] InternalBuffer => _buffer;
+ ///
+ /// Returns a read-only span over the entire internal buffer.
+ ///
+ /// A read-only span over the entire internal buffer.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan GetInternalSpan() => _buffer.AsSpan();
+ ///
+ /// Removes all elements from the buffer.
+ ///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Clear()
- {
+ public void Clear() {
Array.Clear(_buffer, 0, _buffer.Length);
_start = 0;
_size = 0;
}
+ ///
+ /// Returns the maximum value in the buffer.
+ ///
+ /// The maximum value in the buffer.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public double Max()
- {
+ public double Max() {
if (_size == 0)
ThrowInvalidOperationException();
return MaxSimd();
}
+ ///
+ /// Returns the minimum value in the buffer.
+ ///
+ /// The minimum value in the buffer.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public double Min()
- {
+ public double Min() {
if (_size == 0)
ThrowInvalidOperationException();
return MinSimd();
}
+ ///
+ /// Computes the sum of all values in the buffer.
+ ///
+ /// The sum of all values in the buffer.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public double Sum()
- {
+ public double Sum() {
return SumSimd();
}
+ ///
+ /// Computes the average of all values in the buffer.
+ ///
+ /// The average of all values in the buffer.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public double Average()
- {
+ public double Average() {
if (_size == 0)
ThrowInvalidOperationException();
@@ -209,26 +268,22 @@ public class CircularBuffer : IEnumerable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private double MaxSimd()
- {
+ private double MaxSimd() {
var span = GetSpan();
var vectorSize = Vector.Count;
var maxVector = new Vector(double.MinValue);
int i = 0;
- for (; i <= span.Length - vectorSize; i += vectorSize)
- {
+ for (; i <= span.Length - vectorSize; i += vectorSize) {
maxVector = Vector.Max(maxVector, new Vector(span.Slice(i, vectorSize)));
}
double max = double.MinValue;
- for (int j = 0; j < vectorSize; j++)
- {
+ for (int j = 0; j < vectorSize; j++) {
max = Math.Max(max, maxVector[j]);
}
- for (; i < span.Length; i++)
- {
+ for (; i < span.Length; i++) {
max = Math.Max(max, span[i]);
}
@@ -236,26 +291,22 @@ public class CircularBuffer : IEnumerable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private double MinSimd()
- {
+ private double MinSimd() {
var span = GetSpan();
var vectorSize = Vector.Count;
var minVector = new Vector(double.MaxValue);
int i = 0;
- for (; i <= span.Length - vectorSize; i += vectorSize)
- {
+ for (; i <= span.Length - vectorSize; i += vectorSize) {
minVector = Vector.Min(minVector, new Vector(span.Slice(i, vectorSize)));
}
double min = double.MaxValue;
- for (int j = 0; j < vectorSize; j++)
- {
+ for (int j = 0; j < vectorSize; j++) {
min = Math.Min(min, minVector[j]);
}
- for (; i < span.Length; i++)
- {
+ for (; i < span.Length; i++) {
min = Math.Min(min, span[i]);
}
@@ -263,45 +314,46 @@ public class CircularBuffer : IEnumerable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private double SumSimd()
- {
+ private double SumSimd() {
var span = GetSpan();
var vectorSize = Vector.Count;
var sumVector = Vector.Zero;
int i = 0;
- for (; i <= span.Length - vectorSize; i += vectorSize)
- {
+ for (; i <= span.Length - vectorSize; i += vectorSize) {
sumVector += new Vector(span.Slice(i, vectorSize));
}
double sum = 0;
- for (int j = 0; j < vectorSize; j++)
- {
+ for (int j = 0; j < vectorSize; j++) {
sum += sumVector[j];
}
- for (; i < span.Length; i++)
- {
+ for (; i < span.Length; i++) {
sum += span[i];
}
return sum;
}
- public double[] ToArray()
- {
+ ///
+ /// Copies the buffer elements to a new array.
+ ///
+ /// An array containing copies of the buffer elements.
+ public double[] ToArray() {
double[] array = new double[_size];
CopyTo(array, 0);
return array;
}
- public void ParallelOperation(Func operation)
- {
+ ///
+ /// Performs a parallel operation on the buffer elements.
+ ///
+ /// The operation to perform on each partition of the buffer.
+ public void ParallelOperation(Func operation) {
const int MinimumPartitionSize = 1024;
- if (_size < MinimumPartitionSize)
- {
+ if (_size < MinimumPartitionSize) {
var span = GetSpan();
var array = span.ToArray();
operation(array, 0, array.Length);
@@ -311,8 +363,7 @@ public class CircularBuffer : IEnumerable
int partitionCount = Environment.ProcessorCount;
int partitionSize = _size / partitionCount;
- if (partitionSize < MinimumPartitionSize)
- {
+ if (partitionSize < MinimumPartitionSize) {
partitionCount = Math.Max(1, _size / MinimumPartitionSize);
partitionSize = _size / partitionCount;
}
@@ -320,13 +371,10 @@ public class CircularBuffer : IEnumerable
var buffer = ToArray();
var results = new double[partitionCount];
- Parallel.For(0, partitionCount, i =>
- {
+ Parallel.For(0, partitionCount, i => {
int start = i * partitionSize;
int length = (i == partitionCount - 1) ? _size - start : partitionSize;
results[i] = operation(buffer, start, length);
});
-
}
-
}
\ No newline at end of file
diff --git a/lib/statistics/Curvature.cs b/lib/statistics/Curvature.cs
index 4f3f6df7..838ffeab 100644
--- a/lib/statistics/Curvature.cs
+++ b/lib/statistics/Curvature.cs
@@ -1,128 +1,156 @@
-using System;
-using System.Collections.Generic;
+namespace QuanTAlib;
-namespace QuanTAlib
+///
+/// Calculates the rate of change of the slope over a specified period.
+/// Provides insights into trend acceleration or deceleration.
+///
+public class Curvature : AbstractBase
{
- public class Curvature : AbstractBase
+ private readonly int _period;
+ private readonly Slope _slopeCalculator;
+ private readonly CircularBuffer _slopeBuffer;
+
+ public double? Intercept { get; private set; }
+ public double? StdDev { get; private set; }
+ public double? RSquared { get; private set; }
+ public double? Line { get; private set; }
+
+ ///
+ /// Initializes a new instance of the Curvature class.
+ ///
+ /// The number of data points to consider for calculation.
+ ///
+ /// Thrown when the period is 2 or less.
+ ///
+ public Curvature(int period)
{
- private readonly int _period;
- private readonly Slope _slopeCalculator;
- private readonly CircularBuffer _slopeBuffer;
-
- public double? Intercept { get; private set; }
- public double? StdDev { get; private set; }
- public double? RSquared { get; private set; }
- public double? Line { get; private set; }
-
- public Curvature(int period)
+ if (period <= 2)
{
- if (period <= 2)
+ throw new ArgumentOutOfRangeException(nameof(period), period,
+ "Period must be greater than 2 for Curvature calculation.");
+ }
+ _period = period;
+ WarmupPeriod = period * 2 - 1; // Number of points needed for period number of slopes
+ _slopeCalculator = new Slope(period);
+ _slopeBuffer = new CircularBuffer(period);
+ Name = $"Curvature(period={period})";
+
+ Init();
+ }
+
+ ///
+ /// Initializes a new instance of the Curvature class with a data source.
+ ///
+ /// The source object that publishes data.
+ /// The number of data points to consider.
+ public Curvature(object source, int period) : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ ///
+ /// Resets the Curvature indicator to its initial state.
+ ///
+ public override void Init()
+ {
+ base.Init();
+ _slopeBuffer.Clear();
+ Intercept = null;
+ StdDev = null;
+ RSquared = null;
+ Line = null;
+ }
+
+ ///
+ /// Manages the state of the indicator.
+ ///
+ /// Indicates if the current data point is new.
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _lastValidValue = Input.Value;
+ _index++;
+ }
+ }
+
+ ///
+ /// Performs the curvature calculation.
+ ///
+ ///
+ /// The calculated curvature value. Positive for increasing slope, negative for decreasing.
+ ///
+ ///
+ /// Uses least squares method for optimal calculation. Also computes additional statistics
+ /// such as Intercept, Standard Deviation, R-Squared, and Line value.
+ ///
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+
+ var slopeResult = _slopeCalculator.Calc(Input);
+ _slopeBuffer.Add(slopeResult.Value, Input.IsNew);
+
+ double curvature = 0;
+
+ if (_slopeBuffer.Count < 2)
+ {
+ return curvature; // Not enough points for calculation
+ }
+
+ int count = Math.Min(_slopeBuffer.Count, _period);
+ var slopes = _slopeBuffer.GetSpan().ToArray();
+
+ // Calculate averages
+ double sumX = 0, sumY = 0;
+ for (int i = 0; i < count; i++)
+ {
+ sumX += i + 1;
+ sumY += slopes[i];
+ }
+ double avgX = sumX / count;
+ double avgY = sumY / count;
+
+ // Least squares method
+ double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
+ for (int i = 0; i < count; i++)
+ {
+ double devX = (i + 1) - avgX;
+ double devY = slopes[i] - avgY;
+ sumSqX += devX * devX;
+ sumSqY += devY * devY;
+ sumSqXY += devX * devY;
+ }
+
+ if (sumSqX > 0)
+ {
+ curvature = sumSqXY / sumSqX;
+ Intercept = avgY - (curvature * avgX);
+
+ // Calculate Standard Deviation and R-Squared
+ double stdDevX = Math.Sqrt(sumSqX / count);
+ double stdDevY = Math.Sqrt(sumSqY / count);
+ StdDev = stdDevY;
+
+ if (stdDevX * stdDevY != 0)
{
- throw new ArgumentOutOfRangeException(nameof(period), period,
- "Period must be greater than 2 for Curvature calculation.");
+ double r = sumSqXY / (stdDevX * stdDevY) / count;
+ RSquared = r * r;
}
- _period = period;
- WarmupPeriod = period * 2 - 1; // We need this many points to get period number of slopes
- _slopeCalculator = new Slope(period);
- _slopeBuffer = new CircularBuffer(period);
- Name = $"Curvature(period={period})";
- Init();
+ // Calculate last Line value (y = mx + b)
+ Line = (curvature * count) + Intercept;
}
-
- public Curvature(object source, int period) : this(period)
+ else
{
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
- }
-
- public override void Init()
- {
- base.Init();
- _slopeBuffer.Clear();
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
- _lastValidValue = Input.Value;
- _index++;
- }
- }
-
- protected override double Calculation()
- {
- ManageState(Input.IsNew);
-
- // Calculate slope
- var slopeResult = _slopeCalculator.Calc(Input);
- _slopeBuffer.Add(slopeResult.Value, Input.IsNew);
-
- double curvature = 0;
-
- if (_slopeBuffer.Count < 2)
- {
- return curvature; // Return 0 when there are fewer than 2 slope points
- }
-
- int count = Math.Min(_slopeBuffer.Count, _period);
- var slopes = _slopeBuffer.GetSpan().ToArray();
-
- // Calculate averages
- double sumX = 0, sumY = 0;
- for (int i = 0; i < count; i++)
- {
- sumX += i + 1;
- sumY += slopes[i];
- }
- double avgX = sumX / count;
- double avgY = sumY / count;
-
- // Least squares method
- double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
- for (int i = 0; i < count; i++)
- {
- double devX = (i + 1) - avgX;
- double devY = slopes[i] - avgY;
- sumSqX += devX * devX;
- sumSqY += devY * devY;
- sumSqXY += devX * devY;
- }
-
- if (sumSqX > 0)
- {
- curvature = sumSqXY / sumSqX;
- Intercept = avgY - (curvature * avgX);
-
- // Calculate Standard Deviation and R-Squared
- double stdDevX = Math.Sqrt(sumSqX / count);
- double stdDevY = Math.Sqrt(sumSqY / count);
- StdDev = stdDevY;
-
- if (stdDevX * stdDevY != 0)
- {
- double r = sumSqXY / (stdDevX * stdDevY) / count;
- RSquared = r * r;
- }
-
- // Calculate last Line value (y = mx + b)
- Line = (curvature * count) + Intercept;
- }
- else
- {
- Intercept = null;
- StdDev = null;
- RSquared = null;
- Line = null;
- }
-
- IsHot = _slopeBuffer.Count == _period;
- return curvature;
- }
+ IsHot = _slopeBuffer.Count == _period;
+ return curvature;
}
}
\ No newline at end of file
diff --git a/lib/statistics/Entropy.cs b/lib/statistics/Entropy.cs
index a5f561bb..df59cb93 100644
--- a/lib/statistics/Entropy.cs
+++ b/lib/statistics/Entropy.cs
@@ -1,19 +1,27 @@
namespace QuanTAlib;
-using System;
-using System.Linq;
-
-// Shannon's Entropy calculation
+///
+/// Measures the unpredictability of data using Shannon's Entropy.
+/// Provides insights into the randomness or information content of the time series.
+///
public class Entropy : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _buffer;
+ ///
+ /// Initializes a new instance of the Entropy class.
+ ///
+ /// The number of data points to consider for calculation.
+ ///
+ /// Thrown when the period is less than 2.
+ ///
public Entropy(int period) : base()
{
if (period < 2)
{
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for entropy calculation.");
+ throw new ArgumentOutOfRangeException(nameof(period),
+ "Period must be greater than or equal to 2 for entropy calculation.");
}
Period = period;
WarmupPeriod = 2;
@@ -22,18 +30,30 @@ public class Entropy : AbstractBase
Init();
}
+ ///
+ /// Initializes a new instance of the Entropy class with a data source.
+ ///
+ /// The source object that publishes data.
+ /// The number of data points to consider.
public Entropy(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
+ ///
+ /// Resets the Entropy indicator to its initial state.
+ ///
public override void Init()
{
base.Init();
_buffer.Clear();
}
+ ///
+ /// Manages the state of the indicator.
+ ///
+ /// Indicates if the current data point is new.
protected override void ManageState(bool isNew)
{
if (isNew)
@@ -43,6 +63,17 @@ public class Entropy : AbstractBase
}
}
+ ///
+ /// Performs the entropy calculation.
+ ///
+ ///
+ /// The calculated entropy value, normalized between 0 and 1.
+ /// 1 indicates maximum randomness, 0 indicates perfect predictability.
+ ///
+ ///
+ /// Uses Shannon's Entropy formula and normalizes the result based on the
+ /// number of unique values in the current period.
+ ///
protected override double Calculation()
{
ManageState(Input.IsNew);
@@ -70,9 +101,11 @@ public class Entropy : AbstractBase
double maxEntropy = Math.Log2(uniqueValueCount);
entropy = entropy == 0 ? 1 : entropy / maxEntropy;
-
}
- else { entropy = 1; }
+ else
+ {
+ entropy = 1; // Default to maximum entropy when insufficient data
+ }
IsHot = _buffer.Count >= Period;
return entropy;
diff --git a/lib/statistics/Kurtosis.cs b/lib/statistics/Kurtosis.cs
index 895a175a..af13ac0b 100644
--- a/lib/statistics/Kurtosis.cs
+++ b/lib/statistics/Kurtosis.cs
@@ -1,16 +1,27 @@
namespace QuanTAlib;
-// Excess kurtosis calculated with Sheskin Algorithm
+///
+/// Calculates excess kurtosis using the Sheskin Algorithm.
+/// Measures the "tailedness" of the probability distribution of a real-valued random variable.
+///
public class Kurtosis : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _buffer;
+ ///
+ /// Initializes a new instance of the Kurtosis class.
+ ///
+ /// The number of data points to consider for calculation.
+ ///
+ /// Thrown when the period is less than 4.
+ ///
public Kurtosis(int period) : base()
{
if (period < 4)
{
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 4 for kurtosis calculation.");
+ throw new ArgumentOutOfRangeException(nameof(period),
+ "Period must be greater than or equal to 4 for kurtosis calculation.");
}
Period = period;
WarmupPeriod = Period - 1;
@@ -19,18 +30,30 @@ public class Kurtosis : AbstractBase
Init();
}
+ ///
+ /// Initializes a new instance of the Kurtosis class with a data source.
+ ///
+ /// The source object that publishes data.
+ /// The number of data points to consider.
public Kurtosis(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
+ ///
+ /// Resets the Kurtosis indicator to its initial state.
+ ///
public override void Init()
{
base.Init();
_buffer.Clear();
}
+ ///
+ /// Manages the state of the indicator.
+ ///
+ /// Indicates if the current data point is new.
protected override void ManageState(bool isNew)
{
if (isNew)
@@ -40,6 +63,17 @@ public class Kurtosis : AbstractBase
}
}
+ ///
+ /// Performs the kurtosis calculation.
+ ///
+ ///
+ /// The calculated excess kurtosis. Positive for heavy-tailed distributions,
+ /// negative for light-tailed distributions.
+ ///
+ ///
+ /// Uses the Sheskin Algorithm for kurtosis calculation.
+ /// Requires at least 4 data points for a valid calculation.
+ ///
protected override double Calculation()
{
ManageState(Input.IsNew);
@@ -65,7 +99,7 @@ public class Kurtosis : AbstractBase
double variance = s2 / (n - 1);
- // Using the Sheskin Algorithm for kurtosis
+ // Sheskin Algorithm
kurtosis = (n * (n + 1) * s4) / (variance * variance * (n - 3) * (n - 1) * (n - 2))
- (3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)));
}
diff --git a/lib/statistics/Max.cs b/lib/statistics/Max.cs
index 4eb63fcf..b96fa31c 100644
--- a/lib/statistics/Max.cs
+++ b/lib/statistics/Max.cs
@@ -1,80 +1,114 @@
-using System;
+namespace QuanTAlib;
-namespace QuanTAlib
+///
+/// Calculates the maximum value over a specified period, with an optional decay factor.
+/// Useful for tracking the highest point in a time series with the ability to gradually forget old peaks.
+///
+public class Max : AbstractBase
{
- public class Max : AbstractBase
+ private readonly int Period;
+ private readonly CircularBuffer _buffer;
+ private readonly double _halfLife;
+ private double _currentMax, _p_currentMax;
+ private int _timeSinceNewMax, _p_timeSinceNewMax;
+
+ ///
+ /// Initializes a new instance of the Max class.
+ ///
+ /// The number of data points to consider. Must be at least 1.
+ /// Half-life decay factor. Set to 0 for no decay, higher for faster forgetting. Default is 0.
+ ///
+ /// Thrown when the period is less than 1 or decay is negative.
+ ///
+ public Max(int period, double decay = 0) : base()
{
- private readonly int Period;
- private readonly CircularBuffer _buffer;
- private readonly double _halfLife;
- private double _currentMax, _p_currentMax;
- private int _timeSinceNewMax, _p_timeSinceNewMax;
-
- public Max(int period, double decay = 0) : base()
+ if (period < 1)
{
- if (period < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
- }
- if (decay < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
- }
- Period = period;
- WarmupPeriod = 0;
- _buffer = new CircularBuffer(period);
- _halfLife = decay * 0.1;
- Name = $"Max(period={period}, halfLife={decay:F2})";
- Init();
+ throw new ArgumentOutOfRangeException(nameof(period),
+ "Period must be greater than or equal to 1.");
}
-
- public Max(object source, int period, double decay = 0) : this(period, decay)
+ if (decay < 0)
{
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ throw new ArgumentOutOfRangeException(nameof(decay),
+ "Half-life must be non-negative.");
}
+ Period = period;
+ WarmupPeriod = 0;
+ _buffer = new CircularBuffer(period);
+ _halfLife = decay * 0.1;
+ Name = $"Max(period={period}, halfLife={decay:F2})";
+ Init();
+ }
- public override void Init()
+ ///
+ /// Initializes a new instance of the Max class with a data source.
+ ///
+ /// The source object that publishes data.
+ /// The number of data points to consider.
+ /// Half-life decay factor. Default is 0.
+ public Max(object source, int period, double decay = 0) : this(period, decay)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ ///
+ /// Resets the Max indicator to its initial state.
+ ///
+ public override void Init()
+ {
+ base.Init();
+ _currentMax = double.MinValue;
+ _timeSinceNewMax = 0;
+ }
+
+ ///
+ /// Manages the state of the indicator.
+ ///
+ /// Indicates if the current data point is new.
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
{
- base.Init();
- _currentMax = double.MinValue;
+ _p_currentMax = _currentMax;
+ _lastValidValue = Input.Value;
+ _index++;
+ _timeSinceNewMax++;
+ _p_timeSinceNewMax = _timeSinceNewMax;
+ }
+ else
+ {
+ _currentMax = _p_currentMax;
+ _timeSinceNewMax = _p_timeSinceNewMax;
+ }
+ }
+
+ ///
+ /// Performs the max calculation.
+ ///
+ ///
+ /// The current maximum value, potentially adjusted by the decay factor.
+ ///
+ ///
+ /// Uses a decay factor to gradually forget old peaks. The max value is always
+ /// capped by the highest value in the current period.
+ ///
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+ _buffer.Add(Input.Value, Input.IsNew);
+
+ if (Input.Value >= _currentMax)
+ {
+ _currentMax = Input.Value;
_timeSinceNewMax = 0;
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
- _p_currentMax = _currentMax;
- _lastValidValue = Input.Value;
- _index++;
- _timeSinceNewMax++;
- _p_timeSinceNewMax = _timeSinceNewMax;
- }
- else
- {
- _currentMax = _p_currentMax;
- _timeSinceNewMax = _p_timeSinceNewMax;
- }
- }
+ double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period);
+ _currentMax = _currentMax - decayRate * (_currentMax - _buffer.Average());
+ _currentMax = Math.Min(_currentMax, _buffer.Max());
- protected override double Calculation()
- {
- ManageState(Input.IsNew);
- _buffer.Add(Input.Value, Input.IsNew);
-
- if (Input.Value >= _currentMax)
- {
- _currentMax = Input.Value;
- _timeSinceNewMax = 0;
- }
-
- double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period);
- _currentMax = _currentMax - decayRate * (_currentMax - _buffer.Average());
- _currentMax = Math.Min(_currentMax, _buffer.Max());
-
- IsHot = true;
- return _currentMax;
- }
+ IsHot = true;
+ return _currentMax;
}
-}
\ No newline at end of file
+}
diff --git a/lib/statistics/Median.cs b/lib/statistics/Median.cs
index a1a95783..666dc6a8 100644
--- a/lib/statistics/Median.cs
+++ b/lib/statistics/Median.cs
@@ -1,69 +1,99 @@
-using System;
-using System.Linq;
+namespace QuanTAlib;
-namespace QuanTAlib
+///
+/// Calculates the median value over a specified period.
+/// Provides a measure of central tendency that is robust to outliers.
+///
+public class Median : AbstractBase
{
- public class Median : AbstractBase
+ private readonly int Period;
+ private readonly CircularBuffer _buffer;
+
+ ///
+ /// Initializes a new instance of the Median class.
+ ///
+ /// The number of data points to consider. Must be at least 1.
+ ///
+ /// Thrown when the period is less than 1.
+ ///
+ public Median(int period) : base()
{
- private readonly int Period;
- private readonly CircularBuffer _buffer;
-
- public Median(int period) : base()
+ if (period < 1)
{
- if (period < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
- }
- Period = period;
- WarmupPeriod = period;
- _buffer = new CircularBuffer(period);
- Name = $"Median(period={period})";
- Init();
+ throw new ArgumentOutOfRangeException(nameof(period),
+ "Period must be greater than or equal to 1.");
}
+ Period = period;
+ WarmupPeriod = period;
+ _buffer = new CircularBuffer(period);
+ Name = $"Median(period={period})";
+ Init();
+ }
- public Median(object source, int period) : this(period)
+ ///
+ /// Initializes a new instance of the Median class with a data source.
+ ///
+ /// The source object that publishes data.
+ /// The number of data points to consider.
+ public Median(object source, int period) : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ ///
+ /// Manages the state of the indicator.
+ ///
+ /// Indicates if the current data point is new.
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
{
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ _lastValidValue = Input.Value;
+ _index++;
}
+ }
- protected override void ManageState(bool isNew)
+ ///
+ /// Performs the median calculation.
+ ///
+ ///
+ /// The current median value of the dataset.
+ ///
+ ///
+ /// Uses a sorting approach to find the median. If there's not enough data,
+ /// it uses the average as a temporary measure.
+ ///
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+ _buffer.Add(Input.Value, Input.IsNew);
+
+ double median;
+ if (_index >= Period)
{
- if (isNew)
+ var sortedValues = _buffer.GetSpan().ToArray();
+ Array.Sort(sortedValues);
+ int middleIndex = sortedValues.Length / 2;
+
+ if (sortedValues.Length % 2 == 0)
{
- _lastValidValue = Input.Value;
- _index++;
- }
- }
-
- protected override double Calculation()
- {
- ManageState(Input.IsNew);
- _buffer.Add(Input.Value, Input.IsNew);
-
- double median;
- if (_index >= Period)
- {
- var sortedValues = _buffer.GetSpan().ToArray();
- Array.Sort(sortedValues);
- int middleIndex = sortedValues.Length / 2;
-
- if (sortedValues.Length % 2 == 0)
- {
- median = (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0;
- }
- else
- {
- median = sortedValues[middleIndex];
- }
+ // Even number of values: average of two middle values
+ median = (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0;
}
else
{
- median = _buffer.Average(); // Use average until we have enough data points
+ // Odd number of values: middle value
+ median = sortedValues[middleIndex];
}
-
- IsHot = _index >= WarmupPeriod;
- return median;
}
+ else
+ {
+ // Not enough data, use average as temporary measure
+ median = _buffer.Average();
+ }
+
+ IsHot = _index >= WarmupPeriod;
+ return median;
}
-}
\ No newline at end of file
+}
diff --git a/lib/statistics/Min.cs b/lib/statistics/Min.cs
index a6b30278..a525d966 100644
--- a/lib/statistics/Min.cs
+++ b/lib/statistics/Min.cs
@@ -1,80 +1,106 @@
-using System;
+namespace QuanTAlib;
-namespace QuanTAlib
-{
- public class Min : AbstractBase
- {
- private readonly int Period;
- private readonly CircularBuffer _buffer;
- private readonly double _halfLife;
- private double _currentMin, _p_currentMin;
- private int _timeSinceNewMin, _p_timeSinceNewMin;
+///
+/// Represents a minimum value calculator with optional decay over a specified period.
+/// This class calculates the minimum value within a given period, with the ability to
+/// apply a decay factor to give more weight to recent values.
+///
+///
+/// The Min class uses a circular buffer to store values and calculates the minimum
+/// efficiently. It also implements a decay mechanism to adjust the minimum value over
+/// time, allowing for a more responsive indicator in changing market conditions.
+///
+public class Min : AbstractBase {
+ private readonly int Period;
+ private readonly CircularBuffer _buffer;
+ private readonly double _halfLife;
+ private double _currentMin, _p_currentMin;
+ private int _timeSinceNewMin, _p_timeSinceNewMin;
- public Min(int period, double decay = 0) : base()
- {
- if (period < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
- }
- if (decay < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
- }
- Period = period;
- WarmupPeriod = 0;
- _buffer = new CircularBuffer(period);
- _halfLife = decay * 0.1;
- Name = $"Min(period={period}, halfLife={decay:F2})";
- Init();
+ ///
+ /// Initializes a new instance of the Min class with the specified period and decay.
+ ///
+ /// The period over which to calculate the minimum value.
+ /// The decay factor to apply to older values (default is 0).
+ ///
+ /// Thrown when period is less than 1 or decay is negative.
+ ///
+ public Min(int period, double decay = 0) : base() {
+ if (period < 1) {
+ throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
-
- public Min(object source, int period, double decay = 0) : this(period, decay)
- {
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ if (decay < 0) {
+ throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
}
+ Period = period;
+ WarmupPeriod = 0;
+ _buffer = new CircularBuffer(period);
+ _halfLife = decay * 0.1;
+ Name = $"Min(period={period}, halfLife={decay:F2})";
+ Init();
+ }
- public override void Init()
- {
- base.Init();
- _currentMin = double.MaxValue;
+ ///
+ /// Initializes a new instance of the Min class with the specified source, period, and decay.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the minimum value.
+ /// The decay factor to apply to older values (default is 0).
+ public Min(object source, int period, double decay = 0) : this(period, decay) {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ ///
+ /// Initializes the Min instance by setting initial values.
+ ///
+ public override void Init() {
+ base.Init();
+ _currentMin = double.MaxValue;
+ _timeSinceNewMin = 0;
+ }
+
+ ///
+ /// Manages the state of the Min instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
+ _p_currentMin = _currentMin;
+ _lastValidValue = Input.Value;
+ _index++;
+ _timeSinceNewMin++;
+ _p_timeSinceNewMin = _timeSinceNewMin;
+ } else {
+ _currentMin = _p_currentMin;
+ _timeSinceNewMin = _p_timeSinceNewMin;
+ }
+ }
+
+ ///
+ /// Performs the minimum value calculation with decay.
+ ///
+ /// The calculated minimum value for the current period.
+ ///
+ /// This method updates the current minimum value based on the input, applies the decay
+ /// factor, and ensures the result is not lower than the actual minimum in the buffer.
+ /// The decay rate is calculated using an exponential function based on the time since
+ /// the last new minimum and the specified half-life.
+ ///
+ protected override double Calculation() {
+ ManageState(Input.IsNew);
+ _buffer.Add(Input.Value, Input.IsNew);
+
+ if (Input.Value <= _currentMin) {
+ _currentMin = Input.Value;
_timeSinceNewMin = 0;
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
- _p_currentMin = _currentMin;
- _lastValidValue = Input.Value;
- _index++;
- _timeSinceNewMin++;
- _p_timeSinceNewMin = _timeSinceNewMin;
- }
- else
- {
- _currentMin = _p_currentMin;
- _timeSinceNewMin = _p_timeSinceNewMin;
- }
- }
+ double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
+ _currentMin = _currentMin + decayRate * (_buffer.Average() - _currentMin);
+ _currentMin = Math.Max(_currentMin, _buffer.Min());
- protected override double Calculation()
- {
- ManageState(Input.IsNew);
- _buffer.Add(Input.Value, Input.IsNew);
-
- if (Input.Value <= _currentMin)
- {
- _currentMin = Input.Value;
- _timeSinceNewMin = 0;
- }
-
- double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
- _currentMin = _currentMin + decayRate * (_buffer.Average() - _currentMin);
- _currentMin = Math.Max(_currentMin, _buffer.Min());
-
- IsHot = true;
- return _currentMin;
- }
+ IsHot = true;
+ return _currentMin;
}
-}
\ No newline at end of file
+}
diff --git a/lib/statistics/Mode.cs b/lib/statistics/Mode.cs
index c5f23e52..bcd3ab57 100644
--- a/lib/statistics/Mode.cs
+++ b/lib/statistics/Mode.cs
@@ -1,14 +1,27 @@
namespace QuanTAlib;
-public class Mode : AbstractBase
-{
+///
+/// Represents a mode calculator that determines the most frequent value in a specified period.
+/// If multiple values have the same highest frequency, it returns their average.
+///
+///
+/// The Mode class uses a circular buffer to store values and calculates the mode
+/// efficiently. Before the specified period is reached, it returns the average of
+/// the available values as an approximation.
+///
+public class Mode : AbstractBase {
private readonly int Period;
private readonly CircularBuffer _buffer;
- public Mode(int period) : base()
- {
- if (period < 1)
- {
+ ///
+ /// Initializes a new instance of the Mode class with the specified period.
+ ///
+ /// The period over which to calculate the mode.
+ ///
+ /// Thrown when period is less than 1.
+ ///
+ public Mode(int period) : base() {
+ if (period < 1) {
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
Period = period;
@@ -18,29 +31,45 @@ public class Mode : AbstractBase
Init();
}
- public Mode(object source, int period) : this(period)
- {
+ ///
+ /// Initializes a new instance of the Mode class with the specified source and period.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the mode.
+ public Mode(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
+ ///
+ /// Manages the state of the Mode instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
- protected override double Calculation()
- {
+ ///
+ /// Performs the mode calculation for the current period.
+ ///
+ ///
+ /// The calculated mode (most frequent value) for the current period.
+ /// If multiple values have the same highest frequency, returns their average.
+ ///
+ ///
+ /// Before the specified period is reached, this method returns the average of
+ /// the available values as an approximation of the mode. Once the period is
+ /// reached, it calculates the true mode by grouping and counting the values.
+ ///
+ protected override double Calculation() {
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double mode;
- if (_index >= Period)
- {
+ if (_index >= Period) {
var values = _buffer.GetSpan().ToArray();
var groupedValues = values.GroupBy(v => v)
.OrderByDescending(g => g.Count())
@@ -53,9 +82,7 @@ public class Mode : AbstractBase
.ToList();
mode = modes.Average(); // If there are multiple modes, we return their average
- }
- else
- {
+ } else {
mode = _buffer.Average(); // Use average until we have enough data points
}
diff --git a/lib/statistics/Percentile.cs b/lib/statistics/Percentile.cs
index 5377e840..a985a637 100644
--- a/lib/statistics/Percentile.cs
+++ b/lib/statistics/Percentile.cs
@@ -1,22 +1,33 @@
namespace QuanTAlib;
-using System;
-using System.Linq;
-
-public class Percentile : AbstractBase
-{
+///
+/// Represents a percentile calculator that determines the value at a specified percentile
+/// in a given period of data points.
+///
+///
+/// The Percentile class uses a circular buffer to store values and calculates the
+/// percentile efficiently. It uses linear interpolation when the percentile falls
+/// between two data points. Before the specified period is reached, it returns the
+/// average of the available values as an approximation.
+///
+public class Percentile : AbstractBase {
private readonly int Period;
private readonly double Percent;
private readonly CircularBuffer _buffer;
- public Percentile(int period, double percent) : base()
- {
- if (period < 2)
- {
+ ///
+ /// Initializes a new instance of the Percentile class with the specified period and percentile.
+ ///
+ /// The period over which to calculate the percentile.
+ /// The percentile to calculate (between 0 and 100).
+ ///
+ /// Thrown when period is less than 2 or percent is not between 0 and 100.
+ ///
+ public Percentile(int period, double percent) : base() {
+ if (period < 2) {
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for percentile calculation.");
}
- if (percent < 0 || percent > 100)
- {
+ if (percent < 0 || percent > 100) {
throw new ArgumentOutOfRangeException(nameof(percent), "Percent must be between 0 and 100.");
}
Period = period;
@@ -27,35 +38,54 @@ public class Percentile : AbstractBase
Init();
}
- public Percentile(object source, int period, double percent) : this(period, percent)
- {
+ ///
+ /// Initializes a new instance of the Percentile class with the specified source, period, and percentile.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the percentile.
+ /// The percentile to calculate (between 0 and 100).
+ public Percentile(object source, int period, double percent) : this(period, percent) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
- public override void Init()
- {
+ ///
+ /// Initializes the Percentile instance by clearing the buffer.
+ ///
+ public override void Init() {
base.Init();
_buffer.Clear();
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
+ ///
+ /// Manages the state of the Percentile instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
-protected override double Calculation()
- {
+ ///
+ /// Performs the percentile calculation for the current period.
+ ///
+ ///
+ /// The calculated percentile value for the current period.
+ ///
+ ///
+ /// This method uses linear interpolation when the percentile falls between two data points.
+ /// Before the specified period is reached, it returns the average of the available values
+ /// as an approximation. Once the period is reached, it calculates the true percentile by
+ /// sorting the values and interpolating as necessary.
+ ///
+ protected override double Calculation() {
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double result;
- if (_buffer.Count >= Period)
- {
+ if (_buffer.Count >= Period) {
var values = _buffer.GetSpan().ToArray();
Array.Sort(values);
@@ -63,21 +93,16 @@ protected override double Calculation()
int lowerIndex = (int)Math.Floor(position);
int upperIndex = (int)Math.Ceiling(position);
- if (lowerIndex == upperIndex)
- {
+ if (lowerIndex == upperIndex) {
result = values[lowerIndex];
- }
- else
- {
+ } else {
// Interpolate between the two nearest values
double lowerValue = values[lowerIndex];
double upperValue = values[upperIndex];
double fraction = position - lowerIndex;
result = lowerValue + (upperValue - lowerValue) * fraction;
}
- }
- else
- {
+ } else {
// Use average for insufficient data, like the Median class
result = _buffer.Average();
}
diff --git a/lib/statistics/Skew.cs b/lib/statistics/Skew.cs
index f70cec0a..0d32f72a 100644
--- a/lib/statistics/Skew.cs
+++ b/lib/statistics/Skew.cs
@@ -1,17 +1,28 @@
namespace QuanTAlib;
-using System;
-using System.Linq;
-
-public class Skew : AbstractBase
-{
+///
+/// Represents a skewness calculator that measures the asymmetry of the probability
+/// distribution of a real-valued random variable about its mean.
+///
+///
+/// The Skew class uses a circular buffer to store values and calculates the skewness
+/// efficiently. It uses the adjusted Fisher-Pearson standardized moment coefficient
+/// for sample skewness calculation. A minimum of 3 data points is required for the
+/// calculation.
+///
+public class Skew : AbstractBase {
private readonly int Period;
private readonly CircularBuffer _buffer;
- public Skew(int period) : base()
- {
- if (period < 3)
- {
+ ///
+ /// Initializes a new instance of the Skew class with the specified period.
+ ///
+ /// The period over which to calculate the skewness.
+ ///
+ /// Thrown when period is less than 3.
+ ///
+ public Skew(int period) : base() {
+ if (period < 3) {
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 3 for skewness calculation.");
}
Period = period;
@@ -21,36 +32,54 @@ public class Skew : AbstractBase
Init();
}
- public Skew(object source, int period) : this(period)
- {
+ ///
+ /// Initializes a new instance of the Skew class with the specified source and period.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the skewness.
+ public Skew(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
- public override void Init()
- {
+ ///
+ /// Initializes the Skew instance by clearing the buffer.
+ ///
+ public override void Init() {
base.Init();
_buffer.Clear();
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
+ ///
+ /// Manages the state of the Skew instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
- protected override double Calculation()
- {
+ ///
+ /// Performs the skewness calculation for the current period.
+ ///
+ ///
+ /// The calculated skewness value for the current period.
+ ///
+ ///
+ /// This method uses the adjusted Fisher-Pearson standardized moment coefficient
+ /// to calculate the sample skewness. It requires at least 3 data points for the
+ /// calculation. If there are fewer than 3 data points, or if the standard
+ /// deviation is zero, the method returns 0.
+ ///
+ protected override double Calculation() {
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double skew = 0;
- if (_buffer.Count >= 3) // We need at least 3 data points for skewness
- {
+ if (_buffer.Count >= 3) { // We need at least 3 data points for skewness
var values = _buffer.GetSpan().ToArray();
double mean = values.Average();
double n = values.Length;
@@ -58,8 +87,7 @@ public class Skew : AbstractBase
double sumCubedDeviations = 0;
double sumSquaredDeviations = 0;
- foreach (var value in values)
- {
+ foreach (var value in values) {
double deviation = value - mean;
sumCubedDeviations += Math.Pow(deviation, 3);
sumSquaredDeviations += Math.Pow(deviation, 2);
@@ -70,8 +98,7 @@ public class Skew : AbstractBase
double m2 = sumSquaredDeviations / n;
double s3 = Math.Pow(m2, 1.5);
- if (s3 != 0) // Avoid division by zero
- {
+ if (s3 != 0) { // Avoid division by zero
skew = (Math.Sqrt(n * (n - 1)) / (n - 2)) * (m3 / s3);
}
}
diff --git a/lib/statistics/Slope.cs b/lib/statistics/Slope.cs
index ec2d97ea..293144ac 100644
--- a/lib/statistics/Slope.cs
+++ b/lib/statistics/Slope.cs
@@ -1,128 +1,147 @@
-using System;
-using System.Collections.Generic;
+namespace QuanTAlib;
-namespace QuanTAlib
-{
- public class Slope : AbstractBase
- {
- private readonly int _period;
- private readonly CircularBuffer _buffer;
- private readonly CircularBuffer _timeBuffer;
+///
+/// Represents a slope calculator that performs linear regression on a series of data points.
+///
+///
+/// The Slope class calculates the slope of a linear regression line, along with other
+/// statistical measures such as intercept, standard deviation, R-squared, and the last
+/// point on the regression line. It uses the least squares method for calculation.
+///
+public class Slope : AbstractBase {
+ private readonly int _period;
+ private readonly CircularBuffer _buffer;
+ private readonly CircularBuffer _timeBuffer;
+ public double? Intercept { get; private set; }
+ public double? StdDev { get; private set; }
+ public double? RSquared { get; private set; }
+ public double? Line { get; private set; }
- public double? Intercept { get; private set; }
- public double? StdDev { get; private set; }
- public double? RSquared { get; private set; }
- public double? Line { get; private set; }
+ ///
+ /// Initializes a new instance of the Slope class with the specified period.
+ ///
+ /// The period over which to calculate the slope.
+ ///
+ /// Thrown when period is less than or equal to 1.
+ ///
+ public Slope(int period) {
+ if (period <= 1) {
+ throw new ArgumentOutOfRangeException(nameof(period), period,
+ "Period must be greater than 1 for Slope/Linear Regression.");
+ }
+ _period = period;
+ WarmupPeriod = period;
+ _buffer = new CircularBuffer(period);
+ _timeBuffer = new CircularBuffer(period);
+ Name = $"Slope(period={period})";
- public Slope(int period)
- {
- if (period <= 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period), period,
- "Period must be greater than 1 for Slope/Linear Regression.");
+ Init();
+ }
+
+ ///
+ /// Initializes a new instance of the Slope class with the specified source and period.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the slope.
+ public Slope(object source, int period) : this(period) {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ ///
+ /// Initializes the Slope instance by clearing buffers and resetting calculated values.
+ ///
+ public override void Init() {
+ base.Init();
+ _buffer.Clear();
+ _timeBuffer.Clear();
+ Intercept = null;
+ StdDev = null;
+ RSquared = null;
+ Line = null;
+ }
+
+ ///
+ /// Manages the state of the Slope instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
+ _lastValidValue = Input.Value;
+ _index++;
+ }
+ }
+
+ ///
+ /// Performs the slope calculation using linear regression for the current period.
+ ///
+ ///
+ /// The calculated slope value for the current period.
+ ///
+ ///
+ /// This method uses the least squares method to calculate the slope of the regression line.
+ /// It also calculates and updates the Intercept, StdDev, RSquared, and Line properties.
+ /// If there are fewer than 2 data points, or if the sum of squared x deviations is 0,
+ /// the method returns 0 and sets the additional properties to null.
+ ///
+ protected override double Calculation() {
+ ManageState(Input.IsNew);
+
+ _buffer.Add(Input.Value, Input.IsNew);
+ _timeBuffer.Add(Input.Time.Ticks, Input.IsNew);
+
+ double slope = 0;
+
+ if (_buffer.Count < 2) {
+ return slope; // Return 0 when there are fewer than 2 points
+ }
+
+ int count = Math.Min(_buffer.Count, _period);
+ var values = _buffer.GetSpan().ToArray();
+
+ // Calculate averages
+ double sumX = 0, sumY = 0;
+ for (int i = 0; i < count; i++) {
+ sumX += i + 1;
+ sumY += values[i];
+ }
+ double avgX = sumX / count;
+ double avgY = sumY / count;
+
+ // Least squares method
+ double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
+ for (int i = 0; i < count; i++) {
+ double devX = (i + 1) - avgX;
+ double devY = values[i] - avgY;
+ sumSqX += devX * devX;
+ sumSqY += devY * devY;
+ sumSqXY += devX * devY;
+ }
+
+ if (sumSqX > 0) {
+ slope = sumSqXY / sumSqX;
+ Intercept = avgY - (slope * avgX);
+
+ // Calculate Standard Deviation and R-Squared
+ double stdDevX = Math.Sqrt(sumSqX / count);
+ double stdDevY = Math.Sqrt(sumSqY / count);
+ StdDev = stdDevY;
+
+ if (stdDevX * stdDevY != 0) {
+ double r = sumSqXY / (stdDevX * stdDevY) / count;
+ RSquared = r * r;
}
- _period = period;
- WarmupPeriod = period;
- _buffer = new CircularBuffer(period);
- _timeBuffer = new CircularBuffer(period);
- Name = $"Slope(period={period})";
- Init();
- }
-
- public Slope(object source, int period) : this(period)
- {
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
- }
-
- public override void Init()
- {
- base.Init();
- _buffer.Clear();
- _timeBuffer.Clear();
+ // Calculate last Line value (y = mx + b)
+ Line = (slope * count) + Intercept;
+ } else {
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
- _lastValidValue = Input.Value;
- _index++;
- }
- }
-
- protected override double Calculation()
- {
- ManageState(Input.IsNew);
-
- _buffer.Add(Input.Value, Input.IsNew);
- _timeBuffer.Add(Input.Time.Ticks, Input.IsNew);
-
- double slope = 0;
-
- if (_buffer.Count < 2)
- {
- return slope; // Return 0 when there are fewer than 2 points
- }
-
- int count = Math.Min(_buffer.Count, _period);
- var values = _buffer.GetSpan().ToArray();
-
- // Calculate averages
- double sumX = 0, sumY = 0;
- for (int i = 0; i < count; i++)
- {
- sumX += i + 1;
- sumY += values[i];
- }
- double avgX = sumX / count;
- double avgY = sumY / count;
-
- // Least squares method
- double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
- for (int i = 0; i < count; i++)
- {
- double devX = (i + 1) - avgX;
- double devY = values[i] - avgY;
- sumSqX += devX * devX;
- sumSqY += devY * devY;
- sumSqXY += devX * devY;
- }
-
- if (sumSqX > 0)
- {
- slope = sumSqXY / sumSqX;
- Intercept = avgY - (slope * avgX);
-
- // Calculate Standard Deviation and R-Squared
- double stdDevX = Math.Sqrt(sumSqX / count);
- double stdDevY = Math.Sqrt(sumSqY / count);
- StdDev = stdDevY;
-
- if (stdDevX * stdDevY != 0)
- {
- double r = sumSqXY / (stdDevX * stdDevY) / count;
- RSquared = r * r;
- }
-
- // Calculate last Line value (y = mx + b)
- Line = (slope * count) + Intercept;
- }
- else
- {
- Intercept = null;
- StdDev = null;
- RSquared = null;
- Line = null;
- }
-
- IsHot = _buffer.Count == _period;
- return slope;
- }
+ IsHot = _buffer.Count == _period;
+ return slope;
}
-}
\ No newline at end of file
+}
diff --git a/lib/statistics/Stddev.cs b/lib/statistics/Stddev.cs
index ca49b2e5..8d5c30e9 100644
--- a/lib/statistics/Stddev.cs
+++ b/lib/statistics/Stddev.cs
@@ -1,69 +1,105 @@
-using System;
-using System.Linq;
+namespace QuanTAlib;
-namespace QuanTAlib
-{
- public class Stddev : AbstractBase
- {
- private readonly int Period;
- private readonly bool IsPopulation;
- private readonly CircularBuffer _buffer;
+///
+/// Represents a standard deviation calculator that measures the amount of variation or
+/// dispersion of a set of values.
+///
+///
+/// The Stddev class calculates either the population standard deviation or the sample
+/// standard deviation based on the isPopulation parameter. It uses a circular buffer
+/// to efficiently manage the data points within the specified period.
+///
+public class Stddev : AbstractBase {
+ private readonly int Period;
+ private readonly bool IsPopulation;
+ private readonly CircularBuffer _buffer;
- public Stddev(int period, bool isPopulation = false) : base()
- {
- if (period < 2)
- {
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
- }
- Period = period;
- IsPopulation = isPopulation;
- WarmupPeriod = 0;
- _buffer = new CircularBuffer(period);
- Name = $"Stddev(period={period}, population={isPopulation})";
- Init();
+ ///
+ /// Initializes a new instance of the Stddev class with the specified period and
+ /// population flag.
+ ///
+ /// The period over which to calculate the standard deviation.
+ ///
+ /// A flag indicating whether to calculate population (true) or sample (false) standard deviation.
+ ///
+ ///
+ /// Thrown when period is less than 2.
+ ///
+ public Stddev(int period, bool isPopulation = false) : base() {
+ if (period < 2) {
+ throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
+ Period = period;
+ IsPopulation = isPopulation;
+ WarmupPeriod = 0;
+ _buffer = new CircularBuffer(period);
+ Name = $"Stddev(period={period}, population={isPopulation})";
+ Init();
+ }
- public Stddev(object source, int period, bool isPopulation = false) : this(period, isPopulation)
- {
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
- }
+ ///
+ /// Initializes a new instance of the Stddev class with the specified source, period,
+ /// and population flag.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the standard deviation.
+ ///
+ /// A flag indicating whether to calculate population (true) or sample (false) standard deviation.
+ ///
+ public Stddev(object source, int period, bool isPopulation = false) : this(period, isPopulation) {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
- public override void Init()
- {
- base.Init();
- _buffer.Clear();
- }
+ ///
+ /// Initializes the Stddev instance by clearing the buffer.
+ ///
+ public override void Init() {
+ base.Init();
+ _buffer.Clear();
+ }
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
- _lastValidValue = Input.Value;
- _index++;
- }
- }
-
- protected override double Calculation()
- {
- ManageState(Input.IsNew);
-
- _buffer.Add(Input.Value, Input.IsNew);
-
- double stddev = 0;
- if (_buffer.Count > 1)
- {
- var values = _buffer.GetSpan().ToArray();
- double mean = values.Average();
- double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
-
- double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
- double variance = sumOfSquaredDifferences / divisor;
- stddev = Math.Sqrt(variance);
- }
-
- IsHot = true; // StdDev calc is valid from bar 1
- return stddev;
+ ///
+ /// Manages the state of the Stddev instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
+ _lastValidValue = Input.Value;
+ _index++;
}
}
-}
\ No newline at end of file
+
+ ///
+ /// Performs the standard deviation calculation for the current period.
+ ///
+ ///
+ /// The calculated standard deviation value for the current period.
+ ///
+ ///
+ /// This method calculates the standard deviation using the formula:
+ /// sqrt(sum((x - mean)^2) / n) for population, or
+ /// sqrt(sum((x - mean)^2) / (n - 1)) for sample,
+ /// where x is each value, mean is the average of all values, and n is the number of values.
+ /// If there's only one value in the buffer, the method returns 0.
+ ///
+ protected override double Calculation() {
+ ManageState(Input.IsNew);
+
+ _buffer.Add(Input.Value, Input.IsNew);
+
+ double stddev = 0;
+ if (_buffer.Count > 1) {
+ var values = _buffer.GetSpan().ToArray();
+ double mean = values.Average();
+ double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
+
+ double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
+ double variance = sumOfSquaredDifferences / divisor;
+ stddev = Math.Sqrt(variance);
+ }
+
+ IsHot = true; // StdDev calc is valid from bar 1
+ return stddev;
+ }
+}
diff --git a/lib/statistics/Variance.cs b/lib/statistics/Variance.cs
index 4dc37329..dc910203 100644
--- a/lib/statistics/Variance.cs
+++ b/lib/statistics/Variance.cs
@@ -1,68 +1,104 @@
-using System;
-using System.Linq;
+namespace QuanTAlib;
-namespace QuanTAlib
-{
- public class Variance : AbstractBase
- {
- private readonly int Period;
- private readonly bool IsPopulation;
- private readonly CircularBuffer _buffer;
+///
+/// Represents a variance calculator that measures the spread of a set of numbers
+/// from their average value.
+///
+///
+/// The Variance class calculates either the population variance or the sample
+/// variance based on the isPopulation parameter. It uses a circular buffer
+/// to efficiently manage the data points within the specified period.
+///
+public class Variance : AbstractBase {
+ private readonly int Period;
+ private readonly bool IsPopulation;
+ private readonly CircularBuffer _buffer;
- public Variance(int period, bool isPopulation = false) : base()
- {
- if (period < 2)
- {
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
- }
- Period = period;
- IsPopulation = isPopulation;
- WarmupPeriod = 0;
- _buffer = new CircularBuffer(period);
- Name = $"Variance(period={period}, population={isPopulation})";
- Init();
+ ///
+ /// Initializes a new instance of the Variance class with the specified period and
+ /// population flag.
+ ///
+ /// The period over which to calculate the variance.
+ ///
+ /// A flag indicating whether to calculate population (true) or sample (false) variance.
+ ///
+ ///
+ /// Thrown when period is less than 2.
+ ///
+ public Variance(int period, bool isPopulation = false) : base() {
+ if (period < 2) {
+ throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
+ Period = period;
+ IsPopulation = isPopulation;
+ WarmupPeriod = 0;
+ _buffer = new CircularBuffer(period);
+ Name = $"Variance(period={period}, population={isPopulation})";
+ Init();
+ }
- public Variance(object source, int period, bool isPopulation = false) : this(period, isPopulation)
- {
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
- }
+ ///
+ /// Initializes a new instance of the Variance class with the specified source, period,
+ /// and population flag.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the variance.
+ ///
+ /// A flag indicating whether to calculate population (true) or sample (false) variance.
+ ///
+ public Variance(object source, int period, bool isPopulation = false) : this(period, isPopulation) {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
- public override void Init()
- {
- base.Init();
- _buffer.Clear();
- }
+ ///
+ /// Initializes the Variance instance by clearing the buffer.
+ ///
+ public override void Init() {
+ base.Init();
+ _buffer.Clear();
+ }
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
- _lastValidValue = Input.Value;
- _index++;
- }
- }
-
- protected override double Calculation()
- {
- ManageState(Input.IsNew);
-
- _buffer.Add(Input.Value, Input.IsNew);
-
- double variance = 0;
- if (_buffer.Count > 1)
- {
- var values = _buffer.GetSpan().ToArray();
- double mean = values.Average();
- double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
-
- double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
- variance = sumOfSquaredDifferences / divisor;
- }
-
- IsHot = true;
- return variance;
+ ///
+ /// Manages the state of the Variance instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
+ _lastValidValue = Input.Value;
+ _index++;
}
}
-}
\ No newline at end of file
+
+ ///
+ /// Performs the variance calculation for the current period.
+ ///
+ ///
+ /// The calculated variance value for the current period.
+ ///
+ ///
+ /// This method calculates the variance using the formula:
+ /// sum((x - mean)^2) / n for population, or
+ /// sum((x - mean)^2) / (n - 1) for sample,
+ /// where x is each value, mean is the average of all values, and n is the number of values.
+ /// If there's only one value in the buffer, the method returns 0.
+ ///
+ protected override double Calculation() {
+ ManageState(Input.IsNew);
+
+ _buffer.Add(Input.Value, Input.IsNew);
+
+ double variance = 0;
+ if (_buffer.Count > 1) {
+ var values = _buffer.GetSpan().ToArray();
+ double mean = values.Average();
+ double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
+
+ double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
+ variance = sumOfSquaredDifferences / divisor;
+ }
+
+ IsHot = true;
+ return variance;
+ }
+}
diff --git a/lib/statistics/Zscore.cs b/lib/statistics/Zscore.cs
index 6d9a5cee..dafd0f20 100644
--- a/lib/statistics/Zscore.cs
+++ b/lib/statistics/Zscore.cs
@@ -1,17 +1,27 @@
namespace QuanTAlib;
-using System;
-using System.Linq;
-
-public class Zscore : AbstractBase
-{
+///
+/// Represents a Z-score calculator that measures how many standard deviations
+/// an element is from the mean of a set of values.
+///
+///
+/// The Zscore class calculates the Z-score (also known as standard score) for
+/// the most recent value in a given period. It uses a circular buffer to
+/// efficiently manage the data points within the specified period.
+///
+public class Zscore : AbstractBase {
private readonly int Period;
private readonly CircularBuffer _buffer;
- public Zscore(int period) : base()
- {
- if (period < 2)
- {
+ ///
+ /// Initializes a new instance of the Zscore class with the specified period.
+ ///
+ /// The period over which to calculate the Z-score.
+ ///
+ /// Thrown when period is less than 2.
+ ///
+ public Zscore(int period) : base() {
+ if (period < 2) {
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for Z-score calculation.");
}
Period = period;
@@ -21,36 +31,54 @@ public class Zscore : AbstractBase
Init();
}
- public Zscore(object source, int period) : this(period)
- {
+ ///
+ /// Initializes a new instance of the Zscore class with the specified source and period.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the Z-score.
+ public Zscore(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
- public override void Init()
- {
+ ///
+ /// Initializes the Zscore instance by clearing the buffer.
+ ///
+ public override void Init() {
base.Init();
_buffer.Clear();
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
+ ///
+ /// Manages the state of the Zscore instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
- protected override double Calculation()
- {
+ ///
+ /// Performs the Z-score calculation for the current period.
+ ///
+ ///
+ /// The calculated Z-score value for the most recent input in the current period.
+ ///
+ ///
+ /// This method calculates the Z-score using the formula:
+ /// Z = (x - μ) / σ
+ /// where x is the input value, μ is the mean of the period, and σ is the sample standard deviation.
+ /// If there are fewer than 2 data points or if the standard deviation is 0, the method returns 0.
+ ///
+ protected override double Calculation() {
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double zScore = 0;
- if (_buffer.Count >= 2) // We need at least 2 data points for Z-score
- {
+ if (_buffer.Count >= 2) { // We need at least 2 data points for Z-score
var values = _buffer.GetSpan().ToArray();
double mean = values.Average();
double n = values.Length;
@@ -58,8 +86,7 @@ public class Zscore : AbstractBase
double sumSquaredDeviations = values.Sum(x => Math.Pow(x - mean, 2));
double standardDeviation = Math.Sqrt(sumSquaredDeviations / (n - 1)); // Sample standard deviation
- if (standardDeviation != 0) // Avoid division by zero
- {
+ if (standardDeviation != 0) { // Avoid division by zero
zScore = (Input.Value - mean) / standardDeviation;
}
}
diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs
index 79198654..f3167410 100644
--- a/lib/volatility/Atr.cs
+++ b/lib/volatility/Atr.cs
@@ -1,14 +1,26 @@
namespace QuanTAlib;
-public class Atr : AbstractBarBase
-{
+///
+/// Represents an Average True Range (ATR) calculator, a measure of market volatility.
+///
+///
+/// The ATR class calculates the average true range using an Exponential Moving Average (EMA)
+/// of the true range. The true range is the greatest of: current high - current low,
+/// absolute value of current high - previous close, or absolute value of current low - previous close.
+///
+public class Atr : AbstractBarBase {
private readonly Ema _ma;
private double _prevClose, _p_prevClose;
- public Atr(int period) : base()
- {
- if (period < 1)
- {
+ ///
+ /// Initializes a new instance of the Atr class with the specified period.
+ ///
+ /// The period over which to calculate the ATR.
+ ///
+ /// Thrown when period is less than 1.
+ ///
+ public Atr(int period) : base() {
+ if (period < 1) {
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_ma = new(1.0/period);
@@ -16,34 +28,50 @@ public class Atr : AbstractBarBase
Name = $"ATR({period})";
}
- public Atr(object source, int period) : this(period)
- {
+ ///
+ /// Initializes a new instance of the Atr class with the specified source and period.
+ ///
+ /// The source object to subscribe to for bar updates.
+ /// The period over which to calculate the ATR.
+ public Atr(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
}
- public override void Init()
- {
+ ///
+ /// Initializes the Atr instance by setting up the initial state.
+ ///
+ public override void Init() {
base.Init();
_ma.Init();
_prevClose = double.NaN;
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
+ ///
+ /// Manages the state of the Atr instance based on whether a new bar is being processed.
+ ///
+ /// Indicates whether the current input is a new bar.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
_index++;
_p_prevClose = _prevClose;
- }
- else
- {
+ } else {
_prevClose = _p_prevClose;
}
}
- protected override double Calculation()
- {
+ ///
+ /// Performs the ATR calculation for the current bar.
+ ///
+ ///
+ /// The calculated ATR value for the current bar.
+ ///
+ ///
+ /// This method calculates the true range for the current bar and then uses an EMA
+ /// to smooth the true range values. For the first bar, it uses the high-low range
+ /// as the true range.
+ ///
+ protected override double Calculation() {
ManageState(Input.IsNew);
double trueRange = Math.Max(
@@ -53,8 +81,7 @@ public class Atr : AbstractBarBase
),
Math.Abs(Input.Low - _prevClose)
);
- if (_index < 2)
- {
+ if (_index < 2) {
trueRange = Input.High - Input.Low;
}
@@ -64,6 +91,4 @@ public class Atr : AbstractBarBase
return emaTrueRange.Value;
}
-
}
-
diff --git a/lib/volatility/Historical.cs b/lib/volatility/Historical.cs
index 1b9998ba..21b80dd8 100644
--- a/lib/volatility/Historical.cs
+++ b/lib/volatility/Historical.cs
@@ -1,17 +1,31 @@
namespace QuanTAlib;
-public class Historical : AbstractBase
-{
+///
+/// Represents a historical volatility calculator that measures the dispersion of returns
+/// for a given security or market index over a specific period.
+///
+///
+/// The Historical class calculates volatility based on logarithmic returns. It can provide
+/// both annualized and non-annualized volatility measures. The calculation uses a sample
+/// standard deviation formula and assumes 252 trading days in a year for annualization.
+///
+public class Historical : AbstractBase {
private readonly int Period;
private readonly bool IsAnnualized;
private readonly CircularBuffer _buffer;
private readonly CircularBuffer _logReturns;
private double _previousClose;
- public Historical(int period, bool isAnnualized = true) : base()
- {
- if (period < 2)
- {
+ ///
+ /// Initializes a new instance of the Historical class with the specified period and annualization flag.
+ ///
+ /// The period over which to calculate historical volatility.
+ /// Whether to annualize the volatility (default is true).
+ ///
+ /// Thrown when period is less than 2.
+ ///
+ public Historical(int period, bool isAnnualized = true) : base() {
+ if (period < 2) {
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
Period = period;
@@ -23,46 +37,64 @@ public class Historical : AbstractBase
Init();
}
- public Historical(object source, int period, bool isAnnualized = true) : this(period, isAnnualized)
- {
+ ///
+ /// Initializes a new instance of the Historical class with the specified source, period, and annualization flag.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate historical volatility.
+ /// Whether to annualize the volatility (default is true).
+ public Historical(object source, int period, bool isAnnualized = true) : this(period, isAnnualized) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
- public override void Init()
- {
+ ///
+ /// Initializes the Historical instance by clearing buffers and resetting the previous close value.
+ ///
+ public override void Init() {
base.Init();
_buffer.Clear();
_logReturns.Clear();
_previousClose = 0;
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
+ ///
+ /// Manages the state of the Historical instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
- protected override double Calculation()
- {
+ ///
+ /// Performs the historical volatility calculation for the current period.
+ ///
+ ///
+ /// The calculated historical volatility value for the current period.
+ ///
+ ///
+ /// This method calculates the volatility using the following steps:
+ /// 1. Compute logarithmic returns.
+ /// 2. Calculate the sample standard deviation of the log returns.
+ /// 3. If annualized, multiply by the square root of 252 (assumed trading days in a year).
+ /// The method returns 0 until enough data points are available for the calculation.
+ ///
+ protected override double Calculation() {
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
double volatility = 0;
- if (_buffer.Count > 1)
- {
- if (_previousClose != 0)
- {
+ if (_buffer.Count > 1) {
+ if (_previousClose != 0) {
double logReturn = Math.Log(Input.Value / _previousClose);
_logReturns.Add(logReturn, Input.IsNew);
}
- if (_logReturns.Count == Period)
- {
+ if (_logReturns.Count == Period) {
var returns = _logReturns.GetSpan().ToArray();
double mean = returns.Average();
double sumOfSquaredDifferences = returns.Sum(x => Math.Pow(x - mean, 2));
@@ -70,8 +102,7 @@ public class Historical : AbstractBase
double variance = sumOfSquaredDifferences / (Period - 1); // Using sample standard deviation
volatility = Math.Sqrt(variance);
- if (IsAnnualized)
- {
+ if (IsAnnualized) {
// Assuming 252 trading days in a year. Adjust as needed.
volatility *= Math.Sqrt(252);
}
diff --git a/lib/volatility/Realized.cs b/lib/volatility/Realized.cs
index 075a0dbe..ddcd8c76 100644
--- a/lib/volatility/Realized.cs
+++ b/lib/volatility/Realized.cs
@@ -1,16 +1,31 @@
namespace QuanTAlib;
-public class Realized : AbstractBase
-{
+
+///
+/// Represents a realized volatility calculator that measures the actual price fluctuations
+/// observed in the market over a specific period.
+///
+///
+/// The Realized class calculates volatility based on logarithmic returns. It can provide
+/// both annualized and non-annualized volatility measures. The calculation uses a rolling
+/// sum of squared returns for efficiency and assumes 252 trading days in a year for annualization.
+///
+public class Realized : AbstractBase {
private readonly int Period;
private readonly bool IsAnnualized;
private readonly CircularBuffer _returns;
private double _previousClose;
private double _sumSquaredReturns;
- public Realized(int period, bool isAnnualized = true) : base()
- {
- if (period < 2)
- {
+ ///
+ /// Initializes a new instance of the Realized class with the specified period and annualization flag.
+ ///
+ /// The period over which to calculate realized volatility.
+ /// Whether to annualize the volatility (default is true).
+ ///
+ /// Thrown when period is less than 2.
+ ///
+ public Realized(int period, bool isAnnualized = true) : base() {
+ if (period < 2) {
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
Period = period;
@@ -21,34 +36,50 @@ public class Realized : AbstractBase
Init();
}
- public override void Init()
- {
+ ///
+ /// Initializes the Realized instance by clearing buffers and resetting calculation variables.
+ ///
+ public override void Init() {
base.Init();
_returns.Clear();
_previousClose = 0;
_sumSquaredReturns = 0;
}
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
+ ///
+ /// Manages the state of the Realized instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
- protected override double Calculation()
- {
+ ///
+ /// Performs the realized volatility calculation for the current period.
+ ///
+ ///
+ /// The calculated realized volatility value for the current period.
+ ///
+ ///
+ /// This method calculates the volatility using the following steps:
+ /// 1. Compute logarithmic returns.
+ /// 2. Maintain a rolling sum of squared returns.
+ /// 3. Calculate the variance using the sum of squared returns.
+ /// 4. Take the square root of the variance to get volatility.
+ /// 5. If annualized, multiply by the square root of 252 (assumed trading days in a year).
+ /// The method returns 0 until enough data points are available for the calculation.
+ ///
+ protected override double Calculation() {
ManageState(Input.IsNew);
double volatility = 0;
- if (_previousClose != 0)
- {
+ if (_previousClose != 0) {
double logReturn = Math.Log(Input.Value / _previousClose);
- if (_returns.Count == Period)
- {
+ if (_returns.Count == Period) {
// Remove the oldest squared return from the sum
_sumSquaredReturns -= Math.Pow(_returns[0], 2);
}
@@ -56,13 +87,11 @@ public class Realized : AbstractBase
_returns.Add(logReturn, Input.IsNew);
_sumSquaredReturns += Math.Pow(logReturn, 2);
- if (_returns.Count == Period)
- {
+ if (_returns.Count == Period) {
double variance = _sumSquaredReturns / Period;
volatility = Math.Sqrt(variance);
- if (IsAnnualized)
- {
+ if (IsAnnualized) {
// Assuming 252 trading days in a year. Adjust as needed.
volatility *= Math.Sqrt(252);
}
diff --git a/lib/volatility/Rvi.cs b/lib/volatility/Rvi.cs
index 276d733e..ee3349ea 100644
--- a/lib/volatility/Rvi.cs
+++ b/lib/volatility/Rvi.cs
@@ -1,87 +1,110 @@
-/*
-Reference:
- Donald Dorsey, who introduced the concept in the 1993 issue of Technical Analysis
- of Stocks & Commodities Magazine. He designed the RVI to focus on the direction of
- price movements in relation to volatility. Dorsey’s methodology is often cited in
- technical analysis literature and further elaborated on in various technical analysis
- guides and platforms.
-*/
+namespace QuanTAlib;
+///
+/// Represents a Relative Volatility Index (RVI) calculator, which measures the direction
+/// of volatility in relation to price movements.
+///
+///
+/// The RVI was introduced by Donald Dorsey in the 1993 issue of Technical Analysis
+/// of Stocks & Commodities Magazine. It focuses on the direction of price movements
+/// in relation to volatility. The indicator uses standard deviation calculations
+/// to determine whether volatility is increasing more in up moves or down moves.
+///
+/// This implementation uses a combination of Standard Deviation and Simple Moving Average
+/// calculations to compute the RVI.
+///
+public class Rvi : AbstractBase {
+ private readonly int Period;
+ private Stddev _upStdDev, _downStdDev;
+ private Sma _upSma, _downSma;
+ private double _previousClose;
-using System;
-
-namespace QuanTAlib
-{
- public class Rvi : AbstractBase
- {
- private readonly int Period;
- private Stddev _upStdDev, _downStdDev;
- private Sma _upSma, _downSma;
- private double _previousClose;
-
- public Rvi(int period) : base()
- {
- if (period < 2)
- {
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
- }
- Period = period;
- WarmupPeriod = period;
- Name = $"RVI(period={period})";
- _upStdDev = new Stddev(Period);
- _downStdDev = new Stddev(Period);
- _upSma = new(Period);
- _downSma = new(Period);
- Init();
+ ///
+ /// Initializes a new instance of the Rvi class with the specified period.
+ ///
+ /// The period over which to calculate the RVI.
+ ///
+ /// Thrown when period is less than 2.
+ ///
+ public Rvi(int period) : base() {
+ if (period < 2) {
+ throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
}
+ Period = period;
+ WarmupPeriod = period;
+ Name = $"RVI(period={period})";
+ _upStdDev = new Stddev(Period);
+ _downStdDev = new Stddev(Period);
+ _upSma = new(Period);
+ _downSma = new(Period);
+ Init();
+ }
- public Rvi(object source, int period) : this(period)
- {
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
- }
+ ///
+ /// Initializes a new instance of the Rvi class with the specified source and period.
+ ///
+ /// The source object to subscribe to for value updates.
+ /// The period over which to calculate the RVI.
+ public Rvi(object source, int period) : this(period) {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
- public override void Init()
- {
- base.Init();
- _previousClose = 0;
- }
+ ///
+ /// Initializes the Rvi instance by setting up the initial state.
+ ///
+ public override void Init() {
+ base.Init();
+ _previousClose = 0;
+ }
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
- _lastValidValue = Value;
- _index++;
- }
- }
-
- protected override double Calculation()
- {
- ManageState(Input.IsNew);
-
- double close = Input.Value;
- double change = close - _previousClose;
-
- double upMove = Math.Max(change, 0);
- double downMove = Math.Max(-change, 0);
-
- _upSma.Calc(_upStdDev.Calc(new TValue(Input.Time, upMove, Input.IsNew)));
- _downSma.Calc(_downStdDev.Calc(new TValue(Input.Time, downMove, Input.IsNew)));
-
- double rvi;
- if (_upSma.Value + _downSma.Value != 0)
- {
- rvi = 100 * _upSma.Value / (_upSma.Value + _downSma.Value);
- }
- else
- {
- rvi = 0;
- }
-
- _previousClose = close;
- IsHot = _index >= WarmupPeriod;
- return rvi;
+ ///
+ /// Manages the state of the Rvi instance based on whether a new value is being processed.
+ ///
+ /// Indicates whether the current input is a new value.
+ protected override void ManageState(bool isNew) {
+ if (isNew) {
+ _lastValidValue = Value;
+ _index++;
}
}
-}
\ No newline at end of file
+
+ ///
+ /// Performs the RVI calculation for the current input.
+ ///
+ ///
+ /// The calculated RVI value for the current input.
+ ///
+ ///
+ /// This method calculates the RVI using the following steps:
+ /// 1. Calculate the change in price from the previous close.
+ /// 2. Determine the up move and down move based on the change.
+ /// 3. Calculate standard deviations of up and down moves.
+ /// 4. Apply a simple moving average to the standard deviations.
+ /// 5. Compute the RVI as a percentage of up volatility to total volatility.
+ /// The method returns 0 if the sum of up and down volatility is zero.
+ ///
+ protected override double Calculation() {
+ ManageState(Input.IsNew);
+
+ double close = Input.Value;
+ double change = close - _previousClose;
+
+ double upMove = Math.Max(change, 0);
+ double downMove = Math.Max(-change, 0);
+
+ _upSma.Calc(_upStdDev.Calc(new TValue(Input.Time, upMove, Input.IsNew)));
+ _downSma.Calc(_downStdDev.Calc(new TValue(Input.Time, downMove, Input.IsNew)));
+
+ double rvi;
+ if (_upSma.Value + _downSma.Value != 0) {
+ rvi = 100 * _upSma.Value / (_upSma.Value + _downSma.Value);
+ } else {
+ rvi = 0;
+ }
+
+ _previousClose = close;
+ IsHot = _index >= WarmupPeriod;
+ return rvi;
+ }
+}