diff --git a/Source/Basics/MIDPOINT_Series.cs b/Source/Basics/MIDPOINT_Series.cs
new file mode 100644
index 00000000..ea5bdd0a
--- /dev/null
+++ b/Source/Basics/MIDPOINT_Series.cs
@@ -0,0 +1,44 @@
+namespace QuanTAlib;
+using System;
+
+/*
+MIDPOINT: Midpoint value (max+min)/2 in the given period in the series.
+ If period = 0 => period = full length of the series
+
+Sources:
+ https://thefaqblog.com/what-is-the-midpoint-in-statistics/
+
+ */
+
+public class MIDPOINT_Series : Single_TSeries_Indicator
+{
+ public MIDPOINT_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((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 _max = TValue.v;
+ double _min = TValue.v;
+ for (int i = 0; i < this._buffer.Count; i++)
+ {
+ _max = Math.Max(this._buffer[i], _max);
+ _min = Math.Min(this._buffer[i], _min);
+ }
+ double _mid = (_max + _min) * 0.5;
+
+ var result = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _mid);
+
+ base.Add(result, update);
+ }
+}
\ No newline at end of file
diff --git a/Source/Basics/MIDPRICE_Series.cs b/Source/Basics/MIDPRICE_Series.cs
new file mode 100644
index 00000000..f91a0f47
--- /dev/null
+++ b/Source/Basics/MIDPRICE_Series.cs
@@ -0,0 +1,50 @@
+namespace QuanTAlib;
+using System;
+
+/*
+MIDPRICE: Midpoint price (highhest high + lowest low)/2 in the given period in the series.
+ If period = 0 => period = full length of the series
+
+ */
+
+public class MIDPRICE_Series : Single_TBars_Indicator
+{
+ public MIDPRICE_Series(TBars source, int period, bool useNaN = false) : base(source, period, useNaN)
+ {
+ if (base._bars.Count > 0)
+ { base.Add(base._bars); }
+ }
+ private readonly System.Collections.Generic.List _bufferhi = new();
+ private readonly System.Collections.Generic.List _bufferlo = new();
+
+ 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); }
+
+ 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 _mid = (_max + _min) * 0.5;
+
+ var result = (TBar.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _mid);
+
+ base.Add(result, update);
+ }
+}
\ No newline at end of file
diff --git a/Source/Basics/SUM_Series.cs b/Source/Basics/SUM_Series.cs
new file mode 100644
index 00000000..bb98134d
--- /dev/null
+++ b/Source/Basics/SUM_Series.cs
@@ -0,0 +1,35 @@
+namespace QuanTAlib;
+using System;
+
+/*
+SUM: Cumulative Sum (aka Running Total)
+ SUM across a period provides a rolling sum of all values across the period.
+ If SUM values would be divided with period, the output would be SMA()
+
+Sources:
+ https://en.wikipedia.org/wiki/CUSUM
+
+ */
+
+public class SUM_Series : Single_TSeries_Indicator
+{
+ public SUM_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) { _buffer[_buffer.Count - 1] = TValue.v; }
+ else { _buffer.Add(TValue.v); }
+ if (_buffer.Count > this._p && this._p != 0) { _buffer.RemoveAt(0); }
+
+ double _sum = 0;
+ for (int i = 0; i < _buffer.Count; i++) { _sum += _buffer[i]; }
+
+ var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _sum);
+
+ base.Add(result, update);
+ }
+}
diff --git a/Source/Trends/ALMA_Series.cs b/Source/Trends/ALMA_Series.cs
index 82072d68..122cc2d4 100644
--- a/Source/Trends/ALMA_Series.cs
+++ b/Source/Trends/ALMA_Series.cs
@@ -14,52 +14,54 @@ Sources:
https://phemex.com/academy/what-is-arnaud-legoux-moving-averages
https://www.prorealcode.com/prorealtime-indicators/alma-arnaud-legoux-moving-average/
+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)
+ {
+ 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;
+ }
+ }
}
diff --git a/Tests/Validations/Pandas_TA.cs b/Tests/Validations/Pandas_TA.cs
index f7d3b346..67b86791 100644
--- a/Tests/Validations/Pandas_TA.cs
+++ b/Tests/Validations/Pandas_TA.cs
@@ -6,123 +6,171 @@ using Python.Included;
namespace Validations;
public class PandasTA : IDisposable
-{
- private GBM_Feed bars;
- private Random rnd = new();
- private int period;
- private 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");
-
- 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();
- }
-
- [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), 7), Math.Round(QL.Last().v, 7));
- }
-
- [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), 7), Math.Round(QL.Last().v, 7));
- }
-
- [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 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), 7), Math.Round(QL.Last().v, 7));
- }
-
- [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), 7), Math.Round(QL.Last().v, 7));
- }
-
- [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), 7), Math.Round(QL.Last().v, 7));
- }
-
- [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), 7), Math.Round(QL.Last().v, 7));
- }
-
- [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), 7), Math.Round(QL.Last().v, 7));
+{
+ private GBM_Feed bars;
+ private Random rnd = new();
+ private int period;
+ private 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");
+ Installer.PipInstallModule("git+https://github.com/twopirllc/pandas-ta@development");
+
+ 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();
+ }
+
+ [Fact]
+ void HL2()
+ {
+ var pta = df.ta.hl2(high: df.high, low: df.low);
+ Assert.Equal(Math.Round((double)pta.tail(1), 7), Math.Round(bars.HL2.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(bars.HLC3.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(bars.OHLC4.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(QL.Last().v, 7));
+ }
+
+ /*
+ [Fact]
+ void ALMA()
+ {
+ ALMA_Series QL = new(bars.Close, period: period, offset: 0.85, sigma: 6.0, false);
+ var pta = df.ta.alma(close: df.close, length: period, distribution_offset: 0.85, sigma: 6.0);
+ Assert.Equal(Math.Round((double)pta.tail(1), 7), Math.Round(QL.Last().v, 7));
+ }
+ */
+
+ [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), 7), Math.Round(QL.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(QL.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(QL.Last().v, 7));
+ }
+
+ [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 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), 7), Math.Round(QL.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(QL.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(QL.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(QL.Last().v, 7));
+ }
+
+ [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), 7), Math.Round(QL.Last().v, 7));
+ }
}
\ No newline at end of file
diff --git a/Tests/Validations/TA_LIB.cs b/Tests/Validations/TA_LIB.cs
index 6776c783..84f92250 100644
--- a/Tests/Validations/TA_LIB.cs
+++ b/Tests/Validations/TA_LIB.cs
@@ -85,6 +85,33 @@ public class TA_LIB
}
[Fact]
+ public void SUM()
+ {
+ SUM_Series QL = new(bars.Close, period, false);
+ Core.Sum(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period);
+
+ Assert.Equal(Math.Round(TALIB[TALIB.Length - outBegIdx - 1], 6, MidpointRounding.AwayFromZero), Math.Round(QL.Last().v, 6, MidpointRounding.AwayFromZero));
+ }
+
+ [Fact]
+ public void MIDPRICE()
+ {
+ MIDPRICE_Series QL = new(bars, period, false);
+ Core.MidPrice(inhigh, inlow, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period);
+
+ Assert.Equal(Math.Round(TALIB[TALIB.Length - outBegIdx - 1], 6, MidpointRounding.AwayFromZero), Math.Round(QL.Last().v, 6, MidpointRounding.AwayFromZero));
+ }
+
+ [Fact]
+ public void MIDPOINT()
+ {
+ MIDPOINT_Series QL = new(bars.Close, period, false);
+ Core.MidPoint(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period);
+
+ 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);
diff --git a/docs/readme.md b/docs/readme.md
index e455846a..c6d0e743 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -43,11 +43,11 @@ See [Getting Started](https://github.com/mihakralj/QuanTAlib/blob/main/Docs/gett
| ✔️ OHL3 - (Open+High+Low)/3 | `.OHL3` |||
| ⭐ OHLC4 - Average Price | `.OHLC4` | AVGPRICE |️ GetBaseQuote |
| ⭐ HLCC4 - Weighted Price | `.HLCC4` | WCLPRICE ||
+| ⭐ MIDPOINT - Midpoint value | `MIDPOINT_Series` | MIDPOINT ||
+| ⭐ MIDPRICE - Midpoint price | `MIDPRICE_Series` | MIDPRICE ||
| ⭐ MAX - Max value | `MAX_Series` | MAX ||
| ⭐ MIN - Min value | `MIN_Series` | MIN ||
-| ⛔ MID - Midpoint value || MIDPOINT ||
-| ⛔ MIDP - Midpoint price || MIDPRICE ||
-| ⛔ SUM - Summation || SUM ||
+| ⭐ SUM - Summation | `SUM_Series` | SUM ||
| ⭐ ADD - Addition | `ADD_Series` | ADD ||
| ⭐ SUB - Subtraction | `SUB_Series` | SUB ||
| ⭐ MUL - Multiplication | `MUL_Series` | MUL ||