From 54a7e4a19635e6069ab8f53d392da5517533d4a6 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Sun, 24 Apr 2022 16:53:16 -0700 Subject: [PATCH] KAMA, SMMA, ZLMA --- .../{ZLEMA_chart.cs => SMMA_chart.cs} | 24 ++--- Quantower/Indicators/ZLMA_chart.cs | 94 +++++++++++++++++++ Source/Indicators/HMA_Series.cs | 2 +- Source/Indicators/KAMA_Series.cs | 46 +++++---- Source/Indicators/SMMA_Series.cs | 58 ++++++++++++ Source/Indicators/ZLEMA_Series.cs | 33 ++----- Source/QuanTAlib.csproj | 2 +- Tests/MovingAvg/SMMA_Test.cs | 33 +++++++ Tests/Validations/Skender_Stock.cs | 9 ++ 9 files changed, 238 insertions(+), 63 deletions(-) rename Quantower/Indicators/{ZLEMA_chart.cs => SMMA_chart.cs} (64%) create mode 100644 Quantower/Indicators/ZLMA_chart.cs create mode 100644 Source/Indicators/SMMA_Series.cs create mode 100644 Tests/MovingAvg/SMMA_Test.cs diff --git a/Quantower/Indicators/ZLEMA_chart.cs b/Quantower/Indicators/SMMA_chart.cs similarity index 64% rename from Quantower/Indicators/ZLEMA_chart.cs rename to Quantower/Indicators/SMMA_chart.cs index 2c33c693..6bea81cb 100644 --- a/Quantower/Indicators/ZLEMA_chart.cs +++ b/Quantower/Indicators/SMMA_chart.cs @@ -2,7 +2,7 @@ using System.Drawing; using TradingPlatform.BusinessLayer; namespace QuanTAlib; -public class ZLEMA_chart : Indicator +public class SMMA_chart : Indicator { #region Parameters @@ -16,27 +16,28 @@ public class ZLEMA_chart : Indicator #endregion Parameters - private TBars bars; + private TBars bars; /////// - private ZLEMA_Series indicator; + private SMMA_Series indicator; /////// - public ZLEMA_chart() + public SMMA_chart() { this.SeparateWindow = false; - this.Name = "ZLEMA - Zero-lag Exponential Moving Average"; - this.Description = "Zero-Lag Exponential Moving Average description"; - this.AddLineSeries("ZLEMA", Color.RoyalBlue, 3, LineStyle.Solid); + this.Name = "SMMA - Smoothed Moving Average"; + this.Description = "Smoothed Moving Average description"; + this.AddLineSeries("SMMA", Color.RoyalBlue, 3, LineStyle.Solid); } protected override void OnInit() { - this.bars = new(); - this.ShortName = "ZLEMA (" + TBars.SelectStr(this.DataSource) + ", " + this.Period + ")"; - this.indicator = new(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); + this.ShortName = "SMMA (" + TBars.SelectStr(this.DataSource) + ", " + this.Period + ")"; + this.bars = new(); + this.indicator = new(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); } - protected override void OnUpdate(UpdateArgs args) + + protected override void OnUpdate(UpdateArgs args) { bool update = !(args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar); @@ -44,7 +45,6 @@ public class ZLEMA_chart : Indicator this.GetPrice(PriceType.High), this.GetPrice(PriceType.Low), this.GetPrice(PriceType.Close), this.GetPrice(PriceType.Volume), update); - double result = this.indicator[this.indicator.Count - 1].v; this.SetValue(result); } diff --git a/Quantower/Indicators/ZLMA_chart.cs b/Quantower/Indicators/ZLMA_chart.cs new file mode 100644 index 00000000..e8370495 --- /dev/null +++ b/Quantower/Indicators/ZLMA_chart.cs @@ -0,0 +1,94 @@ +using System.Collections; +using System.Drawing; +using System.Drawing.Text; +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class ZLMA_chart : Indicator +{ + #region Parameters + + [InputParameter("Smoothing period", 0, 1, 999, 1, 1)] + private int Period = 10; + + [InputParameter("Data source", 1, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int DataSource = 3; + + [InputParameter("MA algorithm", 2, variants: new object[] + { "SMA", 0, + "WMA", 1, + "EMA", 2, + "DEMA", 3, + "TEMA", 4, + "HMA", 5, + "KAMA", 6, + "JMA", 7, + "SMMA", 8 + })] + private int matype = 2; + +#endregion Parameters + + private TBars bars; + /////// + private ZL_Series zerolag; + private TSeries indicator; + /////// + + public ZLMA_chart() + { + this.SeparateWindow = false; + this.Name = "ZLMA - Zero-lag Moving Average"; + this.Description = "Zero-Lag Moving Average description"; + this.AddLineSeries("ZLMA", Color.RoyalBlue, 3, LineStyle.Solid); + } + + protected override void OnInit() + { + this.bars = new(); + string maname = matype switch + { + 0 => "SMA", + 1 => "WMA", + 2 => "EMA", + 3 => "DEMA", + 4 => "TEMA", + 5 => "HMA", + 6 => "KAMA", + 7 => "JMA", + 8 => "SMMA", + _ => "???" + }; + + this.ShortName = "ZLMA (" + maname + ", " + TBars.SelectStr(this.DataSource) + ", " + this.Period + ")"; + this.zerolag = new(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); + this.indicator = matype switch + { + 0 => new SMA_Series(source: zerolag, period: this.Period, useNaN: false), + 1 => new WMA_Series(source: zerolag, period: this.Period, useNaN: false), + 2 => new EMA_Series(source: zerolag, period: this.Period, useNaN: false), + 3 => new DEMA_Series(source: zerolag, period: this.Period, useNaN: false), + 4 => new TEMA_Series(source: zerolag, period: this.Period, useNaN: false), + 5 => new HMA_Series(source: zerolag, period: this.Period, useNaN: false), + 6 => new KAMA_Series(source: zerolag, period: this.Period, useNaN: false), + 7 => new JMA_Series(source: zerolag, period: this.Period, useNaN: false), + 8 => new SMMA_Series(source: zerolag, period: this.Period, useNaN: false), + _ => new EMA_Series(source: zerolag, period: this.Period, useNaN: false) + }; + } + + protected override void OnUpdate(UpdateArgs args) + { + bool update = !(args.Reason == UpdateReason.NewBar || + args.Reason == UpdateReason.HistoricalBar); + this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), + this.GetPrice(PriceType.High), this.GetPrice(PriceType.Low), + this.GetPrice(PriceType.Close), + this.GetPrice(PriceType.Volume), update); + + double result = this.indicator[this.indicator.Count - 1].v; + this.SetValue(result); + } +} diff --git a/Source/Indicators/HMA_Series.cs b/Source/Indicators/HMA_Series.cs index e620b2f8..4b36c0b8 100644 --- a/Source/Indicators/HMA_Series.cs +++ b/Source/Indicators/HMA_Series.cs @@ -59,7 +59,7 @@ public class HMA_Series : TSeries this._buf1.Add(data.v); this._buf2.Add(data.v); } - if (this._buf1.Count > (int)(Math.Ceiling((double)this._p / 2))) + if (this._buf1.Count > (int)((double)this._p / 2)) { this._buf1.RemoveAt(0); } diff --git a/Source/Indicators/KAMA_Series.cs b/Source/Indicators/KAMA_Series.cs index eda39bc4..20c49714 100644 --- a/Source/Indicators/KAMA_Series.cs +++ b/Source/Indicators/KAMA_Series.cs @@ -35,30 +35,28 @@ public class KAMA_Series : Single_TSeries_Indicator _scSlow = 2.0 / (slow+1); if (base._data.Count > 0) { base.Add(base._data); } } - public override void Add((System.DateTime t, double v) TValue, bool update) - { - if (update){ - _buffer[_buffer.Count - 1] = TValue.v; - this._lastkama = this._lastlastkama; - } else { - _buffer.Add(TValue.v); - } - if (_buffer.Count > _p + 1) { _buffer.RemoveAt(0); } - double _kama = TValue.v; - if (this.Count < this._p) { - for (int i = 0; i < this._buffer.Count; i++) { _kama += this._buffer[i]; } - _kama /= this._buffer.Count; - } else { - double _change = Math.Abs(_buffer[_buffer.Count - 1] - _buffer[(_buffer.Count > _p + 1) ? 1 : 0]); - double _sumpv = 0; - for (int i = 1; i < _buffer.Count; i++) - { - _sumpv += Math.Abs(_buffer[(_buffer.Count > 0) ? i : 0] - _buffer[i - 1]); - } - double _er = (_sumpv == 0) ? 0 : _change / _sumpv; - double _sc = (_er * (_scFast - _scSlow)) + _scSlow; - _kama = (_lastkama + (_sc * _sc * (TValue.v - _lastkama))); - } + public override void Add((System.DateTime t, double v) TValue, bool update) + { + if (update){ + _buffer[_buffer.Count - 1] = TValue.v; + this._lastkama = this._lastlastkama; + } else { + _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]; } + _kama /= this._buffer.Count; + } else { + double _change = Math.Abs(_buffer[_buffer.Count - 1] - _buffer[(_buffer.Count > _p + 1) ? 1 : 0]); + double _sumpv = 0; + for (int i = 1; i < _buffer.Count; i++) + { _sumpv += Math.Abs(_buffer[(_buffer.Count > 0) ? i : 0] - _buffer[i - 1]); } + double _er = (_sumpv == 0) ? 0 : _change / _sumpv; + double _sc = (_er * (_scFast - _scSlow)) + _scSlow; + _kama = (_lastkama + (_sc * _sc * (TValue.v - _lastkama))); + } _lastlastkama = _lastkama; _lastkama = _kama; var result = (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _kama); diff --git a/Source/Indicators/SMMA_Series.cs b/Source/Indicators/SMMA_Series.cs new file mode 100644 index 00000000..7bcec3d8 --- /dev/null +++ b/Source/Indicators/SMMA_Series.cs @@ -0,0 +1,58 @@ +namespace QuanTAlib; +using System; + +/* +SMMA: Smoothed Moving Average + The Smoothed Moving Average (SMMA) is a combination of a SMA and an EMA. It gives the recent prices + an equal weighting as the historic prices as it takes all available price data into account. + The main advantage of a smoothed moving average is that it removes short-term fluctuations. + + SMMA(i) = (SMMA-1*(N-1) + CLOSE (i)) / N + +Sources: + https://blog.earn2trade.com/smoothed-moving-average + https://guide.traderevolution.com/traderevolution/mobile-applications/phone/android/technical-indicators/moving-averages/smma-smoothed-moving-average + https://www.chartmill.com/documentation/technical-analysis-indicators/217-MOVING-AVERAGES-%7C-The-Smoothed-Moving-Average-%28SMMA%29 + + */ + +public class SMMA_Series : Single_TSeries_Indicator +{ + private readonly System.Collections.Generic.List _buffer = new(); + private double _lastsmma, _lastlastsmma; + + public SMMA_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) + { + this._lastsmma = this._lastlastsmma = double.NaN; + if (this._data.Count > 0) { base.Add(this._data); } + } + + public override void Add((DateTime t, double v) TValue, bool update) + { + double _smma = 0; + if (update) { this._lastsmma = this._lastlastsmma; } + + 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; + } + else + { + _smma = ((_lastsmma * (_p-1)) + TValue.v) / _p ; + } + + 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); + } +} \ No newline at end of file diff --git a/Source/Indicators/ZLEMA_Series.cs b/Source/Indicators/ZLEMA_Series.cs index 96210d6f..e9ffdd3c 100644 --- a/Source/Indicators/ZLEMA_Series.cs +++ b/Source/Indicators/ZLEMA_Series.cs @@ -21,35 +21,18 @@ Remark: public class ZLEMA_Series : Single_TSeries_Indicator { - private readonly double _k, _k1m; - private double _lastema, _lastlastema; + private ZL_Series zlag; + private EMA_Series ema; - public ZLEMA_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) - { - this._k = 2.0 / (double)(period + 1); - this._k1m = 1.0 - this._k; - this._lastema = this._lastlastema = double.NaN; - - - if (base._data.Count > 0) { base.Add(base._data); } - } + public ZLEMA_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) + { + zlag = new(source: source, period: period, useNaN: false); + ema = new EMA_Series(source: source, period: period, useNaN: useNaN); + } public override void Add((System.DateTime t, double v) TValue, bool update) { - if (update) - { - this._lastema = this._lastlastema; - } - int _lag = (int)(0.5 * (_p - 1)); - int _l = Math.Max(this._data.Count - _lag, 0); - double _lagdata = 1 * TValue.v - this._data[_l].v; - - double _ema = System.Double.IsNaN(this._lastema) ? _lagdata : _lagdata * this._k + this._lastema * this._k1m; - this._lastlastema = this._lastema; - this._lastema = _ema; - - (System.DateTime t, double v) result = - (TValue.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _ema); + (System.DateTime t, double v) result = ema[ema.Count-1]; base.Add(result, update); } diff --git a/Source/QuanTAlib.csproj b/Source/QuanTAlib.csproj index cf665210..45a52c97 100644 --- a/Source/QuanTAlib.csproj +++ b/Source/QuanTAlib.csproj @@ -1,6 +1,6 @@  - 0.1.11 + 0.1.12 QuanTAlib Library of Technical Indicators for .NET diff --git a/Tests/MovingAvg/SMMA_Test.cs b/Tests/MovingAvg/SMMA_Test.cs new file mode 100644 index 00000000..b6b1f79e --- /dev/null +++ b/Tests/MovingAvg/SMMA_Test.cs @@ -0,0 +1,33 @@ +using Xunit; +using System; +using QuanTAlib; + +namespace MovingAvg; +public class SMMA_Test +{ + [Fact] + public void Add_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + SMMA_Series c = new(a, 3); + Assert.Equal(6, c.Count); + a.Add(5); + Assert.Equal(a.Count, c.Count); + a.Add(0, update: true); + Assert.Equal(a.Count, c.Count); + } + + [Fact] + public void Edge_Test() + { + TSeries a = new() { double.NaN, double.Epsilon, double.PositiveInfinity, double.MaxValue }; + SMMA_Series c = new(a, 3); + Assert.Equal(a.Count, c.Count); + a.Add(double.NaN); + Assert.Equal(a.Count, c.Count); + a.Add(double.PositiveInfinity); + Assert.Equal(a.Count, c.Count); + + } + +} diff --git a/Tests/Validations/Skender_Stock.cs b/Tests/Validations/Skender_Stock.cs index a1adde8d..d93ee632 100644 --- a/Tests/Validations/Skender_Stock.cs +++ b/Tests/Validations/Skender_Stock.cs @@ -117,4 +117,13 @@ public class Skender_Stock Assert.Equal(Math.Round((double)SK.Last().Kama!, 8), Math.Round(QL.Last().v, 8)); } + + [Fact] + public void SMMA() + { + SMMA_Series QL = new(this.bars.Close, this.period, useNaN: false); + var SK = this.quotes.GetSmma(this.period); + + Assert.Equal(Math.Round((double)SK.Last().Smma!, 8), Math.Round(QL.Last().v, 8)); + } }