Files
QuanTAlib/lib/core/tseries.cs
T

147 lines
4.2 KiB
C#
Raw Normal View History

2025-11-25 20:40:46 -08:00
using System.Collections;
2024-11-03 16:24:08 -08:00
using System.Runtime.CompilerServices;
2025-11-25 20:40:46 -08:00
using System.Runtime.InteropServices;
2024-11-03 16:24:08 -08:00
namespace QuanTAlib;
2025-11-25 20:40:46 -08:00
/// <summary>
/// A high-performance time series implementation using Structure of Arrays (SoA) layout.
/// Stores Time (long) and Value (double) in separate contiguous arrays for SIMD efficiency.
/// Supports "New Bar" vs "Update Last" streaming semantics.
/// </summary>
public class TSeries : IReadOnlyList<TValue>
2024-11-03 16:24:08 -08:00
{
2025-11-25 20:40:46 -08:00
// Internal storage: SoA layout
// We use List<T> for dynamic sizing but access internal arrays via CollectionsMarshal for speed
protected readonly List<long> _t;
protected readonly List<double> _v;
2024-11-03 16:24:08 -08:00
2025-11-25 20:40:46 -08:00
public string Name { get; set; } = "Data";
2024-11-03 16:24:08 -08:00
2025-11-25 20:40:46 -08:00
// Event optimization: Use Action<TValue> to avoid EventArgs allocation
// Note: Events are generally discouraged in the hot path of this high-perf design,
// but kept for compatibility/chaining.
public event Action<TValue>? Pub;
2024-11-03 16:24:08 -08:00
2025-11-25 20:40:46 -08:00
public TSeries()
{
_t = new List<long>();
_v = new List<double>();
}
2024-11-03 16:24:08 -08:00
/// <summary>
2025-11-25 20:40:46 -08:00
/// Constructor with capacity hint to avoid List growth overhead.
2024-11-03 16:24:08 -08:00
/// </summary>
2025-11-25 20:40:46 -08:00
public TSeries(int capacity)
2024-11-03 16:24:08 -08:00
{
2025-11-25 20:40:46 -08:00
_t = new List<long>(capacity);
_v = new List<double>(capacity);
}
/// <summary>
/// Constructor for wrapping existing lists (e.g. from TBarSeries).
/// </summary>
public TSeries(List<long> time, List<double> values)
{
_t = time;
_v = values;
}
public int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _v.Count;
}
public TValue this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(_t[index], _v[index]);
}
public TValue Last
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _v.Count > 0 ? new(_t[^1], _v[^1]) : default;
}
public double LastValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _v.Count > 0 ? _v[^1] : double.NaN;
}
public long LastTime
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _t.Count > 0 ? _t[^1] : 0;
}
/// <summary>
/// Direct access to the underlying Value array as a Span for SIMD operations.
/// </summary>
public ReadOnlySpan<double> Values
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => CollectionsMarshal.AsSpan(_v);
}
/// <summary>
/// Direct access to the underlying Time array as a Span.
/// </summary>
public ReadOnlySpan<long> Times
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => CollectionsMarshal.AsSpan(_t);
2024-11-03 16:24:08 -08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2025-11-25 20:40:46 -08:00
public virtual void Add(TValue value, bool isNew)
2024-11-03 16:24:08 -08:00
{
2025-11-25 20:40:46 -08:00
if (isNew || _v.Count == 0)
2024-11-03 16:24:08 -08:00
{
2025-11-25 20:40:46 -08:00
_t.Add(value.Time);
_v.Add(value.Value);
2024-11-03 16:24:08 -08:00
}
else
{
2025-11-25 20:40:46 -08:00
// Update last bar
int lastIdx = _v.Count - 1;
_t[lastIdx] = value.Time;
_v[lastIdx] = value.Value;
2024-11-03 16:24:08 -08:00
}
2025-11-25 20:40:46 -08:00
Pub?.Invoke(value);
2024-11-03 16:24:08 -08:00
}
2025-11-25 20:40:46 -08:00
// Overload for backward compatibility (assumes isNew=true)
2024-11-03 16:24:08 -08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2025-11-25 20:40:46 -08:00
public virtual void Add(TValue value) => Add(value, true);
2024-11-03 16:24:08 -08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2025-11-25 20:40:46 -08:00
public void Add(long time, double value, bool isNew = true) => Add(new TValue(time, value), isNew);
2024-11-03 16:24:08 -08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2025-11-25 20:40:46 -08:00
public void Add(DateTime time, double value, bool isNew = true) => Add(new TValue(time.Ticks, value), isNew);
2024-11-03 16:24:08 -08:00
public void Add(IEnumerable<double> values)
{
2025-11-25 20:40:46 -08:00
long t = DateTime.UtcNow.Ticks;
foreach (var v in values)
2024-11-03 16:24:08 -08:00
{
2025-11-25 20:40:46 -08:00
Add(new TValue(t, v), isNew: true);
t += TimeSpan.TicksPerMinute; // Dummy time increment
2024-11-03 16:24:08 -08:00
}
}
2025-11-25 20:40:46 -08:00
// IEnumerable implementation
public IEnumerator<TValue> GetEnumerator()
2024-11-03 16:24:08 -08:00
{
2025-11-25 20:40:46 -08:00
for (int i = 0; i < _v.Count; i++)
2024-11-03 16:24:08 -08:00
{
2025-11-25 20:40:46 -08:00
yield return new TValue(_t[i], _v[i]);
2024-11-03 16:24:08 -08:00
}
}
2025-11-25 20:40:46 -08:00
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
2024-11-03 16:24:08 -08:00
}