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:
Miha Kralj
2026-03-12 12:34:16 -07:00
parent 8937b0c0fa
commit 060649192f
1149 changed files with 1780 additions and 3316 deletions
@@ -0,0 +1,269 @@
using TradingPlatform.BusinessLayer;
namespace QuanTAlib.Quantower.Tests;
public class LunarIndicatorTests
{
[Fact]
public void LunarIndicator_Constructor_SetsDefaults()
{
var indicator = new LunarIndicator();
Assert.True(indicator.ShowColdValues);
Assert.Equal("LUNAR - Lunar Phase", indicator.Name);
Assert.True(indicator.SeparateWindow);
Assert.True(indicator.OnBackGround);
}
[Fact]
public void LunarIndicator_MinHistoryDepths_EqualsZero()
{
var indicator = new LunarIndicator();
Assert.Equal(0, LunarIndicator.MinHistoryDepths);
Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
}
[Fact]
public void LunarIndicator_ShortName_IsLunar()
{
var indicator = new LunarIndicator();
Assert.Equal("LUNAR", indicator.ShortName);
}
[Fact]
public void LunarIndicator_Initialize_CreatesInternalLunar()
{
var indicator = new LunarIndicator();
// Initialize should not throw
indicator.Initialize();
// After init, line series should exist (Lunar Phase + 3 reference lines)
Assert.Equal(4, indicator.LinesSeries.Count);
}
[Fact]
public void LunarIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
{
var indicator = new LunarIndicator();
indicator.Initialize();
// Add historical data
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process update
var args = new UpdateArgs(UpdateReason.HistoricalBar);
indicator.ProcessUpdate(args);
// Line series should have a value
Assert.Equal(1, indicator.LinesSeries[0].Count);
double value = indicator.LinesSeries[0].GetValue(0);
Assert.True(double.IsFinite(value));
Assert.True(value >= 0.0 && value <= 1.0, $"Lunar phase should be 0-1, got {value}");
}
[Fact]
public void LunarIndicator_ProcessUpdate_NewBar_ComputesValue()
{
var indicator = new LunarIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.HistoricalData.AddBar(now.AddMinutes(1), 102, 108, 100, 106);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
Assert.Equal(2, indicator.LinesSeries[0].Count);
}
[Fact]
public void LunarIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
{
var indicator = new LunarIndicator();
indicator.Initialize();
// Should not throw an exception
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
// Assert that the indicator still exists (method completed without exception)
Assert.NotNull(indicator);
}
[Fact]
public void LunarIndicator_MultipleUpdates_ProducesCorrectSequence()
{
var indicator = new LunarIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
int barCount = 30; // Cover roughly one lunar month
for (int i = 0; i < barCount; i++)
{
indicator.HistoricalData.AddBar(now.AddDays(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
// All values should be in valid range [0, 1]
for (int i = 0; i < barCount; i++)
{
double value = indicator.LinesSeries[0].GetValue(barCount - 1 - i);
Assert.True(double.IsFinite(value), $"Value at index {i} should be finite");
Assert.True(value >= 0.0 && value <= 1.0, $"Value at index {i} should be 0-1, got {value}");
}
}
[Fact]
public void LunarIndicator_ShowColdValues_CanBeChanged()
{
var indicator = new LunarIndicator { ShowColdValues = true };
Assert.True(indicator.ShowColdValues);
indicator.ShowColdValues = false;
Assert.False(indicator.ShowColdValues);
}
[Fact]
public void LunarIndicator_ProcessUpdate_IgnoresNonBarUpdates()
{
var indicator = new LunarIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
// Process historical bar first
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Process other update reasons - should not throw
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
// Assert that the indicator still exists (method completed without exception)
Assert.NotNull(indicator);
}
[Fact]
public void LunarIndicator_LineSeries_HasCorrectProperties()
{
var indicator = new LunarIndicator();
indicator.Initialize();
var lineSeries = indicator.LinesSeries[0];
Assert.Equal("Lunar Phase", lineSeries.Name);
Assert.Equal(2, lineSeries.Width);
Assert.Equal(LineStyle.Solid, lineSeries.Style);
}
[Fact]
public void LunarIndicator_ReferenceLines_HaveCorrectValues()
{
var indicator = new LunarIndicator();
indicator.Initialize();
var now = DateTime.UtcNow;
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
// Check reference line values
Assert.Equal(0.0, indicator.LinesSeries[1].GetValue(0)); // New Moon line
Assert.Equal(1.0, indicator.LinesSeries[2].GetValue(0)); // Full Moon line
Assert.Equal(0.5, indicator.LinesSeries[3].GetValue(0)); // Quarter line
}
[Fact]
public void LunarIndicator_PhaseVariesOverTime()
{
var indicator = new LunarIndicator();
indicator.Initialize();
var baseDate = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// Add bars over a lunar month
for (int i = 0; i < 30; i++)
{
indicator.HistoricalData.AddBar(baseDate.AddDays(i), 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
}
// Collect all values
var phases = new double[30];
for (int i = 0; i < 30; i++)
{
phases[i] = indicator.LinesSeries[0].GetValue(29 - i);
}
// Verify there's variation in phases (not all same value)
double minPhase = phases.Min();
double maxPhase = phases.Max();
Assert.True(maxPhase - minPhase > 0.5,
$"Lunar phase should vary significantly over a month. Min: {minPhase}, Max: {maxPhase}");
}
[Fact]
public void LunarIndicator_ProducesValidPhase()
{
var indicator = new LunarIndicator();
indicator.Initialize();
// Use any date - the phase should be in valid range
var testDate = new DateTime(2025, 1, 15, 12, 0, 0, DateTimeKind.Utc);
indicator.HistoricalData.AddBar(testDate, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double phase = indicator.LinesSeries[0].GetValue(0);
Assert.True(phase >= 0.0 && phase <= 1.0, $"Phase should be in [0,1] range, got {phase}");
}
[Fact]
public void LunarIndicator_PhaseVariesWithDate()
{
var indicator = new LunarIndicator();
indicator.Initialize();
// Add bars at different dates and verify phases vary
var date1 = new DateTime(2025, 1, 1, 12, 0, 0, DateTimeKind.Utc);
var date2 = new DateTime(2025, 1, 15, 12, 0, 0, DateTimeKind.Utc);
indicator.HistoricalData.AddBar(date1, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double phase1 = indicator.LinesSeries[0].GetValue(0);
indicator.HistoricalData.AddBar(date2, 100, 105, 95, 102);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
double phase2 = indicator.LinesSeries[0].GetValue(0);
// Phases at different dates should differ (14 days apart = significant lunar change)
Assert.NotEqual(phase1, phase2);
}
[Fact]
public void LunarIndicator_HasFourLineSeries()
{
var indicator = new LunarIndicator();
indicator.Initialize();
Assert.Equal(4, indicator.LinesSeries.Count);
Assert.Equal("Lunar Phase", indicator.LinesSeries[0].Name);
Assert.Equal("New Moon", indicator.LinesSeries[1].Name);
Assert.Equal("Full Moon", indicator.LinesSeries[2].Name);
Assert.Equal("Quarter", indicator.LinesSeries[3].Name);
}
[Fact]
public void LunarIndicator_SourceCodeLink_IsValid()
{
var indicator = new LunarIndicator();
Assert.NotNull(indicator.SourceCodeLink);
Assert.Contains("Lunar.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
}
}
+309
View File
@@ -0,0 +1,309 @@
namespace QuanTAlib.Tests;
using Xunit;
public class LunarTests
{
private const double Tolerance = 1e-6;
// Known lunar phase dates (verified against astronomical data)
// New Moon: ~0.0, Full Moon: ~1.0, Quarters: ~0.5
[Fact]
public void Lunar_ConstructorDefaults()
{
var lunar = new Lunar();
Assert.Equal("Lunar", lunar.Name);
Assert.Equal(0, lunar.WarmupPeriod);
Assert.True(lunar.IsHot);
}
[Fact]
public void Lunar_Update_ReturnsValidPhase()
{
var lunar = new Lunar();
var input = new TValue(DateTime.UtcNow, 100.0);
var result = lunar.Update(input);
Assert.True(result.Value >= 0.0 && result.Value <= 1.0);
Assert.Equal(input.Time, result.Time);
}
[Fact]
public void Lunar_KnownNewMoon_ReturnsLowPhase()
{
// January 29, 2025 - New Moon at 12:36 UTC
var newMoon = new DateTime(2025, 1, 29, 12, 36, 0, DateTimeKind.Utc);
double phase = Lunar.CalculatePhase(newMoon);
// New moon should be close to 0
Assert.True(phase < 0.05, $"Expected phase < 0.05 at new moon, got {phase}");
}
[Fact]
public void Lunar_KnownFullMoon_ReturnsHighPhase()
{
// February 12, 2025 - Full Moon at 13:53 UTC
var fullMoon = new DateTime(2025, 2, 12, 13, 53, 0, DateTimeKind.Utc);
double phase = Lunar.CalculatePhase(fullMoon);
// Full moon should be close to 1
Assert.True(phase > 0.95, $"Expected phase > 0.95 at full moon, got {phase}");
}
[Fact]
public void Lunar_FirstQuarter_ReturnsHalfPhase()
{
// February 5, 2025 - First Quarter at 08:02 UTC
var firstQuarter = new DateTime(2025, 2, 5, 8, 2, 0, DateTimeKind.Utc);
double phase = Lunar.CalculatePhase(firstQuarter);
// First quarter should be around 0.5
Assert.True(phase > 0.4 && phase < 0.6, $"Expected phase ~0.5 at first quarter, got {phase}");
}
[Fact]
public void Lunar_LastQuarter_ReturnsHalfPhase()
{
// February 20, 2025 - Last Quarter at 17:33 UTC
var lastQuarter = new DateTime(2025, 2, 20, 17, 33, 0, DateTimeKind.Utc);
double phase = Lunar.CalculatePhase(lastQuarter);
// Last quarter should be around 0.5
Assert.True(phase > 0.4 && phase < 0.6, $"Expected phase ~0.5 at last quarter, got {phase}");
}
[Fact]
public void Lunar_PhaseCycle_Increases_Then_Decreases()
{
// Check phase increases from new moon to full moon
var startDate = new DateTime(2025, 1, 29, 12, 0, 0, DateTimeKind.Utc); // New moon
double prevPhase = Lunar.CalculatePhase(startDate);
// Check for 7 days after new moon - phase should generally increase
for (int day = 1; day <= 7; day++)
{
var date = startDate.AddDays(day);
double phase = Lunar.CalculatePhase(date);
// Allow small fluctuations due to orbital mechanics
Assert.True(phase >= prevPhase - 0.01,
$"Phase should increase from new moon: day {day}, prev={prevPhase}, curr={phase}");
prevPhase = phase;
}
}
[Fact]
public void Lunar_LunarMonth_Cycle()
{
// One lunar month is approximately 29.53 days
var startDate = new DateTime(2025, 1, 29, 12, 36, 0, DateTimeKind.Utc); // New Moon
double startPhase = Lunar.CalculatePhase(startDate);
// After ~29.53 days, should be back to similar phase
var endDate = startDate.AddDays(29.53);
double endPhase = Lunar.CalculatePhase(endDate);
Assert.True(Math.Abs(startPhase - endPhase) < 0.1,
$"Phase should return to ~same value after lunar month: start={startPhase}, end={endPhase}");
}
[Fact]
public void Lunar_Batch_MatchesStreaming()
{
var startDate = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
int count = 100;
// Create timestamps
var timestamps = new long[count];
var expected = new double[count];
for (int i = 0; i < count; i++)
{
var date = startDate.AddDays(i);
timestamps[i] = new DateTimeOffset(date).ToUnixTimeMilliseconds();
expected[i] = Lunar.CalculatePhase(date);
}
// Calculate using batch
var output = new double[count];
Lunar.Batch(timestamps, output);
// Compare
for (int i = 0; i < count; i++)
{
Assert.Equal(expected[i], output[i], Tolerance);
}
}
[Fact]
public void Lunar_TSeries_Update()
{
var startDate = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var series = new TSeries(30);
for (int i = 0; i < 30; i++)
{
series.Add(new TValue(startDate.AddDays(i), 100.0 + i));
}
var lunar = new Lunar();
var result = lunar.Update(series);
Assert.Equal(30, result.Count);
// Verify each value
for (int i = 0; i < 30; i++)
{
double expectedPhase = Lunar.CalculatePhase(series[i].Time);
Assert.Equal(expectedPhase, result[i].Value, Tolerance);
}
}
[Fact]
public void Lunar_StaticCalculate_TSeries()
{
var startDate = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var series = new TSeries(30);
for (int i = 0; i < 30; i++)
{
series.Add(new TValue(startDate.AddDays(i), 100.0 + i));
}
var result = Lunar.Batch(series);
Assert.Equal(30, result.Count);
for (int i = 0; i < 30; i++)
{
double expectedPhase = Lunar.CalculatePhase(series[i].Time);
Assert.Equal(expectedPhase, result[i].Value, Tolerance);
}
}
[Fact]
public void Lunar_Chaining_Works()
{
var source = new Sma(10);
var lunar = new Lunar(source);
bool eventFired = false;
lunar.Pub += (object? sender, in TValueEventArgs args) => eventFired = true;
var input = new TValue(DateTime.UtcNow, 100.0);
source.Update(input);
Assert.True(eventFired);
}
[Fact]
public void Lunar_Reset()
{
var lunar = new Lunar();
var input = new TValue(DateTime.UtcNow, 100.0);
lunar.Update(input);
lunar.Reset();
// After reset, Last should be reset
Assert.Equal(0, lunar.Last.Value);
}
[Fact]
public void Lunar_UnixTimestamp_CalculatesCorrectly()
{
// Test using known Unix timestamp
// January 1, 2020 00:00:00 UTC = 1577836800000 ms
long unixMs = 1577836800000;
double phase1 = Lunar.CalculatePhase(unixMs);
var dateTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc);
double phase2 = Lunar.CalculatePhase(dateTime);
Assert.Equal(phase1, phase2, Tolerance);
}
[Fact]
public void Lunar_Phase_AlwaysInRange()
{
// Test across multiple years
var startDate = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc);
for (int day = 0; day < 365 * 5; day++) // 5 years
{
var date = startDate.AddDays(day);
double phase = Lunar.CalculatePhase(date);
Assert.True(phase >= 0.0 && phase <= 1.0,
$"Phase out of range at {date}: {phase}");
}
}
[Fact]
public void Lunar_HistoricalNewMoon_1999()
{
// December 7, 1999 - New Moon at 22:32 UTC
var newMoon = new DateTime(1999, 12, 7, 22, 32, 0, DateTimeKind.Utc);
double phase = Lunar.CalculatePhase(newMoon);
Assert.True(phase < 0.05, $"Expected low phase at 1999 new moon, got {phase}");
}
[Fact]
public void Lunar_HistoricalFullMoon_2000()
{
// January 21, 2000 - Full Moon (also a lunar eclipse)
var fullMoon = new DateTime(2000, 1, 21, 4, 40, 0, DateTimeKind.Utc);
double phase = Lunar.CalculatePhase(fullMoon);
Assert.True(phase > 0.95, $"Expected high phase at 2000 full moon, got {phase}");
}
[Fact]
public void Lunar_Batch_ThrowsOnLengthMismatch()
{
var timestamps = new long[10];
var output = new double[5];
Assert.Throws<ArgumentException>(() => Lunar.Batch(timestamps, output));
}
[Fact]
public void Lunar_EmptyTSeries_ReturnsEmpty()
{
var lunar = new Lunar();
var empty = new TSeries();
var result = lunar.Update(empty);
Assert.Empty(result);
}
[Fact]
public void Lunar_IsNew_Parameter_DoesNotAffectResult()
{
var lunar = new Lunar();
var input = new TValue(DateTime.UtcNow, 100.0);
var result1 = lunar.Update(input, isNew: true);
lunar.Reset();
var result2 = lunar.Update(input, isNew: false);
// Lunar phase is deterministic from timestamp, isNew shouldn't matter
Assert.Equal(result1.Value, result2.Value, Tolerance);
}
[Fact]
public void Lunar_DateTimeKind_Unspecified_TreatedAsUtc()
{
var unspecified = new DateTime(2025, 1, 15, 12, 0, 0, DateTimeKind.Unspecified);
var utc = new DateTime(2025, 1, 15, 12, 0, 0, DateTimeKind.Utc);
double phase1 = Lunar.CalculatePhase(unspecified);
double phase2 = Lunar.CalculatePhase(utc);
Assert.Equal(phase1, phase2, Tolerance);
}
}
@@ -0,0 +1,105 @@
using Xunit;
namespace QuanTAlib.Tests;
/// <summary>
/// Validation tests for Lunar Phase indicator.
/// Lunar is a deterministic astronomical calculation not implemented in trading libraries
/// (TA-Lib, Skender, Tulip), so validation is done against known astronomical events
/// and mathematical properties of the lunar cycle.
/// </summary>
public class LunarValidationTests
{
[Fact]
public void Validation_OutputRange_ZeroToOne()
{
// Lunar phase output should always be in [0, 1]
var lunar = new Lunar();
var gbm = new GBM(seed: 42);
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
foreach (var bar in bars)
{
lunar.Update(new TValue(bar.Time, bar.Close));
double val = lunar.Last.Value;
Assert.True(val >= 0.0 && val <= 1.0,
$"Lunar phase {val} is outside expected range [0, 1]");
}
}
[Fact]
public void Validation_DeterministicForSameTimestamp()
{
// Same timestamp should always produce the same lunar phase
var lunar1 = new Lunar();
var lunar2 = new Lunar();
var fixedTime = new DateTime(2024, 1, 15, 12, 0, 0, DateTimeKind.Utc);
lunar1.Update(new TValue(fixedTime, 100.0));
lunar2.Update(new TValue(fixedTime, 200.0));
Assert.Equal(lunar1.Last.Value, lunar2.Last.Value, 1e-12);
}
[Fact]
public void Validation_PriceIndependent()
{
// Lunar phase depends only on timestamp, not on price
var lunar = new Lunar();
var t1 = new DateTime(2024, 3, 10, 0, 0, 0, DateTimeKind.Utc);
lunar.Update(new TValue(t1, 50.0));
double val1 = lunar.Last.Value;
lunar = new Lunar();
lunar.Update(new TValue(t1, 999.0));
double val2 = lunar.Last.Value;
Assert.Equal(val1, val2, 1e-12);
}
[Fact]
public void Validation_CyclePeriodApprox29Days()
{
// The synodic lunar cycle is ~29.53 days
// Over a 60-day window we should see roughly 2 full cycles
var lunar = new Lunar();
var start = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var values = new List<double>();
for (int day = 0; day < 60; day++)
{
var t = start.AddDays(day);
lunar.Update(new TValue(t, 100.0));
values.Add(lunar.Last.Value);
}
// Verify the cycle completes: values should vary significantly over 60 days
double minVal = values.Min();
double maxVal = values.Max();
double range = maxVal - minVal;
// Over 60 days (~2 synodic months) we should see significant variation
Assert.True(range > 0.5,
$"Expected lunar phase range > 0.5 over 60 days, got range={range} (min={minVal}, max={maxVal})");
}
[Fact]
public void Validation_FiniteOutputs()
{
// All outputs should be finite
var lunar = new Lunar();
var gbm = new GBM(seed: 99);
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
foreach (var bar in bars)
{
lunar.Update(new TValue(bar.Time, bar.Close));
Assert.True(double.IsFinite(lunar.Last.Value),
$"Lunar produced non-finite value: {lunar.Last.Value}");
}
}
}