/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
namespace QuantConnect.Indicators
{
///
/// Implements the Mesa Adaptive Moving Average (MAMA) indicator along with the following FAMA (Following Adaptive Moving Average) as a secondary indicator.
/// The MAMA adjusts its smoothing factor based on the market's volatility, making it more adaptive than a simple moving average.
///
public class MesaAdaptiveMovingAverage : BarIndicator, IIndicatorWarmUpPeriodProvider
{
///
/// The fast limit value used in the adaptive calculation.
///
private readonly decimal _fastLimit;
///
/// The slow limit value used in the adaptive calculation.
///
private readonly decimal _slowLimit;
///
/// Conversion factor for converting radians to degrees.
///
private readonly decimal _rad2Deg = 180m / (4m * (decimal)Math.Atan(1.0));
///
/// Rolling windows to store historical data for calculation purposes.
///
private readonly RollingWindow _priceHistory;
private readonly RollingWindow _smoothHistory;
private readonly RollingWindow _detrendHistory;
private readonly RollingWindow _inPhaseHistory;
private readonly RollingWindow _quadratureHistory;
///
/// Variables holding previous calculation values for use in subsequent iterations.
///
private decimal _prevPeriod;
private decimal _prevInPhase2;
private decimal _prevQuadrature2;
private decimal _prevReal;
private decimal _prevImaginary;
private decimal _prevSmoothPeriod;
private decimal _prevPhase;
private decimal _prevMama;
///
/// Gets the FAMA (Following Adaptive Moving Average) indicator value.
///
public IndicatorBase Fama { get; }
///
/// Initializes a new instance of the MesaAdaptiveMovingAverage class.
///
/// The name of the indicator.
/// The fast limit for the adaptive moving average (default is 0.5).
/// The slow limit for the adaptive moving average (default is 0.05).
public MesaAdaptiveMovingAverage(string name, decimal fastLimit = 0.5m, decimal slowLimit = 0.05m)
: base(name)
{
_fastLimit = fastLimit;
_slowLimit = slowLimit;
_priceHistory = new RollingWindow(13);
_smoothHistory = new RollingWindow(6);
_detrendHistory = new RollingWindow(6);
_inPhaseHistory = new RollingWindow(6);
_quadratureHistory = new RollingWindow(6);
_prevPeriod = 0m;
_prevInPhase2 = 0m;
_prevQuadrature2 = 0m;
_prevReal = 0m;
_prevImaginary = 0m;
_prevSmoothPeriod = 0m;
_prevPhase = 0m;
_prevMama = 0m;
Fama = new Identity(name + "_Fama");
}
///
/// Initializes a new instance of the MesaAdaptiveMovingAverage class with default name ("MAMA")
/// and the specified fast and slow limits for the adaptive moving average calculation.
///
/// The fast limit for the adaptive moving average (default is 0.5).
/// The slow limit for the adaptive moving average (default is 0.05).
public MesaAdaptiveMovingAverage(decimal fastLimit = 0.5m, decimal slowLimit = 0.05m)
: this($"MAMA", fastLimit, slowLimit)
{
}
///
/// Returns whether the indicator has enough data to be used (ready to calculate values).
///
public override bool IsReady => Samples >= WarmUpPeriod;
///
/// Gets the number of periods required for warming up the indicator.
/// 33 periods are sufficient for the MAMA to provide stable and accurate results,
///
public int WarmUpPeriod => 33;
///
/// Computes the next value for the Mesa Adaptive Moving Average (MAMA).
/// It calculates the MAMA by applying a series of steps including smoothing, detrending, and phase adjustments.
///
/// The input bar (price data).
/// The calculated MAMA value.
protected override decimal ComputeNextValue(IBaseDataBar input)
{
var price = (input.High + input.Low) / 2;
_priceHistory.Add(price);
if (!_priceHistory.IsReady)
{
return decimal.Zero;
}
//Calculate the MAMA and FAMA
var (mama, fama) = ComputeMamaAndFama();
// Update previous values
_prevMama = mama;
Fama.Update(input.EndTime, fama);
if (!IsReady)
{
return decimal.Zero;
}
return mama;
}
private (decimal, decimal) ComputeMamaAndFama()
{
const decimal smallCoefficient = 0.0962m;
const decimal largeCoefficient = 0.5769m;
var adjustedPeriod = 0.075m * _prevPeriod + 0.54m;
// Compute the smoothed price value using a weighted average of the most recent prices.
var smooth = (4 * _priceHistory[0] + 3 * _priceHistory[1] + 2 * _priceHistory[2] + _priceHistory[3]) / 10;
// Detrend the smoothed price to remove market noise, applying coefficients and adjusted period.
var detrender = (smallCoefficient * smooth + largeCoefficient * _smoothHistory[1] - largeCoefficient * _smoothHistory[3] - smallCoefficient * _smoothHistory[5]) * adjustedPeriod;
// Compute the InPhase (I1) and Quadrature (Q1) components for the adaptive moving average.
var quadrature1 = (smallCoefficient * detrender + largeCoefficient * _detrendHistory[1] - largeCoefficient * _detrendHistory[3] - smallCoefficient * _detrendHistory[5]) * adjustedPeriod;
var inPhase1 = _detrendHistory[2];
// Advance the phase of I1 and Q1 by 90 degrees
var adjustedInPhase = (smallCoefficient * inPhase1 + largeCoefficient * _inPhaseHistory[1] - largeCoefficient * _inPhaseHistory[3] - smallCoefficient * _inPhaseHistory[5]) * adjustedPeriod;
var adjustedQuadrature = (smallCoefficient * quadrature1 + largeCoefficient * _quadratureHistory[1] - largeCoefficient * _quadratureHistory[3] - smallCoefficient * _quadratureHistory[5]) * adjustedPeriod;
var inPhase2 = inPhase1 - adjustedQuadrature;
var quadrature2 = quadrature1 + adjustedInPhase;
// Smooth the I2 and Q2 components before applying the discriminator
inPhase2 = 0.2m * inPhase2 + 0.8m * _prevInPhase2;
quadrature2 = 0.2m * quadrature2 + 0.8m * _prevQuadrature2;
// Get alpha
var alpha = ComputeAlpha(inPhase1, quadrature1, inPhase2, quadrature2);
// Calculate the MAMA and FAMA
var mama = alpha * _priceHistory[0] + (1m - alpha) * _prevMama;
var fama = 0.5m * alpha * mama + (1m - 0.5m * alpha) * Fama.Current.Value;
// Update rolling history
_smoothHistory.Add(smooth);
_detrendHistory.Add(detrender);
_inPhaseHistory.Add(inPhase1);
_quadratureHistory.Add(quadrature1);
return (mama, fama);
}
private decimal ComputeAlpha(decimal inPhase1, decimal quadrature1, decimal inPhase2, decimal quadrature2)
{
var real = inPhase2 * _prevInPhase2 + quadrature2 * _prevQuadrature2;
var imaginary = inPhase2 * _prevQuadrature2 - quadrature2 * _prevInPhase2;
real = 0.2m * real + 0.8m * _prevReal;
imaginary = 0.2m * imaginary + 0.8m * _prevImaginary;
// Calculate the period
var period = 0m;
if (imaginary != 0 && real != 0)
{
var angleInDegrees = (decimal)Math.Atan((double)(imaginary / real)) * _rad2Deg;
period = (angleInDegrees > 0) ? 360m / angleInDegrees : 0m;
}
// Limit the period to certain thresholds
if (period > 1.5m * _prevPeriod)
{
period = 1.5m * _prevPeriod;
}
if (period < 0.67m * _prevPeriod)
{
period = 0.67m * _prevPeriod;
}
if (period < 6)
{
period = 6;
}
if (period > 50)
{
period = 50;
}
// Smooth the period and calculate the phase
period = 0.2m * period + 0.8m * _prevPeriod;
var smoothPeriod = 0.33m * period + 0.67m * _prevSmoothPeriod;
// Calculate the phase
var phase = 0m;
if (inPhase1 != 0)
{
phase = (decimal)Math.Atan((double)(quadrature1 / inPhase1)) * _rad2Deg;
}
// Calculate the delta phase
var deltaPhase = _prevPhase - phase;
if (deltaPhase < 1m)
{
deltaPhase = 1m;
}
// Calculate alpha
var alpha = _fastLimit / deltaPhase;
if (alpha < _slowLimit)
{
alpha = _slowLimit;
}
// Update previous values
_prevInPhase2 = inPhase2;
_prevQuadrature2 = quadrature2;
_prevReal = real;
_prevImaginary = imaginary;
_prevPeriod = period;
_prevSmoothPeriod = smoothPeriod;
_prevPhase = phase;
return alpha;
}
///
/// Resets the indicator's state, clearing history and resetting internal values.
///
public override void Reset()
{
_priceHistory.Reset();
_smoothHistory.Reset();
_detrendHistory.Reset();
_inPhaseHistory.Reset();
_quadratureHistory.Reset();
_prevPeriod = 0m;
_prevInPhase2 = 0m;
_prevQuadrature2 = 0m;
_prevReal = 0m;
_prevImaginary = 0m;
_prevSmoothPeriod = 0m;
_prevPhase = 0m;
_prevMama = 0m;
Fama.Reset();
base.Reset();
}
}
}