mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 17:18:05 +00:00
updates from mac
This commit is contained in:
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user