//+------------------------------------------------------------------+ //| Chapter2.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "AbstractAnalyser.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct StructureLevels { TimePrice protectedHigh; TimePrice protectedLow; TimePrice bullNoBos, noBullBosLow; TimePrice bearNoBos, noBearBosHigh; TimePrice bullConfirmedLow; TimePrice bearConfirmedHigh; //--- BOS (Break of Structure) TimePrice bosBull1; TimePrice bosBull1B, confirmedLowB; TimePrice bosBull2; TimePrice bosBull2B, protectedLowB; TimePrice bosBear1; TimePrice bosBear1B, confirmedHighB; TimePrice bosBear2; TimePrice bosBear2B, protectedHighB; //--- Swings Tracking TimePrice lastSwingHigh; TimePrice lastSwingLow; TimePrice bosSwingHigh; TimePrice bosSwingLow; int direction; //--- Print all levels to the Experts log void PrintLevels() { Print("--- StructureLevels ---"); Print("protectedHigh: ", protectedHigh.ToString()); Print("protectedLow: ", protectedLow.ToString()); Print("bullNoBos: ", bullNoBos.ToString()); Print("noBullBosLow: ", noBullBosLow.ToString()); Print("bearNoBos: ", bearNoBos.ToString()); Print("noBearBosHigh: ", noBearBosHigh.ToString()); Print("bullConfirmedLow: ", bullConfirmedLow.ToString()); Print("bearConfirmedHigh: ", bearConfirmedHigh.ToString()); Print("bosBull1: ", bosBull1.ToString()); Print("bosBull1B: ", bosBull1B.ToString()); Print("bosBull2: ", bosBull2.ToString()); Print("bosBull2B: ", bosBull2B.ToString()); Print("bosBear1: ", bosBear1.ToString()); Print("bosBear1B: ", bosBear1B.ToString()); Print("bosBear2: ", bosBear2.ToString()); Print("bosBear2B: ", bosBear2B.ToString()); Print("confirmedLowB: ", confirmedLowB.ToString()); Print("protectedLowB: ", protectedLowB.ToString()); Print("confirmedHighB:", confirmedHighB.ToString()); Print("protectedHighB:", protectedHighB.ToString()); Print("lastSwingHigh: ", lastSwingHigh.ToString()); Print("lastSwingLow: ", lastSwingLow.ToString()); Print("bosSwingHigh: ", bosSwingHigh.ToString()); Print("bosSwingLow: ", bosSwingLow.ToString()); Print("direction: ", direction); } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct StructureInternal { TimePrice extreme; // The extreme valley/peak before breaking the level TimePrice level; // The inner tracked level datetime breakTime; // When it was broken bool broken; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct InternalTrackingState { int direction; // 1 = Bull, -1 = Bear, 0 = None TimePrice lastExtremeHigh; // the peak that caused a downward break TimePrice lastExtremeLow; // the valley that caused an upward break void Reset() { direction = 0; lastExtremeHigh.Reset(); lastExtremeLow.Reset(); } }; //+------------------------------------------------------------------+ //| Helper to draw Internal Structure Breaks | //+------------------------------------------------------------------+ class TrendlineDrawerHelper { public: static void DrawInternal(string symbol, ENUM_TIMEFRAMES tf, StructureInternal &intStruct, int dir, string labelTxt) { long chart_id = 0; // Default to main chart string baseName = "Int_" + symbol + "_" + EnumToString(tf) + "_" + TimeToString(intStruct.breakTime); string lineName = baseName + "_lvl"; string extName = baseName + "_ext"; string lblName = baseName + "_lbl"; color col = (dir == 1) ? clrDodgerBlue : clrCrimson; // Draw the broken internal level ObjectCreate(chart_id, lineName, OBJ_TREND, 0, intStruct.level.x, intStruct.level.y, intStruct.breakTime, intStruct.level.y); ObjectSetInteger(chart_id, lineName, OBJPROP_COLOR, col); ObjectSetInteger(chart_id, lineName, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(chart_id, lineName, OBJPROP_WIDTH, 1); ObjectSetInteger(chart_id, lineName, OBJPROP_RAY_RIGHT, false); GetVisibilityMaskManual(lineName, tf); // Draw vertical dot line connecting the extreme down/up to the broken level ObjectCreate(chart_id, extName, OBJ_TREND, 0, intStruct.extreme.x, intStruct.extreme.y, intStruct.extreme.x, intStruct.level.y); ObjectSetInteger(chart_id, extName, OBJPROP_COLOR, col); ObjectSetInteger(chart_id, extName, OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(chart_id, extName, OBJPROP_WIDTH, 1); ObjectSetInteger(chart_id, extName, OBJPROP_RAY_RIGHT, false); GetVisibilityMaskManual(extName, tf); //set // Add text label /* ObjectCreate(chart_id, lblName, OBJ_TEXT, 0, intStruct.breakTime, intStruct.level.y); ObjectSetString(chart_id, lblName, OBJPROP_TEXT, labelTxt); ObjectSetInteger(chart_id, lblName, OBJPROP_COLOR, col); ObjectSetInteger(chart_id, lblName, OBJPROP_FONTSIZE, 8);*/ } static void ClearDrawings(string symbol, ENUM_TIMEFRAMES tf) { long chart_id = 0; int total = ObjectsTotal(chart_id, 0, -1); string prefix = "Int_" + symbol + "_" + EnumToString(tf); for(int i = total - 1; i >= 0; i--) { string objName = ObjectName(chart_id, i, 0, -1); if(StringFind(objName, prefix) == 0) { ObjectDelete(chart_id, objName); } } } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class StructureState { public: string symbol; ENUM_TIMEFRAMES tf; bool bullNoBosActive, bullSeekingReplacementNoBos; bool bearNoBosActive, bearSeekingReplacementNoBos; bool isStartup; StructureLevels lvl; // Tracking constraints for Internal Structure InternalTrackingState internalState; TimePrice internalBullExtreme; TimePrice internalBearExtreme; CArrayList* internals; StructureState() : bullNoBosActive(false), bearNoBosActive(false), isStartup(true), bullSeekingReplacementNoBos(true), bearSeekingReplacementNoBos(true) { internals = new CArrayList(); } ~StructureState() { delete internals; } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class Chapter2: public AbstractAnalyser { private: Indicator* ind; TimeFrameDataSeries* bar; StructureState* activeState; CArrayList* states; //+------------------------------------------------------------------+ //| Helper: Push Market Struct Event | //+------------------------------------------------------------------+ void PushStructEvent(Direction dir, StructType type, datetime tA, double pA, datetime tB, double pB) { MarketStructEvent* e = new MarketStructEvent(bar.symbol, bar.tf, dir, type, tA, pA, tB, pB); eventBus.Push(e); } //+------------------------------------------------------------------+ //| Helper: Push Market Update Event | //+------------------------------------------------------------------+ void PushUpdateEvent(Direction dir, StructType type, datetime tA, double pA, datetime tB, double pB) { MarketUpdateEvent* e = new MarketUpdateEvent(bar.symbol, bar.tf, dir, type, tA, pA, tB, pB); eventBus.Push(e); } //+------------------------------------------------------------------+ //| Helper: Push Direction Change Event | //+------------------------------------------------------------------+ void PushDirChange(Direction dir) { DirectionChangeEvent* e = new DirectionChangeEvent(bar.symbol, bar.tf, dir); eventBus.Push(e); } //+------------------------------------------------------------------+ //| Find existing symbol/timeframe state | //+------------------------------------------------------------------+ int FindExist(string symbol, ENUM_TIMEFRAMES tf) { for(int i = 0; i < states.Size(); i++) { StructureState* data = states.At(i); if(data.symbol == symbol && data.tf == tf) return i; } return -1; } //+------------------------------------------------------------------+ //| Check structure break | //| Fixed: Use High/Low of swing candle instead of Close | //+------------------------------------------------------------------+ bool CheckBreak(bool isAbove, int i, bool tpOk, double tpY) { if(!tpOk) return false; // A break is confirmed only when a swing high/low forms and its extreme exceeds the level. // We do NOT require the close to be beyond the level. if(isAbove) return (bar.High(i) > tpY) && IsSwingHigh(i, ind); return (bar.Low(i) < tpY) && IsSwingLow(i, ind); } //+------------------------------------------------------------------+ //| Track Live Swings for reference later | //+------------------------------------------------------------------+ void TrackSwings(int i) { if(IsSwingHigh(i, ind)) activeState.lvl.lastSwingHigh.Set(bar.Time(i), bar.High(i)); if(IsSwingLow(i, ind)) activeState.lvl.lastSwingLow.Set(bar.Time(i), bar.Low(i)); } //+------------------------------------------------------------------+ //| Helper: Sync Internal State Constraint with Outer | //+------------------------------------------------------------------+ void SyncInternalState() { TimePrice limitLvl; if(activeState.lvl.direction == 1) { limitLvl = activeState.lvl.protectedLow.Ok() ? activeState.lvl.protectedLow : activeState.lvl.bullConfirmedLow; } else if(activeState.lvl.direction == -1) { limitLvl = activeState.lvl.protectedHigh.Ok() ? activeState.lvl.protectedHigh : activeState.lvl.bearConfirmedHigh; } // No internals time must be before the protected/confirmed constraint if(limitLvl.Ok()) { if(activeState.internalState.lastExtremeHigh.Ok() && activeState.internalState.lastExtremeHigh.x < limitLvl.x) activeState.internalState.lastExtremeHigh.Reset(); if(activeState.internalState.lastExtremeLow.Ok() && activeState.internalState.lastExtremeLow.x < limitLvl.x) activeState.internalState.lastExtremeLow.Reset(); } } public: Chapter2() { states = new CArrayList(); } ~Chapter2() { for(int i = 0; i < states.Size(); i++) { StructureState* state = states.At(i); delete state; } delete states; } virtual void Reset(string symbol, ENUM_TIMEFRAMES tf) override { int index = FindExist(symbol, tf); if(index >= 0) { StructureState* state = states.At(index); if(activeState == state) activeState = NULL; states.RemoveAt(index); delete state; TrendlineDrawerHelper::ClearDrawings(symbol, tf); Print("Chapter2 Analyser: State & Internal Drawings successfully reset for ", symbol, " [", EnumToString(tf), "]"); } } virtual string GetAnalyserName() override { return "Chapter2"; } //+------------------------------------------------------------------+ //| Process bar | //+------------------------------------------------------------------+ virtual void ProcessBar(TimeFrameDataSeries* series, int i) override { ind = series.GetIndicator(0); bar = series; int stateIndex = FindExist(series.symbol, series.tf); if(stateIndex < 0) { StructureState* s = new StructureState(); s.symbol = series.symbol; s.tf = series.tf; states.Add(s); activeState = s; } else { activeState = states.At(stateIndex); } // Track running swings directly upon processing TrackSwings(i); if(!activeState.bullNoBosActive && activeState.bullSeekingReplacementNoBos && activeState.isStartup) OnInitTracking(true, i); if(!activeState.bearNoBosActive && activeState.bearSeekingReplacementNoBos && activeState.isStartup) OnInitTracking(false, i); if(activeState.isStartup && activeState.bearNoBosActive && activeState.bullNoBosActive) { if(activeState.lvl.bullNoBos.x < activeState.lvl.bearNoBos.x) { activeState.lvl.noBullBosLow.Set(activeState.lvl.bearNoBos); activeState.internalBullExtreme.Set(activeState.lvl.bearNoBos); } else { activeState.lvl.noBearBosHigh.Set(activeState.lvl.bullNoBos); activeState.internalBearExtreme.Set(activeState.lvl.bullNoBos); } activeState.isStartup = false; } else if(!activeState.isStartup && (!activeState.bearNoBosActive || !activeState.bullNoBosActive)) return; FindDirection(i); UpdateNoBosLive(i); CheckBos(i); CheckChoch(i); CheckReversal(i); } //+------------------------------------------------------------------+ //| Check reversals | //+------------------------------------------------------------------+ void CheckReversal(int i) { datetime t = bar.Time(i); //=============================================================== // Bullish → Bearish reversal //=============================================================== if(activeState.lvl.direction == 1) { //--- Protected Low has priority if(activeState.lvl.protectedLow.Ok()) { if(!activeState.lvl.protectedLowB.Ok() && CheckBreak(false, i, activeState.lvl.protectedLow.Ok(), activeState.lvl.protectedLow.y)) { activeState.lvl.protectedLowB.Set(t, activeState.lvl.protectedLow.y); PushUpdateEvent(BEAR, ProtectedLow, activeState.lvl.protectedLow.x, activeState.lvl.protectedLow.y, activeState.lvl.protectedLowB.x, activeState.lvl.protectedLowB.y); PushDirChange(BEAR); activeState.lvl.bearConfirmedHigh = activeState.lvl.bullNoBos; activeState.lvl.confirmedHighB.Reset(); PushStructEvent(BULL, BearConfirmedHigh, activeState.lvl.bearConfirmedHigh.x, activeState.lvl.bearConfirmedHigh.y, 0, 0); activeState.lvl.bosBear1 = activeState.lvl.protectedLow; activeState.lvl.bosBear1B.Set(t, activeState.lvl.protectedLow.y); PushStructEvent(BULL, BearBos, activeState.lvl.bosBear1.x, activeState.lvl.bosBear1.y, activeState.lvl.bosBear1B.x, activeState.lvl.bosBear1B.y); activeState.lvl.bearNoBos.Set(t, bar.Low(i)); activeState.lvl.noBearBosHigh.Set(t, bar.High(i)); // Wipe Internal Structure on Outer Reversal activeState.internalState.Reset(); activeState.internalBearExtreme.Set(t, bar.Low(i)); PushStructEvent(BEAR, BearNoBos, t, bar.Low(i), 0, 0); activeState.lvl.direction = -1; return; } } //--- No Protected Low → Confirmed Low becomes reversal level if(!activeState.lvl.protectedLow.Ok()) { if(activeState.lvl.bullConfirmedLow.Ok() && !activeState.lvl.confirmedLowB.Ok() && CheckBreak(false, i, activeState.lvl.bullConfirmedLow.Ok(), activeState.lvl.bullConfirmedLow.y)) { if(activeState.lvl.bosSwingLow.Ok() && activeState.lvl.bullConfirmedLow.x != activeState.lvl.bosSwingLow.x) { activeState.lvl.bullConfirmedLow = activeState.lvl.bosSwingLow; activeState.lvl.confirmedLowB.Reset(); PushStructEvent(BULL, BullConfirmedLow, activeState.lvl.bullConfirmedLow.x, activeState.lvl.bullConfirmedLow.y, 0, 0); if(!CheckBreak(false, i, activeState.lvl.bullConfirmedLow.Ok(), activeState.lvl.bullConfirmedLow.y)) return; } activeState.lvl.confirmedLowB.Set(t, activeState.lvl.bullConfirmedLow.y); PushUpdateEvent(BEAR, BullConfirmedLow, activeState.lvl.bullConfirmedLow.x, activeState.lvl.bullConfirmedLow.y, activeState.lvl.confirmedLowB.x, activeState.lvl.confirmedLowB.y); PushDirChange(BEAR); activeState.lvl.bearConfirmedHigh = activeState.lvl.bullNoBos; activeState.lvl.confirmedHighB.Reset(); PushStructEvent(BULL, BearConfirmedHigh, activeState.lvl.bearConfirmedHigh.x, activeState.lvl.bearConfirmedHigh.y, 0, 0); activeState.lvl.bosBear1 = activeState.lvl.bullConfirmedLow; activeState.lvl.bosBear1B.Set(t, activeState.lvl.bullConfirmedLow.y); PushStructEvent(BULL, BearBos, activeState.lvl.bosBear1.x, activeState.lvl.bosBear1.y, activeState.lvl.bosBear1B.x, activeState.lvl.bosBear1B.y); activeState.lvl.bearNoBos.Set(t, bar.Low(i)); activeState.lvl.noBearBosHigh.Set(t, bar.High(i)); // Wipe Internal Structure on Outer Reversal activeState.internalState.Reset(); activeState.internalBearExtreme.Set(t, bar.Low(i)); PushStructEvent(BEAR, BearNoBos, t, bar.Low(i), 0, 0); activeState.lvl.direction = -1; return; } } } //=============================================================== // Bearish → Bullish reversal //=============================================================== if(activeState.lvl.direction == -1) { //--- Protected High has priority if(activeState.lvl.protectedHigh.Ok()) { if(!activeState.lvl.protectedHighB.Ok() && CheckBreak(true, i, activeState.lvl.protectedHigh.Ok(), activeState.lvl.protectedHigh.y)) { activeState.lvl.protectedHighB.Set(t, activeState.lvl.protectedHigh.y); PushUpdateEvent(BULL, ProtectedHigh, activeState.lvl.protectedHigh.x, activeState.lvl.protectedHigh.y, activeState.lvl.protectedHighB.x, activeState.lvl.protectedHighB.y); PushDirChange(BULL); activeState.lvl.bullConfirmedLow = activeState.lvl.bearNoBos; activeState.lvl.confirmedLowB.Reset(); PushStructEvent(BEAR, BullConfirmedLow, activeState.lvl.bullConfirmedLow.x, activeState.lvl.bullConfirmedLow.y, 0, 0); activeState.lvl.bosBull1 = activeState.lvl.protectedHigh; activeState.lvl.bosBull1B.Set(t, activeState.lvl.protectedHigh.y); PushStructEvent(BEAR, BullBos, activeState.lvl.bosBull1.x, activeState.lvl.bosBull1.y, activeState.lvl.bosBull1B.x, activeState.lvl.bosBull1B.y); activeState.lvl.bullNoBos.Set(t, bar.High(i)); activeState.lvl.noBullBosLow.Set(t, bar.Low(i)); // Wipe Internal Structure on Outer Reversal activeState.internalState.Reset(); activeState.internalBullExtreme.Set(t, bar.High(i)); PushStructEvent(BULL, BullNoBos, t, bar.High(i), 0, 0); activeState.lvl.direction = 1; return; } } //--- No Protected High → Confirmed High becomes reversal level if(!activeState.lvl.protectedHigh.Ok()) { if(activeState.lvl.bearConfirmedHigh.Ok() && !activeState.lvl.confirmedHighB.Ok() && CheckBreak(true, i, activeState.lvl.bearConfirmedHigh.Ok(), activeState.lvl.bearConfirmedHigh.y)) { if(activeState.lvl.bosSwingHigh.Ok() && activeState.lvl.bearConfirmedHigh.x != activeState.lvl.bosSwingHigh.x) { activeState.lvl.bearConfirmedHigh = activeState.lvl.bosSwingHigh; activeState.lvl.confirmedHighB.Reset(); PushStructEvent(BEAR, BearConfirmedHigh, activeState.lvl.bearConfirmedHigh.x, activeState.lvl.bearConfirmedHigh.y, 0, 0); if(!CheckBreak(true, i, activeState.lvl.bearConfirmedHigh.Ok(), activeState.lvl.bearConfirmedHigh.y)) return; } activeState.lvl.confirmedHighB.Set(t, activeState.lvl.bearConfirmedHigh.y); PushUpdateEvent(BULL, BearConfirmedHigh, activeState.lvl.bearConfirmedHigh.x, activeState.lvl.bearConfirmedHigh.y, activeState.lvl.confirmedHighB.x, activeState.lvl.confirmedHighB.y); PushDirChange(BULL); activeState.lvl.bullConfirmedLow = activeState.lvl.bearNoBos; activeState.lvl.confirmedLowB.Reset(); PushStructEvent(BEAR, BullConfirmedLow, activeState.lvl.bullConfirmedLow.x, activeState.lvl.bullConfirmedLow.y, 0, 0); activeState.lvl.bosBull1 = activeState.lvl.bearConfirmedHigh; activeState.lvl.bosBull1B.Set(t, activeState.lvl.bearConfirmedHigh.y); PushStructEvent(BEAR, BullBos, activeState.lvl.bosBull1.x, activeState.lvl.bosBull1.y, activeState.lvl.bosBull1B.x, activeState.lvl.bosBull1B.y); activeState.lvl.bullNoBos.Set(t, bar.High(i)); activeState.lvl.noBullBosLow.Set(t, bar.Low(i)); // Wipe Internal Structure on Outer Reversal activeState.internalState.Reset(); activeState.internalBullExtreme.Set(t, bar.High(i)); PushStructEvent(BULL, BullNoBos, t, bar.High(i), 0, 0); activeState.lvl.direction = 1; return; } } } } //+------------------------------------------------------------------+ //| Check CHOCH | //+------------------------------------------------------------------+ void CheckChoch(int i) { datetime t = bar.Time(i); //--- Bullish structure if(activeState.lvl.direction == 1 && activeState.lvl.protectedLow.Ok()) { if(activeState.lvl.bullConfirmedLow.Ok() && !activeState.lvl.confirmedLowB.Ok() && CheckBreak(false, i, activeState.lvl.bullConfirmedLow.Ok(), activeState.lvl.bullConfirmedLow.y)) { if(activeState.lvl.bosSwingLow.Ok() && activeState.lvl.bullConfirmedLow.x != activeState.lvl.bosSwingLow.x) { activeState.lvl.bullConfirmedLow = activeState.lvl.bosSwingLow; activeState.lvl.confirmedLowB.Reset(); PushStructEvent(BULL, BullConfirmedLow, activeState.lvl.bullConfirmedLow.x, activeState.lvl.bullConfirmedLow.y, 0, 0); if(!CheckBreak(false, i, activeState.lvl.bullConfirmedLow.Ok(), activeState.lvl.bullConfirmedLow.y)) return; } activeState.lvl.confirmedLowB.Set(t, activeState.lvl.bullConfirmedLow.y); PushUpdateEvent(BEAR, BullConfirmedLow, activeState.lvl.bullConfirmedLow.x, activeState.lvl.bullConfirmedLow.y, activeState.lvl.confirmedLowB.x, activeState.lvl.confirmedLowB.y); } } //--- Bearish structure if(activeState.lvl.direction == -1 && activeState.lvl.protectedHigh.Ok()) { if(activeState.lvl.bearConfirmedHigh.Ok() && !activeState.lvl.confirmedHighB.Ok() && CheckBreak(true, i, activeState.lvl.bearConfirmedHigh.Ok(), activeState.lvl.bearConfirmedHigh.y)) { if(activeState.lvl.bosSwingHigh.Ok() && activeState.lvl.bearConfirmedHigh.x != activeState.lvl.bosSwingHigh.x) { activeState.lvl.bearConfirmedHigh = activeState.lvl.bosSwingHigh; activeState.lvl.confirmedHighB.Reset(); PushStructEvent(BEAR, BearConfirmedHigh, activeState.lvl.bearConfirmedHigh.x, activeState.lvl.bearConfirmedHigh.y, 0, 0); if(!CheckBreak(true, i, activeState.lvl.bearConfirmedHigh.Ok(), activeState.lvl.bearConfirmedHigh.y)) return; } activeState.lvl.confirmedHighB.Set(t, activeState.lvl.bearConfirmedHigh.y); PushUpdateEvent(BULL, BearConfirmedHigh, activeState.lvl.bearConfirmedHigh.x, activeState.lvl.bearConfirmedHigh.y, activeState.lvl.confirmedHighB.x, activeState.lvl.confirmedHighB.y); } } } //+------------------------------------------------------------------+ //| Check BOS | //+------------------------------------------------------------------+ void CheckBos(int i) { datetime t = bar.Time(i); //--- Bullish BOS if(activeState.lvl.direction == 1) { if(activeState.lvl.bullNoBos.Ok() && CheckBreak(true, i, activeState.lvl.bullNoBos.Ok(), activeState.lvl.bullNoBos.y)) { if(activeState.lvl.noBullBosLow.Ok() && activeState.lvl.bullConfirmedLow.Ok() && activeState.lvl.noBullBosLow.y > activeState.lvl.bullConfirmedLow.y) { activeState.lvl.protectedLow = activeState.lvl.bullConfirmedLow; activeState.lvl.protectedLowB.Reset(); PushStructEvent(BEAR, ProtectedLow, activeState.lvl.protectedLow.x, activeState.lvl.protectedLow.y, 0, 0); activeState.lvl.bosBull2 = activeState.lvl.bosBull1; activeState.lvl.bosBull2B = activeState.lvl.bosBull1B; PushStructEvent(BULL, BullBos0, activeState.lvl.bosBull2.x, activeState.lvl.bosBull2.y, activeState.lvl.bosBull2B.x, activeState.lvl.bosBull2B.y); } activeState.lvl.bullConfirmedLow = activeState.lvl.noBullBosLow; activeState.lvl.bosSwingLow = activeState.lvl.lastSwingLow; activeState.lvl.confirmedLowB.Reset(); PushStructEvent(BULL, BullConfirmedLow, activeState.lvl.bullConfirmedLow.x, activeState.lvl.bullConfirmedLow.y, 0, 0); activeState.lvl.bosBull1 = activeState.lvl.bullNoBos; activeState.lvl.bosBull1B.Set(t, activeState.lvl.bullNoBos.y); PushStructEvent(BEAR, BullBos, activeState.lvl.bosBull1.x, activeState.lvl.bosBull1.y, activeState.lvl.bosBull1B.x, activeState.lvl.bosBull1B.y); activeState.lvl.bullNoBos.Set(t, bar.High(i)); PushStructEvent(BULL, BullNoBos, t, bar.High(i), 0, 0); activeState.lvl.noBullBosLow.Set(t, bar.Low(i)); // Outer BOS confirms new leg -> wipe internals from old leg activeState.internalState.Reset(); activeState.internalBullExtreme.Set(t, bar.High(i)); PushStructEvent(BEAR, NoBullBosLow, activeState.lvl.noBullBosLow.x, activeState.lvl.noBullBosLow.y, 0, 0); return; } } //--- Bearish BOS if(activeState.lvl.direction == -1) { if(activeState.lvl.bearNoBos.Ok() && CheckBreak(false, i, activeState.lvl.bearNoBos.Ok(), activeState.lvl.bearNoBos.y)) { if(activeState.lvl.noBearBosHigh.Ok() && activeState.lvl.bearConfirmedHigh.Ok() && activeState.lvl.noBearBosHigh.y < activeState.lvl.bearConfirmedHigh.y) { activeState.lvl.protectedHigh = activeState.lvl.bearConfirmedHigh; activeState.lvl.protectedHighB.Reset(); PushStructEvent(BULL, ProtectedHigh, activeState.lvl.protectedHigh.x, activeState.lvl.protectedHigh.y, 0, 0); activeState.lvl.bosBear2 = activeState.lvl.bosBear1; activeState.lvl.bosBear2B = activeState.lvl.bosBear1B; PushStructEvent(BEAR, BearBos0, activeState.lvl.bosBear2.x, activeState.lvl.bosBear2.y, activeState.lvl.bosBear2B.x, activeState.lvl.bosBear2B.y); } activeState.lvl.bearConfirmedHigh = activeState.lvl.noBearBosHigh; activeState.lvl.bosSwingHigh = activeState.lvl.lastSwingHigh; activeState.lvl.confirmedHighB.Reset(); PushStructEvent(BEAR, BearConfirmedHigh, activeState.lvl.bearConfirmedHigh.x, activeState.lvl.bearConfirmedHigh.y, 0, 0); activeState.lvl.bosBear1 = activeState.lvl.bearNoBos; activeState.lvl.bosBear1B.Set(t, activeState.lvl.bearNoBos.y); PushStructEvent(BULL, BearBos, activeState.lvl.bosBear1.x, activeState.lvl.bosBear1.y, activeState.lvl.bosBear1B.x, activeState.lvl.bosBear1B.y); activeState.lvl.bearNoBos.Set(t, bar.Low(i)); PushStructEvent(BEAR, BearNoBos, t, bar.Low(i), 0, 0); activeState.lvl.noBearBosHigh.Set(t, bar.High(i)); // Outer BOS confirms new leg -> wipe internals from old leg activeState.internalState.Reset(); activeState.internalBearExtreme.Set(t, bar.Low(i)); PushStructEvent(BULL, NoBearBosHigh, activeState.lvl.noBearBosHigh.x, activeState.lvl.noBearBosHigh.y, 0, 0); return; } } } //+------------------------------------------------------------------+ //| Update live No BOS levels & Internal Structure Tracks | //+------------------------------------------------------------------+ void UpdateNoBosLive(int i) { datetime t = bar.Time(i); if(activeState.lvl.direction == 1 || activeState.lvl.direction == 0) { // 0. Sync outer constraint SyncInternalState(); // 1. Check for Internal CHOCH (Upward break of the last extreme high while pulling back) if(activeState.internalState.direction == -1 && activeState.internalState.lastExtremeHigh.Ok()) { if(bar.Close(i) > activeState.internalState.lastExtremeHigh.y) { StructureInternal intStruct; intStruct.extreme = activeState.lvl.noBullBosLow; // The valley that caused it intStruct.level = activeState.internalState.lastExtremeHigh; intStruct.breakTime = t; intStruct.broken = true; activeState.internals.Add(intStruct); TrendlineDrawerHelper::DrawInternal(activeState.symbol, activeState.tf, intStruct, 1, "iCHOCH"); activeState.internalState.direction = 1; activeState.internalState.lastExtremeLow = activeState.lvl.noBullBosLow; // New internal support } } // 2. Trailing down outer limits & logging Internal BOS / CHOCH downwards if(!activeState.lvl.noBullBosLow.Ok() || bar.Low(i) < activeState.lvl.noBullBosLow.y) { if(activeState.lvl.noBullBosLow.Ok() && activeState.internalBullExtreme.Ok()) { // If an extreme peak was formed after the previous limit translation... if(activeState.internalBullExtreme.x > activeState.lvl.noBullBosLow.x) { StructureInternal intStruct; intStruct.extreme = activeState.internalBullExtreme; intStruct.level = activeState.lvl.noBullBosLow; intStruct.breakTime = t; intStruct.broken = true; activeState.internals.Add(intStruct); string lbl = (activeState.internalState.direction == 1 || activeState.internalState.direction == 0) ? "iCHOCH" : "iBOS"; TrendlineDrawerHelper::DrawInternal(activeState.symbol, activeState.tf, intStruct, -1, lbl); activeState.internalState.direction = -1; activeState.internalState.lastExtremeHigh = activeState.internalBullExtreme; // New internal resistance } } // Safely translate NoBos limits activeState.lvl.noBullBosLow.Set(t, bar.Low(i)); // Reset internal extreme tracking after boundary movement activeState.internalBullExtreme.Set(t, bar.High(i)); PushStructEvent(BEAR, NoBullBosLow, activeState.lvl.noBullBosLow.x, activeState.lvl.noBullBosLow.y, 0, 0); } else { // Constantly monitor for the peak before breaking down the bound if(!activeState.internalBullExtreme.Ok() || bar.High(i) > activeState.internalBullExtreme.y) { activeState.internalBullExtreme.Set(t, bar.High(i)); } } if(activeState.lvl.direction == 1) return; } if(activeState.lvl.direction == -1 || activeState.lvl.direction == 0) { // 0. Sync outer constraint SyncInternalState(); // 1. Check for Internal CHOCH (Downward break of the last extreme low while pulling back upwards) if(activeState.internalState.direction == 1 && activeState.internalState.lastExtremeLow.Ok()) { if(bar.Close(i) < activeState.internalState.lastExtremeLow.y) { StructureInternal intStruct; intStruct.extreme = activeState.lvl.noBearBosHigh; // The peak that caused it intStruct.level = activeState.internalState.lastExtremeLow; intStruct.breakTime = t; intStruct.broken = true; activeState.internals.Add(intStruct); TrendlineDrawerHelper::DrawInternal(activeState.symbol, activeState.tf, intStruct, -1, "iCHOCH"); activeState.internalState.direction = -1; activeState.internalState.lastExtremeHigh = activeState.lvl.noBearBosHigh; // New internal resistance } } // 2. Trailing up outer limits & logging Internal BOS / CHOCH upwards if(!activeState.lvl.noBearBosHigh.Ok() || bar.High(i) > activeState.lvl.noBearBosHigh.y) { if(activeState.lvl.noBearBosHigh.Ok() && activeState.internalBearExtreme.Ok()) { // If an extreme valley was formed after the previous limit translation... if(activeState.internalBearExtreme.x > activeState.lvl.noBearBosHigh.x) { StructureInternal intStruct; intStruct.extreme = activeState.internalBearExtreme; intStruct.level = activeState.lvl.noBearBosHigh; intStruct.breakTime = t; intStruct.broken = true; activeState.internals.Add(intStruct); string lbl = (activeState.internalState.direction == -1 || activeState.internalState.direction == 0) ? "iCHOCH" : "iBOS"; TrendlineDrawerHelper::DrawInternal(activeState.symbol, activeState.tf, intStruct, 1, lbl); activeState.internalState.direction = 1; activeState.internalState.lastExtremeLow = activeState.internalBearExtreme; // New internal support } } // Safely translate NoBos limits activeState.lvl.noBearBosHigh.Set(t, bar.High(i)); // Reset internal extreme tracking after boundary movement activeState.internalBearExtreme.Set(t, bar.Low(i)); PushStructEvent(BULL, NoBearBosHigh, activeState.lvl.noBearBosHigh.x, activeState.lvl.noBearBosHigh.y, 0, 0); } else { // Constantly monitor for the valley before breaking up the bound if(!activeState.internalBearExtreme.Ok() || bar.Low(i) < activeState.internalBearExtreme.y) { activeState.internalBearExtreme.Set(t, bar.Low(i)); } } } } //+------------------------------------------------------------------+ //| Find initial direction | //+------------------------------------------------------------------+ void FindDirection(int i) { if(activeState.lvl.direction == 0) { //--- Bullish if(activeState.lvl.bullNoBos.Ok() && CheckBreak(true, i, activeState.lvl.bullNoBos.Ok(), activeState.lvl.bullNoBos.y)) { activeState.lvl.bosBull1.Set(activeState.lvl.bullNoBos); activeState.lvl.bosBull1B.Set(bar.Time(i), activeState.lvl.bullNoBos.y); PushStructEvent(BULL, BullBos, activeState.lvl.bosBull1.x, activeState.lvl.bosBull1.y, activeState.lvl.bosBull1B.x, activeState.lvl.bosBull1B.y); activeState.lvl.bullConfirmedLow.Set(activeState.lvl.noBullBosLow); activeState.lvl.confirmedLowB.Reset(); activeState.lvl.bosSwingLow = activeState.lvl.lastSwingLow; PushStructEvent(BULL, BullConfirmedLow, activeState.lvl.bullConfirmedLow.x, activeState.lvl.bullConfirmedLow.y, 0, 0); activeState.lvl.bullNoBos.Set(bar.Time(i), bar.High(i)); activeState.lvl.noBullBosLow.Set(bar.Time(i), bar.Low(i)); activeState.internalState.Reset(); activeState.internalBullExtreme.Set(bar.Time(i), bar.High(i)); PushStructEvent(BULL, BullNoBos, bar.Time(i), bar.High(i), 0, 0); PushDirChange(BULL); activeState.lvl.direction = 1; return; } //--- Bearish if(activeState.lvl.bearNoBos.Ok() && CheckBreak(false, i, activeState.lvl.bearNoBos.Ok(), activeState.lvl.bearNoBos.y)) { activeState.lvl.bosBear1.Set(activeState.lvl.bearNoBos); activeState.lvl.bosBear1B.Set(bar.Time(i), activeState.lvl.bearNoBos.y); PushStructEvent(BEAR, BearBos, activeState.lvl.bosBear1.x, activeState.lvl.bosBear1.y, activeState.lvl.bosBear1B.x, activeState.lvl.bosBear1B.y); activeState.lvl.bearConfirmedHigh.Set(activeState.lvl.noBearBosHigh); activeState.lvl.confirmedHighB.Reset(); activeState.lvl.bosSwingHigh = activeState.lvl.lastSwingHigh; PushStructEvent(BEAR, BearConfirmedHigh, activeState.lvl.bearConfirmedHigh.x, activeState.lvl.bearConfirmedHigh.y, 0, 0); activeState.lvl.bearNoBos.Set(bar.Time(i), bar.Low(i)); activeState.lvl.noBearBosHigh.Set(bar.Time(i), bar.High(i)); activeState.internalState.Reset(); activeState.internalBearExtreme.Set(bar.Time(i), bar.Low(i)); PushStructEvent(BEAR, BearNoBos, bar.Time(i), bar.Low(i), 0, 0); PushDirChange(BEAR); activeState.lvl.direction = -1; } } } //+------------------------------------------------------------------+ //| Initialize tracking | //+------------------------------------------------------------------+ void OnInitTracking(bool isBull, int i) { if(isBull) { if(!IsSwingHigh(i, ind)) return; activeState.lvl.bullNoBos.Set(i, bar.Time(i), bar.High(i)); activeState.lvl.noBullBosLow.Set(i, bar.Time(i), bar.Low(i)); activeState.internalBullExtreme.Set(i, bar.Time(i), bar.High(i)); // Int tracking PushStructEvent(BULL, BullNoBos, bar.Time(i), bar.High(i), 0, 0); activeState.bullNoBosActive = true; return; } if(!IsSwingLow(i, ind)) return; activeState.lvl.bearNoBos.Set(i, bar.Time(i), bar.Low(i)); activeState.lvl.noBearBosHigh.Set(i, bar.Time(i), bar.High(i)); activeState.internalBearExtreme.Set(i, bar.Time(i), bar.Low(i)); // Int tracking PushStructEvent(BEAR, BearNoBos, bar.Time(i), bar.Low(i), 0, 0); activeState.bearNoBosActive = true; } //+------------------------------------------------------------------+ //| Detect swings (Uses class member 'bar' implicitly) | //+------------------------------------------------------------------+ void detectSwings(int sB) { if(IsSwingHigh(sB, ind)) { SwingDetactedEvent* s = new SwingDetactedEvent(bar.symbol, bar.tf, BULL, bar.Time(sB), bar.High(sB)); eventBus.Push(s); } if(IsSwingLow(sB, ind)) { SwingDetactedEvent* s = new SwingDetactedEvent(bar.symbol, bar.tf, BEAR, bar.Time(sB), bar.Low(sB)); eventBus.Push(s); } } //+------------------------------------------------------------------+ //| Bullish candle | //+------------------------------------------------------------------+ bool isBullCandle(double open, double close) { return (open < close); } }; //+------------------------------------------------------------------+