Lean  $LEAN_TAG$
MesaAdaptiveMovingAverage.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
18 
20 {
21  /// <summary>
22  /// Implements the Mesa Adaptive Moving Average (MAMA) indicator along with the following FAMA (Following Adaptive Moving Average) as a secondary indicator.
23  /// The MAMA adjusts its smoothing factor based on the market's volatility, making it more adaptive than a simple moving average.
24  /// </summary>
26  {
27  /// <summary>
28  /// The fast limit value used in the adaptive calculation.
29  /// </summary>
30  private readonly decimal _fastLimit;
31 
32  /// <summary>
33  /// The slow limit value used in the adaptive calculation.
34  /// </summary>
35  private readonly decimal _slowLimit;
36 
37  /// <summary>
38  /// Conversion factor for converting radians to degrees.
39  /// </summary>
40  private readonly decimal _rad2Deg = 180m / (4m * (decimal)Math.Atan(1.0));
41 
42  /// <summary>
43  /// Rolling windows to store historical data for calculation purposes.
44  /// </summary>
45  private readonly RollingWindow<decimal> _priceHistory;
46  private readonly RollingWindow<decimal> _smoothHistory;
47  private readonly RollingWindow<decimal> _detrendHistory;
48  private readonly RollingWindow<decimal> _inPhaseHistory;
49  private readonly RollingWindow<decimal> _quadratureHistory;
50 
51  /// <summary>
52  /// Variables holding previous calculation values for use in subsequent iterations.
53  /// </summary>
54  private decimal _prevPeriod;
55  private decimal _prevInPhase2;
56  private decimal _prevQuadrature2;
57  private decimal _prevReal;
58  private decimal _prevImaginary;
59  private decimal _prevSmoothPeriod;
60  private decimal _prevPhase;
61  private decimal _prevMama;
62 
63  /// <summary>
64  /// Gets the FAMA (Following Adaptive Moving Average) indicator value.
65  /// </summary>
67 
68  /// <summary>
69  /// Initializes a new instance of the MesaAdaptiveMovingAverage class.
70  /// </summary>
71  /// <param name="name">The name of the indicator.</param>
72  /// <param name="fastLimit">The fast limit for the adaptive moving average (default is 0.5).</param>
73  /// <param name="slowLimit">The slow limit for the adaptive moving average (default is 0.05).</param>
74  public MesaAdaptiveMovingAverage(string name, decimal fastLimit = 0.5m, decimal slowLimit = 0.05m)
75  : base(name)
76  {
77  _fastLimit = fastLimit;
78  _slowLimit = slowLimit;
79  _priceHistory = new RollingWindow<decimal>(13);
80  _smoothHistory = new RollingWindow<decimal>(6);
81  _detrendHistory = new RollingWindow<decimal>(6);
82  _inPhaseHistory = new RollingWindow<decimal>(6);
83  _quadratureHistory = new RollingWindow<decimal>(6);
84  _prevPeriod = 0m;
85  _prevInPhase2 = 0m;
86  _prevQuadrature2 = 0m;
87  _prevReal = 0m;
88  _prevImaginary = 0m;
89  _prevSmoothPeriod = 0m;
90  _prevPhase = 0m;
91  _prevMama = 0m;
92  Fama = new Identity(name + "_Fama");
93  }
94 
95  /// <summary>
96  /// Initializes a new instance of the MesaAdaptiveMovingAverage class with default name ("MAMA")
97  /// and the specified fast and slow limits for the adaptive moving average calculation.
98  /// </summary>
99  /// <param name="fastLimit">The fast limit for the adaptive moving average (default is 0.5).</param>
100  /// <param name="slowLimit">The slow limit for the adaptive moving average (default is 0.05).</param>
101  public MesaAdaptiveMovingAverage(decimal fastLimit = 0.5m, decimal slowLimit = 0.05m)
102  : this($"MAMA", fastLimit, slowLimit)
103  {
104  }
105 
106 
107  /// <summary>
108  /// Returns whether the indicator has enough data to be used (ready to calculate values).
109  /// </summary>
110  public override bool IsReady => Samples >= WarmUpPeriod;
111 
112  /// <summary>
113  /// Gets the number of periods required for warming up the indicator.
114  /// 33 periods are sufficient for the MAMA to provide stable and accurate results,
115  /// </summary>
116  public int WarmUpPeriod => 33;
117 
118  /// <summary>
119  /// Computes the next value for the Mesa Adaptive Moving Average (MAMA).
120  /// It calculates the MAMA by applying a series of steps including smoothing, detrending, and phase adjustments.
121  /// </summary>
122  /// <param name="input">The input bar (price data).</param>
123  /// <returns>The calculated MAMA value.</returns>
124  protected override decimal ComputeNextValue(IBaseDataBar input)
125  {
126  var price = (input.High + input.Low) / 2;
127  _priceHistory.Add(price);
128 
129  if (!_priceHistory.IsReady)
130  {
131  return decimal.Zero;
132  }
133 
134  //Calculate the MAMA and FAMA
135  var (mama, fama) = ComputeMamaAndFama();
136 
137  // Update previous values
138  _prevMama = mama;
139  Fama.Update(input.EndTime, fama);
140 
141  if (!IsReady)
142  {
143  return decimal.Zero;
144  }
145  return mama;
146  }
147 
148  private (decimal, decimal) ComputeMamaAndFama()
149  {
150  const decimal smallCoefficient = 0.0962m;
151  const decimal largeCoefficient = 0.5769m;
152 
153  var adjustedPeriod = 0.075m * _prevPeriod + 0.54m;
154 
155  // Compute the smoothed price value using a weighted average of the most recent prices.
156  var smooth = (4 * _priceHistory[0] + 3 * _priceHistory[1] + 2 * _priceHistory[2] + _priceHistory[3]) / 10;
157 
158  // Detrend the smoothed price to remove market noise, applying coefficients and adjusted period.
159  var detrender = (smallCoefficient * smooth + largeCoefficient * _smoothHistory[1] - largeCoefficient * _smoothHistory[3] - smallCoefficient * _smoothHistory[5]) * adjustedPeriod;
160 
161  // Compute the InPhase (I1) and Quadrature (Q1) components for the adaptive moving average.
162  var quadrature1 = (smallCoefficient * detrender + largeCoefficient * _detrendHistory[1] - largeCoefficient * _detrendHistory[3] - smallCoefficient * _detrendHistory[5]) * adjustedPeriod;
163  var inPhase1 = _detrendHistory[2];
164 
165  // Advance the phase of I1 and Q1 by 90 degrees
166  var adjustedInPhase = (smallCoefficient * inPhase1 + largeCoefficient * _inPhaseHistory[1] - largeCoefficient * _inPhaseHistory[3] - smallCoefficient * _inPhaseHistory[5]) * adjustedPeriod;
167  var adjustedQuadrature = (smallCoefficient * quadrature1 + largeCoefficient * _quadratureHistory[1] - largeCoefficient * _quadratureHistory[3] - smallCoefficient * _quadratureHistory[5]) * adjustedPeriod;
168  var inPhase2 = inPhase1 - adjustedQuadrature;
169  var quadrature2 = quadrature1 + adjustedInPhase;
170 
171  // Smooth the I2 and Q2 components before applying the discriminator
172  inPhase2 = 0.2m * inPhase2 + 0.8m * _prevInPhase2;
173  quadrature2 = 0.2m * quadrature2 + 0.8m * _prevQuadrature2;
174 
175  // Get alpha
176  var alpha = ComputeAlpha(inPhase1, quadrature1, inPhase2, quadrature2);
177 
178  // Calculate the MAMA and FAMA
179  var mama = alpha * _priceHistory[0] + (1m - alpha) * _prevMama;
180  var fama = 0.5m * alpha * mama + (1m - 0.5m * alpha) * Fama.Current.Value;
181 
182  // Update rolling history
183  _smoothHistory.Add(smooth);
184  _detrendHistory.Add(detrender);
185  _inPhaseHistory.Add(inPhase1);
186  _quadratureHistory.Add(quadrature1);
187 
188  return (mama, fama);
189  }
190 
191  private decimal ComputeAlpha(decimal inPhase1, decimal quadrature1, decimal inPhase2, decimal quadrature2)
192  {
193  var real = inPhase2 * _prevInPhase2 + quadrature2 * _prevQuadrature2;
194  var imaginary = inPhase2 * _prevQuadrature2 - quadrature2 * _prevInPhase2;
195  real = 0.2m * real + 0.8m * _prevReal;
196  imaginary = 0.2m * imaginary + 0.8m * _prevImaginary;
197 
198  // Calculate the period
199  var period = 0m;
200  if (imaginary != 0 && real != 0)
201  {
202  var angleInDegrees = (decimal)Math.Atan((double)(imaginary / real)) * _rad2Deg;
203  period = (angleInDegrees > 0) ? 360m / angleInDegrees : 0m;
204  }
205 
206  // Limit the period to certain thresholds
207  if (period > 1.5m * _prevPeriod)
208  {
209  period = 1.5m * _prevPeriod;
210  }
211  if (period < 0.67m * _prevPeriod)
212  {
213  period = 0.67m * _prevPeriod;
214  }
215  if (period < 6)
216  {
217  period = 6;
218  }
219  if (period > 50)
220  {
221  period = 50;
222  }
223 
224  // Smooth the period and calculate the phase
225  period = 0.2m * period + 0.8m * _prevPeriod;
226  var smoothPeriod = 0.33m * period + 0.67m * _prevSmoothPeriod;
227 
228  // Calculate the phase
229  var phase = 0m;
230  if (inPhase1 != 0)
231  {
232  phase = (decimal)Math.Atan((double)(quadrature1 / inPhase1)) * _rad2Deg;
233  }
234 
235  // Calculate the delta phase
236  var deltaPhase = _prevPhase - phase;
237  if (deltaPhase < 1m)
238  {
239  deltaPhase = 1m;
240  }
241 
242  // Calculate alpha
243  var alpha = _fastLimit / deltaPhase;
244  if (alpha < _slowLimit)
245  {
246  alpha = _slowLimit;
247  }
248 
249  // Update previous values
250  _prevInPhase2 = inPhase2;
251  _prevQuadrature2 = quadrature2;
252  _prevReal = real;
253  _prevImaginary = imaginary;
254  _prevPeriod = period;
255  _prevSmoothPeriod = smoothPeriod;
256  _prevPhase = phase;
257 
258  return alpha;
259  }
260 
261  /// <summary>
262  /// Resets the indicator's state, clearing history and resetting internal values.
263  /// </summary>
264  public override void Reset()
265  {
266  _priceHistory.Reset();
267  _smoothHistory.Reset();
268  _detrendHistory.Reset();
269  _inPhaseHistory.Reset();
270  _quadratureHistory.Reset();
271  _prevPeriod = 0m;
272  _prevInPhase2 = 0m;
273  _prevQuadrature2 = 0m;
274  _prevReal = 0m;
275  _prevImaginary = 0m;
276  _prevSmoothPeriod = 0m;
277  _prevPhase = 0m;
278  _prevMama = 0m;
279  Fama.Reset();
280  base.Reset();
281  }
282  }
283 }