mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 13:08:04 +00:00
SIMD Refactor: Merge simd-dev into dev (#55)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: aider (openrouter/anthropic/claude-sonnet-4) <aider@aider.chat> Co-authored-by: Warp <agent@warp.dev>
This commit is contained in:
co-authored by
Claude Opus 4.5
aider
Warp
parent
5bcdf8d614
commit
86fe32a682
@@ -0,0 +1,500 @@
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class TBarTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsPropertiesCorrectly()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
const double open = 100;
|
||||
const double high = 110;
|
||||
const double low = 90;
|
||||
const double close = 105;
|
||||
const double volume = 1000;
|
||||
|
||||
var bar = new TBar(time, open, high, low, close, volume);
|
||||
|
||||
Assert.Equal(time, bar.Time);
|
||||
Assert.Equal(open, bar.Open);
|
||||
Assert.Equal(high, bar.High);
|
||||
Assert.Equal(low, bar.Low);
|
||||
Assert.Equal(close, bar.Close);
|
||||
Assert.Equal(volume, bar.Volume);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithDateTime_SetsPropertiesCorrectly()
|
||||
{
|
||||
var dateTime = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc);
|
||||
const double open = 100;
|
||||
const double high = 110;
|
||||
const double low = 90;
|
||||
const double close = 105;
|
||||
const double volume = 1000;
|
||||
|
||||
var bar = new TBar(dateTime, open, high, low, close, volume);
|
||||
|
||||
Assert.Equal(dateTime.Ticks, bar.Time);
|
||||
Assert.Equal(open, bar.Open);
|
||||
Assert.Equal(high, bar.High);
|
||||
Assert.Equal(low, bar.Low);
|
||||
Assert.Equal(close, bar.Close);
|
||||
Assert.Equal(volume, bar.Volume);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsDateTime_ReturnsCorrectDateTime()
|
||||
{
|
||||
var dateTime = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc);
|
||||
var bar = new TBar(dateTime, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.Equal(dateTime, bar.AsDateTime);
|
||||
Assert.Equal(DateTimeKind.Utc, bar.AsDateTime.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void O_Property_ReturnsTValueWithOpenPrice()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
var bar = new TBar(time, 100, 110, 90, 105, 1000);
|
||||
|
||||
TValue o = bar.O;
|
||||
|
||||
Assert.Equal(time, o.Time);
|
||||
Assert.Equal(100.0, o.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void H_Property_ReturnsTValueWithHighPrice()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
var bar = new TBar(time, 100, 110, 90, 105, 1000);
|
||||
|
||||
TValue h = bar.H;
|
||||
|
||||
Assert.Equal(time, h.Time);
|
||||
Assert.Equal(110.0, h.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void L_Property_ReturnsTValueWithLowPrice()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
var bar = new TBar(time, 100, 110, 90, 105, 1000);
|
||||
|
||||
TValue l = bar.L;
|
||||
|
||||
Assert.Equal(time, l.Time);
|
||||
Assert.Equal(90.0, l.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void C_Property_ReturnsTValueWithClosePrice()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
var bar = new TBar(time, 100, 110, 90, 105, 1000);
|
||||
|
||||
TValue c = bar.C;
|
||||
|
||||
Assert.Equal(time, c.Time);
|
||||
Assert.Equal(105.0, c.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void V_Property_ReturnsTValueWithVolume()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
var bar = new TBar(time, 100, 110, 90, 105, 1000);
|
||||
|
||||
TValue v = bar.V;
|
||||
|
||||
Assert.Equal(time, v.Time);
|
||||
Assert.Equal(1000.0, v.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HL2_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 105, 1000);
|
||||
Assert.Equal(100.0, bar.HL2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OC2_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 104, 1000);
|
||||
Assert.Equal(102.0, bar.OC2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OHL3_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 105, 1000);
|
||||
Assert.Equal(100.0, bar.OHL3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HLC3_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 100, 1000);
|
||||
Assert.Equal(100.0, bar.HLC3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OHLC4_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 100, 1000);
|
||||
Assert.Equal(100.0, bar.OHLC4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HLCC4_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 100, 1000);
|
||||
Assert.Equal(100.0, bar.HLCC4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversion_ToDouble_ReturnsClosePrice()
|
||||
{
|
||||
var bar = new TBar(0, 100, 110, 90, 105, 1000);
|
||||
double closePrice = bar;
|
||||
Assert.Equal(105.0, closePrice);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversion_ToTValue_ReturnsClosePriceWithTime()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversion_ToDateTime_ReturnsCorrectDateTime()
|
||||
{
|
||||
var dateTime = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc);
|
||||
var bar = new TBar(dateTime, 100, 110, 90, 105, 1000);
|
||||
|
||||
DateTime result = bar;
|
||||
|
||||
Assert.Equal(dateTime, result);
|
||||
Assert.Equal(DateTimeKind.Utc, result.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_ReturnsFormattedString()
|
||||
{
|
||||
var dateTime = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc);
|
||||
var bar = new TBar(dateTime, 100.5, 110.25, 90.75, 105.0, 1000.0);
|
||||
|
||||
string result = bar.ToString();
|
||||
|
||||
Assert.Contains("2024-06-15", result, StringComparison.Ordinal);
|
||||
Assert.Contains("10:30:00", result, StringComparison.Ordinal);
|
||||
Assert.Contains("O=100.50", result, StringComparison.Ordinal);
|
||||
Assert.Contains("H=110.25", result, StringComparison.Ordinal);
|
||||
Assert.Contains("L=90.75", result, StringComparison.Ordinal);
|
||||
Assert.Contains("C=105.00", result, StringComparison.Ordinal);
|
||||
Assert.Contains("V=1000.00", result, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_TBar_SameBars_ReturnsTrue()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.True(bar1.Equals(bar2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_TBar_DifferentTime_ReturnsFalse()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12346, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.False(bar1.Equals(bar2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_TBar_DifferentOpen_ReturnsFalse()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 101, 110, 90, 105, 1000);
|
||||
|
||||
Assert.False(bar1.Equals(bar2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_TBar_DifferentHigh_ReturnsFalse()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 100, 111, 90, 105, 1000);
|
||||
|
||||
Assert.False(bar1.Equals(bar2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_TBar_DifferentLow_ReturnsFalse()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 100, 110, 91, 105, 1000);
|
||||
|
||||
Assert.False(bar1.Equals(bar2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_TBar_DifferentClose_ReturnsFalse()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 100, 110, 90, 106, 1000);
|
||||
|
||||
Assert.False(bar1.Equals(bar2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_TBar_DifferentVolume_ReturnsFalse()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 100, 110, 90, 105, 1001);
|
||||
|
||||
Assert.False(bar1.Equals(bar2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_Object_SameTBar_ReturnsTrue()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
object bar2 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.True(bar1.Equals(bar2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_Object_DifferentType_ReturnsFalse()
|
||||
{
|
||||
var bar = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
object other = "not a TBar";
|
||||
|
||||
Assert.False(bar.Equals(other));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_Object_Null_ReturnsFalse()
|
||||
{
|
||||
var bar = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.False(bar.Equals(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_SameBars_ReturnsSameHashCode()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.Equal(bar1.GetHashCode(), bar2.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_DifferentBars_ReturnsDifferentHashCode()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12346, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.NotEqual(bar1.GetHashCode(), bar2.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_SameBars_ReturnsTrue()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.True(bar1 == bar2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_DifferentBars_ReturnsFalse()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12346, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.False(bar1 == bar2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_SameBars_ReturnsFalse()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.False(bar1 != bar2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_DifferentBars_ReturnsTrue()
|
||||
{
|
||||
var bar1 = new TBar(12345, 100, 110, 90, 105, 1000);
|
||||
var bar2 = new TBar(12346, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.True(bar1 != bar2);
|
||||
}
|
||||
|
||||
// Additional edge case tests
|
||||
[Fact]
|
||||
public void Constructor_WithLocalDateTime_ConvertsToUtc()
|
||||
{
|
||||
var localDateTime = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Local);
|
||||
var bar = new TBar(localDateTime, 100, 110, 90, 105, 1000);
|
||||
|
||||
// AsDateTime should return UTC
|
||||
Assert.Equal(DateTimeKind.Utc, bar.AsDateTime.Kind);
|
||||
Assert.Equal(localDateTime.ToUniversalTime().Ticks, bar.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithUnspecifiedDateTime_ConvertsToUtc()
|
||||
{
|
||||
var unspecifiedDateTime = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Unspecified);
|
||||
var bar = new TBar(unspecifiedDateTime, 100, 110, 90, 105, 1000);
|
||||
|
||||
// Should be converted to UTC
|
||||
Assert.Equal(DateTimeKind.Utc, bar.AsDateTime.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultTBar_HasZeroValues()
|
||||
{
|
||||
var bar = default(TBar);
|
||||
|
||||
Assert.Equal(0, bar.Time);
|
||||
Assert.Equal(0.0, bar.Open);
|
||||
Assert.Equal(0.0, bar.High);
|
||||
Assert.Equal(0.0, bar.Low);
|
||||
Assert.Equal(0.0, bar.Close);
|
||||
Assert.Equal(0.0, bar.Volume);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBar_WithNaN_HandlesGracefully()
|
||||
{
|
||||
var bar = new TBar(12345, double.NaN, 110, 90, 105, 1000);
|
||||
|
||||
Assert.True(double.IsNaN(bar.Open));
|
||||
Assert.True(double.IsNaN(bar.O.Value));
|
||||
Assert.True(double.IsNaN(bar.OHL3)); // Uses Open
|
||||
Assert.True(double.IsNaN(bar.OC2)); // Uses Open
|
||||
Assert.True(double.IsNaN(bar.OHLC4)); // Uses Open
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBar_WithInfinity_HandlesGracefully()
|
||||
{
|
||||
var bar = new TBar(12345, 100, double.PositiveInfinity, 90, 105, 1000);
|
||||
|
||||
Assert.True(double.IsPositiveInfinity(bar.High));
|
||||
Assert.True(double.IsPositiveInfinity(bar.H.Value));
|
||||
Assert.True(double.IsPositiveInfinity(bar.HL2)); // Uses High
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBar_WithMaxValue_HandlesGracefully()
|
||||
{
|
||||
var bar = new TBar(12345, double.MaxValue, double.MaxValue, double.MinValue, 105, 1000);
|
||||
|
||||
Assert.Equal(double.MaxValue, bar.Open);
|
||||
Assert.Equal(double.MaxValue, bar.High);
|
||||
Assert.Equal(double.MinValue, bar.Low);
|
||||
// HL2 calculation with extreme values
|
||||
Assert.True(double.IsFinite(bar.HL2) || double.IsInfinity(bar.HL2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TBar_WithEpsilon_HandlesGracefully()
|
||||
{
|
||||
var bar = new TBar(12345, double.Epsilon, double.Epsilon, double.Epsilon, double.Epsilon, double.Epsilon);
|
||||
|
||||
Assert.Equal(double.Epsilon, bar.Open);
|
||||
Assert.Equal(double.Epsilon, bar.Close);
|
||||
Assert.True(bar.HL2 > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HL2_WithNegativeValues_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, -100, -90, -110, -95, 1000);
|
||||
|
||||
Assert.Equal(-100.0, bar.HL2); // (-90 + -110) / 2
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OHLC4_WithNegativeValues_CalculatesCorrectly()
|
||||
{
|
||||
var bar = new TBar(0, -100, -90, -110, -100, 1000);
|
||||
|
||||
Assert.Equal(-100.0, bar.OHLC4); // (-100 + -90 + -110 + -100) / 4
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitConversion_ToTValue_PreservesTimeAndClose()
|
||||
{
|
||||
const long time = 12_345_678_901_234_567;
|
||||
var bar = new TBar(time, 100, 110, 90, 105.5, 1000);
|
||||
|
||||
TValue tv = bar;
|
||||
|
||||
Assert.Equal(time, tv.Time);
|
||||
Assert.Equal(105.5, tv.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_WithNaN_DoesNotThrow()
|
||||
{
|
||||
var bar = new TBar(DateTime.UtcNow.Ticks, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN);
|
||||
|
||||
string result = bar.ToString();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("NaN", result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void O_H_L_C_V_AllHaveSameTime()
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
var bar = new TBar(time, 100, 110, 90, 105, 1000);
|
||||
|
||||
Assert.Equal(time, bar.O.Time);
|
||||
Assert.Equal(time, bar.H.Time);
|
||||
Assert.Equal(time, bar.L.Time);
|
||||
Assert.Equal(time, bar.C.Time);
|
||||
Assert.Equal(time, bar.V.Time);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HLCC4_DoubleWeightsClose()
|
||||
{
|
||||
// HLCC4 = (High + Low + Close + Close) / 4
|
||||
var bar = new TBar(0, 100, 120, 80, 100, 1000);
|
||||
|
||||
// (120 + 80 + 100 + 100) / 4 = 400 / 4 = 100
|
||||
Assert.Equal(100.0, bar.HLCC4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OHL3_ExcludesClose()
|
||||
{
|
||||
// OHL3 = (Open + High + Low) / 3
|
||||
var bar = new TBar(0, 90, 120, 60, 999, 1000);
|
||||
|
||||
// (90 + 120 + 60) / 3 = 270 / 3 = 90
|
||||
Assert.Equal(90.0, bar.OHL3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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)
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// A lightweight struct representing an OHLCV bar.
|
||||
/// Pure data type: 48 bytes (long + 5 doubles).
|
||||
/// </summary>
|
||||
[SkipLocalsInit]
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly record struct TBar(long Time, double Open, double High, double Low, double Close, double Volume)
|
||||
{
|
||||
public DateTime AsDateTime => new(Time, DateTimeKind.Utc);
|
||||
|
||||
// TValue conversions (Zero-copy / lightweight creation)
|
||||
public TValue O { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(Time, Open); }
|
||||
public TValue H { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(Time, High); }
|
||||
public TValue L { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(Time, Low); }
|
||||
public TValue C { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(Time, Close); }
|
||||
public TValue V { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(Time, Volume); }
|
||||
|
||||
// Computed properties (calculated on demand, no storage overhead)
|
||||
public double HL2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (High + Low) * 0.5; }
|
||||
public double OC2 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (Open + Close) * 0.5; }
|
||||
public double OHL3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (Open + High + Low) / 3.0; }
|
||||
public double HLC3 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (High + Low + Close) / 3.0; }
|
||||
public double OHLC4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (Open + High + Low + Close) * 0.25; }
|
||||
public double HLCC4 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (High + Low + Close + Close) * 0.25; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TBar(DateTime time, double open, double high, double low, double close, double volume)
|
||||
: this(time.Kind == DateTimeKind.Utc ? time.Ticks : time.ToUniversalTime().Ticks, open, high, low, close, volume)
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator double(TBar bar) => bar.Close;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator TValue(TBar bar) => new(bar.Time, bar.Close);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator DateTime(TBar bar) => new(bar.Time, DateTimeKind.Utc);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override string ToString() => $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]";
|
||||
}
|
||||
Reference in New Issue
Block a user