262 lines
11 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|