v0.8.6: update indicator docs, ndepend tooling, ALMA refactor, gitignore cleanup

This commit is contained in:
Miha Kralj
2026-03-13 13:46:52 -07:00
parent e3e9555fc1
commit c75135ab14
402 changed files with 2222 additions and 1779 deletions
+21 -15
View File
@@ -5,7 +5,7 @@ using TradingPlatform.BusinessLayer;
namespace QuanTAlib;
[SkipLocalsInit]
public class AlmaIndicator : Indicator, IWatchlistIndicator
public sealed class AlmaIndicator : Indicator, IWatchlistIndicator
{
[InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)]
public int Period { get; set; } = 9;
@@ -22,32 +22,33 @@ public class AlmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Show cold values", sortIndex: 21)]
public bool ShowColdValues { get; set; } = true;
private Alma ma = null!;
protected LineSeries Series;
protected string SourceName = null!;
private Alma _ma = null!;
private readonly LineSeries _series;
private string _sourceName = null!;
private Func<IHistoryItem, double> _priceSelector = null!;
public static int MinHistoryDepths => 0;
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public override string ShortName => $"ALMA {Period}:{SourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/alma/Alma.Quantower.cs";
public override string ShortName => $"ALMA {Period}:{_sourceName}";
public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends_FIR/alma/Alma.Quantower.cs";
public AlmaIndicator()
{
OnBackGround = true;
SeparateWindow = false;
SourceName = Source.ToString();
_sourceName = Source.ToString();
Name = "ALMA - Arnaud Legoux Moving Average";
Description = "Arnaud Legoux Moving Average";
Series = new LineSeries(name: $"ALMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(Series);
Description = "Arnaud Legoux Moving Average with Gaussian weighting";
_series = new LineSeries(name: $"ALMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnInit()
{
ma = new Alma(Period, Offset, Sigma);
SourceName = Source.ToString();
_ma = new Alma(Period, Offset, Sigma);
_sourceName = Source.ToString();
_priceSelector = Source.GetPriceSelector();
base.OnInit();
}
@@ -55,10 +56,15 @@ public class AlmaIndicator : Indicator, IWatchlistIndicator
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnUpdate(UpdateArgs args)
{
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar && args.Reason != UpdateReason.NewTick)
{
return;
}
var item = HistoricalData[Count - 1, SeekOriginHistory.Begin];
TValue result = _ma.Update(new TValue(item.TimeLeft.Ticks, _priceSelector(item)), args.IsNewBar());
TValue result = ma.Update(new TValue(item.TimeLeft.Ticks, _priceSelector(item)), isNew: args.IsNewBar());
Series.SetValue(result.Value, ma.IsHot, ShowColdValues);
_series.SetValue(result.Value, _ma.IsHot, ShowColdValues);
_series.SetMarker(0, Color.Transparent);
}
}
+228 -280
View File
@@ -1,6 +1,7 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace QuanTAlib;
@@ -8,10 +9,12 @@ namespace QuanTAlib;
/// ALMA: Arnaud Legoux Moving Average
/// </summary>
/// <remarks>
/// Gaussian-weighted MA with adjustable offset and sigma for responsiveness control.
/// Higher offset (0-1) = more responsive; higher sigma = sharper weights.
/// Gaussian-weighted FIR filter with configurable offset and sigma parameters.
/// The offset controls the peak of the Gaussian (0 = leftmost, 1 = rightmost).
/// The sigma controls the width of the Gaussian curve.
///
/// Calculation: <c>W_i = exp(-(i - m)² / (2s²))</c> where <c>m = offset × (period-1)</c>.
/// Calculation: <c>ALMA = Σ(w_i × P_i) / Σ(w_i)</c> where <c>w_i = exp(-((i - m)²) / (2s²))</c>,
/// <c>m = offset × (period - 1)</c>, <c>s = period / sigma</c>.
/// </remarks>
/// <seealso href="Alma.md">Detailed documentation</seealso>
[SkipLocalsInit]
@@ -21,152 +24,148 @@ public sealed class Alma : AbstractBase
private readonly double _offset;
private readonly double _sigma;
private readonly double[] _weights;
private readonly double _invWeightSum;
private readonly RingBuffer _buffer;
private readonly ITValuePublisher? _source;
private readonly TValuePublishedHandler? _pubHandler;
private bool _isNew = true;
private readonly TValuePublishedHandler? _handler;
private bool _disposed;
[StructLayout(LayoutKind.Auto)]
private record struct State(double LastValidValue, bool IsInitialized);
private record struct State(double LastInput, double LastValidValue, bool HasSeenValidData);
private State _state;
private State _pState;
public bool IsNew => _isNew;
public override bool IsHot => _buffer.IsFull;
/// <summary>
/// Default value to use for LastValidValue when no valid data has been seen yet.
/// Defaults to double.NaN to avoid silently introducing zeros.
/// </summary>
public double DefaultLastValidValue { get; set; } = double.NaN;
/// <summary>
/// Creates ALMA with specified parameters.
/// Initializes a new instance of the <see cref="Alma"/> class.
/// </summary>
/// <param name="period">Window size (must be > 0)</param>
/// <param name="offset">Gaussian offset (0-1, default 0.85). Closer to 1 makes it more responsive.</param>
/// <param name="sigma">Standard deviation (default 6). Higher values make it sharper.</param>
/// <param name="period">The lookback window size. Must be greater than 0.</param>
/// <param name="offset">The Gaussian peak offset (0.0 to 1.0). Default: 0.85.</param>
/// <param name="sigma">The Gaussian width divisor. Default: 6.0.</param>
public Alma(int period, double offset = 0.85, double sigma = 6.0)
{
if (period <= 0)
{
throw new ArgumentException("Period must be greater than 0", nameof(period));
}
if (sigma <= 0)
if (offset < 0.0 || offset > 1.0)
{
throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be between 0.0 and 1.0");
}
if (sigma <= 0.0)
{
throw new ArgumentException("Sigma must be greater than 0", nameof(sigma));
}
if (offset < 0 || offset > 1)
{
throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be between 0 and 1");
}
_period = period;
_offset = offset;
_sigma = sigma;
_buffer = new RingBuffer(period);
_weights = new double[period];
Name = $"Alma({period}, {offset:F2}, {sigma:F2})";
_weights = ComputeNormalizedWeights(period, offset, sigma);
Name = $"Alma({period},{offset:F2},{sigma:F1})";
WarmupPeriod = period;
ComputeWeights(_weights, period, offset, sigma, out _invWeightSum);
_state = new State(double.NaN, IsInitialized: false);
}
/// <summary>
/// Initializes a new instance of the <see cref="Alma"/> class with a source publisher.
/// </summary>
public Alma(ITValuePublisher source, int period, double offset = 0.85, double sigma = 6.0)
: this(period, offset, sigma)
{
_source = source;
_pubHandler = Handle;
_source.Pub += _pubHandler;
_handler = Handle;
source.Pub += _handler;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Handle(object? sender, in TValueEventArgs e) => Update(e.Value, e.IsNew);
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing && _source != null && _pubHandler != null)
if (disposing && _source != null && _handler != null)
{
_source.Pub -= _pubHandler;
_source.Pub -= _handler;
}
_disposed = true;
}
base.Dispose(disposing);
}
/// <summary>
/// Computes Gaussian weights for ALMA.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeWeights(Span<double> weights, int period, double offset, double sigma, out double invWeightSum)
{
double m = offset * (period - 1);
double s = period / sigma;
double s2 = 2 * s * s;
double sum = 0;
for (int i = 0; i < period; i++)
{
double v = i - m;
double w = Math.Exp(-(v * v) / s2);
weights[i] = w;
sum += w;
}
invWeightSum = 1.0 / sum;
}
public override bool IsHot => _buffer.IsFull;
public bool IsNew { get; private set; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double GetValidValue(double input)
{
if (double.IsFinite(input))
{
_state.LastValidValue = input;
_state.HasSeenValidData = true;
return input;
}
return _state.IsInitialized ? _state.LastValidValue : double.NaN;
return _state.HasSeenValidData ? _state.LastValidValue : DefaultLastValidValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateWeightedSum()
{
double result = 0;
int count = _buffer.Count;
int weightOffset = _period - count;
int idx = 0;
foreach (double item in _buffer)
{
result = Math.FusedMultiplyAdd(_weights[weightOffset + idx], item, result);
idx++;
}
// Normalize for partial windows
if (count < _period)
{
double wSum = 0;
for (int i = weightOffset; i < _period; i++)
{
wSum += _weights[i];
}
return wSum > 0 ? result / wSum : result;
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override TValue Update(TValue input, bool isNew = true)
{
_isNew = isNew;
return Update(input, isNew, publish: true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private TValue Update(TValue input, bool isNew, bool publish)
{
IsNew = isNew;
if (isNew)
{
double val = GetValidValue(input.Value);
_buffer.Add(val);
_state.LastInput = val;
_pState = _state;
}
else
{
if (_buffer.Count == 0)
{
throw new InvalidOperationException(
"Cannot call Update with isNew=false when buffer is empty. " +
"The first update must have isNew=true to initialize state.");
}
_state = _pState;
double val = GetValidValue(input.Value);
_buffer.UpdateNewest(val);
}
if (double.IsFinite(input.Value))
{
_state = _state with { LastValidValue = input.Value, IsInitialized = true };
}
// Retrieve valid value (handles NaN propagation prevention)
double val = GetValidValue(input.Value);
_buffer.Add(val, isNew);
double result = 0;
if (_buffer.Count > 0)
{
result = CalculateWeightedSum();
}
double result = CalculateWeightedSum();
Last = new TValue(input.Time, result);
if (publish)
{
PubEvent(Last, isNew);
}
PubEvent(Last, isNew);
return Last;
}
@@ -174,7 +173,7 @@ public sealed class Alma : AbstractBase
{
if (source.Count == 0)
{
return new TSeries([], []);
return [];
}
int len = source.Count;
@@ -189,20 +188,14 @@ public sealed class Alma : AbstractBase
Batch(source.Values, vSpan, _period, _offset, _sigma);
source.Times.CopyTo(tSpan);
// Restore state
_buffer.Clear();
_state = default;
// Replay last part to restore buffer state
int startIndex = Math.Max(0, len - _period);
for (int i = startIndex; i < len; i++)
{
Update(source[i], isNew: true, publish: false);
}
Prime(source.Values);
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
return new TSeries(t, v);
}
private void Handle(object? sender, in TValueEventArgs e) => Update(e.Value, e.IsNew);
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
{
if (source.Length == 0)
@@ -210,246 +203,201 @@ public sealed class Alma : AbstractBase
return;
}
// Reset state
_buffer.Clear();
_state = default;
_pState = default;
int len = source.Length;
int windowSize = Math.Min(len, _period);
int startIndex = len - windowSize;
int warmupLength = Math.Min(source.Length, WarmupPeriod);
int startIndex = source.Length - warmupLength;
// Seed LastValidValue from history before warmup window
double lastValid = double.NaN;
for (int i = startIndex - 1; i >= 0; i--)
// Seed LastValidValue
_state.LastValidValue = DefaultLastValidValue;
_state.HasSeenValidData = false;
if (startIndex > 0)
{
if (double.IsFinite(source[i]))
{
lastValid = source[i];
break;
}
}
// If not found, search in warmup window
if (double.IsNaN(lastValid))
{
for (int i = startIndex; i < source.Length; i++)
for (int i = startIndex - 1; i >= 0; i--)
{
if (double.IsFinite(source[i]))
{
lastValid = source[i];
_state.LastValidValue = source[i];
_state.HasSeenValidData = true;
break;
}
}
}
// Initialize state with seeded LastValidValue
if (double.IsFinite(lastValid))
// Reset buffer and process window
_buffer.Clear();
for (int i = startIndex; i < len; i++)
{
_state = new State(lastValid, IsInitialized: true);
double val = GetValidValue(source[i]);
_buffer.Add(val);
_state.LastInput = val;
}
// Feed the warmup data
for (int i = startIndex; i < source.Length; i++)
{
Update(new TValue(DateTime.MinValue, source[i]), isNew: true, publish: false);
}
// Calculate Last
double result = CalculateWeightedSum();
Last = new TValue(DateTime.MinValue, result);
_pState = _state;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateWeightedSum()
public override void Reset()
{
int count = _buffer.Count;
if (count == 0)
{
return 0;
}
if (count < _period)
{
// Partial buffer: align newest with newest
// Buffer[0] (oldest) -> Weights[period - count]
ReadOnlySpan<double> bufferSpan = _buffer.GetSpan();
int weightOffset = _period - count;
// Use DotProduct for partial sum
double sum = bufferSpan.DotProduct(_weights.AsSpan(weightOffset, count));
// Calculate weightSum for this subset
double wSum = 0;
for (int i = 0; i < count; i++)
{
wSum += _weights[weightOffset + i];
}
return wSum > 0 ? sum / wSum : 0;
}
// Full buffer: use precomputed _weightSum and SIMD DotProduct
// We use InternalBuffer and StartIndex to avoid allocation and handle wrapping
ReadOnlySpan<double> internalBuf = _buffer.InternalBuffer;
int head = _buffer.StartIndex;
// Part 1: Oldest to End of Buffer -> InternalBuffer[Head ... Cap-1]
// Matches Weights[0 ... Cap-Head-1]
int part1Len = _period - head;
double sum1 = internalBuf.Slice(head, part1Len).DotProduct(_weights.AsSpan(0, part1Len));
// Part 2: Start of Buffer to Newest -> InternalBuffer[0 ... Head-1]
// Matches Weights[Cap-Head ... Cap-1]
double sum2 = internalBuf[..head].DotProduct(_weights.AsSpan(part1Len));
return (sum1 + sum2) * _invWeightSum;
_buffer.Clear();
_state = default;
_pState = default;
Last = default;
}
/// <summary>
/// Computes ALMA for a TSeries using batch processing.
/// </summary>
public static TSeries Batch(TSeries source, int period, double offset = 0.85, double sigma = 6.0)
{
var alma = new Alma(period, offset, sigma);
return alma.Update(source);
}
/// <summary>
/// Computes ALMA for raw spans using batch processing.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, double offset = 0.85, double sigma = 6.0)
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period,
double offset = 0.85, double sigma = 6.0)
{
if (period <= 0)
{
throw new ArgumentException("Period must be greater than 0", nameof(period));
}
if (sigma <= 0)
if (sigma <= 0.0)
{
throw new ArgumentException("Sigma must be greater than 0", nameof(sigma));
}
if (offset < 0 || offset > 1)
if (offset < 0.0 || offset > 1.0)
{
throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be between 0 and 1");
throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be between 0.0 and 1.0");
}
if (source.Length != output.Length)
{
throw new ArgumentException("Source and output must have the same length", nameof(output));
}
// Allocation Strategy: Stack for small periods, Pool for large
double[]? weightsArray = period > 256 ? ArrayPool<double>.Shared.Rent(period) : null;
Span<double> weights = period <= 256
? stackalloc double[period]
: weightsArray!.AsSpan(0, period);
double[]? bufferArray = period > 256 ? ArrayPool<double>.Shared.Rent(period) : null;
Span<double> buffer = period <= 256
? stackalloc double[period]
: bufferArray!.AsSpan(0, period);
// Precompute weights using shared helper
ComputeWeights(weights, period, offset, sigma, out double invWeightSum);
int bufferIdx = 0;
int count = 0;
double lastValid = double.NaN; // Start with NaN to detect first valid value
double currentWeightSum = 0;
try
int len = source.Length;
if (len == 0)
{
for (int i = 0; i < source.Length; i++)
{
double val = source[i];
// Strict NaN handling: maintain NaN until first valid value
if (double.IsFinite(val))
{
lastValid = val;
}
else if (double.IsFinite(lastValid))
{
val = lastValid;
}
else
{
val = 0.0; // Fallback if series starts with NaN
}
// Add to circular buffer
buffer[bufferIdx] = val;
bufferIdx = (bufferIdx + 1) % period;
if (count < period)
{
count++;
// Incremental weight sum update for warmup
currentWeightSum += weights[period - count];
}
double sum = 0;
if (count == period)
{
// Buffer is full. bufferIdx points to the oldest element (next write position)
// Split the dot product to handle circular buffer wrap-around
int part1Len = period - bufferIdx;
// Part 1: Oldest data (at bufferIdx..End) * Start of Weights
sum += buffer.Slice(bufferIdx, part1Len).DotProduct(weights.Slice(0, part1Len));
// Part 2: Newest data (at 0..bufferIdx) * End of Weights
sum += buffer.Slice(0, bufferIdx).DotProduct(weights.Slice(part1Len));
output[i] = sum * invWeightSum;
}
else
{
// Partial buffer
int startIdx = (bufferIdx - count + period) % period;
int weightOffset = period - count;
if (startIdx + count <= period)
{
// Contiguous in buffer
sum = buffer.Slice(startIdx, count).DotProduct(weights.Slice(weightOffset, count));
}
else
{
// Wrapped in buffer
int part1Len = period - startIdx;
int part2Len = count - part1Len;
sum = buffer.Slice(startIdx, part1Len).DotProduct(weights.Slice(weightOffset, part1Len));
sum += buffer.Slice(0, part2Len).DotProduct(weights.Slice(weightOffset + part1Len, part2Len));
}
output[i] = currentWeightSum > 0 ? sum / currentWeightSum : 0;
}
}
return;
}
finally
{
if (weightsArray != null)
{
ArrayPool<double>.Shared.Return(weightsArray);
}
if (bufferArray != null)
{
ArrayPool<double>.Shared.Return(bufferArray);
}
}
CalculateScalarCore(source, output, period, offset, sigma);
}
public static (TSeries Results, Alma Indicator) Calculate(TSeries source, int period, double offset = 0.85, double sigma = 6.0)
/// <summary>
/// Computes ALMA and returns both the result series and a warmed-up indicator instance.
/// </summary>
public static (TSeries Results, Alma Indicator) Calculate(TSeries source, int period,
double offset = 0.85, double sigma = 6.0)
{
var indicator = new Alma(period, offset, sigma);
TSeries results = indicator.Update(source);
return (results, indicator);
}
public override void Reset()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CalculateScalarCore(ReadOnlySpan<double> source, Span<double> output,
int period, double offset, double sigma)
{
_buffer.Clear();
_state = new State(double.NaN, IsInitialized: false);
_pState = _state;
Last = default;
int len = source.Length;
double[] weights = ComputeNormalizedWeights(period, offset, sigma);
double lastValid = double.NaN;
Span<double> buffer = period <= 512 ? stackalloc double[period] : new double[period];
int bufferCount = 0;
int bufferIdx = 0;
for (int i = 0; i < len; i++)
{
double val = source[i];
if (double.IsFinite(val))
{
lastValid = val;
}
else
{
val = lastValid;
}
// Add to circular buffer
buffer[bufferIdx] = val;
bufferIdx++;
if (bufferIdx >= period)
{
bufferIdx = 0;
}
if (bufferCount < period)
{
bufferCount++;
}
// Compute weighted sum
double result = 0;
int weightOffset = period - bufferCount;
if (bufferCount == period)
{
// Full window — iterate from oldest to newest
int readIdx = bufferIdx; // bufferIdx now points to oldest
for (int k = 0; k < period; k++)
{
result = Math.FusedMultiplyAdd(weights[k], buffer[readIdx], result);
readIdx++;
if (readIdx >= period)
{
readIdx = 0;
}
}
}
else
{
// Partial window — use tail weights
double wSum = 0;
for (int k = 0; k < bufferCount; k++)
{
int wi = weightOffset + k;
result = Math.FusedMultiplyAdd(weights[wi], buffer[k], result);
wSum += weights[wi];
}
result = wSum > 0 ? result / wSum : result;
}
output[i] = result;
}
}
/// <summary>
/// Pre-computes normalized Gaussian weights for the ALMA filter.
/// Weights are normalized so that their sum equals 1.0.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double[] ComputeNormalizedWeights(int period, double offset, double sigma)
{
double[] w = new double[period];
double m = offset * (period - 1);
double s = period / sigma;
double s2 = 2.0 * s * s;
double wSum = 0;
for (int i = 0; i < period; i++)
{
double d = i - m;
w[i] = Math.Exp(-(d * d) / s2);
wSum += w[i];
}
// Normalize weights to sum to 1.0
double invSum = 1.0 / wSum;
for (int i = 0; i < period; i++)
{
w[i] *= invSum;
}
return w;
}
}
+3 -5
View File
@@ -14,10 +14,8 @@
| **Signature** | [alma_signature](alma_signature.md) |
- ALMA is a Finite Impulse Response (FIR) filter that applies a Gaussian window to price data.
- Parameterized by `period`, `offset` (default 0.85), `sigma` (default 6.0).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
- **Similar:** [FWMA](../fwma/fwma.md), [SinEma](../sinema/sinema.md) | **Complementary:** ATR for volatility filter | **Trading note:** Gaussian-weighted FIR filter; offset parameter controls responsiveness vs smoothness tradeoff.
- Validated against Skender, Ooples, and Pandas-TA reference implementations.
ALMA is a Finite Impulse Response (FIR) filter that applies a Gaussian window to price data. Unlike the Simple Moving Average (which treats 10-minute-old data with the same reverence as 1-minute-old data) or the Exponential Moving Average (which holds onto history like a hoarder), ALMA allows you to shape the weight distribution precisely. It lets you define the trade-off between smoothness and lag using standard deviation ($\sigma$) and offset, rather than arbitrary periods.
@@ -180,4 +178,4 @@ QuanTAlib validates against reference implementations that respect the Gaussian
* $\sigma = 1$: The curve is flat. You have reinvented the Simple Moving Average (badly).
* $\sigma = 10$: The curve is a needle. You are sampling one specific bar in history.
3. **Cold Start**: ALMA requires a full window ($L$) to be mathematically valid. First $L-1$ bars are convergence noise. Ignore them.
3. **Cold Start**: ALMA requires a full window ($L$) to be mathematically valid. First $L-1$ bars are convergence noise. Ignore them.
+5 -6
View File
@@ -13,11 +13,10 @@
| **PineScript** | [blma.pine](blma.pine) |
| **Signature** | [blma_signature](blma_signature.md) |
- The Blackman Window Moving Average (BLMA) applies a triple-cosine window function from digital signal processing to financial time series.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
- BLMA is a FIR filter that applies a triple-cosine Blackman window function from digital signal processing to financial time series.
- Best suited as a long-term trend filter due to its superior noise suppression (-58 dB sidelobes) at the cost of ~N/2 lag.
- **Similar:** [WMA](../wma/wma.md), [TRIMA](../trima/trima.md) | **Complementary:** Trend confirmation | **Trading note:** Blackman-windowed MA; low sidelobe leakage for clean spectral response.
- Validated against reference implementations using the standard Blackman window formula.
The Blackman Window Moving Average (BLMA) applies a triple-cosine window function from digital signal processing to financial time series. Originally developed by **Ralph Beebe Blackman** at Bell Labs in the 1950s for spectral analysis, this filter provides superior noise suppression compared to standard moving averages by minimizing spectral leakage.
@@ -119,4 +118,4 @@ BLMA is validated against a reference implementation using the standard Blackman
### Common Pitfalls
* **Lag**: BLMA has more lag than EMA or WMA because it suppresses the most recent data. It is a smoothing filter, not a leading indicator.
* **Warmup**: During the first $N$ bars, the window expands dynamically. The full noise-suppression characteristics are only achieved after $N$ bars.
* **Warmup**: During the first $N$ bars, the window expands dynamically. The full noise-suppression characteristics are only achieved after $N$ bars.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [bwma_signature](bwma_signature.md) |
- BWMA is a Finite Impulse Response (FIR) filter that applies a Bessel-derived window function to weight price data.
- Parameterized by `period`, `order` (default 0).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [GWMA](../gwma/gwma.md), [HanMA](../hanma/hanma.md) | **Complementary:** ATR for volatility | **Trading note:** Blackman-Window MA; FIR filter with Blackman taper. Good spectral leakage suppression.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
BWMA is a Finite Impulse Response (FIR) filter that applies a Bessel-derived window function to weight price data. The weighting follows a parabolic (or higher-order polynomial) profile that emphasizes the center of the lookback window while smoothly tapering to zero at the edges. Unlike rectangular (SMA) or exponential (EMA) weighting, BWMA provides a mathematically smooth transition that reduces spectral leakage and Gibbs phenomenon artifacts.
@@ -216,4 +214,4 @@ Self-consistency validation ensures:
* [ALMA](../alma/Alma.md) - Gaussian window with adjustable offset
* [WMA](../wma/Wma.md) - Linear weighting (triangular window)
* [SINEMA](../sinema/Sinema.md) - Sine-weighted moving average
* [SINEMA](../sinema/Sinema.md) - Sine-weighted moving average
+2 -4
View File
@@ -13,9 +13,7 @@
| **PineScript** | [conv.pine](conv.pine) |
- CONV (Convolution Moving Average) is the ultimate tool for the signal processing purist.
- Parameterized by double[] kernel.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [SMA](../sma/Sma.md), [ALMA](../alma/alma.md) | **Trading note:** Convolution operator; applies custom kernel to price. Foundation of all FIR moving averages.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
CONV (Convolution Moving Average) is the ultimate tool for the signal processing purist. It doesn't presume to know what kind of smoothing you need; it simply asks for a kernel (a set of weights) and applies it to the data. Want a Gaussian filter? A Sinc filter? A custom edge-detection filter? CONV runs them all.
@@ -112,4 +110,4 @@ Validation is performed by reproducing standard moving averages (SMA, WMA, TRIMA
1. **Kernel Direction**: Our implementation applies the kernel such that the last element of the kernel multiplies the most recent data point. If you import kernels from other DSP libraries, you might need to reverse them.
2. **Normalization**: Kernel weights are *not* automatically normalized. If the sum of the weights is not 1.0, the output scale will be different from the input scale. This is a feature, not a bug (allows for differential filters).
3. **Performance**: A kernel size of 1000 will be 100x slower than a kernel size of 10. Use FFT-based convolution for massive kernels (not implemented here; this is for trading, not searching for extraterrestrial life).
3. **Performance**: A kernel size of 1000 will be 100x slower than a kernel size of 10. Use FFT-based convolution for massive kernels (not implemented here; this is for trading, not searching for extraterrestrial life).
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [crma_signature](crma_signature.md) |
- CRMA fits a degree-3 polynomial $y = a_0 + a_1 x + a_2 x^2 + a_3 x^3$ to the most recent $N$ bars via ordinary least squares, then returns the fitt...
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [SMA](../sma/Sma.md), [TrIMA](../trima/trima.md) | **Complementary:** Trend strength indicators | **Trading note:** Cubic-Root weighted MA; gentle weighting profile between uniform (SMA) and triangular (TrIMA).
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
CRMA fits a degree-3 polynomial $y = a_0 + a_1 x + a_2 x^2 + a_3 x^3$ to the most recent $N$ bars via ordinary least squares, then returns the fitted endpoint value $a_0$. By capturing inflection and curvature that linear and quadratic models miss, CRMA tracks S-shaped reversals and accelerating trends with measurably lower endpoint error than LSMA or QRMA on non-stationary price series. The cost is a 4x4 linear system solve per bar, which is O(1) once power sums are accumulated in O(N).
@@ -132,4 +130,4 @@ O(N) per bar. For default N = 14: ~347 cycles. Resync re-computes sums every 100
| 4×4 Gaussian elimination | No | Fixed scalar 64-op system; not worth SIMD setup |
| Polynomial evaluation | No | 4-term Horner; scalar is fastest for degree 3 |
Batch throughput for the sum and cross-product phases: AVX2 achieves ~4× scalar. Gaussian elimination and Horner evaluation remain scalar. Net batch speedup for N = 14, large series: approximately 2.5× over fully scalar.
Batch throughput for the sum and cross-product phases: AVX2 achieves ~4× scalar. Gaussian elimination and Horner evaluation remain scalar. Net batch speedup for N = 14, large series: approximately 2.5× over fully scalar.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [dwma_signature](dwma_signature.md) |
- DWMA (Double Weighted Moving Average) is exactly what it says on the tin: a Weighted Moving Average of a Weighted Moving Average.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `(period * 2) - 1` bars of warmup before first valid output (IsHot = true).
- **Similar:** [WMA](../wma/wma.md), [FWMA](../fwma/fwma.md) | **Complementary:** Volume confirmation | **Trading note:** Distance-Weighted MA; assigns weights based on distance from current bar.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
DWMA (Double Weighted Moving Average) is exactly what it says on the tin: a Weighted Moving Average of a Weighted Moving Average. Unlike DEMA, which tries to *remove* lag, DWMA accepts lag as the price of admission for superior noise reduction. It produces a curve that is incredibly smooth, ideal for identifying long-term trends without getting faked out by market chop.
@@ -98,4 +96,4 @@ Validated against chained WMA implementations in standard libraries.
1. **Lag**: This indicator lags. A lot. Do not use it for entry signals on tight timeframes. Use it for trend filtering (e.g., "only buy if price > DWMA").
2. **Warmup**: It takes roughly $2 \times N$ bars to produce valid data.
3. **Confusion with DEMA**: DEMA = Fast, DWMA = Smooth. Do not mix them up.
3. **Confusion with DEMA**: DEMA = Fast, DWMA = Smooth. Do not mix them up.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [fwma_signature](fwma_signature.md) |
- The Fibonacci Weighted Moving Average applies the Fibonacci sequence as FIR filter weights, assigning exponentially growing importance to recent bars.
- Parameterized by `period` (default 10).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [WMA](../wma/wma.md), [DWMA](../dwma/dwma.md) | **Complementary:** Trend strength indicators | **Trading note:** Fibonacci-Weighted MA; weights follow Fibonacci sequence, naturally emphasizing recent data.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Fibonacci Weighted Moving Average applies the Fibonacci sequence as FIR filter weights, assigning exponentially growing importance to recent bars. Where WMA uses linear weights (1, 2, 3, ..., N) and PWMA uses parabolic weights ($1^2, 2^2, ..., N^2$), FWMA uses F(1), F(2), ..., F(N). The Fibonacci growth rate ($\phi \approx 1.618$) produces a weighting profile between exponential and parabolic, giving FWMA a distinctive "golden ratio decay" that concentrates roughly 61.8% of total weight in the most recent third of the window.
@@ -196,4 +194,4 @@ For small periods ($N \leq 8$), a single AVX2 register can hold the entire weigh
- Fischer, R. (1993). *Fibonacci Applications and Strategies for Traders*. Wiley.
- Koshy, T. (2001). *Fibonacci and Lucas Numbers with Applications*. Wiley.
- everget (2018). "Fibonacci Weighted Moving Average." TradingView open-source indicator.
- Binet, J. P. M. (1843). "Mémoire sur l'intégration des équations linéaires aux différences finies." *Comptes Rendus*, 17, 563-567.
- Binet, J. P. M. (1843). "Mémoire sur l'intégration des équations linéaires aux différences finies." *Comptes Rendus*, 17, 563-567.
+7 -6
View File
@@ -13,11 +13,12 @@
| **PineScript** | [gwma.pine](gwma.pine) |
| **Signature** | [gwma_signature](gwma_signature.md) |
- GWMA is a Finite Impulse Response (FIR) filter that applies a centered Gaussian window to price data.
- Parameterized by `period`, `sigma` (default 0.4).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
- GWMA is a symmetric FIR filter applying a centered Gaussian window to price data, providing optimal noise reduction with zero phase distortion at the cost of fixed lag equal to half the window length.
- Similar to [ALMA](../alma/Alma.md) (offset Gaussian), [SINEMA](../sinema/Sinema.md) (sine window), and [Kaiser](../kaiser/Kaiser.md) — all windowed FIR filters with different smoothing profiles.
- Pair with RSI or Stochastic to confirm trend strength; GWMA excels at defining trend direction but not momentum.
- The sigma parameter controls weight concentration: lower sigma sharpens the center peak for cycle detection, higher sigma broadens toward SMA-like behavior.
- **Similar:** [ALMA](../alma/alma.md), [WMA](../wma/wma.md) | **Complementary:** Volume indicators | **Trading note:** Gaussian-Weighted MA; bell-curve weights for symmetric smoothing.
- Validated against mathematical definition and PineScript reference implementation.
GWMA is a Finite Impulse Response (FIR) filter that applies a centered Gaussian window to price data. Unlike ALMA (which allows shifting the Gaussian peak via an offset parameter), GWMA centers the bell curve at the middle of the lookback window. The sigma parameter controls the width of the Gaussian, determining how sharply the weights decay from the center.
@@ -209,4 +210,4 @@ QuanTAlib validates GWMA against its mathematical definition and internal consis
3. **Cold Start**: GWMA requires a full window ($L$) to be mathematically valid. First $L-1$ bars are convergence noise.
4. **Centered vs Offset**: Don't confuse GWMA with ALMA. GWMA always centers the Gaussian; ALMA lets you shift it. If you find yourself wanting offset control, use ALMA instead.
4. **Centered vs Offset**: Don't confuse GWMA with ALMA. GWMA always centers the Gaussian; ALMA lets you shift it. If you find yourself wanting offset control, use ALMA instead.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [hamma_signature](hamma_signature.md) |
- HAMMA is a Finite Impulse Response (FIR) filter that applies a Hamming window to price data.
- Parameterized by `period` (default 10).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [HanMA](../hanma/hanma.md), [BWMA](../bwma/Bwma.md) | **Complementary:** ATR for bands | **Trading note:** Hamming-Window MA; FIR filter minimizing sidelobe amplitude. Balance of main lobe width vs leakage.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
HAMMA is a Finite Impulse Response (FIR) filter that applies a Hamming window to price data. The Hamming window is a raised cosine with specific coefficients (0.54 and 0.46) chosen to minimize the amplitude of the first side lobe in the frequency domain. This makes it particularly effective at separating the signal (trend) from nearby noise frequencies.
@@ -194,4 +192,4 @@ QuanTAlib validates HAMMA against its mathematical definition and internal consi
4. **Small Periods**: With very small periods (e.g., 3), the window shape degenerates. The edge-center-edge pattern becomes less meaningful. Consider period >= 5 for meaningful Hamming characteristics.
5. **Side Lobe Trade-off**: The -43 dB first side lobe comes at the cost of slightly wider main lobe than Hanning. If frequency resolution matters more than side lobe suppression, consider other windows.
5. **Side Lobe Trade-off**: The -43 dB first side lobe comes at the cost of slightly wider main lobe than Hanning. If frequency resolution matters more than side lobe suppression, consider other windows.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [hanma_signature](hanma_signature.md) |
- HANMA is a Finite Impulse Response (FIR) filter that applies a Hanning (Hann) window to price data.
- Parameterized by `period` (default 10).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [WMA](../wma/wma.md), [SinEma](../sinema/sinema.md) | **Complementary:** Trend following | **Trading note:** Hann-Windowed MA; cosine-bell window for smooth spectral characteristics.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
HANMA is a Finite Impulse Response (FIR) filter that applies a Hanning (Hann) window to price data. The Hanning window is a pure raised cosine with edge weights of exactly zero, which provides excellent side lobe suppression while maintaining a narrower main lobe than Hamming. It's particularly effective when you want to eliminate boundary discontinuities entirely.
@@ -197,4 +195,4 @@ QuanTAlib validates HANMA against its mathematical definition and internal consi
5. **Small Periods**: With very small periods (e.g., 3), the window shape degenerates. A period of 3 produces weights [0, 1, 0]—essentially just the middle value. Consider period >= 5 for meaningful Hanning characteristics.
6. **Side Lobe Trade-off**: The -32 dB first side lobe is worse than Hamming's -43 dB, but the narrower main lobe provides better frequency resolution. Choose based on whether you prioritize frequency resolution or side lobe suppression.
6. **Side Lobe Trade-off**: The -32 dB first side lobe is worse than Hamming's -43 dB, but the narrower main lobe provides better frequency resolution. Choose based on whether you prioritize frequency resolution or side lobe suppression.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [hend_signature](hend_signature.md) |
- HEND is a symmetric FIR filter derived from the Henderson (1916) closed-form weight formula, designed to pass cubic polynomial trends without disto...
- Parameterized by `period` (default 7).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [LSMA](../lsma/lsma.md), [TSF](../tsf/Tsf.md) | **Complementary:** StdDev | **Trading note:** Henderson MA; used by Australian Bureau of Statistics. Optimal for extracting smooth trend from noisy data.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
HEND is a symmetric FIR filter derived from the Henderson (1916) closed-form weight formula, designed to pass cubic polynomial trends without distortion while maximally suppressing irregular noise. Used as the core smoother in the X-11 and X-13ARIMA-SEATS seasonal adjustment frameworks by statistical agencies worldwide, HEND achieves the theoretically optimal trade-off between smoothness (measured by the sum of squared third differences of the weights) and fidelity for cubic trends. Weights can be negative at the edges, giving the filter a bandpass-like property that sharpens trend-cycle extraction.
@@ -117,4 +115,4 @@ O(N) per bar. For default N = 7 (5-term odd period): ~31 cycles. For N = 23 (com
| Negative-weight handling | Yes | No special treatment needed; signed FMA handles negatives |
| Cross-bar independence | Yes | Each bar's output is independent; full outer-loop vectorization |
With AVX2, 4 bars can be processed simultaneously (each is an N-tap dot product). Total batch throughput: ~N/4 cycles per bar for large series. For N = 23 and 1000-bar batch: ~5750 cycles vs ~95000 scalar — approximately 16.5× speedup (memory-bound at larger N).
With AVX2, 4 bars can be processed simultaneously (each is an N-tap dot product). Total batch throughput: ~N/4 cycles per bar for large series. For N = 23 and 1000-bar batch: ~5750 cycles vs ~95000 scalar — approximately 16.5× speedup (memory-bound at larger N).
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [hma_signature](hma_signature.md) |
- HMA (Hull Moving Average) is a solution to the eternal struggle between smoothness and lag.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period + sqrtPeriod - 1` bars of warmup before first valid output (IsHot = true).
- **Similar:** [DEMA](../../trends_IIR/dema/dema.md), [TEMA](../../trends_IIR/tema/tema.md) | **Complementary:** Signal line crossover | **Trading note:** Alan Hulls MA; cascades WMAs to nearly eliminate lag while maintaining smoothness.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
HMA (Hull Moving Average) is a solution to the eternal struggle between smoothness and lag. Most indicators force you to choose one; HMA gives you both. It achieves this by using weighted moving averages (WMAs) in a clever configuration that cancels out lag while maintaining the smoothing properties of the WMA.
@@ -107,4 +105,4 @@ Discrepancies exist due to different rounding methods for integer periods.
* **QuanTAlib**: Uses integer truncation (floor) for $N/2$ and $\sqrt{N}$.
* **Ooples**: Uses `Math.Round` (nearest integer).
This results in different effective periods for $N=14$ ($\sqrt{14} \approx 3.74 \to 3$ vs $4$) and others where the fractional part $\ge 0.5$. Validation tests match exactly for periods where rounding logic aligns (e.g., $N=9, 20, 50$).
This results in different effective periods for $N=14$ ($\sqrt{14} \approx 3.74 \to 3$ vs $4$) and others where the fractional part $\ge 0.5$. Validation tests match exactly for periods where rounding logic aligns (e.g., $N=9, 20, 50$).
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [ilrs_signature](ilrs_signature.md) |
- ILRS computes the linear regression slope over a rolling window, then accumulates it via discrete integration (running sum) to reconstruct a smooth...
- Parameterized by `period` (default 14).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [LSMA](../lsma/lsma.md), [LinReg](../../statistics/linreg/LinReg.md) | **Complementary:** R² for fit quality | **Trading note:** Integral of Linear Regression Slope; smoothed trend derived from cumulative regression.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
ILRS computes the linear regression slope over a rolling window, then accumulates it via discrete integration (running sum) to reconstruct a smoothed price-level signal. By differentiating (slope extraction) and reintegrating, ILRS acts as a low-pass filter that preserves trend direction while suppressing high-frequency noise more aggressively than LSMA. The integration step introduces a natural momentum quality: the output continues rising even as slope magnitude diminishes, making ILRS particularly effective for trend-following systems that need early exit signals based on slope deceleration.
@@ -140,4 +138,4 @@ O(1) per bar after warmup (the incremental sum pattern removes the N-scan). For
| Slope formula | Yes | `VFNMADD`, `VDIVPD` once prefix sums are built |
| Integral (prefix sum of slopes) | Partial | Sequential scan; parallel prefix available but overhead > benefit for N < 1000 |
Batch mode can precompute prefix sums vectorially then compute all slopes in parallel. The integral sum remains a sequential dependency. Net speedup for large series: ~2× over scalar.
Batch mode can precompute prefix sums vectorially then compute all slopes in parallel. The integral sum remains a sequential dependency. Net speedup for large series: ~2× over scalar.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [kaiser_signature](kaiser_signature.md) |
- KAISER applies the Kaiser-Bessel window function as FIR filter weights, providing a single parameter ($\beta$) that continuously controls the trade...
- Parameterized by `period` (default 14), `beta` (default 3.0).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [ALMA](../alma/alma.md), [BLMA](../blma/blma.md) | **Complementary:** Cycle analysis | **Trading note:** Kaiser-windowed MA; adjustable sidelobe suppression via beta parameter.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
KAISER applies the Kaiser-Bessel window function as FIR filter weights, providing a single parameter ($\beta$) that continuously controls the trade-off between main lobe width (transition band sharpness) and sidelobe attenuation (stopband rejection). At $\beta = 0$ it degenerates to a rectangular window (SMA); at $\beta \approx 5.65$ it approximates the Blackman window; at $\beta \approx 8.6$ it matches the Hamming window's sidelobe profile. This makes KAISER the most flexible single-parameter window-based moving average, allowing traders to tune frequency selectivity without changing the window length.
@@ -133,4 +131,4 @@ O(N) per bar. For default N = 14: ~59 cycles. Weight computation at construction
| Symmetric weight exploitation | Yes | Kaiser weights are symmetric: w[i] = w[N-1-i]; SIMD can fuse pairs |
| Cross-bar independence | Yes | Each bar fully independent; outer-loop SIMD viable |
Due to symmetric weights (w[i] = w[N-1-i]), the FIR can be folded: each pair (oldest + newest) shares the same weight, halving the multiply count to N/2 FMA. AVX2 batch throughput: approximately N/8 cycles per bar — for N = 14, ~1.75 cycles/bar at peak.
Due to symmetric weights (w[i] = w[N-1-i]), the FIR can be folded: each pair (oldest + newest) shares the same weight, halving the multiply count to N/2 FMA. AVX2 batch throughput: approximately N/8 cycles per bar — for N = 14, ~1.75 cycles/bar at peak.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [lanczos_signature](lanczos_signature.md) |
- LANCZOS applies the normalized sinc function $\text{sinc}(x) = \sin(\pi x)/(\pi x)$ as a symmetric FIR window, producing a moving average with near...
- Parameterized by `period` (default 14).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [SinEma](../sinema/sinema.md), [NyqMA](../nyqma/Nyqma.md) | **Complementary:** FFT for frequency analysis | **Trading note:** Lanczos filter; sinc function with Lanczos window. Near-ideal low-pass with sharp cutoff.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
LANCZOS applies the normalized sinc function $\text{sinc}(x) = \sin(\pi x)/(\pi x)$ as a symmetric FIR window, producing a moving average with near-ideal low-pass frequency characteristics. The sinc function is the impulse response of the perfect brick-wall low-pass filter; windowing it to finite length trades sharp cutoff for practical realizability. The result is a smoother with minimal Gibbs phenomenon ringing and excellent passband flatness, at the cost of small negative sidelobe weights that can cause minor overshooting on sharp price discontinuities.
@@ -121,4 +119,4 @@ O(N) per bar. For default N = 14: ~59 cycles. Sinc weights are computed once at
| Cross-bar independence | Yes | Batch outer loop: process 4 output bars per AVX2 iteration |
| Negative weight handling | Yes | Signed FMA; no branch needed |
AVX2 batch throughput with symmetric folding: ~N/8 cycles per output bar. For N = 14 over 1000-bar batch: ~1750 cycles vs ~59000 cycles scalar (~34× speedup at peak, memory-limited at larger N).
AVX2 batch throughput with symmetric folding: ~N/8 cycles per output bar. For N = 14 over 1000-bar batch: ~1750 cycles vs ~59000 cycles scalar (~34× speedup at peak, memory-limited at larger N).
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [lsma_signature](lsma_signature.md) |
- LSMA (Least Squares Moving Average), also known as the Moving Linear Regression or Endpoint Moving Average, calculates the least squares regression...
- Parameterized by `period`, `offset` (default 0).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [EPMA](../epma/epma.md), [ALMA](../alma/alma.md) | **Complementary:** R-squared for regression quality | **Trading note:** Least Squares MA; linear regression value at current bar, minimizing squared deviations.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
LSMA (Least Squares Moving Average), also known as the Moving Linear Regression or Endpoint Moving Average, calculates the least squares regression line for the preceding time periods. In plain English: it finds the "best fit" line for the data window and tells you where that line ends.
@@ -113,4 +111,4 @@ Validated against Skender.
1. **Overshoot**: Because it projects a trend, LSMA will overshoot significantly when the trend reverses. It assumes the trend continues.
2. **Offset**: You can use a positive offset to extrapolate into the future (forecasting), or a negative offset to center the average.
3. **Noise**: It is very sensitive to outliers because it tries to fit a line to them.
3. **Noise**: It is very sensitive to outliers because it tries to fit a line to them.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [nlma_signature](nlma_signature.md) |
- NLMA uses a two-phase damped cosine kernel with $5P - 1$ taps (where $P$ is the user period).
- Parameterized by `period` (default 14).
- Output range: Tracks input.
- Requires 1 bar of warmup before first valid output (IsHot = true).
- **Similar:** [ALMA](../alma/alma.md), [GWMA](../gwma/gwma.md) | **Complementary:** ATR for volatility | **Trading note:** Non-Linear MA; adapts weight profile to price behavior.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
NLMA uses a two-phase damped cosine kernel with $5P - 1$ taps (where $P$ is the user period). Phase 1 builds the initial sweep; Phase 2 extends it through multiple cosine cycles. The kernel's negative weights in the mid-section subtract lagged price components, reducing group delay well below what a positive-only SMA of the same length achieves. Normalization by the signed weight sum preserves DC gain of 1.0. The result is a trend-following filter with moderate overshoot but substantially less lag than conventional moving averages.
@@ -180,4 +178,4 @@ For period = 14, the 69-weight array (552 bytes) fits in L1 cache. AVX2 batch th
- Igorad / TrendLaboratory. "NonLagMA" original MQL4 source code. The canonical reference for the two-phase kernel formula.
- Finware Ltd. "FATL/SATL Digital Filters." Technical documentation for FinWare trading software. Inspiration for the negative-weight lag cancellation approach.
- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. Chapter 4: FIR Filters with Negative Weights.
- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. Chapter 4: FIR Filters with Negative Weights.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [nyqma_signature](nyqma_signature.md) |
- NYQMA combines a primary LWMA (Linear Weighted Moving Average) with a secondary LWMA applied to the first, using lag-compensating extrapolation: $\...
- Parameterized by `period` (default 89), `nyquistperiod` (default 21).
- Output range: Tracks input.
- Requires 1 bar of warmup before first valid output (IsHot = true).
- **Similar:** [Lanczos](../lanczos/Lanczos.md), [SinEma](../sinema/sinema.md) | **Complementary:** Cycle detection | **Trading note:** Nyquist MA; designed around Nyquist frequency. Optimal for eliminating aliased cycles.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
NYQMA combines a primary LWMA (Linear Weighted Moving Average) with a secondary LWMA applied to the first, using lag-compensating extrapolation: $\text{NYQMA} = (1+\alpha) \cdot \text{MA}_1 - \alpha \cdot \text{MA}_2$, where $\alpha = N_2 / (N_1 - N_2)$. The Nyquist constraint $N_2 \leq \lfloor N_1/2 \rfloor$ ensures the second smoothing does not introduce aliasing artifacts into the output. This produces a lag-reduced moving average grounded in sampling theory rather than ad-hoc coefficient tuning. Streaming update is O(1) per bar via composed Wma instances; batch mode uses stackalloc/ArrayPool with FMA in the extrapolation loop.
@@ -180,4 +178,4 @@ The $1 \times 10^{-6}$ tolerance for batch vs streaming reflects expected floati
- Dürschner, M.G. *Gleitende Durchschnitte 3.0*. (Original NYQMA publication, German language.)
- Shannon, C.E. (1949). "Communication in the Presence of Noise." *Proceedings of the IRE*, 37(1), 10-21.
- Nyquist, H. (1928). "Certain Topics in Telegraph Transmission Theory." *Transactions of the AIEE*, 47(2), 617-644.
- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. (PMA reference for comparison.)
- Ehlers, J.F. (2001). *Rocket Science for Traders*. Wiley. (PMA reference for comparison.)
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [parzen_signature](parzen_signature.md) |
- PARZEN applies the Parzen (de la Vallée-Poussin) window function as FIR filter weights, producing a moving average with exceptional sidelobe suppre...
- Parameterized by `period` (default 14).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [GWMA](../gwma/gwma.md), [BWMA](../bwma/Bwma.md) | **Complementary:** ATR | **Trading note:** Parzen-window MA; piecewise-cubic taper. Very smooth with low spectral leakage.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
PARZEN applies the Parzen (de la Vallée-Poussin) window function as FIR filter weights, producing a moving average with exceptional sidelobe suppression ($-24$ dB/octave rolloff) and a smooth bell-shaped kernel. The Parzen window is the self-convolution of two triangular (Bartlett) windows at half-length, which guarantees continuous first and second derivatives at all points. This makes it one of the few windows whose frequency response has no discontinuities in its first three derivatives, yielding the fastest sidelobe decay rate among common windows without requiring the computational cost of Bessel functions (Kaiser) or specialized polynomials (Henderson).
@@ -131,4 +129,4 @@ O(N) per bar. For default N = 14: ~59 cycles. No negative weights — normalizat
| Parzen symmetry | Yes | Symmetric window: w[i] = w[N-1-i]; fold for N/2 FMAs |
| Cross-bar independence | Yes | Full outer-loop SIMD viable |
Symmetric folding halves the multiply count. AVX2 batch throughput: ~N/8 cycles per output bar. Non-negative weights avoid any masking overhead, giving slightly cleaner codegen than sinc-based filters.
Symmetric folding halves the multiply count. AVX2 batch throughput: ~N/8 cycles per output bar. Non-negative weights avoid any masking overhead, giving slightly cleaner codegen than sinc-based filters.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [pma_signature](pma_signature.md) |
- PMA (Predictive Moving Average) is a lag-cancellation filter that uses linear extrapolation of dual WMA (Weighted Moving Average) cascades to predi...
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `(period * 2) - 1` bars of warmup before first valid output (IsHot = true).
- **Similar:** [LSMA](../lsma/lsma.md), [Polyfit](../../statistics/polyfit/Polyfit.md) | **Complementary:** R² for trend quality | **Trading note:** Polynomial MA; fits nth-degree polynomial. Captures curves better than linear regression.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
PMA (Predictive Moving Average) is a lag-cancellation filter that uses linear extrapolation of dual WMA (Weighted Moving Average) cascades to predict price direction. It produces two outputs: the PMA line (extrapolated trend) and a Trigger line for crossover signals. Default period is 7 per Ehlers' original specification.
@@ -190,4 +188,4 @@ PMA has no direct equivalent in external libraries. Validation uses component co
- Ehlers, J.F. (2004). *Cybernetic Analysis for Stocks and Futures*. John Wiley and Sons.
- Ehlers, J.F. (2001). "MESA Adaptive Moving Average." *Technical Analysis of Stocks and Commodities*, September 2001.
- Mulloy, P.G. (1994). "Smoothing Data with Faster Moving Averages." *Technical Analysis of Stocks and Commodities*, February 1994.
- Richardson, L.F. (1911). "The Approximate Arithmetical Solution by Finite Differences of Physical Problems." *Philosophical Transactions of the Royal Society A*, 210: 307-357.
- Richardson, L.F. (1911). "The Approximate Arithmetical Solution by Finite Differences of Physical Problems." *Philosophical Transactions of the Royal Society A*, 210: 307-357.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [pwma_signature](pwma_signature.md) |
- PWMA (Parabolic Weighted Moving Average) applies a parabolic ($i^2$) weighting scheme to the data window.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [FWMA](../fwma/fwma.md), [WMA](../wma/wma.md) | **Complementary:** Trend filters | **Trading note:** Pascal-Weighted MA; weights from Pascals triangle for smooth, symmetric kernel.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
PWMA (Parabolic Weighted Moving Average) applies a parabolic ($i^2$) weighting scheme to the data window. This assigns massive importance to the most recent data points while still technically including the older data. It's like a WMA on steroids.
@@ -96,4 +94,4 @@ Validated against Ooples.
| **Skender** | N/A | Not implemented |
| **TA-Lib** | N/A | Not implemented |
| **Tulip** | N/A | Not implemented. |
| **Tulip** | N/A | Not implemented. |
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [qrma_signature](qrma_signature.md) |
- QRMA fits a second-degree polynomial $y = a + bx + cx^2$ to the most recent $N$ bars via ordinary least squares, then returns the fitted value at t...
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [LSMA](../lsma/lsma.md), [PMA](../pma/Pma.md) | **Complementary:** Trend indicators | **Trading note:** Quadratic Regression MA; 2nd-order polynomial fit. Captures parabolic acceleration.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
QRMA fits a second-degree polynomial $y = a + bx + cx^2$ to the most recent $N$ bars via ordinary least squares, then returns the fitted value at the endpoint (newest bar). By capturing curvature that LSMA (degree-1) misses, QRMA provides meaningfully better tracking of accelerating or decelerating price trends. The 3x3 normal-equation system is solved via Cramer's rule in O(1) after an O(N) data accumulation pass, making it computationally efficient and suitable for streaming applications.
@@ -140,4 +138,4 @@ O(N) per bar from power sum accumulation. For default N = 14: ~214 cycles. Compa
| Cramer 3×3 solve | No | Fixed 30-op scalar system; SIMD setup overhead exceeds benefit |
| Quadratic evaluation (Horner) | No | 2 FMAs; scalar fastest at degree 2 |
Batch speedup for the sum accumulation phases: ~3× with AVX2. Solve and evaluation phases remain scalar. Net batch speedup for large series: approximately 2× over fully scalar.
Batch speedup for the sum accumulation phases: ~3× with AVX2. Solve and evaluation phases remain scalar. Net batch speedup for large series: approximately 2× over fully scalar.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [rain_signature](rain_signature.md) |
- RAIN recursively applies SMA 10 times, producing 10 layers of progressively smoother price representation, then computes a weighted average across ...
- Parameterized by `period`.
- Output range: Tracks input.
- Requires 1 bar of warmup before first valid output (IsHot = true).
- **Similar:** [ALMA](../alma/alma.md), [FWMA](../fwma/fwma.md) | **Complementary:** ATR | **Trading note:** Raised-cosine MA; smooth taper at edges. Good sidelobe suppression for noise reduction.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
RAIN recursively applies SMA 10 times, producing 10 layers of progressively smoother price representation, then computes a weighted average across all layers. Layers 1-4 receive weights 5, 4, 3, 2 (emphasizing the more responsive layers), while layers 5-10 each receive weight 1, for a total divisor of 20. This multi-scale composition produces a moving average that responds to short-term price changes through the lightly smoothed upper layers while maintaining stability through the heavily smoothed lower layers.
@@ -134,4 +132,4 @@ O(1) per bar. Each of the 10 SMA layers is O(1); the composite sum is 10 FMA ope
| Weighted composite | Yes | 10-element dot product; fits in 23 AVX2 registers |
| Cross-bar independence | Yes | Outer loop fully vectorizable: 4 output bars per pass |
Because all 10 SMA layers are independent, the entire computation can be vectorized across layers AND across bars simultaneously. AVX2 can process 4 bars per pass, each bar updating all 10 layers via 10-register prefix sums. Estimated batch speedup for large series: ~6× over scalar.
Because all 10 SMA layers are independent, the entire computation can be vectorized across layers AND across bars simultaneously. AVX2 can process 4 bars per pass, each bar updating all 10 layers via 10-register prefix sums. Estimated batch speedup for large series: ~6× over scalar.
+2 -4
View File
@@ -13,9 +13,7 @@
| **PineScript** | [rwma.pine](rwma.pine) |
- RWMA weights each bar's contribution to the average by its price range (high minus low), giving greater influence to volatile bars and less to narr...
- Parameterized by `period` (default 14).
- Output range: Tracks input.
- Requires `> period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [WMA](../wma/wma.md), [EMA](../../trends_IIR/ema/ema.md) | **Trading note:** Right-weighted MA; concentrates weight on recent data while maintaining FIR structure.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
RWMA weights each bar's contribution to the average by its price range (high minus low), giving greater influence to volatile bars and less to narrow-range, indecisive bars. The logic: a bar with a large range represents stronger price discovery and carries more informational content than a low-range doji. This produces a moving average that gravitates toward prices established during high-activity periods, naturally incorporating volatility as a relevance signal without requiring a separate volatility indicator.
@@ -120,4 +118,4 @@ O(1) per bar. The division is the dominant cost. Resync every 1000 bars prevents
| Prefix sum of range | Partial | Same as above |
| Final division | Yes | `VDIVPD` after prefix sums built; zero-guard via `VCMPPD` + blend |
Both prefix sums can be built with AVX2 prefix-scan kernels. Once built, all N sliding-window divisions can be computed in parallel. Batch speedup: approximately 4× over scalar for large series.
Both prefix sums can be built with AVX2 prefix-scan kernels. Once built, all N sliding-window divisions can be computed in parallel. Batch speedup: approximately 4× over scalar for large series.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [sgma_signature](sgma_signature.md) |
- SGMA is a Finite Impulse Response (FIR) filter that uses polynomial fitting to smooth data while preserving higher moments (peaks, valleys, and inf...
- Parameterized by `period` (default 9), `degree` (default 2).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [SGF](../../filters/sgf/Sgf.md), [LSMA](../lsma/lsma.md) | **Trading note:** Savitzky-Golay MA; polynomial smoothing that preserves higher moments. Good for derivative estimation.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
SGMA is a Finite Impulse Response (FIR) filter that uses polynomial fitting to smooth data while preserving higher moments (peaks, valleys, and inflection points). Unlike the Simple Moving Average (which flattens everything) or the Exponential Moving Average (which introduces phase lag), SGMA uses polynomial weighting to maintain the original signal's shape characteristics.
@@ -195,4 +193,4 @@ QuanTAlib validates SGMA against mathematical properties rather than external li
4. **Cold Start**: SGMA requires a full window ($L$) to produce mathematically valid output. The first $L-1$ bars are warmup noise. Check `IsHot` before trading on the signal.
5. **High Degree Instability**: Degrees 3-4 concentrate weight heavily in the center. While this preserves shape, it also means a small number of bars dominate the output—approaching the behavior of a very short moving average with extra smoothing on the tails.
5. **High Degree Instability**: Degrees 3-4 concentrate weight heavily in the center. While this preserves shape, it also means a small number of bars dominate the output—approaching the behavior of a very short moving average with extra smoothing on the tails.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [sinema_signature](sinema_signature.md) |
- The Sine-Weighted Moving Average (SINEMA) applies sine-wave weighting to data points within the lookback window.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [ALMA](../alma/alma.md), [BLMA](../blma/blma.md) | **Complementary:** Cycle indicators | **Trading note:** Sine-weighted MA; half-sine kernel for naturally smooth bell-shaped weights.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Sine-Weighted Moving Average (SINEMA) applies sine-wave weighting to data points within the lookback window. Weights follow the formula $w_i = \sin(\pi \cdot (i+1) / N)$, creating a smooth bell-shaped distribution that emphasizes middle values while gracefully tapering at the edges. Unlike SMA's uniform weighting or WMA's linear ramp, sine weighting provides a natural transition that reduces high-frequency noise while preserving mid-frequency trends.
@@ -128,4 +126,4 @@ Validation tests verify:
## References
- Harris, F. J. (1978). "On the use of windows for harmonic analysis with the discrete Fourier transform." *Proceedings of the IEEE*, 66(1), 51-83.
- Oppenheim, A. V., & Schafer, R. W. (2010). *Discrete-Time Signal Processing* (3rd ed.). Pearson.
- Oppenheim, A. V., & Schafer, R. W. (2010). *Discrete-Time Signal Processing* (3rd ed.). Pearson.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [sma_signature](sma_signature.md) |
- The Simple Moving Average (SMA) is the unweighted arithmetic mean of the last $N$ data points.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [EMA](../../trends_IIR/ema/ema.md), [WMA](../wma/wma.md) | **Complementary:** ATR for Keltner-style bands | **Trading note:** Simple Moving Average; equal-weight FIR filter. Most basic and widely used MA. Foundation of many composite indicators.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Simple Moving Average (SMA) is the unweighted arithmetic mean of the last $N$ data points. It acts as a low-pass filter, smoothing out high-frequency noise to reveal the underlying trend. While conceptually simple, efficient implementation on modern hardware requires careful attention to memory access patterns and vectorization.
@@ -115,4 +113,4 @@ For 512 bars:
| **TA-Lib** | ✅ | Matches `TA_SMA` exactly. |
| **Skender** | ✅ | Matches `GetSma` exactly. |
| **Tulip** | ✅ | Matches `sma` exactly. |
| **Ooples** | ✅ | Matches `CalculateSimpleMovingAverage`. |
| **Ooples** | ✅ | Matches `CalculateSimpleMovingAverage`. |
+1 -3
View File
@@ -15,8 +15,6 @@
- SP15 is a fixed-coefficient symmetric FIR filter with 15 weights: $[-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3]$ divided by 320.
- No configurable parameters; computation is stateless per bar.
- Output range: Tracks input.
- Requires `Period` bars of warmup before first valid output (IsHot = true).
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
SP15 is a fixed-coefficient symmetric FIR filter with 15 weights: $[-3, -6, -5, 3, 21, 46, 67, 74, 67, 46, 21, 3, -5, -6, -3]$ divided by 320. The weights were designed by John Spencer to have zero frequency response at periods 4 and 5 (frequencies $2\pi/4$ and $2\pi/5$), making the filter effective at removing quarterly and quintile seasonal components from economic time series. The negative edge weights provide bandpass-like characteristics, and the fixed design requires no parameters beyond the source series.
@@ -128,4 +126,4 @@ O(1) per bar (N is fixed at 15). The dot product takes ~60 cycles on modern x86.
| Negative edge weights | Yes | Signed FMA; no special masking |
| Fixed-N: 15 taps | Yes | Compiler can fully unroll the 15-FMA loop at O3 |
With symmetric folding (8 unique weight pairs), the 15-tap dot product reduces to ~8 FMAs. AVX2 processes 4 output bars per outer iteration. Batch throughput: ~2 cycles per output bar at peak. Unrolled codegen fits entirely in instruction cache.
With symmetric folding (8 unique weight pairs), the 15-tap dot product reduces to ~8 FMAs. AVX2 processes 4 output bars per outer iteration. Batch throughput: ~2 cycles per output bar at peak. Unrolled codegen fits entirely in instruction cache.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [swma_signature](swma_signature.md) |
- SWMA applies triangular (symmetric) weights that peak at the center of the window and taper linearly to the edges.
- Parameterized by `period` (default 4).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [WMA](../wma/wma.md), [SMA](../sma/Sma.md) | **Trading note:** Symmetric-Weighted MA; bell-shaped weight profile centered on middle. Reduces end-point bias.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
SWMA applies triangular (symmetric) weights that peak at the center of the window and taper linearly to the edges. For period $N$, the weight at position $i$ is $w(i) = (N/2 + 1) - |i - N/2|$, producing a tent-shaped kernel. This is mathematically equivalent to convolving two rectangular windows (SMA of SMA), giving SWMA a frequency response that is the square of the SMA's sinc-like response. The result is smoother than SMA with better sidelobe suppression, at the cost of slightly more lag.
@@ -126,4 +124,4 @@ O(N) per bar. For default N = 14: ~59 cycles. Triangular weights are strictly po
| Symmetric triangular window | Yes | Fold: only ⌈N/2⌉ unique weights; halves FMA count |
| Cross-bar independence | Yes | 4 output bars per AVX2 pass |
Symmetric folding reduces the effective FMA count to ⌈N/2⌉. For N = 14: 7 FMAs per bar. AVX2 batch throughput: ~N/8 cycles per bar. Among the windowed FIR filters, SWMA has the fewest effective operations due to its simple triangular shape.
Symmetric folding reduces the effective FMA count to ⌈N/2⌉. For N = 14: 7 FMAs per bar. AVX2 batch throughput: ~N/8 cycles per bar. Among the windowed FIR filters, SWMA has the fewest effective operations due to its simple triangular shape.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [trima_signature](trima_signature.md) |
- The Triangular Moving Average (TRIMA) places the majority of its weight on the middle of the data window, tapering off linearly towards the ends.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `p1 + p2 - 1` bars of warmup before first valid output (IsHot = true).
- **Similar:** [WMA](../wma/wma.md), [SMA](../../trends_IIR/sma/sma.md) | **Complementary:** Volume analysis | **Trading note:** Triangular MA; double-smoothed SMA with triangle-shaped weights peaking at center.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Triangular Moving Average (TRIMA) places the majority of its weight on the middle of the data window, tapering off linearly towards the ends. This creates a triangular weight distribution (hence the name). It is mathematically equivalent to a double-smoothed SMA.
@@ -90,4 +88,4 @@ Each SMA component benefits from SIMD prefix-sum optimization:
| **TA-Lib** | ✅ | Matches `TA_TRIMA` exactly. |
| **Skender** | ✅ | Matches composite `SMA(SMA)` logic. |
| **Tulip** | ✅ | Matches `trima` exactly. |
| **Ooples** | N/A | Not implemented. |
| **Ooples** | N/A | Not implemented. |
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [tsf_signature](tsf_signature.md) |
- TSF projects the least-squares regression line one bar forward, providing a statistically grounded forecast of the next bar's value.
- Parameterized by `period` (default 14).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [LSMA](../lsma/lsma.md), [LinReg](../../statistics/linreg/LinReg.md) | **Complementary:** R² for forecast reliability | **Trading note:** Time Series Forecast; linear regression extrapolated one bar ahead. Predictive MA.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
TSF projects the least-squares regression line one bar forward, providing a statistically grounded forecast of the next bar's value. Unlike simple moving averages that smooth past data, TSF answers the question: "If the current trend continues, where will price be next?" This makes it inherently leading rather than lagging, though the forecast degrades quickly beyond one step.
@@ -144,4 +142,4 @@ The O(1) running-sum algorithm is inherently serial due to data dependencies. Ba
- Tushar Chande, *The New Technical Trader*, 1994
- TA-Lib: `TA_TSF` function (www.ta-lib.org)
- PineScript: `ta.linreg(source, length, -1)` (offset=-1 = one step ahead)
- PineScript: `ta.linreg(source, length, -1)` (offset=-1 = one step ahead)
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [tukey_w_signature](tukey_w_signature.md) |
- TUKEY_W applies the Tukey (tapered cosine) window as FIR filter weights, offering a single parameter $\alpha$ that controls the fraction of the win...
- Parameterized by `period` (default 20), `alpha` (default 0.5).
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [HanMA](../hanma/hanma.md), [BWMA](../bwma/Bwma.md) | **Trading note:** Tukey-window MA; adjustable taper parameter between rectangular and Hann.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
TUKEY_W applies the Tukey (tapered cosine) window as FIR filter weights, offering a single parameter $\alpha$ that controls the fraction of the window that is cosine-tapered. At $\alpha = 0$, the window is rectangular (SMA). At $\alpha = 1$, it becomes the Hann window. The default $\alpha = 0.5$ tapers 25% at each edge while keeping the central 50% flat at unity, combining the passband efficiency of the rectangular window with the sidelobe suppression of cosine tapering. This makes Tukey the default "when in doubt" window in spectral analysis, and by extension, a sensible default for window-based moving averages.
@@ -134,4 +132,4 @@ O(N) per bar. For default N = 14: ~59 cycles. Non-negative quartic weights; no s
| Tukey symmetric window | Yes | Symmetric: fold to ⌈N/2⌉ unique weights |
| Cross-bar independence | Yes | 4 output bars per AVX2 pass |
Tukey biweight shares the same symmetric FIR structure as Kaiser and Parzen. Symmetric folding halves FMA count to ⌈N/2⌉. AVX2 batch throughput: ~N/8 cycles per bar.
Tukey biweight shares the same symmetric FIR structure as Kaiser and Parzen. Symmetric folding halves FMA count to ⌈N/2⌉. AVX2 batch throughput: ~N/8 cycles per bar.
+2 -4
View File
@@ -14,9 +14,7 @@
| **Signature** | [wma_signature](wma_signature.md) |
- The Weighted Moving Average (WMA) assigns a linearly decreasing weight to data points.
- Parameterized by `period`.
- Output range: Tracks input.
- Requires `period` bars of warmup before first valid output (IsHot = true).
- **Similar:** [FWMA](../fwma/fwma.md), [TRIMA](../trima/trima.md) | **Complementary:** WMA crossover systems | **Trading note:** Linearly weighted MA; recent prices get higher weight, faster response than SMA.
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
The Weighted Moving Average (WMA) assigns a linearly decreasing weight to data points. The most recent price gets weight $N$, the one before it $N-1$, down to 1. This makes it more responsive to recent price changes than an SMA, but without the infinite tail of an EMA.
@@ -107,4 +105,4 @@ The batch path achieves near-linear scaling for large datasets.
| **TA-Lib** | ✅ | Matches `TA_WMA` exactly. |
| **Skender** | ✅ | Matches `GetWma` exactly. |
| **Tulip** | ✅ | Matches `wma` exactly. |
| **Ooples** | ✅ | Matches `CalculateWeightedMovingAverage`. |
| **Ooples** | ✅ | Matches `CalculateWeightedMovingAverage`. |