mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 10:38:05 +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,270 @@
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class NormalizeTests
|
||||
{
|
||||
private readonly GBM _gbm = new(100, 0.05, 0.2, seed: 42);
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Constructor_ValidPeriod_SetsProperties()
|
||||
{
|
||||
var norm = new Normalize(20);
|
||||
|
||||
Assert.Equal("Normalize(20)", norm.Name);
|
||||
Assert.Equal(20, norm.WarmupPeriod);
|
||||
Assert.False(norm.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Constructor_InvalidPeriod_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new Normalize(0));
|
||||
Assert.Throws<ArgumentException>(() => new Normalize(-1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Update_BasicCalculation()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// Feed values: 10, 20, 30, 40, 50
|
||||
// After 5 values: min=10, max=50, range=40
|
||||
// Current value 50: (50-10)/40 = 1.0
|
||||
norm.Update(new TValue(DateTime.UtcNow, 10));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 20));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 30));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 40));
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
|
||||
Assert.Equal(1.0, result.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Update_MinValueReturnsZero()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 40));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 30));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 20));
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, 10));
|
||||
|
||||
// min=10, max=50, value=10: (10-10)/40 = 0.0
|
||||
Assert.Equal(0.0, result.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Update_MidValueReturnsFifty()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 25));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 75));
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
|
||||
// min=0, max=100, value=50: (50-0)/100 = 0.5
|
||||
Assert.Equal(0.5, result.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Update_FlatRange_ReturnsHalf()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// All same values
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
|
||||
// Flat range returns 0.5
|
||||
Assert.Equal(0.5, result.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Update_IsNew_False_RollsBack()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
|
||||
var result1 = norm.Update(new TValue(DateTime.UtcNow, 25), isNew: true);
|
||||
var result2 = norm.Update(new TValue(DateTime.UtcNow, 75), isNew: false);
|
||||
|
||||
// Both should use the same buffer state before the update
|
||||
// The last isNew=false should overwrite the isNew=true result
|
||||
Assert.NotEqual(result1.Value, result2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Update_NaN_UsesLastValid()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
var valid = norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
|
||||
var nanResult = norm.Update(new TValue(DateTime.UtcNow, double.NaN));
|
||||
|
||||
Assert.Equal(valid.Value, nanResult.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Update_Infinity_UsesLastValid()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
var valid = norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
|
||||
var infResult = norm.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
|
||||
|
||||
Assert.Equal(valid.Value, infResult.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_IsHot_BecomesTrue_AfterWarmup()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, i * 10));
|
||||
Assert.False(norm.IsHot);
|
||||
}
|
||||
|
||||
norm.Update(new TValue(DateTime.UtcNow, 40));
|
||||
Assert.True(norm.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Reset_ClearsState()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, i * 10));
|
||||
}
|
||||
|
||||
Assert.True(norm.IsHot);
|
||||
|
||||
norm.Reset();
|
||||
|
||||
Assert.False(norm.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_OutputAlwaysInRange()
|
||||
{
|
||||
var norm = new Normalize(20);
|
||||
var series = _gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in series)
|
||||
{
|
||||
var result = norm.Update(new TValue(bar.Time, bar.Close));
|
||||
Assert.True(result.Value >= 0.0 && result.Value <= 1.0,
|
||||
$"Normalize output {result.Value} should be in [0, 1]");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Chaining_WorksCorrectly()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var norm = new Normalize(source, 10);
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
source.Add(new TValue(DateTime.UtcNow.AddSeconds(i), i * 5));
|
||||
}
|
||||
|
||||
Assert.True(norm.IsHot);
|
||||
// Last value is 95 (19*5), min in last 10 is 50 (10*5), max is 95
|
||||
// (95 - 50) / (95 - 50) = 1.0
|
||||
Assert.Equal(1.0, norm.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_StaticCalculate_TSeries_MatchesStreaming()
|
||||
{
|
||||
var series = _gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var tseries = new TSeries();
|
||||
foreach (var bar in series)
|
||||
{
|
||||
tseries.Add(new TValue(bar.Time, bar.Close), true);
|
||||
}
|
||||
|
||||
// Static calculation
|
||||
var staticResult = Normalize.Batch(tseries, 14);
|
||||
|
||||
// Streaming calculation
|
||||
var streamNorm = new Normalize(14);
|
||||
var streamResult = new TSeries();
|
||||
foreach (var bar in series)
|
||||
{
|
||||
streamResult.Add(streamNorm.Update(new TValue(bar.Time, bar.Close)), true);
|
||||
}
|
||||
|
||||
// Compare last 50 values
|
||||
for (int i = 50; i < 100; i++)
|
||||
{
|
||||
Assert.Equal(staticResult[i].Value, streamResult[i].Value, 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_StaticCalculate_Span_MatchesStreaming()
|
||||
{
|
||||
var series = _gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
double[] values = series.Select(b => b.Close).ToArray();
|
||||
double[] output = new double[values.Length];
|
||||
|
||||
// Span calculation
|
||||
Normalize.Batch(values, output, 14);
|
||||
|
||||
// Streaming calculation
|
||||
var norm = new Normalize(14);
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, values[i]));
|
||||
Assert.Equal(output[i], result.Value, 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_StaticCalculate_Span_ValidatesParameters()
|
||||
{
|
||||
double[] source = { 1, 2, 3, 4, 5 };
|
||||
double[] output = new double[5];
|
||||
|
||||
Assert.Throws<ArgumentException>(() => Normalize.Batch(Array.Empty<double>(), output));
|
||||
Assert.Throws<ArgumentException>(() => Normalize.Batch(source, new double[3]));
|
||||
Assert.Throws<ArgumentException>(() => Normalize.Batch(source, output, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_RollingWindow_DropsOldValues()
|
||||
{
|
||||
var norm = new Normalize(3);
|
||||
|
||||
// Feed: 0, 100, 50 -> range [0, 100]
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
|
||||
// Feed: 60, now window is [100, 50, 60] -> range [50, 100]
|
||||
// 60 in range [50, 100]: (60-50)/50 = 0.2
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, 60));
|
||||
Assert.Equal(0.2, result.Value, 1e-10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Validation tests for Normalize indicator.
|
||||
/// Since Normalize is a basic mathematical transformation, validation focuses on
|
||||
/// mathematical properties rather than external library comparison.
|
||||
/// </summary>
|
||||
public class NormalizeValidationTests
|
||||
{
|
||||
private readonly GBM _gbm = new(100, 0.05, 0.2, seed: 42);
|
||||
|
||||
[Fact]
|
||||
public void Normalize_OutputBounds_AlwaysZeroToOne()
|
||||
{
|
||||
// Test across multiple periods and data sets
|
||||
int[] periods = { 5, 14, 50, 100 };
|
||||
|
||||
foreach (var period in periods)
|
||||
{
|
||||
var norm = new Normalize(period);
|
||||
var series = _gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in series)
|
||||
{
|
||||
var result = norm.Update(new TValue(bar.Time, bar.Close));
|
||||
Assert.True(result.Value >= 0.0 && result.Value <= 1.0,
|
||||
$"Period {period}: output {result.Value} not in [0,1]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_MaxInWindow_ReturnsOne()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// Create ascending sequence
|
||||
double[] values = { 10, 20, 30, 40, 50 };
|
||||
|
||||
foreach (var v in values)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, v));
|
||||
}
|
||||
|
||||
// Max value (50) should normalize to 1.0
|
||||
Assert.Equal(1.0, norm.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_MinInWindow_ReturnsZero()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// Create descending sequence ending at min
|
||||
double[] values = { 50, 40, 30, 20, 10 };
|
||||
|
||||
foreach (var v in values)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, v));
|
||||
}
|
||||
|
||||
// Min value (10) should normalize to 0.0
|
||||
Assert.Equal(0.0, norm.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_LinearMapping_Correct()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// Set up window with known range [0, 100]
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50)); // Placeholder
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50)); // Placeholder
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50)); // Placeholder
|
||||
|
||||
// Test various values - (value - 0) / (100 - 0) = value / 100
|
||||
double[] testValues = { 0, 25, 50, 75, 100 };
|
||||
double[] expected = { 0.0, 0.25, 0.5, 0.75, 1.0 };
|
||||
|
||||
for (int i = 0; i < testValues.Length; i++)
|
||||
{
|
||||
// Reset and refill to maintain window [0, 100, test, test, test]
|
||||
norm.Reset();
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, testValues[i]));
|
||||
norm.Update(new TValue(DateTime.UtcNow, testValues[i]));
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, testValues[i]));
|
||||
|
||||
Assert.Equal(expected[i], result.Value, 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_ConstantInput_ReturnsHalf()
|
||||
{
|
||||
var norm = new Normalize(10);
|
||||
|
||||
// All same values
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
norm.Update(new TValue(DateTime.UtcNow, 42.0));
|
||||
}
|
||||
|
||||
// Flat range: should return 0.5
|
||||
Assert.Equal(0.5, norm.Last.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_RollingWindow_AdaptsToNewRange()
|
||||
{
|
||||
var norm = new Normalize(3);
|
||||
|
||||
// Initial window [10, 20, 30] - range 20
|
||||
norm.Update(new TValue(DateTime.UtcNow, 10));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 20));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 30));
|
||||
|
||||
// Value 25 in range [10, 30]: (25-10)/(30-10) = 0.75
|
||||
var result1 = norm.Update(new TValue(DateTime.UtcNow, 25));
|
||||
// Window is now [20, 30, 25], range [20, 30]
|
||||
// (25-20)/(30-20) = 0.5
|
||||
Assert.Equal(0.5, result1.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_NegativeValues_WorksCorrectly()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// Range from -50 to +50
|
||||
norm.Update(new TValue(DateTime.UtcNow, -50));
|
||||
norm.Update(new TValue(DateTime.UtcNow, -25));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 25));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
|
||||
// max=50, value=50: (50-(-50))/(50-(-50)) = 100/100 = 1.0
|
||||
Assert.Equal(1.0, norm.Last.Value, 1e-10);
|
||||
|
||||
// Test zero: (0-(-50))/(50-(-50)) = 50/100 = 0.5
|
||||
norm.Reset();
|
||||
norm.Update(new TValue(DateTime.UtcNow, -50));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
var zeroResult = norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
Assert.Equal(0.5, zeroResult.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_SmallRange_HighPrecision()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// Very small range
|
||||
double baseVal = 100.0;
|
||||
double epsilon = 1e-8;
|
||||
|
||||
norm.Update(new TValue(DateTime.UtcNow, baseVal));
|
||||
norm.Update(new TValue(DateTime.UtcNow, baseVal + epsilon));
|
||||
norm.Update(new TValue(DateTime.UtcNow, baseVal + epsilon / 2));
|
||||
norm.Update(new TValue(DateTime.UtcNow, baseVal + epsilon / 4));
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, baseVal + epsilon * 0.75));
|
||||
|
||||
// Should be in valid range
|
||||
Assert.True(result.Value >= 0.0 && result.Value <= 1.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_LargeRange_StillPrecise()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// Very large range
|
||||
norm.Update(new TValue(DateTime.UtcNow, -1e10));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 1e10));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
|
||||
// 0 in range [-1e10, 1e10]: (0 - (-1e10)) / (2e10) = 0.5
|
||||
Assert.Equal(0.5, result.Value, 1e-6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_StreamingVsBatch_Match()
|
||||
{
|
||||
var series = _gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
double[] values = series.Select(b => b.Close).ToArray();
|
||||
|
||||
// Streaming
|
||||
var streamNorm = new Normalize(14);
|
||||
var streamResults = new double[values.Length];
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
streamResults[i] = streamNorm.Update(new TValue(DateTime.UtcNow, values[i])).Value;
|
||||
}
|
||||
|
||||
// Batch
|
||||
double[] batchResults = new double[values.Length];
|
||||
Normalize.Batch(values, batchResults, 14);
|
||||
|
||||
// Compare all values
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
Assert.Equal(batchResults[i], streamResults[i], 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_AllModes_Consistent()
|
||||
{
|
||||
var series = _gbm.Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
int period = 14;
|
||||
|
||||
// Mode 1: Streaming via Update(TValue)
|
||||
var norm1 = new Normalize(period);
|
||||
var results1 = new List<double>();
|
||||
foreach (var bar in series)
|
||||
{
|
||||
results1.Add(norm1.Update(new TValue(bar.Time, bar.Close)).Value);
|
||||
}
|
||||
|
||||
// Mode 2: Batch via Update(TSeries)
|
||||
var tseries = new TSeries();
|
||||
foreach (var bar in series)
|
||||
{
|
||||
tseries.Add(new TValue(bar.Time, bar.Close), true);
|
||||
}
|
||||
|
||||
var results2 = Normalize.Batch(tseries, period);
|
||||
|
||||
// Mode 3: Static span Calculate
|
||||
double[] values = series.Select(b => b.Close).ToArray();
|
||||
double[] results3 = new double[values.Length];
|
||||
Normalize.Batch(values, results3, period);
|
||||
|
||||
// Mode 4: Event-based chaining
|
||||
var source = new TSeries();
|
||||
var norm4 = new Normalize(source, period);
|
||||
foreach (var bar in series)
|
||||
{
|
||||
source.Add(new TValue(bar.Time, bar.Close), true);
|
||||
}
|
||||
|
||||
var results4 = norm4.Last.Value;
|
||||
|
||||
// Compare all modes (use last 50 values for stability)
|
||||
for (int i = 50; i < 100; i++)
|
||||
{
|
||||
Assert.Equal(results1[i], results2[i].Value, 1e-10);
|
||||
Assert.Equal(results1[i], results3[i], 1e-10);
|
||||
}
|
||||
// Verify Mode 4 matches last value from other modes
|
||||
Assert.Equal(results1[^1], results4, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_BarCorrection_WorksCorrectly()
|
||||
{
|
||||
var norm = new Normalize(5);
|
||||
|
||||
// Build up buffer
|
||||
norm.Update(new TValue(DateTime.UtcNow, 0));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 100));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
norm.Update(new TValue(DateTime.UtcNow, 50));
|
||||
|
||||
// New bar
|
||||
var first = norm.Update(new TValue(DateTime.UtcNow, 75), isNew: true);
|
||||
|
||||
// Correction (same bar, different value)
|
||||
var corrected = norm.Update(new TValue(DateTime.UtcNow, 25), isNew: false);
|
||||
|
||||
// Values should be different
|
||||
Assert.NotEqual(first.Value, corrected.Value);
|
||||
|
||||
// Further correction should still work
|
||||
var corrected2 = norm.Update(new TValue(DateTime.UtcNow, 50), isNew: false);
|
||||
Assert.NotEqual(corrected.Value, corrected2.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_Period1_ReturnsHalf()
|
||||
{
|
||||
var norm = new Normalize(1);
|
||||
|
||||
// With period 1, min = max = current value, so range = 0
|
||||
var result = norm.Update(new TValue(DateTime.UtcNow, 42));
|
||||
|
||||
// Flat range returns 0.5
|
||||
Assert.Equal(0.5, result.Value, 1e-10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_VeryLargePeriod_StillWorks()
|
||||
{
|
||||
var norm = new Normalize(1000);
|
||||
var series = _gbm.Fetch(1500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in series)
|
||||
{
|
||||
var result = norm.Update(new TValue(bar.Time, bar.Close));
|
||||
Assert.True(double.IsFinite(result.Value));
|
||||
Assert.True(result.Value >= 0.0 && result.Value <= 1.0);
|
||||
}
|
||||
|
||||
Assert.True(norm.IsHot);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user