Files
Bell-PriceActionWithEma-EA/Experts/EA_ICT.mq5
T

371 lines
13 KiB
Plaintext

//+------------------------------------------------------------------+
//| EA ICT-style: Daily Bias -> MidTf FVG -> LowTf pullback entries |
//+------------------------------------------------------------------+
#property strict
#include <Trade/Trade.mqh>
CTrade trade;
//--- Inputs
input double RiskPerTrade = 1.0; // % equity risk per trade
input int MagicNumber = 33333;
input int Mid_TF_FVG_lookback = 200; // bars to scan for MidTf FVG
input int Low_TF_FVG_lookback = 200; // bars to scan for LowTf FVG/MSS
input double MinADX = 10.0;
input int ADXPeriod = 14;
input double MaxAcceptableSpread = 200; // points
input double RR = 3.0; // Risk:Reward multiplier (TP = RR * risk distance)
//--- Timeframe configuration (configurable)
input ENUM_TIMEFRAMES TF_High = PERIOD_D1; // Higher timeframe used for bias
input ENUM_TIMEFRAMES TF_Mid = PERIOD_H1; // Mid timeframe for FVG
input ENUM_TIMEFRAMES TF_Low = PERIOD_M5; // Low timeframe for entries
//--- Internal structs
struct Zone { double top; double bottom; int from_index; int to_index; };
enum DailyBias { BIAS_UNKNOWN=0, BIAS_UP=1, BIAS_DOWN=-1 };
//--- Forward
DailyBias DetermineDailyBias();
int FindMidTfFvg(DailyBias bias, Zone &foundZone);
int FindLowTfFvg(Zone &midTfZone, DailyBias bias, Zone &lowTfZone);
bool DetectLowTfMSS(int &mssType, double &mssPrice);
double CalculateLotForRisk(double entryPrice, double stopPrice);
bool HasActiveOrders();
void PlaceLimitOrder(int side, double price, double sl, double tp, double lot);
//+------------------------------------------------------------------+
int OnInit()
{
PrintFormat("EA init: TF_High=%d TF_Mid=%d TF_Low=%d", (int)TF_High, (int)TF_Mid, (int)TF_Low);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnTick()
{
static datetime lastBarTime=0;
datetime t = iTime(_Symbol, TF_Low, 0);
if(t==lastBarTime) return;
lastBarTime = t;
// basic symbol params check
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(point <= 0) { Print("Invalid SYMBOL_POINT -> abort tick"); return; }
// spread check
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
double spreadPts = (ask - bid)/point;
if(spreadPts > MaxAcceptableSpread) { PrintFormat("Spread too high: %.1f pts", spreadPts); return; }
// 1) bias on TF_High
DailyBias bias = DetermineDailyBias();
if(bias==BIAS_UNKNOWN) { Print("Bias unknown -> skip"); return; }
PrintFormat("Bias = %d", bias);
// 2) Mid TF FVG
Zone midZone;
if(FindMidTfFvg(bias, midZone) == 0) { Print("No MidTf FVG -> skip"); return; }
PrintFormat("Mid zone: top=%.5f bottom=%.5f", midZone.top, midZone.bottom);
// 3) Low TF FVG inside mid zone
Zone lowZone;
if(FindLowTfFvg(midZone, bias, lowZone) == 0) { Print("No LowTf FVG -> skip"); return; }
PrintFormat("Low zone: top=%.5f bottom=%.5f", lowZone.top, lowZone.bottom);
// 4) MSS detect on low TF
int mssType=0; double mssPrice=0;
if(!DetectLowTfMSS(mssType, mssPrice)) { Print("No LowTf MSS -> skip"); return; }
PrintFormat("LowTf MSS type=%d price=%.5f", mssType, mssPrice);
// 5) Entry at lowZone.bottom (buy) or top (sell)
double entry = (bias==BIAS_UP) ? lowZone.bottom : lowZone.top;
double sl=0, tp=0;
// compute swing extreme on TF_Low with validation
if(bias==BIAS_UP)
{
double swingLow = DBL_MAX;
for(int i=0;i<Low_TF_FVG_lookback;i++)
{
double lv = iLow(_Symbol, TF_Low, i);
if(lv <= 0) continue; // skip invalid bars
if(lv < swingLow) swingLow = lv;
}
if(swingLow==DBL_MAX) { Print("Not enough low TF bars -> skip"); return; }
sl = swingLow - 5*point;
if(sl >= entry) { Print("SL >= entry -> skip"); return; }
tp = entry + RR * (entry - sl);
}
else
{
double swingHigh = -DBL_MAX;
for(int i=0;i<Low_TF_FVG_lookback;i++)
{
double hv = iHigh(_Symbol, TF_Low, i);
if(hv <= 0) continue;
if(hv > swingHigh) swingHigh = hv;
}
if(swingHigh==-DBL_MAX) { Print("Not enough low TF bars -> skip"); return; }
sl = swingHigh + 5*point;
if(sl <= entry) { Print("SL <= entry -> skip"); return; }
tp = entry - RR * (sl - entry);
}
// 6) lot calc
double lot = CalculateLotForRisk(entry, sl);
if(lot <= 0) { Print("Lot <= 0 -> skip"); return; }
// 7) only one active for EA on symbol
int side = (bias==BIAS_UP) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
if(HasActiveOrders()) { Print("Active order exists -> skip"); return; }
PlaceLimitOrder(side, entry, sl, tp, lot);
}
//+------------------------------------------------------------------+
DailyBias DetermineDailyBias()
{
int idx1=1, idx2=2, tries=0;
while(tries<5)
{
double close1 = iClose(_Symbol, TF_High, idx1);
double high2 = iHigh (_Symbol, TF_High, idx2);
double low2 = iLow (_Symbol, TF_High, idx2);
if(close1 > high2) return BIAS_UP;
if(close1 < low2) return BIAS_DOWN;
idx1++; idx2++; tries++;
if(idx2>500) break;
}
return BIAS_UNKNOWN;
}
//+------------------------------------------------------------------+
int FindMidTfFvg(DailyBias bias, Zone &foundZone)
{
int limit = Mid_TF_FVG_lookback;
for(int i=1;i<limit-2;i++)
{
double high_i = iHigh(_Symbol, TF_Mid, i);
double low_i = iLow (_Symbol, TF_Mid, i);
double high_i2 = iHigh(_Symbol, TF_Mid, i+2);
double low_i2 = iLow (_Symbol, TF_Mid, i+2);
if(high_i<=0 || low_i<=0 || high_i2<=0 || low_i2<=0) continue;
if(bias==BIAS_UP)
{
if(low_i > high_i2)
{
foundZone.top = low_i; foundZone.bottom = high_i2;
foundZone.from_index = i+2; foundZone.to_index = i;
return 1;
}
}
else if(bias==BIAS_DOWN)
{
if(high_i < low_i2)
{
double t = MathMax(high_i, low_i2);
double b = MathMin(high_i, low_i2);
foundZone.top = t; foundZone.bottom = b;
foundZone.from_index = i+2; foundZone.to_index = i;
return 1;
}
}
}
return 0;
}
//+------------------------------------------------------------------+
int FindLowTfFvg(Zone &midTfZone, DailyBias bias, Zone &lowTfZone)
{
int limit = Low_TF_FVG_lookback;
for(int i=1;i<limit-2;i++)
{
double high_i = iHigh(_Symbol, TF_Low, i);
double low_i = iLow (_Symbol, TF_Low, i);
double high_i2 = iHigh(_Symbol, TF_Low, i+2);
double low_i2 = iLow (_Symbol, TF_Low, i+2);
if(high_i<=0 || low_i<=0 || high_i2<=0 || low_i2<=0) continue;
if(bias==BIAS_UP)
{
if(low_i > high_i2)
{
double top = low_i; double bottom = high_i2;
if(bottom <= midTfZone.top && top >= midTfZone.bottom)
{
lowTfZone.top = top; lowTfZone.bottom = bottom; return 1;
}
}
}
else
{
if(high_i < low_i2)
{
double top = low_i2; double bottom = high_i;
if(bottom <= midTfZone.top && top >= midTfZone.bottom)
{
lowTfZone.top = top; lowTfZone.bottom = bottom; return 1;
}
}
}
}
return 0;
}
//+------------------------------------------------------------------+
bool DetectLowTfMSS(int &mssType, double &mssPrice)
{
double lastSwingHigh = -DBL_MAX; int idxHigh=-1;
double lastSwingLow = DBL_MAX; int idxLow=-1;
int look = 50;
for(int i=2;i<look;i++)
{
double h = iHigh(_Symbol, TF_Low, i);
double l = iLow (_Symbol, TF_Low, i);
if(h>0 && h > lastSwingHigh) { lastSwingHigh=h; idxHigh=i; }
if(l>0 && l < lastSwingLow) { lastSwingLow=l; idxLow=i; }
}
double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
if(ask > lastSwingHigh) { mssType=1; mssPrice = lastSwingHigh; return true; }
if(bid < lastSwingLow) { mssType=-1; mssPrice = lastSwingLow; return true; }
return false;
}
//+------------------------------------------------------------------+
double CalculateLotForRisk(double entryPrice, double stopPrice)
{
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskMoney = equity * (RiskPerTrade/100.0);
double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT);
double tickValue = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
// Fallback: if broker returns 0 for tickValue/tickSize, estimate valuePerPoint using contract size:
double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
if(tickValue<=0 || tickSize<=0)
{
// rough fallback: assume 1 lot -> contractSize * point movement value (this might be instrument-specific)
tickValue = contractSize; tickSize = 1.0;
}
if(point<=0 || tickValue<=0 || tickSize<=0) { Print("Invalid symbol params for lot calc"); return 0; }
double stopPoints = MathAbs(entryPrice - stopPrice)/point;
if(stopPoints <= 0) return 0;
double valuePerPoint = tickValue * (point / tickSize);
if(valuePerPoint <= 0) { Print("valuePerPoint invalid"); return 0; }
double rawLot = riskMoney / (stopPoints * valuePerPoint);
double minLot = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
double lotStep= SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
double maxLot = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
if(lotStep<=0) lotStep=0.01;
double n = MathFloor(rawLot / lotStep);
double lot = n * lotStep;
if(lot < minLot) lot = minLot;
if(lot > maxLot) lot = maxLot;
lot = NormalizeDouble(lot,2);
PrintFormat("CalcLot: entry=%.5f stop=%.5f stopPts=%.1f rawLot=%.4f finalLot=%.2f", entryPrice, stopPrice, stopPoints, rawLot, lot);
return lot;
}
//+------------------------------------------------------------------+
bool HasActiveOrders()
{
// check open positions
for(int i=0;i<PositionsTotal();i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
long magic = (long)PositionGetInteger(POSITION_MAGIC);
string sym = PositionGetString(POSITION_SYMBOL);
if(magic==MagicNumber && sym==_Symbol) return true;
}
}
// check pending orders
for(int i=0;i<OrdersTotal();i++)
{
ulong ticket = OrderGetTicket(i);
if(OrderSelect(ticket))
{
long magic = (long)OrderGetInteger(ORDER_MAGIC);
string sym = OrderGetString(ORDER_SYMBOL);
if(magic==MagicNumber && sym==_Symbol) return true;
}
}
return false;
}
//+------------------------------------------------------------------+
void PlaceLimitOrder(int side, double price, double sl, double tp, double lot)
{
MqlTradeRequest request; MqlTradeResult result;
ZeroMemory(request); ZeroMemory(result);
request.action = TRADE_ACTION_PENDING;
request.symbol = _Symbol;
request.volume = lot;
request.price = price;
request.sl = sl;
request.tp = tp;
request.deviation = 20;
request.magic = MagicNumber;
request.comment = (side==POSITION_TYPE_BUY) ? "ICT_BUY_LIMIT" : "ICT_SELL_LIMIT";
request.type_time = ORDER_TIME_GTC;
request.type_filling = ORDER_FILLING_RETURN;
request.type = (side==POSITION_TYPE_BUY) ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_SELL_LIMIT;
if(!OrderSend(request,result))
{
PrintFormat("OrderSend() failed: retcode=%d comment=%s", result.retcode, result.comment);
return;
}
if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED)
{
ulong ticket = result.order;
PrintFormat("Placed pending order ticket=%I64u side=%d price=%.5f lot=%.2f SL=%.5f TP=%.5f",
ticket, side, price, lot, sl, tp);
// draw lines
string n_sl = StringFormat("ICT_SL_%I64u", ticket);
string n_tp = StringFormat("ICT_TP_%I64u", ticket);
string n_en = StringFormat("ICT_ENTRY_%I64u", ticket);
if(ObjectFind(0,n_sl) == -1) ObjectCreate(0,n_sl,OBJ_HLINE,0,0,sl);
if(ObjectFind(0,n_tp) == -1) ObjectCreate(0,n_tp,OBJ_HLINE,0,0,tp);
if(ObjectFind(0,n_en) == -1) ObjectCreate(0,n_en,OBJ_HLINE,0,0,price);
ObjectSetInteger(0,n_sl,OBJPROP_COLOR,clrRed);
ObjectSetInteger(0,n_tp,OBJPROP_COLOR,clrLime);
ObjectSetInteger(0,n_en,OBJPROP_COLOR,clrYellow);
ObjectSetString(0,n_sl,OBJPROP_TEXT,"SL: "+DoubleToString(sl,(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)));
}
else
{
PrintFormat("OrderSend returned retcode=%d comment=%s", result.retcode, result.comment);
}
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
int total = ObjectsTotal(0);
for(int i=total-1;i>=0;i--)
{
string name = ObjectName(0,i);
if(StringFind(name,"ICT_ENTRY_")==0 || StringFind(name,"ICT_SL_")==0 || StringFind(name,"ICT_TP_")==0)
ObjectDelete(0,name);
}
Print("EA deinitialized - cleaned objects.");
}
//+------------------------------------------------------------------+