feat: Add Prime method to various indicators for initializing state with historical data

- Implemented Prime method in Vel, Ao, Apo, Frama, Adl, Adosc, Aobv, Cmf, Efi, Eom, Iii, Kvo, Mfi, Nvi, Obv, Pvd, Pvi, Pvo, Pvr, Pvt, Tvi, Twap, Va, Vf, Vo, Vroc, Vwad, Vwap, and Vwma classes.
- The Prime method resets the indicator state and processes the provided historical bar data to initialize the indicator.
- Added warmup period property to Adl and Wad classes to define the minimum number of data points required for validity.
- Updated benchmark tests to use Batch methods for performance evaluation.
This commit is contained in:
Miha Kralj
2026-02-11 20:38:38 -08:00
parent 75c6a9f135
commit 653aafacd8
71 changed files with 10527 additions and 242 deletions
+499 -1
View File
@@ -303,7 +303,7 @@ public class TValueTests
string result = tValue.ToString(null, CultureInfo.InvariantCulture);
Assert.Contains("", result, StringComparison.Ordinal);
Assert.Contains("\u221E", result, StringComparison.Ordinal);
}
[Fact]
@@ -373,4 +373,502 @@ public class TValueTests
Assert.Equal(long.MaxValue, tValue.Time);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: ToString() parameterless override
// ────────────────────────────────────────────────────────────────────
[Fact]
public void ToString_Parameterless_NormalValue_FormatsCorrectly()
{
var dt = new DateTime(2023, 6, 15, 14, 30, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, 42.0);
#pragma warning disable MA0011 // IFormatProvider is missing - intentionally testing parameterless overload
string result = tValue.ToString();
#pragma warning restore MA0011
Assert.StartsWith("[", result, StringComparison.Ordinal);
Assert.EndsWith("]", result, StringComparison.Ordinal);
Assert.Contains("2023-06-15", result, StringComparison.Ordinal);
Assert.Contains("14:30:00", result, StringComparison.Ordinal);
Assert.Contains("42.00", result, StringComparison.Ordinal);
}
[Fact]
public void ToString_Parameterless_PositiveInfinity_ContainsInfinitySymbol()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.PositiveInfinity);
#pragma warning disable MA0011
string result = tValue.ToString();
#pragma warning restore MA0011
Assert.Contains("\u221E", result, StringComparison.Ordinal);
}
[Fact]
public void ToString_Parameterless_NegativeInfinity_ContainsMinusInfinitySymbol()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.NegativeInfinity);
#pragma warning disable MA0011
string result = tValue.ToString();
#pragma warning restore MA0011
Assert.Contains("-\u221E", result, StringComparison.Ordinal);
}
[Fact]
public void ToString_Parameterless_NaN_ContainsNaN()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.NaN);
#pragma warning disable MA0011
string result = tValue.ToString();
#pragma warning restore MA0011
Assert.Contains("NaN", result, StringComparison.Ordinal);
}
[Fact]
public void ToString_Parameterless_ZeroValue_FormatsAsZero()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, 0.0);
#pragma warning disable MA0011
string result = tValue.ToString();
#pragma warning restore MA0011
Assert.Contains("0.00", result, StringComparison.Ordinal);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: ToString(string?, IFormatProvider?) — NotSupportedException
// ────────────────────────────────────────────────────────────────────
[Fact]
public void ToString_WithCustomFormat_ThrowsNotSupportedException()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
Assert.Throws<NotSupportedException>(() => tValue.ToString("F4", CultureInfo.InvariantCulture));
}
[Fact]
public void ToString_WithNonEmptyFormat_ThrowsNotSupportedException()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
var ex = Assert.Throws<NotSupportedException>(() => tValue.ToString("G", null));
Assert.Contains("Custom format", ex.Message, StringComparison.Ordinal);
Assert.Contains("'G'", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void ToString_WithNullFormat_DoesNotThrow()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, 42.0);
string result = tValue.ToString(null, null);
Assert.Contains("42.00", result, StringComparison.Ordinal);
}
[Fact]
public void ToString_WithEmptyFormat_DoesNotThrow()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, 42.0);
string result = tValue.ToString("", null);
Assert.Contains("42.00", result, StringComparison.Ordinal);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: TryFormat — allocation-free span formatting
// ────────────────────────────────────────────────────────────────────
[Fact]
public void TryFormat_NormalValue_FormatsCorrectly()
{
var dt = new DateTime(2023, 6, 15, 14, 30, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, 42.0);
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, CultureInfo.InvariantCulture);
Assert.True(result);
Assert.True(charsWritten > 0);
string formatted = new string(buffer.Slice(0, charsWritten));
Assert.StartsWith("[", formatted, StringComparison.Ordinal);
Assert.EndsWith("]", formatted, StringComparison.Ordinal);
Assert.Contains("2023-06-15", formatted, StringComparison.Ordinal);
Assert.Contains("14:30:00", formatted, StringComparison.Ordinal);
Assert.Contains("42.00", formatted, StringComparison.Ordinal);
}
[Fact]
public void TryFormat_PositiveInfinity_FormatsWithSymbol()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.PositiveInfinity);
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
string formatted = new string(buffer.Slice(0, charsWritten));
Assert.Contains("\u221E", formatted, StringComparison.Ordinal);
Assert.StartsWith("[", formatted, StringComparison.Ordinal);
Assert.EndsWith("]", formatted, StringComparison.Ordinal);
}
[Fact]
public void TryFormat_NegativeInfinity_FormatsWithMinusSymbol()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.NegativeInfinity);
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
string formatted = new string(buffer.Slice(0, charsWritten));
Assert.Contains("-\u221E", formatted, StringComparison.Ordinal);
}
[Fact]
public void TryFormat_NaN_FormatsAsNaN()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.NaN);
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
string formatted = new string(buffer.Slice(0, charsWritten));
Assert.Contains("NaN", formatted, StringComparison.Ordinal);
}
[Fact]
public void TryFormat_BufferTooSmall_ReturnsFalse()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
Span<char> buffer = stackalloc char[10]; // Way too small (< 24)
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.False(result);
Assert.Equal(0, charsWritten);
}
[Fact]
public void TryFormat_BufferExactlyTooSmall_ReturnsFalse()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
Span<char> buffer = stackalloc char[23]; // One less than minimum (24)
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.False(result);
Assert.Equal(0, charsWritten);
}
[Fact]
public void TryFormat_BufferBarelyLargeEnough_ForShortValue()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// NaN is shortest value format (3 chars): "[yyyy-MM-dd HH:mm:ss, NaN]" = 26 chars
var tValue = new TValue(dt.Ticks, double.NaN);
Span<char> buffer = stackalloc char[26];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
Assert.Equal(26, charsWritten);
}
[Fact]
public void TryFormat_BufferTooSmallForSeparator_ReturnsFalse()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, 42.0);
// Buffer: 1 (opening bracket) + 19 (datetime) + 1 = 21 (too small for ", ")
Span<char> buffer = stackalloc char[21];
// This should fail because the initial < 24 check triggers first
bool result = tValue.TryFormat(buffer, out _, ReadOnlySpan<char>.Empty, null);
Assert.False(result);
}
[Fact]
public void TryFormat_BufferTooSmallForClosingBracket_ReturnsFalse()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// ∞ is 1 char: "[yyyy-MM-dd HH:mm:ss, ∞]" = 24 chars
var tValue = new TValue(dt.Ticks, double.PositiveInfinity);
// Need exactly 24 chars: 1 + 19 + 2 + 1 + 1 = 24
// Providing 23 — enough to pass the initial check but too small for final ']'
// Actually the initial check is < 24, so 23 fails there.
// Let's use 24 which is exactly enough for ∞ case
Span<char> buffer = stackalloc char[24];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
Assert.Equal(24, charsWritten);
}
[Fact]
public void TryFormat_LargeNegativeValue_FormatsCorrectly()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, -99999.99);
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, CultureInfo.InvariantCulture);
Assert.True(result);
string formatted = new string(buffer.Slice(0, charsWritten));
Assert.Contains("-99999.99", formatted, StringComparison.Ordinal);
}
[Fact]
public void TryFormat_ZeroValue_FormatsAsZero()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, 0.0);
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, CultureInfo.InvariantCulture);
Assert.True(result);
string formatted = new string(buffer.Slice(0, charsWritten));
Assert.Contains("0.00", formatted, StringComparison.Ordinal);
}
[Fact]
public void TryFormat_ConsistentWithToString()
{
var dt = new DateTime(2023, 6, 15, 14, 30, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, 123.456);
#pragma warning disable MA0011
string fromToString = tValue.ToString();
#pragma warning restore MA0011
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
string fromTryFormat = new string(buffer.Slice(0, charsWritten));
Assert.Equal(fromToString, fromTryFormat);
}
[Fact]
public void TryFormat_NaN_ConsistentWithToString()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.NaN);
#pragma warning disable MA0011
string fromToString = tValue.ToString();
#pragma warning restore MA0011
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
string fromTryFormat = new string(buffer.Slice(0, charsWritten));
Assert.Equal(fromToString, fromTryFormat);
}
[Fact]
public void TryFormat_PositiveInfinity_ConsistentWithToString()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.PositiveInfinity);
#pragma warning disable MA0011
string fromToString = tValue.ToString();
#pragma warning restore MA0011
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
string fromTryFormat = new string(buffer.Slice(0, charsWritten));
Assert.Equal(fromToString, fromTryFormat);
}
[Fact]
public void TryFormat_NegativeInfinity_ConsistentWithToString()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.NegativeInfinity);
#pragma warning disable MA0011
string fromToString = tValue.ToString();
#pragma warning restore MA0011
Span<char> buffer = stackalloc char[128];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
string fromTryFormat = new string(buffer.Slice(0, charsWritten));
Assert.Equal(fromToString, fromTryFormat);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: Record struct features (with expression, Deconstruct)
// ────────────────────────────────────────────────────────────────────
[Fact]
public void WithExpression_CreatesModifiedCopy()
{
var original = new TValue(12345, 100.0);
var modified = original with { Value = 200.0 };
Assert.Equal(12345, modified.Time);
Assert.Equal(200.0, modified.Value);
Assert.Equal(100.0, original.Value); // Original unchanged
}
[Fact]
public void WithExpression_TimeModified_CreatesModifiedCopy()
{
var original = new TValue(12345, 100.0);
var modified = original with { Time = 99999 };
Assert.Equal(99999, modified.Time);
Assert.Equal(100.0, modified.Value);
}
[Fact]
public void Deconstruct_ExtractsTimeAndValue()
{
var tValue = new TValue(12345, 42.0);
var (time, value) = tValue;
Assert.Equal(12345, time);
Assert.Equal(42.0, value);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: ISpanFormattable interface contract
// ────────────────────────────────────────────────────────────────────
[Fact]
public void ISpanFormattable_ImplementedCorrectly()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
// Verify TValue implements ISpanFormattable
Assert.IsAssignableFrom<ISpanFormattable>(tValue);
}
[Fact]
public void IFormattable_ImplementedCorrectly()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
// ISpanFormattable extends IFormattable
Assert.IsAssignableFrom<IFormattable>(tValue);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: ToString with negative infinity (was missing)
// ────────────────────────────────────────────────────────────────────
[Fact]
public void ToString_WithNegativeInfinity_FormatsCorrectly()
{
var dt = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc);
var tValue = new TValue(dt.Ticks, double.NegativeInfinity);
string result = tValue.ToString(null, CultureInfo.InvariantCulture);
Assert.Contains("-\u221E", result, StringComparison.Ordinal);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: Explicit double conversion edge cases
// ────────────────────────────────────────────────────────────────────
[Fact]
public void ExplicitConversion_ToDouble_WithPositiveInfinity_ReturnsInfinity()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, double.PositiveInfinity);
double val = (double)tValue;
Assert.True(double.IsPositiveInfinity(val));
}
[Fact]
public void ExplicitConversion_ToDouble_WithNegativeInfinity_ReturnsNegativeInfinity()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, double.NegativeInfinity);
double val = (double)tValue;
Assert.True(double.IsNegativeInfinity(val));
}
[Fact]
public void ExplicitConversion_ToDouble_WithZero_ReturnsZero()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, 0.0);
double val = (double)tValue;
Assert.Equal(0.0, val);
}
// ────────────────────────────────────────────────────────────────────
// NEW TESTS: TryFormat buffer edge cases for -∞ and NaN
// ────────────────────────────────────────────────────────────────────
[Fact]
public void TryFormat_NegativeInfinity_ExactBuffer()
{
var dt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// "-∞" is 2 chars: "[yyyy-MM-dd HH:mm:ss, -∞]" = 25 chars
var tValue = new TValue(dt.Ticks, double.NegativeInfinity);
Span<char> buffer = stackalloc char[25];
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.True(result);
Assert.Equal(25, charsWritten);
}
[Fact]
public void TryFormat_EmptyBuffer_ReturnsFalse()
{
var tValue = new TValue(DateTime.UtcNow.Ticks, 42.0);
Span<char> buffer = Span<char>.Empty;
bool result = tValue.TryFormat(buffer, out int charsWritten, ReadOnlySpan<char>.Empty, null);
Assert.False(result);
Assert.Equal(0, charsWritten);
}
}