mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-19 19:18:05 +00:00
Refactor code formatting and improve consistency across various test files
- Removed unnecessary blank lines in multiple test files to enhance readability. - Ensured consistent spacing and formatting in the `Trima`, `Usf`, `Vidya`, `Wma`, and `Atr` test classes. - Updated comments for clarity and consistency in the `Atr` and `Adl` classes. - Adjusted project files for better structure and maintainability.
This commit is contained in:
@@ -113,7 +113,7 @@ public class SimdExtensionsTests
|
||||
double[] data = new double[1000];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] = i + 1.0;
|
||||
|
||||
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
double expected = 1000.0 * 1001.0 / 2.0;
|
||||
Assert.Equal(expected, span.SumSIMD(), precision: 8);
|
||||
@@ -324,7 +324,7 @@ public class SimdExtensionsTests
|
||||
{
|
||||
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
|
||||
|
||||
double variance = span.VarianceSIMD();
|
||||
Assert.True(Math.Abs(variance - 4.571428) < 0.0001);
|
||||
}
|
||||
@@ -334,10 +334,10 @@ public class SimdExtensionsTests
|
||||
{
|
||||
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
|
||||
|
||||
double mean = 5.0;
|
||||
double variance = span.VarianceSIMD(mean);
|
||||
|
||||
|
||||
Assert.True(variance > 0);
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ public class SimdExtensionsTests
|
||||
{
|
||||
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
|
||||
|
||||
double stdDev = span.StdDevSIMD();
|
||||
Assert.True(Math.Abs(stdDev - 2.138) < 0.01);
|
||||
}
|
||||
@@ -389,7 +389,7 @@ public class SimdExtensionsTests
|
||||
{
|
||||
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
|
||||
|
||||
double stdDev = span.StdDevSIMD(5.0);
|
||||
Assert.True(stdDev > 0);
|
||||
}
|
||||
@@ -556,14 +556,14 @@ public class SimdExtensionsTests
|
||||
public void SIMD_WorksWithTSeriesValues()
|
||||
{
|
||||
var series = new TSeries(100);
|
||||
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow.Ticks + i, i + 1.0);
|
||||
}
|
||||
|
||||
var values = series.Values;
|
||||
|
||||
|
||||
double sum = values.SumSIMD();
|
||||
double avg = values.AverageSIMD();
|
||||
double min = values.MinSIMD();
|
||||
@@ -587,7 +587,7 @@ public class SimdExtensionsTests
|
||||
var bars = gbm.Fetch(1000, startTime, interval);
|
||||
|
||||
var closeValues = bars.Close.Values;
|
||||
|
||||
|
||||
double sum = closeValues.SumSIMD();
|
||||
double avg = closeValues.AverageSIMD();
|
||||
double min = closeValues.MinSIMD();
|
||||
@@ -611,7 +611,7 @@ public class SimdExtensionsTests
|
||||
_ = closeValues.SumSIMD();
|
||||
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
|
||||
double sum = closeValues.SumSIMD();
|
||||
double avg = closeValues.AverageSIMD();
|
||||
double min = closeValues.MinSIMD();
|
||||
@@ -619,7 +619,7 @@ public class SimdExtensionsTests
|
||||
var (minAlt, maxAlt) = closeValues.MinMaxSIMD();
|
||||
double variance = closeValues.VarianceSIMD();
|
||||
double stdDev = closeValues.StdDevSIMD();
|
||||
|
||||
|
||||
sw.Stop();
|
||||
|
||||
Assert.True(sum > 0);
|
||||
@@ -630,8 +630,8 @@ public class SimdExtensionsTests
|
||||
Assert.Equal(max, maxAlt);
|
||||
Assert.True(variance > 0);
|
||||
Assert.True(stdDev > 0);
|
||||
|
||||
Assert.True(sw.ElapsedMilliseconds < 50,
|
||||
|
||||
Assert.True(sw.ElapsedMilliseconds < 50,
|
||||
$"SIMD operations took {sw.ElapsedMilliseconds}ms, expected < 50ms");
|
||||
}
|
||||
|
||||
@@ -640,12 +640,12 @@ public class SimdExtensionsTests
|
||||
{
|
||||
double[] data = [1.0, 2.0, 3.0];
|
||||
var span = new ReadOnlySpan<double>(data);
|
||||
|
||||
|
||||
Assert.Equal(6.0, span.SumSIMD());
|
||||
Assert.Equal(1.0, span.MinSIMD());
|
||||
Assert.Equal(3.0, span.MaxSIMD());
|
||||
Assert.Equal(2.0, span.AverageSIMD());
|
||||
|
||||
|
||||
var (min, max) = span.MinMaxSIMD();
|
||||
Assert.Equal(1.0, min);
|
||||
Assert.Equal(3.0, max);
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
# SimdExtensions Class
|
||||
|
||||
`SimdExtensions` provides high-performance, SIMD-accelerated extension methods for `ReadOnlySpan<double>`. It leverages .NET's `Vector<T>` to achieve 4-8x speedups on supported hardware (AVX2, AVX-512) while automatically falling back to scalar implementations on older hardware.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Hardware Acceleration**: Uses CPU vector registers to process multiple elements in parallel.
|
||||
- **Automatic Fallback**: Gracefully handles non-SIMD hardware or small arrays.
|
||||
- **Zero-Allocation**: Operates directly on spans without creating new arrays.
|
||||
- **Aggressive Inlining**: Methods are marked for inlining to minimize call overhead.
|
||||
|
||||
## Available Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `ContainsNonFinite()` | Checks if span contains any non-finite values (NaN or Infinity). |
|
||||
| `SumSIMD()` | Calculates the sum of elements. |
|
||||
| `MinSIMD()` | Finds the minimum value. |
|
||||
| `MaxSIMD()` | Finds the maximum value. |
|
||||
| `MinMaxSIMD()` | Finds both min and max in a single pass (more efficient than separate calls). |
|
||||
| `AverageSIMD()` | Calculates the arithmetic mean. |
|
||||
| `VarianceSIMD()` | Calculates the sample variance. |
|
||||
| `StdDevSIMD()` | Calculates the sample standard deviation. |
|
||||
| `DotProduct()` | Calculates the dot product of two spans. |
|
||||
|
||||
## Performance
|
||||
|
||||
On modern CPUs (e.g., Intel Core i7/i9, AMD Ryzen), these methods typically outperform standard LINQ or scalar loops by a factor of 4 to 8 for large arrays.
|
||||
|
||||
## Usage
|
||||
|
||||
```csharp
|
||||
using QuanTAlib;
|
||||
|
||||
double[] data = { 1.0, 2.0, 3.0, 4.0, 5.0, ... };
|
||||
ReadOnlySpan<double> span = data;
|
||||
|
||||
// Calculate sum
|
||||
double sum = span.SumSIMD();
|
||||
|
||||
// Calculate min and max in one pass
|
||||
var (min, max) = span.MinMaxSIMD();
|
||||
|
||||
// Calculate standard deviation
|
||||
double stdDev = span.StdDevSIMD();
|
||||
|
||||
// Check for valid data
|
||||
bool hasInvalid = span.ContainsNonFinite();
|
||||
|
||||
// Calculate dot product
|
||||
double dot = span.DotProduct(otherSpan);
|
||||
```
|
||||
# SimdExtensions Class
|
||||
|
||||
`SimdExtensions` provides high-performance, SIMD-accelerated extension methods for `ReadOnlySpan<double>`. It leverages .NET's `Vector<T>` to achieve 4-8x speedups on supported hardware (AVX2, AVX-512) while automatically falling back to scalar implementations on older hardware.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Hardware Acceleration**: Uses CPU vector registers to process multiple elements in parallel.
|
||||
- **Automatic Fallback**: Gracefully handles non-SIMD hardware or small arrays.
|
||||
- **Zero-Allocation**: Operates directly on spans without creating new arrays.
|
||||
- **Aggressive Inlining**: Methods are marked for inlining to minimize call overhead.
|
||||
|
||||
## Available Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `ContainsNonFinite()` | Checks if span contains any non-finite values (NaN or Infinity). |
|
||||
| `SumSIMD()` | Calculates the sum of elements. |
|
||||
| `MinSIMD()` | Finds the minimum value. |
|
||||
| `MaxSIMD()` | Finds the maximum value. |
|
||||
| `MinMaxSIMD()` | Finds both min and max in a single pass (more efficient than separate calls). |
|
||||
| `AverageSIMD()` | Calculates the arithmetic mean. |
|
||||
| `VarianceSIMD()` | Calculates the sample variance. |
|
||||
| `StdDevSIMD()` | Calculates the sample standard deviation. |
|
||||
| `DotProduct()` | Calculates the dot product of two spans. |
|
||||
|
||||
## Performance
|
||||
|
||||
On modern CPUs (e.g., Intel Core i7/i9, AMD Ryzen), these methods typically outperform standard LINQ or scalar loops by a factor of 4 to 8 for large arrays.
|
||||
|
||||
## Usage
|
||||
|
||||
```csharp
|
||||
using QuanTAlib;
|
||||
|
||||
double[] data = { 1.0, 2.0, 3.0, 4.0, 5.0, ... };
|
||||
ReadOnlySpan<double> span = data;
|
||||
|
||||
// Calculate sum
|
||||
double sum = span.SumSIMD();
|
||||
|
||||
// Calculate min and max in one pass
|
||||
var (min, max) = span.MinMaxSIMD();
|
||||
|
||||
// Calculate standard deviation
|
||||
double stdDev = span.StdDevSIMD();
|
||||
|
||||
// Check for valid data
|
||||
bool hasInvalid = span.ContainsNonFinite();
|
||||
|
||||
// Calculate dot product
|
||||
double dot = span.DotProduct(otherSpan);
|
||||
```
|
||||
|
||||
@@ -168,9 +168,9 @@ public class TBarTests
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
var bar = new TBar(time, 100, 110, 90, 105, 1000);
|
||||
|
||||
|
||||
TValue tv = bar;
|
||||
|
||||
|
||||
Assert.Equal(time, tv.Time);
|
||||
Assert.Equal(105.0, tv.Value);
|
||||
}
|
||||
|
||||
+112
-112
@@ -1,112 +1,112 @@
|
||||
# TBar: OHLCV Bar Struct
|
||||
|
||||
## What It Does
|
||||
|
||||
`TBar` is a lightweight, immutable struct representing a single OHLCV (Open, High, Low, Close, Volume) bar. It serves as the fundamental unit for price data in QuanTAlib, designed to hold market data with minimal memory overhead while providing convenient accessors for common price derivations.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Financial data processing often involves millions of bars. Storing these as classes would create massive GC pressure and memory fragmentation. `TBar` is designed as a **pure data struct** to ensure:
|
||||
|
||||
* **Compactness**: Occupies exactly 48 bytes (1 `long` + 5 `double`s), fitting efficiently in memory.
|
||||
* **Immutability**: Thread-safe by default; values cannot change once created.
|
||||
* **Zero-Cost Abstractions**: Computed properties (like `HL2`) are calculated on-demand, requiring no extra storage.
|
||||
|
||||
## How It Works
|
||||
|
||||
`TBar` is a `readonly record struct` that stores:
|
||||
|
||||
* **Time**: Timestamp in ticks.
|
||||
* **Open, High, Low, Close**: Price components.
|
||||
* **Volume**: Traded volume.
|
||||
|
||||
It includes implicit conversions to `double` (defaulting to Close price) and `TValue` (Time + Close), allowing it to be used interchangeably with simpler types in many contexts.
|
||||
|
||||
## Structure
|
||||
|
||||
### Definition
|
||||
|
||||
```csharp
|
||||
public readonly record struct TBar(long Time, double Open, double High, double Low, double Close, double Volume);
|
||||
```
|
||||
|
||||
### Core Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Time` | `long` | Timestamp in ticks (UTC). |
|
||||
| `Open` | `double` | Opening price. |
|
||||
| `High` | `double` | Highest price. |
|
||||
| `Low` | `double` | Lowest price. |
|
||||
| `Close` | `double` | Closing price. |
|
||||
| `Volume` | `double` | Traded volume. |
|
||||
|
||||
### Computed Properties (Zero-Storage)
|
||||
|
||||
| Property | Formula | Description |
|
||||
|----------|---------|-------------|
|
||||
| `HL2` | `(H + L) / 2` | Median Price. |
|
||||
| `OC2` | `(O + C) / 2` | Midpoint Price. |
|
||||
| `OHL3` | `(O + H + L) / 3` | Typical Price (Variant). |
|
||||
| `HLC3` | `(H + L + C) / 3` | Typical Price. |
|
||||
| `OHLC4` | `(O + H + L + C) / 4` | Weighted Close. |
|
||||
| `HLCC4` | `(H + L + 2C) / 4` | Weighted Close (Variant). |
|
||||
|
||||
### TValue Accessors
|
||||
|
||||
Efficiently extracts components as `TValue` pairs:
|
||||
|
||||
* `O`, `H`, `L`, `C`, `V`
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating a Bar
|
||||
|
||||
```csharp
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
|
||||
```
|
||||
|
||||
### Implicit Conversions
|
||||
|
||||
```csharp
|
||||
TBar bar = ...;
|
||||
|
||||
// Treat as double (uses Close price)
|
||||
double price = bar;
|
||||
|
||||
// Treat as TValue (Time + Close)
|
||||
TValue tv = bar;
|
||||
|
||||
// Treat as DateTime
|
||||
DateTime dt = bar;
|
||||
```
|
||||
|
||||
### Using Computed Properties
|
||||
|
||||
```csharp
|
||||
// Calculate Typical Price on the fly
|
||||
double typical = bar.HLC3;
|
||||
```
|
||||
|
||||
## Performance Profile
|
||||
|
||||
* **Memory**: 48 bytes per instance.
|
||||
* **Allocation**: 0 bytes (Stack allocated).
|
||||
* **Access**: Direct field access (no property overhead).
|
||||
|
||||
## Integration
|
||||
|
||||
`TBar` is the primary input for:
|
||||
|
||||
* **TBarSeries**: A collection of bars.
|
||||
* **Indicators**: Some indicators (like ATR) require full `TBar` input rather than just a single value.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
* **SkipLocalsInit**: Marked with `[SkipLocalsInit]` for performance in tight loops.
|
||||
* **AggressiveInlining**: All computed properties are inlined to ensure they are as fast as writing the formula manually.
|
||||
|
||||
## References
|
||||
|
||||
* [OHLC Chart](https://en.wikipedia.org/wiki/Open-high-low-close_chart)
|
||||
* [C# Record Structs](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record)
|
||||
# TBar: OHLCV Bar Struct
|
||||
|
||||
## What It Does
|
||||
|
||||
`TBar` is a lightweight, immutable struct representing a single OHLCV (Open, High, Low, Close, Volume) bar. It serves as the fundamental unit for price data in QuanTAlib, designed to hold market data with minimal memory overhead while providing convenient accessors for common price derivations.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Financial data processing often involves millions of bars. Storing these as classes would create massive GC pressure and memory fragmentation. `TBar` is designed as a **pure data struct** to ensure:
|
||||
|
||||
* **Compactness**: Occupies exactly 48 bytes (1 `long` + 5 `double`s), fitting efficiently in memory.
|
||||
* **Immutability**: Thread-safe by default; values cannot change once created.
|
||||
* **Zero-Cost Abstractions**: Computed properties (like `HL2`) are calculated on-demand, requiring no extra storage.
|
||||
|
||||
## How It Works
|
||||
|
||||
`TBar` is a `readonly record struct` that stores:
|
||||
|
||||
* **Time**: Timestamp in ticks.
|
||||
* **Open, High, Low, Close**: Price components.
|
||||
* **Volume**: Traded volume.
|
||||
|
||||
It includes implicit conversions to `double` (defaulting to Close price) and `TValue` (Time + Close), allowing it to be used interchangeably with simpler types in many contexts.
|
||||
|
||||
## Structure
|
||||
|
||||
### Definition
|
||||
|
||||
```csharp
|
||||
public readonly record struct TBar(long Time, double Open, double High, double Low, double Close, double Volume);
|
||||
```
|
||||
|
||||
### Core Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Time` | `long` | Timestamp in ticks (UTC). |
|
||||
| `Open` | `double` | Opening price. |
|
||||
| `High` | `double` | Highest price. |
|
||||
| `Low` | `double` | Lowest price. |
|
||||
| `Close` | `double` | Closing price. |
|
||||
| `Volume` | `double` | Traded volume. |
|
||||
|
||||
### Computed Properties (Zero-Storage)
|
||||
|
||||
| Property | Formula | Description |
|
||||
|----------|---------|-------------|
|
||||
| `HL2` | `(H + L) / 2` | Median Price. |
|
||||
| `OC2` | `(O + C) / 2` | Midpoint Price. |
|
||||
| `OHL3` | `(O + H + L) / 3` | Typical Price (Variant). |
|
||||
| `HLC3` | `(H + L + C) / 3` | Typical Price. |
|
||||
| `OHLC4` | `(O + H + L + C) / 4` | Weighted Close. |
|
||||
| `HLCC4` | `(H + L + 2C) / 4` | Weighted Close (Variant). |
|
||||
|
||||
### TValue Accessors
|
||||
|
||||
Efficiently extracts components as `TValue` pairs:
|
||||
|
||||
* `O`, `H`, `L`, `C`, `V`
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating a Bar
|
||||
|
||||
```csharp
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000);
|
||||
```
|
||||
|
||||
### Implicit Conversions
|
||||
|
||||
```csharp
|
||||
TBar bar = ...;
|
||||
|
||||
// Treat as double (uses Close price)
|
||||
double price = bar;
|
||||
|
||||
// Treat as TValue (Time + Close)
|
||||
TValue tv = bar;
|
||||
|
||||
// Treat as DateTime
|
||||
DateTime dt = bar;
|
||||
```
|
||||
|
||||
### Using Computed Properties
|
||||
|
||||
```csharp
|
||||
// Calculate Typical Price on the fly
|
||||
double typical = bar.HLC3;
|
||||
```
|
||||
|
||||
## Performance Profile
|
||||
|
||||
* **Memory**: 48 bytes per instance.
|
||||
* **Allocation**: 0 bytes (Stack allocated).
|
||||
* **Access**: Direct field access (no property overhead).
|
||||
|
||||
## Integration
|
||||
|
||||
`TBar` is the primary input for:
|
||||
|
||||
* **TBarSeries**: A collection of bars.
|
||||
* **Indicators**: Some indicators (like ATR) require full `TBar` input rather than just a single value.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
* **SkipLocalsInit**: Marked with `[SkipLocalsInit]` for performance in tight loops.
|
||||
* **AggressiveInlining**: All computed properties are inlined to ensure they are as fast as writing the formula manually.
|
||||
|
||||
## References
|
||||
|
||||
* [OHLC Chart](https://en.wikipedia.org/wiki/Open-high-low-close_chart)
|
||||
* [C# Record Structs](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record)
|
||||
|
||||
@@ -40,9 +40,9 @@ public class TBarSeriesTests
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000);
|
||||
|
||||
|
||||
series.Add(bar, isNew: true);
|
||||
|
||||
|
||||
Assert.Single(series);
|
||||
Assert.Equal(105.0, series.Last.Close);
|
||||
}
|
||||
@@ -54,10 +54,10 @@ public class TBarSeriesTests
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
var bar1 = new TBar(time, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(time, 100, 112, 90, 108, 1200);
|
||||
|
||||
|
||||
series.Add(bar1, isNew: true);
|
||||
series.Add(bar2, isNew: false);
|
||||
|
||||
|
||||
Assert.Single(series);
|
||||
Assert.Equal(108.0, series.Last.Close);
|
||||
Assert.Equal(112.0, series.Last.High);
|
||||
@@ -68,9 +68,9 @@ public class TBarSeriesTests
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000);
|
||||
|
||||
|
||||
series.Add(bar, isNew: false);
|
||||
|
||||
|
||||
Assert.Single(series);
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ public class TBarSeriesTests
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
|
||||
|
||||
series.Add(time, 100, 110, 90, 105, 1000, isNew: true);
|
||||
|
||||
|
||||
Assert.Single(series);
|
||||
Assert.Equal(time, series.Last.Time);
|
||||
}
|
||||
@@ -91,9 +91,9 @@ public class TBarSeriesTests
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var dt = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc);
|
||||
|
||||
|
||||
series.Add(dt, 100, 110, 90, 105, 1000, isNew: true);
|
||||
|
||||
|
||||
Assert.Single(series);
|
||||
Assert.Equal(dt.Ticks, series.Last.Time);
|
||||
}
|
||||
@@ -108,9 +108,9 @@ public class TBarSeriesTests
|
||||
var lows = new double[] { 5, 15, 25 };
|
||||
var closes = new double[] { 12, 22, 32 };
|
||||
var volumes = new double[] { 100, 200, 300 };
|
||||
|
||||
|
||||
series.Add(times, opens, highs, lows, closes, volumes);
|
||||
|
||||
|
||||
Assert.Equal(3, series.Count);
|
||||
Assert.Equal(10, series[0].Open);
|
||||
Assert.Equal(32, series[2].Close);
|
||||
@@ -121,15 +121,15 @@ public class TBarSeriesTests
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000);
|
||||
|
||||
|
||||
series.Add(bar, isNew: true);
|
||||
|
||||
|
||||
Assert.Single(series.Open);
|
||||
Assert.Single(series.High);
|
||||
Assert.Single(series.Low);
|
||||
Assert.Single(series.Close);
|
||||
Assert.Single(series.Volume);
|
||||
|
||||
|
||||
Assert.Equal(100.0, series.Open.Last.Value);
|
||||
Assert.Equal(110.0, series.High.Last.Value);
|
||||
Assert.Equal(90.0, series.Low.Last.Value);
|
||||
@@ -143,7 +143,7 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
var bar = new TBar(DateTime.UtcNow.Ticks, 100, 110, 90, 105, 1000);
|
||||
series.Add(bar, isNew: true);
|
||||
|
||||
|
||||
Assert.Same(series.Open, series.O);
|
||||
Assert.Same(series.High, series.H);
|
||||
Assert.Same(series.Low, series.L);
|
||||
@@ -155,7 +155,7 @@ public class TBarSeriesTests
|
||||
public void SubSeries_HaveCorrectNames()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
|
||||
|
||||
Assert.Equal("Open", series.Open.Name);
|
||||
Assert.Equal("High", series.High.Name);
|
||||
Assert.Equal("Low", series.Low.Name);
|
||||
@@ -167,9 +167,9 @@ public class TBarSeriesTests
|
||||
public void Last_EmptySeries_ReturnsDefault()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
|
||||
|
||||
var last = series.Last;
|
||||
|
||||
|
||||
Assert.Equal(0, last.Time);
|
||||
Assert.Equal(0.0, last.Open);
|
||||
Assert.Equal(0.0, last.Close);
|
||||
@@ -181,9 +181,9 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
|
||||
var last = series.Last;
|
||||
|
||||
|
||||
Assert.Equal(200, last.Time);
|
||||
Assert.Equal(22.0, last.Close);
|
||||
}
|
||||
@@ -201,7 +201,7 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
|
||||
Assert.Equal(200, series.LastTime);
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
|
||||
Assert.Equal(20.0, series.LastOpen);
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
|
||||
Assert.Equal(25.0, series.LastHigh);
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
|
||||
Assert.Equal(15.0, series.LastLow);
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
|
||||
Assert.Equal(22.0, series.LastClose);
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
|
||||
Assert.Equal(200.0, series.LastVolume);
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ public class TBarSeriesTests
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
series.Add(300, 30, 35, 25, 32, 300);
|
||||
|
||||
|
||||
Assert.Equal(100, series[0].Time);
|
||||
Assert.Equal(10.0, series[0].Open);
|
||||
Assert.Equal(200, series[1].Time);
|
||||
@@ -310,12 +310,12 @@ public class TBarSeriesTests
|
||||
public void Count_ReturnsCorrectValue()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
|
||||
|
||||
Assert.Empty(series);
|
||||
|
||||
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
Assert.Single(series);
|
||||
|
||||
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
Assert.Equal(2, series.Count);
|
||||
}
|
||||
@@ -327,9 +327,9 @@ public class TBarSeriesTests
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
series.Add(300, 30, 35, 25, 32, 300);
|
||||
|
||||
|
||||
var list = series.ToList();
|
||||
|
||||
|
||||
Assert.Equal(3, list.Count);
|
||||
Assert.Equal(10.0, list[0].Open);
|
||||
Assert.Equal(22.0, list[1].Close);
|
||||
@@ -349,7 +349,7 @@ public class TBarSeriesTests
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
|
||||
Assert.Equal(2, list.Count);
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ public class TBarSeriesTests
|
||||
|
||||
var barToAdd = new TBar(100, 10, 15, 5, 12, 100);
|
||||
series.Add(barToAdd, isNew: true);
|
||||
|
||||
|
||||
Assert.NotNull(received);
|
||||
Assert.Equal(100, received.Value.Time);
|
||||
Assert.Equal(12.0, received.Value.Close);
|
||||
@@ -377,7 +377,7 @@ public class TBarSeriesTests
|
||||
series.Pub += (object? sender, in TBarEventArgs args) => received = args.Value;
|
||||
|
||||
series.Add(100, 10, 18, 5, 15, 150, isNew: false);
|
||||
|
||||
|
||||
Assert.NotNull(received);
|
||||
Assert.Equal(15.0, received.Value.Close);
|
||||
Assert.Equal(18.0, received.Value.High);
|
||||
@@ -389,7 +389,7 @@ public class TBarSeriesTests
|
||||
var series = new TBarSeries();
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
|
||||
|
||||
Assert.Equal(series.Open.Times[0], series.Close.Times[0]);
|
||||
Assert.Equal(series.High.Times[1], series.Volume.Times[1]);
|
||||
}
|
||||
@@ -398,11 +398,11 @@ public class TBarSeriesTests
|
||||
public void Add_MultipleBars_MaintainsOrder()
|
||||
{
|
||||
var series = new TBarSeries();
|
||||
|
||||
|
||||
series.Add(100, 10, 15, 5, 12, 100);
|
||||
series.Add(200, 20, 25, 15, 22, 200);
|
||||
series.Add(300, 30, 35, 25, 32, 300);
|
||||
|
||||
|
||||
Assert.Equal(3, series.Count);
|
||||
Assert.Equal(100, series[0].Time);
|
||||
Assert.Equal(200, series[1].Time);
|
||||
|
||||
+121
-121
@@ -1,121 +1,121 @@
|
||||
# TBarSeries: OHLCV Data Container
|
||||
|
||||
## What It Does
|
||||
|
||||
`TBarSeries` is a high-performance collection of OHLCV bars. It is the primary data structure for managing historical and real-time market data in QuanTAlib. It uses a **Structure of Arrays (SoA)** layout to optimize memory access and enable efficient SIMD operations across individual price components.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
A naive implementation of a bar series would be a `List<TBar>`. However, this is inefficient for technical analysis. Most indicators only need one component at a time (e.g., SMA uses Close prices). Iterating over a `List<TBar>` to get Close prices loads unnecessary Open, High, Low, and Volume data into the CPU cache, wasting bandwidth.
|
||||
|
||||
`TBarSeries` solves this by storing each component in its own contiguous array. This allows:
|
||||
|
||||
* **Component Views**: You can access `Close` prices as a `TSeries` without copying data.
|
||||
* **Cache Efficiency**: Iterating over `Close` prices loads *only* Close prices.
|
||||
* **Unified Time**: All component series share a single Time array, ensuring synchronization.
|
||||
|
||||
## How It Works
|
||||
|
||||
Internally, `TBarSeries` maintains six parallel lists:
|
||||
|
||||
1. `_t` (Time)
|
||||
2. `_o` (Open)
|
||||
3. `_h` (High)
|
||||
4. `_l` (Low)
|
||||
5. `_c` (Close)
|
||||
6. `_v` (Volume)
|
||||
|
||||
It exposes these internal lists as `TSeries` properties (`Open`, `High`, `Low`, `Close`, `Volume`), which act as read-only views into the master data.
|
||||
|
||||
## Structure
|
||||
|
||||
### Definition
|
||||
|
||||
```csharp
|
||||
public class TBarSeries : IReadOnlyList<TBar>
|
||||
{
|
||||
// Component Views (TSeries)
|
||||
public TSeries Open { get; }
|
||||
public TSeries High { get; }
|
||||
public TSeries Low { get; }
|
||||
public TSeries Close { get; }
|
||||
public TSeries Volume { get; }
|
||||
|
||||
// Aliases
|
||||
public TSeries O => Open;
|
||||
public TSeries H => High;
|
||||
public TSeries L => Low;
|
||||
public TSeries C => Close;
|
||||
public TSeries V => Volume;
|
||||
}
|
||||
```
|
||||
|
||||
### Core Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `Add(TBar bar, bool isNew)` | Adds a bar or updates the last one. |
|
||||
| `Add(DateTime time, double o, double h, double l, double c, double v)` | Adds raw values directly. |
|
||||
| `Count` | Returns the number of bars. |
|
||||
| `Last` | Returns the most recent `TBar`. |
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating and Populating
|
||||
|
||||
```csharp
|
||||
var bars = new TBarSeries();
|
||||
|
||||
// Add a new bar
|
||||
bars.Add(new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000));
|
||||
|
||||
// Add raw values
|
||||
bars.Add(DateTime.UtcNow, 100, 105, 95, 102, 1000);
|
||||
```
|
||||
|
||||
### Accessing Data
|
||||
|
||||
```csharp
|
||||
// Get the last full bar
|
||||
TBar lastBar = bars.Last;
|
||||
|
||||
// Get the Close series (Zero-Copy)
|
||||
TSeries closes = bars.Close;
|
||||
|
||||
// Calculate SMA on Close prices
|
||||
var sma = new Sma(14);
|
||||
var result = sma.Calculate(bars.Close);
|
||||
```
|
||||
|
||||
### Streaming Updates
|
||||
|
||||
```csharp
|
||||
// New minute starts
|
||||
bars.Add(newBar, isNew: true);
|
||||
|
||||
// Price updates within the same minute
|
||||
bars.Add(updatedBar, isNew: false); // Updates the last bar in place
|
||||
```
|
||||
|
||||
## Performance Profile
|
||||
|
||||
* **Memory Layout**: SoA (Structure of Arrays).
|
||||
* **Component Access**: Zero-copy `TSeries` views.
|
||||
* **Iteration**: Cache-friendly for single-component analysis.
|
||||
|
||||
## Integration
|
||||
|
||||
`TBarSeries` is the standard input for multi-input indicators (like ATR, ADX) and the primary data source for trading strategies.
|
||||
|
||||
* **Indicators**: Can be passed to indicators that require full bar data.
|
||||
* **Strategies**: Provides the historical context needed for signal generation.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
* **Shared Storage**: The `TSeries` views (`Open`, `Close`, etc.) do not own their data; they point to the internal lists of the `TBarSeries`. This means modifying the `TBarSeries` automatically updates all views.
|
||||
* **Synchronization**: Because all views share the same `_t` (Time) list, they are guaranteed to be perfectly synchronized.
|
||||
|
||||
## References
|
||||
|
||||
* [Structure of Arrays (SoA)](https://en.wikipedia.org/wiki/AOS_and_SOA)
|
||||
* [Data Locality](https://gameprogrammingpatterns.com/data-locality.html)
|
||||
# TBarSeries: OHLCV Data Container
|
||||
|
||||
## What It Does
|
||||
|
||||
`TBarSeries` is a high-performance collection of OHLCV bars. It is the primary data structure for managing historical and real-time market data in QuanTAlib. It uses a **Structure of Arrays (SoA)** layout to optimize memory access and enable efficient SIMD operations across individual price components.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
A naive implementation of a bar series would be a `List<TBar>`. However, this is inefficient for technical analysis. Most indicators only need one component at a time (e.g., SMA uses Close prices). Iterating over a `List<TBar>` to get Close prices loads unnecessary Open, High, Low, and Volume data into the CPU cache, wasting bandwidth.
|
||||
|
||||
`TBarSeries` solves this by storing each component in its own contiguous array. This allows:
|
||||
|
||||
* **Component Views**: You can access `Close` prices as a `TSeries` without copying data.
|
||||
* **Cache Efficiency**: Iterating over `Close` prices loads *only* Close prices.
|
||||
* **Unified Time**: All component series share a single Time array, ensuring synchronization.
|
||||
|
||||
## How It Works
|
||||
|
||||
Internally, `TBarSeries` maintains six parallel lists:
|
||||
|
||||
1. `_t` (Time)
|
||||
2. `_o` (Open)
|
||||
3. `_h` (High)
|
||||
4. `_l` (Low)
|
||||
5. `_c` (Close)
|
||||
6. `_v` (Volume)
|
||||
|
||||
It exposes these internal lists as `TSeries` properties (`Open`, `High`, `Low`, `Close`, `Volume`), which act as read-only views into the master data.
|
||||
|
||||
## Structure
|
||||
|
||||
### Definition
|
||||
|
||||
```csharp
|
||||
public class TBarSeries : IReadOnlyList<TBar>
|
||||
{
|
||||
// Component Views (TSeries)
|
||||
public TSeries Open { get; }
|
||||
public TSeries High { get; }
|
||||
public TSeries Low { get; }
|
||||
public TSeries Close { get; }
|
||||
public TSeries Volume { get; }
|
||||
|
||||
// Aliases
|
||||
public TSeries O => Open;
|
||||
public TSeries H => High;
|
||||
public TSeries L => Low;
|
||||
public TSeries C => Close;
|
||||
public TSeries V => Volume;
|
||||
}
|
||||
```
|
||||
|
||||
### Core Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `Add(TBar bar, bool isNew)` | Adds a bar or updates the last one. |
|
||||
| `Add(DateTime time, double o, double h, double l, double c, double v)` | Adds raw values directly. |
|
||||
| `Count` | Returns the number of bars. |
|
||||
| `Last` | Returns the most recent `TBar`. |
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating and Populating
|
||||
|
||||
```csharp
|
||||
var bars = new TBarSeries();
|
||||
|
||||
// Add a new bar
|
||||
bars.Add(new TBar(DateTime.UtcNow, 100, 105, 95, 102, 1000));
|
||||
|
||||
// Add raw values
|
||||
bars.Add(DateTime.UtcNow, 100, 105, 95, 102, 1000);
|
||||
```
|
||||
|
||||
### Accessing Data
|
||||
|
||||
```csharp
|
||||
// Get the last full bar
|
||||
TBar lastBar = bars.Last;
|
||||
|
||||
// Get the Close series (Zero-Copy)
|
||||
TSeries closes = bars.Close;
|
||||
|
||||
// Calculate SMA on Close prices
|
||||
var sma = new Sma(14);
|
||||
var result = sma.Calculate(bars.Close);
|
||||
```
|
||||
|
||||
### Streaming Updates
|
||||
|
||||
```csharp
|
||||
// New minute starts
|
||||
bars.Add(newBar, isNew: true);
|
||||
|
||||
// Price updates within the same minute
|
||||
bars.Add(updatedBar, isNew: false); // Updates the last bar in place
|
||||
```
|
||||
|
||||
## Performance Profile
|
||||
|
||||
* **Memory Layout**: SoA (Structure of Arrays).
|
||||
* **Component Access**: Zero-copy `TSeries` views.
|
||||
* **Iteration**: Cache-friendly for single-component analysis.
|
||||
|
||||
## Integration
|
||||
|
||||
`TBarSeries` is the standard input for multi-input indicators (like ATR, ADX) and the primary data source for trading strategies.
|
||||
|
||||
* **Indicators**: Can be passed to indicators that require full bar data.
|
||||
* **Strategies**: Provides the historical context needed for signal generation.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
* **Shared Storage**: The `TSeries` views (`Open`, `Close`, etc.) do not own their data; they point to the internal lists of the `TBarSeries`. This means modifying the `TBarSeries` automatically updates all views.
|
||||
* **Synchronization**: Because all views share the same `_t` (Time) list, they are guaranteed to be perfectly synchronized.
|
||||
|
||||
## References
|
||||
|
||||
* [Structure of Arrays (SoA)](https://en.wikipedia.org/wiki/AOS_and_SOA)
|
||||
* [Data Locality](https://gameprogrammingpatterns.com/data-locality.html)
|
||||
|
||||
@@ -16,9 +16,9 @@ public readonly struct TValueEventArgs
|
||||
|
||||
// Performance-focused event args struct; not derived from EventArgs by design.
|
||||
// We intentionally deviate from the standard EventArgs pattern here for perf.
|
||||
// MA0046 suppressed: struct-based args avoid heap allocations in high-frequency events.
|
||||
#pragma warning disable MA0046 // The second parameter must be of type 'System.EventArgs' or a derived type
|
||||
public delegate void TValuePublishedHandler(object? sender, in TValueEventArgs args);
|
||||
#pragma warning restore MA0046
|
||||
|
||||
/// <summary>
|
||||
/// Interface for objects that publish TValue updates.
|
||||
@@ -30,3 +30,4 @@ public interface ITValuePublisher
|
||||
/// </summary>
|
||||
event TValuePublishedHandler? Pub;
|
||||
}
|
||||
#pragma warning restore MA0046
|
||||
|
||||
@@ -298,7 +298,7 @@ public class TSeriesTests
|
||||
{
|
||||
var series = new TSeries();
|
||||
TValue? received = null;
|
||||
series.Pub += (object? sender, TValueEventArgs args) => received = args.Value;
|
||||
series.Pub += (object? sender, in TValueEventArgs args) => received = args.Value;
|
||||
|
||||
series.Add(100, 42.0);
|
||||
|
||||
@@ -313,7 +313,7 @@ public class TSeriesTests
|
||||
var series = new TSeries();
|
||||
TValue? received = null;
|
||||
series.Add(100, 42.0);
|
||||
series.Pub += (object? sender, TValueEventArgs args) => received = args.Value;
|
||||
series.Pub += (object? sender, in TValueEventArgs args) => received = args.Value;
|
||||
|
||||
series.Add(100, 43.0, isNew: false);
|
||||
|
||||
|
||||
+114
-114
@@ -1,114 +1,114 @@
|
||||
# TSeries: Time Series Data Container
|
||||
|
||||
## What It Does
|
||||
|
||||
`TSeries` is a high-performance, memory-efficient container for time-series data. Unlike standard collections (like `List<TValue>`), it uses a **Structure of Arrays (SoA)** layout internally. This means it stores timestamps and values in separate contiguous arrays, optimizing memory access patterns for numerical processing and SIMD vectorization.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Standard object-oriented collections (Array of Structures - AoS) are cache-inefficient for numerical algorithms. When calculating a moving average, the CPU only needs the values, but an AoS layout forces it to load interleaved timestamps into the cache, wasting bandwidth.
|
||||
|
||||
`TSeries` solves this by decoupling time and value storage:
|
||||
|
||||
* **Cache Locality**: Iterating over values loads only values.
|
||||
* **SIMD Readiness**: The internal value array can be exposed directly as a `Span<double>` for AVX/SSE processing.
|
||||
* **Zero-Copy Views**: Data is accessed without defensive copying, ensuring maximum throughput.
|
||||
|
||||
## How It Works
|
||||
|
||||
`TSeries` maintains two parallel internal lists:
|
||||
|
||||
1. `List<long> _t`: Stores timestamps.
|
||||
2. `List<double> _v`: Stores values.
|
||||
|
||||
It implements `IReadOnlyList<TValue>`, allowing it to be treated as a standard collection of `TValue` structs when needed, but its true power lies in its column-oriented properties (`Values`, `Times`).
|
||||
|
||||
## Structure
|
||||
|
||||
### Definition
|
||||
|
||||
```csharp
|
||||
public class TSeries : IReadOnlyList<TValue>, ITValuePublisher
|
||||
```
|
||||
|
||||
### Core Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Values` | `ReadOnlySpan<double>` | Direct access to the value array (SIMD-ready). |
|
||||
| `Times` | `ReadOnlySpan<long>` | Direct access to the timestamp array. |
|
||||
| `Last` | `TValue` | The most recent time-value pair. |
|
||||
| `Count` | `int` | Number of elements in the series. |
|
||||
| `Name` | `string` | Optional identifier for the series. |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Pub` | `Action<TValue>` | Fired whenever a new value is added or updated. |
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating and Populating
|
||||
|
||||
```csharp
|
||||
var series = new TSeries();
|
||||
|
||||
// Add a new bar (isNew = true by default)
|
||||
series.Add(DateTime.UtcNow, 100.0);
|
||||
|
||||
// Add multiple values
|
||||
series.Add(new List<double> { 1.0, 2.0, 3.0 });
|
||||
```
|
||||
|
||||
### Streaming Updates (Real-time)
|
||||
|
||||
`TSeries` supports "bar updates" where the last value changes until the bar closes.
|
||||
|
||||
```csharp
|
||||
// New minute starts
|
||||
series.Add(time, 100.0, isNew: true);
|
||||
|
||||
// Price updates within the same minute
|
||||
series.Add(time, 101.0, isNew: false); // Overwrites last value
|
||||
series.Add(time, 102.0, isNew: false); // Overwrites last value
|
||||
```
|
||||
|
||||
### SIMD Processing
|
||||
|
||||
```csharp
|
||||
// Calculate average using SIMD (via Span)
|
||||
double sum = 0;
|
||||
foreach (var v in series.Values) { sum += v; } // Compiler vectorizes this
|
||||
```
|
||||
|
||||
### Reactive Subscription
|
||||
|
||||
```csharp
|
||||
series.Pub += (item) => Console.WriteLine($"New value: {item}");
|
||||
```
|
||||
|
||||
## Performance Profile
|
||||
|
||||
* **Memory Layout**: SoA (Structure of Arrays).
|
||||
* **Access Speed**: O(1) for random access.
|
||||
* **Iteration**: Cache-friendly linear scan.
|
||||
* **SIMD**: Fully supported via `Values` span.
|
||||
|
||||
## Integration
|
||||
|
||||
`TSeries` is the standard output format for all indicators in QuanTAlib.
|
||||
|
||||
* **Input**: Can be fed into indicators via `Update(TSeries)`.
|
||||
* **Output**: Indicators return `TSeries` from their `Calculate` methods.
|
||||
* **Visualization**: Easily mappable to charting libraries due to separate Time/Value arrays.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
* **CollectionsMarshal**: Uses `CollectionsMarshal.AsSpan` to expose internal list storage as spans without copying. This is unsafe if the list is modified during span access, but provides maximum performance for single-threaded algorithms.
|
||||
* **Virtual Methods**: `Add` is virtual to allow derived classes (like `TBarSeries` components) to intercept updates if necessary.
|
||||
|
||||
## References
|
||||
|
||||
* [Data-Oriented Design](https://en.wikipedia.org/wiki/Data-oriented_design)
|
||||
* [SIMD in .NET](https://learn.microsoft.com/en-us/dotnet/standard/simd)
|
||||
# TSeries: Time Series Data Container
|
||||
|
||||
## What It Does
|
||||
|
||||
`TSeries` is a high-performance, memory-efficient container for time-series data. Unlike standard collections (like `List<TValue>`), it uses a **Structure of Arrays (SoA)** layout internally. This means it stores timestamps and values in separate contiguous arrays, optimizing memory access patterns for numerical processing and SIMD vectorization.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Standard object-oriented collections (Array of Structures - AoS) are cache-inefficient for numerical algorithms. When calculating a moving average, the CPU only needs the values, but an AoS layout forces it to load interleaved timestamps into the cache, wasting bandwidth.
|
||||
|
||||
`TSeries` solves this by decoupling time and value storage:
|
||||
|
||||
* **Cache Locality**: Iterating over values loads only values.
|
||||
* **SIMD Readiness**: The internal value array can be exposed directly as a `Span<double>` for AVX/SSE processing.
|
||||
* **Zero-Copy Views**: Data is accessed without defensive copying, ensuring maximum throughput.
|
||||
|
||||
## How It Works
|
||||
|
||||
`TSeries` maintains two parallel internal lists:
|
||||
|
||||
1. `List<long> _t`: Stores timestamps.
|
||||
2. `List<double> _v`: Stores values.
|
||||
|
||||
It implements `IReadOnlyList<TValue>`, allowing it to be treated as a standard collection of `TValue` structs when needed, but its true power lies in its column-oriented properties (`Values`, `Times`).
|
||||
|
||||
## Structure
|
||||
|
||||
### Definition
|
||||
|
||||
```csharp
|
||||
public class TSeries : IReadOnlyList<TValue>, ITValuePublisher
|
||||
```
|
||||
|
||||
### Core Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Values` | `ReadOnlySpan<double>` | Direct access to the value array (SIMD-ready). |
|
||||
| `Times` | `ReadOnlySpan<long>` | Direct access to the timestamp array. |
|
||||
| `Last` | `TValue` | The most recent time-value pair. |
|
||||
| `Count` | `int` | Number of elements in the series. |
|
||||
| `Name` | `string` | Optional identifier for the series. |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Pub` | `Action<TValue>` | Fired whenever a new value is added or updated. |
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating and Populating
|
||||
|
||||
```csharp
|
||||
var series = new TSeries();
|
||||
|
||||
// Add a new bar (isNew = true by default)
|
||||
series.Add(DateTime.UtcNow, 100.0);
|
||||
|
||||
// Add multiple values
|
||||
series.Add(new List<double> { 1.0, 2.0, 3.0 });
|
||||
```
|
||||
|
||||
### Streaming Updates (Real-time)
|
||||
|
||||
`TSeries` supports "bar updates" where the last value changes until the bar closes.
|
||||
|
||||
```csharp
|
||||
// New minute starts
|
||||
series.Add(time, 100.0, isNew: true);
|
||||
|
||||
// Price updates within the same minute
|
||||
series.Add(time, 101.0, isNew: false); // Overwrites last value
|
||||
series.Add(time, 102.0, isNew: false); // Overwrites last value
|
||||
```
|
||||
|
||||
### SIMD Processing
|
||||
|
||||
```csharp
|
||||
// Calculate average using SIMD (via Span)
|
||||
double sum = 0;
|
||||
foreach (var v in series.Values) { sum += v; } // Compiler vectorizes this
|
||||
```
|
||||
|
||||
### Reactive Subscription
|
||||
|
||||
```csharp
|
||||
series.Pub += (item) => Console.WriteLine($"New value: {item}");
|
||||
```
|
||||
|
||||
## Performance Profile
|
||||
|
||||
* **Memory Layout**: SoA (Structure of Arrays).
|
||||
* **Access Speed**: O(1) for random access.
|
||||
* **Iteration**: Cache-friendly linear scan.
|
||||
* **SIMD**: Fully supported via `Values` span.
|
||||
|
||||
## Integration
|
||||
|
||||
`TSeries` is the standard output format for all indicators in QuanTAlib.
|
||||
|
||||
* **Input**: Can be fed into indicators via `Update(TSeries)`.
|
||||
* **Output**: Indicators return `TSeries` from their `Calculate` methods.
|
||||
* **Visualization**: Easily mappable to charting libraries due to separate Time/Value arrays.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
* **CollectionsMarshal**: Uses `CollectionsMarshal.AsSpan` to expose internal list storage as spans without copying. This is unsafe if the list is modified during span access, but provides maximum performance for single-threaded algorithms.
|
||||
* **Virtual Methods**: `Add` is virtual to allow derived classes (like `TBarSeries` components) to intercept updates if necessary.
|
||||
|
||||
## References
|
||||
|
||||
* [Data-Oriented Design](https://en.wikipedia.org/wiki/Data-oriented_design)
|
||||
* [SIMD in .NET](https://learn.microsoft.com/en-us/dotnet/standard/simd)
|
||||
|
||||
@@ -54,9 +54,9 @@ namespace QuanTAlib.Tests
|
||||
public void ImplicitConversion_ToDouble_ReturnsValue()
|
||||
{
|
||||
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
|
||||
|
||||
|
||||
double val = tValue;
|
||||
|
||||
|
||||
Assert.Equal(42.0, val);
|
||||
}
|
||||
|
||||
|
||||
+99
-99
@@ -1,99 +1,99 @@
|
||||
# TValue: Time-Value Pair
|
||||
|
||||
## What It Does
|
||||
|
||||
`TValue` is the fundamental atomic unit of data in QuanTAlib. It represents a single point in a time series, consisting of a timestamp and a double-precision floating-point value. It serves as the standard input and output format for all indicators and data streams.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
In high-frequency trading and quantitative analysis, memory allocation is a critical bottleneck. `TValue` is designed as a **lightweight, immutable struct** to ensure:
|
||||
|
||||
* **Zero Heap Allocation**: Being a struct, it lives on the stack or embedded in arrays, avoiding Garbage Collector (GC) pressure.
|
||||
* **Thread Safety**: Immutability guarantees safe concurrent access.
|
||||
* **Minimal Footprint**: Occupies exactly 16 bytes (8 bytes for `long` Time + 8 bytes for `double` Value), fitting efficiently in CPU cache lines.
|
||||
|
||||
## How It Works
|
||||
|
||||
`TValue` is implemented as a `readonly record struct`. It encapsulates:
|
||||
|
||||
* **Time**: A `long` representing ticks (UTC).
|
||||
* **Value**: A `double` representing the data magnitude.
|
||||
|
||||
It supports implicit conversions to `double` (extracting the value) and `DateTime` (extracting the time), making it syntactically fluid to use in calculations.
|
||||
|
||||
## Structure
|
||||
|
||||
### Definition
|
||||
|
||||
```csharp
|
||||
public readonly record struct TValue(long Time, double Value);
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Time` | `long` | Timestamp in ticks (UTC). |
|
||||
| `Value` | `double` | The data value. |
|
||||
| `AsDateTime` | `DateTime` | Helper to view `Time` as a `DateTime` object. |
|
||||
|
||||
### Constructors
|
||||
|
||||
| Constructor | Description |
|
||||
|-------------|-------------|
|
||||
| `new TValue(long time, double value)` | Creates a TValue from raw ticks. |
|
||||
| `new TValue(DateTime time, double value)` | Creates a TValue from a DateTime object. |
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating TValues
|
||||
|
||||
```csharp
|
||||
// From DateTime
|
||||
var t1 = new TValue(DateTime.UtcNow, 100.5);
|
||||
|
||||
// From Ticks
|
||||
var t2 = new TValue(DateTime.UtcNow.Ticks, 100.5);
|
||||
```
|
||||
|
||||
### Implicit Conversions
|
||||
|
||||
```csharp
|
||||
TValue tv = new TValue(DateTime.UtcNow, 42.0);
|
||||
|
||||
// Implicitly converts to double
|
||||
double val = tv; // 42.0
|
||||
|
||||
// Implicitly converts to DateTime
|
||||
DateTime dt = tv; // DateTime object
|
||||
```
|
||||
|
||||
### String Representation
|
||||
|
||||
```csharp
|
||||
Console.WriteLine(tv); // Output: "[2024-01-01 12:00:00, 42.00]"
|
||||
```
|
||||
|
||||
## Performance Profile
|
||||
|
||||
* **Memory**: 16 bytes per instance.
|
||||
* **Allocation**: 0 bytes (Stack allocated).
|
||||
* **Copying**: Cheap (fits in two 64-bit registers).
|
||||
|
||||
## Integration
|
||||
|
||||
`TValue` is the primary currency of the library:
|
||||
|
||||
* **Indicators**: `Update(TValue input)` accepts it.
|
||||
* **Series**: `TSeries` stores collections of it.
|
||||
* **Events**: `ITValuePublisher` broadcasts it.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
* **SkipLocalsInit**: The struct is marked with `[SkipLocalsInit]` to suppress zero-initialization of locals, squeezing out nanoseconds in tight loops.
|
||||
* **AggressiveInlining**: All accessors and operators are inlined to ensure zero abstraction penalty.
|
||||
|
||||
## References
|
||||
|
||||
* [Structure of Arrays (SoA)](https://en.wikipedia.org/wiki/AOS_and_SOA)
|
||||
* [C# Struct Performance](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct)
|
||||
# TValue: Time-Value Pair
|
||||
|
||||
## What It Does
|
||||
|
||||
`TValue` is the fundamental atomic unit of data in QuanTAlib. It represents a single point in a time series, consisting of a timestamp and a double-precision floating-point value. It serves as the standard input and output format for all indicators and data streams.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
In high-frequency trading and quantitative analysis, memory allocation is a critical bottleneck. `TValue` is designed as a **lightweight, immutable struct** to ensure:
|
||||
|
||||
* **Zero Heap Allocation**: Being a struct, it lives on the stack or embedded in arrays, avoiding Garbage Collector (GC) pressure.
|
||||
* **Thread Safety**: Immutability guarantees safe concurrent access.
|
||||
* **Minimal Footprint**: Occupies exactly 16 bytes (8 bytes for `long` Time + 8 bytes for `double` Value), fitting efficiently in CPU cache lines.
|
||||
|
||||
## How It Works
|
||||
|
||||
`TValue` is implemented as a `readonly record struct`. It encapsulates:
|
||||
|
||||
* **Time**: A `long` representing ticks (UTC).
|
||||
* **Value**: A `double` representing the data magnitude.
|
||||
|
||||
It supports implicit conversions to `double` (extracting the value) and `DateTime` (extracting the time), making it syntactically fluid to use in calculations.
|
||||
|
||||
## Structure
|
||||
|
||||
### Definition
|
||||
|
||||
```csharp
|
||||
public readonly record struct TValue(long Time, double Value);
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Time` | `long` | Timestamp in ticks (UTC). |
|
||||
| `Value` | `double` | The data value. |
|
||||
| `AsDateTime` | `DateTime` | Helper to view `Time` as a `DateTime` object. |
|
||||
|
||||
### Constructors
|
||||
|
||||
| Constructor | Description |
|
||||
|-------------|-------------|
|
||||
| `new TValue(long time, double value)` | Creates a TValue from raw ticks. |
|
||||
| `new TValue(DateTime time, double value)` | Creates a TValue from a DateTime object. |
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating TValues
|
||||
|
||||
```csharp
|
||||
// From DateTime
|
||||
var t1 = new TValue(DateTime.UtcNow, 100.5);
|
||||
|
||||
// From Ticks
|
||||
var t2 = new TValue(DateTime.UtcNow.Ticks, 100.5);
|
||||
```
|
||||
|
||||
### Implicit Conversions
|
||||
|
||||
```csharp
|
||||
TValue tv = new TValue(DateTime.UtcNow, 42.0);
|
||||
|
||||
// Implicitly converts to double
|
||||
double val = tv; // 42.0
|
||||
|
||||
// Implicitly converts to DateTime
|
||||
DateTime dt = tv; // DateTime object
|
||||
```
|
||||
|
||||
### String Representation
|
||||
|
||||
```csharp
|
||||
Console.WriteLine(tv); // Output: "[2024-01-01 12:00:00, 42.00]"
|
||||
```
|
||||
|
||||
## Performance Profile
|
||||
|
||||
* **Memory**: 16 bytes per instance.
|
||||
* **Allocation**: 0 bytes (Stack allocated).
|
||||
* **Copying**: Cheap (fits in two 64-bit registers).
|
||||
|
||||
## Integration
|
||||
|
||||
`TValue` is the primary currency of the library:
|
||||
|
||||
* **Indicators**: `Update(TValue input)` accepts it.
|
||||
* **Series**: `TSeries` stores collections of it.
|
||||
* **Events**: `ITValuePublisher` broadcasts it.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
* **SkipLocalsInit**: The struct is marked with `[SkipLocalsInit]` to suppress zero-initialization of locals, squeezing out nanoseconds in tight loops.
|
||||
* **AggressiveInlining**: All accessors and operators are inlined to ensure zero abstraction penalty.
|
||||
|
||||
## References
|
||||
|
||||
* [Structure of Arrays (SoA)](https://en.wikipedia.org/wiki/AOS_and_SOA)
|
||||
* [C# Struct Performance](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct)
|
||||
|
||||
@@ -14,7 +14,7 @@ public readonly record struct TValue(long Time, double Value)
|
||||
public DateTime AsDateTime => new(Time, DateTimeKind.Utc);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TValue(DateTime time, double value)
|
||||
public TValue(DateTime time, double value)
|
||||
: this(time.Kind == DateTimeKind.Utc ? time.Ticks : time.ToUniversalTime().Ticks, value)
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user