Files
Miha Kralj 060649192f 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
2026-03-12 12:34:16 -07:00

362 lines
14 KiB
C#

using Xunit;
namespace QuanTAlib.Tests;
public class SigmoidTests
{
private readonly GBM _gbm = new(1000, 0.05, 0.2, seed: 100);
private const double Epsilon = 1e-10;
// ═══════════════════════════════════════════════════════════════════════════════
// Constructor Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Constructor_WithDefaultParameters_SetsCorrectName()
{
var sigmoid = new Sigmoid();
Assert.Equal("Sigmoid(1.00,0.00)", sigmoid.Name);
}
[Fact]
public void Constructor_WithCustomParameters_SetsCorrectName()
{
var sigmoid = new Sigmoid(k: 0.5, x0: 100.0);
Assert.Equal("Sigmoid(0.50,100.00)", sigmoid.Name);
}
[Fact]
public void Constructor_WithZeroK_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new Sigmoid(k: 0));
}
[Fact]
public void Constructor_WithNegativeK_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new Sigmoid(k: -1));
}
[Fact]
public void Constructor_WarmupPeriod_IsZero()
{
var sigmoid = new Sigmoid();
Assert.Equal(0, sigmoid.WarmupPeriod);
}
// ═══════════════════════════════════════════════════════════════════════════════
// Basic Update Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Update_ReturnsTValue()
{
var sigmoid = new Sigmoid();
var input = new TValue(DateTime.UtcNow, 0.0);
var result = sigmoid.Update(input);
Assert.IsType<TValue>(result);
}
[Fact]
public void Update_AtMidpoint_ReturnsHalf()
{
var sigmoid = new Sigmoid(k: 1.0, x0: 0.0);
var input = new TValue(DateTime.UtcNow, 0.0);
var result = sigmoid.Update(input);
Assert.Equal(0.5, result.Value, Epsilon);
}
[Fact]
public void Update_AtMidpointWithOffset_ReturnsHalf()
{
var sigmoid = new Sigmoid(k: 1.0, x0: 100.0);
var input = new TValue(DateTime.UtcNow, 100.0);
var result = sigmoid.Update(input);
Assert.Equal(0.5, result.Value, Epsilon);
}
[Fact]
public void Update_Last_IsUpdated()
{
var sigmoid = new Sigmoid();
var input = new TValue(DateTime.UtcNow, 1.0);
sigmoid.Update(input);
Assert.Equal(input.Time, sigmoid.Last.Time);
}
[Fact]
public void Update_IsHot_IsAlwaysTrue()
{
var sigmoid = new Sigmoid();
Assert.True(sigmoid.IsHot);
sigmoid.Update(new TValue(DateTime.UtcNow, 0.0));
Assert.True(sigmoid.IsHot);
}
// ═══════════════════════════════════════════════════════════════════════════════
// isNew State Management Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Update_WithIsNewTrue_AdvancesState()
{
var sigmoid = new Sigmoid();
var input1 = new TValue(DateTime.UtcNow, 1.0);
var input2 = new TValue(DateTime.UtcNow.AddSeconds(1), 2.0);
var result1 = sigmoid.Update(input1, isNew: true);
var result2 = sigmoid.Update(input2, isNew: true);
Assert.NotEqual(result1.Value, result2.Value);
}
[Fact]
public void Update_WithIsNewFalse_ReplacesCurrentBar()
{
var sigmoid = new Sigmoid();
var input1 = new TValue(DateTime.UtcNow, 1.0);
var input2 = new TValue(DateTime.UtcNow, 2.0);
sigmoid.Update(input1, isNew: true);
var result = sigmoid.Update(input2, isNew: false);
Assert.Equal(sigmoid.Last.Value, result.Value);
}
[Fact]
public void Update_IterativeCorrections_RestoreState()
{
var sigmoid = new Sigmoid();
// Initial value
sigmoid.Update(new TValue(DateTime.UtcNow, 1.0), isNew: true);
double afterFirst = sigmoid.Last.Value;
// Multiple corrections (isNew = false)
sigmoid.Update(new TValue(DateTime.UtcNow, 2.0), isNew: false);
sigmoid.Update(new TValue(DateTime.UtcNow, 3.0), isNew: false);
sigmoid.Update(new TValue(DateTime.UtcNow, 1.0), isNew: false);
// Should restore to same state as after first update with same input
Assert.Equal(afterFirst, sigmoid.Last.Value, Epsilon);
}
// ═══════════════════════════════════════════════════════════════════════════════
// NaN/Infinity Handling Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Update_NaN_UsesLastValidValue()
{
var sigmoid = new Sigmoid();
sigmoid.Update(new TValue(DateTime.UtcNow, 1.0), isNew: true);
double lastValid = sigmoid.Last.Value;
var nanResult = sigmoid.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.NaN), isNew: true);
Assert.Equal(lastValid, nanResult.Value);
}
[Fact]
public void Update_PositiveInfinity_UsesLastValidValue()
{
var sigmoid = new Sigmoid();
sigmoid.Update(new TValue(DateTime.UtcNow, 0.0), isNew: true);
double lastValid = sigmoid.Last.Value;
var infResult = sigmoid.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.PositiveInfinity), isNew: true);
Assert.Equal(lastValid, infResult.Value);
}
[Fact]
public void Update_NegativeInfinity_UsesLastValidValue()
{
var sigmoid = new Sigmoid();
sigmoid.Update(new TValue(DateTime.UtcNow, 0.0), isNew: true);
double lastValid = sigmoid.Last.Value;
var infResult = sigmoid.Update(new TValue(DateTime.UtcNow.AddSeconds(1), double.NegativeInfinity), isNew: true);
Assert.Equal(lastValid, infResult.Value);
}
// ═══════════════════════════════════════════════════════════════════════════════
// Reset Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Reset_ClearsState()
{
var sigmoid = new Sigmoid();
sigmoid.Update(new TValue(DateTime.UtcNow, 1.0), isNew: true);
sigmoid.Reset();
Assert.Equal(default, sigmoid.Last);
}
// ═══════════════════════════════════════════════════════════════════════════════
// TSeries Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Update_TSeries_ReturnsCorrectLength()
{
var sigmoid = new Sigmoid();
var series = new TSeries();
for (int i = 0; i < 100; i++)
{
series.Add(new TValue(DateTime.UtcNow.AddSeconds(i), i - 50), isNew: true);
}
var result = sigmoid.Update(series);
Assert.Equal(series.Count, result.Count);
}
[Fact]
public void Calculate_TSeries_ReturnsCorrectLength()
{
var series = new TSeries();
for (int i = 0; i < 100; i++)
{
series.Add(new TValue(DateTime.UtcNow.AddSeconds(i), i - 50), isNew: true);
}
var result = Sigmoid.Batch(series);
Assert.Equal(series.Count, result.Count);
}
// ═══════════════════════════════════════════════════════════════════════════════
// Span API Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Calculate_Span_EmptySource_ThrowsArgumentException()
{
double[] output = new double[10];
Assert.Throws<ArgumentException>(() => Sigmoid.Batch(ReadOnlySpan<double>.Empty, output.AsSpan()));
}
[Fact]
public void Calculate_Span_OutputTooSmall_ThrowsArgumentException()
{
double[] source = [1, 2, 3, 4, 5];
double[] output = new double[3];
Assert.Throws<ArgumentException>(() => Sigmoid.Batch(source.AsSpan(), output.AsSpan()));
}
[Fact]
public void Calculate_Span_InvalidK_ThrowsArgumentException()
{
double[] source = [1, 2, 3, 4, 5];
double[] output = new double[5];
Assert.Throws<ArgumentException>(() => Sigmoid.Batch(source.AsSpan(), output.AsSpan(), k: 0));
}
[Fact]
public void Calculate_Span_MatchesStreaming()
{
double[] source = new GBM(seed: 42).Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).CloseValues.ToArray();
double[] spanOutput = new double[source.Length];
Sigmoid.Batch(source.AsSpan(), spanOutput.AsSpan());
var sigmoid = new Sigmoid();
double[] streamOutput = new double[source.Length];
for (int i = 0; i < source.Length; i++)
{
streamOutput[i] = sigmoid.Update(new TValue(DateTime.UtcNow.AddSeconds(i), source[i]), true).Value;
}
for (int i = 0; i < source.Length; i++)
{
Assert.Equal(streamOutput[i], spanOutput[i], Epsilon);
}
}
[Fact]
public void Calculate_Span_HandlesNaN()
{
double[] source = [1.0, double.NaN, 2.0];
double[] output = new double[3];
Sigmoid.Batch(source.AsSpan(), output.AsSpan());
Assert.True(double.IsFinite(output[0]));
Assert.True(double.IsFinite(output[1])); // NaN replaced with last valid
Assert.True(double.IsFinite(output[2]));
}
// ═══════════════════════════════════════════════════════════════════════════════
// Chaining Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Chaining_PublishesEvents()
{
var source = new TSeries();
var sigmoid = new Sigmoid(source);
int eventCount = 0;
sigmoid.Pub += (_, in _) => eventCount++;
for (int i = 0; i < 10; i++)
{
source.Add(new TValue(DateTime.UtcNow.AddSeconds(i), i), isNew: true);
}
Assert.Equal(10, eventCount);
}
// ═══════════════════════════════════════════════════════════════════════════════
// Steepness Parameter Tests
// ═══════════════════════════════════════════════════════════════════════════════
[Fact]
public void Update_HigherK_CreatesSteeperTransition()
{
var sigmoidLow = new Sigmoid(k: 0.5);
var sigmoidHigh = new Sigmoid(k: 5.0);
// At x=1, higher k should give value closer to 1
var resultLow = sigmoidLow.Update(new TValue(DateTime.UtcNow, 1.0));
var resultHigh = sigmoidHigh.Update(new TValue(DateTime.UtcNow, 1.0));
Assert.True(resultHigh.Value > resultLow.Value);
}
[Fact]
public void Update_DifferentX0_ShiftsMidpoint()
{
var sigmoid0 = new Sigmoid(k: 1.0, x0: 0.0);
var sigmoid100 = new Sigmoid(k: 1.0, x0: 100.0);
// At x=0, sigmoid with x0=0 should be 0.5
var result0 = sigmoid0.Update(new TValue(DateTime.UtcNow, 0.0));
// At x=100, sigmoid with x0=100 should be 0.5
var result100 = sigmoid100.Update(new TValue(DateTime.UtcNow, 100.0));
Assert.Equal(0.5, result0.Value, Epsilon);
Assert.Equal(0.5, result100.Value, Epsilon);
}
}