Test/Session Regime Tracker.mq5
365 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
//| SessionClassificationEngine.mq5 |
//| Intraday Session Classification Engine | roc need recalculation
//+------------------------------------------------------------------+ #288 Ranginging needs calculation, make room for undefined.
#property copyright "Session Classification EA"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
//--- Inputs: Session 1
input group "=== Session 1 ==="
input string InpS1Name = "Asia";
input ENUM_TIMEFRAMES InpS1TF = PERIOD_M15;
input int InpS1StartHour = 0;
input int InpS1StartMin = 0;
input int InpS1EndHour = 7;
input int InpS1EndMin = 0;
//--- Inputs: Session 2
input group "=== Session 2 ==="
input string InpS2Name = "London";
input ENUM_TIMEFRAMES InpS2TF = PERIOD_M15;
input int InpS2StartHour = 7;
input int InpS2StartMin = 0;
input int InpS2EndHour = 12;
input int InpS2EndMin = 0;
//--- Inputs: Session 3
input group "=== Session 3 ==="
input string InpS3Name = "NewYork";
input ENUM_TIMEFRAMES InpS3TF = PERIOD_M15;
input int InpS3StartHour = 12;
input int InpS3StartMin = 0;
input int InpS3EndHour = 17;
input int InpS3EndMin = 0;
//--- Inputs: Classification Thresholds
input group "=== Classification Thresholds ==="
double InpVolatileCompressionMax = 0.7; // Compression < X → potentially volatile
input double InpVolatileSpanMultiplier = 1.0; // Span > avg * X → volatile
double InpTrendCompressionMin = 0.65; // Compression > X → trending
double InpTrendSlopeMin = 0.00005; // |slope| > X → trending
input int InpAvgSpanLookbackDays = 20; // Days for historical avg span
//--- Inputs: Visuals
input group "=== Visuals ==="
input color InpColorTrending = clrDodgerBlue;
input color InpColorRanging = clrGoldenrod;
input color InpColorVolatile = clrCrimson;
input int InpFillAlpha = 40;
input int InpFontSize = 9;
input string InpFontName = "Arial Black";
//--- Structure
struct SessionDef
{
string name;
ENUM_TIMEFRAMES tf;
int startHour;
int startMin;
int endHour;
int endMin;
};
struct SessionStats
{
double highestHigh;
double lowestLow;
double bodyHigh; // highest of max(open,close)
double bodyLow; // lowest of min(open,close)
double sumBodies;
double slope;
double roc; // rate of change (close-end vs close-start)
double span;
double compression;
string classification;
};
//--- Globals
SessionDef g_sessions[3];
datetime g_lastProcessedDay = 0;
int g_avgSpanSamples = 0;
double g_avgSpanSum = 0.0;
double g_avgSpan = 0.0;
int g_rectCounter = 0;
//+------------------------------------------------------------------+
int OnInit()
{
g_sessions[0].name = InpS1Name;
g_sessions[0].tf = InpS1TF;
g_sessions[0].startHour = InpS1StartHour;
g_sessions[0].startMin = InpS1StartMin;
g_sessions[0].endHour = InpS1EndHour;
g_sessions[0].endMin = InpS1EndMin;
g_sessions[1].name = InpS2Name;
g_sessions[1].tf = InpS2TF;
g_sessions[1].startHour = InpS2StartHour;
g_sessions[1].startMin = InpS2StartMin;
g_sessions[1].endHour = InpS2EndHour;
g_sessions[1].endMin = InpS2EndMin;
g_sessions[2].name = InpS3Name;
g_sessions[2].tf = InpS3TF;
g_sessions[2].startHour = InpS3StartHour;
g_sessions[2].startMin = InpS3StartMin;
g_sessions[2].endHour = InpS3EndHour;
g_sessions[2].endMin = InpS3EndMin;
Print("Session Classification EA initialized.");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
ObjectsDeleteAll(0, "SCE_");
}
//+------------------------------------------------------------------+
void OnTick()
{
datetime now = TimeCurrent();
MqlDateTime dt;
TimeToStruct(now, dt);
// Build today's midnight
MqlDateTime mid = dt;
mid.hour = 0; mid.min = 0; mid.sec = 0;
datetime todayMidnight = StructToTime(mid);
// New day detection
if(todayMidnight != g_lastProcessedDay)
{
g_lastProcessedDay = todayMidnight;
ProcessNewDay(todayMidnight);
}
}
//+------------------------------------------------------------------+
//| Handle a new day: classify yesterday's sessions |
//+------------------------------------------------------------------+
void ProcessNewDay(datetime todayMidnight)
{
// We analyze the PREVIOUS day's completed sessions
datetime yesterdayMidnight = todayMidnight - 86400;
// Clear previous drawings
ObjectsDeleteAll(0, "SCE_");
g_rectCounter = 0;
double daySpanTotal = 0.0;
for(int i = 0; i < 3; i++)
{
datetime sStart = yesterdayMidnight + g_sessions[i].startHour * 3600
+ g_sessions[i].startMin * 60;
datetime sEnd = yesterdayMidnight + g_sessions[i].endHour * 3600
+ g_sessions[i].endMin * 60;
SessionStats st;
if(!ComputeSession(g_sessions[i].tf, sStart, sEnd, st))
{
PrintFormat("Session %s: no data.", g_sessions[i].name);
continue;
}
// Average span tracking (all session spans across days)
g_avgSpanSum += st.span;
g_avgSpanSamples++;
if(g_avgSpanSamples > 0)
g_avgSpan = g_avgSpanSum / g_avgSpanSamples;
// Classify
st.classification = ClassifySession(st, g_avgSpan);
// Draw
DrawSession(sStart, sEnd, st, i);
daySpanTotal += st.span;
printf("avgSpanSum: %f, avgSpanSamples: %i, avgSpan: %f, totalSpanDays: %f", g_avgSpanSum, g_avgSpanSamples, g_avgSpan, daySpanTotal);
PrintFormat("[%s] %s | H=%.5f L=%.5f BodyH=%.5f BodyL=%.5f Slope=%.5f ROC=%.5f Comp=%.3f Span=%.5f Class=%s",
g_sessions[i].name,
TimeToString(sStart, TIME_DATE|TIME_MINUTES),
st.highestHigh, st.lowestLow, st.bodyHigh, st.bodyLow,
st.slope, st.roc, st.compression, st.span, st.classification);
}
}
//+------------------------------------------------------------------+
//| Compute session stats from bars |
//+------------------------------------------------------------------+
bool ComputeSession(ENUM_TIMEFRAMES tf, datetime sStart, datetime sEnd, SessionStats &st)
{
st.highestHigh = -DBL_MAX;
st.lowestLow = DBL_MAX;
st.bodyHigh = -DBL_MAX;
st.bodyLow = DBL_MAX;
st.sumBodies = 0.0;
st.slope = 0.0;
st.roc = 0.0;
st.span = 0.0;
st.compression = 0.0;
st.classification = "RANGING";
MqlRates rates[];
int copied = CopyRates(_Symbol, tf, sStart, sEnd, rates);
if(copied <= 1)
return false;
int n = copied;
double closes[];
ArrayResize(closes, n);
for(int i = 0; i < n; i++)
{
if(rates[i].high > st.highestHigh) st.highestHigh = rates[i].high;
if(rates[i].low < st.lowestLow) st.lowestLow = rates[i].low;
printf("rangeHigh: %f, rangeLow: %f", st.highestHigh, st.lowestLow); //corrrect
double bHi = MathMax(rates[i].open, rates[i].close);
double bLo = MathMin(rates[i].open, rates[i].close);
if(bHi > st.bodyHigh) st.bodyHigh = bHi;
if(bLo < st.bodyLow) st.bodyLow = bLo;
printf("bodyHigh: %f, bodyLow: %f", st.bodyHigh, st.bodyLow); //corrrect
st.sumBodies = st.bodyHigh - st.bodyLow;
printf("st.bodyHeight: %f", st.sumBodies); //corrrect
closes[i] = rates[i].close;
}
st.span = st.highestHigh - st.lowestLow;
printf("st.rangeHeight: %f", st.span); //corrrect
if(st.span <= 0.0)
{
st.classification = "RANGING";
return true;
}
st.compression = st.sumBodies / st.span;
printf("compression: %f", st.compression);
// Linear regression slope over closes
st.slope = LinearRegressionSlope(closes, n);
// Rate of change: (last close - first close) / first close
if(closes[0] != 0.0)
st.roc = (closes[n-1] - closes[0]) / closes[0];
else
st.roc = 0.0;
return true;
}
//+------------------------------------------------------------------+
//| Standard linear regression slope |
//+------------------------------------------------------------------+
double LinearRegressionSlope(const double &y[], int n)
{
if(n < 2) return 0.0;
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for(int i = 0; i < n; i++)
{
double x = (double)i;
sumX += x;
sumY += y[i];
sumXY += x * y[i];
sumX2 += x * x;
}
double denom = n * sumX2 - sumX * sumX;
if(MathAbs(denom) < 1e-12) return 0.0;
double slope = (n * sumXY - sumX * sumY) / denom;
printf("slope: %f", slope);
return slope;
}
//+------------------------------------------------------------------+
//| Classification matrix per spec |
//+------------------------------------------------------------------+
string ClassifySession(const SessionStats &st, double avgSpan)
{
// Edge case: no movement
if(st.span == 0.0)
return "RANGING";
// Condition A: VOLATILE
if(st.compression < InpVolatileCompressionMax &&
avgSpan > 0.0 &&
st.span > avgSpan * InpVolatileSpanMultiplier)
{
return "VOLATILE";
}
// Condition B: TRENDING
if(st.compression > InpTrendCompressionMin &&
MathAbs(st.slope) > InpTrendSlopeMin)
{
return "TRENDING";
}
// Condition C: RANGING (default)
return "RANGING";
}
//+------------------------------------------------------------------+
//| Draw rectangle + text label |
//+------------------------------------------------------------------+
void DrawSession(datetime sStart, datetime sEnd, const SessionStats &st, int idx)
{
color baseCol = InpColorRanging;
if(st.classification == "TRENDING") baseCol = InpColorTrending;
else if(st.classification == "VOLATILE") baseCol = InpColorVolatile;
string rname = StringFormat("SCE_RECT_%d_%d", idx, (int)sStart);
string tname = StringFormat("SCE_TEXT_%d_%d", idx, (int)sStart);
datetime t1 = sStart;
datetime t2 = sEnd;
double p1 = st.lowestLow;
double p2 = st.highestHigh;
if(!ObjectCreate(0, rname, OBJ_RECTANGLE, 0, t1, p1, t2, p2))
{
PrintFormat("ObjectCreate rectangle failed: %d", GetLastError());
return;
}
ObjectSetInteger(0, rname, OBJPROP_COLOR, baseCol);
ObjectSetInteger(0, rname, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, rname, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, rname, OBJPROP_FILL, true);
ObjectSetInteger(0, rname, OBJPROP_BACK, true);
ObjectSetInteger(0, rname, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, rname, OBJPROP_HIDDEN, true);
// Text label just below rectangle
double labelPrice = p1 - (st.span * 0.05);
if(st.span == 0.0)
labelPrice = p1 - _Point * 50;
string labelText = StringFormat("%s: %s", g_sessions[idx].name, st.classification);
if(!ObjectCreate(0, tname, OBJ_TEXT, 0, t1, labelPrice))
{
PrintFormat("ObjectCreate text failed: %d", GetLastError());
return;
}
ObjectSetString(0, tname, OBJPROP_TEXT, labelText);
ObjectSetString(0, tname, OBJPROP_FONT, InpFontName);
ObjectSetInteger(0, tname, OBJPROP_FONTSIZE, InpFontSize);
ObjectSetInteger(0, tname, OBJPROP_COLOR, baseCol);
ObjectSetInteger(0, tname, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
ObjectSetInteger(0, tname, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, tname, OBJPROP_HIDDEN, true);
g_rectCounter++;
}
//+------------------------------------------------------------------+