diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index af8691e..a1e1179 100644 --- a/NANDR_ORB_OB_EA.mq5 +++ b/NANDR_ORB_OB_EA.mq5 @@ -450,23 +450,22 @@ void UpdateORBSessions() for(int s = 0; s < g_SessionCount; s++) { - SSession *sess = &g_Sessions[s]; - if(!sess.enabled) continue; + if(!g_Sessions[s].enabled) continue; // Session open bar starts accumulation - if(IsSessionOpenBar(*sess, barTime)) + if(IsSessionOpenBar(g_Sessions[s], barTime)) { // Reset session state for new day's ORB - sess.orbHigh = barHigh; - sess.orbLow = barLow; - sess.orbBarCount = 1; - sess.orbStartTime= barTime; - sess.orbComplete = (g_OrbBarsNeeded == 1); // M15: complete immediately - sess.breakoutDir = 0; - sess.inBreakout = false; - sess.inRetest = false; - sess.breakoutBarsAgo = 0; - sess.tradesThisSession = 0; + g_Sessions[s].orbHigh = barHigh; + g_Sessions[s].orbLow = barLow; + g_Sessions[s].orbBarCount = 1; + g_Sessions[s].orbStartTime = barTime; + g_Sessions[s].orbComplete = (g_OrbBarsNeeded == 1); + g_Sessions[s].breakoutDir = 0; + g_Sessions[s].inBreakout = false; + g_Sessions[s].inRetest = false; + g_Sessions[s].breakoutBarsAgo = 0; + g_Sessions[s].tradesThisSession= 0; if(InpShowORBLines) DrawORBLines(s); @@ -474,23 +473,25 @@ void UpdateORBSessions() } // Still accumulating (M5 or M1) - if(!sess.orbComplete && sess.orbBarCount > 0 && IsWithinOrbWindow(*sess, barTime)) + if(!g_Sessions[s].orbComplete && g_Sessions[s].orbBarCount > 0 + && IsWithinOrbWindow(g_Sessions[s], barTime)) { - sess.orbHigh = MathMax(sess.orbHigh, barHigh); - sess.orbLow = MathMin(sess.orbLow, barLow); - sess.orbBarCount++; - if(sess.orbBarCount >= g_OrbBarsNeeded) + g_Sessions[s].orbHigh = MathMax(g_Sessions[s].orbHigh, barHigh); + g_Sessions[s].orbLow = MathMin(g_Sessions[s].orbLow, barLow); + g_Sessions[s].orbBarCount++; + if(g_Sessions[s].orbBarCount >= g_OrbBarsNeeded) { - sess.orbComplete = true; - PrintFormat("NANDR EA: [%s] ORB complete. High=%.2f Low=%.2f", sess.name, sess.orbHigh, sess.orbLow); + g_Sessions[s].orbComplete = true; + PrintFormat("NANDR EA: [%s] ORB complete. High=%.2f Low=%.2f", + g_Sessions[s].name, g_Sessions[s].orbHigh, g_Sessions[s].orbLow); } if(InpShowORBLines) DrawORBLines(s); } // Track breakout bar age - if(sess.inBreakout) - sess.breakoutBarsAgo++; + if(g_Sessions[s].inBreakout) + g_Sessions[s].breakoutBarsAgo++; } } @@ -553,31 +554,32 @@ void DetectBreakouts() { for(int s = 0; s < g_SessionCount; s++) { - SSession *sess = &g_Sessions[s]; - if(!sess.enabled || !sess.orbComplete) continue; - if(sess.orbHigh <= 0 || sess.orbLow >= DBL_MAX) continue; - if(sess.inBreakout || sess.inRetest) continue; // already tracking one + if(!g_Sessions[s].enabled || !g_Sessions[s].orbComplete) continue; + if(g_Sessions[s].orbHigh <= 0 || g_Sessions[s].orbLow >= DBL_MAX) continue; + if(g_Sessions[s].inBreakout || g_Sessions[s].inRetest) continue; bool bullBO = InpUseStrictFilter - ? CheckStrictBreakout( 1, sess.orbHigh) - : CheckSimpleBreakout( 1, sess.orbHigh); + ? CheckStrictBreakout( 1, g_Sessions[s].orbHigh) + : CheckSimpleBreakout( 1, g_Sessions[s].orbHigh); bool bearBO = InpUseStrictFilter - ? CheckStrictBreakout(-1, sess.orbLow) - : CheckSimpleBreakout(-1, sess.orbLow); + ? CheckStrictBreakout(-1, g_Sessions[s].orbLow) + : CheckSimpleBreakout(-1, g_Sessions[s].orbLow); if(bullBO) { - sess.breakoutDir = 1; - sess.inBreakout = true; - sess.breakoutBarsAgo = 0; - PrintFormat("NANDR EA: [%s] Bullish ORB breakout at %.2f", sess.name, sess.orbHigh); + g_Sessions[s].breakoutDir = 1; + g_Sessions[s].inBreakout = true; + g_Sessions[s].breakoutBarsAgo= 0; + PrintFormat("NANDR EA: [%s] Bullish ORB breakout at %.2f", + g_Sessions[s].name, g_Sessions[s].orbHigh); } else if(bearBO) { - sess.breakoutDir = -1; - sess.inBreakout = true; - sess.breakoutBarsAgo = 0; - PrintFormat("NANDR EA: [%s] Bearish ORB breakout at %.2f", sess.name, sess.orbLow); + g_Sessions[s].breakoutDir = -1; + g_Sessions[s].inBreakout = true; + g_Sessions[s].breakoutBarsAgo= 0; + PrintFormat("NANDR EA: [%s] Bearish ORB breakout at %.2f", + g_Sessions[s].name, g_Sessions[s].orbLow); } } } @@ -587,8 +589,7 @@ void DetectBreakouts() //+------------------------------------------------------------------+ bool DetectRetest(int sessIdx, int &dir) { - SSession *sess = &g_Sessions[sessIdx]; - if(!sess.inBreakout) return false; + if(!g_Sessions[sessIdx].inBreakout) return false; double c0 = iClose(g_Symbol, PERIOD_CURRENT, 0); double c1 = iClose(g_Symbol, PERIOD_CURRENT, 1); @@ -596,28 +597,36 @@ bool DetectRetest(int sessIdx, int &dir) double l0 = iLow(g_Symbol, PERIOD_CURRENT, 0); // Bullish retest: previous bar was above ORB, this bar touched it and closed back above - if(sess.breakoutDir == 1) + if(g_Sessions[sessIdx].breakoutDir == 1) { - bool retest = (c1 > sess.orbHigh) && (l0 <= sess.orbHigh) && (c0 >= sess.orbHigh); + bool retest = (c1 > g_Sessions[sessIdx].orbHigh) + && (l0 <= g_Sessions[sessIdx].orbHigh) + && (c0 >= g_Sessions[sessIdx].orbHigh); if(retest) { dir = 1; return true; } } // Bearish retest: previous bar was below ORB, this bar touched it and closed back below - else if(sess.breakoutDir == -1) + else if(g_Sessions[sessIdx].breakoutDir == -1) { - bool retest = (c1 < sess.orbLow) && (h0 >= sess.orbLow) && (c0 <= sess.orbLow); + bool retest = (c1 < g_Sessions[sessIdx].orbLow) + && (h0 >= g_Sessions[sessIdx].orbLow) + && (c0 <= g_Sessions[sessIdx].orbLow); if(retest) { dir = -1; return true; } } // Failed retest - if(sess.breakoutDir == 1 && c0 < sess.orbHigh && c1 > sess.orbHigh) + if(g_Sessions[sessIdx].breakoutDir == 1 + && c0 < g_Sessions[sessIdx].orbHigh && c1 > g_Sessions[sessIdx].orbHigh) { - PrintFormat("NANDR EA: [%s] Failed bullish retest", sess.name); - sess.inBreakout = false; sess.breakoutDir = 0; + PrintFormat("NANDR EA: [%s] Failed bullish retest", g_Sessions[sessIdx].name); + g_Sessions[sessIdx].inBreakout = false; + g_Sessions[sessIdx].breakoutDir = 0; } - else if(sess.breakoutDir == -1 && c0 > sess.orbLow && c1 < sess.orbLow) + else if(g_Sessions[sessIdx].breakoutDir == -1 + && c0 > g_Sessions[sessIdx].orbLow && c1 < g_Sessions[sessIdx].orbLow) { - PrintFormat("NANDR EA: [%s] Failed bearish retest", sess.name); - sess.inBreakout = false; sess.breakoutDir = 0; + PrintFormat("NANDR EA: [%s] Failed bearish retest", g_Sessions[sessIdx].name); + g_Sessions[sessIdx].inBreakout = false; + g_Sessions[sessIdx].breakoutDir = 0; } return false; @@ -634,7 +643,7 @@ void ScanOrderBlocks() // Check for volume pivot at bar[length] // Volume at bar[length] must be highest in [0..2*length] - double pivotVol = iVolume(g_Symbol, PERIOD_CURRENT, length); + long pivotVol = iVolume(g_Symbol, PERIOD_CURRENT, length); bool isVolPivot = true; for(int i = 0; i < length * 2 + 1; i++) { @@ -911,22 +920,21 @@ void CheckEntrySignals() { for(int s = 0; s < g_SessionCount; s++) { - SSession *sess = &g_Sessions[s]; - if(!sess.enabled || !sess.orbComplete) continue; - if(sess.tradesThisSession >= InpMaxPosPerSession) continue; + if(!g_Sessions[s].enabled || !g_Sessions[s].orbComplete) continue; + if(g_Sessions[s].tradesThisSession >= InpMaxPosPerSession) continue; int dir = 0; if(!DetectRetest(s, dir)) continue; double obTop = 0, obBottom = 0; bool obFound = IsOBNearLevel( - (dir > 0) ? sess.orbHigh : sess.orbLow, + (dir > 0) ? g_Sessions[s].orbHigh : g_Sessions[s].orbLow, dir, obTop, obBottom); // If OB required and not found, skip if(InpOBRequireConf && !obFound) continue; - double orbLevel = (dir > 0) ? sess.orbHigh : sess.orbLow; + double orbLevel = (dir > 0) ? g_Sessions[s].orbHigh : g_Sessions[s].orbLow; OpenTrade(dir, s, orbLevel, obTop, obBottom); } } @@ -946,7 +954,7 @@ void ManageOpenTrades() double sl = g_Position.StopLoss(); double currentSL= sl; double price = g_Position.PriceCurrent(); - long ticket = g_Position.Ticket(); + ulong ticket = g_Position.Ticket(); int posDir = (g_Position.PositionType() == POSITION_TYPE_BUY) ? 1 : -1; // Breakeven @@ -1083,46 +1091,51 @@ void UpdateClosedTrades() void DrawORBLines(int sessIdx) { if(sessIdx < 0 || sessIdx >= g_SessionCount) return; - SSession *sess = &g_Sessions[sessIdx]; color lineColor = clrWhite; - if(sess.name == "Tokyo") lineColor = clrDodgerBlue; - else if(sess.name == "London") lineColor = clrTomato; - else if(sess.name == "NY" || sess.name == "NYORB") lineColor = clrGold; - else if(sess.name == "DailyOpen") lineColor = clrSilver; + if(g_Sessions[sessIdx].name == "Tokyo") lineColor = clrDodgerBlue; + else if(g_Sessions[sessIdx].name == "London") lineColor = clrTomato; + else if(g_Sessions[sessIdx].name == "NY" + || g_Sessions[sessIdx].name == "NYORB") lineColor = clrGold; + else if(g_Sessions[sessIdx].name == "DailyOpen") lineColor = clrSilver; - string prefix = "NANDR_ORB_" + sess.name + "_"; + string prefix = "NANDR_ORB_" + g_Sessions[sessIdx].name + "_"; // High line string hName = prefix + "High"; ObjectDelete(0, hName); - if(sess.orbHigh > 0) + if(g_Sessions[sessIdx].orbHigh > 0) { - ObjectCreate(0, hName, OBJ_HLINE, 0, 0, sess.orbHigh); + ObjectCreate(0, hName, OBJ_HLINE, 0, 0, g_Sessions[sessIdx].orbHigh); ObjectSetInteger(0, hName, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, hName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, hName, OBJPROP_WIDTH, 1); - ObjectSetString(0, hName, OBJPROP_TOOLTIP, sess.name + " ORB High: " + DoubleToString(sess.orbHigh, 2)); + ObjectSetString(0, hName, OBJPROP_TOOLTIP, + g_Sessions[sessIdx].name + " ORB High: " + + DoubleToString(g_Sessions[sessIdx].orbHigh, 2)); } // Low line string lName = prefix + "Low"; ObjectDelete(0, lName); - if(sess.orbLow < DBL_MAX && sess.orbLow > 0) + if(g_Sessions[sessIdx].orbLow < DBL_MAX && g_Sessions[sessIdx].orbLow > 0) { - ObjectCreate(0, lName, OBJ_HLINE, 0, 0, sess.orbLow); + ObjectCreate(0, lName, OBJ_HLINE, 0, 0, g_Sessions[sessIdx].orbLow); ObjectSetInteger(0, lName, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, lName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, lName, OBJPROP_WIDTH, 1); - ObjectSetString(0, lName, OBJPROP_TOOLTIP, sess.name + " ORB Low: " + DoubleToString(sess.orbLow, 2)); + ObjectSetString(0, lName, OBJPROP_TOOLTIP, + g_Sessions[sessIdx].name + " ORB Low: " + + DoubleToString(g_Sessions[sessIdx].orbLow, 2)); } // Mid line (dotted) - if(sess.orbHigh > 0 && sess.orbLow < DBL_MAX && sess.orbLow > 0) + if(g_Sessions[sessIdx].orbHigh > 0 + && g_Sessions[sessIdx].orbLow < DBL_MAX && g_Sessions[sessIdx].orbLow > 0) { string mName = prefix + "Mid"; ObjectDelete(0, mName); - double mid = (sess.orbHigh + sess.orbLow) / 2.0; + double mid = (g_Sessions[sessIdx].orbHigh + g_Sessions[sessIdx].orbLow) / 2.0; ObjectCreate(0, mName, OBJ_HLINE, 0, 0, mid); ObjectSetInteger(0, mName, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, mName, OBJPROP_STYLE, STYLE_DOT); @@ -1137,7 +1150,9 @@ void DrawORBLines(int sessIdx) //+------------------------------------------------------------------+ void DrawOBZone(int idx, bool isBull) { - SOrderBlock *ob = isBull ? &g_BullOBs[idx] : &g_BearOBs[idx]; + SOrderBlock ob; + if(isBull) ob = g_BullOBs[idx]; + else ob = g_BearOBs[idx]; if(!ob.active) return; color bgColor = isBull ? (color)ColorToARGB(clrGreen, 40) : (color)ColorToARGB(clrRed, 40); @@ -1207,6 +1222,28 @@ void DrawTradeLabel(int dir, double entry, double sl, double tp) ChartRedraw(0); } +// Dashboard helper — module-level state set by DrawDashboard before each call +string g_DBPrefix = "NANDR_DB_"; +int g_DBX = 15; +int g_DBY = 30; +int g_DBDY = 18; +int g_DBLine = 0; + +void DBLine(string txt, color col, int fontSize = 9) +{ + string name = g_DBPrefix + IntegerToString(g_DBLine); + ObjectDelete(0, name); + ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); + ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(0, name, OBJPROP_XDISTANCE, g_DBX); + ObjectSetInteger(0, name, OBJPROP_YDISTANCE, g_DBY + g_DBLine * g_DBDY); + ObjectSetInteger(0, name, OBJPROP_COLOR, col); + ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); + ObjectSetString(0, name, OBJPROP_FONT, "Consolas"); + ObjectSetString(0, name, OBJPROP_TEXT, txt); + g_DBLine++; +} + //+------------------------------------------------------------------+ //| Draw dashboard | //+------------------------------------------------------------------+ @@ -1214,72 +1251,63 @@ void DrawDashboard() { if(!InpShowDashboard) return; - string prefix = "NANDR_DB_"; - int x = 15; - int y = 30; - int dy = 18; - int line = 0; color title = clrGold; color normal = clrSilver; color good = clrLimeGreen; color bad = clrTomato; - auto DrawText = [&](string txt, color col, int fontSize = 9) - { - string name = prefix + IntegerToString(line); - ObjectDelete(0, name); - ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); - ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); - ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); - ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y + line * dy); - ObjectSetInteger(0, name, OBJPROP_COLOR, col); - ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); - ObjectSetString(0, name, OBJPROP_FONT, "Consolas"); - ObjectSetString(0, name, OBJPROP_TEXT, txt); - line++; - }; + g_DBPrefix = "NANDR_DB_"; + g_DBX = 15; + g_DBY = 30; + g_DBDY = 18; + g_DBLine = 0; - DrawText("▌ NANDR ORB+OB EA", title, 10); - DrawText("─────────────────────────", normal); - DrawText(StringFormat("Symbol : %s TF: %s", g_Symbol, EnumToString(Period())), normal); - DrawText(StringFormat("OrbTF : M%d BarsNeeded: %d", (int)InpOrbTimeframe, g_OrbBarsNeeded), normal); - DrawText("─────────────────────────", normal); + DBLine("== NANDR ORB+OB EA ==", title, 10); + DBLine("-------------------------", normal); + DBLine(StringFormat("Symbol : %s TF: %s", g_Symbol, EnumToString(Period())), normal); + DBLine(StringFormat("OrbTF : M%d BarsNeeded: %d", (int)InpOrbTimeframe, g_OrbBarsNeeded), normal); + DBLine("-------------------------", normal); // Sessions for(int s = 0; s < g_SessionCount; s++) { - SSession *sess = &g_Sessions[s]; - string status = sess.orbComplete ? "ARMED" : (sess.orbBarCount > 0 ? "ACCUM" : "WAIT "); - if(sess.inBreakout) status = (sess.breakoutDir > 0 ? "BULL↑" : "BEAR↓"); - color sc = sess.orbComplete ? good : normal; + string status; + if(g_Sessions[s].orbComplete) + status = "ARMED"; + else if(g_Sessions[s].orbBarCount > 0) + status = "ACCUM"; + else + status = "WAIT "; + if(g_Sessions[s].inBreakout) + status = (g_Sessions[s].breakoutDir > 0 ? "BULL+" : "BEAR-"); + color sc = g_Sessions[s].orbComplete ? good : normal; if(g_TradingHalted) sc = bad; - DrawText(StringFormat("%-8s H:%.2f L:%.2f [%s]", - sess.name, sess.orbHigh > 0 ? sess.orbHigh : 0, - sess.orbLow < DBL_MAX ? sess.orbLow : 0, status), sc); + DBLine(StringFormat("%-8s H:%.2f L:%.2f [%s]", + g_Sessions[s].name, + g_Sessions[s].orbHigh > 0 ? g_Sessions[s].orbHigh : 0, + g_Sessions[s].orbLow < DBL_MAX ? g_Sessions[s].orbLow : 0, + status), sc); } - DrawText("─────────────────────────", normal); - - // OB counts - DrawText(StringFormat("BullOBs: %d BearOBs: %d", g_BullOBCount, g_BearOBCount), normal); - - DrawText("─────────────────────────", normal); + DBLine("-------------------------", normal); + DBLine(StringFormat("BullOBs: %d BearOBs: %d", g_BullOBCount, g_BearOBCount), normal); + DBLine("-------------------------", normal); // Daily stats double wr = (g_TodayWins + g_TodayLosses > 0) ? (double)g_TodayWins / (g_TodayWins + g_TodayLosses) * 100.0 : 0; - DrawText(StringFormat("Trades : %d / %d", g_TodayTrades, InpMaxTradesPerDay), normal); - DrawText(StringFormat("W/L : %d / %d WR: %.0f%%", g_TodayWins, g_TodayLosses, wr), - g_TodayWins >= g_TodayLosses ? good : normal); - DrawText(StringFormat("Today PnL: %+.2f USD", g_TodayPnL), - g_TodayPnL >= 0 ? good : bad); - DrawText(StringFormat("Equity : %.2f", AccountInfoDouble(ACCOUNT_EQUITY)), normal); + DBLine(StringFormat("Trades : %d / %d", g_TodayTrades, InpMaxTradesPerDay), normal); + DBLine(StringFormat("W/L : %d / %d WR: %.0f%%", g_TodayWins, g_TodayLosses, wr), + g_TodayWins >= g_TodayLosses ? good : normal); + DBLine(StringFormat("Today PnL: %+.2f USD", g_TodayPnL), + g_TodayPnL >= 0 ? good : bad); + DBLine(StringFormat("Equity : %.2f", AccountInfoDouble(ACCOUNT_EQUITY)), normal); if(g_TradingHalted) - DrawText("⚠ TRADING HALTED — Daily limit", bad, 10); + DBLine("! TRADING HALTED - Daily limit", bad, 10); else if(g_SessionCount == 0) - DrawText("⚠ No sessions enabled", bad); + DBLine("! No sessions enabled", bad); ChartRedraw(0); }