0
0
Fourche 1
miroir de https://github.com/A6-9V/MQL5-Google-Onedrive.git synchronisé 2026-04-11 13:10:57 +00:00
MQL5-Google-Onedrive/mt5/MQL5/Indicators/SMC_TrendBreakout_MTF.mq5

367 lignes
13 Kio
MQL5
Brut Lien permanent Vue normale Historique

//+------------------------------------------------------------------+
//| SMC_TrendBreakout_MTF.mq5 |
//| Visual indicator: BOS/CHoCH + Donchian breakout + MTF filter |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2
// --- plot 0: buy arrows
#property indicator_label1 "Long"
#property indicator_type1 DRAW_ARROW
#property indicator_color1 clrLimeGreen
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
// --- plot 1: sell arrows
#property indicator_label2 "Short"
#property indicator_type2 DRAW_ARROW
#property indicator_color2 clrTomato
#property indicator_style2 STYLE_SOLID
#property indicator_width2 2
input group "SMC (structure)"
input bool UseSMC = true;
input bool UseCHoCH = true; // label opposite break as CHoCH
input group "Trend Breakout"
input bool UseDonchianBreakout = true;
input int DonchianLookback = 20; // bars (completed bars)
input group "Lower timeframe confirmation"
input bool RequireMTFConfirm = true;
input ENUM_TIMEFRAMES LowerTF = PERIOD_M5;
input int EMAFast = 20;
input int EMASlow = 50;
input group "Signals"
input bool FireOnClose = true; // use last closed bar (recommended)
input int ArrowCodeLong = 233; // wingdings: up arrow
input int ArrowCodeShort = 234; // wingdings: down arrow
input int ArrowOffsetPoints = 10; // vertical offset in points
input group "Drawing"
input bool DrawStructureLines = true;
input bool DrawBreakoutLines = false;
input int MaxObjects = 200; // prevent clutter
input group "Notifications"
input bool PopupAlerts = false;
input bool PushNotifications = false;
double gLongBuf[];
double gShortBuf[];
int gFractalsHandle = INVALID_HANDLE;
int gEmaFastHandle = INVALID_HANDLE;
int gEmaSlowHandle = INVALID_HANDLE;
// Tracks last trend direction on main TF: 1 bullish, -1 bearish, 0 unknown
int gTrendDir = 0;
string gObjPrefix;
int g_objCount = 0; // ⚡ Bolt: Cached object count for performance
datetime gLastBarTime = 0;
int gDonLookback = 0; // ⚡ Bolt: Cached Donchian lookback
int gSigBar = 0; // ⚡ Bolt: Cached signal bar index
static int ClampInt(const int v, const int lo, const int hi) { return (v<lo?lo:(v>hi?hi:v)); }
static void Notify(const string msg)
{
if(PopupAlerts) Alert(msg);
if(PushNotifications) SendNotification(msg);
}
static void SafeDeleteOldObjects()
{
// ⚡ Bolt: Performance optimization.
// Using a cached count and native ObjectsDeleteAll() is much faster than manual loops
// that iterate through all chart objects using ObjectName() and StringFind().
if(g_objCount <= MaxObjects) return;
ObjectsDeleteAll(0, gObjPrefix);
g_objCount = 0;
}
static void DrawHLine(const string name, const double price, const color c, const ENUM_LINE_STYLE st, const int w)
{
if(!DrawStructureLines && !DrawBreakoutLines) return;
if(ObjectFind(0, name) >= 0) return;
if(ObjectCreate(0, name, OBJ_HLINE, 0, 0, price))
{
g_objCount++; // ⚡ Bolt: Track created objects
}
ObjectSetInteger(0, name, OBJPROP_COLOR, c);
ObjectSetInteger(0, name, OBJPROP_STYLE, st);
ObjectSetInteger(0, name, OBJPROP_WIDTH, w);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
}
static void DrawText(const string name, const datetime t, const double price, const string txt, const color c)
{
if(ObjectFind(0, name) >= 0) return;
if(ObjectCreate(0, name, OBJ_TEXT, 0, t, price))
{
g_objCount++; // ⚡ Bolt: Track created objects
}
ObjectSetString(0, name, OBJPROP_TEXT, txt);
ObjectSetInteger(0, name, OBJPROP_COLOR, c);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
}
static double HighestHigh(const double &high[], const int start, const int count)
{
// ⚡ Bolt: Use native ArrayMaximum for better performance.
// MQL5 native functions are implemented in optimized C++ and are faster than manual loops.
int index = ArrayMaximum(high, start, count);
return (index != -1) ? high[index] : -DBL_MAX;
}
static double LowestLow(const double &low[], const int start, const int count)
{
// ⚡ Bolt: Use native ArrayMinimum for better performance.
// MQL5 native functions are implemented in optimized C++ and are faster than manual loops.
int index = ArrayMinimum(low, start, count);
return (index != -1) ? low[index] : DBL_MAX;
}
// --- Cached MTF direction (performance)
static datetime g_mtfDir_lastCheckTime = 0;
static int g_mtfDir_cachedValue = 0;
static int GetMTFDir()
{
if(!RequireMTFConfirm) return 0;
if(gEmaFastHandle == INVALID_HANDLE || gEmaSlowHandle == INVALID_HANDLE) return 0;
// PERF: Only check for new MTF direction on a new bar of the LowerTF.
// ⚡ Bolt: Use iTime for a lighter check than CopyTime.
datetime mtfTime = iTime(_Symbol, LowerTF, 0);
if(mtfTime == 0) return 0; // History not ready
if(mtfTime == g_mtfDir_lastCheckTime) return g_mtfDir_cachedValue;
g_mtfDir_lastCheckTime = mtfTime;
// ⚡ Bolt: Efficiently copy only the single required value.
double fast[1], slow[1];
if(CopyBuffer(gEmaFastHandle, 0, 1, 1, fast) != 1) { g_mtfDir_cachedValue = 0; return 0; }
if(CopyBuffer(gEmaSlowHandle, 0, 1, 1, slow) != 1) { g_mtfDir_cachedValue = 0; return 0; }
if(fast[0] > slow[0]) g_mtfDir_cachedValue = 1;
else if(fast[0] < slow[0]) g_mtfDir_cachedValue = -1;
else g_mtfDir_cachedValue = 0;
return g_mtfDir_cachedValue;
}
int OnInit()
{
SetIndexBuffer(0, gLongBuf, INDICATOR_DATA);
SetIndexBuffer(1, gShortBuf, INDICATOR_DATA);
ArraySetAsSeries(gLongBuf, true);
ArraySetAsSeries(gShortBuf, true);
PlotIndexSetInteger(0, PLOT_ARROW, ArrowCodeLong);
PlotIndexSetInteger(1, PLOT_ARROW, ArrowCodeShort);
gObjPrefix = StringFormat("SMC_TB_MTF_%I64u_", (long)ChartID());
// ⚡ Bolt: Initialize object count for performance optimization.
// One-time O(N) loop is acceptable in OnInit to avoid O(N) loops in OnCalculate.
g_objCount = 0;
int total = ObjectsTotal(0, 0, -1);
for(int i = 0; i < total; i++)
{
if(StringFind(ObjectName(0, i), gObjPrefix) == 0) g_objCount++;
}
// fractals on main TF
gFractalsHandle = iFractals(_Symbol, _Period);
if(gFractalsHandle == INVALID_HANDLE) return INIT_FAILED;
// EMA handles on lower TF (for confirmation)
gEmaFastHandle = iMA(_Symbol, LowerTF, EMAFast, 0, MODE_EMA, PRICE_CLOSE);
gEmaSlowHandle = iMA(_Symbol, LowerTF, EMASlow, 0, MODE_EMA, PRICE_CLOSE);
// ⚡ Bolt: Cache values to avoid redundant calculations in OnCalculate
gDonLookback = (DonchianLookback < 2 ? 2 : DonchianLookback);
gSigBar = (FireOnClose ? 1 : 0);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(gFractalsHandle != INVALID_HANDLE) IndicatorRelease(gFractalsHandle);
if(gEmaFastHandle != INVALID_HANDLE) IndicatorRelease(gEmaFastHandle);
if(gEmaSlowHandle != INVALID_HANDLE) IndicatorRelease(gEmaSlowHandle);
}
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 < 100) return 0;
// ⚡ Bolt: Early exit if not a new bar. This avoids redundant ArraySetAsSeries calls
// and buffer clearing loops on every price tick.
// We check prev_calculated > 0 to ensure a full recalculation is performed if requested by the terminal.
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == 0) return 0; // History not ready
if(prev_calculated > 0 && currentBarTime == gLastBarTime) return rates_total;
gLastBarTime = currentBarTime;
ArraySetAsSeries(time, true);
ArraySetAsSeries(open, true);
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
// ⚡ Bolt: Use ArrayInitialize for efficient bulk clearing on first run.
if(prev_calculated == 0)
{
ArrayInitialize(gLongBuf, EMPTY_VALUE);
ArrayInitialize(gShortBuf, EMPTY_VALUE);
}
else
{
// Clear only newly calculated area (usually just the current bar)
int start = rates_total - prev_calculated;
start = ClampInt(start, 0, rates_total - 1);
// ⚡ Bolt: Use ArrayFill for faster partial buffer clearing.
ArrayFill(gLongBuf, 0, start + 1, EMPTY_VALUE);
ArrayFill(gShortBuf, 0, start + 1, EMPTY_VALUE);
}
SafeDeleteOldObjects();
if(gSigBar >= rates_total - 1) return rates_total;
// Find most recent confirmed swing high/low (fractal appears 2 bars after formation)
double lastSwingHigh = 0.0; datetime lastSwingHighT = 0;
double lastSwingLow = 0.0; datetime lastSwingLowT = 0;
if(UseSMC)
{
// ⚡ Bolt: Lazy load fractal data only if SMC features are enabled.
int need = MathMin(600, rates_total);
double upFr[600], dnFr[600];
// Note: For static arrays, we skip ArraySetAsSeries and rely on CopyBuffer's default
// filling order (index 0 is the start_pos bar) to improve efficiency.
if(CopyBuffer(gFractalsHandle, 0, 0, need, upFr) > 0 &&
CopyBuffer(gFractalsHandle, 1, 0, need, dnFr) > 0)
{
for(int i = gSigBar + 2; i < need; i++)
{
// ⚡ Bolt: Fix bug where EMPTY_VALUE (DBL_MAX) was incorrectly identified as a valid fractal.
// This avoids processing incorrect data and ensures correct signal logic.
if(lastSwingHighT == 0 && upFr[i] != 0.0 && upFr[i] != EMPTY_VALUE) { lastSwingHigh = upFr[i]; lastSwingHighT = time[i]; }
if(lastSwingLowT == 0 && dnFr[i] != 0.0 && dnFr[i] != EMPTY_VALUE) { lastSwingLow = dnFr[i]; lastSwingLowT = time[i]; }
if(lastSwingHighT != 0 && lastSwingLowT != 0) break;
}
}
}
// --- Determine Donchian breakout bounds (exclude current forming bar and signal bar)
double donHigh = 0;
double donLow = 0;
if(UseDonchianBreakout)
{
// ⚡ Bolt: Lazy calculate Donchian bounds only if breakout features are enabled.
int donStart = gSigBar + 1;
int donCount = gDonLookback;
if(donStart + donCount < rates_total)
{
donHigh = HighestHigh(high, donStart, donCount);
donLow = LowestLow(low, donStart, donCount);
}
else return rates_total;
}
// --- Lower TF confirmation
int mtfDir = GetMTFDir(); // 1 up, -1 down, 0 neutral/unknown
bool mtfOkLong = (!RequireMTFConfirm) || (mtfDir == 1);
bool mtfOkShort = (!RequireMTFConfirm) || (mtfDir == -1);
// --- Signals
bool smcLong=false, smcShort=false, donLong=false, donShort=false;
if(UseSMC)
{
if(lastSwingHighT!=0 && close[gSigBar] > lastSwingHigh) smcLong = true;
if(lastSwingLowT!=0 && close[gSigBar] < lastSwingLow) smcShort = true;
}
if(UseDonchianBreakout)
{
if(close[gSigBar] > donHigh) donLong = true;
if(close[gSigBar] < donLow) donShort = true;
}
bool finalLong = (smcLong || donLong) && mtfOkLong;
bool finalShort = (smcShort || donShort) && mtfOkShort;
// --- Plot & draw
double pnt = _Point;
if(finalLong)
{
gLongBuf[gSigBar] = low[gSigBar] - ArrowOffsetPoints * pnt;
color c = clrLimeGreen;
if(smcLong && lastSwingHighT!=0 && DrawStructureLines)
{
int breakDir = 1;
bool choch = (UseCHoCH && gTrendDir!=0 && breakDir != gTrendDir);
string kind = (choch ? "CHoCH↑" : "BOS↑");
string n1 = gObjPrefix + StringFormat("SMC_%s_%I64d", kind, (long)time[gSigBar]);
DrawHLine(n1+"_L", lastSwingHigh, c, STYLE_DOT, 1);
DrawText(n1+"_T", time[gSigBar], lastSwingHigh, kind, c);
gTrendDir = breakDir;
}
if(donLong && DrawBreakoutLines)
{
string n2 = gObjPrefix + StringFormat("DON_H_%I64d", (long)time[gSigBar]);
DrawHLine(n2, donHigh, clrDeepSkyBlue, STYLE_DASH, 1);
}
Notify(StringFormat("%s LONG | TF=%s | MTF=%s", _Symbol, EnumToString(_Period), EnumToString(LowerTF)));
}
if(finalShort)
{
gShortBuf[gSigBar] = high[gSigBar] + ArrowOffsetPoints * pnt;
color c = clrTomato;
if(smcShort && lastSwingLowT!=0 && DrawStructureLines)
{
int breakDir = -1;
bool choch = (UseCHoCH && gTrendDir!=0 && breakDir != gTrendDir);
string kind = (choch ? "CHoCH↓" : "BOS↓");
string n1 = gObjPrefix + StringFormat("SMC_%s_%I64d", kind, (long)time[gSigBar]);
DrawHLine(n1+"_L", lastSwingLow, c, STYLE_DOT, 1);
DrawText(n1+"_T", time[gSigBar], lastSwingLow, kind, c);
gTrendDir = breakDir;
}
if(donShort && DrawBreakoutLines)
{
string n2 = gObjPrefix + StringFormat("DON_L_%I64d", (long)time[gSigBar]);
DrawHLine(n2, donLow, clrDeepSkyBlue, STYLE_DASH, 1);
}
Notify(StringFormat("%s SHORT | TF=%s | MTF=%s", _Symbol, EnumToString(_Period), EnumToString(LowerTF)));
}
return rates_total;
}