mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-28 01:37:43 +00:00
130 lines
4.1 KiB
C#
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;
|
|
}
|
|
}
|