295 行
11 KiB
MQL5
295 行
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
| |||
| |||
*/
| |||
|