/*
* 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.
*/
namespace QuantConnect.Indicators;
using System.Linq;
using QuantConnect.Data.Market;
///
/// Represents the Tom Demark Sequential indicator, which is used to identify potential trend exhaustion points.
/// This implementation tracks both the setup and countdown phases, and can be extended to handle bullish and bearish setups,
/// as well as qualifiers such as Perfect Setups and Countdown completions.
///
/// - **Setup Phase**: Detects a trend by counting 9 consecutive bars where the close is less than (Buy Setup)
/// or greater than (Sell Setup) the close 4 bars earlier.
/// - **TDST Support/Resistance**: After a valid 9-bar setup completes, the indicator records the **lowest low** (for Sell Setup)
/// or **highest high** (for Buy Setup) among the 9 bars. These are referred to as Support Price(for Buy Setup) and
/// Resistance Price (for Sell Setup), and serve as critical thresholds that validate the continuation of the countdown phase.
/// - **Countdown Phase**: Once a valid setup is completed, the indicator attempts to count 13 qualifying bars (not necessarily consecutive)
/// where the close is less than (Buy Countdown) or greater than (Sell Countdown) the low/high 2 bars earlier.
/// During this phase, the TDST Support/Resistance levels are checked — if the price breaks these levels, the countdown phase is reset,
/// as the trend reversal condition is considered invalidated.
///
///
/// This implementation uses a rolling window of 10 bars and begins logic when at least 5 bars are available.
///
///
///
///
///
public class TomDemarkSequential : WindowIndicator
{
///
/// The fixed number of bars used in the setup phase, as defined by the Tom DeMark Sequential (TD Sequential) indicator.
/// According to the TomDemark Sequential rules, a setup consists of exactly 9 consecutive qualifying bars.
///
private const int MaxSetupCount = 9;
///
/// The fixed number of bars used in the countdown phase, per the Tom Demark Sequential methodology.
/// A countdown is completed after exactly 13 qualifying bars.
/// These values are not configurable as they are fundamental to the algorithm's definition.
///
private const int MaxCountdownCount = 13;
///
/// The windows of size 10 bars are enough and begin logic when at least 5 bars are available.
///
private const int WindowSize = 10;
///
/// The TomDemark Sequential setup logic requires at least 6 bars to begin evaluating valid price flips and setups
///
private const int RequiredSamples = 6;
private static decimal Default => (decimal)TomDemarkSequentialPhase.None;
private TomDemarkSequentialPhase _currentPhase;
///
/// Gets the current resistance price calculated during a Tom Demark Sequential buy setup.
/// This is the highest high of the 9-bar setup and can act as a resistance level.
///
public decimal ResistancePrice { get; private set; }
///
/// Gets the current support price calculated during a Tom Demark Sequential sell setup.
/// This is the lowest low of the 9-bar setup and can act as a support level.
///
public decimal SupportPrice { get; private set; }
///
/// Gets the current Setup step count in the active Tom Demark Sequential (Buy/Sell) Setup phase
/// (e.g., 1 to 9 in Setup), 0 if not in setup phase.
///
public int SetupPhaseStepCount { get; private set; }
///
/// Gets the current Countdown step count in the active TomDemark Sequential (Buy/Sell) Countdown phase
/// (e.g., 1 to 13 in Setup), 0 if bar is not in Countdown phase.
///
public int CountdownPhaseStepCount { get; private set; }
///
/// Initializes a new instance of the indicator.
///
/// The name of the indicator.
public TomDemarkSequential(string name = "TomDemarkSequential")
: base(name, WindowSize)
{
}
///
/// Gets a value indicating whether the indicator is ready and fully initialized.
/// Returns true when at least 6 bars have been received, which is the minimum required
/// for checking for pre-requisite price flips and for comparing the current close price
/// to the close price 4 bars ago (used in the setup logic).
///
public override bool IsReady => Samples >= RequiredSamples;
///
/// Gets the number of data points (bars) required before the indicator is considered ready.
/// The TomDemark Sequential setup logic requires at least 6 bars to begin evaluating valid setups.
///
public override int WarmUpPeriod => RequiredSamples;
///
/// Resets the indicator to its initial state by clearing all internal counters, flags,
/// and historical bar data. This allows the indicator to be reused from a clean state.
///
public override void Reset()
{
SetupPhaseStepCount = 0;
CountdownPhaseStepCount = 0;
SupportPrice = 0;
ResistancePrice = 0;
_currentPhase = TomDemarkSequentialPhase.None;
base.Reset();
}
///
/// Computes the next value of the TD Sequential indicator based on the provided .
///
/// The window of data held in this indicator
/// The current input value to this indicator on this time step
/// The TomDemarkSequentialPhase state of the TD Sequential indicator for the current bar.
protected override decimal ComputeNextValue(IReadOnlyWindow window, IBaseDataBar current)
{
if (!IsReady)
{
return Default;
}
var bar4Ago = window[4];
var bar2Ago = window[2];
// Initialize setup if nothing is active
if (IsCurrentPhase(TomDemarkSequentialPhase.None))
{
var prevBar = window[1]; // previous to current bar
var prevBar4Ago = window[5]; // 4 bars before prevBar
return InitializeSetupPhase(current, bar4Ago, prevBar, prevBar4Ago);
}
// Buy Setup
if (IsCurrentPhase(TomDemarkSequentialPhase.BuySetup))
{
return HandleBuySetupPhase(current, bar4Ago, bar2Ago, window);
}
// Sell Setup
if (IsCurrentPhase(TomDemarkSequentialPhase.SellSetup))
{
return HandleSellSetupPhase(current, bar4Ago, bar2Ago, window);
}
// Buy Countdown
if (IsCurrentPhase(TomDemarkSequentialPhase.BuyCountdown))
{
return HandleBuyCountDown(current, bar2Ago);
}
// Sell Countdown
if (IsCurrentPhase(TomDemarkSequentialPhase.SellCountdown))
{
return HandleSellCountDown(current, bar2Ago);
}
return Default;
}
private bool IsCurrentPhase(TomDemarkSequentialPhase phase)
{
return _currentPhase == phase;
}
private decimal HandleSellCountDown(IBaseDataBar current, IBaseDataBar bar2Ago)
{
if (current.Close >= bar2Ago.High)
{
CountdownPhaseStepCount++;
if (CountdownPhaseStepCount == MaxCountdownCount)
{
_currentPhase = TomDemarkSequentialPhase.None;
}
return (decimal)TomDemarkSequentialPhase.SellCountdown;
}
if (current.Close < SupportPrice)
{
_currentPhase = TomDemarkSequentialPhase.None;
}
return Default;
}
private decimal HandleBuyCountDown(IBaseDataBar current, IBaseDataBar bar2Ago)
{
if (current.Close <= bar2Ago.Low)
{
CountdownPhaseStepCount++;
if (CountdownPhaseStepCount == MaxCountdownCount)
{
_currentPhase = TomDemarkSequentialPhase.None;
}
return (decimal)TomDemarkSequentialPhase.BuyCountdown;
}
if (current.Close > ResistancePrice)
{
_currentPhase = TomDemarkSequentialPhase.None;
}
return Default;
}
private decimal HandleSellSetupPhase(
IBaseDataBar current,
IBaseDataBar bar4Ago,
IBaseDataBar bar2Ago,
IReadOnlyWindow window
)
{
if (current.Close > bar4Ago.Close)
{
SetupPhaseStepCount++;
if (SetupPhaseStepCount == MaxSetupCount)
{
var isPerfect = IsSellSetupPerfect(window);
_currentPhase = TomDemarkSequentialPhase.SellCountdown;
// Check if the close of bar 9 or current bar is greater than the high two bars earlier
if (current.Close > bar2Ago.High)
{
CountdownPhaseStepCount = 1;
}
SupportPrice = window.Skip(window.Count - MaxSetupCount).Take(MaxSetupCount).Min(b => b.Low);
return
(decimal)(isPerfect
? TomDemarkSequentialPhase.SellSetupPerfect
: TomDemarkSequentialPhase.SellSetup);
}
return (decimal)TomDemarkSequentialPhase.SellSetup;
}
_currentPhase = TomDemarkSequentialPhase.None;
return Default;
}
private decimal HandleBuySetupPhase(
IBaseDataBar current,
IBaseDataBar bar4Ago,
IBaseDataBar bar2Ago,
IReadOnlyWindow window
)
{
if (current.Close < bar4Ago.Close)
{
SetupPhaseStepCount++;
if (SetupPhaseStepCount == MaxSetupCount)
{
var isPerfect = IsBuySetupPerfect(window);
_currentPhase = TomDemarkSequentialPhase.BuyCountdown;
ResistancePrice = window.Skip(window.Count - MaxSetupCount).Take(MaxSetupCount).Max(b => b.High);
// check the close of bar 9 is less than the low two bars earlier.
if (current.Close < bar2Ago.Low)
{
CountdownPhaseStepCount = 1;
}
return
(decimal)(isPerfect ? TomDemarkSequentialPhase.BuySetupPerfect : TomDemarkSequentialPhase.BuySetup);
}
return (decimal)TomDemarkSequentialPhase.BuySetup;
}
_currentPhase = TomDemarkSequentialPhase.None;
return Default;
}
private decimal InitializeSetupPhase(
IBaseDataBar current,
IBaseDataBar bar4Ago,
IBaseDataBar prevBar,
IBaseDataBar prevBar4Ago
)
{
// Bearish flip: prev close > previous's close[4 bars ago] and current close < close[4 bars ago]
if (prevBar.Close > prevBar4Ago.Close && current.Close < bar4Ago.Close)
{
_currentPhase = TomDemarkSequentialPhase.BuySetup;
SetupPhaseStepCount = 1;
return (decimal)TomDemarkSequentialPhase.BuySetup;
}
// Bullish flip: prev close < previous's close[4 bars ago] and current close > close[4 bars ago]
if (prevBar.Close < prevBar4Ago.Close && current.Close > bar4Ago.Close)
{
_currentPhase = TomDemarkSequentialPhase.SellSetup;
SetupPhaseStepCount = 1;
return (decimal)TomDemarkSequentialPhase.SellSetup;
}
return Default;
}
private static bool IsBuySetupPerfect(IReadOnlyWindow window)
{
var bar6 = window[3];
var bar7 = window[2];
var bar8 = window[1];
var bar9 = window[0];
return bar8.Low < bar6.Low && bar8.Low < bar7.Low ||
bar9.Low < bar6.Low && bar9.Low < bar7.Low;
}
private static bool IsSellSetupPerfect(IReadOnlyWindow window)
{
var bar6 = window[3];
var bar7 = window[2];
var bar8 = window[1];
var bar9 = window[0];
return bar8.High > bar6.High && bar8.High > bar7.High ||
bar9.High > bar6.High && bar9.High > bar7.High;
}
}
///
/// Represents the different phases of the TomDemark Sequential indicator.
///
public enum TomDemarkSequentialPhase
{
///
/// No active phase.
///
None = 0,
///
/// Buy setup phase.
///
BuySetup = 1,
///
/// Sell setup phase.
///
SellSetup = 2,
///
/// Buy countdown phase.
///
BuyCountdown = 3,
///
/// Sell countdown phase.
///
SellCountdown = 4,
///
/// Perfect buy setup phase.
///
BuySetupPerfect = 5,
///
/// Perfect sell setup phase.
///
SellSetupPerfect = 6
}