//+------------------------------------------------------------------+ //| 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 //--- 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++; } //+------------------------------------------------------------------+