mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-07 05:27:43 +00:00
653aafacd8
- 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.
807 lines
26 KiB
C#
807 lines
26 KiB
C#
using TradingPlatform.BusinessLayer;
|
|
using Xunit;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
public class QstickIndicatorTests
|
|
{
|
|
// ── Constructor & Defaults ──────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Constructor_CreatesValidIndicator()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.NotNull(indicator);
|
|
Assert.Equal("Qstick Indicator", indicator.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_Description_IsNotEmpty()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.False(string.IsNullOrWhiteSpace(indicator.Description));
|
|
Assert.Contains("candlestick", indicator.Description, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_SeparateWindow_IsTrue()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.True(indicator.SeparateWindow);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_CreatesOneLineSeries()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.Single(indicator.LinesSeries);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_LineSeries_NameIsQstick()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.Equal("Qstick", indicator.LinesSeries[0].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultPeriod_Is14()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.Equal(14, indicator.Period);
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultMaType_IsSMA()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.Equal("SMA", indicator.MaType);
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultShowColdValues_IsTrue()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.True(indicator.ShowColdValues);
|
|
}
|
|
|
|
// ── ShortName ───────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void ShortName_DefaultParameters_IncludesPeriodAndMaType()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.Equal("QSTICK(14,SMA)", indicator.ShortName);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShortName_CustomPeriod_ReflectsNewPeriod()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 20 };
|
|
Assert.Equal("QSTICK(20,SMA)", indicator.ShortName);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShortName_EmaMode_IncludesEMA()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 20, MaType = "EMA" };
|
|
Assert.Equal("QSTICK(20,EMA)", indicator.ShortName);
|
|
}
|
|
|
|
// ── MinHistoryDepths ────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void MinHistoryDepths_Static_EqualsZero()
|
|
{
|
|
Assert.Equal(0, QstickIndicator.MinHistoryDepths);
|
|
}
|
|
|
|
[Fact]
|
|
public void MinHistoryDepths_Interface_EqualsZero()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
IWatchlistIndicator watchlistIndicator = indicator;
|
|
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
|
|
}
|
|
|
|
// ── OnInit ──────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Initialize_SmaMode_CreatesInternalIndicator()
|
|
{
|
|
var indicator = new QstickIndicator { MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
Assert.NotNull(indicator);
|
|
}
|
|
|
|
[Fact]
|
|
public void Initialize_EmaMode_CreatesInternalIndicator()
|
|
{
|
|
var indicator = new QstickIndicator { MaType = "EMA" };
|
|
indicator.Initialize();
|
|
|
|
Assert.NotNull(indicator);
|
|
}
|
|
|
|
[Fact]
|
|
public void Initialize_AddsZeroLineLevel()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
indicator.Initialize();
|
|
|
|
// OnInit calls AddLineLevel(0, "Zero", ...)
|
|
Assert.True(indicator.LineLevels.Count >= 1);
|
|
}
|
|
|
|
// ── ProcessUpdate: HistoricalBar ────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void ProcessUpdate_HistoricalBar_ComputesValue()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 3, MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 110.0, 95.0, 105.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// After 5 bars with constant diff=5, Qstick=5
|
|
Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
|
|
}
|
|
|
|
[Fact]
|
|
public void ProcessUpdate_HistoricalBar_BullishBars_PositiveValue()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 3, MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 110.0, 95.0, 105.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// All bullish, diff=5 each, SMA=5.0
|
|
Assert.Equal(5.0, indicator.LinesSeries[0].GetValue(0), 10);
|
|
}
|
|
|
|
// ── ProcessUpdate: NewBar ───────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void ProcessUpdate_NewBar_ComputesValue()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 3, MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
indicator.HistoricalData.AddBar(now, 100.0, 110.0, 95.0, 105.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(1), 100.0, 108.0, 95.0, 103.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
|
|
|
Assert.Equal(2, indicator.LinesSeries[0].Count);
|
|
}
|
|
|
|
// ── ProcessUpdate: NewTick ──────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void ProcessUpdate_NewTick_ProcessesWithoutError()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 3, MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
indicator.HistoricalData.AddBar(now, 100.0, 110.0, 95.0, 105.0, 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));
|
|
}
|
|
|
|
// ── SMA vs EMA ──────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void SmaMode_BearishBars_ProducesNegativeQstick()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 5, MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 105.0, 90.0, 95.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// All bearish (close < open), diff=-5
|
|
Assert.Equal(-5.0, indicator.LinesSeries[0].GetValue(0), 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void SmaMode_DojiBars_ProducesZeroQstick()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 5, MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 105.0, 95.0, 100.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
Assert.Equal(0.0, indicator.LinesSeries[0].GetValue(0), 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void EmaMode_BullishBars_ProducesPositiveQstick()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 5, MaType = "EMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 110.0, 95.0, 105.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
// All same diff=5, EMA converges to 5.0
|
|
Assert.Equal(5.0, indicator.LinesSeries[0].GetValue(0), 10);
|
|
}
|
|
|
|
// ── Core Calculation Integration ────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void CalculationIntegration_SmaMode_ProducesCorrectValues()
|
|
{
|
|
var qstickCore = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
|
|
var bar1 = new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000);
|
|
var bar2 = new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000);
|
|
var bar3 = new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 106.0, 1000);
|
|
|
|
qstickCore.Update(bar1);
|
|
qstickCore.Update(bar2);
|
|
var result = qstickCore.Update(bar3);
|
|
|
|
// SMA of (5, 3, 6) = 14/3 ≈ 4.667
|
|
Assert.Equal(14.0 / 3.0, result.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void EmaMode_CalculatesCorrectly()
|
|
{
|
|
var qstickCore = new Qstick(3, useEma: true);
|
|
var time = DateTime.UtcNow;
|
|
|
|
// Bar 1: diff = 5
|
|
qstickCore.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
|
|
// Bar 2: diff = -3, EMA with alpha = 2/(3+1) = 0.5
|
|
var result = qstickCore.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 97.0, 1000));
|
|
|
|
// EMA = 0.5 * -3 + 0.5 * 5 = 1.0
|
|
Assert.Equal(1.0, result.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void CalculationIntegration_MixedBullishBearish()
|
|
{
|
|
var qstickCore = new Qstick(4);
|
|
var time = DateTime.UtcNow;
|
|
|
|
// 2 bullish (diff=5), 2 bearish (diff=-5) → SMA = 0
|
|
qstickCore.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
qstickCore.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
qstickCore.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 105.0, 90.0, 95.0, 1000));
|
|
var result = qstickCore.Update(new TBar(time.AddMinutes(3).Ticks, 100.0, 105.0, 90.0, 95.0, 1000));
|
|
|
|
Assert.Equal(0.0, result.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void BullishBars_ProducePositiveQstick()
|
|
{
|
|
var qstick = new Qstick(5);
|
|
var time = DateTime.UtcNow;
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
qstick.Update(new TBar(time.AddMinutes(i).Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
}
|
|
|
|
Assert.True(qstick.Last.Value > 0);
|
|
Assert.Equal(5.0, qstick.Last.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void BearishBars_ProduceNegativeQstick()
|
|
{
|
|
var qstick = new Qstick(5);
|
|
var time = DateTime.UtcNow;
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
qstick.Update(new TBar(time.AddMinutes(i).Ticks, 100.0, 105.0, 90.0, 95.0, 1000));
|
|
}
|
|
|
|
Assert.True(qstick.Last.Value < 0);
|
|
Assert.Equal(-5.0, qstick.Last.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void DojiBars_ProduceZeroQstick()
|
|
{
|
|
var qstick = new Qstick(5);
|
|
var time = DateTime.UtcNow;
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
qstick.Update(new TBar(time.AddMinutes(i).Ticks, 100.0, 105.0, 95.0, 100.0, 1000));
|
|
}
|
|
|
|
Assert.Equal(0.0, qstick.Last.Value, 10);
|
|
}
|
|
|
|
// ── Core Indicator Features ─────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void CoreIndicator_ResetsCorrectly()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
|
|
qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
|
|
|
|
Assert.NotEqual(default, qstick.Last);
|
|
|
|
qstick.Reset();
|
|
|
|
Assert.False(qstick.IsHot);
|
|
Assert.Equal(default, qstick.Last);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_IsHot_SmaMode_AfterPeriodBars()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
|
|
Assert.False(qstick.IsHot);
|
|
|
|
qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
Assert.False(qstick.IsHot);
|
|
|
|
qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
Assert.False(qstick.IsHot);
|
|
|
|
qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
Assert.True(qstick.IsHot);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_IsHot_EmaMode_AfterFirstBar()
|
|
{
|
|
var qstick = new Qstick(3, useEma: true);
|
|
var time = DateTime.UtcNow;
|
|
|
|
Assert.False(qstick.IsHot);
|
|
|
|
qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
Assert.True(qstick.IsHot);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_Period_ReturnsConstructorValue()
|
|
{
|
|
var qstick = new Qstick(20);
|
|
Assert.Equal(20, qstick.Period);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_UseEma_ReturnsConstructorValue()
|
|
{
|
|
var qstickSma = new Qstick(10, useEma: false);
|
|
Assert.False(qstickSma.UseEma);
|
|
|
|
var qstickEma = new Qstick(10, useEma: true);
|
|
Assert.True(qstickEma.UseEma);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_WarmupPeriod_EqualsPeriod()
|
|
{
|
|
var qstick = new Qstick(20);
|
|
Assert.Equal(20, qstick.WarmupPeriod);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_Name_SmaMode_DoesNotIncludeEma()
|
|
{
|
|
var qstick = new Qstick(14);
|
|
Assert.Equal("QSTICK(14)", qstick.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_Name_EmaMode_IncludesEma()
|
|
{
|
|
var qstick = new Qstick(14, useEma: true);
|
|
Assert.Equal("QSTICK(14,EMA)", qstick.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_InvalidPeriod_ThrowsArgumentException()
|
|
{
|
|
Assert.Throws<ArgumentException>(() => new Qstick(0));
|
|
Assert.Throws<ArgumentException>(() => new Qstick(-1));
|
|
}
|
|
|
|
// ── NaN/Infinity Handling ───────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void CoreIndicator_NaNOpen_ReturnsLastValue()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
|
|
var firstResult = qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
var nanResult = qstick.Update(new TBar(time.AddMinutes(1).Ticks, double.NaN, 110.0, 95.0, 105.0, 1000));
|
|
|
|
Assert.Equal(firstResult.Value, nanResult.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_InfinityClose_ReturnsLastValue()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
|
|
var firstResult = qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
var infResult = qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 110.0, 95.0, double.PositiveInfinity, 1000));
|
|
|
|
Assert.Equal(firstResult.Value, infResult.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_NegativeInfinityOpen_ReturnsLastValue()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
|
|
var firstResult = qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
var infResult = qstick.Update(new TBar(time.AddMinutes(1).Ticks, double.NegativeInfinity, 110.0, 95.0, 105.0, 1000));
|
|
|
|
Assert.Equal(firstResult.Value, infResult.Value);
|
|
}
|
|
|
|
// ── Bar Correction (isNew=false) ────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void CoreIndicator_BarCorrection_SmaMode_UpdatesLastBar()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
|
|
qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 110.0, 95.0, 103.0, 1000));
|
|
|
|
// New bar
|
|
qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 106.0, 1000), isNew: true);
|
|
var afterNew = qstick.Last.Value;
|
|
|
|
// Correct the same bar (isNew=false)
|
|
var afterCorrection = qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 112.0, 93.0, 110.0, 1000), isNew: false);
|
|
|
|
// Value should change since close-open changed from 6 to 10
|
|
Assert.NotEqual(afterNew, afterCorrection.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_BarCorrection_EmaMode_RollsBackState()
|
|
{
|
|
var qstick = new Qstick(3, useEma: true);
|
|
var time = DateTime.UtcNow;
|
|
|
|
qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
|
|
// New bar
|
|
qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 108.0, 95.0, 106.0, 1000), isNew: true);
|
|
var afterNew = qstick.Last.Value;
|
|
|
|
// Correct the same bar (isNew=false)
|
|
var afterCorrection = qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 112.0, 93.0, 110.0, 1000), isNew: false);
|
|
|
|
Assert.NotEqual(afterNew, afterCorrection.Value);
|
|
}
|
|
|
|
// ── Batch / Update(TBarSeries) / Calculate ──────────────────────────
|
|
|
|
[Fact]
|
|
public void CoreIndicator_UpdateTBarSeries_ReturnsTSeries()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
var series = new TBarSeries();
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
series.Add(time.AddMinutes(i).Ticks, 100.0, 110.0, 95.0, 105.0 + i, 1000);
|
|
}
|
|
|
|
var result = qstick.Update(series);
|
|
|
|
Assert.Equal(5, result.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_UpdateTBarSeries_EmptySeries_ReturnsEmpty()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var series = new TBarSeries();
|
|
|
|
var result = qstick.Update(series);
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_Batch_ReturnsResults()
|
|
{
|
|
var time = DateTime.UtcNow;
|
|
var series = new TBarSeries();
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
series.Add(time.AddMinutes(i).Ticks, 100.0, 110.0, 95.0, 105.0, 1000);
|
|
}
|
|
|
|
var result = Qstick.Batch(series, period: 3);
|
|
|
|
Assert.Equal(5, result.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_BatchEma_ReturnsResults()
|
|
{
|
|
var time = DateTime.UtcNow;
|
|
var series = new TBarSeries();
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
series.Add(time.AddMinutes(i).Ticks, 100.0, 110.0, 95.0, 105.0, 1000);
|
|
}
|
|
|
|
var result = Qstick.Batch(series, period: 3, useEma: true);
|
|
|
|
Assert.Equal(5, result.Count);
|
|
Assert.Equal(5.0, result.Last.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_Calculate_ReturnsResultsAndIndicator()
|
|
{
|
|
var time = DateTime.UtcNow;
|
|
var series = new TBarSeries();
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
series.Add(time.AddMinutes(i).Ticks, 100.0, 110.0, 95.0, 105.0, 1000);
|
|
}
|
|
|
|
var (results, indicator) = Qstick.Calculate(series, period: 3);
|
|
|
|
Assert.Equal(5, results.Count);
|
|
Assert.NotNull(indicator);
|
|
Assert.True(indicator.IsHot);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_Prime_WarmsUpIndicator()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
var series = new TBarSeries();
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
series.Add(time.AddMinutes(i).Ticks, 100.0, 110.0, 95.0, 105.0, 1000);
|
|
}
|
|
|
|
qstick.Prime(series);
|
|
|
|
Assert.True(qstick.IsHot);
|
|
Assert.Equal(5.0, qstick.Last.Value, 10);
|
|
}
|
|
|
|
// ── Pub Event ───────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void CoreIndicator_PubEvent_FiresOnUpdate()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
int eventCount = 0;
|
|
|
|
qstick.Pub += (object? sender, in TValueEventArgs args) => eventCount++;
|
|
|
|
qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
|
|
qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
|
|
|
|
Assert.Equal(2, eventCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void CoreIndicator_PubEvent_NaN_StillFires()
|
|
{
|
|
var qstick = new Qstick(3);
|
|
var time = DateTime.UtcNow;
|
|
int eventCount = 0;
|
|
|
|
qstick.Pub += (object? sender, in TValueEventArgs args) => eventCount++;
|
|
|
|
qstick.Update(new TBar(time.Ticks, double.NaN, 110.0, 95.0, 105.0, 1000));
|
|
|
|
Assert.Equal(1, eventCount);
|
|
}
|
|
|
|
// ── Different Periods ───────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void DifferentPeriods_ProduceDifferentResults()
|
|
{
|
|
var indicator1 = new QstickIndicator { Period = 3, MaType = "SMA" };
|
|
var indicator2 = new QstickIndicator { Period = 10, MaType = "SMA" };
|
|
indicator1.Initialize();
|
|
indicator2.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
double close = 105.0 + (i % 2 == 0 ? 3.0 : -3.0);
|
|
indicator1.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 115.0, 85.0, close, 1000);
|
|
indicator1.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
indicator2.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 115.0, 85.0, close, 1000);
|
|
indicator2.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double val1 = indicator1.LinesSeries[0].GetValue(0);
|
|
double val2 = indicator2.LinesSeries[0].GetValue(0);
|
|
Assert.NotEqual(val1, val2);
|
|
}
|
|
|
|
[Fact]
|
|
public void SmaVsEma_SameData_ProduceDifferentResults()
|
|
{
|
|
var smaIndicator = new QstickIndicator { Period = 5, MaType = "SMA" };
|
|
var emaIndicator = new QstickIndicator { Period = 5, MaType = "EMA" };
|
|
smaIndicator.Initialize();
|
|
emaIndicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
double[] closes = [105.0, 97.0, 108.0, 99.0, 102.0];
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
smaIndicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 115.0, 85.0, closes[i], 1000);
|
|
smaIndicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
emaIndicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 115.0, 85.0, closes[i], 1000);
|
|
emaIndicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
double smaVal = smaIndicator.LinesSeries[0].GetValue(0);
|
|
double emaVal = emaIndicator.LinesSeries[0].GetValue(0);
|
|
// SMA = (5 + -3 + 8 + -1 + 2) / 5 = 11/5 = 2.2
|
|
Assert.Equal(2.2, smaVal, 10);
|
|
Assert.NotEqual(smaVal, emaVal);
|
|
}
|
|
|
|
// ── Reinit ──────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Reinitialize_WithDifferentParameters_ResetsState()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 5, MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 110.0, 95.0, 105.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
indicator.Period = 10;
|
|
indicator.MaType = "EMA";
|
|
indicator.Initialize();
|
|
|
|
Assert.Equal("QSTICK(10,EMA)", indicator.ShortName);
|
|
}
|
|
|
|
// ── ShowColdValues ──────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void ShowColdValues_CanBeSetToFalse()
|
|
{
|
|
var indicator = new QstickIndicator { ShowColdValues = false };
|
|
Assert.False(indicator.ShowColdValues);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowColdValues_False_ProcessesWithoutError()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 5, ShowColdValues = false };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(i), 100.0, 110.0, 95.0, 105.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
}
|
|
|
|
Assert.NotNull(indicator);
|
|
}
|
|
|
|
// ── Multiple bars through adapter with known values ─────────────────
|
|
|
|
[Fact]
|
|
public void MultipleBars_ThroughAdapter_ProducesExpectedValues()
|
|
{
|
|
var indicator = new QstickIndicator { Period = 3, MaType = "SMA" };
|
|
indicator.Initialize();
|
|
|
|
var now = DateTime.UtcNow;
|
|
// Bar 1: diff = 5 (105-100)
|
|
indicator.HistoricalData.AddBar(now, 100.0, 110.0, 95.0, 105.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
// Bar 2: diff = 3 (103-100)
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(1), 100.0, 105.0, 95.0, 103.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
// Bar 3: diff = 6 (106-100)
|
|
indicator.HistoricalData.AddBar(now.AddMinutes(2), 100.0, 108.0, 95.0, 106.0, 1000);
|
|
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
|
|
|
|
// SMA(3) of [5, 3, 6] = 14/3 ≈ 4.667
|
|
Assert.Equal(14.0 / 3.0, indicator.LinesSeries[0].GetValue(0), 10);
|
|
}
|
|
|
|
// ── Parameters can be modified ──────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Period_CanBeChanged()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.Equal(14, indicator.Period);
|
|
|
|
indicator.Period = 30;
|
|
Assert.Equal(30, indicator.Period);
|
|
}
|
|
|
|
[Fact]
|
|
public void MaType_CanBeChanged()
|
|
{
|
|
var indicator = new QstickIndicator();
|
|
Assert.Equal("SMA", indicator.MaType);
|
|
|
|
indicator.MaType = "EMA";
|
|
Assert.Equal("EMA", indicator.MaType);
|
|
}
|
|
}
|