//+------------------------------------------------------------------+ //| C_Chapter2.mqh| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "AbstractChapter.mqh" input int ba = 3; class C_Chapter2 : public CAbstractChapter { private: //======================== // Bull state //======================== int bosHigh, bosHigh0, noBosHigh; int bosHighbreakIndex, bosHighbreakIndex0; string protectedLowText; //======================== // Bear state //======================== int bosLow, bosLow0, noBosLow; int bosLowbreakIndex, bosLowbreakIndex0; string protectedHighText; //======================== // Gates (independent engines) //======================== int bullStartFrom; int bearStartFrom; int protectedHighIndex; int protectedLowIndex; //======================== // Keys //======================== string K_B_NoBosHigh() { return "CH2_B_NoBosHigh"; } string K_B_BosHigh() { return "CH2_B_BosHigh"; } string K_B_BosHigh0() { return "CH2_B_BosHigh0"; } string K_B_ConfLow() { return "CH2_B_ConfLow"; } string K_B_ProtLow() { return "CH2_B_ProtLow"; } string K_S_NoBosLow() { return "CH2_S_NoBosLow"; } string K_S_BosLow() { return "CH2_S_BosLow"; } string K_S_BosLow0() { return "CH2_S_BosLow0"; } string K_S_ConfHigh() { return "CH2_S_ConfHigh"; } string K_S_ProtHigh() { return "CH2_S_ProtHigh"; } //======================== // Draw //======================== void DrawLine(string key, int fromIndex, int toIndex, double price, string text, color col = clrChartreuse, int style = 1) { if(fromIndex < 0 || toIndex < 0) return; if(fromIndex >= bar.Size() || toIndex >= bar.Size()) return; debug.LineWithCenteredText( key, bar.Time(fromIndex), price, bar.Time(toIndex), price, text, style, col ); } //======================== // Breaks //======================== bool BreakAbove(int refIndex, int barIndex) { return bar.Close(barIndex) > bar.High(refIndex); } bool BreakBelow(int refIndex, int barIndex) { return bar.Close(barIndex) < bar.Low(refIndex); } //======================== // Extremes between points //======================== int findLowest(int fromIndex, int toIndex) { int lowestIndex = -1; for(int i = fromIndex + 1; i < toIndex; i++) { if(lowestIndex == -1 || bar.Low(i) < bar.Low(lowestIndex)) lowestIndex = i; } return lowestIndex; } int findHighest(int fromIndex, int toIndex) { int highestIndex = -1; for(int i = fromIndex + 1; i < toIndex; i++) { if(highestIndex == -1 || bar.High(i) > bar.High(highestIndex)) highestIndex = i; } return highestIndex; } //======================== // Find Nth previous VALID swing from an anchor (inclusive) // n=0 -> current if valid, n=1 -> 1 swing before, n=2 -> 2 swings before //======================== int PrevSwingHighIndex(int anchor, int n) { int count = -1; for(int i = anchor; i >= 0; i--) { if(swing.IsValidValue(swing.HighAt(i))) { count++; if(count == n) return i; } } return -1; } int PrevSwingLowIndex(int anchor, int n) { int count = -1; for(int i = anchor; i >= 0; i--) { if(swing.IsValidValue(swing.LowAt(i))) { count++; if(count == n) return i; } } return -1; } //======================== // Engines (pure updates, independent) //======================== void UpdateBullFrom(int start) { for(int i = start; i < bar.Size() - 1; i++) { double up = swing.HighAt(i); if(swing.IsValidValue(up)) { if(bosHigh >= 0) { if(up > bar.High(bosHigh) && (noBosHigh < 0 || up > bar.High(noBosHigh))) noBosHigh = i; } else { if(noBosHigh < 0 || up > bar.High(noBosHigh)) noBosHigh = i; } } if(noBosHigh != -1) { if(BreakAbove(noBosHigh, i) && swing.IsValidValue(swing.HighAt(noBosHigh))) { bosHigh0 = bosHigh; bosHighbreakIndex0 = bosHighbreakIndex; bosHigh = noBosHigh; bosHighbreakIndex = i; noBosHigh = i; } } if(noBosHigh >= 0 && !swing.IsValidValue(swing.HighAt(noBosHigh))) { if(bar.High(i) > bar.High(noBosHigh)) noBosHigh = i; } } } void UpdateBearFrom(int start) { for(int i = start; i < bar.Size() - 1; i++) { double dn = swing.LowAt(i); if(swing.IsValidValue(dn)) { if(bosLow >= 0) { if(dn < bar.Low(bosLow) && (noBosLow < 0 || dn < bar.Low(noBosLow))) noBosLow = i; } else { if(noBosLow < 0 || dn < bar.Low(noBosLow)) noBosLow = i; } } if(noBosLow != -1) { if(BreakBelow(noBosLow, i) && swing.IsValidValue(swing.LowAt(noBosLow))) { bosLow0 = bosLow; bosLowbreakIndex0 = bosLowbreakIndex; bosLow = noBosLow; bosLowbreakIndex = i; noBosLow = i; } } if(noBosLow >= 0 && !swing.IsValidValue(swing.LowAt(noBosLow))) { if(bar.Low(i) < bar.Low(noBosLow)) noBosLow = i; } } } //======================== // Pipelines (draw + detect reversal) //======================== void confirmedLowChoch(int pNoBosHigh, int pBosHighBreak) { DrawLine(K_B_BosHigh(), pNoBosHigh, pBosHighBreak, bar.High(pNoBosHigh), "BOS", clrChartreuse, 1); int confirmedLow = findLowest(pNoBosHigh, pBosHighBreak); int breakBar = bar.Size() - 1; string text = "Confirmed Low"; for(int i = confirmedLow; i < bar.Size() - 1; i++) { if(BreakBelow(confirmedLow, i)) { text = "CHOCH"; protectedLowText = "Protected Low"; breakBar = i; break; } else { protectedLowText = "Confirmed Low"; } } DrawLine(K_B_ConfLow(), confirmedLow, breakBar, bar.Low(confirmedLow), text, clrChartreuse, 1); } // returns: START INDEX = 2 swings before the break (low side) int protectedLowBos(int pBosHigh, int pBosHighBreak) { DrawLine(K_B_BosHigh0(), pBosHigh, pBosHighBreak, bar.High(pBosHigh), "BOS", clrChartreuse, 1); int protectedLow = findLowest(pBosHigh, pBosHighBreak); int breakBar = bar.Size() - 1; string text = protectedLowText; for(int i = protectedLow; i < bar.Size() - 1; i++) { if(BreakBelow(protectedLow, i)) { text = "BOS REVERSAL"; breakBar = i; break; } } protectedLowIndex=protectedLow; DrawLine(K_B_ProtLow(), protectedLow, breakBar, bar.Low(protectedLow), text, clrChartreuse, 1); if(text == "BOS REVERSAL") { // anchor at breakBar, take 2 swing lows before break return PrevSwingLowIndex(breakBar, ba); } return -1; } void confirmedHighChoch(int pNoBosLow, int pBosLowBreak) { DrawLine(K_S_BosLow(), pNoBosLow, pBosLowBreak, bar.Low(pNoBosLow), "BOS", clrTomato, 1); int confirmedHigh = findHighest(pNoBosLow, pBosLowBreak); int breakBar = bar.Size() - 1; string text = "Confirmed High"; for(int i = confirmedHigh; i < bar.Size() - 1; i++) { if(BreakAbove(confirmedHigh, i)) { text = "CHOCH"; protectedHighText = "Protected High"; breakBar = i; break; } else { protectedHighText = "Confirmed High"; } } DrawLine(K_S_ConfHigh(), confirmedHigh, breakBar, bar.High(confirmedHigh), text, clrTomato, 1); } // returns: START INDEX = 2 swings before the break (high side) int protectedHighBos(int pBosLow, int pBosLowBreak) { DrawLine(K_S_BosLow0(), pBosLow, pBosLowBreak, bar.Low(pBosLow), "BOS", clrTomato, 1); int protectedHigh = findHighest(pBosLow, pBosLowBreak); int breakBar = bar.Size() - 1; string text = protectedHighText; for(int i = protectedHigh; i < bar.Size() - 1; i++) { if(BreakAbove(protectedHigh, i)) { text = "BOS REVERSAL"; breakBar = i; break; } } protectedHighIndex = protectedHigh; DrawLine(K_S_ProtHigh(), protectedHigh, breakBar, bar.High(protectedHigh), text, clrTomato, 1); if(text == "BOS REVERSAL") { // anchor at breakBar, take 2 swing highs before break return PrevSwingHighIndex(breakBar, ba); } return -1; } //======================== // Relationship switches (start from 2 swings before break) //======================== void SwitchToBearFromBull(int startSwingLow) { bearStartFrom = (startSwingLow >= 0 ? startSwingLow : bearStartFrom); bosLow = -1; bosLow0 = -1; bosLowbreakIndex = -1; bosLowbreakIndex0 = -1; protectedHighText = ""; noBosLow = startSwingLow; } void SwitchToBullFromBear(int startSwingHigh) { bullStartFrom = (startSwingHigh >= 0 ? startSwingHigh : bullStartFrom); bosHigh = -1; bosHigh0 = -1; bosHighbreakIndex = -1; bosHighbreakIndex0 = -1; protectedLowText = ""; noBosHigh = startSwingHigh; } public: C_Chapter2(); ~C_Chapter2(); void OnCalculate(); // >>> getters for Chapter 3 wiring int GetProtectedHighIndex() { return protectedHighIndex; } int GetProtectedLowIndex() { return protectedLowIndex; } }; //+------------------------------------------------------------------+ C_Chapter2::C_Chapter2() { bosHigh = -1; bosHigh0 = -1; noBosHigh = -1; bosHighbreakIndex = -1; bosHighbreakIndex0 = -1; protectedLowText = ""; bosLow = -1; bosLow0 = -1; noBosLow = -1; bosLowbreakIndex = -1; bosLowbreakIndex0 = -1; protectedHighText = ""; bullStartFrom = 0; bearStartFrom = 0; } C_Chapter2::~C_Chapter2() {} //+------------------------------------------------------------------+ void C_Chapter2::OnCalculate() { if(OnNewBar()) return; bar.UpdateFromMonthsBack(symbol, timeframe, 3); // Update both engines every time (independent) UpdateBullFrom(bullStartFrom); UpdateBearFrom(bearStartFrom); // Draw CHOCH legs if(bosHigh >= 0 && bosHighbreakIndex >= 0) confirmedLowChoch(bosHigh, bosHighbreakIndex); if(bosLow >= 0 && bosLowbreakIndex >= 0) confirmedHighChoch(bosLow, bosLowbreakIndex); // Detect reversals: start opposite engine from 2 swings before break int startBearFromSwingLow = -1; int startBullFromSwingHigh = -1; if(bosHigh0 >= 0 && bosHighbreakIndex0 >= 0) startBearFromSwingLow = protectedLowBos(bosHigh0, bosHighbreakIndex0); if(bosLow0 >= 0 && bosLowbreakIndex0 >= 0) startBullFromSwingHigh = protectedHighBos(bosLow0, bosLowbreakIndex0); if(startBearFromSwingLow >= 0) { SwitchToBearFromBull(startBearFromSwingLow); UpdateBearFrom(bearStartFrom); } if(startBullFromSwingHigh >= 0) { SwitchToBullFromBear(startBullFromSwingHigh); UpdateBullFrom(bullStartFrom); } // NoBOS lines if(noBosHigh >= 0) DrawLine(K_B_NoBosHigh(), noBosHigh, bar.GetBarCount() - 1, bar.High(noBosHigh), "NO BOS", clrChartreuse, 1); if(noBosLow >= 0) DrawLine(K_S_NoBosLow(), noBosLow, bar.GetBarCount() - 1, bar.Low(noBosLow), "NO BOS", clrTomato, 1); } //+------------------------------------------------------------------+