XML Documentation

This commit is contained in:
Miha Kralj
2024-10-05 15:20:13 -07:00
parent 30d93e724d
commit 3458b14ebb
23 changed files with 1596 additions and 986 deletions
+2 -2
View File
@@ -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: []
sha: []
+1 -1
View File
@@ -1,5 +1,5 @@
# Backlog and done
|**QT**|**Cht**|Cmnt|Docs|isNew|Valid|
|**QT**|**Chart**|Cmnt|Docs|isNew|Validation|
|--|:--:|:--:|:--:|:--:|:--:|
|AFIRMA|✔️|||||
+89 -56
View File
@@ -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||
|<br>||||
|**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`||||
|<br>|||||
|**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`|||`✔️`|
|<br>||||
|**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|||
|<br>||||
|**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|||
|<br>||||
|**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||||
+44 -25
View File
@@ -1,56 +1,76 @@
namespace QuanTAlib;
/// <summary>
/// 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.
/// </summary>
public abstract class AbstractBarBase : iTValue
{
/// <remarks>
/// 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.
/// </remarks>
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
}
/// <summary>
/// Subscribes to bar data updates.
/// </summary>
/// <param name="source">The source of the bar data.</param>
/// <param name="args">The event arguments containing the bar data.</param>
public void Sub(object source, in TBarEventArgs args) => Calc(args.Bar);
public virtual void Init()
{
/// <summary>
/// Initializes the indicator's state.
/// </summary>
public virtual void Init() {
_index = 0;
_lastValidValue = 0;
}
public virtual TValue Calc(TBar input)
{
/// <summary>
/// Calculates the indicator value based on the input bar.
/// </summary>
/// <param name="input">The input bar data.</param>
/// <returns>A TValue containing the calculated result.</returns>
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()
{
/// <summary>
/// Retrieves the last valid calculated value.
/// </summary>
/// <returns>The last valid value of the indicator.</returns>
protected virtual double GetLastValid() {
return this.Value;
}
/// <summary>
/// Manages the state of the indicator based on whether a new bar is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new bar.</param>
protected abstract void ManageState(bool isNew);
/// <summary>
/// Performs the actual calculation of the indicator value.
/// </summary>
/// <returns>The calculated indicator value.</returns>
protected abstract double Calculation();
/// <summary>
@@ -59,8 +79,7 @@ public abstract class AbstractBarBase : iTValue
/// </summary>
/// <param name="value">The calculated TValue to process.</param>
/// <returns>The processed TValue.</returns>
protected virtual TValue Process(TValue value)
{
protected virtual TValue Process(TValue value) {
this.Time = value.Time;
this.Value = value.Value;
this.IsNew = value.IsNew;
+32 -12
View File
@@ -2,29 +2,29 @@ namespace QuanTAlib;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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
}
/// <summary>
@@ -34,6 +34,9 @@ public abstract class AbstractBase : iTValue
/// <param name="args">The argument containing the new data point.</param>
public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick);
/// <summary>
/// Initializes the indicator's state.
/// </summary>
public virtual void Init()
{
_index = 0;
@@ -41,11 +44,14 @@ public abstract class AbstractBase : iTValue
}
/// <summary>
/// Calculates the indicator value based on the input; calls specific Calculation() method
/// where implementation is
/// Calculates the indicator value based on the input.
/// </summary>
/// <param name="input">The input value for the calculation.</param>
/// <returns>A TValue representing the calculated indicator value.</returns>
/// <remarks>
/// 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.
/// </remarks>
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));
}
/// <summary>
/// Retrieves the last valid calculated value.
/// </summary>
/// <returns>The last valid value of the indicator.</returns>
protected virtual double GetLastValid()
{
return this.Value;
}
/// <summary>
/// Manages the state of the indicator based on whether a new data point is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new data point.</param>
protected abstract void ManageState(bool isNew);
/// <summary>
/// Performs the actual calculation of the indicator value.
/// </summary>
/// <returns>The calculated indicator value.</returns>
protected abstract double Calculation();
/// <summary>
+146 -98
View File
@@ -4,55 +4,72 @@ using System.Numerics;
namespace QuanTAlib;
public class CircularBuffer : IEnumerable<double>
{
/// <summary>
/// Represents a circular buffer of double values with fixed capacity.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class CircularBuffer : IEnumerable<double> {
private readonly double[] _buffer;
private int _start = 0;
private int _size = 0;
/// <summary>
/// Gets the maximum number of elements that can be contained in the buffer.
/// </summary>
public int Capacity { get; }
/// <summary>
/// Gets the number of elements currently contained in the buffer.
/// </summary>
public int Count => _size;
public CircularBuffer(int capacity)
{
/// <summary>
/// Initializes a new instance of the CircularBuffer class with the specified capacity.
/// </summary>
/// <param name="capacity">The maximum number of elements the buffer can hold.</param>
public CircularBuffer(int capacity) {
Capacity = capacity;
_buffer = GC.AllocateArray<double>(capacity, pinned: true);
}
/// <summary>
/// Adds an item to the buffer.
/// </summary>
/// <param name="item">The item to add to the buffer.</param>
/// <param name="isNew">Indicates whether the item is a new value or an update to the last added value.</param>
[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]
{
/// <summary>
/// Gets or sets the element at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
/// <returns>The element at the specified index.</returns>
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<double>
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeException()
{
private static void ThrowArgumentOutOfRangeException() {
throw new ArgumentOutOfRangeException("index", "Index is out of range.");
}
/// <summary>
/// Gets the newest (most recently added) element in the buffer.
/// </summary>
/// <returns>The newest element in the buffer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Newest()
{
public double Newest() {
if (_size == 0)
return 0;
return _buffer[(_start + _size - 1) % Capacity];
}
/// <summary>
/// Gets the oldest element in the buffer.
/// </summary>
/// <returns>The oldest element in the buffer.</returns>
[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.");
}
/// <summary>
/// Returns an enumerator that iterates through the buffer.
/// </summary>
/// <returns>An enumerator for the buffer.</returns>
public Enumerator GetEnumerator() => new(this);
IEnumerator<double> IEnumerable<double>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public struct Enumerator : IEnumerator<double>
{
/// <summary>
/// Represents an enumerator for the CircularBuffer.
/// </summary>
public struct Enumerator : IEnumerator<double> {
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;
}
/// <summary>
/// Advances the enumerator to the next element of the buffer.
/// </summary>
/// <returns>true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
public bool MoveNext() {
if (_index + 1 >= _buffer._size)
return false;
@@ -116,92 +145,122 @@ public class CircularBuffer : IEnumerable<double>
return true;
}
/// <summary>
/// Gets the element in the buffer at the current position of the enumerator.
/// </summary>
public double Current => _current;
object IEnumerator.Current => Current;
public void Reset()
{
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the buffer.
/// </summary>
public void Reset() {
_index = -1;
_current = default;
}
/// <summary>
/// Disposes the enumerator.
/// </summary>
public void Dispose() { }
}
/// <summary>
/// Copies the elements of the buffer to an array, starting at a particular array index.
/// </summary>
/// <param name="destination">The one-dimensional array that is the destination of the elements copied from the buffer.</param>
/// <param name="destinationIndex">The zero-based index in array at which copying begins.</param>
[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);
}
}
/// <summary>
/// Returns a read-only span over the contents of the buffer.
/// </summary>
/// <returns>A read-only span over the buffer contents.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<double> GetSpan()
{
public ReadOnlySpan<double> GetSpan() {
if (_size == 0)
return ReadOnlySpan<double>.Empty;
if (_start + _size <= Capacity)
{
if (_start + _size <= Capacity) {
return new ReadOnlySpan<double>(_buffer, _start, _size);
}
else
{
} else {
return new ReadOnlySpan<double>(ToArray());
}
}
/// <summary>
/// Gets the internal buffer array.
/// </summary>
public double[] InternalBuffer => _buffer;
/// <summary>
/// Returns a read-only span over the entire internal buffer.
/// </summary>
/// <returns>A read-only span over the entire internal buffer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<double> GetInternalSpan() => _buffer.AsSpan();
/// <summary>
/// Removes all elements from the buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
public void Clear() {
Array.Clear(_buffer, 0, _buffer.Length);
_start = 0;
_size = 0;
}
/// <summary>
/// Returns the maximum value in the buffer.
/// </summary>
/// <returns>The maximum value in the buffer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Max()
{
public double Max() {
if (_size == 0)
ThrowInvalidOperationException();
return MaxSimd();
}
/// <summary>
/// Returns the minimum value in the buffer.
/// </summary>
/// <returns>The minimum value in the buffer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Min()
{
public double Min() {
if (_size == 0)
ThrowInvalidOperationException();
return MinSimd();
}
/// <summary>
/// Computes the sum of all values in the buffer.
/// </summary>
/// <returns>The sum of all values in the buffer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Sum()
{
public double Sum() {
return SumSimd();
}
/// <summary>
/// Computes the average of all values in the buffer.
/// </summary>
/// <returns>The average of all values in the buffer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Average()
{
public double Average() {
if (_size == 0)
ThrowInvalidOperationException();
@@ -209,26 +268,22 @@ public class CircularBuffer : IEnumerable<double>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double MaxSimd()
{
private double MaxSimd() {
var span = GetSpan();
var vectorSize = Vector<double>.Count;
var maxVector = new Vector<double>(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<double>(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<double>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double MinSimd()
{
private double MinSimd() {
var span = GetSpan();
var vectorSize = Vector<double>.Count;
var minVector = new Vector<double>(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<double>(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<double>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double SumSimd()
{
private double SumSimd() {
var span = GetSpan();
var vectorSize = Vector<double>.Count;
var sumVector = Vector<double>.Zero;
int i = 0;
for (; i <= span.Length - vectorSize; i += vectorSize)
{
for (; i <= span.Length - vectorSize; i += vectorSize) {
sumVector += new Vector<double>(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()
{
/// <summary>
/// Copies the buffer elements to a new array.
/// </summary>
/// <returns>An array containing copies of the buffer elements.</returns>
public double[] ToArray() {
double[] array = new double[_size];
CopyTo(array, 0);
return array;
}
public void ParallelOperation(Func<double[], int, int, double> operation)
{
/// <summary>
/// Performs a parallel operation on the buffer elements.
/// </summary>
/// <param name="operation">The operation to perform on each partition of the buffer.</param>
public void ParallelOperation(Func<double[], int, int, double> 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<double>
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<double>
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);
});
}
}
+139 -111
View File
@@ -1,128 +1,156 @@
using System;
using System.Collections.Generic;
namespace QuanTAlib;
namespace QuanTAlib
/// <summary>
/// Calculates the rate of change of the slope over a specified period.
/// Provides insights into trend acceleration or deceleration.
/// </summary>
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; }
/// <summary>
/// Initializes a new instance of the Curvature class.
/// </summary>
/// <param name="period">The number of data points to consider for calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is 2 or less.
/// </exception>
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();
}
/// <summary>
/// Initializes a new instance of the Curvature class with a data source.
/// </summary>
/// <param name="source">The source object that publishes data.</param>
/// <param name="period">The number of data points to consider.</param>
public Curvature(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Resets the Curvature indicator to its initial state.
/// </summary>
public override void Init()
{
base.Init();
_slopeBuffer.Clear();
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
}
/// <summary>
/// Manages the state of the indicator.
/// </summary>
/// <param name="isNew">Indicates if the current data point is new.</param>
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
/// <summary>
/// Performs the curvature calculation.
/// </summary>
/// <returns>
/// The calculated curvature value. Positive for increasing slope, negative for decreasing.
/// </returns>
/// <remarks>
/// Uses least squares method for optimal calculation. Also computes additional statistics
/// such as Intercept, Standard Deviation, R-Squared, and Line value.
/// </remarks>
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;
}
}
+40 -7
View File
@@ -1,19 +1,27 @@
namespace QuanTAlib;
using System;
using System.Linq;
// Shannon's Entropy calculation
/// <summary>
/// Measures the unpredictability of data using Shannon's Entropy.
/// Provides insights into the randomness or information content of the time series.
/// </summary>
public class Entropy : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _buffer;
/// <summary>
/// Initializes a new instance of the Entropy class.
/// </summary>
/// <param name="period">The number of data points to consider for calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is less than 2.
/// </exception>
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();
}
/// <summary>
/// Initializes a new instance of the Entropy class with a data source.
/// </summary>
/// <param name="source">The source object that publishes data.</param>
/// <param name="period">The number of data points to consider.</param>
public Entropy(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Resets the Entropy indicator to its initial state.
/// </summary>
public override void Init()
{
base.Init();
_buffer.Clear();
}
/// <summary>
/// Manages the state of the indicator.
/// </summary>
/// <param name="isNew">Indicates if the current data point is new.</param>
protected override void ManageState(bool isNew)
{
if (isNew)
@@ -43,6 +63,17 @@ public class Entropy : AbstractBase
}
}
/// <summary>
/// Performs the entropy calculation.
/// </summary>
/// <returns>
/// The calculated entropy value, normalized between 0 and 1.
/// 1 indicates maximum randomness, 0 indicates perfect predictability.
/// </returns>
/// <remarks>
/// Uses Shannon's Entropy formula and normalizes the result based on the
/// number of unique values in the current period.
/// </remarks>
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;
+37 -3
View File
@@ -1,16 +1,27 @@
namespace QuanTAlib;
// Excess kurtosis calculated with Sheskin Algorithm
/// <summary>
/// Calculates excess kurtosis using the Sheskin Algorithm.
/// Measures the "tailedness" of the probability distribution of a real-valued random variable.
/// </summary>
public class Kurtosis : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _buffer;
/// <summary>
/// Initializes a new instance of the Kurtosis class.
/// </summary>
/// <param name="period">The number of data points to consider for calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is less than 4.
/// </exception>
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();
}
/// <summary>
/// Initializes a new instance of the Kurtosis class with a data source.
/// </summary>
/// <param name="source">The source object that publishes data.</param>
/// <param name="period">The number of data points to consider.</param>
public Kurtosis(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Resets the Kurtosis indicator to its initial state.
/// </summary>
public override void Init()
{
base.Init();
_buffer.Clear();
}
/// <summary>
/// Manages the state of the indicator.
/// </summary>
/// <param name="isNew">Indicates if the current data point is new.</param>
protected override void ManageState(bool isNew)
{
if (isNew)
@@ -40,6 +63,17 @@ public class Kurtosis : AbstractBase
}
}
/// <summary>
/// Performs the kurtosis calculation.
/// </summary>
/// <returns>
/// The calculated excess kurtosis. Positive for heavy-tailed distributions,
/// negative for light-tailed distributions.
/// </returns>
/// <remarks>
/// Uses the Sheskin Algorithm for kurtosis calculation.
/// Requires at least 4 data points for a valid calculation.
/// </remarks>
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)));
}
+100 -66
View File
@@ -1,80 +1,114 @@
using System;
namespace QuanTAlib;
namespace QuanTAlib
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the Max class.
/// </summary>
/// <param name="period">The number of data points to consider. Must be at least 1.</param>
/// <param name="decay">Half-life decay factor. Set to 0 for no decay, higher for faster forgetting. Default is 0.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is less than 1 or decay is negative.
/// </exception>
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()
/// <summary>
/// Initializes a new instance of the Max class with a data source.
/// </summary>
/// <param name="source">The source object that publishes data.</param>
/// <param name="period">The number of data points to consider.</param>
/// <param name="decay">Half-life decay factor. Default is 0.</param>
public Max(object source, int period, double decay = 0) : this(period, decay)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Resets the Max indicator to its initial state.
/// </summary>
public override void Init()
{
base.Init();
_currentMax = double.MinValue;
_timeSinceNewMax = 0;
}
/// <summary>
/// Manages the state of the indicator.
/// </summary>
/// <param name="isNew">Indicates if the current data point is new.</param>
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;
}
}
/// <summary>
/// Performs the max calculation.
/// </summary>
/// <returns>
/// The current maximum value, potentially adjusted by the decay factor.
/// </returns>
/// <remarks>
/// Uses a decay factor to gradually forget old peaks. The max value is always
/// capped by the highest value in the current period.
/// </remarks>
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;
}
}
}
+82 -52
View File
@@ -1,69 +1,99 @@
using System;
using System.Linq;
namespace QuanTAlib;
namespace QuanTAlib
/// <summary>
/// Calculates the median value over a specified period.
/// Provides a measure of central tendency that is robust to outliers.
/// </summary>
public class Median : AbstractBase
{
public class Median : AbstractBase
private readonly int Period;
private readonly CircularBuffer _buffer;
/// <summary>
/// Initializes a new instance of the Median class.
/// </summary>
/// <param name="period">The number of data points to consider. Must be at least 1.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the period is less than 1.
/// </exception>
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)
/// <summary>
/// Initializes a new instance of the Median class with a data source.
/// </summary>
/// <param name="source">The source object that publishes data.</param>
/// <param name="period">The number of data points to consider.</param>
public Median(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Manages the state of the indicator.
/// </summary>
/// <param name="isNew">Indicates if the current data point is new.</param>
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)
/// <summary>
/// Performs the median calculation.
/// </summary>
/// <returns>
/// The current median value of the dataset.
/// </returns>
/// <remarks>
/// Uses a sorting approach to find the median. If there's not enough data,
/// it uses the average as a temporary measure.
/// </remarks>
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;
}
}
}
+96 -70
View File
@@ -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;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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();
/// <summary>
/// Initializes a new instance of the Min class with the specified period and decay.
/// </summary>
/// <param name="period">The period over which to calculate the minimum value.</param>
/// <param name="decay">The decay factor to apply to older values (default is 0).</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 1 or decay is negative.
/// </exception>
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;
/// <summary>
/// Initializes a new instance of the Min class with the specified source, period, and decay.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the minimum value.</param>
/// <param name="decay">The decay factor to apply to older values (default is 0).</param>
public Min(object source, int period, double decay = 0) : this(period, decay) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Initializes the Min instance by setting initial values.
/// </summary>
public override void Init() {
base.Init();
_currentMin = double.MaxValue;
_timeSinceNewMin = 0;
}
/// <summary>
/// Manages the state of the Min instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
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;
}
}
/// <summary>
/// Performs the minimum value calculation with decay.
/// </summary>
/// <returns>The calculated minimum value for the current period.</returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
}
+46 -19
View File
@@ -1,14 +1,27 @@
namespace QuanTAlib;
public class Mode : AbstractBase
{
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class Mode : AbstractBase {
private readonly int Period;
private readonly CircularBuffer _buffer;
public Mode(int period) : base()
{
if (period < 1)
{
/// <summary>
/// Initializes a new instance of the Mode class with the specified period.
/// </summary>
/// <param name="period">The period over which to calculate the mode.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 1.
/// </exception>
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)
{
/// <summary>
/// Initializes a new instance of the Mode class with the specified source and period.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the mode.</param>
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)
{
/// <summary>
/// Manages the state of the Mode instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
/// <summary>
/// Performs the mode calculation for the current period.
/// </summary>
/// <returns>
/// The calculated mode (most frequent value) for the current period.
/// If multiple values have the same highest frequency, returns their average.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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
}
+56 -31
View File
@@ -1,22 +1,33 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Percentile : AbstractBase
{
/// <summary>
/// Represents a percentile calculator that determines the value at a specified percentile
/// in a given period of data points.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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)
{
/// <summary>
/// Initializes a new instance of the Percentile class with the specified period and percentile.
/// </summary>
/// <param name="period">The period over which to calculate the percentile.</param>
/// <param name="percent">The percentile to calculate (between 0 and 100).</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2 or percent is not between 0 and 100.
/// </exception>
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)
{
/// <summary>
/// Initializes a new instance of the Percentile class with the specified source, period, and percentile.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the percentile.</param>
/// <param name="percent">The percentile to calculate (between 0 and 100).</param>
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()
{
/// <summary>
/// Initializes the Percentile instance by clearing the buffer.
/// </summary>
public override void Init() {
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
/// <summary>
/// Manages the state of the Percentile instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
/// <summary>
/// Performs the percentile calculation for the current period.
/// </summary>
/// <returns>
/// The calculated percentile value for the current period.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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();
}
+52 -25
View File
@@ -1,17 +1,28 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Skew : AbstractBase
{
/// <summary>
/// Represents a skewness calculator that measures the asymmetry of the probability
/// distribution of a real-valued random variable about its mean.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class Skew : AbstractBase {
private readonly int Period;
private readonly CircularBuffer _buffer;
public Skew(int period) : base()
{
if (period < 3)
{
/// <summary>
/// Initializes a new instance of the Skew class with the specified period.
/// </summary>
/// <param name="period">The period over which to calculate the skewness.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 3.
/// </exception>
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)
{
/// <summary>
/// Initializes a new instance of the Skew class with the specified source and period.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the skewness.</param>
public Skew(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
/// <summary>
/// Initializes the Skew instance by clearing the buffer.
/// </summary>
public override void Init() {
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
/// <summary>
/// Manages the state of the Skew instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
/// <summary>
/// Performs the skewness calculation for the current period.
/// </summary>
/// <returns>
/// The calculated skewness value for the current period.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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);
}
}
+135 -116
View File
@@ -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;
/// <summary>
/// Represents a slope calculator that performs linear regression on a series of data points.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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; }
/// <summary>
/// Initializes a new instance of the Slope class with the specified period.
/// </summary>
/// <param name="period">The period over which to calculate the slope.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than or equal to 1.
/// </exception>
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();
}
/// <summary>
/// Initializes a new instance of the Slope class with the specified source and period.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the slope.</param>
public Slope(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
/// <summary>
/// Initializes the Slope instance by clearing buffers and resetting calculated values.
/// </summary>
public override void Init() {
base.Init();
_buffer.Clear();
_timeBuffer.Clear();
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
}
/// <summary>
/// Manages the state of the Slope instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
/// <summary>
/// Performs the slope calculation using linear regression for the current period.
/// </summary>
/// <returns>
/// The calculated slope value for the current period.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
}
+97 -61
View File
@@ -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;
/// <summary>
/// Represents a standard deviation calculator that measures the amount of variation or
/// dispersion of a set of values.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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();
/// <summary>
/// Initializes a new instance of the Stddev class with the specified period and
/// population flag.
/// </summary>
/// <param name="period">The period over which to calculate the standard deviation.</param>
/// <param name="isPopulation">
/// A flag indicating whether to calculate population (true) or sample (false) standard deviation.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2.
/// </exception>
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));
}
/// <summary>
/// Initializes a new instance of the Stddev class with the specified source, period,
/// and population flag.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the standard deviation.</param>
/// <param name="isPopulation">
/// A flag indicating whether to calculate population (true) or sample (false) standard deviation.
/// </param>
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();
}
/// <summary>
/// Initializes the Stddev instance by clearing the buffer.
/// </summary>
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;
/// <summary>
/// Manages the state of the Stddev instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
}
/// <summary>
/// Performs the standard deviation calculation for the current period.
/// </summary>
/// <returns>
/// The calculated standard deviation value for the current period.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
+96 -60
View File
@@ -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;
/// <summary>
/// Represents a variance calculator that measures the spread of a set of numbers
/// from their average value.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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();
/// <summary>
/// Initializes a new instance of the Variance class with the specified period and
/// population flag.
/// </summary>
/// <param name="period">The period over which to calculate the variance.</param>
/// <param name="isPopulation">
/// A flag indicating whether to calculate population (true) or sample (false) variance.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2.
/// </exception>
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));
}
/// <summary>
/// Initializes a new instance of the Variance class with the specified source, period,
/// and population flag.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the variance.</param>
/// <param name="isPopulation">
/// A flag indicating whether to calculate population (true) or sample (false) variance.
/// </param>
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();
}
/// <summary>
/// Initializes the Variance instance by clearing the buffer.
/// </summary>
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;
/// <summary>
/// Manages the state of the Variance instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
}
/// <summary>
/// Performs the variance calculation for the current period.
/// </summary>
/// <returns>
/// The calculated variance value for the current period.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
+50 -23
View File
@@ -1,17 +1,27 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Zscore : AbstractBase
{
/// <summary>
/// Represents a Z-score calculator that measures how many standard deviations
/// an element is from the mean of a set of values.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class Zscore : AbstractBase {
private readonly int Period;
private readonly CircularBuffer _buffer;
public Zscore(int period) : base()
{
if (period < 2)
{
/// <summary>
/// Initializes a new instance of the Zscore class with the specified period.
/// </summary>
/// <param name="period">The period over which to calculate the Z-score.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2.
/// </exception>
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)
{
/// <summary>
/// Initializes a new instance of the Zscore class with the specified source and period.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the Z-score.</param>
public Zscore(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
/// <summary>
/// Initializes the Zscore instance by clearing the buffer.
/// </summary>
public override void Init() {
base.Init();
_buffer.Clear();
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
/// <summary>
/// Manages the state of the Zscore instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
/// <summary>
/// Performs the Z-score calculation for the current period.
/// </summary>
/// <returns>
/// The calculated Z-score value for the most recent input in the current period.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
+48 -23
View File
@@ -1,14 +1,26 @@
namespace QuanTAlib;
public class Atr : AbstractBarBase
{
/// <summary>
/// Represents an Average True Range (ATR) calculator, a measure of market volatility.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class Atr : AbstractBarBase {
private readonly Ema _ma;
private double _prevClose, _p_prevClose;
public Atr(int period) : base()
{
if (period < 1)
{
/// <summary>
/// Initializes a new instance of the Atr class with the specified period.
/// </summary>
/// <param name="period">The period over which to calculate the ATR.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 1.
/// </exception>
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)
{
/// <summary>
/// Initializes a new instance of the Atr class with the specified source and period.
/// </summary>
/// <param name="source">The source object to subscribe to for bar updates.</param>
/// <param name="period">The period over which to calculate the ATR.</param>
public Atr(object source, int period) : this(period) {
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
}
public override void Init()
{
/// <summary>
/// Initializes the Atr instance by setting up the initial state.
/// </summary>
public override void Init() {
base.Init();
_ma.Init();
_prevClose = double.NaN;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
/// <summary>
/// Manages the state of the Atr instance based on whether a new bar is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new bar.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_index++;
_p_prevClose = _prevClose;
}
else
{
} else {
_prevClose = _p_prevClose;
}
}
protected override double Calculation()
{
/// <summary>
/// Performs the ATR calculation for the current bar.
/// </summary>
/// <returns>
/// The calculated ATR value for the current bar.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
+55 -24
View File
@@ -1,17 +1,31 @@
namespace QuanTAlib;
public class Historical : AbstractBase
{
/// <summary>
/// Represents a historical volatility calculator that measures the dispersion of returns
/// for a given security or market index over a specific period.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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)
{
/// <summary>
/// Initializes a new instance of the Historical class with the specified period and annualization flag.
/// </summary>
/// <param name="period">The period over which to calculate historical volatility.</param>
/// <param name="isAnnualized">Whether to annualize the volatility (default is true).</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2.
/// </exception>
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)
{
/// <summary>
/// Initializes a new instance of the Historical class with the specified source, period, and annualization flag.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate historical volatility.</param>
/// <param name="isAnnualized">Whether to annualize the volatility (default is true).</param>
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()
{
/// <summary>
/// Initializes the Historical instance by clearing buffers and resetting the previous close value.
/// </summary>
public override void Init() {
base.Init();
_buffer.Clear();
_logReturns.Clear();
_previousClose = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
/// <summary>
/// Manages the state of the Historical instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
/// <summary>
/// Performs the historical volatility calculation for the current period.
/// </summary>
/// <returns>
/// The calculated historical volatility value for the current period.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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);
}
+51 -22
View File
@@ -1,16 +1,31 @@
namespace QuanTAlib;
public class Realized : AbstractBase
{
/// <summary>
/// Represents a realized volatility calculator that measures the actual price fluctuations
/// observed in the market over a specific period.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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)
{
/// <summary>
/// Initializes a new instance of the Realized class with the specified period and annualization flag.
/// </summary>
/// <param name="period">The period over which to calculate realized volatility.</param>
/// <param name="isAnnualized">Whether to annualize the volatility (default is true).</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2.
/// </exception>
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()
{
/// <summary>
/// Initializes the Realized instance by clearing buffers and resetting calculation variables.
/// </summary>
public override void Init() {
base.Init();
_returns.Clear();
_previousClose = 0;
_sumSquaredReturns = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
/// <summary>
/// Manages the state of the Realized instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Input.Value;
_index++;
}
}
protected override double Calculation()
{
/// <summary>
/// Performs the realized volatility calculation for the current period.
/// </summary>
/// <returns>
/// The calculated realized volatility value for the current period.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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);
}
+102 -79
View File
@@ -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. Dorseys methodology is often cited in
technical analysis literature and further elaborated on in various technical analysis
guides and platforms.
*/
namespace QuanTAlib;
/// <summary>
/// Represents a Relative Volatility Index (RVI) calculator, which measures the direction
/// of volatility in relation to price movements.
/// </summary>
/// <remarks>
/// The RVI was introduced by Donald Dorsey in the 1993 issue of Technical Analysis
/// of Stocks &amp; 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.
/// </remarks>
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();
/// <summary>
/// Initializes a new instance of the Rvi class with the specified period.
/// </summary>
/// <param name="period">The period over which to calculate the RVI.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 2.
/// </exception>
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));
}
/// <summary>
/// Initializes a new instance of the Rvi class with the specified source and period.
/// </summary>
/// <param name="source">The source object to subscribe to for value updates.</param>
/// <param name="period">The period over which to calculate the RVI.</param>
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;
}
/// <summary>
/// Initializes the Rvi instance by setting up the initial state.
/// </summary>
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;
/// <summary>
/// Manages the state of the Rvi instance based on whether a new value is being processed.
/// </summary>
/// <param name="isNew">Indicates whether the current input is a new value.</param>
protected override void ManageState(bool isNew) {
if (isNew) {
_lastValidValue = Value;
_index++;
}
}
}
/// <summary>
/// Performs the RVI calculation for the current input.
/// </summary>
/// <returns>
/// The calculated RVI value for the current input.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}