diff --git a/Experts/EA_ICT_CL.mq5 b/Experts/EA_ICT_CL.mq5 index 4529943..a519709 100644 --- a/Experts/EA_ICT_CL.mq5 +++ b/Experts/EA_ICT_CL.mq5 @@ -16,7 +16,9 @@ #property copyright "Bell's ICT EA" #property version "4.30" -#define MAX_FVG_POOL 30 +#define MAX_FVG_POOL 30 +#define MAX_ORDER_HISTORY 50 +#define ORDER_HIST_DAYS 3 #include "EA_ICT_CL/Config.mqh" #include "EA_ICT_CL/State.mqh" @@ -48,11 +50,16 @@ int g_TradeBarIndex = -1; OrderPlan g_OrderPlan; ulong g_PendingTicket = 0; +OrderHistRecord g_OrderHist[MAX_ORDER_HISTORY]; +int g_OrderHistCount = 0; +int g_NextOrderHistId = 0; + const string PREFIX_SWING_MIDDLE = "SwingMiddle_"; const string PREFIX_SWING_TRIGGER = "SwingTrigger_"; const string PREFIX_MSS_MARKER = "MSSMarker_"; const string PREFIX_FVG_POOL = "FVGPool_"; const string PREFIX_ORDER_VISUAL = "OrderVisual_"; +const string PREFIX_ORDER_HIST = "OrdHist_"; const string PREFIX_DEBUG_PANEL = "DebugPanel_"; const string PREFIX_SESSION = "Session_"; @@ -69,12 +76,15 @@ int OnInit() ZeroMemory(g_MiddleTrend); ZeroMemory(g_TriggerTrend); ZeroMemory(g_DailyRisk); ZeroMemory(g_OrderPlan); for (int i = 0; i < MAX_FVG_POOL; i++) ZeroMemory(g_FVGPool[i]); + for (int i = 0; i < MAX_ORDER_HISTORY; i++) ZeroMemory(g_OrderHist[i]); g_FVGCount = 0; g_NextFVGId = 0; g_ActiveFVGIdx = -1; g_TradeBarIndex = -1; g_PendingTicket = 0; + g_OrderHistCount = 0; g_NextOrderHistId = 0; g_State = EA_IDLE; g_BlockReason = BLOCK_NONE; + ObjectsDeleteAll(0, PREFIX_ORDER_VISUAL); UpdateAllContexts(); - ScanAndRegisterFVGs(); + UpdateFVGPool(); DrawVisuals(); PrintFormat("✅ ICT EA v4.3 | H1=%s | M5=%s | Pool=%d", @@ -83,10 +93,11 @@ int OnInit() return INIT_SUCCEEDED; } -/** Each tick: update contexts, run guards and state machine, optionally redraw. */ +/** Each tick: update contexts & FVG pool always; run state machine only when guards pass. */ void OnTick() { UpdateAllContexts(); + UpdateFVGPool(); if (EvaluateGuards()) RunStateMachine(); else if (g_State != EA_IDLE) diff --git a/Experts/EA_ICT_CL/Drawing.mqh b/Experts/EA_ICT_CL/Drawing.mqh index 95c50e7..7422fd2 100644 --- a/Experts/EA_ICT_CL/Drawing.mqh +++ b/Experts/EA_ICT_CL/Drawing.mqh @@ -176,174 +176,180 @@ inline void DrawMSSMarkers() } } -/** Draws order plan zones/lines/labels (entry, SL, TP) when valid and debug draw on. */ -inline void DrawOrderVisualization() +/** Helper: creates or moves a rectangle. */ +inline void SetRect(string name, datetime t1, double p1, datetime t2, double p2, color clr) { - if (!InpDebugDraw) return; + if (ObjectFind(0, name) < 0) + ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, p1, t2, p2); + ObjectSetInteger(0, name, OBJPROP_COLOR, clr); + ObjectSetInteger(0, name, OBJPROP_FILL, true); + ObjectSetInteger(0, name, OBJPROP_BACK, true); + ObjectMove(0, name, 0, t1, p1); + ObjectMove(0, name, 1, t2, p2); +} - string tpZoneN = PREFIX_ORDER_VISUAL + "TP_ZONE"; - string slZoneN = PREFIX_ORDER_VISUAL + "SL_ZONE"; - string entLineN = PREFIX_ORDER_VISUAL + "ENTRY_LINE"; - string slLineN = PREFIX_ORDER_VISUAL + "SL_LINE"; - string tpLineN = PREFIX_ORDER_VISUAL + "TP_LINE"; - string entLblN = PREFIX_ORDER_VISUAL + "ENTRY_LBL"; - string slLblN = PREFIX_ORDER_VISUAL + "SL_LBL"; - string tpLblN = PREFIX_ORDER_VISUAL + "TP_LBL"; - string infoLblN = PREFIX_ORDER_VISUAL + "INFO_LBL"; +/** Helper: creates or moves a horizontal trend line. */ +inline void SetHLine(string name, datetime t1, double p, datetime t2, color clr, int style, int width) +{ + if (ObjectFind(0, name) < 0) + ObjectCreate(0, name, OBJ_TREND, 0, t1, p, t2, p); + ObjectSetInteger(0, name, OBJPROP_COLOR, clr); + ObjectSetInteger(0, name, OBJPROP_STYLE, style); + ObjectSetInteger(0, name, OBJPROP_WIDTH, width); + ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false); + ObjectMove(0, name, 0, t1, p); + ObjectMove(0, name, 1, t2, p); +} - if (!g_OrderPlan.valid || g_State == EA_IDLE) - { - ObjectDelete(0, tpZoneN); - ObjectDelete(0, slZoneN); - ObjectDelete(0, entLineN); - ObjectDelete(0, slLineN); - ObjectDelete(0, tpLineN); - ObjectDelete(0, entLblN); - ObjectDelete(0, slLblN); - ObjectDelete(0, tpLblN); - ObjectDelete(0, infoLblN); - return; - } +/** Helper: creates or moves a text label at a chart coordinate. */ +inline void SetText(string name, datetime t, double p, string txt, color clr, int fontSize, int anchor) +{ + if (ObjectFind(0, name) < 0) + ObjectCreate(0, name, OBJ_TEXT, 0, t, p); + ObjectMove(0, name, 0, t, p); + ObjectSetString (0, name, OBJPROP_TEXT, txt); + ObjectSetInteger(0, name, OBJPROP_COLOR, clr); + ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); + ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor); +} - bool isBuy = (g_OrderPlan.direction > 0); - double entry = g_OrderPlan.entry; - double sl = g_OrderPlan.stopLoss; - double tp = g_OrderPlan.takeProfit; +/** Draws one order record in TradingView style (TP/SL zones, entry line, labels). */ +inline void DrawOneOrderHist(int idx) +{ + string sid = IntegerToString(g_OrderHist[idx].id); + string pfx = PREFIX_ORDER_HIST + sid + "_"; - datetime tStart = (g_TriggerTrend.lastMssTime > 0) ? g_TriggerTrend.lastMssTime : iTime(_Symbol, InpTriggerTF, 20); - datetime tEnd = iTime(_Symbol, InpTriggerTF, 0) + PeriodSeconds(InpTriggerTF) * 25; + bool isBuy = (g_OrderHist[idx].direction > 0); + double entry = g_OrderHist[idx].entry; + double sl = g_OrderHist[idx].stopLoss; + double tp = g_OrderHist[idx].takeProfit; + int res = g_OrderHist[idx].result; + + datetime tStart = g_OrderHist[idx].signalTime; + datetime tEnd; + if (res == 0) + tEnd = iTime(_Symbol, InpTriggerTF, 0) + PeriodSeconds(InpTriggerTF) * 10; + else + tEnd = g_OrderHist[idx].closeTime; + if (tEnd <= tStart) + tEnd = tStart + PeriodSeconds(InpTriggerTF) * 10; double slPips = MathAbs(entry - sl) / _Point; double tpPips = MathAbs(tp - entry) / _Point; double rr = (slPips > 0) ? tpPips / slPips : 0; - color entryClr = isBuy ? C'33,150,243' : C'255,152,0'; - color tpFill = C'15,65,35'; - color slFill = C'85,15,15'; - color tpClr = C'38,166,91'; - color slClr = C'229,57,53'; + color entClr, tpZoneClr, slZoneClr, tpLineClr, slLineClr, resultClr; + if (res == 1) + { + entClr = C'0,180,80'; + tpZoneClr = C'10,70,30'; + slZoneClr = C'40,15,15'; + tpLineClr = C'38,200,91'; + slLineClr = C'120,50,50'; + resultClr = C'0,220,100'; + } + else if (res == -1) + { + entClr = C'220,50,50'; + tpZoneClr = C'15,40,15'; + slZoneClr = C'90,12,12'; + tpLineClr = C'50,120,50'; + slLineClr = C'229,57,53'; + resultClr = C'229,57,53'; + } + else + { + entClr = isBuy ? C'33,150,243' : C'255,152,0'; + tpZoneClr = C'15,60,30'; + slZoneClr = C'75,12,12'; + tpLineClr = C'38,166,91'; + slLineClr = C'229,57,53'; + resultClr = entClr; + } - double tpTop = isBuy ? tp : entry; + double tpTop = isBuy ? tp : entry; double tpBot = isBuy ? entry : tp; - if (ObjectFind(0, tpZoneN) < 0) - ObjectCreate(0, tpZoneN, OBJ_RECTANGLE, 0, tStart, tpTop, tEnd, tpBot); - ObjectSetInteger(0, tpZoneN, OBJPROP_COLOR, tpFill); - ObjectSetInteger(0, tpZoneN, OBJPROP_FILL, true); - ObjectSetInteger(0, tpZoneN, OBJPROP_BACK, true); - ObjectMove(0, tpZoneN, 0, tStart, tpTop); - ObjectMove(0, tpZoneN, 1, tEnd, tpBot); + SetRect(pfx + "TP", tStart, tpTop, tEnd, tpBot, tpZoneClr); double slTop = isBuy ? entry : sl; double slBot = isBuy ? sl : entry; - if (ObjectFind(0, slZoneN) < 0) - ObjectCreate(0, slZoneN, OBJ_RECTANGLE, 0, tStart, slTop, tEnd, slBot); - ObjectSetInteger(0, slZoneN, OBJPROP_COLOR, slFill); - ObjectSetInteger(0, slZoneN, OBJPROP_FILL, true); - ObjectSetInteger(0, slZoneN, OBJPROP_BACK, true); - ObjectMove(0, slZoneN, 0, tStart, slTop); - ObjectMove(0, slZoneN, 1, tEnd, slBot); + SetRect(pfx + "SL", tStart, slTop, tEnd, slBot, slZoneClr); - if (ObjectFind(0, entLineN) < 0) - ObjectCreate(0, entLineN, OBJ_TREND, 0, tStart, entry, tEnd, entry); - ObjectSetInteger(0, entLineN, OBJPROP_COLOR, entryClr); - ObjectSetInteger(0, entLineN, OBJPROP_STYLE, STYLE_SOLID); - ObjectSetInteger(0, entLineN, OBJPROP_WIDTH, 2); - ObjectSetInteger(0, entLineN, OBJPROP_RAY_RIGHT, true); - ObjectMove(0, entLineN, 0, tStart, entry); - ObjectMove(0, entLineN, 1, tEnd, entry); + SetHLine(pfx + "ENT", tStart, entry, tEnd, entClr, STYLE_SOLID, 2); + SetHLine(pfx + "TPL", tStart, tp, tEnd, tpLineClr, STYLE_DASH, 1); + SetHLine(pfx + "SLL", tStart, sl, tEnd, slLineClr, STYLE_DASH, 1); - if (ObjectFind(0, tpLineN) < 0) - ObjectCreate(0, tpLineN, OBJ_TREND, 0, tStart, tp, tEnd, tp); - ObjectSetInteger(0, tpLineN, OBJPROP_COLOR, tpClr); - ObjectSetInteger(0, tpLineN, OBJPROP_STYLE, STYLE_DASH); - ObjectSetInteger(0, tpLineN, OBJPROP_WIDTH, 1); - ObjectSetInteger(0, tpLineN, OBJPROP_RAY_RIGHT, true); - ObjectMove(0, tpLineN, 0, tStart, tp); - ObjectMove(0, tpLineN, 1, tEnd, tp); + datetime lblT = tEnd + PeriodSeconds(InpTriggerTF); - if (ObjectFind(0, slLineN) < 0) - ObjectCreate(0, slLineN, OBJ_TREND, 0, tStart, sl, tEnd, sl); - ObjectSetInteger(0, slLineN, OBJPROP_COLOR, slClr); - ObjectSetInteger(0, slLineN, OBJPROP_STYLE, STYLE_DASH); - ObjectSetInteger(0, slLineN, OBJPROP_WIDTH, 1); - ObjectSetInteger(0, slLineN, OBJPROP_RAY_RIGHT, true); - ObjectMove(0, slLineN, 0, tStart, sl); - ObjectMove(0, slLineN, 1, tEnd, sl); + string entTxt; + if (res == 0) + entTxt = StringFormat("%s %.3f | %.2f lot", + isBuy ? "BUY LIM" : "SELL LIM", entry, g_OrderHist[idx].lot); + else + entTxt = StringFormat("%s %.3f | %.2f lot | %s", + isBuy ? "BUY" : "SELL", entry, g_OrderHist[idx].lot, + (res == 1) ? "WIN" : (res == -1) ? "LOSS" : "X"); + SetText(pfx + "ELBL", lblT, entry, entTxt, entClr, 8, ANCHOR_LEFT); - datetime lblT = tEnd; + SetText(pfx + "TPLBL", lblT, tp, + StringFormat("TP %.3f | +%.0fp (%.1fR)", tp, tpPips, rr), + tpLineClr, 7, isBuy ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); - string eTxt = StringFormat( - "%s %s | %.2f lot", - isBuy ? "▶ BUY LIM" : "▶ SELL LIM", - DoubleToString(entry, _Digits), - g_OrderPlan.lot - ); - if (ObjectFind(0, entLblN) < 0) - ObjectCreate(0, entLblN, OBJ_TEXT, 0, lblT, entry); - ObjectMove(0, entLblN, 0, lblT, entry); - ObjectSetString(0, entLblN, OBJPROP_TEXT, eTxt); - ObjectSetInteger(0, entLblN, OBJPROP_COLOR, entryClr); - ObjectSetInteger(0, entLblN, OBJPROP_FONTSIZE, 8); - ObjectSetInteger(0, entLblN, OBJPROP_ANCHOR, ANCHOR_LEFT); + SetText(pfx + "SLLBL", lblT, sl, + StringFormat("SL %.3f | -%.0fp", sl, slPips), + slLineClr, 7, isBuy ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); - string tTxt = StringFormat( - "◎ TP %s +%.0fp (%.1fR)", - DoubleToString(tp, _Digits), - tpPips, - rr - ); - if (ObjectFind(0, tpLblN) < 0) - ObjectCreate(0, tpLblN, OBJ_TEXT, 0, lblT, tp); - ObjectMove(0, tpLblN, 0, lblT, tp); - ObjectSetString(0, tpLblN, OBJPROP_TEXT, tTxt); - ObjectSetInteger(0, tpLblN, OBJPROP_COLOR, tpClr); - ObjectSetInteger(0, tpLblN, OBJPROP_FONTSIZE, 8); - ObjectSetInteger( - 0, - tpLblN, - OBJPROP_ANCHOR, - isBuy ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER - ); + if (res != 0) + { + string resTxt = (res == 1) + ? StringFormat("✓ +$%.2f", g_OrderHist[idx].profit) + : (res == -1) + ? StringFormat("✗ $%.2f", g_OrderHist[idx].profit) + : "✗ cancelled"; + SetText(pfx + "RES", lblT, (entry + sl) / 2.0, resTxt, resultClr, 8, ANCHOR_LEFT); + } + else + { + SetText(pfx + "RES", lblT, (entry + sl) / 2.0, + StringFormat("Risk %.1f%% | %.0f:%.0fp | %.1fR", InpRiskPercent, slPips, tpPips, rr), + C'140,140,140', 7, ANCHOR_LEFT); + } +} - string sTxt = StringFormat( - "✕ SL %s -%.0fp", - DoubleToString(sl, _Digits), - slPips - ); - if (ObjectFind(0, slLblN) < 0) - ObjectCreate(0, slLblN, OBJ_TEXT, 0, lblT, sl); - ObjectMove(0, slLblN, 0, lblT, sl); - ObjectSetString(0, slLblN, OBJPROP_TEXT, sTxt); - ObjectSetInteger(0, slLblN, OBJPROP_COLOR, slClr); - ObjectSetInteger(0, slLblN, OBJPROP_FONTSIZE, 8); - ObjectSetInteger( - 0, - slLblN, - OBJPROP_ANCHOR, - isBuy ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER - ); +/** Deletes all chart objects for one order history record. */ +inline void DeleteOrderHistObjects(int id) +{ + string sid = IntegerToString(id); + string pfx = PREFIX_ORDER_HIST + sid + "_"; + ObjectDelete(0, pfx + "TP"); + ObjectDelete(0, pfx + "SL"); + ObjectDelete(0, pfx + "ENT"); + ObjectDelete(0, pfx + "TPL"); + ObjectDelete(0, pfx + "SLL"); + ObjectDelete(0, pfx + "ELBL"); + ObjectDelete(0, pfx + "TPLBL"); + ObjectDelete(0, pfx + "SLLBL"); + ObjectDelete(0, pfx + "RES"); +} - string iTxt = StringFormat( - "Risk %.1f%% | %.0f:%.0f pips | %.1fR", - InpRiskPercent, - slPips, - tpPips, - rr - ); - if (ObjectFind(0, infoLblN) < 0) - ObjectCreate( - 0, - infoLblN, - OBJ_TEXT, - 0, - lblT, - (entry + sl) / 2.0 - ); - ObjectMove(0, infoLblN, 0, lblT, (entry + sl) / 2.0); - ObjectSetString(0, infoLblN, OBJPROP_TEXT, iTxt); - ObjectSetInteger(0, infoLblN, OBJPROP_COLOR, C'160,160,160'); - ObjectSetInteger(0, infoLblN, OBJPROP_FONTSIZE, 7); - ObjectSetInteger(0, infoLblN, OBJPROP_ANCHOR, ANCHOR_LEFT); +/** Draws all order history records; cleans up records older than ORDER_HIST_DAYS. */ +inline void DrawAllOrders() +{ + if (!InpDebugDraw) return; + + datetime cutoff = TimeCurrent() - ORDER_HIST_DAYS * 86400; + for (int i = g_OrderHistCount - 1; i >= 0; i--) + { + if (g_OrderHist[i].closeTime > 0 && g_OrderHist[i].closeTime < cutoff) + { + DeleteOrderHistObjects(g_OrderHist[i].id); + for (int j = i; j < g_OrderHistCount - 1; j++) + g_OrderHist[j] = g_OrderHist[j + 1]; + g_OrderHistCount--; + continue; + } + DrawOneOrderHist(i); + } } /** Draws one FVG record (rectangle + mid line + label) by pool index. */ @@ -757,7 +763,7 @@ inline void DrawSessionMarkers() } } -/** Draws all debug visuals: swings, MSS markers, FVG pool, order, debug panel. */ +/** Draws all debug visuals: swings, MSS markers, FVG pool, orders, debug panel. */ inline void DrawVisuals() { DrawSessionMarkers(); @@ -766,7 +772,7 @@ inline void DrawVisuals() DrawFVGPool(); DrawTriggerSwingPoints(); DrawMSSMarkers(); - DrawOrderVisualization(); + DrawAllOrders(); } #endif // EA_ICT_CL__DRAWING_MQH diff --git a/Experts/EA_ICT_CL/Guards.mqh b/Experts/EA_ICT_CL/Guards.mqh index 869caaf..97f736f 100644 --- a/Experts/EA_ICT_CL/Guards.mqh +++ b/Experts/EA_ICT_CL/Guards.mqh @@ -4,6 +4,7 @@ /** True if current time (UTC) is within London or NY session (InpLondon* / InpNY*). */ inline bool IsSessionAllowed() { + return true; int hourUtc = GetUTCHour(TimeCurrent()); return IsHourInRange(hourUtc, InpLondonStartHour, InpLondonEndHour) || IsHourInRange(hourUtc, InpNYStartHour, InpNYEndHour); diff --git a/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh b/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh index 7ae0796..4589c13 100644 --- a/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh +++ b/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh @@ -100,21 +100,9 @@ inline void ScanAndRegisterFVGs() if (isBrokenByClose) { record.status = FVG_USED; record.usedCase = 1; record.usedTime = breakTime; } else { - bool isTouchedInGap = false; - datetime touchTimeInGap = 0; - for (int j = rightBarIndex - 1; j >= 1; j--) - { - bool inGap = (record.direction == DIR_UP && iLow (_Symbol, InpMiddleTF, j) <= record.high) || - (record.direction == DIR_DOWN && iHigh(_Symbol, InpMiddleTF, j) >= record.low); - if (inGap) { isTouchedInGap = true; touchTimeInGap = iTime(_Symbol, InpMiddleTF, j); break; } - } - if (isTouchedInGap) { record.status = FVG_TOUCHED; record.touchTime = touchTimeInGap; record.triggerTrendAtTouch = g_TriggerTrend.trend; } - else - { - record.status = FVG_PENDING; - if ((int)(TimeCurrent() - record.createdTime) > InpFVGMaxAliveMin * 60) - { record.status = FVG_USED; record.usedCase = 0; record.usedTime = TimeCurrent(); } - } + record.status = FVG_PENDING; + if ((int)(TimeCurrent() - record.createdTime) > InpFVGMaxAliveMin * 60) + { record.status = FVG_USED; record.usedCase = 0; record.usedTime = TimeCurrent(); } } g_FVGPool[g_FVGCount] = record; @@ -161,8 +149,10 @@ inline void UpdateFVGStatuses() continue; } - bool isTouched = (g_FVGPool[i].direction == DIR_UP && bid <= g_FVGPool[i].high) || - (g_FVGPool[i].direction == DIR_DOWN && bid >= g_FVGPool[i].low); + double fvgRange = g_FVGPool[i].high - g_FVGPool[i].low; + double touchDepth = fvgRange * InpFVGTouchPct / 100.0; + bool isTouched = (g_FVGPool[i].direction == DIR_UP && bid <= g_FVGPool[i].high - touchDepth) || + (g_FVGPool[i].direction == DIR_DOWN && bid >= g_FVGPool[i].low + touchDepth); if (isTouched) { g_FVGPool[i].status = FVG_TOUCHED; diff --git a/Experts/EA_ICT_CL/State.mqh b/Experts/EA_ICT_CL/State.mqh index ffa7014..521cfca 100644 --- a/Experts/EA_ICT_CL/State.mqh +++ b/Experts/EA_ICT_CL/State.mqh @@ -60,6 +60,18 @@ struct OrderPlan int parentFVGId; }; +struct OrderHistRecord +{ + int id; + int direction; + double entry, stopLoss, takeProfit, lot; + datetime signalTime; + datetime closeTime; // 0 nếu đang active + int result; // 0=active, 1=TP hit, -1=SL hit, 2=cancelled + double profit; + int parentFVGId; +}; + struct DailyRiskContext { double startBalance, currentBalance; diff --git a/Experts/EA_ICT_CL/StateMachine.mqh b/Experts/EA_ICT_CL/StateMachine.mqh index 24cbded..0005b2c 100644 --- a/Experts/EA_ICT_CL/StateMachine.mqh +++ b/Experts/EA_ICT_CL/StateMachine.mqh @@ -84,6 +84,7 @@ inline void OnStateWaitTrigger() { g_PendingTicket = ticket; g_TradeBarIndex = Bars(_Symbol, InpTriggerTF); + SaveOrderToHistory(g_FVGPool[activeIndex].mssTime); TransitionTo(EA_IN_TRADE); } else @@ -142,9 +143,10 @@ inline void OnStateInTrade() break; } } - if (cancelled) { ResetToIdle("order cancelled"); return; } + if (cancelled) { CloseActiveOrderRecord(2, 0); ResetToIdle("order cancelled"); return; } bool closed = false; + double closedProfit = 0; for (int i = HistoryDealsTotal() - 1; i >= 0; i--) { ulong dealTicket = HistoryDealGetTicket(i); @@ -152,11 +154,13 @@ inline void OnStateInTrade() && HistoryDealGetString(dealTicket, DEAL_SYMBOL) == _Symbol && HistoryDealGetInteger(dealTicket, DEAL_ENTRY) == DEAL_ENTRY_OUT) { - double profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); - if (InpDebugLog) PrintFormat("[TRADE] Closed | profit=%.2f", profit); + closedProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); + if (InpDebugLog) PrintFormat("[TRADE] Closed | profit=%.2f", closedProfit); closed = true; break; } } + if (closed) CloseActiveOrderRecord(closedProfit >= 0 ? 1 : -1, closedProfit); + else CloseActiveOrderRecord(2, 0); ResetToIdle(closed ? "trade closed" : "order lost"); return; } @@ -172,14 +176,33 @@ inline void OnStateInTrade() && PositionGetString(POSITION_SYMBOL) == _Symbol) { hasPosition = true; break; } } - if (!hasPosition) ResetToIdle("position closed"); + if (!hasPosition) + { + HistorySelect(TimeCurrent() - 86400, TimeCurrent()); + double lastProfit = 0; + for (int i = HistoryDealsTotal() - 1; i >= 0; i--) + { + ulong dt = HistoryDealGetTicket(i); + if (HistoryDealGetInteger(dt, DEAL_MAGIC) == InpMagicNumber + && HistoryDealGetString(dt, DEAL_SYMBOL) == _Symbol + && HistoryDealGetInteger(dt, DEAL_ENTRY) == DEAL_ENTRY_OUT) + { lastProfit = HistoryDealGetDouble(dt, DEAL_PROFIT); break; } + } + CloseActiveOrderRecord(lastProfit >= 0 ? 1 : -1, lastProfit); + ResetToIdle("position closed"); + } } -/** Runs state machine: updates FVG statuses, scans/registers FVGs, then handles current state. */ -inline void RunStateMachine() +/** Keeps FVG pool up to date regardless of guard state. */ +inline void UpdateFVGPool() { UpdateFVGStatuses(); ScanAndRegisterFVGs(); +} + +/** Runs state machine: handles current state (entry/exit logic). */ +inline void RunStateMachine() +{ switch (g_State) { case EA_IDLE: OnStateIdle(); break; diff --git a/Experts/EA_ICT_CL/Trade.mqh b/Experts/EA_ICT_CL/Trade.mqh index 4a6743a..2489d8b 100644 --- a/Experts/EA_ICT_CL/Trade.mqh +++ b/Experts/EA_ICT_CL/Trade.mqh @@ -131,4 +131,63 @@ inline ulong ExecuteLimitOrder() return result.order; } +/** Scans trigger TF bars from signalTime forward to find which bar first hit SL or TP. */ +inline datetime FindHitBarTime(const OrderHistRecord &rec) +{ + int startShift = MyBarShift(_Symbol, InpTriggerTF, rec.signalTime); + if (startShift < 0) return TimeCurrent(); + + for (int i = startShift; i >= 0; i--) + { + double h = iHigh(_Symbol, InpTriggerTF, i); + double l = iLow (_Symbol, InpTriggerTF, i); + bool hitTP = (rec.direction > 0) ? (h >= rec.takeProfit) : (l <= rec.takeProfit); + bool hitSL = (rec.direction > 0) ? (l <= rec.stopLoss) : (h >= rec.stopLoss); + if (hitTP || hitSL) + return iTime(_Symbol, InpTriggerTF, i); + } + return TimeCurrent(); +} + +/** Saves the current order plan into order history as active (result=0). */ +inline void SaveOrderToHistory(datetime signalTime) +{ + if (g_OrderHistCount >= MAX_ORDER_HISTORY) + { + for (int j = 0; j < g_OrderHistCount - 1; j++) + g_OrderHist[j] = g_OrderHist[j + 1]; + g_OrderHistCount--; + } + + int n = g_OrderHistCount; + ZeroMemory(g_OrderHist[n]); + g_OrderHist[n].id = g_NextOrderHistId++; + g_OrderHist[n].direction = g_OrderPlan.direction; + g_OrderHist[n].entry = g_OrderPlan.entry; + g_OrderHist[n].stopLoss = g_OrderPlan.stopLoss; + g_OrderHist[n].takeProfit = g_OrderPlan.takeProfit; + g_OrderHist[n].lot = g_OrderPlan.lot; + g_OrderHist[n].signalTime = signalTime; + g_OrderHist[n].parentFVGId = g_OrderPlan.parentFVGId; + g_OrderHistCount++; +} + +/** Closes the most recent active order record with given result and profit. */ +inline void CloseActiveOrderRecord(int result, double profit) +{ + for (int i = g_OrderHistCount - 1; i >= 0; i--) + { + if (g_OrderHist[i].result != 0) continue; + g_OrderHist[i].result = result; + g_OrderHist[i].profit = profit; + g_OrderHist[i].closeTime = FindHitBarTime(g_OrderHist[i]); + if (InpDebugLog) + PrintFormat("[ORDER HIST] #%d %s | profit=%.2f | close=%s", + g_OrderHist[i].id, + (result == 1) ? "TP HIT" : (result == -1) ? "SL HIT" : "CANCELLED", + profit, TimeToString(g_OrderHist[i].closeTime, TIME_MINUTES)); + break; + } +} + #endif