forked from steverosenstock/SmartMA
366 lines
27 KiB
MQL5
366 lines
27 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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<rates_total; i++)
|
|
{
|
|
//--- 1. Detect context
|
|
session = DetectSession(time[i]);
|
|
isNews = (Use_News) ? IsEconomicNewsEvent(time[i]) : false;
|
|
highVol = (Use_Volume && tick_volume[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<count; i++)
|
|
{
|
|
if(news[i].impact_type >= 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(i<period) return 0;
|
|
double sum = 0;
|
|
for(int j=0; j<period; j++)
|
|
{
|
|
double tr1 = high[i-j] - low[i-j];
|
|
double tr2 = MathAbs(high[i-j] - close[i-j-1]);
|
|
double tr3 = MathAbs(low[i-j] - close[i-j-1]);
|
|
double tr = MathMax(tr1, MathMax(tr2,tr3));
|
|
sum += tr;
|
|
}
|
|
return sum/period;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Adaptive EMA (volume-weighted, context-sensitive) |
|
|
//+------------------------------------------------------------------+
|
|
double AdaptiveEMA(int i, int period, const double &close[], const long &volume[], bool useVolume, double &buffer[])
|
|
{
|
|
if(i==0) return close[i];
|
|
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];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 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
|
|
|
|
*/
|
|
|