mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 13:08:04 +00:00
docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files
- Remove 'C# Implementation Considerations' sections from 34 indicator .md files - Delete 29 temp PowerShell scripts (_fix_mojibake.ps1, _hex_scan.ps1, etc.) - Move test files into tests/ subdirectories for consistent project structure - Add trader-focused bullet points to indicator documentation
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
using TradingPlatform.BusinessLayer;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class TyppriceIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void TyppriceIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("TYPPRICE - Typical Price", indicator.Name);
|
||||
Assert.False(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_ShortName_IsTypprice()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
Assert.Equal("TYPPRICE", indicator.ShortName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_MinHistoryDepths_EqualsOne()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
|
||||
Assert.Equal(1, TyppriceIndicator.MinHistoryDepths);
|
||||
Assert.Equal(1, ((IWatchlistIndicator)indicator).MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_Initialize_CreatesInternalIndicator()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Single(indicator.LinesSeries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
double basePrice = 100 + i;
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), basePrice, basePrice + 5, basePrice - 5, basePrice + 1, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
}
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.True(double.IsFinite(val));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 105 + i, 95 + i, 102 + i, 1000);
|
||||
}
|
||||
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(10), 110, 115, 105, 112, 1500);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
|
||||
Assert.Equal(2, indicator.LinesSeries[0].Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_ShowColdValues_CanBeToggled()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
|
||||
indicator.ShowColdValues = false;
|
||||
Assert.False(indicator.ShowColdValues);
|
||||
|
||||
indicator.ShowColdValues = true;
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_SourceCodeLink_IsValid()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Typprice.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_ComputesCorrectTypicalPrice()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
// O=100, H=110, L=90, C=105 → (100+110+90)/3 = 100.0
|
||||
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.Equal(300.0 * (1.0 / 3.0), val, 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TyppriceIndicator_IsHotImmediately()
|
||||
{
|
||||
var indicator = new TyppriceIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
||||
|
||||
double val = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.True(double.IsFinite(val));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
// Typprice Unit Tests
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class TyppriceTests
|
||||
{
|
||||
private readonly GBM _gbm;
|
||||
private const double Tolerance = 1e-10;
|
||||
|
||||
public TyppriceTests()
|
||||
{
|
||||
_gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.2, seed: 42);
|
||||
}
|
||||
|
||||
private TBarSeries GenerateBars(int count)
|
||||
{
|
||||
_gbm.Reset(DateTime.UtcNow.Ticks);
|
||||
return _gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
public void Constructor_DefaultParameters_SetsCorrectValues()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
Assert.Equal("Typprice", indicator.Name);
|
||||
Assert.Equal(1, indicator.WarmupPeriod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithSource_SubscribesToEvents()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var indicator = new Typprice(source);
|
||||
source.Add(new TValue(DateTime.UtcNow, 100.0));
|
||||
Assert.NotEqual(default, indicator.Last);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Basic Calculation Tests
|
||||
|
||||
[Fact]
|
||||
public void Update_Bar_ReturnsOHL3()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000);
|
||||
var result = indicator.Update(bar);
|
||||
// (100 + 110 + 90) * (1/3) = 100.0
|
||||
double expected = (100.0 + 110.0 + 90.0) * (1.0 / 3.0);
|
||||
Assert.Equal(expected, result.Value, Tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_Bar_MatchesTBarOHL3()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
var bar = new TBar(DateTime.UtcNow, 50, 60, 40, 55, 500);
|
||||
var result = indicator.Update(bar);
|
||||
Assert.Equal(bar.OHL3, result.Value, Tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_TValue_ReturnsIdentity()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
var result = indicator.Update(new TValue(DateTime.UtcNow, 42.0));
|
||||
Assert.Equal(42.0, result.Value, Tolerance);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State and Bar Correction Tests
|
||||
|
||||
[Fact]
|
||||
public void IsHot_AfterFirstBar_ReturnsTrue()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
Assert.False(indicator.IsHot);
|
||||
indicator.Update(new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000));
|
||||
Assert.True(indicator.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_IsNewFalse_RestoresPreviousState()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
indicator.Update(new TBar(time, 100, 110, 90, 105, 1000), isNew: true);
|
||||
indicator.Update(new TBar(time.AddMinutes(1), 105, 115, 95, 110, 1000), isNew: true);
|
||||
|
||||
var corrected = indicator.Update(new TBar(time.AddMinutes(1), 106, 120, 80, 111, 1000), isNew: false);
|
||||
double expected = (106.0 + 120.0 + 80.0) * (1.0 / 3.0);
|
||||
Assert.Equal(expected, corrected.Value, Tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_MultipleIsNewFalse_ProducesIdempotentResults()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
indicator.Update(new TBar(time, 100, 110, 90, 105, 1000), isNew: true);
|
||||
|
||||
var bar = new TBar(time.AddMinutes(1), 105, 115, 95, 110, 1000);
|
||||
var result1 = indicator.Update(bar, isNew: false);
|
||||
var result2 = indicator.Update(bar, isNew: false);
|
||||
var result3 = indicator.Update(bar, isNew: false);
|
||||
|
||||
Assert.Equal(result1.Value, result2.Value, Tolerance);
|
||||
Assert.Equal(result2.Value, result3.Value, Tolerance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ClearsState()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
indicator.Update(new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000));
|
||||
Assert.True(indicator.IsHot);
|
||||
|
||||
indicator.Reset();
|
||||
Assert.False(indicator.IsHot);
|
||||
Assert.Equal(default, indicator.Last);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region NaN/Infinity Robustness Tests
|
||||
|
||||
[Fact]
|
||||
public void Update_NaN_UsesLastValidValue()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
indicator.Update(new TBar(time, 100, 110, 90, 105, 1000), isNew: true);
|
||||
double validResult = indicator.Last.Value;
|
||||
|
||||
var nanBar = new TBar(time.AddMinutes(1), double.NaN, double.NaN, double.NaN, double.NaN, 1000);
|
||||
var result = indicator.Update(nanBar, isNew: true);
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
Assert.Equal(validResult, result.Value, Tolerance);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Consistency Tests (All Modes)
|
||||
|
||||
[Fact]
|
||||
public void AllModes_ProduceConsistentResults()
|
||||
{
|
||||
var bars = GenerateBars(100);
|
||||
|
||||
// Mode 1: Streaming
|
||||
var streaming = new Typprice();
|
||||
double[] streamingResults = new double[bars.Count];
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
streamingResults[i] = streaming.Update(bars[i], isNew: true).Value;
|
||||
}
|
||||
|
||||
// Mode 2: Batch (TBarSeries)
|
||||
var batchResult = Typprice.Batch(bars);
|
||||
|
||||
// Mode 3: Span batch
|
||||
double[] spanOutput = new double[bars.Count];
|
||||
Typprice.Batch(bars.OpenValues, bars.HighValues, bars.LowValues, spanOutput);
|
||||
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], batchResult.Values[i], Tolerance);
|
||||
Assert.Equal(streamingResults[i], spanOutput[i], Tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllBars_MatchTBarOHL3()
|
||||
{
|
||||
var bars = GenerateBars(50);
|
||||
var indicator = new Typprice();
|
||||
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
var result = indicator.Update(bars[i], isNew: true);
|
||||
Assert.Equal(bars[i].OHL3, result.Value, Tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Batch Validation Tests
|
||||
|
||||
[Fact]
|
||||
public void Batch_MismatchedLengths_ThrowsArgumentException()
|
||||
{
|
||||
double[] open = new double[10];
|
||||
double[] high = new double[5]; // mismatched
|
||||
double[] low = new double[10];
|
||||
double[] output = new double[10];
|
||||
|
||||
var ex = Assert.Throws<ArgumentException>(() => Typprice.Batch(open, high, low, output));
|
||||
Assert.Equal("high", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_OutputTooShort_ThrowsArgumentException()
|
||||
{
|
||||
double[] open = new double[10];
|
||||
double[] high = new double[10];
|
||||
double[] low = new double[10];
|
||||
double[] output = new double[5]; // too short
|
||||
|
||||
var ex = Assert.Throws<ArgumentException>(() => Typprice.Batch(open, high, low, output));
|
||||
Assert.Equal("output", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_EmptyInput_NoOutput()
|
||||
{
|
||||
var bars = new TBarSeries();
|
||||
var result = Typprice.Batch(bars);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_LargeDataset_NoStackOverflow()
|
||||
{
|
||||
var bars = GenerateBars(10_000);
|
||||
double[] output = new double[bars.Count];
|
||||
Typprice.Batch(bars.OpenValues, bars.HighValues, bars.LowValues, output);
|
||||
Assert.True(double.IsFinite(output[^1]));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Chaining Tests
|
||||
|
||||
[Fact]
|
||||
public void Pub_EventFires_OnUpdate()
|
||||
{
|
||||
var indicator = new Typprice();
|
||||
bool fired = false;
|
||||
indicator.Pub += (object? sender, in TValueEventArgs args) => fired = true;
|
||||
|
||||
indicator.Update(new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000));
|
||||
Assert.True(fired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Calculate_Static_ReturnsResultsAndIndicator()
|
||||
{
|
||||
var bars = GenerateBars(50);
|
||||
var (results, ind) = Typprice.Calculate(bars);
|
||||
Assert.Equal(bars.Count, results.Count);
|
||||
Assert.True(ind.IsHot);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Skender.Stock.Indicators;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Validation for Typprice (Typical Price) = (O+H+L)/3.
|
||||
/// Cross-validates against Skender.Stock.Indicators GetBaseQuote(CandlePart.OHL3),
|
||||
/// plus formula verification, streaming-vs-batch consistency, and determinism.
|
||||
/// </summary>
|
||||
public sealed class TyppriceValidationTests : IDisposable
|
||||
{
|
||||
private readonly ValidationTestData _data = new();
|
||||
private readonly ITestOutputHelper _output;
|
||||
private bool _disposed;
|
||||
|
||||
public TyppriceValidationTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed && disposing)
|
||||
{
|
||||
_data.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ── A) Skender OHL3 batch validation ──────────────────────────────────────
|
||||
[Fact]
|
||||
public void Validate_Against_Skender_OHL3_Batch()
|
||||
{
|
||||
// Skender GetBaseQuote(CandlePart.OHL3) computes (Open+High+Low)/3
|
||||
var skenderResults = _data.SkenderQuotes
|
||||
.GetBaseQuote(CandlePart.OHL3)
|
||||
.ToList();
|
||||
|
||||
var qlResult = Typprice.Batch(_data.Bars);
|
||||
|
||||
Assert.Equal(qlResult.Count, skenderResults.Count);
|
||||
|
||||
int count = qlResult.Count;
|
||||
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
|
||||
|
||||
for (int i = start; i < count; i++)
|
||||
{
|
||||
double qlVal = qlResult.Values[i];
|
||||
double skVal = skenderResults[i].Value;
|
||||
|
||||
Assert.True(
|
||||
Math.Abs(qlVal - skVal) <= ValidationHelper.SkenderTolerance,
|
||||
$"Mismatch at index {i}: QuanTAlib={qlVal:G17}, Skender={skVal:G17}, Diff={Math.Abs(qlVal - skVal):G17}");
|
||||
}
|
||||
|
||||
_output.WriteLine($"TYPPRICE vs Skender OHL3 batch: {count} bars, last {count - start} verified within {ValidationHelper.SkenderTolerance}: PASSED");
|
||||
}
|
||||
|
||||
// ── B) Skender OHL3 streaming validation ──────────────────────────────────
|
||||
[Fact]
|
||||
public void Validate_Against_Skender_OHL3_Streaming()
|
||||
{
|
||||
var skenderResults = _data.SkenderQuotes
|
||||
.GetBaseQuote(CandlePart.OHL3)
|
||||
.ToList();
|
||||
|
||||
var ind = new Typprice();
|
||||
int count = _data.Bars.Count;
|
||||
double[] streamValues = new double[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var result = ind.Update(_data.Bars[i], isNew: true);
|
||||
streamValues[i] = result.Value;
|
||||
}
|
||||
|
||||
// Verify last N bars
|
||||
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
|
||||
for (int i = start; i < count; i++)
|
||||
{
|
||||
double qlVal = streamValues[i];
|
||||
double skVal = skenderResults[i].Value;
|
||||
|
||||
Assert.True(
|
||||
Math.Abs(qlVal - skVal) <= ValidationHelper.SkenderTolerance,
|
||||
$"Mismatch at index {i}: QuanTAlib={qlVal:G17}, Skender={skVal:G17}");
|
||||
}
|
||||
|
||||
_output.WriteLine($"TYPPRICE streaming vs Skender OHL3: {count} bars, last {count - start} verified: PASSED");
|
||||
}
|
||||
|
||||
// ── C) Skender OHL3 span validation ───────────────────────────────────────
|
||||
[Fact]
|
||||
[SkipLocalsInit]
|
||||
public void Validate_Against_Skender_OHL3_Span()
|
||||
{
|
||||
var skenderResults = _data.SkenderQuotes
|
||||
.GetBaseQuote(CandlePart.OHL3)
|
||||
.ToList();
|
||||
|
||||
int count = _data.Bars.Count;
|
||||
double[] o = new double[count], h = new double[count], l = new double[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
o[i] = _data.Bars[i].Open;
|
||||
h[i] = _data.Bars[i].High;
|
||||
l[i] = _data.Bars[i].Low;
|
||||
}
|
||||
|
||||
var qlOut = new double[count];
|
||||
Typprice.Batch(o.AsSpan(), h.AsSpan(), l.AsSpan(), qlOut.AsSpan());
|
||||
|
||||
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
|
||||
for (int i = start; i < count; i++)
|
||||
{
|
||||
double qlVal = qlOut[i];
|
||||
double skVal = skenderResults[i].Value;
|
||||
|
||||
Assert.True(
|
||||
Math.Abs(qlVal - skVal) <= ValidationHelper.SkenderTolerance,
|
||||
$"Span mismatch at index {i}: QuanTAlib={qlVal:G17}, Skender={skVal:G17}");
|
||||
}
|
||||
|
||||
_output.WriteLine($"TYPPRICE span vs Skender OHL3: {count} bars, last {count - start} verified: PASSED");
|
||||
}
|
||||
|
||||
// ── D) Formula verification: (O+H+L)/3 ───────────────────────────────────
|
||||
[Fact]
|
||||
public void Validate_Formula_Manual()
|
||||
{
|
||||
var bar = new TBar(DateTime.UtcNow, open: 10.0, high: 18.0, low: 6.0, close: 15.0, volume: 1000);
|
||||
var ind = new Typprice();
|
||||
var result = ind.Update(bar, isNew: true);
|
||||
double expected = (10.0 + 18.0 + 6.0) / 3.0; // = 11.333...
|
||||
Assert.Equal(expected, result.Value, 1e-12);
|
||||
_output.WriteLine($"TYPPRICE formula: expected={expected}, actual={result.Value}: PASSED");
|
||||
}
|
||||
|
||||
// ── E) Streaming == Batch span ────────────────────────────────────────────
|
||||
[Fact]
|
||||
[SkipLocalsInit]
|
||||
public void Validate_Streaming_Equals_Batch()
|
||||
{
|
||||
const int N = 200;
|
||||
var gbm = new GBM(100.0, 0.05, 0.2, seed: 1002);
|
||||
var bars = new TBar[N];
|
||||
for (int i = 0; i < N; i++) { bars[i] = gbm.Next(isNew: true); }
|
||||
|
||||
// Streaming
|
||||
var ind = new Typprice();
|
||||
for (int i = 0; i < N; i++) { ind.Update(bars[i], isNew: true); }
|
||||
double streamVal = ind.Last.Value;
|
||||
|
||||
// Batch span
|
||||
double[] o = new double[N], h = new double[N], l = new double[N];
|
||||
for (int i = 0; i < N; i++) { o[i] = bars[i].Open; h[i] = bars[i].High; l[i] = bars[i].Low; }
|
||||
var qlOut = new double[N];
|
||||
Typprice.Batch(o.AsSpan(), h.AsSpan(), l.AsSpan(), qlOut.AsSpan());
|
||||
|
||||
_output.WriteLine($"Streaming={streamVal:F10}, Batch={qlOut[N - 1]:F10}");
|
||||
Assert.Equal(streamVal, qlOut[N - 1], 1e-12);
|
||||
}
|
||||
|
||||
// ── F) Matches TBar.OHL3 property ─────────────────────────────────────────
|
||||
[Fact]
|
||||
public void Validate_MatchesTBarOHL3()
|
||||
{
|
||||
const int N = 100;
|
||||
var gbm = new GBM(100.0, 0.05, 0.2, seed: 2001);
|
||||
var ind = new Typprice();
|
||||
|
||||
int mismatches = 0;
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
var bar = gbm.Next(isNew: true);
|
||||
var result = ind.Update(bar, isNew: true);
|
||||
double err = Math.Abs(result.Value - bar.OHL3);
|
||||
if (err > 1e-12) { mismatches++; }
|
||||
}
|
||||
|
||||
_output.WriteLine($"TBar.OHL3 comparison: {N} bars, {mismatches} mismatches");
|
||||
Assert.Equal(0, mismatches);
|
||||
}
|
||||
|
||||
// ── G) Batch(TBarSeries) == Calculate ─────────────────────────────────────
|
||||
[Fact]
|
||||
public void Validate_BatchBarSeries_Equals_Calculate()
|
||||
{
|
||||
var (results, _) = Typprice.Calculate(_data.Bars);
|
||||
var batchResult = Typprice.Batch(_data.Bars);
|
||||
|
||||
for (int i = 0; i < _data.Bars.Count; i++)
|
||||
{
|
||||
Assert.Equal(batchResult.Values[i], results.Values[i], 1e-12);
|
||||
}
|
||||
_output.WriteLine("TYPPRICE Batch(TBarSeries) == Calculate: PASSED");
|
||||
}
|
||||
|
||||
// ── H) Determinism ────────────────────────────────────────────────────────
|
||||
[Fact]
|
||||
public void Validate_Deterministic()
|
||||
{
|
||||
var r1 = Typprice.Batch(_data.Bars);
|
||||
var r2 = Typprice.Batch(_data.Bars);
|
||||
for (int i = 0; i < r1.Count; i++) { Assert.Equal(r1.Values[i], r2.Values[i], 15); }
|
||||
_output.WriteLine("TYPPRICE determinism: PASSED");
|
||||
}
|
||||
|
||||
// ── I) Skender OC2 structural validation (bonus) ──────────────────────────
|
||||
[Fact]
|
||||
public void Validate_Skender_OC2_MatchesTBarOC2()
|
||||
{
|
||||
// Verify Skender CandlePart.OC2 = (Open+Close)/2 matches TBar.OC2
|
||||
var skenderResults = _data.SkenderQuotes
|
||||
.GetBaseQuote(CandlePart.OC2)
|
||||
.ToList();
|
||||
|
||||
int count = _data.Bars.Count;
|
||||
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
|
||||
|
||||
for (int i = start; i < count; i++)
|
||||
{
|
||||
double skVal = skenderResults[i].Value;
|
||||
double tbarVal = _data.Bars[i].OC2;
|
||||
|
||||
Assert.True(
|
||||
Math.Abs(skVal - tbarVal) <= ValidationHelper.SkenderTolerance,
|
||||
$"OC2 mismatch at {i}: Skender={skVal:G17}, TBar={tbarVal:G17}");
|
||||
}
|
||||
|
||||
_output.WriteLine($"Skender OC2 vs TBar.OC2: {count} bars, last {count - start} verified: PASSED");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user