//+------------------------------------------------------------------+ //| SmartMA.mq5 | //| Copyright 2025, WebZest, LLC | //| https://www.webzest.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, WebZest, LLC" #property link "https://www.webzest.com" #property version "1.00" #property indicator_chart_window //+------------------------------------------------------------------+ //| SmartMA MT5 - The Self-Explaining Context MA | //| Author: Steve Rosenstock | //| Repository: https://forge.mql5.io/steverosenstock/SmartMA | //| Git: https://forge.mql5.io/steverosenstock/SmartMA.git | //| Version: 1.0 (2025-07-20) | //+------------------------------------------------------------------+ #property copyright "Steve Rosenstock" #property link "https://forge.mql5.io/steverosenstock/SmartMA" #property version "1.0" #property indicator_chart_window #property indicator_buffers 4 #property indicator_plots 3 #property indicator_label1 "SmartMA" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 #property indicator_label2 "GhostMA" #property indicator_type2 DRAW_LINE #property indicator_color2 clrGray #property indicator_width2 1 #property indicator_label3 "RiskBand" #property indicator_type3 DRAW_FILLING #property indicator_color3 clrLightYellow, clrLightYellow #property indicator_width3 2 //--- INPUT PARAMETERS input int MA_Period = 20; // Base MA period (auto-adapting) input int ATR_Period = 14; // ATR for risk band input bool Use_Volume = true; // Volume-weighted MA input bool Use_News = true; // Integrate MT5 news events input bool Show_Risk_Band = true; // Show SmartATR band (+/-) input bool Show_Ghost = true; // Show "Ghost" MA (w/o news) input bool Enable_Alerts = true; // Alert on regime/news change //--- BUFFERS double SmartMABuffer[]; // Main SmartMA output double GhostMABuffer[]; // "Ghost" MA (no news) double RiskBandTop[]; // Top of ATR band double RiskBandBottom[]; // Bottom of ATR band //--- GLOBALS int lastRegime = -1; string lastSession = ""; datetime lastExplained = 0; double lastATR = 0; datetime lastNewsAlert = 0; #define CALENDAR_IMPORTANCE_LOW 1 #define CALENDAR_IMPORTANCE_MEDIUM 2 #define CALENDAR_IMPORTANCE_HIGH 3 //+------------------------------------------------------------------+ //| Initialization | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, SmartMABuffer, INDICATOR_DATA); SetIndexBuffer(1, GhostMABuffer, INDICATOR_DATA); SetIndexBuffer(2, RiskBandTop, INDICATOR_DATA); SetIndexBuffer(3, RiskBandBottom, INDICATOR_DATA); PlotIndexSetString(0, PLOT_LABEL, "SmartMA"); PlotIndexSetString(1, PLOT_LABEL, "GhostMA"); PlotIndexSetString(2, PLOT_LABEL, "RiskBand"); IndicatorSetString(INDICATOR_SHORTNAME, "SmartMA MT5"); IndicatorSetInteger(INDICATOR_DIGITS, 5); ArrayInitialize(SmartMABuffer, 0.0); ArrayInitialize(GhostMABuffer, 0.0); ArrayInitialize(RiskBandTop, 0.0); ArrayInitialize(RiskBandBottom, 0.0); Print("SmartMA MT5 initialized!"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Main Calculation | //+------------------------------------------------------------------+ 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 < MathMax(MA_Period, ATR_Period) + 2) return(0); int currentRegime = -1; // 0=range, 1=trend, 2=neutral string regimeText = ""; string session = ""; int dynamic_period; double atr, vAvg; bool isNews, highVol; for(int i=0; i GetAvgVolume(tick_volume, i, 20) * 1.5); atr = CalculateATR(i, ATR_Period, high, low, close); vAvg = GetAvgVolume(tick_volume, i, MA_Period); // Regime detection (Trend/Range) double rollingATR = CalculateATR(i, 30, high, low, close); if(atr > rollingATR*1.3) { regimeText="Trend"; currentRegime=1; } else if(atr < rollingATR*0.7){ regimeText="Range"; currentRegime=0; } else { regimeText="Neutral"; currentRegime=2; } //--- 2. Dynamic MA period dynamic_period = MA_Period; if(currentRegime==1) dynamic_period = MathMax(7, MA_Period-6); if(currentRegime==0) dynamic_period = MathMin(50, MA_Period+8); if(session=="London") dynamic_period = MathMax(7, dynamic_period-2); if(session=="Asia") dynamic_period = MathMin(50, dynamic_period+4); //--- 3. MA calculation (EMA, volume-weighted if enabled) if(isNews) { SmartMABuffer[i] = (i>0) ? SmartMABuffer[i-1] : close[i]; if(i>0) GhostMABuffer[i] = GhostMABuffer[i-1]; else GhostMABuffer[i] = close[i]; // Alert for news bar (once per bar) if(Enable_Alerts && time[i]!=lastNewsAlert) { Alert("SmartMA: News event detected (", TimeToString(time[i]), ") - MA frozen"); lastNewsAlert = time[i]; } } else { SmartMABuffer[i] = AdaptiveEMA(i, dynamic_period, close, tick_volume, Use_Volume, SmartMABuffer); GhostMABuffer[i] = AdaptiveEMA_Ghost(i, dynamic_period, close, tick_volume, Use_Volume, time, GhostMABuffer); } //--- 4. Risk Band (±ATR) if(Show_Risk_Band) { RiskBandTop[i] = SmartMABuffer[i] + atr; RiskBandBottom[i] = SmartMABuffer[i] - atr; } else { RiskBandTop[i] = 0.0; RiskBandBottom[i] = 0.0; } //--- 5. Self-Explaining Comment (latest bar only) if(i == rates_total-1 && (time[i] != lastExplained)) { string info = "SmartMA: " + regimeText + ", " + session; info += ", Period=" + IntegerToString(dynamic_period); if(isNews) info += ", NEWS EVENT (MA frozen)"; else if(highVol) info += ", High Volume"; Comment(info); // Regime/Session change Alert if(Enable_Alerts && (currentRegime!=lastRegime || session!=lastSession)) { Alert("SmartMA: Context changed to " + regimeText + " / " + session + " ("+TimeToString(time[i])+")"); lastRegime = currentRegime; lastSession = session; } lastExplained = time[i]; } } return(rates_total); } //+------------------------------------------------------------------+ //| Helper: Session detection | //+------------------------------------------------------------------+ int GetEasternOffset() { MqlDateTime dt; TimeToStruct(TimeGMT(), dt); int year = dt.year; // Find second Sunday in March datetime march1 = StringToTime(IntegerToString(year) + ".03.01 00:00"); MqlDateTime marchDT; TimeToStruct(march1, marchDT); int marchDOW = marchDT.day_of_week; int secondSundayMarch = 14 - marchDOW; if(secondSundayMarch <= 7) secondSundayMarch += 7; datetime dstStart = StringToTime(IntegerToString(year) + ".03." + IntegerToString(secondSundayMarch) + " 02:00"); // Find first Sunday in November datetime nov1 = StringToTime(IntegerToString(year) + ".11.01 00:00"); MqlDateTime novDT; TimeToStruct(nov1, novDT); int novDOW = novDT.day_of_week; int firstSundayNov = (novDOW == 0) ? 1 : 8 - novDOW; datetime dstEnd = StringToTime(IntegerToString(year) + ".11." + IntegerToString(firstSundayNov) + " 02:00"); datetime now = TimeGMT(); // Eastern Daylight Time: UTC−4 // Eastern Standard Time: UTC−5 return (now >= dstStart && now < dstEnd) ? -4 : -5; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ datetime LocalTimeCurrent() { return TimeGMT() + GetEasternOffset() * 3600; } string DetectSession(datetime t) { MqlDateTime dt; TimeToStruct(LocalTimeCurrent(), dt); int hour = dt.hour; bool isTokyo = (hour >= 19 || hour < 4); // 7 PM – 4 AM ET bool isLondon = (hour >= 3 && hour < 12); // 3 AM – 12 PM ET bool isNewYork = (hour >= 8 && hour < 17); // 8 AM – 5 PM ET bool overlapTokyoLondon = (hour >= 3 && hour < 4); bool overlapLondonNY = (hour >= 8 && hour < 12); bool overlapAll = (hour == 8); // 8 AM ET is triple overlap if(overlapAll) return "GB-NY-JP"; if(overlapTokyoLondon) return "JP-GB"; if(overlapLondonNY) return "GB-NY"; if(isTokyo) return "Asia"; if(isLondon) return "London"; if(isNewYork) return "NewYork"; return "🌙 Off Hours"; } //+------------------------------------------------------------------+ //| Helper: News event detection (MT5 Economic Calendar) | //+------------------------------------------------------------------+ bool IsEconomicNewsEvent(datetime bartime) { MqlCalendarValue news[]; datetime fromTime = bartime - 60; datetime toTime = bartime + 60; int count = CalendarValueHistory(news, fromTime, toTime); if(count>0) { for(int i=0; i= CALENDAR_IMPORTANCE_MEDIUM) return(true); } } return(false); } //+------------------------------------------------------------------+ //| Helper: Rolling average volume | //+------------------------------------------------------------------+ double GetAvgVolume(const long &volume[], int index, int window) { long sum = 0; int n = 0; for(int i=index; i>index-window && i>=0; i--) { sum += volume[i]; n++; } return (n>0) ? (double)sum/n : 0; } //+------------------------------------------------------------------+ //| Helper: ATR calculation | //+------------------------------------------------------------------+ double CalculateATR(int i, int period, const double &high[], const double &low[], const double &close[]) { if(i0) vWeight = (double)volume[i]/GetAvgVolume(volume, i, period); return alpha*vWeight*close[i] + (1-alpha*vWeight)*buffer[i-1]; } //+------------------------------------------------------------------+ //| Adaptive EMA for Ghost MA (skips news bars) | //+------------------------------------------------------------------+ double AdaptiveEMA_Ghost(int i, int period, const double &close[], const long &volume[], bool useVolume, const datetime &time[], double &buffer[]) { if(i==0) return close[i]; bool isNews = IsEconomicNewsEvent(time[i]); if(isNews) return buffer[i-1]; double alpha = 2.0/(period+1); double vWeight = 1.0; if(useVolume && volume[i]>0) vWeight = (double)volume[i]/GetAvgVolume(volume, i, period); return alpha*vWeight*close[i] + (1-alpha*vWeight)*buffer[i-1]; } //+------------------------------------------------------------------+ //| Developer Notes & Hooks | //| - Community Profiles, Color Themes, Multi-Alerts: Add here! | //+------------------------------------------------------------------+ /* README FOR DEVELOPERS SmartMA MT5 is a fully open, context-adaptive, self-explaining Moving Average for MetaTrader 5. - Context-detection: Sessions, News, Regime (trend/range/neutral) - Adaptive period and volume weighting - "Ghost" MA for comparison (w/o news bars) - ATR-based risk band - Self-explaining chart overlay and alerts Ready for profiles, color themes, regime modules, and more. Fork, improve, and help build the next-generation Moving Average! Repository: https://forge.mql5.io/steverosenstock/SmartMA Git: https://forge.mql5.io/steverosenstock/SmartMA.git MIT License Author: Steve Rosenstock */