Files
2026-07-13 13:02:50 +08:00

262 lines
11 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using System;
using System.Collections.Generic;
using System.Linq;
using Accord;
using QuantConnect.Data.Consolidators;
using QuantConnect.Util;
namespace QuantConnect.Tests.Indicators
{
[TestFixture]
public class TomDemarkSequentialTests : CommonIndicatorTests<IBaseDataBar>
{
protected override string TestFileName => "td_sequential_test_data.csv";
protected override string TestColumnName => "TDS";
protected override TomDemarkSequential CreateIndicator()
{
return new TomDemarkSequential("ABC");
}
[Test]
public void IsReadyAfterPeriodUpdates()
{
var ci = CreateIndicator();
Assert.IsFalse(ci.IsReady);
Enumerable.Range(1, 6).DoForEach(t => ci.Update(new TradeBar()));
Assert.IsTrue(ci.IsReady);
}
[Test]
public override void ResetsProperly()
{
var ci = CreateIndicator();
Enumerable.Range(1, 6).DoForEach(t => ci.Update(new TradeBar()));
Assert.IsTrue(ci.IsReady);
ci.Reset();
TestHelper.AssertIndicatorIsInDefaultState(ci);
}
[Test]
public override void AcceptsRenkoBarsAsInput()
{
var indicator = CreateIndicator();
var renkoConsolidator = new RenkoConsolidator(RenkoBarSize);
var renkoBarCount = 0;
renkoConsolidator.DataConsolidated += (sender, renkoBar) =>
{
renkoBarCount++;
Assert.DoesNotThrow(() => indicator.Update(renkoBar));
};
foreach (var parts in TestHelper.GetCsvFileStream(TestFileName))
{
var tradebar = parts.GetTradeBar();
renkoConsolidator.Update(tradebar);
}
Assert.IsTrue(renkoBarCount >= 1, "At least one Renko bars were emitted.");
renkoConsolidator.Dispose();
}
[Test]
public override void AcceptsVolumeRenkoBarsAsInput()
{
var indicator = CreateIndicator();
var volumeRenkoConsolidator = new VolumeRenkoConsolidator(VolumeRenkoBarSize);
var renkoBarCount = 0;
volumeRenkoConsolidator.DataConsolidated += (sender, volumeRenkoBar) =>
{
renkoBarCount++;
Assert.DoesNotThrow(() => indicator.Update(volumeRenkoBar));
};
foreach (var parts in TestHelper.GetCsvFileStream(TestFileName))
{
var tradebar = parts.GetTradeBar();
volumeRenkoConsolidator.Update(tradebar);
}
Assert.IsTrue(renkoBarCount >= 1, "Atleast one renko bar were emitted.");
volumeRenkoConsolidator.Dispose();
}
[TestCase(TomDemarkSequentialPhase.BuySetup)]
[TestCase(TomDemarkSequentialPhase.SellSetup)]
[TestCase(TomDemarkSequentialPhase.BuyCountdown)]
[TestCase(TomDemarkSequentialPhase.SellCountdown)]
[TestCase(TomDemarkSequentialPhase.BuySetupPerfect)]
[TestCase(TomDemarkSequentialPhase.SellSetupPerfect)]
public void GivenTradeBarsThenValidateExpectedResult(TomDemarkSequentialPhase phase)
{
var indicator = CreateIndicator();
var (prices, time) = SetupData(phase);
var expectedSetupStepCount = 1;
var expectedCountdownStepCount = 2;
var expectedPhase = (decimal)phase;
for (var index = 0; index < prices.Length; index++)
{
var price = prices[index];
var bar = new TradeBar(time, "ABC", price.Open, price.High, price.Low, price.Close, 1000);
var isReady = indicator.Update(bar);
time = time.AddMinutes(1);
if (index >= 5)
{
Assert.IsTrue(isReady);
if (phase is TomDemarkSequentialPhase.BuyCountdown or TomDemarkSequentialPhase.SellCountdown)
{
if (index < 14)
{
Assert.AreEqual(expectedSetupStepCount++, indicator.SetupPhaseStepCount);
}
else
{
Assert.AreEqual(expectedCountdownStepCount++, indicator.CountdownPhaseStepCount);
Assert.AreEqual(expectedPhase, indicator.Current.Value);
}
}
else
{
Assert.AreEqual(expectedSetupStepCount++, indicator.SetupPhaseStepCount);
expectedPhase = index switch
{
< 13 => phase switch
{
TomDemarkSequentialPhase.BuySetupPerfect => (decimal)TomDemarkSequentialPhase.BuySetup,
TomDemarkSequentialPhase.SellSetupPerfect =>
(decimal)TomDemarkSequentialPhase.SellSetup,
_ => (decimal)phase
},
_ => (decimal)phase
};
Assert.AreEqual(expectedPhase, indicator.Current.Value);
}
}
else
{
Assert.IsFalse(isReady);
}
}
}
private struct OCHL
{
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
}
private static (OCHL[], DateTime) SetupData(TomDemarkSequentialPhase phase)
{
OCHL[] prices = [];
var time = new DateTime(2023, 1, 1, 9, 30, 0);
prices = phase switch
{
TomDemarkSequentialPhase.BuySetup =>
[
// Bar 1 to 9 - Close < Close 4 bars ago
new OCHL { Open = 110, High = 111, Low = 109, Close = 100 },
new OCHL { Open = 110, High = 111, Low = 109, Close = 115 },
new OCHL { Open = 110, High = 111, Low = 109, Close = 114 },
new OCHL { Open = 110, High = 111, Low = 109, Close = 113 },
new OCHL { Open = 110, High = 111, Low = 109, Close = 111 },
new OCHL { Open = 110, High = 111, Low = 109, Close = 110 },
new OCHL { Open = 108, High = 109, Low = 107, Close = 106 },
new OCHL { Open = 106, High = 107, Low = 105, Close = 104 },
new OCHL { Open = 104, High = 105, Low = 103, Close = 102 },
new OCHL { Open = 102, High = 103, Low = 101, Close = 100 },
new OCHL { Open = 100, High = 101, Low = 91.5m, Close = 98 },
new OCHL { Open = 98, High = 99, Low = 95.5m, Close = 96 },
new OCHL { Open = 96, High = 97, Low = 96, Close = 94 },
new OCHL { Open = 94, High = 105, Low = 104, Close = 92 }
],
TomDemarkSequentialPhase.SellSetup =>
[
new OCHL { Open = 90, High = 91, Low = 89, Close = 91 },
new OCHL { Open = 90, High = 91, Low = 89, Close = 85 },
new OCHL { Open = 90, High = 91, Low = 89, Close = 87 },
new OCHL { Open = 90, High = 91, Low = 89, Close = 88 },
new OCHL { Open = 90, High = 91, Low = 89, Close = 89 },
new OCHL { Open = 90, High = 91, Low = 89, Close = 90 }, // 1
new OCHL { Open = 91, High = 92, Low = 90, Close = 91 }, // 2
new OCHL { Open = 92, High = 93, Low = 91, Close = 92 }, // 3
new OCHL { Open = 93, High = 94, Low = 92, Close = 93 }, // 4
new OCHL { Open = 94, High = 95, Low = 93, Close = 94 }, // 5 (Close > Bar1.Close)
new OCHL { Open = 95, High = 96, Low = 94, Close = 95 }, // 6 (Close > Bar2.Close)
new OCHL { Open = 96, High = 97, Low = 95, Close = 96 }, // 7 (Close > Bar3.Close)
new OCHL { Open = 97, High = 94, Low = 95.5m, Close = 97 },
new OCHL { Open = 98, High = 91.8m, Low = 90.8m, Close = 98 }
],
TomDemarkSequentialPhase.SellSetupPerfect => CreateSellSetupPerfect(),
TomDemarkSequentialPhase.BuySetupPerfect => CreateBuySetupPerfect(),
TomDemarkSequentialPhase.BuyCountdown => CreateBuyCountdownData(),
TomDemarkSequentialPhase.SellCountdown => CreateSellCountdownData(),
_ => prices
};
return (prices, time);
}
private static OCHL[] CreateSellCountdownData()
{
var ochls = new List<OCHL> { new OCHL { Open = 110, High = 90, Low = 90, Close = 110 } };
ochls.AddRange(Enumerable.Range(100, 25)
.Select(x => new OCHL { Open = x, High = x, Low = x, Close = x }));
return ochls.ToArray();
}
private static OCHL[] CreateBuyCountdownData()
{
var ochls = new List<OCHL> { new OCHL { Open = 90, High = 90, Low = 90, Close = 90 } };
ochls.AddRange(Enumerable.Range(1, 25).Select(y =>
{
var x = (decimal)(100 - y);
return new OCHL { Open = x, High = x, Low = x, Close = x };
}));
return ochls.ToArray();
}
private static OCHL[] CreateSellSetupPerfect()
{
var ochls = new List<OCHL> { new OCHL { Open = 110, High = 90, Low = 90, Close = 110 } };
ochls.AddRange(Enumerable.Range(100, 13)
.Select(x => new OCHL { Open = x, High = x, Low = x, Close = x }));
return ochls.ToArray();
}
private static OCHL[] CreateBuySetupPerfect()
{
var ochls = new List<OCHL> { new OCHL { Open = 90, High = 90, Low = 90, Close = 90 } };
ochls.AddRange(Enumerable.Range(1, 13).Select(y =>
{
var x = (decimal)(100 - y);
return new OCHL { Open = x, High = x, Low = x, Close = x };
}));
return ochls.ToArray();
}
}
}