From cfa10c79e8b365e4c02bfc7c9dc05fbd9ff5cd43 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Thu, 17 Nov 2022 21:59:17 -0800 Subject: [PATCH] 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);