950 lines
No EOL
42 KiB
MQL5
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);
| |
}
| |
};
| |
//+------------------------------------------------------------------+ |