From 0b57a75eb96bcf1bc23d541f8e74adb706e70542 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Sun, 24 Apr 2022 16:53:16 -0700 Subject: [PATCH] KAMA, SMMA, ZLEMA --- .../{ZLEMA_chart.cs => SMMA_chart.cs} | 24 ++--- Quantower/Indicators/ZLMA_chart.cs | 94 +++++++++++++++++++ Source/Basics/ZL_Series.cs | 6 +- Source/Indicators/HMA_Series.cs | 2 +- Source/Indicators/KAMA_Series.cs | 46 +++++---- Source/Indicators/SMMA_Series.cs | 58 ++++++++++++ Source/Indicators/ZLEMA_Series.cs | 71 ++++++++------ Source/QuanTAlib.csproj | 2 +- Tests/MovingAvg/SMMA_Test.cs | 33 +++++++ Tests/Tests.csproj | 8 ++ .../{Pandas_TA.cs => Pandas_TA.cstemp} | 0 Tests/Validations/Skender_Stock.cs | 9 ++ 12 files changed, 284 insertions(+), 69 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 rename Tests/Validations/{Pandas_TA.cs => Pandas_TA.cstemp} (100%) 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/Basics/ZL_Series.cs b/Source/Basics/ZL_Series.cs index c4843d19..04fc419d 100644 --- a/Source/Basics/ZL_Series.cs +++ b/Source/Basics/ZL_Series.cs @@ -3,12 +3,12 @@ using System; /* ZL: Zero Lag - Data is de-lagged by removing the data from “lag” days ago, thus removing + Data is de-lagged by removing the data from “lag” days ago, thus removing (or attempting to) the cumulative effect of the moving average. Calculation: Lag = (Period-1)/2 - ZL = Data + (Data - Data(Lag days ago) ) + ZL = Data + (Data - Data(Lag days ago) ) Sources: https://mudrex.com/blog/zero-lag-ema-trading-strategy/ @@ -24,7 +24,7 @@ public class ZL_Series : Single_TSeries_Indicator public override void Add((DateTime t, double v) TValue, bool update) { int _lag = (int)((_p-1) * 0.5); - _lag = (_data.Count-_lag < 0) ? 0 : _data.Count-_lag; + _lag = (this.Count-_lag < 0) ? 0 : this.Count-_lag; double _zl = TValue.v + (TValue.v - _data[_lag].v); 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..5642cf8f 100644 --- a/Source/Indicators/ZLEMA_Series.cs +++ b/Source/Indicators/ZLEMA_Series.cs @@ -21,36 +21,51 @@ Remark: public class ZLEMA_Series : Single_TSeries_Indicator { - private readonly double _k, _k1m; - private double _lastema, _lastlastema; + private readonly System.Collections.Generic.List _buffer = new(); + private readonly double _k, _k1m; + private double _lastema, _lastlastema; - 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; + public ZLEMA_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) + { + this._k = 2.0 / (this._p + 1); + this._k1m = 1.0 - this._k; + this._lastema = this._lastlastema = double.NaN; + if (base._data.Count > 0) + { base.Add(base._data); } + } + public override void Add((System.DateTime t, double v) TValue, bool update) + { + int _lag = (int)((_p - 1) * 0.5); + _lag = (this.Count - _lag < 0) ? 0 : this.Count - _lag; + double _zl = TValue.v + (TValue.v - _data[_lag].v); + double _ema = 0; + if (update) + { 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); } - if (base._data.Count > 0) { base.Add(base._data); } - } + for (int i = 0; i < this._buffer.Count; i++) + { _ema += this._buffer[i]; } + _ema /= this._buffer.Count; + } + else + { + _ema = TValue.v * this._k + this._lastema * this._k1m; + } - 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; + this._lastlastema = this._lastema; + this._lastema = _ema; - 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); - base.Add(result, update); - - } -} + var ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _ema); + base.Add(ret, update); + } +} \ No newline at end of file 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/Tests.csproj b/Tests/Tests.csproj index ac4a1d2a..e7f92739 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -26,6 +26,14 @@ 1701;1702;MSB3270 + + + + + + + + all diff --git a/Tests/Validations/Pandas_TA.cs b/Tests/Validations/Pandas_TA.cstemp similarity index 100% rename from Tests/Validations/Pandas_TA.cs rename to Tests/Validations/Pandas_TA.cstemp 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)); + } }