mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 17:18:05 +00:00
- Implemented Prime method in Vel, Ao, Apo, Frama, Adl, Adosc, Aobv, Cmf, Efi, Eom, Iii, Kvo, Mfi, Nvi, Obv, Pvd, Pvi, Pvo, Pvr, Pvt, Tvi, Twap, Va, Vf, Vo, Vroc, Vwad, Vwap, and Vwma classes. - The Prime method resets the indicator state and processes the provided historical bar data to initialize the indicator. - Added warmup period property to Adl and Wad classes to define the minimum number of data points required for validity. - Updated benchmark tests to use Batch methods for performance evaluation.
473 lines
14 KiB
C#
473 lines
14 KiB
C#
using TradingPlatform.BusinessLayer;
|
|
using Xunit;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
public class UchannelQuantowerTests
|
|
{
|
|
#region Constructor Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_Constructor_SetsDefaults()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
Assert.Equal(20, indicator.StrPeriod);
|
|
Assert.Equal(20, indicator.CenterPeriod);
|
|
Assert.Equal(1.0, indicator.Multiplier);
|
|
Assert.True(indicator.ShowColdValues);
|
|
Assert.Equal("UCHANNEL - Ehlers Ultimate Channel", indicator.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_Constructor_SetsDisplayProperties()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
Assert.False(indicator.SeparateWindow);
|
|
Assert.True(indicator.OnBackGround);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region MinHistoryDepths Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_MinHistoryDepths_ReturnsMaxOfPeriods()
|
|
{
|
|
var indicator1 = new UchannelIndicator { StrPeriod = 10, CenterPeriod = 20 };
|
|
Assert.Equal(20, indicator1.MinHistoryDepths);
|
|
|
|
var indicator2 = new UchannelIndicator { StrPeriod = 30, CenterPeriod = 15 };
|
|
Assert.Equal(30, indicator2.MinHistoryDepths);
|
|
|
|
var indicator3 = new UchannelIndicator { StrPeriod = 25, CenterPeriod = 25 };
|
|
Assert.Equal(25, indicator3.MinHistoryDepths);
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_MinHistoryDepths_ExplicitInterface()
|
|
{
|
|
var indicator = new UchannelIndicator { StrPeriod = 15, CenterPeriod = 30 };
|
|
|
|
int explicit_value = ((IWatchlistIndicator)indicator).MinHistoryDepths;
|
|
|
|
Assert.Equal(30, explicit_value);
|
|
Assert.Equal(indicator.MinHistoryDepths, explicit_value);
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_MinHistoryDepths_MinPeriods()
|
|
{
|
|
var indicator = new UchannelIndicator { StrPeriod = 1, CenterPeriod = 1 };
|
|
|
|
Assert.Equal(1, indicator.MinHistoryDepths);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ShortName Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_ShortName_FormatsCorrectly()
|
|
{
|
|
var indicator = new UchannelIndicator
|
|
{
|
|
StrPeriod = 15,
|
|
CenterPeriod = 25,
|
|
Multiplier = 2.5
|
|
};
|
|
|
|
Assert.Equal("UCHANNEL (15,25,2.5)", indicator.ShortName);
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_ShortName_DefaultParameters()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
Assert.Equal("UCHANNEL (20,20,1.0)", indicator.ShortName);
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_ShortName_UpdatesWithParameters()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
Assert.Equal("UCHANNEL (20,20,1.0)", indicator.ShortName);
|
|
|
|
indicator.StrPeriod = 10;
|
|
indicator.CenterPeriod = 30;
|
|
indicator.Multiplier = 3.0;
|
|
|
|
Assert.Equal("UCHANNEL (10,30,3.0)", indicator.ShortName);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SourceCodeLink Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_SourceCodeLink_IsValid()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase);
|
|
Assert.Contains("Uchannel.cs", indicator.SourceCodeLink, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Parameter Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_Parameters_CanBeModified()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
indicator.StrPeriod = 30;
|
|
indicator.CenterPeriod = 40;
|
|
indicator.Multiplier = 2.0;
|
|
indicator.ShowColdValues = false;
|
|
|
|
Assert.Equal(30, indicator.StrPeriod);
|
|
Assert.Equal(40, indicator.CenterPeriod);
|
|
Assert.Equal(2.0, indicator.Multiplier);
|
|
Assert.False(indicator.ShowColdValues);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Description Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_Description_IsNotEmpty()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
Assert.False(string.IsNullOrWhiteSpace(indicator.Description));
|
|
Assert.Contains("Ultrasmooth", indicator.Description, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region LineSeries Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_HasFiveLineSeries()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
// The constructor adds 5 line series: Middle, Upper, Lower, STR, Width
|
|
Assert.Equal(5, indicator.LinesSeries.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_LineSeries_HaveCorrectNames()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
Assert.Equal("Middle", indicator.LinesSeries[0].Name);
|
|
Assert.Equal("Upper", indicator.LinesSeries[1].Name);
|
|
Assert.Equal("Lower", indicator.LinesSeries[2].Name);
|
|
Assert.Equal("STR", indicator.LinesSeries[3].Name);
|
|
Assert.Equal("Width", indicator.LinesSeries[4].Name);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Initialize Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_Initialize_DoesNotThrow()
|
|
{
|
|
var indicator = new UchannelIndicator
|
|
{
|
|
StrPeriod = 10,
|
|
CenterPeriod = 15,
|
|
Multiplier = 1.5
|
|
};
|
|
|
|
indicator.Initialize();
|
|
|
|
Assert.NotNull(indicator);
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_Initialize_PreservesLineSeries()
|
|
{
|
|
var indicator = new UchannelIndicator();
|
|
|
|
indicator.Initialize();
|
|
|
|
// Line series should still be present after init
|
|
Assert.Equal(5, indicator.LinesSeries.Count);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ProcessUpdate Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
|
{
|
|
var indicator = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
|
|
|
|
var args = new UpdateArgs(UpdateReason.HistoricalBar);
|
|
indicator.ProcessUpdate(args);
|
|
|
|
// All 5 line series should have values
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
Assert.Equal(1, indicator.LinesSeries[i].Count);
|
|
Assert.True(double.IsFinite(indicator.LinesSeries[i].GetValue(0)));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_ProcessUpdate_NewBar_ComputesValue()
|
|
{
|
|
var indicator = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(1), 102, 108, 100, 106, 1500);
|
|
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
|
|
|
Assert.Equal(2, indicator.LinesSeries[0].Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
|
|
{
|
|
var indicator = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
indicator.HistoricalData.AddBar(now, 100, 105, 95, 102, 1000);
|
|
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
double firstValue = indicator.LinesSeries[0].GetValue(0);
|
|
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
|
|
double secondValue = indicator.LinesSeries[0].GetValue(0);
|
|
|
|
Assert.True(double.IsFinite(firstValue));
|
|
Assert.True(double.IsFinite(secondValue));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Multiple Updates Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_MultipleUpdates_ProducesCorrectSequence()
|
|
{
|
|
var indicator = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5, Multiplier = 1.5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
double[] closes = { 100, 102, 104, 103, 105, 107, 106, 108, 110, 109 };
|
|
|
|
for (int i = 0; i < closes.Length; i++)
|
|
{
|
|
double close = closes[i];
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 3, close - 3, close, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// All 5 series should have values for each bar
|
|
for (int s = 0; s < 5; s++)
|
|
{
|
|
Assert.Equal(closes.Length, indicator.LinesSeries[s].Count);
|
|
}
|
|
|
|
// All last values should be finite
|
|
for (int s = 0; s < 5; s++)
|
|
{
|
|
Assert.True(double.IsFinite(indicator.LinesSeries[s].GetValue(0)));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Band Relationship Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_BandRelationships_AreCorrect()
|
|
{
|
|
var indicator = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5, Multiplier = 2.0 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
// Add varied data to generate band width
|
|
double[] closes = { 100, 105, 95, 110, 90, 105, 100, 108, 92, 103 };
|
|
|
|
for (int i = 0; i < closes.Length; i++)
|
|
{
|
|
double close = closes[i];
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 5, close - 5, close, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// Get last values: Middle=0, Upper=1, Lower=2, STR=3, Width=4
|
|
double middle = indicator.LinesSeries[0].GetValue(0);
|
|
double upper = indicator.LinesSeries[1].GetValue(0);
|
|
double lower = indicator.LinesSeries[2].GetValue(0);
|
|
double width = indicator.LinesSeries[4].GetValue(0);
|
|
|
|
// Band relationships: Upper >= Middle >= Lower
|
|
Assert.True(upper >= middle, $"Upper ({upper}) should be >= Middle ({middle})");
|
|
Assert.True(middle >= lower, $"Middle ({middle}) should be >= Lower ({lower})");
|
|
|
|
// Width = Upper - Lower
|
|
Assert.Equal(upper - lower, width, 6);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Multiplier Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_Multiplier_AffectsBandWidth()
|
|
{
|
|
var indicator1 = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5, Multiplier = 1.0 };
|
|
var indicator2 = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5, Multiplier = 2.0 };
|
|
indicator1.Initialize();
|
|
indicator2.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
double[] closes = { 100, 105, 95, 110, 90, 105, 100, 108, 92, 103 };
|
|
|
|
for (int i = 0; i < closes.Length; i++)
|
|
{
|
|
double close = closes[i];
|
|
indicator1.HistoricalData.AddBar(now.AddMinutes(i), close, close + 5, close - 5, close, 1000);
|
|
indicator1.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
indicator2.HistoricalData.AddBar(now.AddMinutes(i), close, close + 5, close - 5, close, 1000);
|
|
indicator2.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double width1 = indicator1.LinesSeries[4].GetValue(0);
|
|
double width2 = indicator2.LinesSeries[4].GetValue(0);
|
|
|
|
// Width2 should be approximately 2x Width1
|
|
Assert.True(Math.Abs(width2 - 2 * width1) < 0.0001,
|
|
$"Width2 ({width2}) should be ~2x Width1 ({width1})");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Different Period Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_DifferentPeriods_ProduceDifferentResults()
|
|
{
|
|
var indicator1 = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5 };
|
|
var indicator2 = new UchannelIndicator { StrPeriod = 20, CenterPeriod = 20 };
|
|
indicator1.Initialize();
|
|
indicator2.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
double close = 100 + (i % 5) * 2;
|
|
indicator1.HistoricalData.AddBar(now.AddMinutes(i), close, close + 3, close - 3, close, 1000);
|
|
indicator1.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
indicator2.HistoricalData.AddBar(now.AddMinutes(i), close, close + 3, close - 3, close, 1000);
|
|
indicator2.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double middle1 = indicator1.LinesSeries[0].GetValue(0);
|
|
double middle2 = indicator2.LinesSeries[0].GetValue(0);
|
|
|
|
// Different smoothing periods should produce different middle values
|
|
Assert.NotEqual(middle1, middle2);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region STR Series Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_STR_IsNonNegative()
|
|
{
|
|
var indicator = new UchannelIndicator { StrPeriod = 5, CenterPeriod = 5 };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
double[] closes = { 100, 105, 95, 110, 90, 105, 100, 108, 92, 103 };
|
|
|
|
for (int i = 0; i < closes.Length; i++)
|
|
{
|
|
double close = closes[i];
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), close, close + 5, close - 5, close, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// STR (smoothed true range) should be non-negative
|
|
double str = indicator.LinesSeries[3].GetValue(0);
|
|
Assert.True(str >= 0, $"STR ({str}) should be >= 0");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ShowColdValues Tests
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_ShowColdValues_True_ShowsValues()
|
|
{
|
|
var indicator = new UchannelIndicator
|
|
{
|
|
StrPeriod = 50,
|
|
CenterPeriod = 50,
|
|
ShowColdValues = true
|
|
};
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
// Add fewer bars than warmup
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// With ShowColdValues = true, values should be shown even before warmup
|
|
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
|
|
}
|
|
|
|
[Fact]
|
|
public void UchannelIndicator_ShowColdValues_False_SetsNaN()
|
|
{
|
|
var indicator = new UchannelIndicator
|
|
{
|
|
StrPeriod = 50,
|
|
CenterPeriod = 50,
|
|
ShowColdValues = false
|
|
};
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
// Add fewer bars than warmup
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100, 105, 95, 102, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// With ShowColdValues = false, cold values should be NaN before warmup
|
|
Assert.True(double.IsNaN(indicator.LinesSeries[0].GetValue(0)));
|
|
}
|
|
|
|
#endregion
|
|
}
|