Files
Bell-PriceActionWithEma-EA/Experts/EA_ICT_CL/StateMachine.mqh
T

216 lines
7.5 KiB
Plaintext
Raw Normal View History

2026-03-10 13:47:42 +07:00
#ifndef EA_ICT_CL__STATE_MACHINE_MQH
#define EA_ICT_CL__STATE_MACHINE_MQH
2026-03-10 20:41:37 +07:00
/** Transitions EA state to next and optionally logs. */
inline void TransitionTo(EAState nextState)
2026-03-10 13:47:42 +07:00
{
if (InpDebugLog)
2026-03-10 20:41:37 +07:00
PrintFormat("[STATE] %s → %s", EnumToString(g_State), EnumToString(nextState));
g_State = nextState;
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:41:37 +07:00
/** Resets to IDLE, clears active FVG, trade bar, pending ticket and order plan. */
2026-03-10 13:47:42 +07:00
inline void ResetToIdle(string reason = "")
{
if (InpDebugLog && reason != "")
PrintFormat("[RESET→IDLE] %s", reason);
g_ActiveFVGIdx = -1;
g_TradeBarIndex = -1;
g_PendingTicket = 0;
ZeroMemory(g_OrderPlan);
TransitionTo(EA_IDLE);
}
2026-03-10 20:41:37 +07:00
/** Idle: picks best active FVG and transitions to WAIT_TOUCH or WAIT_TRIGGER. */
2026-03-10 13:47:42 +07:00
inline void OnStateIdle()
{
2026-03-10 20:41:37 +07:00
int bestFvgIndex = GetBestActiveFVGIdx();
if (bestFvgIndex < 0) return;
g_ActiveFVGIdx = bestFvgIndex;
2026-03-10 13:47:42 +07:00
if (InpDebugLog)
PrintFormat("[ACTIVE FVG] #%d %s [%.5f%.5f] %s",
2026-03-10 20:41:37 +07:00
g_FVGPool[bestFvgIndex].id, EnumToString(g_FVGPool[bestFvgIndex].direction),
g_FVGPool[bestFvgIndex].low, g_FVGPool[bestFvgIndex].high, EnumToString(g_FVGPool[bestFvgIndex].status));
TransitionTo(g_FVGPool[bestFvgIndex].status == FVG_TOUCHED ? EA_WAIT_TRIGGER : EA_WAIT_TOUCH);
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:41:37 +07:00
/** WaitTouch: checks active FVG still valid, optionally switches to better FVG. */
2026-03-10 13:47:42 +07:00
inline void OnStateWaitTouch()
{
if (g_ActiveFVGIdx < 0) { ResetToIdle("active lost"); return; }
2026-03-10 20:41:37 +07:00
int activeIndex = g_ActiveFVGIdx;
2026-03-10 13:47:42 +07:00
2026-03-10 20:41:37 +07:00
if (g_FVGPool[activeIndex].status == FVG_USED)
{ ResetToIdle(StringFormat("FVG #%d broken/expired", g_FVGPool[activeIndex].id)); return; }
if (g_FVGPool[activeIndex].status == FVG_TOUCHED)
2026-03-10 13:47:42 +07:00
{ TransitionTo(EA_WAIT_TRIGGER); return; }
2026-03-10 20:41:37 +07:00
int betterIndex = GetBestActiveFVGIdx();
if (betterIndex >= 0 && betterIndex != activeIndex)
2026-03-10 13:47:42 +07:00
{
2026-03-10 20:41:37 +07:00
bool isBetterTouched = (g_FVGPool[betterIndex].status == FVG_TOUCHED);
bool isBetterNewer = (g_FVGPool[betterIndex].createdTime > g_FVGPool[activeIndex].createdTime);
if (isBetterTouched || isBetterNewer)
2026-03-10 13:47:42 +07:00
{
if (InpDebugLog)
2026-03-10 20:41:37 +07:00
PrintFormat("[SWITCH] FVG #%d → #%d", g_FVGPool[activeIndex].id, g_FVGPool[betterIndex].id);
g_ActiveFVGIdx = betterIndex;
if (isBetterTouched) TransitionTo(EA_WAIT_TRIGGER);
2026-03-10 13:47:42 +07:00
}
}
}
2026-03-10 20:41:37 +07:00
/** WaitTrigger: on FVG usedCase==2 builds order plan, sends limit order, goes IN_TRADE or resets. */
2026-03-10 13:47:42 +07:00
inline void OnStateWaitTrigger()
{
if (g_ActiveFVGIdx < 0) { ResetToIdle("active lost"); return; }
2026-03-10 20:41:37 +07:00
int activeIndex = g_ActiveFVGIdx;
2026-03-10 13:47:42 +07:00
2026-03-10 20:41:37 +07:00
if (g_FVGPool[activeIndex].status == FVG_USED)
2026-03-10 13:47:42 +07:00
{
2026-03-10 20:41:37 +07:00
if (g_FVGPool[activeIndex].usedCase == 2)
2026-03-10 13:47:42 +07:00
{
if (InpDebugLog)
PrintFormat("[ENTRY SIGNAL] FVG #%d %s [%.5f%.5f] | MSS entry=%.5f SL=%.5f",
2026-03-10 20:41:37 +07:00
g_FVGPool[activeIndex].id, EnumToString(g_FVGPool[activeIndex].direction),
g_FVGPool[activeIndex].low, g_FVGPool[activeIndex].high,
g_FVGPool[activeIndex].mssEntry, g_FVGPool[activeIndex].mssSL);
2026-03-10 13:47:42 +07:00
2026-03-10 20:41:37 +07:00
if (BuildOrderPlan(g_FVGPool[activeIndex].id, g_FVGPool[activeIndex].direction,
g_FVGPool[activeIndex].mssEntry, g_FVGPool[activeIndex].mssSL))
2026-03-10 13:47:42 +07:00
{
ulong ticket = ExecuteLimitOrder();
if (ticket > 0)
{
g_PendingTicket = ticket;
g_TradeBarIndex = Bars(_Symbol, InpTriggerTF);
2026-03-10 23:43:41 +07:00
SaveOrderToHistory(g_FVGPool[activeIndex].mssTime);
2026-03-10 13:47:42 +07:00
TransitionTo(EA_IN_TRADE);
}
else
2026-03-10 20:41:37 +07:00
ResetToIdle(StringFormat("FVG #%d order failed", g_FVGPool[activeIndex].id));
2026-03-10 13:47:42 +07:00
}
else
2026-03-10 20:41:37 +07:00
ResetToIdle(StringFormat("FVG #%d invalid plan", g_FVGPool[activeIndex].id));
2026-03-10 13:47:42 +07:00
}
else
2026-03-10 20:41:37 +07:00
ResetToIdle(StringFormat("FVG #%d case%d", g_FVGPool[activeIndex].id, g_FVGPool[activeIndex].usedCase));
2026-03-10 13:47:42 +07:00
return;
}
2026-03-10 20:41:37 +07:00
if (g_FVGPool[activeIndex].status == FVG_PENDING) { TransitionTo(EA_WAIT_TOUCH); return; }
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:41:37 +07:00
/** InTrade: tracks pending order fill / cancel / expiry; resets when position closed or order lost. */
2026-03-10 13:47:42 +07:00
inline void OnStateInTrade()
{
if (g_PendingTicket > 0)
{
bool foundPending = false;
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderGetTicket(i) == g_PendingTicket) { foundPending = true; break; }
}
if (!foundPending)
{
2026-03-10 20:41:37 +07:00
bool positionFound = false;
2026-03-10 13:47:42 +07:00
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
2026-03-10 20:41:37 +07:00
ulong positionTicket = PositionGetTicket(i);
if (positionTicket > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber
2026-03-10 13:47:42 +07:00
&& PositionGetString(POSITION_SYMBOL) == _Symbol)
{
2026-03-10 20:41:37 +07:00
positionFound = true;
2026-03-10 13:47:42 +07:00
g_PendingTicket = 0;
2026-03-10 20:41:37 +07:00
if (InpDebugLog) PrintFormat("[TRADE] Filled → position #%llu", positionTicket);
2026-03-10 13:47:42 +07:00
break;
}
}
2026-03-10 20:41:37 +07:00
if (!positionFound)
2026-03-10 13:47:42 +07:00
{
HistorySelect(TimeCurrent() - 86400, TimeCurrent());
bool cancelled = false;
for (int i = HistoryOrdersTotal() - 1; i >= 0; i--)
{
2026-03-10 20:41:37 +07:00
ulong historyTicket = HistoryOrderGetTicket(i);
if (historyTicket == g_PendingTicket)
2026-03-10 13:47:42 +07:00
{
2026-03-10 20:41:37 +07:00
long orderState = HistoryOrderGetInteger(historyTicket, ORDER_STATE);
if (orderState == ORDER_STATE_CANCELED || orderState == ORDER_STATE_EXPIRED || orderState == ORDER_STATE_REJECTED)
2026-03-10 13:47:42 +07:00
cancelled = true;
break;
}
}
2026-03-10 23:43:41 +07:00
if (cancelled) { CloseActiveOrderRecord(2, 0); ResetToIdle("order cancelled"); return; }
2026-03-10 13:47:42 +07:00
bool closed = false;
2026-03-10 23:43:41 +07:00
double closedProfit = 0;
2026-03-10 13:47:42 +07:00
for (int i = HistoryDealsTotal() - 1; i >= 0; i--)
{
2026-03-10 20:41:37 +07:00
ulong dealTicket = HistoryDealGetTicket(i);
if (HistoryDealGetInteger(dealTicket, DEAL_MAGIC) == InpMagicNumber
&& HistoryDealGetString(dealTicket, DEAL_SYMBOL) == _Symbol
&& HistoryDealGetInteger(dealTicket, DEAL_ENTRY) == DEAL_ENTRY_OUT)
2026-03-10 13:47:42 +07:00
{
2026-03-10 23:43:41 +07:00
closedProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
if (InpDebugLog) PrintFormat("[TRADE] Closed | profit=%.2f", closedProfit);
2026-03-10 13:47:42 +07:00
closed = true; break;
}
}
2026-03-10 23:43:41 +07:00
if (closed) CloseActiveOrderRecord(closedProfit >= 0 ? 1 : -1, closedProfit);
else CloseActiveOrderRecord(2, 0);
2026-03-10 13:47:42 +07:00
ResetToIdle(closed ? "trade closed" : "order lost");
return;
}
}
return;
}
2026-03-10 20:41:37 +07:00
bool hasPosition = false;
2026-03-10 13:47:42 +07:00
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
2026-03-10 20:41:37 +07:00
ulong positionTicket = PositionGetTicket(i);
if (positionTicket > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber
2026-03-10 13:47:42 +07:00
&& PositionGetString(POSITION_SYMBOL) == _Symbol)
2026-03-10 20:41:37 +07:00
{ hasPosition = true; break; }
2026-03-10 13:47:42 +07:00
}
2026-03-10 23:43:41 +07:00
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");
}
2026-03-10 13:47:42 +07:00
}
2026-03-10 23:43:41 +07:00
/** Keeps FVG pool up to date regardless of guard state. */
inline void UpdateFVGPool()
2026-03-10 13:47:42 +07:00
{
UpdateFVGStatuses();
ScanAndRegisterFVGs();
2026-03-10 23:43:41 +07:00
}
/** Runs state machine: handles current state (entry/exit logic). */
inline void RunStateMachine()
{
2026-03-10 13:47:42 +07:00
switch (g_State)
{
case EA_IDLE: OnStateIdle(); break;
case EA_WAIT_TOUCH: OnStateWaitTouch(); break;
case EA_WAIT_TRIGGER: OnStateWaitTrigger(); break;
case EA_IN_TRADE: OnStateInTrade(); break;
}
}
2026-03-10 20:41:37 +07:00
#endif