424 lines
12 KiB
MQL5
424 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| 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);
| |
}
| |
//+------------------------------------------------------------------+
|