194 lines
6.4 KiB
Plaintext
194 lines
6.4 KiB
Plaintext
#ifndef EA_ICT_CL__TRADE_MQH
|
||
#define EA_ICT_CL__TRADE_MQH
|
||
|
||
/** Calculates lot size from account risk percent and entry–SL 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
|