From 73e342037997a93141fc19ac686073a8a111acec Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Thu, 17 Nov 2022 07:53:45 -0800 Subject: [PATCH 1/2] COVAR semver fix VAR test fix new: COVAR, ZSCORE, CORR, LINREG versioning refactoring --- GitVersion.yml | 3 +- QuanTAlib.sln | 8 +- Quantower/Indicators/ENTP_chart.cs | 4 +- Quantower/Indicators/KURT_chart.cs | 4 +- Quantower/Indicators/MED_chart.cs | 2 +- Source/Basics/ADD_Series.cs | 3 +- Source/Basics/MAX_Series.cs | 16 +- Source/Basics/MIDPOINT_Series.cs | 11 +- Source/Basics/MIDPRICE_Series.cs | 30 +- Source/Basics/MIN_Series.cs | 16 +- ...{Abstracts.cs => Pair_TSeries_Abstract.cs} | 260 ++++---- Source/Basics/Single_TBars_Abstract.cs | 66 ++ Source/Basics/Single_TSeries_Abstract.cs | 63 ++ Source/Feeds/Alphavantage_Feed.cs | 3 +- Source/Feeds/Yahoo_Feed.cs | 7 +- Source/Momentum/CCI_Series.cs | 12 +- Source/QuanTAlib.csproj | 12 +- Source/QuanTAlib.ruleset | 5 + Source/Statistics/BIAS_Series.cs | 15 +- Source/Statistics/CORR_Series.cs | 88 +-- Source/Statistics/COVAR_Series.cs | 40 ++ .../{ENTP_Series.cs => ENTROPY_Series.cs} | 26 +- .../{KURT_Series.cs => KURTOSIS_Series.cs} | 117 ++-- Source/Statistics/LINREG_Series.cs | 6 +- Source/Statistics/MAD_Series.cs | 12 +- Source/Statistics/MAPE_Series.cs | 19 +- .../{MED_Series.cs => MEDIAN_Series.cs} | 91 ++- Source/Statistics/MSE_Series.cs | 13 +- Source/Statistics/SDEV_Series.cs | 13 +- Source/Statistics/SMAPE_Series.cs | 13 +- Source/Statistics/SSDEV_Series.cs | 17 +- Source/Statistics/SVAR_Series.cs | 13 +- Source/Statistics/VAR_Series.cs | 13 +- Source/Statistics/WMAPE_Series.cs | 19 +- Source/Statistics/ZSCORE_Series.cs | 13 +- Source/Trends/ALMA_Series.cs | 86 ++- Source/Trends/DEMA_Series.cs | 15 +- Source/Trends/EMA_Series.cs | 17 +- Source/Trends/HEMA_Series.cs | 20 +- Source/Trends/HMA_Series.cs | 3 +- Source/Trends/JMA_Series.cs | 11 +- Source/Trends/KAMA_Series.cs | 6 +- Source/Trends/MACD_Series.cs | 5 +- Source/Trends/RMA_Series.cs | 19 +- Source/Trends/SMA_Series.cs | 14 +- Source/Trends/SMMA_Series.cs | 17 +- Source/Trends/TEMA_Series.cs | 18 +- Source/Trends/TRIMA_Series.cs | 16 +- Source/Trends/WMA_Series.cs | 8 +- Source/Trends/ZLEMA_Series.cs | 20 +- Source/Volatility/ADL_Series.cs | 5 +- Tests/Statistics/ENTP_Test.cs | 6 +- Tests/Statistics/KURT_Test.cs | 4 +- Tests/Statistics/MED_Test.cs | 4 +- Tests/Tests.csproj | 4 - Tests/Validations/Pandas_TA.cs | 520 +++++++-------- Tests/Validations/Skender_Stock.cs | 609 +++++++++--------- Tests/Validations/TA_LIB.cs | 21 +- docs/readme.md | 9 +- 59 files changed, 1225 insertions(+), 1285 deletions(-) rename Source/Basics/{Abstracts.cs => Pair_TSeries_Abstract.cs} (55%) create mode 100644 Source/Basics/Single_TBars_Abstract.cs create mode 100644 Source/Basics/Single_TSeries_Abstract.cs create mode 100644 Source/QuanTAlib.ruleset create mode 100644 Source/Statistics/COVAR_Series.cs rename Source/Statistics/{ENTP_Series.cs => ENTROPY_Series.cs} (52%) rename Source/Statistics/{KURT_Series.cs => KURTOSIS_Series.cs} (69%) rename Source/Statistics/{MED_Series.cs => MEDIAN_Series.cs} (70%) diff --git a/GitVersion.yml b/GitVersion.yml index deb60508..f7fe92a0 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,10 +1,9 @@ -next-version: 0.1.19 minor-version-bump-message: \+semver:\s?(feature|new) branches: main: regex: ^main$ is-release-branch: true - prevent-increment-of-merged-branch-version: true + prevent-increment-of-merged-branch-version: false mode: ContinuousDelivery tag: '' increment: Patch diff --git a/QuanTAlib.sln b/QuanTAlib.sln index e169574a..3242afe2 100644 --- a/QuanTAlib.sln +++ b/QuanTAlib.sln @@ -3,10 +3,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32210.308 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuanTAlib", "Source\QuanTAlib.csproj", "{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuanTAlib", "Source\QuanTAlib.csproj", "{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Quantower", "Quantower\Quantower.csproj", "{693713F9-F33A-4B33-8F98-63794CA9734C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Debug|Any CPU.Build.0 = Debug|Any CPU {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Release|Any CPU.ActiveCfg = Release|Any CPU {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Release|Any CPU.Build.0 = Release|Any CPU + {693713F9-F33A-4B33-8F98-63794CA9734C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {693713F9-F33A-4B33-8F98-63794CA9734C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {693713F9-F33A-4B33-8F98-63794CA9734C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {693713F9-F33A-4B33-8F98-63794CA9734C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Quantower/Indicators/ENTP_chart.cs b/Quantower/Indicators/ENTP_chart.cs index b5a66f7d..4267974e 100644 --- a/Quantower/Indicators/ENTP_chart.cs +++ b/Quantower/Indicators/ENTP_chart.cs @@ -19,13 +19,13 @@ public class ENTP_chart : Indicator private TBars bars; /////// - private ENTP_Series indicator; + private ENTROPY_Series indicator; /////// public ENTP_chart() { this.SeparateWindow = true; - this.Name = "ENTP - Entropy (Unpredictability)"; + this.Name = "ENTROPY - Entropy (Unpredictability)"; this.Description = "Entropy description"; this.AddLineSeries("ENTROPY", Color.RoyalBlue, 3, LineStyle.Solid); } diff --git a/Quantower/Indicators/KURT_chart.cs b/Quantower/Indicators/KURT_chart.cs index 09cdc5e3..e2e74631 100644 --- a/Quantower/Indicators/KURT_chart.cs +++ b/Quantower/Indicators/KURT_chart.cs @@ -19,13 +19,13 @@ public class KURT_chart : Indicator private TBars bars; /////// - private KURT_Series indicator; + private KURTOSIS_Series indicator; /////// public KURT_chart() { this.SeparateWindow = true; - this.Name = "KURT - Kurtosis (Flatness)"; + this.Name = "KURTOSIS - Kurtosis (Flatness)"; this.Description = "Kurtosis description"; this.AddLineSeries("KURTOSIS", Color.RoyalBlue, 3, LineStyle.Solid); } diff --git a/Quantower/Indicators/MED_chart.cs b/Quantower/Indicators/MED_chart.cs index e8952bbb..d56fc9e0 100644 --- a/Quantower/Indicators/MED_chart.cs +++ b/Quantower/Indicators/MED_chart.cs @@ -19,7 +19,7 @@ public class MED_chart : Indicator private TBars bars; /////// - private MED_Series indicator; + private MEDIAN_Series indicator; /////// public MED_chart() diff --git a/Source/Basics/ADD_Series.cs b/Source/Basics/ADD_Series.cs index 13cc8bb5..0588d216 100644 --- a/Source/Basics/ADD_Series.cs +++ b/Source/Basics/ADD_Series.cs @@ -23,8 +23,7 @@ public class ADD_Series : Pair_TSeries_Indicator public override void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2, bool update) { - (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, - TValue1.v+TValue2.v); + (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, TValue1.v+TValue2.v); if (update) { base[base.Count - 1] = result; } else { base.Add(result); } } } diff --git a/Source/Basics/MAX_Series.cs b/Source/Basics/MAX_Series.cs index 826aeb10..b8b197c3 100644 --- a/Source/Basics/MAX_Series.cs +++ b/Source/Basics/MAX_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* MAX - Maximum value in the given period in the series. @@ -16,18 +17,9 @@ public class MAX_Series : Single_TSeries_Indicator public override void Add((DateTime t, double v) TValue, bool update) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) { this._buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _max = _buffer.Max(); - double _max = TValue.v; - for (int i = 0; i < this._buffer.Count; i++) - { - _max = Math.Max(this._buffer[i], _max); - } - - var result = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _max); - - base.Add(result, update); + base.Add((TValue.t, _max), update, _NaN); } } \ No newline at end of file diff --git a/Source/Basics/MIDPOINT_Series.cs b/Source/Basics/MIDPOINT_Series.cs index 91ddac5c..9bccdebf 100644 --- a/Source/Basics/MIDPOINT_Series.cs +++ b/Source/Basics/MIDPOINT_Series.cs @@ -21,12 +21,7 @@ public class MIDPOINT_Series : Single_TSeries_Indicator public override void Add((DateTime t, double v) TValue, bool update) { - if (update) - { this._buffer[this._buffer.Count - 1] = TValue.v; } - else - { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) - { this._buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); double _max = TValue.v; double _min = TValue.v; @@ -37,8 +32,6 @@ public class MIDPOINT_Series : Single_TSeries_Indicator } double _mid = (_max + _min) * 0.5; - var result = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _mid); - - base.Add(result, update); + base.Add((TValue.t, _mid), update, _NaN); } } \ No newline at end of file diff --git a/Source/Basics/MIDPRICE_Series.cs b/Source/Basics/MIDPRICE_Series.cs index f49e1f1c..f7602c3a 100644 --- a/Source/Basics/MIDPRICE_Series.cs +++ b/Source/Basics/MIDPRICE_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* MIDPRICE: Midpoint price (highhest high + lowest low)/2 in the given period in the series. @@ -19,32 +20,13 @@ public class MIDPRICE_Series : Single_TBars_Indicator public override void Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update) { - if (update) - { - this._bufferhi[this._bufferhi.Count - 1] = TBar.h; - this._bufferlo[this._bufferlo.Count - 1] = TBar.l; - } - else - { - this._bufferhi.Add(TBar.h); - this._bufferlo.Add(TBar.l); - } - if (this._bufferhi.Count > this._p && this._p != 0) - { this._bufferhi.RemoveAt(0); } - if (this._bufferlo.Count > this._p && this._p != 0) - { this._bufferlo.RemoveAt(0); } + Add_Replace_Trim(_bufferhi, TBar.h, _p, update); + Add_Replace_Trim(_bufferlo, TBar.l, _p, update); - double _max = TBar.h; - double _min = TBar.l; - for (int i = 0; i < this._bufferhi.Count; i++) - { - _max = Math.Max(this._bufferhi[i], _max); - _min = Math.Min(this._bufferlo[i], _min); - } + double _max = _bufferhi.Max(); + double _min = _bufferlo.Min(); double _mid = (_max + _min) * 0.5; - var result = (TBar.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _mid); - - base.Add(result, update); + base.Add((TBar.t, _mid), update, _NaN); } } \ No newline at end of file diff --git a/Source/Basics/MIN_Series.cs b/Source/Basics/MIN_Series.cs index ceea135d..2621ec75 100644 --- a/Source/Basics/MIN_Series.cs +++ b/Source/Basics/MIN_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* MIN - Minimum value in the given period in the series. @@ -16,18 +17,9 @@ public class MIN_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) { this._buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); - double _min = TValue.v; - for (int i = 0; i < this._buffer.Count; i++) - { - _min = Math.Min(this._buffer[i], _min); - } - - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _min); - - base.Add(result, update); + double _min = _buffer.Min(); + base.Add((TValue.t, _min), update, _NaN); } } \ No newline at end of file diff --git a/Source/Basics/Abstracts.cs b/Source/Basics/Pair_TSeries_Abstract.cs similarity index 55% rename from Source/Basics/Abstracts.cs rename to Source/Basics/Pair_TSeries_Abstract.cs index ee3c91df..564a9e39 100644 --- a/Source/Basics/Abstracts.cs +++ b/Source/Basics/Pair_TSeries_Abstract.cs @@ -1,148 +1,112 @@ -namespace QuanTAlib; -using System; -/* -Abstract classes with all scaffolding required to build indicators. - All abstracts support period, NaN, and all permutations of Add() methods. - Indicator classess need to implement: - - Chaining constructor (Abstract's constructor executes first) - - Default Add(value) class - - optional Add(series) bulk insert class (for optimization of historical analysis) - - Single_TSeries_Indicator - one single-value TSeries in, one TSeries out. - Pair_TSeries_Indicator - Two TSeries in, one TSeries out. (includes simple semaphoring) - Single_TBars_Indicator - One OHLCV TBars in, one TSeries out. - - */ -public abstract class Single_TSeries_Indicator : TSeries -{ - protected readonly int _p; - protected readonly bool _NaN; - protected readonly TSeries _data; - - // Chainable Constructor - add it at the end of primary constructor :base(source: source, period: period, useNaN: useNaN) - protected Single_TSeries_Indicator(TSeries source, int period, bool useNaN) - { - this._data = source; - this._p = period; - this._NaN = useNaN; - this._data.Pub += this.Sub; - } - - // overridable Add() method to add/update a single item at the end of the list - public new virtual void Add((System.DateTime t, double v) TValue, bool update) => base.Add(TValue, update); - - // potentially overridable Add() method for the whole series (could be replaced with faster bulk algo) - public virtual void Add(TSeries data) { for (int i = 0; i < data.Count; i++) { this.Add(TValue: data[i], update: false); }} - - public new void Add((System.DateTime t, double v) TValue) => this.Add(TValue: TValue, update: false); - public void Add(bool update) => this.Add(TValue: this._data[this._data.Count - 1], update: update); - public void Add() => this.Add(TValue: this._data[this._data.Count - 1], update: false); - public new void Sub(object source, TSeriesEventArgs e) => this.Add(TValue: this._data[this._data.Count - 1], update: e.update); -} - -public abstract class Pair_TSeries_Indicator : TSeries -{ - protected readonly int _p; - protected readonly bool _NaN; - protected readonly TSeries _d1; - protected readonly TSeries _d2; - protected readonly double _dd1, _dd2; - - // Chainable Constructors - add them at the end of primary constructors if needed - protected Pair_TSeries_Indicator(TSeries source1, TSeries source2, int period, bool useNaN) - { - this._p = period; - this._NaN = useNaN; - this._d1 = source1; - this._d2 = source2; - this._dd1 = double.NaN; - this._dd2 = double.NaN; - this._d1.Pub += this.Sub; - this._d2.Pub += this.Sub; - } - protected Pair_TSeries_Indicator(TSeries source1, TSeries source2) - { - this._d1 = source1; - this._d2 = source2; - this._dd1 = double.NaN; - this._dd2 = double.NaN; - this._d1.Pub += this.Sub; - this._d2.Pub += this.Sub; - } - protected Pair_TSeries_Indicator(TSeries source1, double dd2) - { - this._d1 = source1; - this._d2 = new(); - this._dd1 = double.NaN; - this._dd2 = dd2; - this._d1.Pub += this.Sub; - } - protected Pair_TSeries_Indicator(double dd1, TSeries source2) - { - this._d1 = new(); - this._d2 = source2; - this._dd1 = dd1; - this._dd2 = double.NaN; - this._d2.Pub += this.Sub; - } - - // overridable Add(Tvalue, Tvalue) method to add/update a single value at the end of the list - public virtual void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2, bool update) => base.Add(TValue: (TValue1.t, 0), update: update); // default inserts zeros - - // potentially overridable Add() bulk variations (could be replaced with faster bulk algos) - public virtual void Add(TSeries d1, TSeries d2) { for (int i = 0; i < d1.Count; i++) { this.Add(d1[i], d2[i], update: false); }} - public virtual void Add(TSeries d1, double dd2) { for (int i = 0; i < d1.Count; i++) { this.Add(d1[i], (d1[i].t, dd2), update: false); }} - public virtual void Add(double dd1, TSeries d2) { for (int i = 0; i < d2.Count; i++) { this.Add((d2[i].t, dd1), d2[i], update: false); }} - - public void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2) => this.Add(TValue1, TValue2, update: false); - - public void Add(bool update) - { - if ((this._dd1 is double.NaN) && (this._dd2 is double.NaN)) - { - // (Series, Series) - if (update || (this._d1.Count > this.Count && this._d2.Count > this.Count)) - { this.Add(this._d1[this._d1.Count - 1], this._d2[this._d2.Count - 1], update); } - } - else if ((this._dd2 is not double.NaN) && (this._dd1 is double.NaN)) - { - // (Series, Double) - this.Add(TValue1: this._d1[this._d1.Count - 1], TValue2: (this._d1[this._d1.Count - 1].t, this._dd2), update: update); - } - else - { - // (Double, Series) - this.Add(TValue1: (this._d2[this._d2.Count - 1].t, this._dd1), TValue2: this._d2[this._d2.Count - 1], update: update); - } - } - - public void Add() => this.Add(update: false); - public new void Sub(object source, TSeriesEventArgs e) => this.Add(e.update); -} - -public abstract class Single_TBars_Indicator : TSeries -{ - protected readonly int _p; - protected readonly bool _NaN; - protected readonly TBars _bars; - - // Chainable Constructor - add it at the end of primary constructor :base(source: source, period: period, useNaN: useNaN) - protected Single_TBars_Indicator(TBars source, int period, bool useNaN) - { - this._p = period; - this._bars = source; - this._NaN = useNaN; - this._bars.Pub += this.Sub; - } - - // overridable Add() method to add/update a single item at the end of the list - public virtual void Add((System.DateTime t, double o, double h, double l, double c, double v) TBar, bool update) => base.Add((TBar.t, 0.0), update); - - // potentially overridable Add() method for the whole bars or series (could be replaced with faster bulk algo) - public virtual void Add(TBars bars) { for (int i = 0; i < bars.Count; i++) { this.Add(TBar: bars[i], update: false); }} - public virtual void Add(TSeries data) { for (int i = 0; i < data.Count; i++) { base.Add(TValue: data[i], update: false); }} - public void Add((System.DateTime t, double o, double h, double l, double c, double v) TBar) => this.Add(TBar: TBar, update: false); - public void Add(bool update) => this.Add(TBar: this._bars[this._bars.Count - 1], update: update); - public void Add() => this.Add(TBar: this._bars[this._bars.Count - 1], update: false); - public new void Sub(object source, TSeriesEventArgs e) => this.Add(TBar: this._bars[this._bars.Count - 1], update: e.update); -} +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +Abstract classes with all scaffolding required to build indicators. + All abstracts support period, NaN, and all permutations of Add() methods. + Indicator classess need to implement: + - Chaining constructor (Abstract's constructor executes first) + - Default Add(value) class + - optional Add(series) bulk insert class (for optimization of historical analysis) + + Single_TSeries_Indicator - one single-value TSeries in, one TSeries out. + Pair_TSeries_Indicator - Two TSeries in, one TSeries out. (includes simple semaphoring) + Single_TBars_Indicator - One OHLCV TBars in, one TSeries out. + + */ + + +public abstract class Pair_TSeries_Indicator : TSeries +{ + protected readonly int _p; + protected readonly bool _NaN; + protected readonly TSeries _d1; + protected readonly TSeries _d2; + protected readonly double _dd1, _dd2; + + // Chainable Constructors - add them at the end of primary constructors if needed + protected Pair_TSeries_Indicator(TSeries source1, TSeries source2, int period, bool useNaN) + { + this._p = period; + this._NaN = useNaN; + this._d1 = source1; + this._d2 = source2; + this._dd1 = double.NaN; + this._dd2 = double.NaN; + this._d1.Pub += this.Sub; + this._d2.Pub += this.Sub; + } + protected Pair_TSeries_Indicator(TSeries source1, TSeries source2) + { + this._d1 = source1; + this._d2 = source2; + this._dd1 = double.NaN; + this._dd2 = double.NaN; + this._d1.Pub += this.Sub; + this._d2.Pub += this.Sub; + } + protected Pair_TSeries_Indicator(TSeries source1, double dd2) + { + this._d1 = source1; + this._d2 = new(); + this._dd1 = double.NaN; + this._dd2 = dd2; + this._d1.Pub += this.Sub; + } + protected Pair_TSeries_Indicator(double dd1, TSeries source2) + { + this._d1 = new(); + this._d2 = source2; + this._dd1 = dd1; + this._dd2 = double.NaN; + this._d2.Pub += this.Sub; + } + + // overridable Add(Tvalue, Tvalue) method to add/update a single value at the end of the list + public virtual void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2, bool update) => base.Add(TValue: (TValue1.t, 0), update: update); // default inserts zeros + + // potentially overridable Add() bulk variations (could be replaced with faster bulk algos) + public virtual void Add(TSeries d1, TSeries d2) { for (int i = 0; i < d1.Count; i++) { this.Add(d1[i], d2[i], update: false); }} + public virtual void Add(TSeries d1, double dd2) { for (int i = 0; i < d1.Count; i++) { this.Add(d1[i], (d1[i].t, dd2), update: false); }} + public virtual void Add(double dd1, TSeries d2) { for (int i = 0; i < d2.Count; i++) { this.Add((d2[i].t, dd1), d2[i], update: false); }} + + public void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2) => this.Add(TValue1, TValue2, update: false); + + public void Add(bool update) + { + if ((this._dd1 is double.NaN) && (this._dd2 is double.NaN)) + { + // (Series, Series) + if (update || (this._d1.Count > this.Count && this._d2.Count > this.Count)) + { this.Add(this._d1[this._d1.Count - 1], this._d2[this._d2.Count - 1], update); } + } + else if ((this._dd2 is not double.NaN) && (this._dd1 is double.NaN)) + { + // (Series, Double) + this.Add(TValue1: this._d1[this._d1.Count - 1], TValue2: (this._d1[this._d1.Count - 1].t, this._dd2), update: update); + } + else + { + // (Double, Series) + this.Add(TValue1: (this._d2[this._d2.Count - 1].t, this._dd1), TValue2: this._d2[this._d2.Count - 1], update: update); + } + } + + public void Add() => this.Add(update: false); + public new void Sub(object source, TSeriesEventArgs e) => this.Add(e.update); + + protected static void Add_Replace(List l, double v, bool update) + { + if (update) + { l[l.Count - 1] = v; } + else + { l.Add(v); } + } + protected static void Add_Replace_Trim(List l, double v, int p, bool update) + { + Add_Replace(l, v, update); + if (l.Count > p && p != 0) + { l.RemoveAt(0); } + } +} diff --git a/Source/Basics/Single_TBars_Abstract.cs b/Source/Basics/Single_TBars_Abstract.cs new file mode 100644 index 00000000..9d24a889 --- /dev/null +++ b/Source/Basics/Single_TBars_Abstract.cs @@ -0,0 +1,66 @@ +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +Abstract classes with all scaffolding required to build indicators. + All abstracts support period, NaN, and all permutations of Add() methods. + Indicator classess need to implement: + - Chaining constructor (Abstract's constructor executes first) + - Default Add(value) class + - optional Add(series) bulk insert class (for optimization of historical analysis) + + Single_TSeries_Indicator - one single-value TSeries in, one TSeries out. + Pair_TSeries_Indicator - Two TSeries in, one TSeries out. (includes simple semaphoring) + Single_TBars_Indicator - One OHLCV TBars in, one TSeries out. + + */ + +public abstract class Single_TBars_Indicator : TSeries +{ + protected readonly int _p; + protected readonly bool _NaN; + protected readonly TBars _bars; + + // Chainable Constructor - add it at the end of primary constructor :base(source: source, period: period, useNaN: useNaN) + protected Single_TBars_Indicator(TBars source, int period, bool useNaN) + { + this._p = period; + this._bars = source; + this._NaN = useNaN; + this._bars.Pub += this.Sub; + } + + // overridable Add() method to add/update a single item at the end of the list + + + public virtual void Add((System.DateTime t, double o, double h, double l, double c, double v) TBar, bool update) => base.Add((TBar.t, 0.0), update); + public virtual void Add((System.DateTime t, double v) TValue, bool update, bool useNaN) + { + var res = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : TValue.v); + base.Add(res, update); + } + + // potentially overridable Add() method for the whole bars or series (could be replaced with faster bulk algo) + public virtual void Add(TBars bars) { for (int i = 0; i < bars.Count; i++) { this.Add(TBar: bars[i], update: false); }} + public virtual void Add(TSeries data) { for (int i = 0; i < data.Count; i++) { base.Add(TValue: data[i], update: false); }} + public void Add((System.DateTime t, double o, double h, double l, double c, double v) TBar) => this.Add(TBar: TBar, update: false); + public void Add(bool update) => this.Add(TBar: this._bars[this._bars.Count - 1], update: update); + public void Add() => this.Add(TBar: this._bars[this._bars.Count - 1], update: false); + public new void Sub(object source, TSeriesEventArgs e) => this.Add(TBar: this._bars[this._bars.Count - 1], update: e.update); + + protected static void Add_Replace(List l, double v, bool update) + { + if (update) + { l[l.Count - 1] = v; } + else + { l.Add(v); } + } + protected static void Add_Replace_Trim(List l, double v, int p, bool update) + { + Add_Replace(l, v, update); + if (l.Count > p && p != 0) + { l.RemoveAt(0); } + } + +} diff --git a/Source/Basics/Single_TSeries_Abstract.cs b/Source/Basics/Single_TSeries_Abstract.cs new file mode 100644 index 00000000..289cc5b9 --- /dev/null +++ b/Source/Basics/Single_TSeries_Abstract.cs @@ -0,0 +1,63 @@ +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +Abstract classes with all scaffolding required to build indicators. + All abstracts support period, NaN, and all permutations of Add() methods. + Indicator classess need to implement: + - Chaining constructor (Abstract's constructor executes first) + - Default Add(value) class + - optional Add(series) bulk insert class (for optimization of historical analysis) + + Single_TSeries_Indicator - one single-value TSeries in, one TSeries out. + Pair_TSeries_Indicator - Two TSeries in, one TSeries out. (includes simple semaphoring) + Single_TBars_Indicator - One OHLCV TBars in, one TSeries out. + + */ +public abstract class Single_TSeries_Indicator : TSeries +{ + protected readonly int _p; + protected readonly bool _NaN; + protected readonly TSeries _data; + + // Chainable Constructor - add it at the end of primary constructor :base(source: source, period: period, useNaN: useNaN) + protected Single_TSeries_Indicator(TSeries source, int period, bool useNaN) + { + this._data = source; + this._p = period; + this._NaN = useNaN; + this._data.Pub += this.Sub; + } + + // overridable Add() method to add/update a single item at the end of the list + + public virtual void Add((System.DateTime t, double v) TValue, bool update, bool useNaN) + { + var res = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : TValue.v); + base.Add(res, update); + } + public new virtual void Add((System.DateTime t, double v) TValue, bool update) => base.Add(TValue, update); + + // potentially overridable Add() method for the whole series (could be replaced with faster bulk algo) + public virtual void Add(TSeries data) { for (int i = 0; i < data.Count; i++) { this.Add(TValue: data[i], update: false); }} + + public new void Add((System.DateTime t, double v) TValue) => this.Add(TValue: TValue, update: false); + public void Add(bool update) => this.Add(TValue: this._data[this._data.Count - 1], update: update); + public void Add() => this.Add(TValue: this._data[this._data.Count - 1], update: false); + public new void Sub(object source, TSeriesEventArgs e) => this.Add(TValue: this._data[this._data.Count - 1], update: e.update); + + protected static void Add_Replace(List l, double v, bool update) + { + if (update) + { l[l.Count - 1] = v; } + else + { l.Add(v); } + } + protected static void Add_Replace_Trim(List l, double v, int p, bool update) + { + Add_Replace(l, v, update); + if (l.Count > p && p!=0) + { l.RemoveAt(0); } + } +} diff --git a/Source/Feeds/Alphavantage_Feed.cs b/Source/Feeds/Alphavantage_Feed.cs index 96cdafdc..d4f76fc7 100644 --- a/Source/Feeds/Alphavantage_Feed.cs +++ b/Source/Feeds/Alphavantage_Feed.cs @@ -17,12 +17,11 @@ public class Alphavantage_Feed : TBars public Alphavantage_Feed(string Symbol = "IBM", string APIkey = "demo") { System.Net.Http.HttpClient client = new(); - JsonElement json = new(); string req = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED" + "&symbol=" + Symbol + "&apikey=" + APIkey; var msg = client.GetStringAsync(req).Result; var jres = JsonSerializer.Deserialize(msg).RootElement; - jres.TryGetProperty("Time Series (Daily)", out json); + jres.TryGetProperty("Time Series (Daily)", out JsonElement json); if (json.ValueKind == JsonValueKind.Undefined) {throw new InvalidOperationException("Stock symbol "+Symbol+" not found"); } foreach (var val in json.EnumerateObject()) { base.Add(GetOHLC(val)); } diff --git a/Source/Feeds/Yahoo_Feed.cs b/Source/Feeds/Yahoo_Feed.cs index 5e3f7e2c..4808d888 100644 --- a/Source/Feeds/Yahoo_Feed.cs +++ b/Source/Feeds/Yahoo_Feed.cs @@ -9,12 +9,13 @@ Yahoo Finance - Free API feed to collect daily market quotes Period: number of days of collected history (default: 252) Usage: Yahoo_Feed ticker = new("MSFT", 20) - + */ public class Yahoo_Feed : TBars { public Yahoo_Feed(string Symbol = "IBM", int Period = 252) { + Period = (int)(Period*1.45); string requestUrl = "https://query1.finance.yahoo.com/v8/finance/chart/"+ Symbol+"?interval=1d&period1="+ (int)new DateTimeOffset(DateTime.UtcNow.AddDays(-Period+1)).ToUnixTimeSeconds()+"&period2="+ @@ -22,7 +23,7 @@ public class Yahoo_Feed : TBars System.Net.Http.HttpClient client = new(); var msg = client.GetStringAsync(requestUrl).Result; var jresult = JsonSerializer.Deserialize(msg).RootElement; - + jresult.TryGetProperty("chart",out JsonElement json); json.TryGetProperty("result",out json); json[0].TryGetProperty("timestamp",out JsonElement datetime); @@ -33,7 +34,7 @@ public class Yahoo_Feed : TBars json[0].TryGetProperty("low",out JsonElement low); json[0].TryGetProperty("close",out JsonElement close); json[0].TryGetProperty("volume",out JsonElement volume); - + for (int i=0; i CCI: Commodity Channel Index @@ -32,18 +34,16 @@ public class CCI_Series : Single_TBars_Indicator if (this._tp.Count > this._p) { this._tp.RemoveAt(0); } // average TP over _tp buffer - double _avgTp = 0; - for (int i = 0; i < this._tp.Count; i++) { _avgTp+=this._tp[i]; } - _avgTp /= this._tp.Count; + double _avgTp = _tp.Average(); // average Deviation over _tp buffer double _avgDv = 0; for (int i = 0; i < this._tp.Count; i++) { _avgDv += Math.Abs(_avgTp - this._tp[i]); } _avgDv /= this._tp.Count; + double _cci = (_avgDv == 0) ? double.NaN : (this._tp[this._tp.Count-1] - _avgTp) / (0.015 * _avgDv); - var result = (TBar.t, (this.Count < this._p && this._NaN) ? double.NaN : _cci); - base.Add(result, update); - } + base.Add((TBar.t, _cci), update, _NaN); + } } \ No newline at end of file diff --git a/Source/QuanTAlib.csproj b/Source/QuanTAlib.csproj index c91244ba..f0284516 100644 --- a/Source/QuanTAlib.csproj +++ b/Source/QuanTAlib.csproj @@ -2,6 +2,7 @@ QuanTAlib + 0.1.20 Library of Technical Indicators for .NET Quantitative Technical Analysis library for both real-time (streaming) and historical data analysis git @@ -31,7 +32,6 @@ Apache-2.0 - false full @@ -51,7 +51,7 @@ QuanTAlib2.png https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png True - ..\.sonarlint\mihakralj_quantalibcsharp.ruleset + QuanTAlib.ruleset @@ -67,12 +67,4 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - \ No newline at end of file diff --git a/Source/QuanTAlib.ruleset b/Source/QuanTAlib.ruleset new file mode 100644 index 00000000..c546ccdb --- /dev/null +++ b/Source/QuanTAlib.ruleset @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Source/Statistics/BIAS_Series.cs b/Source/Statistics/BIAS_Series.cs index 3fb543b7..7440a5f3 100644 --- a/Source/Statistics/BIAS_Series.cs +++ b/Source/Statistics/BIAS_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* BIAS: Rate of change between the source and a moving average. @@ -23,17 +24,11 @@ public class BIAS_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) { this._buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); - double _sma = 0; - for (int i = 0; i < this._buffer.Count; i++) { _sma += this._buffer[i]; } - _sma /= this._buffer.Count; - double _bias = (this._buffer[this._buffer.Count - 1] / ((_sma != 0) ? _sma : 1)) - 1; + double _sma = _buffer.Average(); + double _bias = (_buffer[_buffer.Count - 1] / ((_sma != 0) ? _sma : 1)) - 1; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _bias); - - base.Add(result, update); + base.Add((TValue.t, _bias), update, _NaN); } } diff --git a/Source/Statistics/CORR_Series.cs b/Source/Statistics/CORR_Series.cs index 8d13ab73..f82d82de 100644 --- a/Source/Statistics/CORR_Series.cs +++ b/Source/Statistics/CORR_Series.cs @@ -1,5 +1,7 @@ namespace QuanTAlib; using System; +using System.Collections.Generic; +using System.Linq; /* CORR: Pearson's Correlation Coefficient @@ -14,57 +16,37 @@ Sources: */ public class CORR_Series : Pair_TSeries_Indicator -{ - public CORR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) - { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } - } - - private readonly System.Collections.Generic.List _x = new(); - private readonly System.Collections.Generic.List _xx = new(); - private readonly System.Collections.Generic.List _y = new(); - private readonly System.Collections.Generic.List _yy = new(); - private readonly System.Collections.Generic.List _xy = new(); - - public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) - { - if (update) - { - _x[_x.Count - 1] = TValue1.v; - _xx[_xx.Count - 1] = TValue1.v * TValue1.v; - _y[_y.Count - 1] = TValue2.v; - _y[_yy.Count - 1] = TValue2.v * TValue2.v; - _xy[_xy.Count - 1] = TValue1.v * TValue2.v; - } - else - { - _x.Add(TValue1.v); - _xx.Add(TValue1.v * TValue1.v); - _y.Add(TValue2.v); - _yy.Add(TValue2.v * TValue2.v); - _xy.Add(TValue1.v * TValue2.v); - } - if (_x.Count > this._p) { _x.RemoveAt(0); } - if (_xx.Count > this._p) { _xx.RemoveAt(0); } - if (_y.Count > this._p) { _y.RemoveAt(0); } - if (_yy.Count > this._p) { _yy.RemoveAt(0); } - if (_xy.Count > this._p) { _xy.RemoveAt(0); } - - double _sumx = 0; - for (int i = 0; i < _x.Count; i++) { _sumx += _x[i]; } - double _sumxx = 0; - for (int i = 0; i < _xx.Count; i++) { _sumxx += _xx[i]; } - double _sumy = 0; - for (int i = 0; i < _y.Count; i++) { _sumy += _y[i]; } - double _sumyy = 0; - for (int i = 0; i < _yy.Count; i++) { _sumyy += _yy[i]; } - double _sumxy = 0; - for (int i = 0; i < _xy.Count; i++) { _sumxy += _xy[i]; } - - double _div = (_sumxx - _sumx * _sumx / _p) * (_sumyy - _sumy * _sumy / _p); - double _cor = (_div != 0) ? (_sumxy - _sumx * _sumy / _p) / Math.Sqrt(_div) : 0.0; - - var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _cor); - if (update) { base[base.Count - 1] = result; } else { base.Add(result); } +{ + public CORR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } } -} \ No newline at end of file + + private readonly System.Collections.Generic.List _x = new(); + private readonly System.Collections.Generic.List _xx = new(); + private readonly System.Collections.Generic.List _y = new(); + private readonly System.Collections.Generic.List _yy = new(); + private readonly System.Collections.Generic.List _xy = new(); + + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) + { + Add_Replace_Trim(_x, TValue1.v, _p, update); + Add_Replace_Trim(_xx, TValue1.v * TValue1.v, _p, update); + Add_Replace_Trim(_y, TValue2.v, _p, update); + Add_Replace_Trim(_yy, TValue2.v * TValue2.v, _p, update); + Add_Replace_Trim(_xy, TValue1.v * TValue2.v, _p, update); + + double _sumx = _x.Sum(); + double _sumxx = _xx.Sum(); + double _sumy = _y.Sum(); + double _sumyy = _yy.Sum(); + double _sumxy = _xy.Sum(); + + double _covar = (_sumxx - _sumx * _sumx / _p) * (_sumyy - _sumy * _sumy / _p); + double _cor = (_covar != 0) ? (_sumxy - _sumx * _sumy / _p) / Math.Sqrt(_covar) : 0.0; + + var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _cor); + if (update) { base[base.Count - 1] = result; } else { base.Add(result); } + + } +} diff --git a/Source/Statistics/COVAR_Series.cs b/Source/Statistics/COVAR_Series.cs new file mode 100644 index 00000000..8fd20b47 --- /dev/null +++ b/Source/Statistics/COVAR_Series.cs @@ -0,0 +1,40 @@ +namespace QuanTAlib; +using System; +using System.Linq; + +/* +COVAR: Covariance + Covariance is defined as the expected value (or mean) of the product + of their deviations from their individual expected values. + +Sources: + https://en.wikipedia.org/wiki/Covariance + + */ + +public class COVAR_Series : Pair_TSeries_Indicator +{ + public COVAR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + } + + private readonly System.Collections.Generic.List _x = new(); + private readonly System.Collections.Generic.List _y = new(); + private readonly System.Collections.Generic.List _xy = new(); + + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) + { + Add_Replace_Trim(_x, TValue1.v, _p, update); + Add_Replace_Trim(_y, TValue2.v, _p, update); + Add_Replace_Trim(_xy, TValue1.v * TValue2.v, _p, update); + + double _avgx = _x.Average(); + double _avgy = _y.Average(); + double _avgxy = _xy.Average(); + double _covar = _avgxy - (_avgx * _avgy); + + var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _covar); + if (update) { base[base.Count - 1] = result; } else { base.Add(result); } + } +} \ No newline at end of file diff --git a/Source/Statistics/ENTP_Series.cs b/Source/Statistics/ENTROPY_Series.cs similarity index 52% rename from Source/Statistics/ENTP_Series.cs rename to Source/Statistics/ENTROPY_Series.cs index a75af82f..f27ea6c7 100644 --- a/Source/Statistics/ENTP_Series.cs +++ b/Source/Statistics/ENTROPY_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* ENTP: Entropy @@ -16,9 +17,9 @@ Sources: */ -public class ENTP_Series : Single_TSeries_Indicator +public class ENTROPY_Series : Single_TSeries_Indicator { - public ENTP_Series(TSeries source, int period, double logbase = 2.0, bool useNaN = false) : base(source, period, useNaN) + public ENTROPY_Series(TSeries source, int period, double logbase = 2.0, bool useNaN = false) : base(source, period, useNaN) { this._logbase = logbase; if (base._data.Count > 0) { base.Add(base._data); } @@ -29,24 +30,15 @@ public class ENTP_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) { this._buffer.RemoveAt(0); } - - double _sum = 0; - for (int i = 0; i < this._buffer.Count; i++) { _sum += this._buffer[i]; } - + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sum = _buffer.Sum(); + double _pp = this._buffer[this._buffer.Count - 1] / _sum; double _ppp = -_pp * Math.Log(_pp) / Math.Log(this._logbase); - if (update) { this._buff2[this._buff2.Count - 1] = _ppp; } - else { this._buff2.Add(_ppp); } - if (this._buff2.Count > this._p && this._p != 0) { this._buff2.RemoveAt(0); } + Add_Replace_Trim(_buff2, _ppp, _p, update); + double _entp = _buff2.Sum(); - double _entp = 0; - for (int i = 0; i < this._buff2.Count; i++) { _entp += this._buff2[i]; } - - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _entp); - base.Add(result, update); + base.Add((TValue.t, _entp), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/KURT_Series.cs b/Source/Statistics/KURTOSIS_Series.cs similarity index 69% rename from Source/Statistics/KURT_Series.cs rename to Source/Statistics/KURTOSIS_Series.cs index 10b1242f..ae1c93b2 100644 --- a/Source/Statistics/KURT_Series.cs +++ b/Source/Statistics/KURTOSIS_Series.cs @@ -1,62 +1,57 @@ -namespace QuanTAlib; -using System; - -/* -KURT: Kurtosis of population - Kurtosis characterizes the relative peakedness or flatness of a distribution - compared with the normal distribution. Positive kurtosis indicates a relatively - peaked distribution. Negative kurtosis indicates a relatively flat distribution. - - The normal curve is called Mesokurtic curve. If the curve of a distribution is - more outlier prone (or heavier-tailed) than a normal or mesokurtic curve then - it is referred to as a Leptokurtic curve. If a curve is less outlier prone (or - lighter-tailed) than a normal curve, it is called as a platykurtic curve. - -Calculation: - sum4 = Σ(close-SMA)^4 - sum2 = (Σ(close-SMA)^2)^2 - KURT = length * (sum4/sum2) - -Sources: - https://en.wikipedia.org/wiki/Kurtosis - https://stats.oarc.ucla.edu/other/mult-pkg/faq/general/faq-whats-with-the-different-formulas-for-kurtosis/ - - */ - -public class KURT_Series : Single_TSeries_Indicator -{ - public KURT_Series(TSeries source, int period, double logbase = 2.0, bool useNaN = false) : base(source, period, useNaN) - { - this._logbase = logbase; - if (base._data.Count > 0) { base.Add(base._data); } - } - protected double _logbase; - private readonly System.Collections.Generic.List _buffer = new(); - - public override void Add((System.DateTime t, double v) TValue, bool update) - { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) { this._buffer.RemoveAt(0); } - - double _n = this._buffer.Count; - - double _avg = 0; - for (int i = 0; i < this._buffer.Count; i++) { _avg += this._buffer[i]; } - _avg /= _n; - - double _s2 = 0; - double _s4 = 0; - for (int i = 0; i < this._buffer.Count; i++) - { - _s2 += (this._buffer[i] - _avg) * (this._buffer[i] - _avg); - _s4 += (this._buffer[i] - _avg) * (this._buffer[i] - _avg) * (this._buffer[i] - _avg) * (this._buffer[i] - _avg); - } - - double _Vx = _s2 / (_n - 1); - double _kurt = (_n > 3) ? ((((_n * (_n + 1)) / (((_n - 1) * (_n - 2)) * (_n - 3))) * (_s4 / (_Vx * _Vx))) - (3 * (((_n - 1) * (_n - 1)) / ((_n - 2) * (_n - 3))))) : Double.NaN; - - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? Double.NaN : _kurt); - base.Add(result, update); - } +namespace QuanTAlib; +using System; +using System.Linq; + +/* +KURT: Kurtosis of population + Kurtosis characterizes the relative peakedness or flatness of a distribution + compared with the normal distribution. Positive kurtosis indicates a relatively + peaked distribution. Negative kurtosis indicates a relatively flat distribution. + + The normal curve is called Mesokurtic curve. If the curve of a distribution is + more outlier prone (or heavier-tailed) than a normal or mesokurtic curve then + it is referred to as a Leptokurtic curve. If a curve is less outlier prone (or + lighter-tailed) than a normal curve, it is called as a platykurtic curve. + +Calculation: + sum4 = Σ(close-SMA)^4 + sum2 = (Σ(close-SMA)^2)^2 + KURT = length * (sum4/sum2) + +Sources: + https://en.wikipedia.org/wiki/Kurtosis + https://stats.oarc.ucla.edu/other/mult-pkg/faq/general/faq-whats-with-the-different-formulas-for-kurtosis/ + + */ + +public class KURTOSIS_Series : Single_TSeries_Indicator +{ + public KURTOSIS_Series(TSeries source, int period, double logbase = 2.0, bool useNaN = false) : base(source, period, useNaN) + { + this._logbase = logbase; + if (base._data.Count > 0) { base.Add(base._data); } + } + protected double _logbase; + private readonly System.Collections.Generic.List _buffer = new(); + + public override void Add((System.DateTime t, double v) TValue, bool update) + { + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _n = this._buffer.Count; + double _avg = _buffer.Average(); + + double _s2 = 0; + double _s4 = 0; + for (int i = 0; i < this._buffer.Count; i++) + { + _s2 += (_buffer[i] - _avg) * (_buffer[i] - _avg); + _s4 += (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg); + } + + double _Vx = _s2 / (_n - 1); + double _kurt = (_n > 3) ? ((((_n * (_n + 1)) / (((_n - 1) * (_n - 2)) * (_n - 3))) * (_s4 / (_Vx * _Vx))) - (3 * (((_n - 1) * (_n - 1)) / ((_n - 2) * (_n - 3))))) : Double.NaN; + + var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? Double.NaN : _kurt); + base.Add(result, update); + } } \ No newline at end of file diff --git a/Source/Statistics/LINREG_Series.cs b/Source/Statistics/LINREG_Series.cs index 44e1bb2c..0987555c 100644 --- a/Source/Statistics/LINREG_Series.cs +++ b/Source/Statistics/LINREG_Series.cs @@ -34,9 +34,7 @@ public class LINREG_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p) { this._buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); int _len = this._buffer.Count; @@ -79,7 +77,7 @@ public class LINREG_Series : Single_TSeries_Indicator double _RSquared = arrr * arrr; var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _slope); - base.Add(ret, update); + base.Add(ret, update, _NaN); ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _intercept); Intercept.Add(ret, update); diff --git a/Source/Statistics/MAD_Series.cs b/Source/Statistics/MAD_Series.cs index 0c76fdaf..8a032e4a 100644 --- a/Source/Statistics/MAD_Series.cs +++ b/Source/Statistics/MAD_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* MAD: Mean Absolute Deviation @@ -24,19 +25,14 @@ public class MAD_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + double _sma = _buffer.Average(); double _mad = 0; for (int i = 0; i < _buffer.Count; i++) { _mad += Math.Abs(_buffer[i] - _sma); } _mad /= this._buffer.Count; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _mad); - base.Add(result, update); + base.Add((TValue.t, _mad), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/MAPE_Series.cs b/Source/Statistics/MAPE_Series.cs index 7b112aae..ca737cf1 100644 --- a/Source/Statistics/MAPE_Series.cs +++ b/Source/Statistics/MAPE_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* MAPE: Mean Absolute Percentage Error @@ -27,19 +28,15 @@ public class MAPE_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _mape = 0; - for (int i = 0; i < _buffer.Count; i++) { _mape += (_buffer[i] != 0) ? Math.Abs(_buffer[i] - _sma) / Math.Abs(_buffer[i]) : double.PositiveInfinity; } - _mape /= this._buffer.Count; + for (int i = 0; i < _buffer.Count; i++) { + _mape += (_buffer[i] != 0) ? Math.Abs(_buffer[i] - _sma) / Math.Abs(_buffer[i]) : double.PositiveInfinity; + } + _mape /= (_buffer.Count>0) ? _buffer.Count : 1; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _mape); - base.Add(result, update); + base.Add((TValue.t, _mape), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/MED_Series.cs b/Source/Statistics/MEDIAN_Series.cs similarity index 70% rename from Source/Statistics/MED_Series.cs rename to Source/Statistics/MEDIAN_Series.cs index bf5e6586..0bbd6948 100644 --- a/Source/Statistics/MED_Series.cs +++ b/Source/Statistics/MEDIAN_Series.cs @@ -1,47 +1,44 @@ -namespace QuanTAlib; -using System; - -/* -MED - Median value - Median of numbers is the middlemost value of the given set of numbers. - It separates the higher half and the lower half of a given data sample. - At least half of the observations are smaller than or equal to median - and at least half of the observations are greater than or equal to the median. - - If the number of values is odd, the middlemost observation of the sorted - list is the median of the given data. If the number of values is even, - median is the average of (n/2)th and [(n/2) + 1]th values of the sorted list. - - If period = 0 => period is max - -Sources: - https://corporatefinanceinstitute.com/resources/knowledge/other/median/ - https://en.wikipedia.org/wiki/Median - - */ - -public class MED_Series : Single_TSeries_Indicator -{ - public MED_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) - { - if (base._data.Count > 0) { base.Add(base._data); } - } - private readonly System.Collections.Generic.List _buffer = new(); - - public override void Add((System.DateTime t, double v) TValue, bool update) - { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) { this._buffer.RemoveAt(0); } - - System.Collections.Generic.List _s = new(this._buffer); - _s.Sort(); - int _p1 = _s.Count / 2; - int _p2 = Math.Max(0, (_s.Count / 2) - 1); - double _med = (_s.Count % 2 != 0) ? _s[_p1] : (_s[_p1] + _s[_p2]) / 2; - - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _med); - - base.Add(result, update); - } -} +namespace QuanTAlib; +using System; +using static System.Net.Mime.MediaTypeNames; + +/* +MED - Median value + Median of numbers is the middlemost value of the given set of numbers. + It separates the higher half and the lower half of a given data sample. + At least half of the observations are smaller than or equal to median + and at least half of the observations are greater than or equal to the median. + + If the number of values is odd, the middlemost observation of the sorted + list is the median of the given data. If the number of values is even, + median is the average of (n/2)th and [(n/2) + 1]th values of the sorted list. + + If period = 0 => period is max + +Sources: + https://corporatefinanceinstitute.com/resources/knowledge/other/median/ + https://en.wikipedia.org/wiki/Median + + */ + +public class MEDIAN_Series : Single_TSeries_Indicator +{ + public MEDIAN_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) + { + if (base._data.Count > 0) { base.Add(base._data); } + } + private readonly System.Collections.Generic.List _buffer = new(); + + public override void Add((System.DateTime t, double v) TValue, bool update) + { + Add_Replace_Trim(_buffer, TValue.v, _p, update); + + System.Collections.Generic.List _s = new(this._buffer); + _s.Sort(); + int _p1 = _s.Count / 2; + int _p2 = Math.Max(0, (_s.Count / 2) - 1); + double _med = (_s.Count % 2 != 0) ? _s[_p1] : (_s[_p1] + _s[_p2]) / 2; + + base.Add((TValue.t, _med), update, _NaN); + } +} diff --git a/Source/Statistics/MSE_Series.cs b/Source/Statistics/MSE_Series.cs index 896dd8b9..1dcdd6a9 100644 --- a/Source/Statistics/MSE_Series.cs +++ b/Source/Statistics/MSE_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* MSE: Mean Square Error @@ -20,19 +21,13 @@ public class MSE_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _mse = 0; for (int i = 0; i < _buffer.Count; i++) { _mse += (_buffer[i] - _sma) * (_buffer[i] - _sma); } _mse /= this._buffer.Count; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _mse); - base.Add(result, update); + base.Add((TValue.t, _mse), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/SDEV_Series.cs b/Source/Statistics/SDEV_Series.cs index 0a0675e6..88f6ed8f 100644 --- a/Source/Statistics/SDEV_Series.cs +++ b/Source/Statistics/SDEV_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* SDEV: Population Standard Deviation @@ -25,20 +26,14 @@ public class SDEV_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _pvar = 0; for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } _pvar /= this._buffer.Count; double _psdev = Math.Sqrt(_pvar); - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _psdev); - base.Add(result, update); + base.Add((TValue.t, _psdev), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/SMAPE_Series.cs b/Source/Statistics/SMAPE_Series.cs index de371fbc..dc0eaaa5 100644 --- a/Source/Statistics/SMAPE_Series.cs +++ b/Source/Statistics/SMAPE_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* SMAPE: Symmetric Mean Absolute Percentage Error @@ -20,19 +21,13 @@ public class SMAPE_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _smape = 0; for (int i = 0; i < _buffer.Count; i++) { _smape += Math.Abs(_buffer[i] - _sma) / (Math.Abs(_buffer[i]) + Math.Abs(_sma)); } _smape /= this._buffer.Count; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _smape); - base.Add(result, update); + base.Add((TValue.t, _smape), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/SSDEV_Series.cs b/Source/Statistics/SSDEV_Series.cs index 376ffa79..ab882476 100644 --- a/Source/Statistics/SSDEV_Series.cs +++ b/Source/Statistics/SSDEV_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* SSDEV: (Corrected) Sample Standard Deviation @@ -25,20 +26,14 @@ public class SSDEV_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) { this._buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < this._buffer.Count; i++) { _sma += this._buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _svar = 0; - for (int i = 0; i < this._buffer.Count; i++) { _svar += (this._buffer[i] - _sma) * (this._buffer[i] - _sma); } - _svar /= (this._buffer.Count > 1) ? this._buffer.Count - 1 : 1; // Bessel's correction + for (int i = 0; i < this._buffer.Count; i++) { _svar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _svar /= (_buffer.Count > 1) ? _buffer.Count - 1 : 1; // Bessel's correction double _ssdev = Math.Sqrt(_svar); - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _ssdev); - base.Add(result, update); + base.Add((TValue.t, _ssdev), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/SVAR_Series.cs b/Source/Statistics/SVAR_Series.cs index 56f6d733..5f569081 100644 --- a/Source/Statistics/SVAR_Series.cs +++ b/Source/Statistics/SVAR_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* SVAR: Sample Variance @@ -25,19 +26,13 @@ public class SVAR_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p && this._p != 0) { this._buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < this._buffer.Count; i++) { _sma += this._buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _svar = 0; for (int i = 0; i < this._buffer.Count; i++) { _svar += (this._buffer[i] - _sma) * (this._buffer[i] - _sma); } _svar /= (this._buffer.Count > 1) ? this._buffer.Count - 1 : 1; // Bessel's correction - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _svar); - base.Add(result, update); + base.Add((TValue.t, _svar), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/VAR_Series.cs b/Source/Statistics/VAR_Series.cs index 9c3b3e63..2aebee3b 100644 --- a/Source/Statistics/VAR_Series.cs +++ b/Source/Statistics/VAR_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* VAR: Population Variance @@ -25,19 +26,13 @@ public class VAR_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _pvar = 0; for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } _pvar /= this._buffer.Count; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _pvar); - base.Add(result, update); + base.Add((TValue.t, _pvar), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/WMAPE_Series.cs b/Source/Statistics/WMAPE_Series.cs index 0d770358..ff2a112a 100644 --- a/Source/Statistics/WMAPE_Series.cs +++ b/Source/Statistics/WMAPE_Series.cs @@ -1,9 +1,12 @@ namespace QuanTAlib; using System; +using System.Linq; /* WMAPE: Weighted Mean Absolute Percentage Error - Measures the size of the error in percentage terms + Measures the size of the error in percentage terms. Improves problems with MAPE + when there are zero or close-to-zero values because there would be a division by zero + or values of MAPE tending to infinity. Sources: https://en.wikipedia.org/wiki/WMAPE @@ -20,13 +23,8 @@ public class WMAPE_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _div = 0; double _wmape = 0; @@ -35,9 +33,8 @@ public class WMAPE_Series : Single_TSeries_Indicator _wmape += Math.Abs(_buffer[i] - _sma); _div += Math.Abs(_buffer[i]); } - _wmape /= _div; + _wmape = (_div!=0) ? _wmape/_div : double.PositiveInfinity; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _wmape); - base.Add(result, update); + base.Add((TValue.t, _wmape), update, _NaN); } } \ No newline at end of file diff --git a/Source/Statistics/ZSCORE_Series.cs b/Source/Statistics/ZSCORE_Series.cs index 73034315..097482d9 100644 --- a/Source/Statistics/ZSCORE_Series.cs +++ b/Source/Statistics/ZSCORE_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* ZSCORE: number of standard deviations from SMA @@ -31,13 +32,8 @@ public class ZSCORE_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); double _pvar = 0; for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } @@ -45,7 +41,6 @@ public class ZSCORE_Series : Single_TSeries_Indicator double _psdev = Math.Sqrt(_pvar); double _zscore = (_psdev == 0) ? double.NaN : (TValue.v - _sma) / _psdev; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _zscore); - base.Add(result, update); + base.Add((TValue.t, _zscore), update, _NaN); } } \ No newline at end of file diff --git a/Source/Trends/ALMA_Series.cs b/Source/Trends/ALMA_Series.cs index 122cc2d4..39ff8101 100644 --- a/Source/Trends/ALMA_Series.cs +++ b/Source/Trends/ALMA_Series.cs @@ -19,49 +19,45 @@ TODO: Discrepancy with Pandas-TA (but passes the validation with Skender.GetAlma */ public class ALMA_Series : Single_TSeries_Indicator -{ - private readonly System.Collections.Generic.List _buffer = new(); - private readonly double[] _weight; - private double _norm; - private readonly double _offset, _sigma; - - public ALMA_Series(TSeries source, int period, double offset = 0.85, double sigma = 6.0, bool useNaN = false) - : base(source, period, useNaN) - { - _offset = offset; - _sigma = sigma; - _weight = new double[period]; - - if (this._data.Count > 0) { base.Add(this._data); } - } - - public override void Add((System.DateTime t, double v) TValue, bool update) - { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else { this._buffer.Add(TValue.v); } - if (this._buffer.Count > this._p) { this._buffer.RemoveAt(0); } - - if (this._buffer.Count <= _p) { calc_weights(); } - - double _weightedSum = 0; - for (int i = 0; i < this._buffer.Count; i++) { _weightedSum += _weight[i] * _buffer[i]; } - double _alma = _weightedSum / _norm; - - var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _alma); - base.Add(ret, update); - } - - private void calc_weights() - { - int _len = this._buffer.Count; - _norm = 0; - double _m = _offset * (_len - 1); - double _s = _len / _sigma; - for (int i = 0; i < _len; i++) - { - double _wt = Math.Exp(-((i - _m) * (i - _m)) / (2 * _s * _s)); - _weight[i] = _wt; - _norm += _wt; - } - } +{ + private readonly System.Collections.Generic.List _buffer = new(); + private readonly double[] _weight; + private double _norm; + private readonly double _offset, _sigma; + + public ALMA_Series(TSeries source, int period, double offset = 0.85, double sigma = 6.0, bool useNaN = false) + : base(source, period, useNaN) + { + _offset = offset; + _sigma = sigma; + _weight = new double[period]; + + if (this._data.Count > 0) { base.Add(this._data); } + } + + public override void Add((System.DateTime t, double v) TValue, bool update) + { + Add_Replace_Trim(_buffer, TValue.v, _p, update); + + if (this._buffer.Count <= _p) + { + int _len = this._buffer.Count; + _norm = 0; + double _m = _offset * (_len - 1); + double _s = _len / _sigma; + for (int i = 0; i < _len; i++) + { + double _wt = Math.Exp(-((i - _m) * (i - _m)) / (2 * _s * _s)); + _weight[i] = _wt; + _norm += _wt; + } + } + + double _weightedSum = 0; + for (int i = 0; i < this._buffer.Count; i++) + { _weightedSum += _weight[i] * _buffer[i]; } + double _alma = _weightedSum / _norm; + + base.Add((TValue.t, _alma), update, _NaN); + } } diff --git a/Source/Trends/DEMA_Series.cs b/Source/Trends/DEMA_Series.cs index 5832b8eb..8a7d8e92 100644 --- a/Source/Trends/DEMA_Series.cs +++ b/Source/Trends/DEMA_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* DEMA: Double Exponential Moving Average @@ -41,16 +42,9 @@ public class DEMA_Series : Single_TSeries_Indicator if (this.Count < this._p) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else - { - _buffer.Add(TValue.v); - } - if (_buffer.Count > this._p) { _buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; _ema1 = _ema2 = _sma; } else @@ -65,7 +59,6 @@ public class DEMA_Series : Single_TSeries_Indicator this._lastema1 = _ema1; this._lastema2 = _ema2; - var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _dema); - base.Add(ret, update); + base.Add((TValue.t, _dema), update, _NaN); } } diff --git a/Source/Trends/EMA_Series.cs b/Source/Trends/EMA_Series.cs index b3be32e4..aae20c2a 100644 --- a/Source/Trends/EMA_Series.cs +++ b/Source/Trends/EMA_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* EMA: Exponential Moving Average @@ -35,20 +36,13 @@ public class EMA_Series : Single_TSeries_Indicator public override void Add((DateTime t, double v) TValue, bool update) { - double _ema = 0; + double _ema; if (update) { this._lastema = this._lastlastema; } if (this.Count < this._p) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else - { - this._buffer.Add(TValue.v); - } - if (this._buffer.Count > this._p) { this._buffer.RemoveAt(0); } - - for (int i = 0; i < this._buffer.Count; i++) { _ema += this._buffer[i]; } - _ema /= this._buffer.Count; + Add_Replace(_buffer, TValue.v, update); + _ema = _buffer.Average(); } else { @@ -58,7 +52,6 @@ public class EMA_Series : Single_TSeries_Indicator this._lastlastema = this._lastema; this._lastema = _ema; - var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _ema); - base.Add(ret, update); + base.Add((TValue.t, _ema), update, _NaN); } } \ No newline at end of file diff --git a/Source/Trends/HEMA_Series.cs b/Source/Trends/HEMA_Series.cs index 0ec45cf7..3f1678f5 100644 --- a/Source/Trends/HEMA_Series.cs +++ b/Source/Trends/HEMA_Series.cs @@ -2,8 +2,8 @@ using System; /* -HEMA: Hull-EMA Moving Average - Modified HUll Moving Average; instead of using WMA (Weighted MA) for acalculation, +HEMA: Hull-EMA Moving Average - a hybrid indicator + Modified HUll Moving Average; instead of using WMA (Weighted MA) for calculation, HEMA uses EMA for Hull's formula: EMA1 = EMA(n/2) of price - where k = 4/(n/2 +1) @@ -39,17 +39,11 @@ public class HEMA_Series : Single_TSeries_Indicator this._lastema2 = this._lastlastema2; this._lastema3 = this._lastlastema3; } - double _ema1 = System.Double.IsNaN(this._lastema1) - ? TValue.v - : TValue.v * this._k1 + this._lastema1 * (1 - this._k1); - double _ema2 = System.Double.IsNaN(this._lastema2) - ? TValue.v - : TValue.v * this._k2 + this._lastema2 * (1 - this._k2); + double _ema1 = System.Double.IsNaN(this._lastema1) ? TValue.v : TValue.v * this._k1 + this._lastema1 * (1 - this._k1); + double _ema2 = System.Double.IsNaN(this._lastema2) ? TValue.v : TValue.v * this._k2 + this._lastema2 * (1 - this._k2); double _rawhema = (2 * _ema1) - _ema2; - double _ema3 = System.Double.IsNaN(this._lastema3) - ? _rawhema - : _rawhema * this._k3 + this._lastema3 * (1 - this._k3); + double _ema3 = System.Double.IsNaN(this._lastema3) ? _rawhema : _rawhema * this._k3 + this._lastema3 * (1 - this._k3); this._lastlastema1 = this._lastema1; this._lastlastema2 = this._lastema2; @@ -58,8 +52,6 @@ public class HEMA_Series : Single_TSeries_Indicator this._lastema2 = _ema2; this._lastema3 = _ema3; - (System.DateTime t, double v) result = - (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _ema3); - base.Add(result, update); + base.Add((TValue.t, _ema3), update, _NaN); } } \ No newline at end of file diff --git a/Source/Trends/HMA_Series.cs b/Source/Trends/HMA_Series.cs index b8acb657..ad6f30f8 100644 --- a/Source/Trends/HMA_Series.cs +++ b/Source/Trends/HMA_Series.cs @@ -73,7 +73,6 @@ public class HMA_Series : TSeries { this._wma1 += this._buf1[i] * this._weights[i]; } - this._wma1 /= (this._buf1.Count * (this._buf1.Count + 1)) * 0.5; this._wma2 = 0; @@ -81,7 +80,6 @@ public class HMA_Series : TSeries { this._wma2 += this._buf2[i] * this._weights[i]; } - this._wma2 /= (this._buf2.Count * (this._buf2.Count + 1)) * 0.5; if (update) @@ -92,6 +90,7 @@ public class HMA_Series : TSeries { this._buf3.Add(2 * this._wma1 - this._wma2); } + if (this._buf3.Count > (int)Math.Sqrt(this._p)) { this._buf3.RemoveAt(0); diff --git a/Source/Trends/JMA_Series.cs b/Source/Trends/JMA_Series.cs index b180d79d..54e588ee 100644 --- a/Source/Trends/JMA_Series.cs +++ b/Source/Trends/JMA_Series.cs @@ -150,12 +150,9 @@ public class JMA_Series : Single_TSeries_Indicator double det1 = ((ma2 - this.prev_jma) * (1 - alpha) * (1 - alpha)) + (this.prev_det1 * alpha * alpha); this.prev_det1 = det1; - var jma = this.prev_jma + det1; - this.prev_jma = jma; + var _jma = this.prev_jma + det1; + this.prev_jma = _jma; - (System.DateTime t, double v) result = - (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : jma); - base.Add(result, update); - - } + base.Add((TValue.t, _jma), update, _NaN); + } } \ No newline at end of file diff --git a/Source/Trends/KAMA_Series.cs b/Source/Trends/KAMA_Series.cs index 2f46e25d..01425f16 100644 --- a/Source/Trends/KAMA_Series.cs +++ b/Source/Trends/KAMA_Series.cs @@ -44,6 +44,7 @@ public class KAMA_Series : Single_TSeries_Indicator _buffer.Add(TValue.v); } if (_buffer.Count > _p + 1) { _buffer.RemoveAt(0); } + double _kama = 0; if (this.Count < this._p) { for (int i = 0; i < this._buffer.Count; i++) { _kama += this._buffer[i]; } @@ -59,7 +60,6 @@ public class KAMA_Series : Single_TSeries_Indicator } _lastlastkama = _lastkama; _lastkama = _kama; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _kama); - base.Add(result, update); - } + base.Add((TValue.t, _kama), update, _NaN); + } } \ No newline at end of file diff --git a/Source/Trends/MACD_Series.cs b/Source/Trends/MACD_Series.cs index 1050cd1f..ebe205f2 100644 --- a/Source/Trends/MACD_Series.cs +++ b/Source/Trends/MACD_Series.cs @@ -40,7 +40,6 @@ public class MACD_Series : Single_TSeries_Indicator _TSfast.Add(TValue, true); } _macd = this._TSmacd[(this.Count < this._TSmacd.Count) ? this.Count : this._TSmacd.Count - 1].v; - var result = (TValue.t, _macd); - base.Add(result, update); - } + base.Add((TValue.t, _macd), update, _NaN); + } } \ No newline at end of file diff --git a/Source/Trends/RMA_Series.cs b/Source/Trends/RMA_Series.cs index 66009437..e8b1ef63 100644 --- a/Source/Trends/RMA_Series.cs +++ b/Source/Trends/RMA_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* RMA: wildeR Moving Average @@ -34,20 +35,13 @@ public class RMA_Series : Single_TSeries_Indicator public override void Add((DateTime t, double v) TValue, bool update) { - double _ema = 0; + double _ema; if (update) { this._lastema = this._lastlastema; } if (this.Count < this._p) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else - { - _buffer.Add(TValue.v); - } - if (_buffer.Count > this._p) { _buffer.RemoveAt(0); } - - for (int i = 0; i < _buffer.Count; i++) { _ema += _buffer[i]; } - _ema /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + _ema = _buffer.Average(); } else { @@ -57,7 +51,6 @@ public class RMA_Series : Single_TSeries_Indicator this._lastlastema = this._lastema; this._lastema = _ema; - var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _ema); - base.Add(ret, update); - } + base.Add((TValue.t, _ema), update, _NaN); + } } \ No newline at end of file diff --git a/Source/Trends/SMA_Series.cs b/Source/Trends/SMA_Series.cs index 6bbeb1fb..1f07a088 100644 --- a/Source/Trends/SMA_Series.cs +++ b/Source/Trends/SMA_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* SMA: Simple Moving Average @@ -26,16 +27,9 @@ public class SMA_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Sum() / _buffer.Count; - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; - - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _sma); - - base.Add(result, update); + base.Add((TValue.t, _sma), update, _NaN); } } diff --git a/Source/Trends/SMMA_Series.cs b/Source/Trends/SMMA_Series.cs index 2f31e824..08a22068 100644 --- a/Source/Trends/SMMA_Series.cs +++ b/Source/Trends/SMMA_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* SMMA: Smoothed Moving Average @@ -34,15 +35,8 @@ public class SMMA_Series : Single_TSeries_Indicator if (this.Count < this._p) { - if (update) { this._buffer[this._buffer.Count - 1] = TValue.v; } - else - { - this._buffer.Add(TValue.v); - } - if (this._buffer.Count > this._p) { this._buffer.RemoveAt(0); } - - for (int i = 0; i < this._buffer.Count; i++) { _smma += this._buffer[i]; } - _smma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + _smma = _buffer.Average(); } else { @@ -52,7 +46,6 @@ public class SMMA_Series : Single_TSeries_Indicator this._lastlastsmma = this._lastsmma; this._lastsmma = _smma; - var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _smma); - base.Add(ret, update); - } + base.Add((TValue.t, _smma), update, _NaN); + } } \ No newline at end of file diff --git a/Source/Trends/TEMA_Series.cs b/Source/Trends/TEMA_Series.cs index ba61d6a0..3cb1aafc 100644 --- a/Source/Trends/TEMA_Series.cs +++ b/Source/Trends/TEMA_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* TEMA: Triple Exponential Moving Average @@ -44,16 +45,8 @@ public class TEMA_Series : Single_TSeries_Indicator if (this.Count < this._p) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else - { - _buffer.Add(TValue.v); - } - if (_buffer.Count > this._p) { _buffer.RemoveAt(0); } - - double _sma = 0; - for (int i = 0; i < _buffer.Count; i++) { _sma += _buffer[i]; } - _sma /= this._buffer.Count; + Add_Replace_Trim(_buffer, TValue.v, _p, update); + double _sma = _buffer.Average(); _ema1 = _ema2 = _ema3 = _sma; } else @@ -72,7 +65,6 @@ public class TEMA_Series : Single_TSeries_Indicator this._lastema2 = _ema2; this._lastema3 = _ema3; - var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _tema); - base.Add(ret, update); - } + base.Add((TValue.t, _tema), update, _NaN); + } } \ No newline at end of file diff --git a/Source/Trends/TRIMA_Series.cs b/Source/Trends/TRIMA_Series.cs index e0fe6e9b..d0276359 100644 --- a/Source/Trends/TRIMA_Series.cs +++ b/Source/Trends/TRIMA_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* TRIMA: Triangular Moving Average @@ -31,19 +32,12 @@ public class TRIMA_Series : Single_TSeries_Indicator { if (update) { _buffer1[_buffer1.Count - 1] = TValue.v; } else { _buffer1.Add(TValue.v); } if (_buffer1.Count > this._p1b && this._p1b != 0) { _buffer1.RemoveAt(0); } - - double _sma1 = 0; - for (int i = 0; i < _buffer1.Count; i++) { _sma1 += _buffer1[i]; } - _sma1 /= this._buffer1.Count; + double _sma1 = _buffer1.Average(); if (update) { _buffer2[_buffer2.Count - 1] = _sma1; } else { _buffer2.Add(_sma1); } if (_buffer2.Count > this._p1a && this._p1a != 0) { _buffer2.RemoveAt(0); } + double _trima = _buffer2.Average(); - double _trima = 0; - for (int i = 0; i < _buffer2.Count; i++) { _trima += _buffer2[i]; } - _trima /= this._buffer2.Count; - - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _trima); - base.Add(result, update); - } + base.Add((TValue.t, _trima), update, _NaN); + } } \ No newline at end of file diff --git a/Source/Trends/WMA_Series.cs b/Source/Trends/WMA_Series.cs index 6b182741..a6c4a1aa 100644 --- a/Source/Trends/WMA_Series.cs +++ b/Source/Trends/WMA_Series.cs @@ -24,16 +24,12 @@ public class WMA_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) { _buffer[_buffer.Count - 1] = TValue.v; } - else { _buffer.Add(TValue.v); } - if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); } + Add_Replace_Trim(_buffer, TValue.v, _p, update); double _wma = 0; for (int i = 0; i < _buffer.Count; i++) { _wma += _buffer[i] * this._weights[i]; } _wma /= (this._buffer.Count * (this._buffer.Count + 1)) * 0.5; - var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _wma); - - base.Add(result, update); + base.Add((TValue.t, _wma), update, _NaN); } } \ No newline at end of file diff --git a/Source/Trends/ZLEMA_Series.cs b/Source/Trends/ZLEMA_Series.cs index ae4fef30..9855a550 100644 --- a/Source/Trends/ZLEMA_Series.cs +++ b/Source/Trends/ZLEMA_Series.cs @@ -1,5 +1,6 @@ namespace QuanTAlib; using System; +using System.Linq; /* ZLEMA: Zero Lag Exponential Moving Average @@ -45,18 +46,8 @@ public class ZLEMA_Series : Single_TSeries_Indicator { this._lastema = this._lastlastema; } if (this.Count < this._p) { - if (update) - { this._buffer[this._buffer.Count - 1] = _zl; } - else - { - this._buffer.Add(_zl); - } - if (this._buffer.Count > this._p) - { this._buffer.RemoveAt(0); } - - for (int i = 0; i < this._buffer.Count; i++) - { _ema += this._buffer[i]; } - _ema /= this._buffer.Count; + Add_Replace_Trim(_buffer, _zl, _p, update); + _ema = _buffer.Average(); } else { @@ -66,7 +57,6 @@ public class ZLEMA_Series : Single_TSeries_Indicator this._lastlastema = this._lastema; this._lastema = _ema; - var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _ema); - base.Add(ret, update); - } + base.Add((TValue.t, _ema), update, _NaN); + } } \ No newline at end of file diff --git a/Source/Volatility/ADL_Series.cs b/Source/Volatility/ADL_Series.cs index c2ae0cc4..72210268 100644 --- a/Source/Volatility/ADL_Series.cs +++ b/Source/Volatility/ADL_Series.cs @@ -37,7 +37,6 @@ public class ADL_Series : Single_TBars_Indicator this._lastlastadl = this._lastadl; this._lastadl = _adl; - var ret = (TBar.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _adl); - base.Add(ret, update); - } + base.Add((TBar.t, _adl), update, _NaN); + } } \ No newline at end of file diff --git a/Tests/Statistics/ENTP_Test.cs b/Tests/Statistics/ENTP_Test.cs index 12098a8a..f7e8aa10 100644 --- a/Tests/Statistics/ENTP_Test.cs +++ b/Tests/Statistics/ENTP_Test.cs @@ -3,13 +3,13 @@ using System; using QuanTAlib; namespace Statistics; -public class KURT_Test +public class KURTOSIS_Test { [Fact] public void Add_Test() { TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - KURT_Series c = new(a, 3); + KURTOSIS_Series c = new(a, 3); Assert.Equal(6, c.Count); a.Add(5); Assert.Equal(a.Count, c.Count); @@ -21,7 +21,7 @@ public class KURT_Test public void Edge_Test() { TSeries a = new() { double.NaN, double.Epsilon, double.PositiveInfinity, double.MaxValue }; - KURT_Series c = new(a, 3); + KURTOSIS_Series c = new(a, 3); Assert.Equal(a.Count, c.Count); a.Add(double.NaN); Assert.Equal(a.Count, c.Count); diff --git a/Tests/Statistics/KURT_Test.cs b/Tests/Statistics/KURT_Test.cs index 93b5220c..c594ff4b 100644 --- a/Tests/Statistics/KURT_Test.cs +++ b/Tests/Statistics/KURT_Test.cs @@ -9,7 +9,7 @@ public class ENTP_Test public void Add_Test() { TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - ENTP_Series c = new(a, 3); + ENTROPY_Series c = new(a, 3); Assert.Equal(6, c.Count); a.Add(5); Assert.Equal(a.Count, c.Count); @@ -21,7 +21,7 @@ public class ENTP_Test public void Edge_Test() { TSeries a = new() { double.NaN, double.Epsilon, double.PositiveInfinity, double.MaxValue }; - ENTP_Series c = new(a, 3); + ENTROPY_Series c = new(a, 3); Assert.Equal(a.Count, c.Count); a.Add(double.NaN); Assert.Equal(a.Count, c.Count); diff --git a/Tests/Statistics/MED_Test.cs b/Tests/Statistics/MED_Test.cs index 778811a4..ccfc2bcc 100644 --- a/Tests/Statistics/MED_Test.cs +++ b/Tests/Statistics/MED_Test.cs @@ -9,7 +9,7 @@ public class MED_Test public void Add_Test() { TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - MED_Series c = new(a, 3); + MEDIAN_Series c = new(a, 3); Assert.Equal(6, c.Count); a.Add(5); Assert.Equal(a.Count, c.Count); @@ -21,7 +21,7 @@ public class MED_Test public void Edge_Test() { TSeries a = new() { double.NaN, double.Epsilon, double.PositiveInfinity, double.MaxValue }; - MED_Series c = new(a, 3); + MEDIAN_Series c = new(a, 3); Assert.Equal(a.Count, c.Count); a.Add(double.NaN); Assert.Equal(a.Count, c.Count); diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index de0bdcd3..c584f538 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -15,10 +15,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/Tests/Validations/Pandas_TA.cs b/Tests/Validations/Pandas_TA.cs index 2bc65c52..4bb285c4 100644 --- a/Tests/Validations/Pandas_TA.cs +++ b/Tests/Validations/Pandas_TA.cs @@ -1,198 +1,206 @@ -using Xunit; -using System; -using QuanTAlib; -using Python.Runtime; -using Python.Included; - -namespace Validations; -public class PandasTA : IDisposable -{ - private readonly GBM_Feed bars; - private readonly Random rnd = new(); - private readonly int period; - private readonly string OStype; - private dynamic np; - private dynamic ta; - private dynamic df; - - public PandasTA() - { - bars = new(5000); - period = rnd.Next(28) + 3; - - // Checking the host OS and setting PythonDLL accordingly - OStype = Environment.OSVersion.ToString(); - if (OStype == "Unix 13.1.0") - { - OStype = @"/usr/local/Cellar/python@3.10/3.10.8/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib"; - } - else - { - OStype = Path.GetFullPath(".") + @"\python-3.10.0-embed-amd64\python310.dll"; - } - - Installer.InstallPath = Path.GetFullPath("."); - Installer.SetupPython().Wait(); - Installer.TryInstallPip(); - Installer.PipInstallModule("pandas-ta"); - //alternative: git+https://github.com/twopirllc/pandas-ta - - Runtime.PythonDLL = OStype; - PythonEngine.Initialize(); - np = Py.Import("numpy"); - ta = Py.Import("pandas_ta"); - - string[] cols = { "open", "high", "low", "close", "volume" }; - double[,] ary = new double[bars.Count, 5]; - for (int i = 0; i < bars.Count; i++) - { - ary[i, 0] = bars.Open[i].v; - ary[i, 1] = bars.High[i].v; - ary[i, 2] = bars.Low[i].v; - ary[i, 3] = bars.Close[i].v; - ary[i, 4] = bars.Volume[i].v; - } - df = ta.DataFrame(data: np.array(ary), index: np.array(bars.Close.t), columns: np.array(cols)); - } - - public void Dispose() - { +using Xunit; +using System; +using QuanTAlib; +using Python.Runtime; +using Python.Included; + +namespace Validations; +public class PandasTA : IDisposable +{ + private readonly GBM_Feed bars; + private readonly Random rnd = new(); + private readonly int period; + private readonly string OStype; + private readonly dynamic np; + private readonly dynamic ta; + private readonly dynamic df; + + public PandasTA() + { + bars = new(5000); + period = rnd.Next(28) + 3; + + // Checking the host OS and setting PythonDLL accordingly + OStype = Environment.OSVersion.ToString(); + if (OStype == "Unix 13.1.0") + { + OStype = @"/usr/local/Cellar/python@3.10/3.10.8/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib"; + } + else + { + OStype = Path.GetFullPath(".") + @"\python-3.10.0-embed-amd64\python310.dll"; + } + + Installer.InstallPath = Path.GetFullPath("."); + Installer.SetupPython().Wait(); + Installer.TryInstallPip(); + Installer.PipInstallModule("pandas-ta"); + //alternative: git+https://github.com/twopirllc/pandas-ta + + Runtime.PythonDLL = OStype; + PythonEngine.Initialize(); + np = Py.Import("numpy"); + ta = Py.Import("pandas_ta"); + + string[] cols = { "open", "high", "low", "close", "volume" }; + double[,] ary = new double[bars.Count, 5]; + for (int i = 0; i < bars.Count; i++) + { + ary[i, 0] = bars.Open[i].v; + ary[i, 1] = bars.High[i].v; + ary[i, 2] = bars.Low[i].v; + ary[i, 3] = bars.Close[i].v; + ary[i, 4] = bars.Volume[i].v; + } + df = ta.DataFrame(data: np.array(ary), index: np.array(bars.Close.t), columns: np.array(cols)); + } + + public void Dispose() + { PythonEngine.Shutdown(); GC.SuppressFinalize(this); - } - - [Fact] - void HL2() - { - var pta = df.ta.hl2(high: df.high, low: df.low); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(bars.HL2.Last().v, 4)); - } - - [Fact] - void HLC3() - { - var pta = df.ta.hlc3(high: df.high, low: df.low, close: df.close); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(bars.HLC3.Last().v, 4)); - } - - [Fact] - void OHLC4() - { - var pta = df.ta.ohlc4(open: df.open, high: df.high, low: df.low, close: df.close); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(bars.OHLC4.Last().v, 4)); - } - - [Fact] - void MEDIAN() - { - MED_Series QL = new(bars.Close, period); - var pta = df.ta.median(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void VARIANCE() - { - VAR_Series QL = new(bars.Close, period); - var pta = df.ta.variance(close: df.close, length: period, ddof:0); - Assert.Equal(Math.Round((double)pta.tail(1), 5), Math.Round(QL.Last().v, 5)); - } - - [Fact] - void SVARIANCE() - { - SVAR_Series QL = new(bars.Close, period); - var pta = df.ta.variance(close: df.close, length: period, ddof: 1); - Assert.Equal(Math.Round((double)pta.tail(1), 5), Math.Round(QL.Last().v, 5)); - } - - [Fact] - void ADL() - { - ADL_Series QL = new(bars); - var pta = df.ta.ad(high: df.high, low: df.low, close:df.close, volume:df.volume); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void ADOSC() - { - ADOSC_Series QL = new(bars); - var pta = df.ta.adosc(high: df.high, low: df.low, close: df.close, volume: df.volume); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void TR() - { - TR_Series QL = new(bars); - var pta = df.ta.true_range(high: df.high, low: df.low, close: df.close); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void ATR() - { - ATR_Series QL = new(bars, period); - var pta = df.ta.atr(high: df.high, low: df.low, close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void RSI() - { - RSI_Series QL = new(bars.Close, period); - var pta = df.ta.rsi(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void TRIMA() - { - //TODO: return length to variable length (period) when Pandas-TA fixes trima - TRIMA_Series QL = new(bars.Close, 11); - var pta = df.ta.trima(close: df.close, length: 11); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void KAMA() - { - KAMA_Series QL = new(bars.Close, period); - var pta = df.ta.kama(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void HMA() - { - HMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.hma(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void SMA() - { - SMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.sma(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void EMA() - { - EMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.ema(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void TEMA() - { - TEMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.tema(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 7), Math.Round(QL.Last().v, 7)); + } + + [Fact] + void HL2() + { + var pta = df.ta.hl2(high: df.high, low: df.low); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(bars.HL2.Last().v, 4)); + } + + [Fact] + void HLC3() + { + var pta = df.ta.hlc3(high: df.high, low: df.low, close: df.close); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(bars.HLC3.Last().v, 4)); + } + + [Fact] + void OHLC4() + { + var pta = df.ta.ohlc4(open: df.open, high: df.high, low: df.low, close: df.close); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(bars.OHLC4.Last().v, 4)); + } + + [Fact] + void MEDIAN() + { + MEDIAN_Series QL = new(bars.Close, period); + var pta = df.ta.median(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void VARIANCE() + { + VAR_Series QL = new(bars.Close, period); + var pta = df.ta.variance(close: df.close, length: period, ddof:0); + Assert.Equal(Math.Round((double)pta.tail(1), 5), Math.Round(QL.Last().v, 5)); + } + + [Fact] + void SVARIANCE() + { + SVAR_Series QL = new(bars.Close, period); + var pta = df.ta.variance(close: df.close, length: period, ddof: 1); + Assert.Equal(Math.Round((double)pta.tail(1), 5), Math.Round(QL.Last().v, 5)); + } + + [Fact] + void ADL() + { + ADL_Series QL = new(bars); + var pta = df.ta.ad(high: df.high, low: df.low, close:df.close, volume:df.volume); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void ADOSC() + { + ADOSC_Series QL = new(bars); + var pta = df.ta.adosc(high: df.high, low: df.low, close: df.close, volume: df.volume); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void TR() + { + TR_Series QL = new(bars); + var pta = df.ta.true_range(high: df.high, low: df.low, close: df.close); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void OBV() + { + OBV_Series QL = new(bars); + var pta = df.ta.obv(close: df.close, volume: df.volume); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void ATR() + { + ATR_Series QL = new(bars, period); + var pta = df.ta.atr(high: df.high, low: df.low, close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void RSI() + { + RSI_Series QL = new(bars.Close, period); + var pta = df.ta.rsi(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void TRIMA() + { + //TODO: return length to variable length (period) when Pandas-TA fixes trima + TRIMA_Series QL = new(bars.Close, 11); + var pta = df.ta.trima(close: df.close, length: 11); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void KAMA() + { + KAMA_Series QL = new(bars.Close, period); + var pta = df.ta.kama(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void HMA() + { + HMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.hma(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void SMA() + { + SMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.sma(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void EMA() + { + EMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.ema(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void TEMA() + { + TEMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.tema(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 7), Math.Round(QL.Last().v, 7)); } [Fact] @@ -219,67 +227,67 @@ public class PandasTA : IDisposable Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); } - [Fact] - void ENTP() - { - ENTP_Series QL = new(bars.Close, period, useNaN: false); - var pta = df.ta.entropy(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void WMA() - { - WMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.wma(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void RMA() - { - RMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.rma(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void ZLEMA() - { - ZLEMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.zlma(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void DEMA() - { - DEMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.dema(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void BIAS() - { - BIAS_Series QL = new(bars.Close, period, false); - var pta = df.ta.bias(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void KURT() - { - KURT_Series QL = new(bars.Close, period, useNaN: false); - var pta = df.ta.kurtosis(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } - - [Fact] - void MAD() - { - MAD_Series QL = new(bars.Close, period, useNaN: false); - var pta = df.ta.mad(close: df.close, length: period); - Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); - } + [Fact] + void ENTROPY() + { + ENTROPY_Series QL = new(bars.Close, period, useNaN: false); + var pta = df.ta.entropy(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void WMA() + { + WMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.wma(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void RMA() + { + RMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.rma(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void ZLEMA() + { + ZLEMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.zlma(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void DEMA() + { + DEMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.dema(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void BIAS() + { + BIAS_Series QL = new(bars.Close, period, false); + var pta = df.ta.bias(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void KURTOSIS() + { + KURTOSIS_Series QL = new(bars.Close, period, useNaN: false); + var pta = df.ta.kurtosis(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } + + [Fact] + void MAD() + { + MAD_Series QL = new(bars.Close, period, useNaN: false); + var pta = df.ta.mad(close: df.close, length: period); + Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); + } } \ No newline at end of file diff --git a/Tests/Validations/Skender_Stock.cs b/Tests/Validations/Skender_Stock.cs index c2976b00..d808e8bb 100644 --- a/Tests/Validations/Skender_Stock.cs +++ b/Tests/Validations/Skender_Stock.cs @@ -5,305 +5,314 @@ using Xunit; namespace Validations; public class Skender_Stock -{ - private readonly GBM_Feed bars; - private readonly Random rnd = new(); - private readonly int period; - private readonly IEnumerable quotes; - - public Skender_Stock() - { - bars = new(Bars: 5000, Volatility: 0.7, Drift: 0.0); - period = rnd.Next(28) + 3; - quotes = bars.Select( - q => new Quote - { - Date = q.t, - Open = (decimal)q.o, - High = (decimal)q.h, - Low = (decimal)q.l, - Close = (decimal)q.c, - Volume = (decimal)q.v - }); - } - - [Fact] - public void SMA() - { - SMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSma(period); - - Assert.Equal(Math.Round((double)SK.Last().Sma!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void EMA() - { - EMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetEma(period); - - Assert.Equal(Math.Round((double)SK.Last().Ema!, 6), Math.Round(QL.Last().v, 6)); - } - [Fact] - public void WMA() - { - WMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetWma(period); - - Assert.Equal(Math.Round((double)SK.Last().Wma!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void DEMA() - { - DEMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetDema(period); - - Assert.Equal(Math.Round((double)SK.Last().Dema!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void TEMA() - { - TEMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetTema(period); - - Assert.Equal(Math.Round((double)SK.Last().Tema!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void MAD() - { - MAD_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period); - - Assert.Equal(Math.Round((double)SK.Last().Mad!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void MSE() - { - MSE_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period); - - Assert.Equal(Math.Round((double)SK.Last().Mse!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void MAPE() - { - MAPE_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period); - - Assert.Equal(Math.Round((double)SK.Last().Mape!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void CORR() - { - CORR_Series QL = new(bars.High, bars.Low, period, false); - var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period); - - Assert.Equal(Math.Round((double)SK.Last().Correlation!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void ATR() - { - ATR_Series QL = new(bars, period, false); - var SK = quotes.GetAtr(period); - - Assert.Equal(Math.Round((double)SK.Last().Atr!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void OBV() - { - OBV_Series QL = new(bars, period, false); - var SK = quotes.GetObv(period); - - // adding volume[0] to OBV to pass the test and keep compatibility with TA-LIB - Assert.Equal(Math.Round(SK.Last().Obv! + (double)quotes.First().Volume!, 5), - Math.Round(QL.Last().v, 5)); - } - - [Fact] - public void ADL() - { - ADL_Series QL = new(bars, false); - var SK = quotes.GetAdl(); - - Assert.Equal(Math.Round(SK.Last().Adl!, 5), Math.Round(QL.Last().v, 5)); - } - - [Fact] - public void CCI() - { - CCI_Series QL = new(bars, period, false); - var SK = quotes.GetCci(period); - - Assert.Equal(Math.Round((double)SK.Last().Cci!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void ATRP() - { - ATRP_Series QL = new(bars, period, false); - var SK = quotes.GetAtr(period); - - Assert.Equal(Math.Round((double)SK.Last().Atrp!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void KAMA() - { - KAMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetKama(period); - - Assert.Equal(Math.Round((double)SK.Last().Kama!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void HMA() - { - HMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetHma(period); - - Assert.Equal(Math.Round((double)SK.Last().Hma!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void SMMA() - { - SMMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetSmma(period); - - Assert.Equal(Math.Round((double)SK.Last().Smma!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void MACD() - { - MACD_Series QL = new(bars.Close, 26, 12, 9, useNaN: false); - var SK = quotes.GetMacd(12, 26, 9); - - Assert.Equal(Math.Round((double)SK.Last().Macd!, 6), Math.Round(QL.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().Signal!, 6), Math.Round(QL.Signal.Last().v, 6)); - } - - [Fact] - public void BBANDS() - { - BBANDS_Series QL = new(bars.Close, period, 2.0, useNaN: false); - var SK = quotes.GetBollingerBands(period, 2.0); - - Assert.Equal(Math.Round((double)SK.Last().Sma!, 6), Math.Round(QL.Mid.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().UpperBand!, 6), Math.Round(QL.Upper.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().LowerBand!, 6), Math.Round(QL.Lower.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().Width!, 6), Math.Round(QL.Bandwidth.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().PercentB!, 6), Math.Round(QL.PercentB.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().ZScore!, 6), Math.Round(QL.Zscore.Last().v, 6)); - } - - [Fact] - public void RSI() - { - RSI_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetRsi(period); - - Assert.Equal(Math.Round((double)SK.Last().Rsi!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void ALMA() - { - ALMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetAlma(period); - - Assert.Equal(Math.Round((double)SK.Last().Alma!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void SDEV() - { - SDEV_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetStdDev(period); - - Assert.Equal(Math.Round((double)SK.Last().StdDev!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void ZSCORE() - { - ZSCORE_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetStdDev(period); - - Assert.Equal(Math.Round((double)SK.Last().ZScore!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void LINREG() - { - LINREG_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetSlope(period); - - Assert.Equal(Math.Round((double)SK.Last().Slope!, 6), Math.Round(QL.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().Intercept!, 6), Math.Round(QL.Intercept.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().RSquared!, 6), Math.Round(QL.RSquared.Last().v, 6)); - Assert.Equal(Math.Round((double)SK.Last().StdDev!, 6), Math.Round(QL.StdDev.Last().v, 6)); - } - - [Fact] - public void TR() - { - TR_Series QL = new(bars, useNaN: false); - var SK = quotes.GetTr(); - - Assert.Equal(Math.Round((double)SK.Last().Tr!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void HL2() - { - TSeries QL = bars.HL2; - var SK = quotes.GetBaseQuote(CandlePart.HL2); - - Assert.Equal(Math.Round(SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void OC2() - { - TSeries QL = bars.OC2; - var SK = quotes.GetBaseQuote(CandlePart.OC2); - - Assert.Equal(Math.Round(SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void HLC3() - { - TSeries QL = bars.HLC3; - var SK = quotes.GetBaseQuote(CandlePart.HLC3); - - Assert.Equal(Math.Round(SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void OHL3() - { - TSeries QL = bars.OHL3; - var SK = quotes.GetBaseQuote(CandlePart.OHL3); - - Assert.Equal(Math.Round(SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); - } - - [Fact] - public void OHLC4() - { - TSeries QL = bars.OHLC4; - var SK = quotes.GetBaseQuote(CandlePart.OHLC4); - - Assert.Equal(Math.Round((double)SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); +{ + private readonly GBM_Feed bars; + private readonly Random rnd = new(); + private readonly int period; + private readonly IEnumerable quotes; + + public Skender_Stock() + { + bars = new(Bars: 5000, Volatility: 0.7, Drift: 0.0); + period = rnd.Next(28) + 3; + quotes = bars.Select( + q => new Quote + { + Date = q.t, + Open = (decimal)q.o, + High = (decimal)q.h, + Low = (decimal)q.l, + Close = (decimal)q.c, + Volume = (decimal)q.v + }); + } + + [Fact] + public void SMA() + { + SMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSma(period); + + Assert.Equal(Math.Round((double)SK.Last().Sma!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void EMA() + { + EMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetEma(period); + + Assert.Equal(Math.Round((double)SK.Last().Ema!, 6), Math.Round(QL.Last().v, 6)); + } + [Fact] + public void WMA() + { + WMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetWma(period); + + Assert.Equal(Math.Round((double)SK.Last().Wma!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void DEMA() + { + DEMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetDema(period); + + Assert.Equal(Math.Round((double)SK.Last().Dema!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void TEMA() + { + TEMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetTema(period); + + Assert.Equal(Math.Round((double)SK.Last().Tema!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void MAD() + { + MAD_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period); + + Assert.Equal(Math.Round((double)SK.Last().Mad!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void MSE() + { + MSE_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period); + + Assert.Equal(Math.Round((double)SK.Last().Mse!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void MAPE() + { + MAPE_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period); + + Assert.Equal(Math.Round((double)SK.Last().Mape!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void COVAR() + { + COVAR_Series QL = new(bars.High, bars.Low, period, false); + var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period); + + Assert.Equal(Math.Round((double)SK.Last().Covariance!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void CORR() + { + CORR_Series QL = new(bars.High, bars.Low, period, false); + var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period); + + Assert.Equal(Math.Round((double)SK.Last().Correlation!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void ATR() + { + ATR_Series QL = new(bars, period, false); + var SK = quotes.GetAtr(period); + + Assert.Equal(Math.Round((double)SK.Last().Atr!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void OBV() + { + OBV_Series QL = new(bars, period, false); + var SK = quotes.GetObv(period); + + // adding volume[0] to OBV to pass the test and keep compatibility with TA-LIB + Assert.Equal(Math.Round(SK.Last().Obv! + (double)quotes.First().Volume!, 5), + Math.Round(QL.Last().v, 5)); + } + + [Fact] + public void ADL() + { + ADL_Series QL = new(bars, false); + var SK = quotes.GetAdl(); + + Assert.Equal(Math.Round(SK.Last().Adl!, 5), Math.Round(QL.Last().v, 5)); + } + + [Fact] + public void CCI() + { + CCI_Series QL = new(bars, period, false); + var SK = quotes.GetCci(period); + + Assert.Equal(Math.Round((double)SK.Last().Cci!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void ATRP() + { + ATRP_Series QL = new(bars, period, false); + var SK = quotes.GetAtr(period); + + Assert.Equal(Math.Round((double)SK.Last().Atrp!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void KAMA() + { + KAMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetKama(period); + + Assert.Equal(Math.Round((double)SK.Last().Kama!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void HMA() + { + HMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetHma(period); + + Assert.Equal(Math.Round((double)SK.Last().Hma!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void SMMA() + { + SMMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetSmma(period); + + Assert.Equal(Math.Round((double)SK.Last().Smma!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void MACD() + { + MACD_Series QL = new(bars.Close, 26, 12, 9, useNaN: false); + var SK = quotes.GetMacd(12, 26, 9); + + Assert.Equal(Math.Round((double)SK.Last().Macd!, 6), Math.Round(QL.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().Signal!, 6), Math.Round(QL.Signal.Last().v, 6)); + } + + [Fact] + public void BBANDS() + { + BBANDS_Series QL = new(bars.Close, period, 2.0, useNaN: false); + var SK = quotes.GetBollingerBands(period, 2.0); + + Assert.Equal(Math.Round((double)SK.Last().Sma!, 6), Math.Round(QL.Mid.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().UpperBand!, 6), Math.Round(QL.Upper.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().LowerBand!, 6), Math.Round(QL.Lower.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().Width!, 6), Math.Round(QL.Bandwidth.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().PercentB!, 6), Math.Round(QL.PercentB.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().ZScore!, 6), Math.Round(QL.Zscore.Last().v, 6)); + } + + [Fact] + public void RSI() + { + RSI_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetRsi(period); + + Assert.Equal(Math.Round((double)SK.Last().Rsi!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void ALMA() + { + ALMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetAlma(period); + + Assert.Equal(Math.Round((double)SK.Last().Alma!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void SDEV() + { + SDEV_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetStdDev(period); + + Assert.Equal(Math.Round((double)SK.Last().StdDev!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void ZSCORE() + { + ZSCORE_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetStdDev(period); + + Assert.Equal(Math.Round((double)SK.Last().ZScore!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void LINREG() + { + LINREG_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetSlope(period); + + Assert.Equal(Math.Round((double)SK.Last().Slope!, 6), Math.Round(QL.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().Intercept!, 6), Math.Round(QL.Intercept.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().RSquared!, 6), Math.Round(QL.RSquared.Last().v, 6)); + Assert.Equal(Math.Round((double)SK.Last().StdDev!, 6), Math.Round(QL.StdDev.Last().v, 6)); + } + + [Fact] + public void TR() + { + TR_Series QL = new(bars, useNaN: false); + var SK = quotes.GetTr(); + + Assert.Equal(Math.Round((double)SK.Last().Tr!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void HL2() + { + TSeries QL = bars.HL2; + var SK = quotes.GetBaseQuote(CandlePart.HL2); + + Assert.Equal(Math.Round(SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void OC2() + { + TSeries QL = bars.OC2; + var SK = quotes.GetBaseQuote(CandlePart.OC2); + + Assert.Equal(Math.Round(SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void HLC3() + { + TSeries QL = bars.HLC3; + var SK = quotes.GetBaseQuote(CandlePart.HLC3); + + Assert.Equal(Math.Round(SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void OHL3() + { + TSeries QL = bars.OHL3; + var SK = quotes.GetBaseQuote(CandlePart.OHL3); + + Assert.Equal(Math.Round(SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] + public void OHLC4() + { + TSeries QL = bars.OHLC4; + var SK = quotes.GetBaseQuote(CandlePart.OHLC4); + + Assert.Equal(Math.Round((double)SK.Last().Value!, 6), Math.Round(QL.Last().v, 6)); } } diff --git a/Tests/Validations/TA_LIB.cs b/Tests/Validations/TA_LIB.cs index caac9335..88688db9 100644 --- a/Tests/Validations/TA_LIB.cs +++ b/Tests/Validations/TA_LIB.cs @@ -1,10 +1,10 @@ -using Xunit; -using System; -using TALib; -using QuanTAlib; - -namespace Validations; -public class TA_LIB +using Xunit; +using System; +using TALib; +using QuanTAlib; + +namespace Validations; +public class TA_LIB { private readonly GBM_Feed bars; private readonly Random rnd = new(); @@ -118,7 +118,7 @@ public class TA_LIB VAR_Series QL = new(bars.Close, period, false); Core.Var(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - Assert.Equal(Math.Round(TALIB[TALIB.Length - outBegIdx - 1], 5, MidpointRounding.AwayFromZero), Math.Round(QL.Last().v, 5)); + Assert.Equal(Math.Round(TALIB[TALIB.Length - outBegIdx - 1], 4, MidpointRounding.AwayFromZero), Math.Round(QL.Last().v, 4)); } [Fact] @@ -314,5 +314,6 @@ public class TA_LIB Core.WclPrice(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); Assert.Equal(Math.Round(TALIB[TALIB.Length - outBegIdx - 1], 6, MidpointRounding.AwayFromZero), Math.Round(QL.Last().v, 6, MidpointRounding.AwayFromZero)); - } -} + } + +} diff --git a/docs/readme.md b/docs/readme.md index eff2c21c..4ff10d2f 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -17,7 +17,6 @@ Quantitative TA Library (**QuanTAlib**) is an easy-to-use C# library for quantit **QuanTAlib** is written with some specific design criteria in mind - some reasons why there is '_yet another C# TA library_': - Written in native C# - no code conversion from TA-LIB or other imported/converted TA libraries -- No usage of Decimal datatypes, LINQ, interface abstractions, or static classes with tons of methods (all for performance reasons) - Supports both **historical data analysis** (working on bulk of historical arrays) and **real-time analysis** (adding one data item at the time without the need to re-calculate the whole history) - Calculate early data right - no hiding of incomplete calculations with NaN values (unless explicitly requested with useNan: true), data is as valid as mathematically possible from the first value - Usage of events - each data series is an event publisher, each indicator is a subscriber - this allows seamless data flow between indicators) @@ -56,9 +55,9 @@ See [Getting Started](https://github.com/mihakralj/QuanTAlib/blob/main/Docs/gett | **STATISTICS & NUMERICAL ANALYSIS** | **QuanTAlib** | **TA-LIB** | **Skender** | **Pandas TA** | | ⭐ BIAS - Bias | `BIAS_Series` ||| bias | | ⭐ CORR - Pearson's Correlation Coefficient | `CORR_Series` | CORREL | GetCorrelation || -| ⛔ COVAR - Covariance ||| GetCorrelation || -| ⭐ ENTP - Entropy | `ENTP_Series` ||| entropy | -| ⭐ KURT - Kurtosis | `KURT_Series` ||| kurtosis | +| ⭐ COVAR - Covariance | `COVAR_Series` || GetCorrelation || +| ⭐ ENTROPY - Entropy | `ENTROPY_Series` ||| entropy | +| ⭐ KURTOSIS - Kurtosis | `KURT_Series` ||| kurtosis | | ⭐ LINREG - Linear Regression | `LINREG_Series` || GetSlope || | ⭐ MAD - Mean Absolute Deviation | `MAD_Series` || GetSma | mad | | ⭐ MAPE - Mean Absolute Percent Error | `MAPE_Series` || GetSma || @@ -79,7 +78,7 @@ See [Getting Started](https://github.com/mihakralj/QuanTAlib/blob/main/Docs/gett | ⭐ ALMA - Arnaud Legoux Moving Average | `ALMA_Series` || GetAlma | alma | | ⛔ ARIMA - Autoregressive Integrated Moving Average ||||| | ⭐ DEMA - Double EMA Average | `DEMA_Series` | DEMA | GetDema | dema | -| ⭐ EMA - Exponential Moving Average | `EMA_Series` || GetEma | ema | +| ⭐ EMA - Exponential Moving Average | `EMA_Series` | EMA | GetEma | ema | | ⛔ EPMA - Endpoint Moving Average ||| GetEpma || | ⛔ FRAMA - Fractal Adaptive Moving Average ||||| | ⛔ FWMA - Fibonacci's Weighted Moving Average |||| fwma | From cfa10c79e8b365e4c02bfc7c9dc05fbd9ff5cd43 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Thu, 17 Nov 2022 21:59:17 -0800 Subject: [PATCH 2/2] MAMA --- Quantower/Quantower.csproj | 4 + Source/QuanTAlib.csproj | 5 +- Source/QuanTAlib.ruleset | 5 -- Source/Trends/MAMA_Series.cs | 131 +++++++++++++++++++++++++++++ Tests/Validations/Pandas_TA.cs | 2 +- Tests/Validations/Skender_Stock.cs | 11 ++- Tests/Validations/TA_LIB.cs | 13 ++- 7 files changed, 161 insertions(+), 10 deletions(-) delete mode 100644 Source/QuanTAlib.ruleset create mode 100644 Source/Trends/MAMA_Series.cs diff --git a/Quantower/Quantower.csproj b/Quantower/Quantower.csproj index 79e26eb8..9cc46c29 100644 --- a/Quantower/Quantower.csproj +++ b/Quantower/Quantower.csproj @@ -13,6 +13,7 @@ AnyCPU disable False + ..\.sonarlint\mihakralj_quantalibcsharp.ruleset True @@ -36,6 +37,9 @@ + + + C:\Quantower\TradingPlatform\v1.124.6\bin\TradingPlatform.BusinessLayer.dll diff --git a/Source/QuanTAlib.csproj b/Source/QuanTAlib.csproj index f0284516..3a18b409 100644 --- a/Source/QuanTAlib.csproj +++ b/Source/QuanTAlib.csproj @@ -2,7 +2,7 @@ QuanTAlib - 0.1.20 + 0.1.21 Library of Technical Indicators for .NET Quantitative Technical Analysis library for both real-time (streaming) and historical data analysis git @@ -51,7 +51,7 @@ QuanTAlib2.png https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png True - QuanTAlib.ruleset + ..\.sonarlint\mihakralj_quantalibcsharp.ruleset @@ -66,5 +66,6 @@ False + \ No newline at end of file diff --git a/Source/QuanTAlib.ruleset b/Source/QuanTAlib.ruleset deleted file mode 100644 index c546ccdb..00000000 --- a/Source/QuanTAlib.ruleset +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Source/Trends/MAMA_Series.cs b/Source/Trends/MAMA_Series.cs new file mode 100644 index 00000000..20102431 --- /dev/null +++ b/Source/Trends/MAMA_Series.cs @@ -0,0 +1,131 @@ +namespace QuanTAlib; +using System; + +/* +MAMA: MESA Adaptive Moving Average + Created by John Ehlers, the MAMA indicator is a 5-period adaptive moving average of + high/low price that uses classic electrical radio-frequency signal processing algorithms + to reduce noise. + + KAMAi = KAMAi - 1 + SC * ( price - KAMAi-1 ) + +Sources: + https://mesasoftware.com/papers/MAMA.pdf + https://www.tradingview.com/script/foQxLbU3-Ehlers-MESA-Adaptive-Moving-Average-LazyBear/ + + */ + +public class MAMA_Series : Single_TSeries_Indicator +{ + public MAMA_Series(TSeries source, double fastlimit = 0.5, double slowlimit = 0.05, bool useNaN = false) : base(source, period: 5, useNaN) + { + fastl = fastlimit; + slowl = slowlimit; + i = 0; + if (base._data.Count > 0) { base.Add(base._data); } + } + + private int i; + private double sumPr, jI, jQ, fastl, slowl; + private (double i, double i1, double i2, double i3, double i4, double i5, double i6, double io) pr, i1, q1, sm, dt; + private (double i, double i1, double io) i2, q2, re, im, pd, ph, mama, fama; + + public override void Add((System.DateTime t, double v) TValue, bool update) + { + if (update) { + i--; + pr.i = pr.i1; pr.i1 = pr.i2; pr.i2 = pr.i3; pr.i3 = pr.i4; pr.i4 = pr.i5; pr.i5 = pr.i6; pr.i6 = pr.io; + i1.i = i1.i1; i1.i1 = i1.i2; i1.i2 = i1.i3; i1.i3 = i1.i4; i1.i4 = i1.i5; i1.i5 = i1.i6; i1.i6 = i1.io; + q1.i = q1.i1; q1.i1 = q1.i2; q1.i2 = q1.i3; q1.i3 = q1.i4; q1.i4 = q1.i5; q1.i5 = q1.i6; q1.i6 = q1.io; + dt.i = dt.i1; dt.i1 = dt.i2; dt.i2 = dt.i3; dt.i3 = dt.i4; dt.i4 = dt.i5; dt.i5 = dt.i6; dt.i6 = dt.io; + sm.i = sm.i1; sm.i1 = sm.i2; sm.i2 = sm.i3; sm.i3 = sm.i4; dt.i4 = sm.i5; sm.i5 = sm.i6; sm.i6 = sm.io; + i2.i = i2.i1; i2.i1 = i2.io; + q2.i = q2.i1; q2.i1 = q2.io; + re.i = re.i1; re.i1 = re.io; + im.i = im.i1; im.i1 = im.io; + pd.i = pd.i1; pd.i1 = pd.io; + ph.i = ph.i1; ph.i1 = ph.io; + mama.i = mama.i1; mama.i1 = mama.io; + fama.i = fama.i1; fama.i1 = fama.io; + } + + pr.i = TValue.v; + if (i > 5) { + double adj = (0.075 * pd.i1) + 0.54; + + // smooth and detrender + sm.i = ((4 * pr.i) + (3 * pr.i1) + (2 * pr.i2) + pr.i3) / 10; + dt.i = ((0.0962 * sm.i) + (0.5769 * sm.i2) - (0.5769 * sm.i4) - (0.0962 * sm.i6)) * adj; + + // in-phase and quadrature + q1.i = ((0.0962 * dt.i) + (0.5769 * dt.i2) - (0.5769 * dt.i4) - (0.0962 * dt.i6)) * adj; + i1.i = dt.i3; + + // advance the phases by 90 degrees + jI = ((0.0962 * i1.i) + (0.5769 * i1.i2) - (0.5769 * i1.i4) - (0.0962 * i1.i6)) * adj; + jQ = ((0.0962 * q1.i) + (0.5769 * q1.i2) - (0.5769 * q1.i4) - (0.0962 * q1.i6)) * adj; + + // phasor addition for 3-bar averaging + i2.i = i1.i - jQ; + q2.i = q1.i + jI; + + i2.i = (0.2 * i2.i) + (0.8 * i2.i1); // smoothing it + q2.i = (0.2 * q2.i) + (0.8 * q2.i1); + + // homodyne discriminator + re.i = (i2.i * i2.i1) + (q2.i * q2.i1); + im.i = (i2.i * q2.i1) - (q2.i * i2.i1); + + re.i = (0.2 * re.i) + (0.8 * re.i1); // smoothing it + im.i = (0.2 * im.i) + (0.8 * im.i1); + + // calculate period + pd.i = (im.i != 0 && re.i != 0) ? (6.283185307179586 / Math.Atan(im.i / re.i)) : 0d; + + // adjust period to thresholds + pd.i = (pd.i > 1.5 * pd.i1) ? 1.5 * pd.i1 : pd.i; + pd.i = (pd.i < 0.67 * pd.i1) ? 0.67 * pd.i1 : pd.i; + pd.i = (pd.i < 6d) ? 6d : pd.i; + pd.i = (pd.i > 50d) ? 50d : pd.i; + + // smooth the period + pd.i = (0.2 * pd.i) + (0.8 * pd.i1); + + // determine phase position + ph.i = (i1.i != 0) ? Math.Atan(q1.i / i1.i) * 57.29577951308232 : 0; + + // change in phase + double delta = Math.Max(ph.i1 - ph.i, 1d); + + // adaptive alpha value + double alpha = Math.Max(fastl / delta, slowl); + + // final indicators + mama.i = ((alpha * pr.i) + ((1d - alpha) * mama.i1)); + fama.i = ((0.5d * alpha * mama.i) + ((1d - (0.5d * alpha)) * fama.i1)); + } + else { + sumPr += pr.i; + pd.i = sm.i = dt.i = i1.i = q1.i = i2.i = q2.i = re.i = im.i = ph.i = 0; + mama.i = fama.i = sumPr / (i+1); + } + i++; + pr.io = pr.i6; pr.i6 = pr.i5; pr.i5 = pr.i4; pr.i4 = pr.i3; pr.i3 = pr.i2; pr.i2 = pr.i1; pr.i1 = pr.i; + i1.io = i1.i6; i1.i6 = i1.i5; i1.i5 = i1.i4; i1.i4 = i1.i3; i1.i3 = i1.i2; i1.i2 = i1.i1; i1.i1 = i1.i; + q1.io = q1.i6; q1.i6 = q1.i5; q1.i5 = q1.i4; q1.i4 = q1.i3; q1.i3 = q1.i2; q1.i2 = q1.i1; q1.i1 = q1.i; + dt.io = dt.i6; dt.i6 = dt.i5; dt.i5 = dt.i4; dt.i4 = dt.i3; dt.i3 = dt.i2; dt.i2 = dt.i1; dt.i1 = dt.i; + sm.io = sm.i6; sm.i6 = sm.i5; sm.i5 = sm.i4; sm.i4 = sm.i3; sm.i3 = sm.i2; sm.i2 = sm.i1; sm.i1 = sm.i; + + i2.io = i2.i1; i2.i1 = i2.i; + q2.io = q2.i1; q2.i1 = q2.i; + re.io = re.i1; re.i1 = re.i; + im.io = im.i1; im.i1 = im.i; + pd.io = pd.i1; pd.i1 = pd.i; + ph.io = ph.i1; ph.i1 = ph.i; + + mama.io = mama.i1; mama.i1 = mama.i; + fama.io = fama.i1; fama.i1 = fama.i; + + base.Add((TValue.t, mama.i), update, _NaN); + } +} diff --git a/Tests/Validations/Pandas_TA.cs b/Tests/Validations/Pandas_TA.cs index 4bb285c4..fb589b18 100644 --- a/Tests/Validations/Pandas_TA.cs +++ b/Tests/Validations/Pandas_TA.cs @@ -157,7 +157,7 @@ public class PandasTA : IDisposable [Fact] void TRIMA() { - //TODO: return length to variable length (period) when Pandas-TA fixes trima + // TODO: return length to variable length (period) when Pandas-TA fixes trima TRIMA_Series QL = new(bars.Close, 11); var pta = df.ta.trima(close: df.close, length: 11); Assert.Equal(Math.Round((double)pta.tail(1), 4), Math.Round(QL.Last().v, 4)); diff --git a/Tests/Validations/Skender_Stock.cs b/Tests/Validations/Skender_Stock.cs index d808e8bb..3e173138 100644 --- a/Tests/Validations/Skender_Stock.cs +++ b/Tests/Validations/Skender_Stock.cs @@ -71,7 +71,16 @@ public class Skender_Stock Assert.Equal(Math.Round((double)SK.Last().Tema!, 6), Math.Round(QL.Last().v, 6)); } - [Fact] + + [Fact] + public void MAMA() { + MAMA_Series QL = new(bars.HL2, fastlimit: 0.5, slowlimit: 0.05); + var SK = quotes.GetMama(fastLimit: 0.5, slowLimit: 0.05); + + Assert.Equal(Math.Round((double)SK.Last().Mama!, 6), Math.Round(QL.Last().v, 6)); + } + + [Fact] public void MAD() { MAD_Series QL = new(bars.Close, period, false); diff --git a/Tests/Validations/TA_LIB.cs b/Tests/Validations/TA_LIB.cs index 88688db9..7dab5701 100644 --- a/Tests/Validations/TA_LIB.cs +++ b/Tests/Validations/TA_LIB.cs @@ -10,6 +10,7 @@ public class TA_LIB private readonly Random rnd = new(); private readonly int period; private readonly double[] TALIB; + private readonly double[] TALIB2; private readonly double[] inopen; private readonly double[] inhigh; private readonly double[] inlow; @@ -21,6 +22,7 @@ public class TA_LIB bars = new(5000); period = rnd.Next(28) + 3; TALIB = new double[bars.Count]; + TALIB2 = new double[bars.Count]; inopen = bars.Open.v.ToArray(); inhigh = bars.High.v.ToArray(); inlow = bars.Low.v.ToArray(); @@ -130,7 +132,16 @@ public class TA_LIB Assert.Equal(Math.Round(TALIB[TALIB.Length - outBegIdx - 1], 6, MidpointRounding.AwayFromZero), Math.Round(QL.Last().v, 6, MidpointRounding.AwayFromZero)); } - [Fact] + + [Fact] + public void MAMA() { + MAMA_Series QL = new(bars.Close, fastlimit: 0.5, slowlimit: 0.05); + Core.Mama(inReal: inclose, startIdx: 0, endIdx: bars.Count - 1, outMama: TALIB, outFama: TALIB2, outBegIdx: out int outBegIdx, outNbElement: out _, optInFastLimit: 0.5, optInSlowLimit: 0.05); + + Assert.Equal(Math.Round(TALIB[TALIB.Length - outBegIdx - 1], 6, MidpointRounding.AwayFromZero), Math.Round(QL.Last().v, 6, MidpointRounding.AwayFromZero)); + } + + [Fact] public void TRIMA() { TRIMA_Series QL = new(bars.Close, period, false);