2026-09-28 15:56:07 +01:00 | | | //+------------------------------------------------------------------+
|
2026-09-28 22:43:44 +01:00 | | | //| SessionClassificationEngine.mq5 |
|
| | | //| Intraday Session Classification Engine | roc need recalculation
|
| | | //+------------------------------------------------------------------+ #288 Ranginging needs calculation, make room for undefined.
|
2026-09-28 15:56:07 +01:00 | | | #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
|
2026-09-28 22:43:44 +01:00 | | | input double InpVolatileSpanMultiplier = 1.0; // Span > avg * X → volatile
|
| | | double InpTrendCompressionMin = 0.65; // Compression > X → trending
|
2026-09-28 15:56:07 +01:00 | | | 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);
|
| | |
|
2026-09-28 22:43:44 +01:00 | | | PrintFormat("[%s] %s | H=%.5f L=%.5f BodyH=%.5f BodyL=%.5f Slope=%.5f ROC=%.5f Comp=%.3f Span=%.5f Class=%s",
|
2026-09-28 15:56:07 +01:00 | | | g_sessions[i].name,
|
| | | TimeToString(sStart, TIME_DATE|TIME_MINUTES),
|
| | | st.highestHigh, st.lowestLow, st.bodyHigh, st.bodyLow,
|
2026-09-28 22:43:44 +01:00 | | | st.slope, st.roc, st.compression, st.span, st.classification);
|
2026-09-28 15:56:07 +01:00 | | | }
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 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++;
|
| | | }
|
2026-09-28 09:42:03 +00:00 | | | //+------------------------------------------------------------------+
|