mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 20:48: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,79 @@
|
||||
using TradingPlatform.BusinessLayer;
|
||||
using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class HtTrendmodeIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void HtTrendmodeIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new HtTrendmodeIndicator();
|
||||
|
||||
Assert.Equal(SourceType.Close, indicator.SourceInput);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("HT_TRENDMODE - Ehlers Hilbert Transform Trend vs Cycle Mode", indicator.Name);
|
||||
Assert.True(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmodeIndicator_MinHistoryDepths_EqualsZero()
|
||||
{
|
||||
var indicator = new HtTrendmodeIndicator();
|
||||
|
||||
Assert.Equal(0, HtTrendmodeIndicator.MinHistoryDepths);
|
||||
IWatchlistIndicator watchlistIndicator = indicator;
|
||||
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmodeIndicator_Initialize_CreatesInternalIndicator()
|
||||
{
|
||||
var indicator = new HtTrendmodeIndicator();
|
||||
|
||||
// Initialize should not throw
|
||||
indicator.Initialize();
|
||||
|
||||
// After init, line series should exist (TrendMode)
|
||||
Assert.Single(indicator.LinesSeries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmodeIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new HtTrendmodeIndicator();
|
||||
indicator.Initialize();
|
||||
|
||||
// Add historical data
|
||||
var now = DateTime.UtcNow;
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100 + i, 110 + i, 90 + i, 105 + i);
|
||||
|
||||
// Process update for each bar to simulate history loading
|
||||
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
||||
indicator.ProcessUpdate(args);
|
||||
}
|
||||
|
||||
// Line series should have a value
|
||||
double trendMode = indicator.LinesSeries[0].GetValue(0);
|
||||
|
||||
Assert.True(double.IsFinite(trendMode));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmodeIndicator_ShortName_IsCorrect()
|
||||
{
|
||||
var indicator = new HtTrendmodeIndicator();
|
||||
Assert.Equal("HT_TRENDMODE", indicator.ShortName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmodeIndicator_SourceCodeLink_IsValid()
|
||||
{
|
||||
var indicator = new HtTrendmodeIndicator();
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("HtTrendmode.Quantower.cs", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
namespace QuanTAlib;
|
||||
|
||||
public class HtTrendmodeTests
|
||||
{
|
||||
[Fact]
|
||||
public void HtTrendmode_BasicConstruction()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
Assert.Equal("HtTrendmode", indicator.Name);
|
||||
Assert.Equal(63, indicator.WarmupPeriod); // TA-Lib lookback period
|
||||
Assert.False(indicator.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_WarmupPeriod()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Feed warmup data - TA-Lib requires 63 bars for lookback
|
||||
for (int i = 0; i < 70; i++)
|
||||
{
|
||||
_ = indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i));
|
||||
|
||||
if (i < 63)
|
||||
{
|
||||
Assert.False(indicator.IsHot, $"Should not be hot at bar {i}");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(indicator.IsHot, "Should be hot after warmup period");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_OutputsBinaryValues()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Use GBM-generated price data
|
||||
var gbm = new GBM(seed: 42);
|
||||
var bars = gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
var result = indicator.Update(bars[i].C);
|
||||
|
||||
// After warmup, output should be 0 or 1
|
||||
if (i >= 40)
|
||||
{
|
||||
Assert.True(result.Value == 0.0 || result.Value == 1.0,
|
||||
$"TrendMode should be 0 or 1, got {result.Value} at bar {i}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_TrendModeProperty()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Feed data
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i * 0.5));
|
||||
}
|
||||
|
||||
// TrendMode property should match output
|
||||
int trendMode = indicator.TrendMode;
|
||||
Assert.True(trendMode == 0 || trendMode == 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_SmoothPeriodProperty()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Feed data
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + Math.Sin(i * 0.2) * 10));
|
||||
}
|
||||
|
||||
// SmoothPeriod should be in valid range
|
||||
double smoothPeriod = indicator.SmoothPeriod;
|
||||
Assert.True(smoothPeriod >= 6.0 && smoothPeriod <= 50.0,
|
||||
$"SmoothPeriod {smoothPeriod} should be between 6 and 50");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_InstPeriodProperty()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Feed data
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + Math.Sin(i * 0.3) * 8));
|
||||
}
|
||||
|
||||
// InstPeriod should be positive
|
||||
double instPeriod = indicator.InstPeriod;
|
||||
Assert.True(instPeriod > 0, $"InstPeriod {instPeriod} should be positive");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_TrendingData_ShouldDetectTrend()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Strong trend: monotonically increasing
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i * 2.0));
|
||||
}
|
||||
|
||||
// With strong trend, inst_period should be larger → trend mode likely
|
||||
// (exact behavior depends on Hilbert Transform dynamics)
|
||||
int trendMode = indicator.TrendMode;
|
||||
Assert.True(trendMode == 0 || trendMode == 1, "Should output valid trend mode");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_CyclicalData_ShouldDetectCycle()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Pure sinusoidal data (strong cycle)
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
double value = 100.0 + Math.Sin(i * 0.4) * 10.0;
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), value));
|
||||
}
|
||||
|
||||
// With cyclical data, smooth_period and inst_period should be closer
|
||||
int trendMode = indicator.TrendMode;
|
||||
Assert.True(trendMode == 0 || trendMode == 1, "Should output valid trend mode");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_HandlesNaN()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Prime with valid data
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i));
|
||||
}
|
||||
|
||||
// Feed NaN - should use last valid value
|
||||
var resultNaN = indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(50), double.NaN));
|
||||
Assert.True(double.IsFinite(resultNaN.Value), "Should handle NaN gracefully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_HandlesInfinity()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Prime with valid data
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i));
|
||||
}
|
||||
|
||||
// Feed Infinity - should use last valid value
|
||||
var resultInf = indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(50), double.PositiveInfinity));
|
||||
Assert.True(double.IsFinite(resultInf.Value), "Should handle Infinity gracefully");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_Reset()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Process enough data to be hot (warmup = 63)
|
||||
for (int i = 0; i < 70; i++)
|
||||
{
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i));
|
||||
}
|
||||
|
||||
Assert.True(indicator.IsHot, "Should be hot after warmup");
|
||||
|
||||
// Reset
|
||||
indicator.Reset();
|
||||
|
||||
Assert.False(indicator.IsHot);
|
||||
Assert.Equal(0, indicator.TrendMode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_BatchUpdate()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
var series = new TSeries();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow.AddMinutes(i), 100.0 + Math.Sin(i * 0.2) * 10);
|
||||
}
|
||||
|
||||
var result = indicator.Update(series);
|
||||
|
||||
Assert.Equal(100, result.Count);
|
||||
|
||||
// All values after warmup should be 0 or 1
|
||||
for (int i = 40; i < result.Count; i++)
|
||||
{
|
||||
Assert.True(result.Values[i] == 0.0 || result.Values[i] == 1.0,
|
||||
$"Batch result at {i} should be 0 or 1, got {result.Values[i]}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_StaticCalculate_SpanVersion()
|
||||
{
|
||||
double[] input = new double[100];
|
||||
double[] output = new double[100];
|
||||
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
input[i] = 100.0 + Math.Sin(i * 0.15) * 8;
|
||||
}
|
||||
|
||||
HtTrendmode.Batch(input.AsSpan(), output.AsSpan());
|
||||
|
||||
// After warmup, all values should be 0 or 1
|
||||
for (int i = 40; i < output.Length; i++)
|
||||
{
|
||||
Assert.True(output[i] == 0.0 || output[i] == 1.0,
|
||||
$"Static Calculate at {i} should be 0 or 1, got {output[i]}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_StaticCalculate_TSeriesVersion()
|
||||
{
|
||||
var series = new TSeries();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow.AddMinutes(i), 100.0 + Math.Sin(i * 0.25) * 12);
|
||||
}
|
||||
|
||||
var result = HtTrendmode.Batch(series);
|
||||
|
||||
Assert.Equal(100, result.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_BarCorrection_IsNewFalse()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Prime indicator
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0 + i));
|
||||
}
|
||||
|
||||
// Get baseline
|
||||
_ = indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(50), 150.0), isNew: true);
|
||||
|
||||
// Update same bar with different value
|
||||
var corrected = indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(50), 152.0), isNew: false);
|
||||
|
||||
// Should reflect the corrected value
|
||||
Assert.True(corrected.Value == 0.0 || corrected.Value == 1.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_StreamingVsBatch_Consistency()
|
||||
{
|
||||
var streamingIndicator = new HtTrendmode();
|
||||
var batchIndicator = new HtTrendmode();
|
||||
|
||||
var series = new TSeries();
|
||||
var streamingResults = new List<double>();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
double value = 100.0 + Math.Sin(i * 0.2) * 10 + Math.Cos(i * 0.3) * 5;
|
||||
series.Add(DateTime.UtcNow.AddMinutes(i), value);
|
||||
|
||||
var result = streamingIndicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), value));
|
||||
streamingResults.Add(result.Value);
|
||||
}
|
||||
|
||||
var batchResult = batchIndicator.Update(series);
|
||||
|
||||
// Compare streaming vs batch
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], batchResult.Values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_Prime()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Prime with enough data to be hot (warmup = 63)
|
||||
double[] primeData = new double[70];
|
||||
for (int i = 0; i < primeData.Length; i++)
|
||||
{
|
||||
primeData[i] = 100.0 + i * 0.5;
|
||||
}
|
||||
|
||||
indicator.Prime(primeData);
|
||||
|
||||
Assert.True(indicator.IsHot, "Should be hot after priming");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_EmptySource()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
var emptySeries = new TSeries();
|
||||
|
||||
var result = indicator.Update(emptySeries);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_ConstantPrice_ShouldNotCrash()
|
||||
{
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Constant price (degenerate case)
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var result = indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), 100.0));
|
||||
Assert.True(double.IsFinite(result.Value), $"Result should be finite at bar {i}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using TALib;
|
||||
using QuanTAlib.Tests;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// Validation tests for HtTrendmode against TA-Lib reference implementation.
|
||||
/// Note: TA-Lib's HT_TRENDMODE is the reference for this indicator.
|
||||
/// </summary>
|
||||
public sealed class HtTrendmodeValidationTests : IDisposable
|
||||
{
|
||||
private readonly ValidationTestData _data;
|
||||
|
||||
public HtTrendmodeValidationTests()
|
||||
{
|
||||
_data = new ValidationTestData();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_data.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_OutputsValidBinaryValues()
|
||||
{
|
||||
// Arrange
|
||||
var indicator = new HtTrendmode();
|
||||
var results = new List<double>();
|
||||
var closeSpan = _data.GetCloseSpan();
|
||||
var timestamps = _data.Timestamps.Span;
|
||||
|
||||
// Act - Process data
|
||||
for (int i = 0; i < _data.Count; i++)
|
||||
{
|
||||
var result = indicator.Update(new TValue(timestamps[i], closeSpan[i]));
|
||||
results.Add(result.Value);
|
||||
}
|
||||
|
||||
// Assert - After warmup, all values should be 0 or 1
|
||||
for (int i = 50; i < results.Count; i++)
|
||||
{
|
||||
double value = results[i];
|
||||
Assert.True(value == 0.0 || value == 1.0,
|
||||
$"TrendMode at index {i} should be 0 or 1, got {value}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_SmoothPeriod_InValidRange()
|
||||
{
|
||||
// Arrange
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Act - Process with sinusoidal data
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
double value = 100.0 + (Math.Sin(i * 0.2) * 10.0) + (Math.Sin(i * 0.05) * 5.0);
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), value));
|
||||
}
|
||||
|
||||
// Assert - SmoothPeriod should be in valid range [6, 50]
|
||||
double smoothPeriod = indicator.SmoothPeriod;
|
||||
Assert.True(smoothPeriod >= 6.0 && smoothPeriod <= 50.0,
|
||||
$"SmoothPeriod {smoothPeriod} should be between 6 and 50");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_InstPeriod_Positive()
|
||||
{
|
||||
// Arrange
|
||||
var indicator = new HtTrendmode();
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
double value = 100.0 + (Math.Sin(i * 0.15) * 8.0);
|
||||
indicator.Update(new TValue(DateTime.UtcNow.AddMinutes(i), value));
|
||||
}
|
||||
|
||||
// Assert
|
||||
double instPeriod = indicator.InstPeriod;
|
||||
Assert.True(instPeriod > 0, $"InstPeriod should be positive, got {instPeriod}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_StreamingVsBatch_Equal()
|
||||
{
|
||||
// Arrange
|
||||
var streamingIndicator = new HtTrendmode();
|
||||
var streamingResults = new List<double>();
|
||||
var closeSpan = _data.GetCloseSpan();
|
||||
var timestamps = _data.Timestamps.Span;
|
||||
|
||||
// Act - Streaming
|
||||
var series = new TSeries();
|
||||
for (int i = 0; i < _data.Count; i++)
|
||||
{
|
||||
series.Add(timestamps[i], closeSpan[i]);
|
||||
var result = streamingIndicator.Update(new TValue(timestamps[i], closeSpan[i]));
|
||||
streamingResults.Add(result.Value);
|
||||
}
|
||||
|
||||
// Act - Batch
|
||||
var batchResult = HtTrendmode.Batch(series);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(streamingResults.Count, batchResult.Count);
|
||||
for (int i = 0; i < streamingResults.Count; i++)
|
||||
{
|
||||
Assert.Equal(streamingResults[i], batchResult.Values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_TrendModeLogic_TALibAlgorithm()
|
||||
{
|
||||
// Arrange - Our implementation now follows TA-Lib's Ehlers algorithm
|
||||
var indicator = new HtTrendmode();
|
||||
var closeSpan = _data.GetCloseSpan();
|
||||
var timestamps = _data.Timestamps.Span;
|
||||
|
||||
// Act - Prime the indicator with enough data
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
indicator.Update(new TValue(timestamps[i], closeSpan[i]));
|
||||
}
|
||||
|
||||
// Assert - TA-Lib TrendMode: binary 0 or 1, using multi-criteria:
|
||||
// 1. SineWave crossings reset daysInTrend
|
||||
// 2. daysInTrend >= 0.5 * smoothPeriod → trending
|
||||
// 3. Phase rate check (normal range → cycle mode)
|
||||
// 4. Price-trendline deviation ≥1.5% → trend override
|
||||
int trendMode = indicator.TrendMode;
|
||||
Assert.True(trendMode == 0 || trendMode == 1, $"TrendMode should be 0 or 1, got {trendMode}");
|
||||
|
||||
// Verify DaysInTrend property works
|
||||
Assert.True(indicator.DaysInTrend >= 0, "DaysInTrend should be non-negative");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests TA-Lib validation. Our implementation now follows TA-Lib's Ehlers algorithm.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MatchesTalib()
|
||||
{
|
||||
// Arrange
|
||||
var indicator = new HtTrendmode();
|
||||
var results = new List<double>();
|
||||
var closeSpan = _data.GetCloseSpan();
|
||||
var timestamps = _data.Timestamps.Span;
|
||||
|
||||
// Act - Process data
|
||||
for (int i = 0; i < _data.Count; i++)
|
||||
{
|
||||
var result = indicator.Update(new TValue(timestamps[i], closeSpan[i]));
|
||||
results.Add(result.Value);
|
||||
}
|
||||
|
||||
// Get TA-Lib results
|
||||
double[] inReal = closeSpan.ToArray();
|
||||
int[] outInteger = new int[inReal.Length];
|
||||
|
||||
var retCode = Functions.HtTrendMode(inReal, 0..^0, outInteger, out var outRange);
|
||||
Assert.Equal(TALib.Core.RetCode.Success, retCode);
|
||||
|
||||
// Compare after warmup
|
||||
int lookback = Functions.HtTrendModeLookback();
|
||||
double[] talibResults = outInteger.Select(x => (double)x).ToArray();
|
||||
ValidationHelper.VerifyData(results, talibResults, outRange, lookback);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_DeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
var indicator1 = new HtTrendmode();
|
||||
var indicator2 = new HtTrendmode();
|
||||
var closeSpan = _data.GetCloseSpan();
|
||||
var timestamps = _data.Timestamps.Span;
|
||||
|
||||
// Act - Same data, same results
|
||||
var results1 = new List<double>();
|
||||
var results2 = new List<double>();
|
||||
|
||||
for (int i = 0; i < _data.Count; i++)
|
||||
{
|
||||
var r1 = indicator1.Update(new TValue(timestamps[i], closeSpan[i]));
|
||||
var r2 = indicator2.Update(new TValue(timestamps[i], closeSpan[i]));
|
||||
results1.Add(r1.Value);
|
||||
results2.Add(r2.Value);
|
||||
}
|
||||
|
||||
// Assert - Deterministic
|
||||
for (int i = 0; i < results1.Count; i++)
|
||||
{
|
||||
Assert.Equal(results1[i], results2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HtTrendmode_Correction_Recomputes()
|
||||
{
|
||||
var ind = new HtTrendmode();
|
||||
var t0 = new DateTime(946_684_800_000_000_0L, DateTimeKind.Utc);
|
||||
|
||||
// Build state well past warmup
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
ind.Update(new TValue(t0.AddMinutes(i),
|
||||
100.0 + (10.0 * Math.Sin(2.0 * Math.PI * i / 20.0))), isNew: true);
|
||||
}
|
||||
|
||||
// Anchor bar
|
||||
var anchorTime = t0.AddMinutes(100);
|
||||
const double anchorPrice = 105.5;
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: true);
|
||||
double anchorSmooth = ind.SmoothPeriod;
|
||||
|
||||
// Correction with a dramatically different price — SmoothPeriod must change
|
||||
ind.Update(new TValue(anchorTime, anchorPrice * 10.0), isNew: false);
|
||||
Assert.NotEqual(anchorSmooth, ind.SmoothPeriod);
|
||||
|
||||
// Correction back to original price — must exactly restore original SmoothPeriod
|
||||
ind.Update(new TValue(anchorTime, anchorPrice), isNew: false);
|
||||
Assert.Equal(anchorSmooth, ind.SmoothPeriod, 1e-9);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user