Files
QuanTAlib/lib/statistics/Min.cs
T
2024-10-27 09:38:53 -07:00

130 lines
4.1 KiB
C#

using System;
namespace QuanTAlib;
/// <summary>
/// MIN: Minimum Value with Decay
/// A statistical measure that tracks the lowest value over a specified period,
/// with an optional decay factor to gradually reduce the influence of older lows.
/// This adaptive approach allows the indicator to respond to changing market conditions.
/// </summary>
/// <remarks>
/// The MIN calculation process:
/// 1. Tracks lowest value in current period
/// 2. Applies exponential decay to old lows
/// 3. Adjusts decay based on time since last low
/// 4. Caps result at current period's minimum
///
/// Key characteristics:
/// - Tracks absolute lowest values
/// - Optional decay for adaptivity
/// - Maintains historical context
/// - Smooth transitions with decay
/// - Period-based windowing
///
/// Formula:
/// decay = 1 - e^(-halfLife * timeSinceMin / period)
/// min = min + decay * (periodAverage - min)
/// min = max(min, periodMinimum)
///
/// Market Applications:
/// - Identify support levels
/// - Track price troughs
/// - Implement trailing stops
/// - Monitor price extremes
/// - Adaptive trend following
///
/// Sources:
/// Technical Analysis of Financial Markets
/// https://www.investopedia.com/terms/s/support.asp
///
/// Note: Decay factor allows for adaptive low tracking
/// </remarks>
public class Min : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _buffer;
private readonly double _halfLife;
private double _currentMin;
private double _p_currentMin;
private int _timeSinceNewMin;
private int _p_timeSinceNewMin;
/// <param name="period">The number of points to consider for minimum calculation.</param>
/// <param name="decay">Half-life decay factor (0 for no decay, higher for faster forgetting).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1 or decay is negative.</exception>
public Min(int period, double decay = 0)
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
if (decay < 0)
{
throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
}
Period = period;
WarmupPeriod = 0;
_buffer = new CircularBuffer(period);
_halfLife = decay * 0.1;
Name = $"Min(period={period}, halfLife={decay:F2})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of points to consider for minimum calculation.</param>
/// <param name="decay">Half-life decay factor (default 0).</param>
public Min(object source, int period, double decay = 0) : this(period, decay)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_currentMin = double.MaxValue;
_timeSinceNewMin = 0;
}
protected override void ManageState(bool isNew)
{
if (isNew)
{
_p_currentMin = _currentMin;
_lastValidValue = Input.Value;
_index++;
_timeSinceNewMin++;
_p_timeSinceNewMin = _timeSinceNewMin;
}
else
{
_currentMin = _p_currentMin;
_timeSinceNewMin = _p_timeSinceNewMin;
}
}
protected override double Calculation()
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
// Update minimum if new value is lower
if (Input.Value <= _currentMin)
{
_currentMin = Input.Value;
_timeSinceNewMin = 0;
}
// Apply decay based on time since last minimum
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
_currentMin += decayRate * (_buffer.Average() - _currentMin);
// Ensure minimum doesn't fall below current period's lowest value
_currentMin = Math.Max(_currentMin, _buffer.Min());
IsHot = true;
return _currentMin;
}
}