SmartMA/SmartMA.mq5

295 lines
11 KiB
MQL5

//+------------------------------------------------------------------+
//| 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;
//+------------------------------------------------------------------+
//| 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 |
//+------------------------------------------------------------------+
string DetectSession(datetime t)
{
int hour = TimeHour(t);
if(hour>=0 && hour<7) return("Asia");
if(hour>=7 && hour<15) return("London");
if(hour>=15 && hour<=23)return("NewYork");
return("Other");
}
//+------------------------------------------------------------------+
//| 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>=CALENDAR_IMPORTANCE_MEDIUM)
return(true);
}
}
return(false);
}
//+------------------------------------------------------------------+
//| Helper: Rolling average volume |
//+------------------------------------------------------------------+
double GetAvgVolume(const long &volume[], int index, int window)
{
double sum = 0; int n = 0;
for(int i=index; i>index-window && i>=0; i--)
{ sum += volume[i]; n++; }
return (n>0) ? 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
*/