Files
Bell-PriceActionWithEma-EA/Experts/EA_ICT_CL/Trade.mqh
T
2026-03-10 23:43:41 +07:00

194 lines
6.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef EA_ICT_CL__TRADE_MQH
#define EA_ICT_CL__TRADE_MQH
/** Calculates lot size from account risk percent and entrySL distance. */
inline double CalcLotFromRisk(double entry, double sl)
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskMoney = balance * InpRiskPercent / 100.0;
double slPips = MathAbs(entry - sl) / _Point;
if (slPips < 1) return 0;
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if (tickValue <= 0 || tickSize <= 0) return 0;
double pipValue = tickValue * (_Point / tickSize);
double rawLot = riskMoney / (slPips * pipValue);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if (lotStep <= 0) lotStep = 0.01;
rawLot = MathFloor(rawLot / lotStep) * lotStep;
rawLot = MathMax(minLot, MathMin(maxLot, rawLot));
return NormalizeDouble(rawLot, 2);
}
/** Fills g_OrderPlan from FVG id, direction, MSS entry and SL; returns true if valid. */
inline bool BuildOrderPlan(int fvgId, MarketDir dir, double mssEntry, double mssSL)
{
ZeroMemory(g_OrderPlan);
double entry = NormalizeDouble(mssEntry, _Digits);
double sl = mssSL;
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;
}
/** Sends limit order from g_OrderPlan; returns ticket or 0 on failure. */
inline ulong ExecuteLimitOrder()
{
if (!g_OrderPlan.valid) return 0;
ENUM_ORDER_TYPE orderType = (g_OrderPlan.direction > 0)
? ORDER_TYPE_BUY_LIMIT
: ORDER_TYPE_SELL_LIMIT;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if (orderType == ORDER_TYPE_BUY_LIMIT && g_OrderPlan.entry >= ask)
{
if (InpDebugLog) PrintFormat("[ORDER] BUY LIMIT entry %.5f >= ask %.5f → skip",
g_OrderPlan.entry, ask);
return 0;
}
if (orderType == ORDER_TYPE_SELL_LIMIT && g_OrderPlan.entry <= bid)
{
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;
request.type = orderType;
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",
(orderType == ORDER_TYPE_BUY_LIMIT) ? "BUY_LIM" : "SELL_LIM",
result.order, g_OrderPlan.lot, g_OrderPlan.entry,
g_OrderPlan.stopLoss, g_OrderPlan.takeProfit);
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