using System.Collections; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace QuanTAlib; /// /// Tag type indicating that a TSeries constructor should share storage with the provided lists /// rather than making defensive copies. Used internally for synchronized sub-series. /// public readonly struct ShareStorageTag { /// /// Singleton instance for the share storage tag. /// public static readonly ShareStorageTag Instance = default; } /// /// High-performance enumerator for TSeries. /// public struct TSeriesEnumerator : IEnumerator, IEquatable { private readonly List _t; private readonly List _v; private readonly int _count; private int _index; private TValue _current; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal TSeriesEnumerator(List t, List v) { _t = t; _v = v; _count = v.Count; _index = -1; _current = default; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { if (_index + 1 >= _count) { return false; } _index++; _current = new TValue(_t[_index], _v[_index]); return true; } public readonly TValue Current => _current; readonly object IEnumerator.Current => Current; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { _index = -1; _current = default; } public readonly void Dispose() { } public readonly bool Equals(TSeriesEnumerator other) => ReferenceEquals(_t, other._t) && ReferenceEquals(_v, other._v) && _count == other._count && _index == other._index; public override readonly bool Equals(object? obj) => obj is TSeriesEnumerator other && Equals(other); public override readonly int GetHashCode() => HashCode.Combine(RuntimeHelpers.GetHashCode(_t), RuntimeHelpers.GetHashCode(_v), _count, _index); public static bool operator ==(TSeriesEnumerator left, TSeriesEnumerator right) => left.Equals(right); public static bool operator !=(TSeriesEnumerator left, TSeriesEnumerator right) => !left.Equals(right); } /// /// 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. /// public class TSeries : IReadOnlyList, ITValuePublisher { #pragma warning disable MA0016 // Prefer using collection abstraction instead of implementation protected readonly List _t; protected readonly List _v; #pragma warning restore MA0016 public string Name { get; set; } = "Data"; public event TValuePublishedHandler? Pub; public TSeries() : this(0) { } public TSeries(int capacity) { _t = new List(capacity); _v = new List(capacity); } public TSeries(IReadOnlyList time, IReadOnlyList values) { // Always make defensive copies to prevent external mutation of internal state _t = [.. time]; _v = [.. values]; } /// /// Internal constructor for creating views that share underlying storage. /// Used by TBarSeries to create synchronized OHLCV sub-series. /// /// The time list to share (not copied). /// The values list to share (not copied). /// Tag type indicating intentional storage sharing. internal TSeries(List time, List values, ShareStorageTag _) { // Direct assignment for intentional storage sharing (internal use only) _t = time; _v = values; } public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _v.Count; } public TValue this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new TValue(_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; } /// /// Direct access to the underlying Value array as a Span for SIMD operations. /// /// /// Lifetime: The returned span is only valid while no structural mutations /// (Add, Clear, etc.) occur on this TSeries. Structural changes invalidate the span. /// Mutation: The span reflects the internal List storage; modifications /// to the series after obtaining the span may cause undefined behavior if the span is still in use. /// public ReadOnlySpan Values { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => CollectionsMarshal.AsSpan(_v); } /// /// Direct access to the underlying Time array as a Span. /// /// /// Lifetime: The returned span is only valid while no structural mutations /// (Add, Clear, etc.) occur on this TSeries. Structural changes invalidate the span. /// Mutation: The span reflects the internal List storage; modifications /// to the series after obtaining the span may cause undefined behavior if the span is still in use. /// public ReadOnlySpan Times { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => CollectionsMarshal.AsSpan(_t); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public virtual void Add(TValue value, bool isNew) { if (isNew || _v.Count == 0) { _t.Add(value.Time); _v.Add(value.Value); } else { int lastIdx = _v.Count - 1; _t[lastIdx] = value.Time; _v[lastIdx] = value.Value; } Pub?.Invoke(this, new TValueEventArgs { Value = value, IsNew = isNew }); } // Overload for backward compatibility (assumes isNew=true) [MethodImpl(MethodImplOptions.AggressiveInlining)] public virtual void Add(TValue value) => Add(value, isNew: true); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(long time, double value, bool isNew = true) => Add(new TValue(time, value), isNew); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(DateTime time, double value, bool isNew = true) => Add(new TValue(time, value), isNew); /// /// Adds a sequence of raw values with fabricated timestamps. /// /// /// Synthetic timestamps: Each element receives a fabricated timestamp starting at /// (captured once at call time) and incrementing by one minute /// per item. These timestamps do not reflect real market time. Use the typed overloads /// (, ) when real timestamps matter. /// public void Add(IEnumerable values) { long t = DateTime.UtcNow.Ticks; foreach (var v in values) { Add(new TValue(t, v), isNew: true); t += TimeSpan.TicksPerMinute; } } // IEnumerable implementation with struct enumerator for zero-allocation iteration [MethodImpl(MethodImplOptions.AggressiveInlining)] public TSeriesEnumerator GetEnumerator() => new(_t, _v); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }