284 lines
12 KiB
MQL5
//+-------------------------------------------------------------------+
| |||
//| LW_MarketStructure_SMC.mq5 |
| |||
//| SMC Market Structure — Swing H/L + BOS + CHoCH |
| |||
//| Author : Yeoh Joo Yam | Display: Lonely Wolf |
| |||
//| 2026-07-02 v1.00 |
| |||
//| |
| |||
//| - Swing High/Low via N-bar fractal (InpSwingN each side). |
| |||
//| - BOS (Break of Structure) = close breaks last swing in the |
| |||
//| direction of the current trend -> continuation (green). |
| |||
//| - CHoCH (Change of Character) = close breaks last swing against |
| |||
//| the current trend -> possible reversal (red). |
| |||
//| - QML (Quasimodo) = on a CHoCH, dotted level at the left-shoulder |
| |||
//| swing (sell=prev high / buy=prev low) = retrace entry level. |
| |||
//+-------------------------------------------------------------------+
| |||
#property copyright "Lonely Wolf"
| |||
#property version "1.10"
| |||
#property description "SMC Market Structure: fractal swing H/L + BOS (continuation) + CHoCH (reversal) + Order Blocks + FVG + QML (Quasimodo) level on reversals. Close-confirmed. All drawn on the chart."
| |||
#property strict
| |||
#property indicator_chart_window
| |||
#property indicator_buffers 2
| |||
#property indicator_plots 2
| |||
| |||
//--- Plot 0: Swing High marker (down arrow at the high)
| |||
#property indicator_label1 "Swing High"
| |||
#property indicator_type1 DRAW_ARROW
| |||
#property indicator_color1 clrDodgerBlue
| |||
#property indicator_width1 1
| |||
| |||
//--- Plot 1: Swing Low marker (up arrow at the low)
| |||
#property indicator_label2 "Swing Low"
| |||
#property indicator_type2 DRAW_ARROW
| |||
#property indicator_color2 clrLimeGreen
| |||
#property indicator_width2 1
| |||
| |||
//============================== INPUTS ===============================
| |||
input int InpSwingN = 5; // Fractal bars each side (swing sensitivity; higher = fewer/bigger)
| |||
input bool InpBreakOnClose = true; // Confirm break on candle CLOSE (false = wick)
| |||
input int InpMaxBars = 1500; // How many recent bars to analyze
| |||
input color InpBOSColor = clrLimeGreen; // BOS = continuation
| |||
input color InpCHoCHColor = clrRed; // CHoCH = reversal
| |||
input bool InpShowLines = true; // Draw the broken-level line
| |||
input bool InpShowLabels = true; // Draw BOS / CHoCH text
| |||
input bool InpShowFVG = true; // Draw Fair Value Gaps (3-candle imbalance)
| |||
input color InpFVGColor = clrGoldenrod; // FVG fill color (brighter = easier to see on black)
| |||
input bool InpShowOB = true; // Draw Order Blocks (last opposite candle before a break)
| |||
input color InpOBBullColor = clrLimeGreen; // Bullish Order Block
| |||
input color InpOBBearColor = clrRed; // Bearish Order Block
| |||
input bool InpShowQML = true; // Draw QML (Quasimodo) level on CHoCH reversals
| |||
input color InpQMLColor = clrMagenta; // QML level (left-shoulder) dotted line
| |||
| |||
//============================== BUFFERS ==============================
| |||
double SwingHighBuf[];
| |||
double SwingLowBuf[];
| |||
| |||
//============================== STATE ================================
| |||
const string OBJ_PREFIX = "LWMS_";
| |||
datetime g_lastBarTime = 0;
| |||
| |||
//============================== INIT =================================
| |||
int OnInit()
| |||
{
| |||
SetIndexBuffer(0, SwingHighBuf, INDICATOR_DATA);
| |||
SetIndexBuffer(1, SwingLowBuf, INDICATOR_DATA);
| |||
| |||
PlotIndexSetInteger(0, PLOT_ARROW, 234); // down arrow, sits at swing high
| |||
PlotIndexSetInteger(1, PLOT_ARROW, 233); // up arrow, sits at swing low
| |||
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
| |||
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);
| |||
| |||
ArraySetAsSeries(SwingHighBuf, false);
| |||
ArraySetAsSeries(SwingLowBuf, false);
| |||
| |||
IndicatorSetString(INDICATOR_SHORTNAME, "LW Market Structure (SMC)");
| |||
return INIT_SUCCEEDED;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
void OnDeinit(const int reason)
| |||
{
| |||
ObjectsDeleteAll(0, OBJ_PREFIX);
| |||
}
| |||
| |||
//============================== DRAW HELPERS =========================
| |||
void DrawBreak(const datetime &time[], int fromBar, int atBar,
| |||
double level, bool bullish, bool isBOS)
| |||
{
| |||
color clr = isBOS ? InpBOSColor : InpCHoCHColor;
| |||
string tag = isBOS ? "BOS" : "CHoCH";
| |||
string arr = bullish ? " ^" : " v";
| |||
string base = OBJ_PREFIX + (string)atBar;
| |||
| |||
if(InpShowLines)
| |||
{
| |||
string ln = base + "_ln";
| |||
ObjectCreate(0, ln, OBJ_TREND, 0, time[fromBar], level, time[atBar], level);
| |||
ObjectSetInteger(0, ln, OBJPROP_COLOR, clr);
| |||
ObjectSetInteger(0, ln, OBJPROP_STYLE, isBOS ? STYLE_SOLID : STYLE_DASH);
| |||
ObjectSetInteger(0, ln, OBJPROP_WIDTH, 1);
| |||
ObjectSetInteger(0, ln, OBJPROP_RAY_RIGHT, false);
| |||
ObjectSetInteger(0, ln, OBJPROP_BACK, true);
| |||
ObjectSetInteger(0, ln, OBJPROP_SELECTABLE, false);
| |||
}
| |||
if(InpShowLabels)
| |||
{
| |||
string tx = base + "_tx";
| |||
ObjectCreate(0, tx, OBJ_TEXT, 0, time[atBar], level);
| |||
ObjectSetString(0, tx, OBJPROP_TEXT, tag + arr);
| |||
ObjectSetInteger(0, tx, OBJPROP_COLOR, clr);
| |||
ObjectSetInteger(0, tx, OBJPROP_FONTSIZE, 9);
| |||
ObjectSetInteger(0, tx, OBJPROP_ANCHOR, bullish ? ANCHOR_LOWER : ANCHOR_UPPER);
| |||
ObjectSetInteger(0, tx, OBJPROP_SELECTABLE, false);
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
void DrawBox(string name, datetime t1, double p1, datetime t2, double p2, color clr, bool fill)
| |||
{
| |||
ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, p1, t2, p2);
| |||
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
| |||
ObjectSetInteger(0, name, OBJPROP_FILL, fill);
| |||
ObjectSetInteger(0, name, OBJPROP_BACK, true);
| |||
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
| |||
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
| |||
}
| |||
| |||
// QML (Quasimodo): after a CHoCH, the "left shoulder" swing = the entry level.
| |||
// Bearish QML (sell) = left-shoulder HIGH ; Bullish QML (buy) = left-shoulder LOW.
| |||
// Dotted horizontal line extended right = the level price may retrace into.
| |||
void DrawQML(const datetime &time[], int shoulderBar, int atBar, double level, bool bullish)
| |||
{
| |||
string base = OBJ_PREFIX + "qml" + (string)atBar;
| |||
string ln = base + "_ln";
| |||
ObjectCreate(0, ln, OBJ_TREND, 0, time[shoulderBar], level, time[atBar], level);
| |||
ObjectSetInteger(0, ln, OBJPROP_COLOR, InpQMLColor);
| |||
ObjectSetInteger(0, ln, OBJPROP_STYLE, STYLE_DOT);
| |||
ObjectSetInteger(0, ln, OBJPROP_WIDTH, 1);
| |||
ObjectSetInteger(0, ln, OBJPROP_RAY_RIGHT, true); // extend right = retrace target
| |||
ObjectSetInteger(0, ln, OBJPROP_BACK, false);
| |||
ObjectSetInteger(0, ln, OBJPROP_SELECTABLE, false);
| |||
| |||
string tx = base + "_tx";
| |||
ObjectCreate(0, tx, OBJ_TEXT, 0, time[atBar], level);
| |||
ObjectSetString(0, tx, OBJPROP_TEXT, bullish ? "QML (buy)" : "QML (sell)");
| |||
ObjectSetInteger(0, tx, OBJPROP_COLOR, InpQMLColor);
| |||
ObjectSetInteger(0, tx, OBJPROP_FONTSIZE, 9);
| |||
ObjectSetInteger(0, tx, OBJPROP_ANCHOR, bullish ? ANCHOR_UPPER : ANCHOR_LOWER);
| |||
ObjectSetInteger(0, tx, OBJPROP_SELECTABLE, false);
| |||
}
| |||
| |||
//============================== ON CALCULATE =========================
| |||
int OnCalculate(const int rates_total,
| |||
const int prev_calculated,
| |||
const datetime &time[],
| |||
const double &open[],
| |||
const double &high[],
| |||
const double &low[],
| |||
const double &close[],
| |||
const long &tick_volume[],
| |||
const long &volume[],
| |||
const int &spread[])
| |||
{
| |||
if(rates_total < InpSwingN * 2 + 5)
| |||
return rates_total;
| |||
| |||
// Only recompute on a new bar (breaks are close-confirmed anyway)
| |||
if(time[rates_total - 1] == g_lastBarTime && prev_calculated > 0)
| |||
return rates_total;
| |||
g_lastBarTime = time[rates_total - 1];
| |||
| |||
// Reset our drawings + buffers for the analysis window
| |||
ObjectsDeleteAll(0, OBJ_PREFIX);
| |||
for(int i = 0; i < rates_total; i++)
| |||
{
| |||
SwingHighBuf[i] = 0.0;
| |||
SwingLowBuf[i] = 0.0;
| |||
}
| |||
| |||
int start = MathMax(InpSwingN, rates_total - InpMaxBars);
| |||
| |||
//--- Pass 1: mark fractal swing highs / lows
| |||
for(int i = start; i < rates_total - InpSwingN; i++)
| |||
{
| |||
bool isHigh = true, isLow = true;
| |||
for(int k = 1; k <= InpSwingN; k++)
| |||
{
| |||
if(high[i] <= high[i - k] || high[i] <= high[i + k])
| |||
isHigh = false;
| |||
if(low[i] >= low[i - k] || low[i] >= low[i + k])
| |||
isLow = false;
| |||
}
| |||
if(isHigh)
| |||
SwingHighBuf[i] = high[i];
| |||
if(isLow)
| |||
SwingLowBuf[i] = low[i];
| |||
}
| |||
| |||
//--- Pass 1b: Fair Value Gaps (3-candle imbalance, drawn over the gap)
| |||
if(InpShowFVG)
| |||
{
| |||
for(int i = start + 1; i < rates_total - 1; i++)
| |||
{
| |||
if(low[i + 1] > high[i - 1]) // bullish FVG (gap up)
| |||
DrawBox(OBJ_PREFIX + "fvg" + (string)i, time[i - 1], high[i - 1],
| |||
time[i + 1], low[i + 1], InpFVGColor, true);
| |||
else
| |||
if(high[i + 1] < low[i - 1]) // bearish FVG (gap down)
| |||
DrawBox(OBJ_PREFIX + "fvg" + (string)i, time[i - 1], low[i - 1],
| |||
time[i + 1], high[i + 1], InpFVGColor, true);
| |||
}
| |||
}
| |||
| |||
//--- Pass 2: BOS / CHoCH state machine
| |||
int trend = 0; // 1 = up, -1 = down, 0 = neutral
| |||
double refHigh = 0.0; // last swing-high level to break (bullish)
| |||
double refLow = 0.0; // last swing-low level to break (bearish)
| |||
int refHighBar = -1, refLowBar = -1;
| |||
bool haveHigh = false, haveLow = false;
| |||
// "left shoulder" = the swing BEFORE the current one (used for QML)
| |||
double prevHigh = 0.0, prevLow = 0.0;
| |||
int prevHighBar= -1, prevLowBar = -1;
| |||
| |||
for(int i = start + InpSwingN; i < rates_total; i++)
| |||
{
| |||
int j = i - InpSwingN; // swing confirmed at bar i sits at bar j
| |||
if(SwingHighBuf[j] > 0.0)
| |||
{
| |||
prevHigh = refHigh;
| |||
prevHighBar = refHighBar;
| |||
refHigh = high[j];
| |||
refHighBar = j;
| |||
haveHigh = true;
| |||
}
| |||
if(SwingLowBuf[j] > 0.0)
| |||
{
| |||
prevLow = refLow;
| |||
prevLowBar = refLowBar;
| |||
refLow = low[j];
| |||
refLowBar = j;
| |||
haveLow = true;
| |||
}
| |||
| |||
double upPx = InpBreakOnClose ? close[i] : high[i];
| |||
double downPx = InpBreakOnClose ? close[i] : low[i];
| |||
| |||
// Bullish break of the last swing high
| |||
if(haveHigh && upPx > refHigh)
| |||
{
| |||
bool isBOS = (trend != -1); // up-break with up/neutral trend = BOS; against down trend = CHoCH
| |||
DrawBreak(time, refHighBar, i, refHigh, true, isBOS);
| |||
if(InpShowOB) // Bullish OB = last DOWN candle before this up-break
| |||
for(int b = i; b > refHighBar; b--)
| |||
if(close[b] < open[b])
| |||
{ DrawBox(OBJ_PREFIX+"ob"+(string)b, time[b], high[b], time[i], low[b], InpOBBullColor, true); break; }
| |||
// Bullish QML: CHoCH up (reversal from down) -> left shoulder = prev swing LOW = buy level
| |||
if(InpShowQML && !isBOS && prevLowBar >= 0)
| |||
DrawQML(time, prevLowBar, i, prevLow, true);
| |||
trend = 1;
| |||
haveHigh = false; // level consumed; next swing high sets a new one
| |||
}
| |||
// Bearish break of the last swing low
| |||
else
| |||
if(haveLow && downPx < refLow)
| |||
{
| |||
bool isBOS = (trend != 1); // down-break with down/neutral trend = BOS; against up trend = CHoCH
| |||
DrawBreak(time, refLowBar, i, refLow, false, isBOS);
| |||
if(InpShowOB) // Bearish OB = last UP candle before this down-break
| |||
for(int b = i; b > refLowBar; b--)
| |||
if(close[b] > open[b])
| |||
{ DrawBox(OBJ_PREFIX+"ob"+(string)b, time[b], high[b], time[i], low[b], InpOBBearColor, true); break; }
| |||
// Bearish QML: CHoCH down (reversal from up) -> left shoulder = prev swing HIGH = sell level
| |||
if(InpShowQML && !isBOS && prevHighBar >= 0)
| |||
DrawQML(time, prevHighBar, i, prevHigh, false);
| |||
trend = -1;
| |||
haveLow = false;
| |||
}
| |||
}
| |||
| |||
return rates_total;
| |||
}
| |||
//+------------------------------------------------------------------+
|