mql5/Experts/_FrameWork/Analysers/Chapter2.mqh
younglubah-gmail a4b935d8d8
2026-09-30 09:06:59 +02:00

950 lines
No EOL
42 KiB
MQL5

//+------------------------------------------------------------------+
//| 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<StructureInternal>* internals;
StructureState() : bullNoBosActive(false), bearNoBosActive(false), isStartup(true),
bullSeekingReplacementNoBos(true), bearSeekingReplacementNoBos(true)
{
internals = new CArrayList<StructureInternal>();
}
~StructureState()
{
delete internals;
}
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class Chapter2: public AbstractAnalyser
{
private:
Indicator* ind;
TimeFrameDataSeries* bar;
StructureState* activeState;
CArrayList<StructureState*>* 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<StructureState*>();
}
~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);
}
};
//+------------------------------------------------------------------+