2026-10-19 18:01:31 +03:00 | | | #property copyright "Lonely Wolf & AI"
|
| | | #property version "1.20"
|
| | | #property description "SMC Market Structure: FVG + OB + QML с автоматическим удалением после 3-го касания"
|
2026-10-19 17:51:23 +03:00 | | | #property strict
|
| | | #property indicator_chart_window
|
| | | #property indicator_buffers 2
|
| | | #property indicator_plots 2
|
| | |
|
2026-10-19 18:01:31 +03:00 | | | //--- Plot 0: Swing High marker
|
2026-10-19 17:51:23 +03:00 | | | #property indicator_label1 "Swing High"
|
| | | #property indicator_type1 DRAW_ARROW
|
| | | #property indicator_color1 clrDodgerBlue
|
| | | #property indicator_width1 1
|
| | |
|
2026-10-19 18:01:31 +03:00 | | | //--- Plot 1: Swing Low marker
|
2026-10-19 17:51:23 +03:00 | | | #property indicator_label2 "Swing Low"
|
| | | #property indicator_type2 DRAW_ARROW
|
| | | #property indicator_color2 clrLimeGreen
|
| | | #property indicator_width2 1
|
| | |
|
| | | //============================== INPUTS ===============================
|
2026-10-19 18:01:31 +03:00 | | | input int InpSwingN = 5;
|
| | | input bool InpBreakOnClose = true;
|
| | | input int InpMaxBars = 1500;
|
| | | input color InpBOSColor = clrLimeGreen;
|
| | | input color InpCHoCHColor = clrRed;
|
| | | input bool InpShowLines = true;
|
| | | input bool InpShowLabels = true;
|
| | | input bool InpShowFVG = true;
|
| | | input color InpFVGColor = clrGoldenrod;
|
| | | input bool InpShowOB = true;
|
| | | input color InpOBBullColor = clrLimeGreen;
|
| | | input color InpOBBearColor = clrRed;
|
| | | input bool InpShowQML = true;
|
| | | input color InpQMLColor = clrMagenta;
|
2026-10-19 17:51:23 +03:00 | | |
|
| | | //============================== 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);
|
| | |
|
2026-10-19 18:01:31 +03:00 | | | PlotIndexSetInteger(0, PLOT_ARROW, 234);
|
| | | PlotIndexSetInteger(1, PLOT_ARROW, 233);
|
2026-10-19 17:51:23 +03:00 | | | PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
|
| | | PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);
|
| | |
|
| | | ArraySetAsSeries(SwingHighBuf, false);
|
| | | ArraySetAsSeries(SwingLowBuf, false);
|
| | |
|
2026-10-19 18:01:31 +03:00 | | | IndicatorSetString(INDICATOR_SHORTNAME, "LW Market Structure (QML 3-Touch Filter)");
|
2026-10-19 17:51:23 +03:00 | | | return INIT_SUCCEEDED;
|
| | | }
|
| | |
|
| | | void OnDeinit(const int reason)
|
| | | {
|
| | | ObjectsDeleteAll(0, OBJ_PREFIX);
|
| | | }
|
| | |
|
| | | //============================== DRAW HELPERS =========================
|
2026-10-19 18:01:31 +03:00 | | | void DrawBreak(const datetime &time[], int fromBar, int atBar, double level, bool bullish, bool isBOS)
|
2026-10-19 17:51:23 +03:00 | | | {
|
| | | 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);
|
| | | }
|
| | |
|
2026-10-19 18:01:31 +03:00 | | | // НОВАЯ ЛОГИКА QML: Считаем касания на истории и ограничиваем длину
|
| | | void DrawQMLWithTouchFilter(const datetime &time[], const double &high[], const double &low[],
|
| | | int shoulderBar, int chochBar, double level, bool bullish, int rates_total)
|
2026-10-19 17:51:23 +03:00 | | | {
|
2026-10-19 18:01:31 +03:00 | | | int touchCount = 0;
|
| | | int endBar = rates_total - 1; // По умолчанию тянем до самого последнего (текущего) бара
|
| | |
|
| | | // Бежим по рынку от момента возникновения CHoCH до текущего момента
|
| | | for(int k = chochBar; k < rates_total; k++)
|
| | | {
|
| | | // Проверяем, коснулась ли/пересекла ли свеча уровень левого плеча
|
| | | if(low[k] <= level && high[k] >= level)
|
| | | {
|
| | | touchCount++; // Касание зафиксировано
|
| | |
|
| | | if(touchCount >= 3) // Если это ТРЕТЬЕ касание
|
| | | {
|
| | | endBar = k; // Обрезаем линию на этом баре
|
| | | break; // Выходим из цикла, дальше линию не рисуем
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // Если уровень QML простоял до текущего момента и не получил 3-х касаний — рисуем его до текущего бара
|
| | | string base = OBJ_PREFIX + "qml" + (string)chochBar;
|
2026-10-19 17:51:23 +03:00 | | | string ln = base + "_ln";
|
2026-10-19 18:01:31 +03:00 | | |
|
| | | ObjectCreate(0, ln, OBJ_TREND, 0, time[shoulderBar], level, time[endBar], level);
|
2026-10-19 17:51:23 +03:00 | | | ObjectSetInteger(0, ln, OBJPROP_COLOR, InpQMLColor);
|
| | | ObjectSetInteger(0, ln, OBJPROP_STYLE, STYLE_DOT);
|
| | | ObjectSetInteger(0, ln, OBJPROP_WIDTH, 1);
|
2026-10-19 18:01:31 +03:00 | | | ObjectSetInteger(0, ln, OBJPROP_RAY_RIGHT, false); // ОТКЛЮЧИЛИ БЕСКОНЕЧНЫЙ ЛУЧ
|
2026-10-19 17:51:23 +03:00 | | | ObjectSetInteger(0, ln, OBJPROP_BACK, false);
|
| | | ObjectSetInteger(0, ln, OBJPROP_SELECTABLE, false);
|
| | |
|
2026-10-19 18:01:31 +03:00 | | | // Текстовую метку ставим в конце линии, но только если она еще не исчезла полностью
|
| | | if(touchCount < 3 || endBar == rates_total - 1)
|
| | | {
|
| | | string tx = base + "_tx";
|
| | | ObjectCreate(0, tx, OBJ_TEXT, 0, time[endBar], 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);
|
| | | }
|
2026-10-19 17:51:23 +03:00 | | | }
|
| | |
|
| | | //============================== 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;
|
| | |
|
| | | if(time[rates_total - 1] == g_lastBarTime && prev_calculated > 0)
|
| | | return rates_total;
|
| | | g_lastBarTime = time[rates_total - 1];
|
| | |
|
| | | 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];
|
| | | }
|
| | |
|
2026-10-19 18:01:31 +03:00 | | | //--- Pass 1b: Fair Value Gaps
|
2026-10-19 17:51:23 +03:00 | | | if(InpShowFVG)
|
| | | {
|
| | | for(int i = start + 1; i < rates_total - 1; i++)
|
| | | {
|
2026-10-19 18:01:31 +03:00 | | | if(low[i + 1] > high[i - 1])
|
| | | DrawBox(OBJ_PREFIX + "fvg" + (string)i, time[i - 1], high[i - 1], time[i + 1], low[i + 1], InpFVGColor, true);
|
2026-10-19 17:51:23 +03:00 | | | else
|
2026-10-19 18:01:31 +03:00 | | | if(high[i + 1] < low[i - 1])
|
| | | DrawBox(OBJ_PREFIX + "fvg" + (string)i, time[i - 1], low[i - 1], time[i + 1], high[i + 1], InpFVGColor, true);
|
2026-10-19 17:51:23 +03:00 | | | }
|
| | | }
|
| | |
|
| | | //--- Pass 2: BOS / CHoCH state machine
|
2026-10-19 18:01:31 +03:00 | | | int trend = 0;
|
| | | double refHigh = 0.0;
|
| | | double refLow = 0.0;
|
2026-10-19 17:51:23 +03:00 | | | int refHighBar = -1, refLowBar = -1;
|
| | | bool haveHigh = false, haveLow = false;
|
| | | double prevHigh = 0.0, prevLow = 0.0;
|
| | | int prevHighBar= -1, prevLowBar = -1;
|
| | |
|
| | | for(int i = start + InpSwingN; i < rates_total; i++)
|
| | | {
|
2026-10-19 18:01:31 +03:00 | | | int j = i - InpSwingN;
|
2026-10-19 17:51:23 +03:00 | | | 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];
|
| | |
|
2026-10-19 18:01:31 +03:00 | | | // Bullish break
|
2026-10-19 17:51:23 +03:00 | | | if(haveHigh && upPx > refHigh)
|
| | | {
|
2026-10-19 18:01:31 +03:00 | | | bool isBOS = (trend != -1);
|
| | | DrawBreak(time, refHighBar, i, refHigh, true, isBOS);
|
| | | if(InpShowOB)
|
| | | 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; }
|
| | |
|
| | | // ВЫЗОВ НОВОЙ ФУНКЦИИ ФИЛЬТРАЦИИ QML BUY
|
| | | if(InpShowQML && !isBOS && prevLowBar >= 0)
|
| | | DrawQMLWithTouchFilter(time, high, low, prevLowBar, i, prevLow, true, rates_total);
|
| | |
|
| | | trend = 1;
|
| | | haveHigh = false;
|
| | | }
|
| | | // Bearish break
|
| | | else
|
| | | if(haveLow && downPx < refLow)
|
| | | {
|
| | | bool isBOS = (trend != 1);
|
| | | DrawBreak(time, refLowBar, i, refLow, false, isBOS);
|
| | | if(InpShowOB)
|
2026-10-19 17:51:23 +03:00 | | | 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; }
|
2026-10-19 18:01:31 +03:00 | | | // ВЫЗОВ НОВОЙ ФУНКЦИИ ФИЛЬТРАЦИИ QML SELL
|
2026-10-19 17:51:23 +03:00 | | | if(InpShowQML && !isBOS && prevHighBar >= 0)
|
2026-10-19 18:01:31 +03:00 | | | DrawQMLWithTouchFilter(time, high, low, prevHighBar, i, prevHigh, false, rates_total);
|
2026-10-19 17:51:23 +03:00 | | | trend = -1;
|
| | | haveLow = false;
|
| | | }
|
| | | }
|
| | | return rates_total;
|
| | | }
|