mirror of
https://github.com/A6-9V/MQL5-Google-Onedrive.git
synced 2026-04-11 15:49:15 +00:00
This optimization improves the execution efficiency of the `SMC_TrendBreakout_MTF` indicator by: - Replacing the manual buffer clearing loop in `OnCalculate()` with the native `ArrayFill()` function. - Replacing the `CopyTime()` call in `GetMTFDir()` with the lighter `iTime()` function for MTF bar timestamp checks. - Refactoring input-based logic (Donchian lookback and signal bar index) into global cached variables initialized in `OnInit()`, avoiding redundant calculations on every new bar. These changes reduce CPU overhead and improve the indicator's responsiveness, especially when attached to multiple charts or using lower timeframe confirmations. Co-authored-by: Mouy-leng <199350297+Mouy-leng@users.noreply.github.com>
367 lines
13 KiB
MQL5
367 lines
13 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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;
|
|
}
|
|
|