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