2026-03-04 16:17:43 +02:00 | | | //+------------------------------------------------------------------+
|
2026-03-15 02:46:38 +02:00 | | | //| C_Chapter2.mqh|
|
2026-03-04 16:17:43 +02:00 | | | //+------------------------------------------------------------------+
|
| | | #property copyright "Copyright 2025, MetaQuotes Ltd."
|
| | | #property link "https://www.mql5.com"
|
2026-03-15 02:46:38 +02:00 | | | #property version "1.00"
|
2026-03-04 16:17:43 +02: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;
|
2026-03-15 02:46:38 +02:00 | | |
|
2026-03-04 17:35:19 +02:00 | | | int protectedHighIndex;
|
| | | int protectedLowIndex;
|
| | | //========================
|
| | | // Keys
|
| | | //========================
|
2026-03-15 02:46:38 +02:00 | | | 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";
|
| | | }
|
2026-03-04 16:17:43 +02:00 | | |
|
| | | //========================
|
| | | // 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
|
| | | //========================
|
2026-03-15 02:46:38 +02:00 | | | 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);
|
| | | }
|
2026-03-04 16:17:43 +02:00 | | |
|
| | | //========================
|
| | | // 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;
|
2026-03-15 02:46:38 +02:00 | | |
|
2026-03-04 16:17:43 +02:00 | | | }
|
| | | }
|
| | | 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);
|
| | | }
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | // returns: START INDEX = 2 swings before the break (low side)
|
2026-03-04 16:17:43 +02:00 | | | 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;
|
| | | }
|
| | | }
|
2026-03-15 02:46:38 +02:00 | | | protectedLowIndex=protectedLow;
|
2026-03-04 16:17:43 +02:00 | | |
|
| | | DrawLine(K_B_ProtLow(), protectedLow, breakBar, bar.Low(protectedLow), text, clrChartreuse, 1);
|
| | |
|
| | | if(text == "BOS REVERSAL") {
|
2026-03-15 02:46:38 +02:00 | | | // anchor at breakBar, take 2 swing lows before break
|
2026-03-04 16:17:43 +02:00 | | | 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);
|
| | | }
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | // returns: START INDEX = 2 swings before the break (high side)
|
2026-03-04 16:17:43 +02:00 | | | 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;
|
| | | }
|
| | | }
|
2026-03-15 02:46:38 +02:00 | | | protectedHighIndex = protectedHigh;
|
2026-03-04 16:17:43 +02:00 | | |
|
| | | DrawLine(K_S_ProtHigh(), protectedHigh, breakBar, bar.High(protectedHigh), text, clrTomato, 1);
|
| | |
|
| | | if(text == "BOS REVERSAL") {
|
2026-03-15 02:46:38 +02:00 | | | // anchor at breakBar, take 2 swing highs before break
|
2026-03-04 16:17:43 +02:00 | | | return PrevSwingHighIndex(breakBar, ba);
|
| | | }
|
| | |
|
| | | return -1;
|
| | | }
|
| | |
|
| | | //========================
|
2026-03-15 02:46:38 +02:00 | | | // Relationship switches (start from 2 swings before break)
|
2026-03-04 16:17:43 +02:00 | | | //========================
|
| | | 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();
|
2026-03-15 02:46:38 +02:00 | | | void OnCalculate();
|
| | |
|
| | | // >>> getters for Chapter 3 wiring
|
2026-03-04 17:35:19 +02:00 | | | int GetProtectedHighIndex() { return protectedHighIndex; }
|
| | | int GetProtectedLowIndex() { return protectedLowIndex; }
|
| | |
|
2026-03-15 02:46:38 +02:00 | | |
|
2026-03-04 16:17:43 +02:00 | | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | 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() {
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | if(OnNewBar())
|
2026-03-04 16:17:43 +02:00 | | | 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);
|
| | |
|
2026-03-15 02:46:38 +02:00 | | | // Detect reversals: start opposite engine from 2 swings before break
|
2026-03-04 16:17:43 +02:00 | | | 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);
|
| | | }
|
2026-03-15 02:46:38 +02:00 | | | //+------------------------------------------------------------------+
|