mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 13:08:04 +00:00
XML Documentation
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
|
mode: ContinuousDeployment
|
||||||
assembly-versioning-scheme: MajorMinorPatch
|
assembly-versioning-scheme: MajorMinorPatch
|
||||||
assembly-file-versioning-scheme: MajorMinorPatch
|
assembly-file-versioning-scheme: MajorMinorPatch
|
||||||
mode: ContinuousDeployment
|
|
||||||
tag-prefix: '[vV]?'
|
tag-prefix: '[vV]?'
|
||||||
major-version-bump-message: '\+semver:\s?(breaking|major)'
|
major-version-bump-message: '\+semver:\s?(breaking|major)'
|
||||||
minor-version-bump-message: '\+semver:\s?(feature|minor)'
|
minor-version-bump-message: '\+semver:\s?(feature|minor)'
|
||||||
@@ -34,4 +34,4 @@ branches:
|
|||||||
pre-release-weight: 0
|
pre-release-weight: 0
|
||||||
|
|
||||||
ignore:
|
ignore:
|
||||||
sha: []
|
sha: []
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Backlog and done
|
# Backlog and done
|
||||||
|
|
||||||
|**QT**|**Cht**|Cmnt|Docs|isNew|Valid|
|
|**QT**|**Chart**|Cmnt|Docs|isNew|Validation|
|
||||||
|--|:--:|:--:|:--:|:--:|:--:|
|
|--|:--:|:--:|:--:|:--:|:--:|
|
||||||
|AFIRMA|✔️|||||
|
|AFIRMA|✔️|||||
|
||||||
@@ -6,13 +6,47 @@
|
|||||||
|
|
||||||
|**BASIC TRANSFORMS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
|**BASIC TRANSFORMS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
||||||
|--|:--:|:--:|:--:|:--:|:--:|
|
|--|:--:|:--:|:--:|:--:|:--:|
|
||||||
|OC2 - Midpoint price|️`.OC2`|`CandlePart.OC2`|`MidPoint`||
|
|OC2 - Midpoint price|️`.OC2`|CandlePart.OC2|MidPoint||
|
||||||
|HL2 - Median Price|️`.HL2`|`CandlePart.HL2`|`MedPrice`||
|
|HL2 - Median Price|️`.HL2`|CandlePart.HL2|MedPrice||
|
||||||
|HLC3 - Typical Price|️`.HLC3`|`CandlePart.HLC3`|`TypPrice`||
|
|HLC3 - Typical Price|️`.HLC3`|CandlePart.HLC3|TypPrice||
|
||||||
|OHL3 - Mean Price|`️.OHL3`|`CandlePart.OHL3`|||
|
|OHL3 - Mean Price|`️.OHL3`|CandlePart.OHL3`|||
|
||||||
|OHLC4 - Average Price|`️.OHLC4`|`CandlePart.OHLC4`|`AvgPrice`||
|
|OHLC4 - Average Price|`️.OHLC4`|CandlePart.OHLC4|AvgPrice||
|
||||||
|HLCC4 - Weighted Price|`️.HLCC4`||`WclPrice`||
|
|HLCC4 - Weighted Price|`️.HLCC4`||WclPrice||
|
||||||
|<br>||||
|
|<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|
|
|**AVERAGES & TRENDS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
||||||
|AFIRMA - Autoregressive Finite Impulse Response Moving Average|`Afirma`||||
|
|AFIRMA - Autoregressive Finite Impulse Response Moving Average|`Afirma`||||
|
||||||
|ALMA - Arnaud Legoux Moving Average|`Alma`|`✔️`|||
|
|ALMA - Arnaud Legoux Moving Average|`Alma`|`✔️`|||
|
||||||
@@ -57,84 +91,83 @@
|
|||||||
|ZLEMA - Zero Lag EMA Average|`Zlema`|||`✔️`|
|
|ZLEMA - Zero Lag EMA Average|`Zlema`|||`✔️`|
|
||||||
|<br>||||
|
|<br>||||
|
||||||
|**VOLATILITY INDICATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
|**VOLATILITY INDICATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
||||||
|ADL - Chaikin Accumulation Distribution Line||`GetAdl`|`Ad`||
|
|ADL - Chaikin Accumulation Distribution Line||GetAdl|Ad||
|
||||||
|ADOSC - Chaikin Accumulation Distribution Oscillator||`GetChaikinOsc`|`AdOsc`||
|
|ADOSC - Chaikin Accumulation Distribution Oscillator||GetChaikinOsc|AdOsc||
|
||||||
|ATR - Average True Range||`GetAtr`|`Atr`||
|
|ATR - Average True Range||GetAtr|Atr||
|
||||||
|ATRP - Average True Range Percent|||||
|
|ATRP - Average True Range Percent|||||
|
||||||
|ATRSTOP - ATR Trailing Stop ||`GetAtrStop`|||
|
|ATRSTOP - ATR Trailing Stop ||GetAtrStop|||
|
||||||
|BETA - Beta coefficient|||||
|
|BBANDS - Bollinger Bands®||BollingerBands|||
|
||||||
|BBANDS - Bollinger Bands®||`BollingerBands`|||
|
|CHAND - Chandelier Exit||GetChandelier|||
|
||||||
|CHAND - Chandelier Exit||`GetChandelier`|||
|
|CRSI - Connor RSI||GetConnorsRsi|||
|
||||||
|CRSI - Connor RSI||`GetConnorsRsi`|||
|
|
||||||
|CVI - Chaikins Volatility|||||
|
|CVI - Chaikins Volatility|||||
|
||||||
|DON - Donchian Channels||`GetDonchian`|||
|
|DON - Donchian Channels||GetDonchian|||
|
||||||
|FCB - Fractal Chaos Bands||`GetFcb`|||
|
|FCB - Fractal Chaos Bands||GetFcb|||
|
||||||
|FISHER - Fisher Transform|||||
|
|FISHER - Fisher Transform|||||
|
||||||
|HV - Historical Volatility|||||
|
|HV - Historical Volatility|||||
|
||||||
|ICH - Ichimoku Cloud||`GetIchimoku`|||
|
|ICH - Ichimoku Cloud||GetIchimoku|||
|
||||||
|KEL - Keltner Channels||`GetKeltner`|||
|
|KEL - Keltner Channels||GetKeltner|||
|
||||||
|NATR - Normalized Average True Range||`GetAtr`|||
|
|NATR - Normalized Average True Range||GetAtr|||
|
||||||
|CHN - Price Channel Indicator|||||
|
|CHN - Price Channel Indicator|||||
|
||||||
|RSI - Relative Strength Index||`GetRsi`|||
|
|RSI - Relative Strength Index||GetRsi|||
|
||||||
|SAR - Parabolic Stop and Reverse||`GetParabolicSar`|||
|
|SAR - Parabolic Stop and Reverse||GetParabolicSar|||
|
||||||
|SRSI - Stochastic RSI||`GetStochRsi`|||
|
|SRSI - Stochastic RSI||GetStochRsi|||
|
||||||
|STARC - Starc Bands||`GetStarcBands`|||
|
|STARC - Starc Bands||GetStarcBands|||
|
||||||
|TR - True Range|||||
|
|TR - True Range|||||
|
||||||
|UI - Ulcer Index||`GetUlcerIndex`|||
|
|UI - Ulcer Index||GetUlcerIndex|||
|
||||||
|VSTOP - Volatility Stop||`GetVolatilityStop`|||
|
|VSTOP - Volatility Stop||GetVolatilityStop|||
|
||||||
|<br>||||
|
|<br>||||
|
||||||
|**MOMENTUM INDICATORS & OSCILLATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
|**MOMENTUM INDICATORS & OSCILLATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
||||||
|AC - Acceleration Oscillator|||||
|
|AC - Acceleration Oscillator|||||
|
||||||
|ADX - Average Directional Movement Index||`GetAdx`|`Adx`||
|
|ADX - Average Directional Movement Index||GetAdx|Adx||
|
||||||
|ADXR - Average Directional Movement Index|| `Rating`|`Adxr`||
|
|ADXR - Average Directional Movement Index||Rating|Adxr||
|
||||||
|AO - Awesome Oscillator||`GetAwesome`|||
|
|AO - Awesome Oscillator||GetAwesome|||
|
||||||
|APO - Absolute Price Oscillator||`Apo`|||
|
|APO - Absolute Price Oscillator||Apo|||
|
||||||
|AROON - Aroon oscillator||`GetAroon`|`Aroon`||
|
|AROON - Aroon oscillator||GetAroon|Aroon||
|
||||||
|BOP - Balance of Power||`GetBop`|`Bop`||
|
|BOP - Balance of Power||GetBop|Bop||
|
||||||
|CCI - Commodity Channel Index||`GetCci`|`Cci`||
|
|CCI - Commodity Channel Index||GetCci|Cci||
|
||||||
|CFO - Chande Forcast Oscillator|||||
|
|CFO - Chande Forcast Oscillator|||||
|
||||||
|CMO - Chande Momentum Oscillator||`GetCmo`|`Cmo`||
|
|CMO - Chande Momentum Oscillator||GetCmo|Cmo||
|
||||||
|CHOP - Choppiness Index||`GetChop`|||
|
|CHOP - Choppiness Index||GetChop|||
|
||||||
|COG - Center of Gravity|||||
|
|COG - Center of Gravity|||||
|
||||||
|COPPOCK - Coppock Curve|||||
|
|COPPOCK - Coppock Curve|||||
|
||||||
|CTI - Ehler's Correlation Trend Indicator|||||
|
|CTI - Ehler's Correlation Trend Indicator|||||
|
||||||
|DPO - Detrended Price Oscillator||`GetDpo`|||
|
|DPO - Detrended Price Oscillator||GetDpo|||
|
||||||
|DMI - Directional Movement Index||`GetDmi`|||
|
|DMI - Directional Movement Index||GetDmi|||
|
||||||
|EFI - Elder Ray's Force Index||`GetElderRay`|||
|
|EFI - Elder Ray's Force Index||GetElderRay|||
|
||||||
|FOSC - Forecast oscillator||||||
|
|FOSC - Forecast oscillator||||||
|
||||||
|GATOR - Gator oscillator||`GetGator`|||
|
|GATOR - Gator oscillator||GetGator|||
|
||||||
|HURST - Hurst Exponent||`GetHurst`|||
|
|HURST - Hurst Exponent||GetHurst|||
|
||||||
|KRI - Kairi Relative Index|||||
|
|KRI - Kairi Relative Index|||||
|
||||||
|KVO - Klinger Volume Oscillator||`GetKvo`||||
|
|KVO - Klinger Volume Oscillator||GetKvo||||
|
||||||
|MFI - Money Flow Index||`GetMfi`|||
|
|MFI - Money Flow Index||GetMfi|||
|
||||||
|MOM - Momentum|||||
|
|MOM - Momentum|||||
|
||||||
|NVI - Negative Volume Index|||||
|
|NVI - Negative Volume Index|||||
|
||||||
|PO - Price Oscillator|||||
|
|PO - Price Oscillator|||||
|
||||||
|PPO - Percentage Price Oscillator|||||
|
|PPO - Percentage Price Oscillator|||||
|
||||||
|PMO - Price Momentum Oscillator||`GetPmo`|||
|
|PMO - Price Momentum Oscillator||GetPmo|||
|
||||||
|PVI - Positive Volume Index|||||
|
|PVI - Positive Volume Index|||||
|
||||||
|ROC - Rate of Change||GetRoc|||
|
|ROC - Rate of Change||GetRoc|||
|
||||||
|RVGI - Relative Vigor Index|||||
|
|RVGI - Relative Vigor Index|||||
|
||||||
|SMI - Stochastic Momentum Index||`GetSmi`|||
|
|SMI - Stochastic Momentum Index||GetSmi|||
|
||||||
|STC - Schaff Trend Cycle||`GetStc`|||
|
|STC - Schaff Trend Cycle||GetStc|||
|
||||||
|STOCH - Stochastic Oscillator||`GetStoch`|||
|
|STOCH - Stochastic Oscillator||`GetStoch|||
|
||||||
|TRIX - 1-day ROC of TEMA||`GetTrix`||`trix.Run`|
|
|TRIX - 1-day ROC of TEMA||GetTrix||trix.Run|
|
||||||
|TSI - True Strength Index||`GetTsi`|||
|
|TSI - True Strength Index||GetTsi|||
|
||||||
|UO - Ultimate Oscillator||`GetUltimate`|||
|
|UO - Ultimate Oscillator||GetUltimate|||
|
||||||
|WILLR - Larry Williams' %R||GetWillia`msR`|||
|
|WILLR - Larry Williams' %R||GetWilliamsR|||
|
||||||
|WGAT - Williams Alligator||`GetAlligator`|||
|
|WGAT - Williams Alligator||GetAlligator|||
|
||||||
|<br>||||
|
|<br>||||
|
||||||
|**VOLUME INDICATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
|**VOLUME INDICATORS**|**QuanTALib**|Skender.Stock|TALib.NETCore|Tulip.NETCore|Trady|
|
||||||
|AOBV - Archer On-Balance Volume|||||
|
|AOBV - Archer On-Balance Volume|||||
|
||||||
|CMF - Chaikin Money Flow||`GetCmf`|||
|
|CMF - Chaikin Money Flow||GetCmf|||
|
||||||
|EOM - Ease of Movement|||||
|
|EOM - Ease of Movement|||||
|
||||||
|KVO - Klinger Volume Oscilaltor|||||
|
|KVO - Klinger Volume Oscilaltor|||||
|
||||||
|OBV - On-Balance Volume||`GetObv`|||
|
|OBV - On-Balance Volume||GetObv|||
|
||||||
|PRS - Price Relative Strength||`GetPrs`|||
|
|PRS - Price Relative Strength||`GetPrs|||
|
||||||
|PVOL - Price-Volume|||||
|
|PVOL - Price-Volume|||||
|
||||||
|PVO - Percentage Volume Oscillator||`GetPvo`|||
|
|PVO - Percentage Volume Oscillator||GetPvo|||
|
||||||
|PVR - Price Volume Rank|||||
|
|PVR - Price Volume Rank|||||
|
||||||
|PVT - Price Volume Trend|||||
|
|PVT - Price Volume Trend|||||
|
||||||
|VP - Volume Profile|||||
|
|VP - Volume Profile|||||
|
||||||
|VWAP - Volume Weighted Average Price||`GetVwap`|||
|
|VWAP - Volume Weighted Average Price||GetVwap|||
|
||||||
|VWMA - Volume Weighted Moving Average||`GetVwma`||||
|
|VWMA - Volume Weighted Moving Average||GetVwma||||
|
||||||
|
|||||||
+44
-25
@@ -1,56 +1,76 @@
|
|||||||
namespace QuanTAlib;
|
namespace QuanTAlib;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a base implementation for financial indicators in the QuanTAlib library.
|
/// Provides a base implementation for financial indicators that work with bar data in the QuanTAlib library.
|
||||||
/// This abstract class implements the iTValue interface and defines common properties
|
|
||||||
/// and methods used by inheriting indicator types.
|
|
||||||
/// </summary>
|
/// </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 DateTime Time { get; set; }
|
||||||
public double Value { get; set; }
|
public double Value { get; set; }
|
||||||
public bool IsNew { get; set; }
|
public bool IsNew { get; set; }
|
||||||
public bool IsHot { get; set; }
|
public bool IsHot { get; set; }
|
||||||
|
|
||||||
public TBar Input { get; set; }
|
public TBar Input { get; set; }
|
||||||
public String Name { get; set; } = "";
|
public String Name { get; set; } = "";
|
||||||
public int WarmupPeriod { get; set; }
|
public int WarmupPeriod { get; set; }
|
||||||
|
public TValue Tick => new(Time, Value, IsNew, IsHot);
|
||||||
public TValue Tick => new(Time, Value, IsNew, IsHot); // Stores the current value of indicator
|
public event ValueSignal Pub = delegate { };
|
||||||
public event ValueSignal Pub = delegate { }; // Publisher of generated values
|
protected int _index;
|
||||||
|
|
||||||
protected int _index; //tracking the position of output
|
|
||||||
protected double _lastValidValue;
|
protected double _lastValidValue;
|
||||||
// other _internal vars defined here
|
protected AbstractBarBase() {
|
||||||
|
// Add parameters into constructor if needed
|
||||||
protected AbstractBarBase()
|
|
||||||
{ //add parameters into constructor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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 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;
|
_index = 0;
|
||||||
_lastValidValue = 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;
|
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));
|
return Process(new TValue(Time: input.Time, Value: GetLastValid(), IsNew: input.IsNew, IsHot: true));
|
||||||
}
|
}
|
||||||
this.Value = Calculation();
|
this.Value = Calculation();
|
||||||
return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot));
|
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;
|
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);
|
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();
|
protected abstract double Calculation();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -59,8 +79,7 @@ public abstract class AbstractBarBase : iTValue
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The calculated TValue to process.</param>
|
/// <param name="value">The calculated TValue to process.</param>
|
||||||
/// <returns>The processed TValue.</returns>
|
/// <returns>The processed TValue.</returns>
|
||||||
protected virtual TValue Process(TValue value)
|
protected virtual TValue Process(TValue value) {
|
||||||
{
|
|
||||||
this.Time = value.Time;
|
this.Time = value.Time;
|
||||||
this.Value = value.Value;
|
this.Value = value.Value;
|
||||||
this.IsNew = value.IsNew;
|
this.IsNew = value.IsNew;
|
||||||
|
|||||||
+32
-12
@@ -2,29 +2,29 @@ namespace QuanTAlib;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a base implementation for financial indicators in the QuanTAlib library.
|
/// 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>
|
/// </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 abstract class AbstractBase : iTValue
|
||||||
{
|
{
|
||||||
public DateTime Time { get; set; }
|
public DateTime Time { get; set; }
|
||||||
public double Value { get; set; }
|
public double Value { get; set; }
|
||||||
public bool IsNew { get; set; }
|
public bool IsNew { get; set; }
|
||||||
public bool IsHot { get; set; }
|
public bool IsHot { get; set; }
|
||||||
|
|
||||||
public TValue Input { get; set; }
|
public TValue Input { get; set; }
|
||||||
public String Name { get; set; } = "";
|
public String Name { get; set; } = "";
|
||||||
public int WarmupPeriod { get; set; }
|
public int WarmupPeriod { get; set; }
|
||||||
|
public TValue Tick => new(Time, Value, IsNew, IsHot);
|
||||||
public TValue Tick => new(Time, Value, IsNew, IsHot); // Stores the current value of indicator
|
public event ValueSignal Pub = delegate { };
|
||||||
public event ValueSignal Pub = delegate { }; // Publisher of generated values
|
protected int _index;
|
||||||
|
|
||||||
protected int _index; //tracking the position of output
|
|
||||||
protected double _lastValidValue;
|
protected double _lastValidValue;
|
||||||
// other _internal vars defined here
|
|
||||||
|
|
||||||
protected AbstractBase()
|
protected AbstractBase()
|
||||||
{ //add parameters into constructor
|
{
|
||||||
|
// Add parameters into constructor if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -34,6 +34,9 @@ public abstract class AbstractBase : iTValue
|
|||||||
/// <param name="args">The argument containing the new data point.</param>
|
/// <param name="args">The argument containing the new data point.</param>
|
||||||
public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick);
|
public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the indicator's state.
|
||||||
|
/// </summary>
|
||||||
public virtual void Init()
|
public virtual void Init()
|
||||||
{
|
{
|
||||||
_index = 0;
|
_index = 0;
|
||||||
@@ -41,11 +44,14 @@ public abstract class AbstractBase : iTValue
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the indicator value based on the input; calls specific Calculation() method
|
/// Calculates the indicator value based on the input.
|
||||||
/// where implementation is
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">The input value for the calculation.</param>
|
/// <param name="input">The input value for the calculation.</param>
|
||||||
/// <returns>A TValue representing the calculated indicator value.</returns>
|
/// <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)
|
public virtual TValue Calc(TValue input)
|
||||||
{
|
{
|
||||||
Input = 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));
|
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()
|
protected virtual double GetLastValid()
|
||||||
{
|
{
|
||||||
return this.Value;
|
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);
|
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();
|
protected abstract double Calculation();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
+146
-98
@@ -4,55 +4,72 @@ using System.Numerics;
|
|||||||
|
|
||||||
namespace QuanTAlib;
|
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 readonly double[] _buffer;
|
||||||
private int _start = 0;
|
private int _start = 0;
|
||||||
private int _size = 0;
|
private int _size = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum number of elements that can be contained in the buffer.
|
||||||
|
/// </summary>
|
||||||
public int Capacity { get; }
|
public int Capacity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of elements currently contained in the buffer.
|
||||||
|
/// </summary>
|
||||||
public int Count => _size;
|
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;
|
Capacity = capacity;
|
||||||
_buffer = GC.AllocateArray<double>(capacity, pinned: true);
|
_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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Add(double item, bool isNew = true)
|
public void Add(double item, bool isNew = true) {
|
||||||
{
|
if (_size == 0 || isNew) {
|
||||||
if (_size == 0 || isNew)
|
if (_size < Capacity) {
|
||||||
{
|
|
||||||
if (_size < Capacity)
|
|
||||||
{
|
|
||||||
_buffer[(_start + _size) % Capacity] = item;
|
_buffer[(_start + _size) % Capacity] = item;
|
||||||
_size++;
|
_size++;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
_buffer[_start] = item;
|
_buffer[_start] = item;
|
||||||
_start = (_start + 1) % Capacity;
|
_start = (_start + 1) % Capacity;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
_buffer[(_start + _size - 1) % Capacity] = item;
|
_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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get {
|
||||||
{
|
|
||||||
int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value;
|
int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value;
|
||||||
actualIndex = Math.Clamp(actualIndex, 0, _size - 1);
|
actualIndex = Math.Clamp(actualIndex, 0, _size - 1);
|
||||||
return _buffer[(_start + actualIndex) % Capacity];
|
return _buffer[(_start + actualIndex) % Capacity];
|
||||||
}
|
}
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
set
|
set {
|
||||||
{
|
|
||||||
int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value;
|
int actualIndex = index.IsFromEnd ? _size - index.Value : index.Value;
|
||||||
actualIndex = Math.Clamp(actualIndex, 0, _size - 1);
|
actualIndex = Math.Clamp(actualIndex, 0, _size - 1);
|
||||||
_buffer[(_start + actualIndex) % Capacity] = value;
|
_buffer[(_start + actualIndex) % Capacity] = value;
|
||||||
@@ -60,54 +77,66 @@ public class CircularBuffer : IEnumerable<double>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static void ThrowArgumentOutOfRangeException()
|
private static void ThrowArgumentOutOfRangeException() {
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException("index", "Index is out of range.");
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Newest()
|
public double Newest() {
|
||||||
{
|
|
||||||
if (_size == 0)
|
if (_size == 0)
|
||||||
return 0;
|
return 0;
|
||||||
return _buffer[(_start + _size - 1) % Capacity];
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Oldest()
|
public double Oldest() {
|
||||||
{
|
|
||||||
if (_size == 0)
|
if (_size == 0)
|
||||||
ThrowInvalidOperationException();
|
ThrowInvalidOperationException();
|
||||||
return _buffer[_start];
|
return _buffer[_start];
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static void ThrowInvalidOperationException()
|
private static void ThrowInvalidOperationException() {
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Buffer is empty.");
|
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);
|
public Enumerator GetEnumerator() => new(this);
|
||||||
IEnumerator<double> IEnumerable<double>.GetEnumerator() => GetEnumerator();
|
IEnumerator<double> IEnumerable<double>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.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 readonly CircularBuffer _buffer;
|
||||||
private int _index;
|
private int _index;
|
||||||
private double _current;
|
private double _current;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal Enumerator(CircularBuffer buffer)
|
internal Enumerator(CircularBuffer buffer) {
|
||||||
{
|
|
||||||
_buffer = buffer;
|
_buffer = buffer;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
_current = default;
|
_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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool MoveNext()
|
public bool MoveNext() {
|
||||||
{
|
|
||||||
if (_index + 1 >= _buffer._size)
|
if (_index + 1 >= _buffer._size)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -116,92 +145,122 @@ public class CircularBuffer : IEnumerable<double>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element in the buffer at the current position of the enumerator.
|
||||||
|
/// </summary>
|
||||||
public double Current => _current;
|
public double Current => _current;
|
||||||
object IEnumerator.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;
|
_index = -1;
|
||||||
_current = default;
|
_current = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the enumerator.
|
||||||
|
/// </summary>
|
||||||
public void Dispose() { }
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void CopyTo(double[] destination, int destinationIndex)
|
public void CopyTo(double[] destination, int destinationIndex) {
|
||||||
{
|
|
||||||
if (_size == 0)
|
if (_size == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_start + _size <= Capacity)
|
if (_start + _size <= Capacity) {
|
||||||
{
|
|
||||||
Array.Copy(_buffer, _start, destination, destinationIndex, _size);
|
Array.Copy(_buffer, _start, destination, destinationIndex, _size);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
int firstPartLength = Capacity - _start;
|
int firstPartLength = Capacity - _start;
|
||||||
Array.Copy(_buffer, _start, destination, destinationIndex, firstPartLength);
|
Array.Copy(_buffer, _start, destination, destinationIndex, firstPartLength);
|
||||||
Array.Copy(_buffer, 0, destination, destinationIndex + firstPartLength, _size - 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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ReadOnlySpan<double> GetSpan()
|
public ReadOnlySpan<double> GetSpan() {
|
||||||
{
|
|
||||||
if (_size == 0)
|
if (_size == 0)
|
||||||
return ReadOnlySpan<double>.Empty;
|
return ReadOnlySpan<double>.Empty;
|
||||||
|
|
||||||
if (_start + _size <= Capacity)
|
if (_start + _size <= Capacity) {
|
||||||
{
|
|
||||||
return new ReadOnlySpan<double>(_buffer, _start, _size);
|
return new ReadOnlySpan<double>(_buffer, _start, _size);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ReadOnlySpan<double>(ToArray());
|
return new ReadOnlySpan<double>(ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the internal buffer array.
|
||||||
|
/// </summary>
|
||||||
public double[] InternalBuffer => _buffer;
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ReadOnlySpan<double> GetInternalSpan() => _buffer.AsSpan();
|
public ReadOnlySpan<double> GetInternalSpan() => _buffer.AsSpan();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all elements from the buffer.
|
||||||
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Clear()
|
public void Clear() {
|
||||||
{
|
|
||||||
Array.Clear(_buffer, 0, _buffer.Length);
|
Array.Clear(_buffer, 0, _buffer.Length);
|
||||||
_start = 0;
|
_start = 0;
|
||||||
_size = 0;
|
_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the maximum value in the buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The maximum value in the buffer.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Max()
|
public double Max() {
|
||||||
{
|
|
||||||
if (_size == 0)
|
if (_size == 0)
|
||||||
ThrowInvalidOperationException();
|
ThrowInvalidOperationException();
|
||||||
|
|
||||||
return MaxSimd();
|
return MaxSimd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the minimum value in the buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The minimum value in the buffer.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Min()
|
public double Min() {
|
||||||
{
|
|
||||||
if (_size == 0)
|
if (_size == 0)
|
||||||
ThrowInvalidOperationException();
|
ThrowInvalidOperationException();
|
||||||
|
|
||||||
return MinSimd();
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Sum()
|
public double Sum() {
|
||||||
{
|
|
||||||
return SumSimd();
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Average()
|
public double Average() {
|
||||||
{
|
|
||||||
if (_size == 0)
|
if (_size == 0)
|
||||||
ThrowInvalidOperationException();
|
ThrowInvalidOperationException();
|
||||||
|
|
||||||
@@ -209,26 +268,22 @@ public class CircularBuffer : IEnumerable<double>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private double MaxSimd()
|
private double MaxSimd() {
|
||||||
{
|
|
||||||
var span = GetSpan();
|
var span = GetSpan();
|
||||||
var vectorSize = Vector<double>.Count;
|
var vectorSize = Vector<double>.Count;
|
||||||
var maxVector = new Vector<double>(double.MinValue);
|
var maxVector = new Vector<double>(double.MinValue);
|
||||||
|
|
||||||
int i = 0;
|
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)));
|
maxVector = Vector.Max(maxVector, new Vector<double>(span.Slice(i, vectorSize)));
|
||||||
}
|
}
|
||||||
|
|
||||||
double max = double.MinValue;
|
double max = double.MinValue;
|
||||||
for (int j = 0; j < vectorSize; j++)
|
for (int j = 0; j < vectorSize; j++) {
|
||||||
{
|
|
||||||
max = Math.Max(max, maxVector[j]);
|
max = Math.Max(max, maxVector[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++) {
|
||||||
{
|
|
||||||
max = Math.Max(max, span[i]);
|
max = Math.Max(max, span[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,26 +291,22 @@ public class CircularBuffer : IEnumerable<double>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private double MinSimd()
|
private double MinSimd() {
|
||||||
{
|
|
||||||
var span = GetSpan();
|
var span = GetSpan();
|
||||||
var vectorSize = Vector<double>.Count;
|
var vectorSize = Vector<double>.Count;
|
||||||
var minVector = new Vector<double>(double.MaxValue);
|
var minVector = new Vector<double>(double.MaxValue);
|
||||||
|
|
||||||
int i = 0;
|
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)));
|
minVector = Vector.Min(minVector, new Vector<double>(span.Slice(i, vectorSize)));
|
||||||
}
|
}
|
||||||
|
|
||||||
double min = double.MaxValue;
|
double min = double.MaxValue;
|
||||||
for (int j = 0; j < vectorSize; j++)
|
for (int j = 0; j < vectorSize; j++) {
|
||||||
{
|
|
||||||
min = Math.Min(min, minVector[j]);
|
min = Math.Min(min, minVector[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++) {
|
||||||
{
|
|
||||||
min = Math.Min(min, span[i]);
|
min = Math.Min(min, span[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,45 +314,46 @@ public class CircularBuffer : IEnumerable<double>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private double SumSimd()
|
private double SumSimd() {
|
||||||
{
|
|
||||||
var span = GetSpan();
|
var span = GetSpan();
|
||||||
var vectorSize = Vector<double>.Count;
|
var vectorSize = Vector<double>.Count;
|
||||||
var sumVector = Vector<double>.Zero;
|
var sumVector = Vector<double>.Zero;
|
||||||
|
|
||||||
int i = 0;
|
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));
|
sumVector += new Vector<double>(span.Slice(i, vectorSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
double sum = 0;
|
double sum = 0;
|
||||||
for (int j = 0; j < vectorSize; j++)
|
for (int j = 0; j < vectorSize; j++) {
|
||||||
{
|
|
||||||
sum += sumVector[j];
|
sum += sumVector[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < span.Length; i++)
|
for (; i < span.Length; i++) {
|
||||||
{
|
|
||||||
sum += span[i];
|
sum += span[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return sum;
|
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];
|
double[] array = new double[_size];
|
||||||
CopyTo(array, 0);
|
CopyTo(array, 0);
|
||||||
return array;
|
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;
|
const int MinimumPartitionSize = 1024;
|
||||||
|
|
||||||
if (_size < MinimumPartitionSize)
|
if (_size < MinimumPartitionSize) {
|
||||||
{
|
|
||||||
var span = GetSpan();
|
var span = GetSpan();
|
||||||
var array = span.ToArray();
|
var array = span.ToArray();
|
||||||
operation(array, 0, array.Length);
|
operation(array, 0, array.Length);
|
||||||
@@ -311,8 +363,7 @@ public class CircularBuffer : IEnumerable<double>
|
|||||||
int partitionCount = Environment.ProcessorCount;
|
int partitionCount = Environment.ProcessorCount;
|
||||||
int partitionSize = _size / partitionCount;
|
int partitionSize = _size / partitionCount;
|
||||||
|
|
||||||
if (partitionSize < MinimumPartitionSize)
|
if (partitionSize < MinimumPartitionSize) {
|
||||||
{
|
|
||||||
partitionCount = Math.Max(1, _size / MinimumPartitionSize);
|
partitionCount = Math.Max(1, _size / MinimumPartitionSize);
|
||||||
partitionSize = _size / partitionCount;
|
partitionSize = _size / partitionCount;
|
||||||
}
|
}
|
||||||
@@ -320,13 +371,10 @@ public class CircularBuffer : IEnumerable<double>
|
|||||||
var buffer = ToArray();
|
var buffer = ToArray();
|
||||||
var results = new double[partitionCount];
|
var results = new double[partitionCount];
|
||||||
|
|
||||||
Parallel.For(0, partitionCount, i =>
|
Parallel.For(0, partitionCount, i => {
|
||||||
{
|
|
||||||
int start = i * partitionSize;
|
int start = i * partitionSize;
|
||||||
int length = (i == partitionCount - 1) ? _size - start : partitionSize;
|
int length = (i == partitionCount - 1) ? _size - start : partitionSize;
|
||||||
results[i] = operation(buffer, start, length);
|
results[i] = operation(buffer, start, length);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+139
-111
@@ -1,128 +1,156 @@
|
|||||||
using System;
|
namespace QuanTAlib;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
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;
|
if (period <= 2)
|
||||||
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)
|
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,
|
double r = sumSqXY / (stdDevX * stdDevY) / count;
|
||||||
"Period must be greater than 2 for Curvature calculation.");
|
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;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public Curvature(object source, int period) : this(period)
|
|
||||||
{
|
{
|
||||||
var pubEvent = source.GetType().GetEvent("Pub");
|
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
base.Init();
|
|
||||||
_slopeBuffer.Clear();
|
|
||||||
Intercept = null;
|
Intercept = null;
|
||||||
StdDev = null;
|
StdDev = null;
|
||||||
RSquared = null;
|
RSquared = null;
|
||||||
Line = null;
|
Line = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
IsHot = _slopeBuffer.Count == _period;
|
||||||
{
|
return curvature;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,27 @@
|
|||||||
namespace QuanTAlib;
|
namespace QuanTAlib;
|
||||||
|
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Linq;
|
/// Measures the unpredictability of data using Shannon's Entropy.
|
||||||
|
/// Provides insights into the randomness or information content of the time series.
|
||||||
// Shannon's Entropy calculation
|
/// </summary>
|
||||||
public class Entropy : AbstractBase
|
public class Entropy : AbstractBase
|
||||||
{
|
{
|
||||||
private readonly int Period;
|
private readonly int Period;
|
||||||
private readonly CircularBuffer _buffer;
|
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()
|
public Entropy(int period) : base()
|
||||||
{
|
{
|
||||||
if (period < 2)
|
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;
|
Period = period;
|
||||||
WarmupPeriod = 2;
|
WarmupPeriod = 2;
|
||||||
@@ -22,18 +30,30 @@ public class Entropy : AbstractBase
|
|||||||
Init();
|
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)
|
public Entropy(object source, int period) : this(period)
|
||||||
{
|
{
|
||||||
var pubEvent = source.GetType().GetEvent("Pub");
|
var pubEvent = source.GetType().GetEvent("Pub");
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the Entropy indicator to its initial state.
|
||||||
|
/// </summary>
|
||||||
public override void Init()
|
public override void Init()
|
||||||
{
|
{
|
||||||
base.Init();
|
base.Init();
|
||||||
_buffer.Clear();
|
_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)
|
protected override void ManageState(bool isNew)
|
||||||
{
|
{
|
||||||
if (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()
|
protected override double Calculation()
|
||||||
{
|
{
|
||||||
ManageState(Input.IsNew);
|
ManageState(Input.IsNew);
|
||||||
@@ -70,9 +101,11 @@ public class Entropy : AbstractBase
|
|||||||
double maxEntropy = Math.Log2(uniqueValueCount);
|
double maxEntropy = Math.Log2(uniqueValueCount);
|
||||||
|
|
||||||
entropy = entropy == 0 ? 1 : entropy / maxEntropy;
|
entropy = entropy == 0 ? 1 : entropy / maxEntropy;
|
||||||
|
|
||||||
}
|
}
|
||||||
else { entropy = 1; }
|
else
|
||||||
|
{
|
||||||
|
entropy = 1; // Default to maximum entropy when insufficient data
|
||||||
|
}
|
||||||
|
|
||||||
IsHot = _buffer.Count >= Period;
|
IsHot = _buffer.Count >= Period;
|
||||||
return entropy;
|
return entropy;
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
namespace QuanTAlib;
|
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
|
public class Kurtosis : AbstractBase
|
||||||
{
|
{
|
||||||
private readonly int Period;
|
private readonly int Period;
|
||||||
private readonly CircularBuffer _buffer;
|
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()
|
public Kurtosis(int period) : base()
|
||||||
{
|
{
|
||||||
if (period < 4)
|
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;
|
Period = period;
|
||||||
WarmupPeriod = Period - 1;
|
WarmupPeriod = Period - 1;
|
||||||
@@ -19,18 +30,30 @@ public class Kurtosis : AbstractBase
|
|||||||
Init();
|
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)
|
public Kurtosis(object source, int period) : this(period)
|
||||||
{
|
{
|
||||||
var pubEvent = source.GetType().GetEvent("Pub");
|
var pubEvent = source.GetType().GetEvent("Pub");
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the Kurtosis indicator to its initial state.
|
||||||
|
/// </summary>
|
||||||
public override void Init()
|
public override void Init()
|
||||||
{
|
{
|
||||||
base.Init();
|
base.Init();
|
||||||
_buffer.Clear();
|
_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)
|
protected override void ManageState(bool isNew)
|
||||||
{
|
{
|
||||||
if (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()
|
protected override double Calculation()
|
||||||
{
|
{
|
||||||
ManageState(Input.IsNew);
|
ManageState(Input.IsNew);
|
||||||
@@ -65,7 +99,7 @@ public class Kurtosis : AbstractBase
|
|||||||
|
|
||||||
double variance = s2 / (n - 1);
|
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))
|
kurtosis = (n * (n + 1) * s4) / (variance * variance * (n - 3) * (n - 1) * (n - 2))
|
||||||
- (3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)));
|
- (3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)));
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-66
@@ -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;
|
if (period < 1)
|
||||||
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)
|
throw new ArgumentOutOfRangeException(nameof(period),
|
||||||
{
|
"Period must be greater than or equal to 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();
|
|
||||||
}
|
}
|
||||||
|
if (decay < 0)
|
||||||
public Max(object source, int period, double decay = 0) : this(period, decay)
|
|
||||||
{
|
{
|
||||||
var pubEvent = source.GetType().GetEvent("Pub");
|
throw new ArgumentOutOfRangeException(nameof(decay),
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
"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();
|
_p_currentMax = _currentMax;
|
||||||
_currentMax = double.MinValue;
|
_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;
|
_timeSinceNewMax = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period);
|
||||||
{
|
_currentMax = _currentMax - decayRate * (_currentMax - _buffer.Average());
|
||||||
if (isNew)
|
_currentMax = Math.Min(_currentMax, _buffer.Max());
|
||||||
{
|
|
||||||
_p_currentMax = _currentMax;
|
|
||||||
_lastValidValue = Input.Value;
|
|
||||||
_index++;
|
|
||||||
_timeSinceNewMax++;
|
|
||||||
_p_timeSinceNewMax = _timeSinceNewMax;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_currentMax = _p_currentMax;
|
|
||||||
_timeSinceNewMax = _p_timeSinceNewMax;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double Calculation()
|
IsHot = true;
|
||||||
{
|
return _currentMax;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-52
@@ -1,69 +1,99 @@
|
|||||||
using System;
|
namespace QuanTAlib;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
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;
|
if (period < 1)
|
||||||
private readonly CircularBuffer _buffer;
|
|
||||||
|
|
||||||
public Median(int period) : base()
|
|
||||||
{
|
{
|
||||||
if (period < 1)
|
throw new ArgumentOutOfRangeException(nameof(period),
|
||||||
{
|
"Period must be greater than or equal to 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();
|
|
||||||
}
|
}
|
||||||
|
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");
|
_lastValidValue = Input.Value;
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
_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;
|
// Even number of values: average of two middle values
|
||||||
_index++;
|
median = (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
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
@@ -1,80 +1,106 @@
|
|||||||
using System;
|
namespace QuanTAlib;
|
||||||
|
|
||||||
namespace QuanTAlib
|
/// <summary>
|
||||||
{
|
/// Represents a minimum value calculator with optional decay over a specified period.
|
||||||
public class Min : AbstractBase
|
/// 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.
|
||||||
private readonly int Period;
|
/// </summary>
|
||||||
private readonly CircularBuffer _buffer;
|
/// <remarks>
|
||||||
private readonly double _halfLife;
|
/// The Min class uses a circular buffer to store values and calculates the minimum
|
||||||
private double _currentMin, _p_currentMin;
|
/// efficiently. It also implements a decay mechanism to adjust the minimum value over
|
||||||
private int _timeSinceNewMin, _p_timeSinceNewMin;
|
/// 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()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Min class with the specified period and decay.
|
||||||
if (period < 1)
|
/// </summary>
|
||||||
{
|
/// <param name="period">The period over which to calculate the minimum value.</param>
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
/// <param name="decay">The decay factor to apply to older values (default is 0).</param>
|
||||||
}
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
if (decay < 0)
|
/// Thrown when period is less than 1 or decay is negative.
|
||||||
{
|
/// </exception>
|
||||||
throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
|
public Min(int period, double decay = 0) : base() {
|
||||||
}
|
if (period < 1) {
|
||||||
Period = period;
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||||
WarmupPeriod = 0;
|
|
||||||
_buffer = new CircularBuffer(period);
|
|
||||||
_halfLife = decay * 0.1;
|
|
||||||
Name = $"Min(period={period}, halfLife={decay:F2})";
|
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
if (decay < 0) {
|
||||||
public Min(object source, int period, double decay = 0) : this(period, decay)
|
throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
|
||||||
{
|
|
||||||
var pubEvent = source.GetType().GetEvent("Pub");
|
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
|
||||||
}
|
}
|
||||||
|
Period = period;
|
||||||
|
WarmupPeriod = 0;
|
||||||
|
_buffer = new CircularBuffer(period);
|
||||||
|
_halfLife = decay * 0.1;
|
||||||
|
Name = $"Min(period={period}, halfLife={decay:F2})";
|
||||||
|
Init();
|
||||||
|
}
|
||||||
|
|
||||||
public override void Init()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Min class with the specified source, period, and decay.
|
||||||
base.Init();
|
/// </summary>
|
||||||
_currentMin = double.MaxValue;
|
/// <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;
|
_timeSinceNewMin = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
|
||||||
{
|
_currentMin = _currentMin + decayRate * (_buffer.Average() - _currentMin);
|
||||||
if (isNew)
|
_currentMin = Math.Max(_currentMin, _buffer.Min());
|
||||||
{
|
|
||||||
_p_currentMin = _currentMin;
|
|
||||||
_lastValidValue = Input.Value;
|
|
||||||
_index++;
|
|
||||||
_timeSinceNewMin++;
|
|
||||||
_p_timeSinceNewMin = _timeSinceNewMin;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_currentMin = _p_currentMin;
|
|
||||||
_timeSinceNewMin = _p_timeSinceNewMin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double Calculation()
|
IsHot = true;
|
||||||
{
|
return _currentMin;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-19
@@ -1,14 +1,27 @@
|
|||||||
namespace QuanTAlib;
|
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 int Period;
|
||||||
private readonly CircularBuffer _buffer;
|
private readonly CircularBuffer _buffer;
|
||||||
|
|
||||||
public Mode(int period) : base()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Mode class with the specified period.
|
||||||
if (period < 1)
|
/// </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.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||||
}
|
}
|
||||||
Period = period;
|
Period = period;
|
||||||
@@ -18,29 +31,45 @@ public class Mode : AbstractBase
|
|||||||
Init();
|
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");
|
var pubEvent = source.GetType().GetEvent("Pub");
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Mode instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </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;
|
_lastValidValue = Input.Value;
|
||||||
_index++;
|
_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);
|
ManageState(Input.IsNew);
|
||||||
_buffer.Add(Input.Value, Input.IsNew);
|
_buffer.Add(Input.Value, Input.IsNew);
|
||||||
|
|
||||||
double mode;
|
double mode;
|
||||||
if (_index >= Period)
|
if (_index >= Period) {
|
||||||
{
|
|
||||||
var values = _buffer.GetSpan().ToArray();
|
var values = _buffer.GetSpan().ToArray();
|
||||||
var groupedValues = values.GroupBy(v => v)
|
var groupedValues = values.GroupBy(v => v)
|
||||||
.OrderByDescending(g => g.Count())
|
.OrderByDescending(g => g.Count())
|
||||||
@@ -53,9 +82,7 @@ public class Mode : AbstractBase
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
mode = modes.Average(); // If there are multiple modes, we return their average
|
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
|
mode = _buffer.Average(); // Use average until we have enough data points
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,33 @@
|
|||||||
namespace QuanTAlib;
|
namespace QuanTAlib;
|
||||||
|
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Linq;
|
/// Represents a percentile calculator that determines the value at a specified percentile
|
||||||
|
/// in a given period of data points.
|
||||||
public class Percentile : AbstractBase
|
/// </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 int Period;
|
||||||
private readonly double Percent;
|
private readonly double Percent;
|
||||||
private readonly CircularBuffer _buffer;
|
private readonly CircularBuffer _buffer;
|
||||||
|
|
||||||
public Percentile(int period, double percent) : base()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Percentile class with the specified period and percentile.
|
||||||
if (period < 2)
|
/// </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.");
|
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.");
|
throw new ArgumentOutOfRangeException(nameof(percent), "Percent must be between 0 and 100.");
|
||||||
}
|
}
|
||||||
Period = period;
|
Period = period;
|
||||||
@@ -27,35 +38,54 @@ public class Percentile : AbstractBase
|
|||||||
Init();
|
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");
|
var pubEvent = source.GetType().GetEvent("Pub");
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
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();
|
base.Init();
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Percentile instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </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;
|
_lastValidValue = Input.Value;
|
||||||
_index++;
|
_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);
|
ManageState(Input.IsNew);
|
||||||
_buffer.Add(Input.Value, Input.IsNew);
|
_buffer.Add(Input.Value, Input.IsNew);
|
||||||
|
|
||||||
double result;
|
double result;
|
||||||
if (_buffer.Count >= Period)
|
if (_buffer.Count >= Period) {
|
||||||
{
|
|
||||||
var values = _buffer.GetSpan().ToArray();
|
var values = _buffer.GetSpan().ToArray();
|
||||||
Array.Sort(values);
|
Array.Sort(values);
|
||||||
|
|
||||||
@@ -63,21 +93,16 @@ protected override double Calculation()
|
|||||||
int lowerIndex = (int)Math.Floor(position);
|
int lowerIndex = (int)Math.Floor(position);
|
||||||
int upperIndex = (int)Math.Ceiling(position);
|
int upperIndex = (int)Math.Ceiling(position);
|
||||||
|
|
||||||
if (lowerIndex == upperIndex)
|
if (lowerIndex == upperIndex) {
|
||||||
{
|
|
||||||
result = values[lowerIndex];
|
result = values[lowerIndex];
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Interpolate between the two nearest values
|
// Interpolate between the two nearest values
|
||||||
double lowerValue = values[lowerIndex];
|
double lowerValue = values[lowerIndex];
|
||||||
double upperValue = values[upperIndex];
|
double upperValue = values[upperIndex];
|
||||||
double fraction = position - lowerIndex;
|
double fraction = position - lowerIndex;
|
||||||
result = lowerValue + (upperValue - lowerValue) * fraction;
|
result = lowerValue + (upperValue - lowerValue) * fraction;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Use average for insufficient data, like the Median class
|
// Use average for insufficient data, like the Median class
|
||||||
result = _buffer.Average();
|
result = _buffer.Average();
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-25
@@ -1,17 +1,28 @@
|
|||||||
namespace QuanTAlib;
|
namespace QuanTAlib;
|
||||||
|
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Linq;
|
/// Represents a skewness calculator that measures the asymmetry of the probability
|
||||||
|
/// distribution of a real-valued random variable about its mean.
|
||||||
public class Skew : AbstractBase
|
/// </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 int Period;
|
||||||
private readonly CircularBuffer _buffer;
|
private readonly CircularBuffer _buffer;
|
||||||
|
|
||||||
public Skew(int period) : base()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Skew class with the specified period.
|
||||||
if (period < 3)
|
/// </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.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 3 for skewness calculation.");
|
||||||
}
|
}
|
||||||
Period = period;
|
Period = period;
|
||||||
@@ -21,36 +32,54 @@ public class Skew : AbstractBase
|
|||||||
Init();
|
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");
|
var pubEvent = source.GetType().GetEvent("Pub");
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
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();
|
base.Init();
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Skew instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </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;
|
_lastValidValue = Input.Value;
|
||||||
_index++;
|
_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);
|
ManageState(Input.IsNew);
|
||||||
|
|
||||||
_buffer.Add(Input.Value, Input.IsNew);
|
_buffer.Add(Input.Value, Input.IsNew);
|
||||||
|
|
||||||
double skew = 0;
|
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();
|
var values = _buffer.GetSpan().ToArray();
|
||||||
double mean = values.Average();
|
double mean = values.Average();
|
||||||
double n = values.Length;
|
double n = values.Length;
|
||||||
@@ -58,8 +87,7 @@ public class Skew : AbstractBase
|
|||||||
double sumCubedDeviations = 0;
|
double sumCubedDeviations = 0;
|
||||||
double sumSquaredDeviations = 0;
|
double sumSquaredDeviations = 0;
|
||||||
|
|
||||||
foreach (var value in values)
|
foreach (var value in values) {
|
||||||
{
|
|
||||||
double deviation = value - mean;
|
double deviation = value - mean;
|
||||||
sumCubedDeviations += Math.Pow(deviation, 3);
|
sumCubedDeviations += Math.Pow(deviation, 3);
|
||||||
sumSquaredDeviations += Math.Pow(deviation, 2);
|
sumSquaredDeviations += Math.Pow(deviation, 2);
|
||||||
@@ -70,8 +98,7 @@ public class Skew : AbstractBase
|
|||||||
double m2 = sumSquaredDeviations / n;
|
double m2 = sumSquaredDeviations / n;
|
||||||
double s3 = Math.Pow(m2, 1.5);
|
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);
|
skew = (Math.Sqrt(n * (n - 1)) / (n - 2)) * (m3 / s3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+135
-116
@@ -1,128 +1,147 @@
|
|||||||
using System;
|
namespace QuanTAlib;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace QuanTAlib
|
/// <summary>
|
||||||
{
|
/// Represents a slope calculator that performs linear regression on a series of data points.
|
||||||
public class Slope : AbstractBase
|
/// </summary>
|
||||||
{
|
/// <remarks>
|
||||||
private readonly int _period;
|
/// The Slope class calculates the slope of a linear regression line, along with other
|
||||||
private readonly CircularBuffer _buffer;
|
/// statistical measures such as intercept, standard deviation, R-squared, and the last
|
||||||
private readonly CircularBuffer _timeBuffer;
|
/// 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; }
|
/// <summary>
|
||||||
public double? StdDev { get; private set; }
|
/// Initializes a new instance of the Slope class with the specified period.
|
||||||
public double? RSquared { get; private set; }
|
/// </summary>
|
||||||
public double? Line { get; private set; }
|
/// <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)
|
Init();
|
||||||
{
|
}
|
||||||
if (period <= 1)
|
|
||||||
{
|
/// <summary>
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), period,
|
/// Initializes a new instance of the Slope class with the specified source and period.
|
||||||
"Period must be greater than 1 for Slope/Linear Regression.");
|
/// </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();
|
// Calculate last Line value (y = mx + b)
|
||||||
}
|
Line = (slope * count) + Intercept;
|
||||||
|
} else {
|
||||||
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();
|
|
||||||
Intercept = null;
|
Intercept = null;
|
||||||
StdDev = null;
|
StdDev = null;
|
||||||
RSquared = null;
|
RSquared = null;
|
||||||
Line = null;
|
Line = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
IsHot = _buffer.Count == _period;
|
||||||
{
|
return slope;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+97
-61
@@ -1,69 +1,105 @@
|
|||||||
using System;
|
namespace QuanTAlib;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace QuanTAlib
|
/// <summary>
|
||||||
{
|
/// Represents a standard deviation calculator that measures the amount of variation or
|
||||||
public class Stddev : AbstractBase
|
/// dispersion of a set of values.
|
||||||
{
|
/// </summary>
|
||||||
private readonly int Period;
|
/// <remarks>
|
||||||
private readonly bool IsPopulation;
|
/// The Stddev class calculates either the population standard deviation or the sample
|
||||||
private readonly CircularBuffer _buffer;
|
/// 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()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Stddev class with the specified period and
|
||||||
if (period < 2)
|
/// population flag.
|
||||||
{
|
/// </summary>
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
/// <param name="period">The period over which to calculate the standard deviation.</param>
|
||||||
}
|
/// <param name="isPopulation">
|
||||||
Period = period;
|
/// A flag indicating whether to calculate population (true) or sample (false) standard deviation.
|
||||||
IsPopulation = isPopulation;
|
/// </param>
|
||||||
WarmupPeriod = 0;
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
_buffer = new CircularBuffer(period);
|
/// Thrown when period is less than 2.
|
||||||
Name = $"Stddev(period={period}, population={isPopulation})";
|
/// </exception>
|
||||||
Init();
|
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)
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Stddev class with the specified source, period,
|
||||||
var pubEvent = source.GetType().GetEvent("Pub");
|
/// and population flag.
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
/// </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()
|
/// <summary>
|
||||||
{
|
/// Initializes the Stddev instance by clearing the buffer.
|
||||||
base.Init();
|
/// </summary>
|
||||||
_buffer.Clear();
|
public override void Init() {
|
||||||
}
|
base.Init();
|
||||||
|
_buffer.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Stddev instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </summary>
|
||||||
{
|
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||||
_lastValidValue = Input.Value;
|
protected override void ManageState(bool isNew) {
|
||||||
_index++;
|
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>
|
||||||
|
/// 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
@@ -1,68 +1,104 @@
|
|||||||
using System;
|
namespace QuanTAlib;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace QuanTAlib
|
/// <summary>
|
||||||
{
|
/// Represents a variance calculator that measures the spread of a set of numbers
|
||||||
public class Variance : AbstractBase
|
/// from their average value.
|
||||||
{
|
/// </summary>
|
||||||
private readonly int Period;
|
/// <remarks>
|
||||||
private readonly bool IsPopulation;
|
/// The Variance class calculates either the population variance or the sample
|
||||||
private readonly CircularBuffer _buffer;
|
/// 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()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Variance class with the specified period and
|
||||||
if (period < 2)
|
/// population flag.
|
||||||
{
|
/// </summary>
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
/// <param name="period">The period over which to calculate the variance.</param>
|
||||||
}
|
/// <param name="isPopulation">
|
||||||
Period = period;
|
/// A flag indicating whether to calculate population (true) or sample (false) variance.
|
||||||
IsPopulation = isPopulation;
|
/// </param>
|
||||||
WarmupPeriod = 0;
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
_buffer = new CircularBuffer(period);
|
/// Thrown when period is less than 2.
|
||||||
Name = $"Variance(period={period}, population={isPopulation})";
|
/// </exception>
|
||||||
Init();
|
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)
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Variance class with the specified source, period,
|
||||||
var pubEvent = source.GetType().GetEvent("Pub");
|
/// and population flag.
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
/// </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()
|
/// <summary>
|
||||||
{
|
/// Initializes the Variance instance by clearing the buffer.
|
||||||
base.Init();
|
/// </summary>
|
||||||
_buffer.Clear();
|
public override void Init() {
|
||||||
}
|
base.Init();
|
||||||
|
_buffer.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Variance instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </summary>
|
||||||
{
|
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||||
_lastValidValue = Input.Value;
|
protected override void ManageState(bool isNew) {
|
||||||
_index++;
|
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>
|
||||||
|
/// 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
@@ -1,17 +1,27 @@
|
|||||||
namespace QuanTAlib;
|
namespace QuanTAlib;
|
||||||
|
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Linq;
|
/// Represents a Z-score calculator that measures how many standard deviations
|
||||||
|
/// an element is from the mean of a set of values.
|
||||||
public class Zscore : AbstractBase
|
/// </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 int Period;
|
||||||
private readonly CircularBuffer _buffer;
|
private readonly CircularBuffer _buffer;
|
||||||
|
|
||||||
public Zscore(int period) : base()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Zscore class with the specified period.
|
||||||
if (period < 2)
|
/// </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.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for Z-score calculation.");
|
||||||
}
|
}
|
||||||
Period = period;
|
Period = period;
|
||||||
@@ -21,36 +31,54 @@ public class Zscore : AbstractBase
|
|||||||
Init();
|
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");
|
var pubEvent = source.GetType().GetEvent("Pub");
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
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();
|
base.Init();
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Zscore instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </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;
|
_lastValidValue = Input.Value;
|
||||||
_index++;
|
_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);
|
ManageState(Input.IsNew);
|
||||||
|
|
||||||
_buffer.Add(Input.Value, Input.IsNew);
|
_buffer.Add(Input.Value, Input.IsNew);
|
||||||
|
|
||||||
double zScore = 0;
|
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();
|
var values = _buffer.GetSpan().ToArray();
|
||||||
double mean = values.Average();
|
double mean = values.Average();
|
||||||
double n = values.Length;
|
double n = values.Length;
|
||||||
@@ -58,8 +86,7 @@ public class Zscore : AbstractBase
|
|||||||
double sumSquaredDeviations = values.Sum(x => Math.Pow(x - mean, 2));
|
double sumSquaredDeviations = values.Sum(x => Math.Pow(x - mean, 2));
|
||||||
double standardDeviation = Math.Sqrt(sumSquaredDeviations / (n - 1)); // Sample standard deviation
|
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;
|
zScore = (Input.Value - mean) / standardDeviation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-23
@@ -1,14 +1,26 @@
|
|||||||
namespace QuanTAlib;
|
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 readonly Ema _ma;
|
||||||
private double _prevClose, _p_prevClose;
|
private double _prevClose, _p_prevClose;
|
||||||
|
|
||||||
public Atr(int period) : base()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Atr class with the specified period.
|
||||||
if (period < 1)
|
/// </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.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||||
}
|
}
|
||||||
_ma = new(1.0/period);
|
_ma = new(1.0/period);
|
||||||
@@ -16,34 +28,50 @@ public class Atr : AbstractBarBase
|
|||||||
Name = $"ATR({period})";
|
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");
|
var pubEvent = source.GetType().GetEvent("Pub");
|
||||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
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();
|
base.Init();
|
||||||
_ma.Init();
|
_ma.Init();
|
||||||
_prevClose = double.NaN;
|
_prevClose = double.NaN;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Atr instance based on whether a new bar is being processed.
|
||||||
if (isNew)
|
/// </summary>
|
||||||
{
|
/// <param name="isNew">Indicates whether the current input is a new bar.</param>
|
||||||
|
protected override void ManageState(bool isNew) {
|
||||||
|
if (isNew) {
|
||||||
_index++;
|
_index++;
|
||||||
_p_prevClose = _prevClose;
|
_p_prevClose = _prevClose;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
_prevClose = _p_prevClose;
|
_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);
|
ManageState(Input.IsNew);
|
||||||
|
|
||||||
double trueRange = Math.Max(
|
double trueRange = Math.Max(
|
||||||
@@ -53,8 +81,7 @@ public class Atr : AbstractBarBase
|
|||||||
),
|
),
|
||||||
Math.Abs(Input.Low - _prevClose)
|
Math.Abs(Input.Low - _prevClose)
|
||||||
);
|
);
|
||||||
if (_index < 2)
|
if (_index < 2) {
|
||||||
{
|
|
||||||
trueRange = Input.High - Input.Low;
|
trueRange = Input.High - Input.Low;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +91,4 @@ public class Atr : AbstractBarBase
|
|||||||
|
|
||||||
return emaTrueRange.Value;
|
return emaTrueRange.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,31 @@
|
|||||||
namespace QuanTAlib;
|
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 int Period;
|
||||||
private readonly bool IsAnnualized;
|
private readonly bool IsAnnualized;
|
||||||
private readonly CircularBuffer _buffer;
|
private readonly CircularBuffer _buffer;
|
||||||
private readonly CircularBuffer _logReturns;
|
private readonly CircularBuffer _logReturns;
|
||||||
private double _previousClose;
|
private double _previousClose;
|
||||||
|
|
||||||
public Historical(int period, bool isAnnualized = true) : base()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Historical class with the specified period and annualization flag.
|
||||||
if (period < 2)
|
/// </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.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
||||||
}
|
}
|
||||||
Period = period;
|
Period = period;
|
||||||
@@ -23,46 +37,64 @@ public class Historical : AbstractBase
|
|||||||
Init();
|
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");
|
var pubEvent = source.GetType().GetEvent("Pub");
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
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();
|
base.Init();
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
_logReturns.Clear();
|
_logReturns.Clear();
|
||||||
_previousClose = 0;
|
_previousClose = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Historical instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </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;
|
_lastValidValue = Input.Value;
|
||||||
_index++;
|
_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);
|
ManageState(Input.IsNew);
|
||||||
|
|
||||||
_buffer.Add(Input.Value, Input.IsNew);
|
_buffer.Add(Input.Value, Input.IsNew);
|
||||||
|
|
||||||
double volatility = 0;
|
double volatility = 0;
|
||||||
if (_buffer.Count > 1)
|
if (_buffer.Count > 1) {
|
||||||
{
|
if (_previousClose != 0) {
|
||||||
if (_previousClose != 0)
|
|
||||||
{
|
|
||||||
double logReturn = Math.Log(Input.Value / _previousClose);
|
double logReturn = Math.Log(Input.Value / _previousClose);
|
||||||
_logReturns.Add(logReturn, Input.IsNew);
|
_logReturns.Add(logReturn, Input.IsNew);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_logReturns.Count == Period)
|
if (_logReturns.Count == Period) {
|
||||||
{
|
|
||||||
var returns = _logReturns.GetSpan().ToArray();
|
var returns = _logReturns.GetSpan().ToArray();
|
||||||
double mean = returns.Average();
|
double mean = returns.Average();
|
||||||
double sumOfSquaredDifferences = returns.Sum(x => Math.Pow(x - mean, 2));
|
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
|
double variance = sumOfSquaredDifferences / (Period - 1); // Using sample standard deviation
|
||||||
volatility = Math.Sqrt(variance);
|
volatility = Math.Sqrt(variance);
|
||||||
|
|
||||||
if (IsAnnualized)
|
if (IsAnnualized) {
|
||||||
{
|
|
||||||
// Assuming 252 trading days in a year. Adjust as needed.
|
// Assuming 252 trading days in a year. Adjust as needed.
|
||||||
volatility *= Math.Sqrt(252);
|
volatility *= Math.Sqrt(252);
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-22
@@ -1,16 +1,31 @@
|
|||||||
namespace QuanTAlib;
|
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 int Period;
|
||||||
private readonly bool IsAnnualized;
|
private readonly bool IsAnnualized;
|
||||||
private readonly CircularBuffer _returns;
|
private readonly CircularBuffer _returns;
|
||||||
private double _previousClose;
|
private double _previousClose;
|
||||||
private double _sumSquaredReturns;
|
private double _sumSquaredReturns;
|
||||||
|
|
||||||
public Realized(int period, bool isAnnualized = true) : base()
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Realized class with the specified period and annualization flag.
|
||||||
if (period < 2)
|
/// </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.");
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
||||||
}
|
}
|
||||||
Period = period;
|
Period = period;
|
||||||
@@ -21,34 +36,50 @@ public class Realized : AbstractBase
|
|||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Init()
|
/// <summary>
|
||||||
{
|
/// Initializes the Realized instance by clearing buffers and resetting calculation variables.
|
||||||
|
/// </summary>
|
||||||
|
public override void Init() {
|
||||||
base.Init();
|
base.Init();
|
||||||
_returns.Clear();
|
_returns.Clear();
|
||||||
_previousClose = 0;
|
_previousClose = 0;
|
||||||
_sumSquaredReturns = 0;
|
_sumSquaredReturns = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Realized instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </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;
|
_lastValidValue = Input.Value;
|
||||||
_index++;
|
_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);
|
ManageState(Input.IsNew);
|
||||||
|
|
||||||
double volatility = 0;
|
double volatility = 0;
|
||||||
if (_previousClose != 0)
|
if (_previousClose != 0) {
|
||||||
{
|
|
||||||
double logReturn = Math.Log(Input.Value / _previousClose);
|
double logReturn = Math.Log(Input.Value / _previousClose);
|
||||||
|
|
||||||
if (_returns.Count == Period)
|
if (_returns.Count == Period) {
|
||||||
{
|
|
||||||
// Remove the oldest squared return from the sum
|
// Remove the oldest squared return from the sum
|
||||||
_sumSquaredReturns -= Math.Pow(_returns[0], 2);
|
_sumSquaredReturns -= Math.Pow(_returns[0], 2);
|
||||||
}
|
}
|
||||||
@@ -56,13 +87,11 @@ public class Realized : AbstractBase
|
|||||||
_returns.Add(logReturn, Input.IsNew);
|
_returns.Add(logReturn, Input.IsNew);
|
||||||
_sumSquaredReturns += Math.Pow(logReturn, 2);
|
_sumSquaredReturns += Math.Pow(logReturn, 2);
|
||||||
|
|
||||||
if (_returns.Count == Period)
|
if (_returns.Count == Period) {
|
||||||
{
|
|
||||||
double variance = _sumSquaredReturns / Period;
|
double variance = _sumSquaredReturns / Period;
|
||||||
volatility = Math.Sqrt(variance);
|
volatility = Math.Sqrt(variance);
|
||||||
|
|
||||||
if (IsAnnualized)
|
if (IsAnnualized) {
|
||||||
{
|
|
||||||
// Assuming 252 trading days in a year. Adjust as needed.
|
// Assuming 252 trading days in a year. Adjust as needed.
|
||||||
volatility *= Math.Sqrt(252);
|
volatility *= Math.Sqrt(252);
|
||||||
}
|
}
|
||||||
|
|||||||
+102
-79
@@ -1,87 +1,110 @@
|
|||||||
/*
|
namespace QuanTAlib;
|
||||||
Reference:
|
|
||||||
Donald Dorsey, who introduced the concept in the 1993 issue of Technical Analysis
|
|
||||||
of Stocks & Commodities Magazine. He designed the RVI to focus on the direction of
|
|
||||||
price movements in relation to volatility. Dorsey’s methodology is often cited in
|
|
||||||
technical analysis literature and further elaborated on in various technical analysis
|
|
||||||
guides and platforms.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
/// <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 & 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;
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the Rvi class with the specified period.
|
||||||
namespace QuanTAlib
|
/// </summary>
|
||||||
{
|
/// <param name="period">The period over which to calculate the RVI.</param>
|
||||||
public class Rvi : AbstractBase
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
{
|
/// Thrown when period is less than 2.
|
||||||
private readonly int Period;
|
/// </exception>
|
||||||
private Stddev _upStdDev, _downStdDev;
|
public Rvi(int period) : base() {
|
||||||
private Sma _upSma, _downSma;
|
if (period < 2) {
|
||||||
private double _previousClose;
|
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
||||||
|
|
||||||
public Rvi(int period) : base()
|
|
||||||
{
|
|
||||||
if (period < 2)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
|
||||||
}
|
|
||||||
Period = period;
|
|
||||||
WarmupPeriod = period;
|
|
||||||
Name = $"RVI(period={period})";
|
|
||||||
_upStdDev = new Stddev(Period);
|
|
||||||
_downStdDev = new Stddev(Period);
|
|
||||||
_upSma = new(Period);
|
|
||||||
_downSma = new(Period);
|
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
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)
|
/// <summary>
|
||||||
{
|
/// Initializes a new instance of the Rvi class with the specified source and period.
|
||||||
var pubEvent = source.GetType().GetEvent("Pub");
|
/// </summary>
|
||||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
/// <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()
|
/// <summary>
|
||||||
{
|
/// Initializes the Rvi instance by setting up the initial state.
|
||||||
base.Init();
|
/// </summary>
|
||||||
_previousClose = 0;
|
public override void Init() {
|
||||||
}
|
base.Init();
|
||||||
|
_previousClose = 0;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ManageState(bool isNew)
|
/// <summary>
|
||||||
{
|
/// Manages the state of the Rvi instance based on whether a new value is being processed.
|
||||||
if (isNew)
|
/// </summary>
|
||||||
{
|
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||||
_lastValidValue = Value;
|
protected override void ManageState(bool isNew) {
|
||||||
_index++;
|
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>
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user