/* * 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 }