mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-16 17:48:05 +00:00
- Updated the Prime method signature in multiple indicators (Jma, Kama, Lsma, Mama, Mgdi, Pwma, Rma, Sma, Ssf, Super, T3, Tema, Trima, Usf, Vidya, Wma, Atr) to accept an optional TimeSpan parameter for improved flexibility. - Added unit tests for Lsma to verify Dispose functionality, ensuring proper unsubscription from the source and thread safety. - Enhanced Mama and Wma classes to handle non-finite inputs gracefully and added checks for valid parameters in constructors. - Introduced additional tests for T3 to validate constructor behavior with invalid volume factors. - Ensured all indicators maintain consistent behavior when handling edge cases, such as empty buffers and non-finite values.
164 lines
4.3 KiB
C#
164 lines
4.3 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace QuanTAlib;
|
|
|
|
/// <summary>
|
|
/// TRIMA: Triangular Moving Average
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// TRIMA applies triangular weighting to data points, emphasizing the middle of the window.
|
|
/// Equivalent to a double SMA: SMA(SMA(period1), period2).
|
|
///
|
|
/// Calculation:
|
|
/// p1 = (period + 1) / 2
|
|
/// p2 = period / 2 + 1
|
|
/// TRIMA = SMA(SMA(input, p1), p2)
|
|
///
|
|
/// O(1) update:
|
|
/// Uses two SMA instances, each with O(1) update complexity.
|
|
///
|
|
/// IsHot:
|
|
/// Becomes true when both internal SMAs are hot.
|
|
/// </remarks>
|
|
[SkipLocalsInit]
|
|
public sealed class Trima : AbstractBase, IDisposable
|
|
{
|
|
private readonly int _period;
|
|
private readonly Sma _sma1;
|
|
private readonly Sma _sma2;
|
|
private readonly TValuePublishedHandler _handler;
|
|
private ITValuePublisher? _publisher;
|
|
|
|
public Trima(int period)
|
|
{
|
|
if (period <= 0) throw new ArgumentException("Period must be greater than 0", nameof(period));
|
|
|
|
_period = period;
|
|
int p1 = (period + 1) / 2;
|
|
int p2 = period / 2 + 1;
|
|
|
|
_sma1 = new Sma(p1);
|
|
_sma2 = new Sma(p2);
|
|
_handler = Handle;
|
|
|
|
Name = $"Trima({period})";
|
|
WarmupPeriod = p1 + p2 - 1;
|
|
}
|
|
|
|
public Trima(ITValuePublisher source, int period) : this(period)
|
|
{
|
|
_publisher = source;
|
|
source.Pub += _handler;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_publisher != null)
|
|
{
|
|
_publisher.Pub -= _handler;
|
|
_publisher = null;
|
|
}
|
|
}
|
|
|
|
public override bool IsHot => _sma1.IsHot && _sma2.IsHot;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override TValue Update(TValue input, bool isNew = true)
|
|
{
|
|
TValue v1 = _sma1.Update(input, isNew);
|
|
TValue v2 = _sma2.Update(v1, isNew);
|
|
|
|
Last = v2;
|
|
PubEvent(Last, isNew);
|
|
return Last;
|
|
}
|
|
|
|
public override TSeries Update(TSeries source)
|
|
{
|
|
if (source.Count == 0) return [];
|
|
|
|
int len = source.Count;
|
|
var t = new List<long>(len);
|
|
var v = new List<double>(len);
|
|
CollectionsMarshal.SetCount(t, len);
|
|
CollectionsMarshal.SetCount(v, len);
|
|
|
|
var tSpan = CollectionsMarshal.AsSpan(t);
|
|
var vSpan = CollectionsMarshal.AsSpan(v);
|
|
|
|
Batch(source.Values, vSpan, _period);
|
|
source.Times.CopyTo(tSpan);
|
|
|
|
Prime(source.Values);
|
|
|
|
Last = new TValue(tSpan[len - 1], vSpan[len - 1]);
|
|
return new TSeries(t, v);
|
|
}
|
|
|
|
private void Handle(object? sender, TValueEventArgs args) => Update(args.Value, args.IsNew);
|
|
|
|
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
|
{
|
|
_sma1.Reset();
|
|
_sma2.Reset();
|
|
|
|
_sma1.Prime(source);
|
|
|
|
// Calculate intermediate SMA series to prime the second SMA
|
|
int p1 = (_period + 1) / 2;
|
|
double[] tempArray = ArrayPool<double>.Shared.Rent(source.Length);
|
|
Span<double> tempSpan = tempArray.AsSpan(0, source.Length);
|
|
|
|
try
|
|
{
|
|
Sma.Batch(source, tempSpan, p1);
|
|
_sma2.Prime(tempSpan);
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<double>.Shared.Return(tempArray);
|
|
}
|
|
}
|
|
|
|
public override void Reset()
|
|
{
|
|
_sma1.Reset();
|
|
_sma2.Reset();
|
|
Last = default;
|
|
}
|
|
|
|
public static TSeries Batch(TSeries source, int period)
|
|
{
|
|
var trima = new Trima(period);
|
|
return trima.Update(source);
|
|
}
|
|
|
|
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
|
{
|
|
if (source.Length != output.Length)
|
|
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
|
if (period <= 0)
|
|
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
|
|
|
int p1 = (period + 1) / 2;
|
|
int p2 = period / 2 + 1;
|
|
|
|
double[] tempArray = ArrayPool<double>.Shared.Rent(source.Length);
|
|
Span<double> tempSpan = tempArray.AsSpan(0, source.Length);
|
|
|
|
try
|
|
{
|
|
Sma.Batch(source, tempSpan, p1);
|
|
Sma.Batch(tempSpan, output, p2);
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<double>.Shared.Return(tempArray);
|
|
}
|
|
}
|
|
}
|