using Skender.Stock.Indicators; using TALib; using OoplesFinance.StockIndicators; using OoplesFinance.StockIndicators.Models; using OoplesFinance.StockIndicators.Enums; using QuanTAlib.Tests; namespace QuanTAlib; /// /// Validation tests for DX (Directional Movement Index). /// Note: DX is the unsmoothed version of ADX. Not all libraries provide DX directly, /// but TA-Lib has DX function. Skender provides ADX which includes DI values. /// public sealed class DxValidationTests : IDisposable { private readonly ValidationTestData _data; public DxValidationTests() { _data = new ValidationTestData(); } public void Dispose() { _data.Dispose(); } [Fact] public void MatchesTulip() { var dx = new Dx(14); var results = new List(); for (int i = 0; i < _data.Bars.Count; i++) { var res = dx.Update(_data.Bars[i]); results.Add(res.Value); } double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); double[][] inputs = { hData, lData, cData }; double[] options = { 14 }; var dxInd = Tulip.Indicators.dx; double[][] outputs = { new double[hData.Length - dxInd.Start(options)] }; dxInd.Run(inputs, options, outputs); double[] tulipResults = outputs[0]; // Tulip initializes differently, so we skip the warmup period to verify convergence int offset = dxInd.Start(options); ValidationHelper.VerifyData(results, tulipResults, lookback: offset); } [Fact] public void DiPlus_MatchesTalib() { var dx = new Dx(14); var diPlusResults = new List(); for (int i = 0; i < _data.Bars.Count; i++) { dx.Update(_data.Bars[i]); diPlusResults.Add(dx.DiPlus.Value); } double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); double[] outReal = new double[_data.Bars.Count]; var retCode = Functions.PlusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.PlusDILookback(14); ValidationHelper.VerifyData(diPlusResults, outReal, outRange, lookback); } [Fact] public void DiMinus_MatchesTalib() { var dx = new Dx(14); var diMinusResults = new List(); for (int i = 0; i < _data.Bars.Count; i++) { dx.Update(_data.Bars[i]); diMinusResults.Add(dx.DiMinus.Value); } double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray(); double[] outReal = new double[_data.Bars.Count]; var retCode = Functions.MinusDI(hData, lData, cData, 0..^0, outReal, out var outRange, 14); Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.MinusDILookback(14); ValidationHelper.VerifyData(diMinusResults, outReal, outRange, lookback); } [Fact] public void DmPlus_MatchesTalib() { var dx = new Dx(14); var dmPlusResults = new List(); for (int i = 0; i < _data.Bars.Count; i++) { dx.Update(_data.Bars[i]); dmPlusResults.Add(dx.DmPlus.Value); } double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); double[] outReal = new double[_data.Bars.Count]; var retCode = Functions.PlusDM(hData, lData, 0..^0, outReal, out var outRange, 14); Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.PlusDMLookback(14); ValidationHelper.VerifyData(dmPlusResults, outReal, outRange, lookback); } [Fact] public void DmMinus_MatchesTalib() { var dx = new Dx(14); var dmMinusResults = new List(); for (int i = 0; i < _data.Bars.Count; i++) { dx.Update(_data.Bars[i]); dmMinusResults.Add(dx.DmMinus.Value); } double[] hData = _data.Bars.High.Select(x => x.Value).ToArray(); double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray(); double[] outReal = new double[_data.Bars.Count]; var retCode = Functions.MinusDM(hData, lData, 0..^0, outReal, out var outRange, 14); Assert.Equal(TALib.Core.RetCode.Success, retCode); int lookback = Functions.MinusDMLookback(14); ValidationHelper.VerifyData(dmMinusResults, outReal, outRange, lookback); } [Fact] public void MatchesSkender_DiValues() { var dx = new Dx(14); var diPlusResults = new List(); var diMinusResults = new List(); for (int i = 0; i < _data.Bars.Count; i++) { dx.Update(_data.Bars[i]); diPlusResults.Add(dx.DiPlus.Value); diMinusResults.Add(dx.DiMinus.Value); } // Skender's GetAdx returns ADX with +DI and -DI values var skenderResults = _data.SkenderQuotes.GetAdx(14).ToList(); // Verify +DI ValidationHelper.VerifyData(diPlusResults, skenderResults, x => x.Pdi); // Verify -DI ValidationHelper.VerifyData(diMinusResults, skenderResults, x => x.Mdi); } }