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

194 lines
6.4 KiB
Plaintext
Raw Normal View History

2026-03-10 13:47:42 +07:00
#ifndef EA_ICT_CL__TRADE_MQH
#define EA_ICT_CL__TRADE_MQH
2026-03-10 20:39:33 +07:00
/** Calculates lot size from account risk percent and entrySL distance. */
2026-03-10 13:47:42 +07:00
inline double CalcLotFromRisk(double entry, double sl)
{
2026-03-10 20:39:33 +07:00
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskMoney = balance * InpRiskPercent / 100.0;
double slPips = MathAbs(entry - sl) / _Point;
2026-03-10 13:47:42 +07:00
if (slPips < 1) return 0;
2026-03-10 20:39:33 +07:00
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
2026-03-10 13:47:42 +07:00
if (tickValue <= 0 || tickSize <= 0) return 0;
2026-03-10 20:39:33 +07:00
double pipValue = tickValue * (_Point / tickSize);
double rawLot = riskMoney / (slPips * pipValue);
2026-03-10 13:47:42 +07:00
2026-03-10 20:39:33 +07:00
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
2026-03-10 13:47:42 +07:00
if (lotStep <= 0) lotStep = 0.01;
rawLot = MathFloor(rawLot / lotStep) * lotStep;
rawLot = MathMax(minLot, MathMin(maxLot, rawLot));
return NormalizeDouble(rawLot, 2);
}
2026-03-10 20:39:33 +07:00
/** Fills g_OrderPlan from FVG id, direction, MSS entry and SL; returns true if valid. */
2026-03-10 13:47:42 +07:00
inline bool BuildOrderPlan(int fvgId, MarketDir dir, double mssEntry, double mssSL)
{
ZeroMemory(g_OrderPlan);
double entry = NormalizeDouble(mssEntry, _Digits);
2026-03-10 20:39:33 +07:00
double sl = mssSL;
2026-03-10 13:47:42 +07:00
if (entry <= 0 || sl <= 0) return false;
double tp;
if (dir == DIR_UP)
{
if (sl >= entry) return false;
sl = NormalizeDouble(sl - 2 * _Point, _Digits);
double riskDist = entry - sl;
tp = NormalizeDouble(entry + InpRiskReward * riskDist, _Digits);
}
else
{
if (sl <= entry) return false;
sl = NormalizeDouble(sl + 2 * _Point, _Digits);
double riskDist = sl - entry;
tp = NormalizeDouble(entry - InpRiskReward * riskDist, _Digits);
}
double lot = CalcLotFromRisk(entry, sl);
if (lot <= 0) return false;
g_OrderPlan.valid = true;
g_OrderPlan.direction = (dir == DIR_UP) ? 1 : -1;
g_OrderPlan.entry = entry;
g_OrderPlan.stopLoss = sl;
g_OrderPlan.takeProfit = tp;
g_OrderPlan.lot = lot;
g_OrderPlan.parentFVGId = fvgId;
if (InpDebugLog)
PrintFormat("[ORDER PLAN] %s | entry=%.5f SL=%.5f TP=%.5f lot=%.2f | FVG#%d",
(dir == DIR_UP) ? "BUY LIMIT" : "SELL LIMIT",
entry, sl, tp, lot, fvgId);
return true;
}
2026-03-10 20:39:33 +07:00
/** Sends limit order from g_OrderPlan; returns ticket or 0 on failure. */
2026-03-10 13:47:42 +07:00
inline ulong ExecuteLimitOrder()
{
if (!g_OrderPlan.valid) return 0;
2026-03-10 20:39:33 +07:00
ENUM_ORDER_TYPE orderType = (g_OrderPlan.direction > 0)
? ORDER_TYPE_BUY_LIMIT
: ORDER_TYPE_SELL_LIMIT;
2026-03-10 13:47:42 +07:00
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
2026-03-10 20:39:33 +07:00
if (orderType == ORDER_TYPE_BUY_LIMIT && g_OrderPlan.entry >= ask)
2026-03-10 13:47:42 +07:00
{
if (InpDebugLog) PrintFormat("[ORDER] BUY LIMIT entry %.5f >= ask %.5f → skip",
g_OrderPlan.entry, ask);
return 0;
}
2026-03-10 20:39:33 +07:00
if (orderType == ORDER_TYPE_SELL_LIMIT && g_OrderPlan.entry <= bid)
2026-03-10 13:47:42 +07:00
{
if (InpDebugLog) PrintFormat("[ORDER] SELL LIMIT entry %.5f <= bid %.5f → skip",
g_OrderPlan.entry, bid);
return 0;
}
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_PENDING;
request.symbol = _Symbol;
request.volume = g_OrderPlan.lot;
2026-03-10 20:39:33 +07:00
request.type = orderType;
2026-03-10 13:47:42 +07:00
request.price = g_OrderPlan.entry;
request.sl = g_OrderPlan.stopLoss;
request.tp = g_OrderPlan.takeProfit;
request.deviation = (ulong)InpSlippage;
request.magic = InpMagicNumber;
request.comment = StringFormat("ICT#%d", g_OrderPlan.parentFVGId);
request.type_filling = ORDER_FILLING_RETURN;
request.type_time = ORDER_TIME_GTC;
if (!OrderSend(request, result))
{
PrintFormat("[ORDER] ❌ retcode=%u", result.retcode);
return 0;
}
if (result.retcode != TRADE_RETCODE_DONE && result.retcode != TRADE_RETCODE_PLACED)
{
PrintFormat("[ORDER] ❌ rejected retcode=%u: %s", result.retcode, result.comment);
return 0;
}
PrintFormat("[ORDER] ✅ %s #%llu | %.2f @ %.5f SL=%.5f TP=%.5f",
2026-03-10 20:39:33 +07:00
(orderType == ORDER_TYPE_BUY_LIMIT) ? "BUY_LIM" : "SELL_LIM",
2026-03-10 13:47:42 +07:00
result.order, g_OrderPlan.lot, g_OrderPlan.entry,
g_OrderPlan.stopLoss, g_OrderPlan.takeProfit);
return result.order;
}
2026-03-10 23:43:41 +07:00
/** 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;
}
}
2026-03-10 20:39:33 +07:00
#endif