updates from mac

This commit is contained in:
Miha Kralj
2025-11-28 13:35:16 -08:00
parent 74b49d2bb4
commit acac3e610c
55 changed files with 126278 additions and 126081 deletions
+49 -49
View File
@@ -1,49 +1,49 @@
using System;
using Xunit;
using QuanTAlib;
namespace QuanTAlib.Tests
{
public class TSeriesTests
{
[Fact]
public void Add_NewValue_IncreasesCount()
{
var series = new TSeries();
long time = DateTime.UtcNow.Ticks;
series.Add(time, 10.0, isNew: true);
Assert.Single(series);
Assert.Equal(10.0, series.Last.Value);
}
[Fact]
public void Add_UpdateValue_DoesNotIncreaseCount()
{
var series = new TSeries();
long time = DateTime.UtcNow.Ticks;
series.Add(time, 10.0, isNew: true);
series.Add(time, 11.0, isNew: false);
Assert.Single(series);
Assert.Equal(11.0, series.Last.Value);
}
[Fact]
public void Add_MultipleValues_MaintainsOrder()
{
var series = new TSeries();
long t0 = DateTime.UtcNow.Ticks;
long t1 = t0 + TimeSpan.TicksPerMinute;
series.Add(t0, 10.0, isNew: true);
series.Add(t1, 20.0, isNew: true);
Assert.Equal(2, series.Count);
Assert.Equal(10.0, series[0].Value);
Assert.Equal(20.0, series[1].Value);
}
}
}
using System;
using Xunit;
using QuanTAlib;
namespace QuanTAlib.Tests
{
public class TSeriesTests
{
[Fact]
public void Add_NewValue_IncreasesCount()
{
var series = new TSeries();
long time = DateTime.UtcNow.Ticks;
series.Add(time, 10.0, isNew: true);
Assert.Single(series);
Assert.Equal(10.0, series.Last.Value);
}
[Fact]
public void Add_UpdateValue_DoesNotIncreaseCount()
{
var series = new TSeries();
long time = DateTime.UtcNow.Ticks;
series.Add(time, 10.0, isNew: true);
series.Add(time, 11.0, isNew: false);
Assert.Single(series);
Assert.Equal(11.0, series.Last.Value);
}
[Fact]
public void Add_MultipleValues_MaintainsOrder()
{
var series = new TSeries();
long t0 = DateTime.UtcNow.Ticks;
long t1 = t0 + TimeSpan.TicksPerMinute;
series.Add(t0, 10.0, isNew: true);
series.Add(t1, 20.0, isNew: true);
Assert.Equal(2, series.Count);
Assert.Equal(10.0, series[0].Value);
Assert.Equal(20.0, series[1].Value);
}
}
}
+56 -56
View File
@@ -1,56 +1,56 @@
# TSeries: Time Series Data
## Overview
`TSeries` is a high-performance container for time-series data. Unlike a standard `List<TValue>`, it uses a **Structure of Arrays (SoA)** layout internally. This means it stores timestamps and values in separate contiguous arrays (`List<long>` and `List<double>`).
This layout is critical for performance because it allows:
1. **SIMD Optimization**: The `Values` property returns a `ReadOnlySpan<double>` that can be directly processed by CPU vector instructions (AVX/SSE).
2. **Cache Locality**: Iterating over values doesn't load timestamps into the CPU cache, and vice versa.
## Structure
```csharp
public class TSeries : IReadOnlyList<TValue>
{
// Internal SoA storage
protected readonly List<long> _t;
protected readonly List<double> _v;
// Public accessors
public ReadOnlySpan<double> Values => ...; // Zero-copy access
public ReadOnlySpan<long> Times => ...; // Zero-copy access
public TValue Last { get; }
public int Count { get; }
}
```
## Key Features
* **SoA Layout**: Optimized for numerical computing and SIMD.
* **Zero-Copy Access**: `Values` and `Times` properties expose internal storage as Spans without copying.
* **Streaming Support**: The `Add` method supports `isNew` parameter to handle intra-bar updates (replacing the last value instead of appending).
* **Event Publishing**: Optional `Pub` event for reactive pipelines.
## Usage
### Creating and Adding Data
```csharp
var series = new TSeries();
series.Add(DateTime.Now, 100.0); // isNew=true by default
```
### Streaming Updates
```csharp
// New bar
series.Add(time, 100.0, isNew: true);
// Update current bar (e.g. price change within same minute)
series.Add(time, 101.0, isNew: false);
```
### SIMD Processing
```csharp
// Calculate average using SIMD
double avg = series.Values.AverageSIMD();
# TSeries: Time Series Data
## Overview
`TSeries` is a high-performance container for time-series data. Unlike a standard `List<TValue>`, it uses a **Structure of Arrays (SoA)** layout internally. This means it stores timestamps and values in separate contiguous arrays (`List<long>` and `List<double>`).
This layout is critical for performance because it allows:
1. **SIMD Optimization**: The `Values` property returns a `ReadOnlySpan<double>` that can be directly processed by CPU vector instructions (AVX/SSE).
2. **Cache Locality**: Iterating over values doesn't load timestamps into the CPU cache, and vice versa.
## Structure
```csharp
public class TSeries : IReadOnlyList<TValue>
{
// Internal SoA storage
protected readonly List<long> _t;
protected readonly List<double> _v;
// Public accessors
public ReadOnlySpan<double> Values => ...; // Zero-copy access
public ReadOnlySpan<long> Times => ...; // Zero-copy access
public TValue Last { get; }
public int Count { get; }
}
```
## Key Features
* **SoA Layout**: Optimized for numerical computing and SIMD.
* **Zero-Copy Access**: `Values` and `Times` properties expose internal storage as Spans without copying.
* **Streaming Support**: The `Add` method supports `isNew` parameter to handle intra-bar updates (replacing the last value instead of appending).
* **Event Publishing**: Optional `Pub` event for reactive pipelines.
## Usage
### Creating and Adding Data
```csharp
var series = new TSeries();
series.Add(DateTime.Now, 100.0); // isNew=true by default
```
### Streaming Updates
```csharp
// New bar
series.Add(time, 100.0, isNew: true);
// Update current bar (e.g. price change within same minute)
series.Add(time, 101.0, isNew: false);
```
### SIMD Processing
```csharp
// Calculate average using SIMD
double avg = series.Values.AverageSIMD();
+146 -146
View File
@@ -1,146 +1,146 @@
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <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>
{
// 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;
public string Name { get; set; } = "Data";
// 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;
public TSeries()
{
_t = new List<long>();
_v = new List<double>();
}
/// <summary>
/// Constructor with capacity hint to avoid List growth overhead.
/// </summary>
public TSeries(int capacity)
{
_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);
}
[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
{
// Update last bar
int lastIdx = _v.Count - 1;
_t[lastIdx] = value.Time;
_v[lastIdx] = value.Value;
}
Pub?.Invoke(value);
}
// Overload for backward compatibility (assumes isNew=true)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void Add(TValue value) => Add(value, 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.Ticks, value), isNew);
public void Add(IEnumerable<double> values)
{
long t = DateTime.UtcNow.Ticks;
foreach (var v in values)
{
Add(new TValue(t, v), isNew: true);
t += TimeSpan.TicksPerMinute; // Dummy time increment
}
}
// IEnumerable implementation
public IEnumerator<TValue> GetEnumerator()
{
for (int i = 0; i < _v.Count; i++)
{
yield return new TValue(_t[i], _v[i]);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <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>
{
// 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;
public string Name { get; set; } = "Data";
// 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;
public TSeries()
{
_t = new List<long>();
_v = new List<double>();
}
/// <summary>
/// Constructor with capacity hint to avoid List growth overhead.
/// </summary>
public TSeries(int capacity)
{
_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);
}
[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
{
// Update last bar
int lastIdx = _v.Count - 1;
_t[lastIdx] = value.Time;
_v[lastIdx] = value.Value;
}
Pub?.Invoke(value);
}
// Overload for backward compatibility (assumes isNew=true)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void Add(TValue value) => Add(value, 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.Ticks, value), isNew);
public void Add(IEnumerable<double> values)
{
long t = DateTime.UtcNow.Ticks;
foreach (var v in values)
{
Add(new TValue(t, v), isNew: true);
t += TimeSpan.TicksPerMinute; // Dummy time increment
}
}
// IEnumerable implementation
public IEnumerator<TValue> GetEnumerator()
{
for (int i = 0; i < _v.Count; i++)
{
yield return new TValue(_t[i], _v[i]);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}