using System; using Xunit; namespace QuanTAlib; public class StcTests { private const int CycleLength = 12; private const int FastLength = 26; private const int SlowLength = 50; private static Stc CreateDefaultStc() => new(kPeriod: CycleLength, dPeriod: CycleLength, fastLength: FastLength, slowLength: SlowLength, smoothing: StcSmoothing.Sigmoid); [Fact] public void Constructor_ValidatesInput() { Assert.Throws(() => new Stc(kPeriod: 1)); Assert.Throws(() => new Stc(dPeriod: 0)); Assert.Throws(() => new Stc(fastLength: 1)); Assert.Throws(() => new Stc(slowLength: 1)); } [Fact] public void Calc_ReturnsValue() { var stc = CreateDefaultStc(); var result = stc.Update(new TValue(DateTime.UtcNow, 100)); // Expect NaN during warmup Assert.True(double.IsNaN(result.Value) || double.IsFinite(result.Value)); } [Fact] public void Properties_Accessible() { var stc = CreateDefaultStc(); Assert.Equal(0, stc.Last.Value); // Initial value before updates Assert.False(stc.IsHot); Assert.Contains("Stc", stc.Name, StringComparison.Ordinal); } [Fact] public void IsHot_BecomesTrueWhenBufferFull() { var stc = CreateDefaultStc(); int warmup = stc.WarmupPeriod; for (int i = 0; i < warmup - 1; i++) { stc.Update(new TValue(DateTime.UtcNow, 100)); Assert.False(stc.IsHot); } stc.Update(new TValue(DateTime.UtcNow, 100)); Assert.True(stc.IsHot); } [Fact] public void BatchCalc_MatchesIterativeCalc() { var iterativeStc = CreateDefaultStc(); var batchStc = CreateDefaultStc(); var series = new TSeries(); var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); for (int i = 0; i < 200; i++) { var bar = gbm.Next(); series.Add(bar.Time, bar.Close); iterativeStc.Update(new TValue(bar.Time, bar.Close)); } var batchResult = batchStc.Update(series); Assert.Equal(iterativeStc.Last.Value, batchResult.Last.Value, 1e-9); } [Fact] public void SpanBatch_MatchesTSeriesBatch() { // Use default parameters for static calculation var series = new TSeries(); var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); double[] input = new double[200]; double[] output = new double[200]; for (int i = 0; i < 200; i++) { var bar = gbm.Next(); series.Add(bar.Time, bar.Close); input[i] = bar.Close; } var batchStc = CreateDefaultStc(); var tseriesResult = batchStc.Update(series); Stc.Batch(input.AsSpan(), output.AsSpan(), kPeriod: CycleLength, dPeriod: CycleLength, fastLength: FastLength, slowLength: SlowLength, smoothing: StcSmoothing.Sigmoid); // Compare last value Assert.Equal(tseriesResult.Last.Value, output[^1], 1e-9); } [Fact] public void NaN_Input_HandledSafely() { var stc = CreateDefaultStc(); stc.Update(new TValue(DateTime.UtcNow, 100)); var result = stc.Update(new TValue(DateTime.UtcNow, double.NaN)); // Should be NaN during warmup Assert.True(double.IsNaN(result.Value) || double.IsFinite(result.Value)); } [Fact] public void SmoothingOptions_ProduceDifferentResults() { var stcSigmoid = new Stc(kPeriod: 10, dPeriod: 10, fastLength: 20, slowLength: 40, smoothing: StcSmoothing.Sigmoid); var stcEma = new Stc(kPeriod: 10, dPeriod: 10, fastLength: 20, slowLength: 40, smoothing: StcSmoothing.Ema); var stcDigital = new Stc(kPeriod: 10, dPeriod: 10, fastLength: 20, slowLength: 40, smoothing: StcSmoothing.Digital); var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); for (int i = 0; i < 100; i++) { double val = gbm.Next().Close; stcSigmoid.Update(new TValue(DateTime.UtcNow, val)); stcEma.Update(new TValue(DateTime.UtcNow, val)); stcDigital.Update(new TValue(DateTime.UtcNow, val)); } Assert.NotEqual(stcSigmoid.Last.Value, stcEma.Last.Value); Assert.NotEqual(stcSigmoid.Last.Value, stcDigital.Last.Value); } }