#property copyright "Copyright 2026, Shahrukh" #property link "https://mql5.io" #property version "1.17" #property description "Closed candle fair value gap scanner EA. Implemented requested loop logic." #include CTrade trade; input group "Gap Detection" input int InpMaximumBars = 400; // Bars to scan input double InpMinimumGapPoints = 20.0; // Minimum gap in points input bool InpShowBullishGaps = true; input bool InpShowBearishGaps = true; input bool InpTargetMitigated = false; // Hide mitigated gaps input bool InpUseFullFill = true; // Full fill or first touch input group "Display" input int InpExtensionBars = 80; // Время жизни прямоугольника и ордера (в свечах) input color InpBullishGapColor = clrGreen; // Яркий цвет для контура input color InpBearishGapColor = clrRed; // Яркий цвет для контура input color InpMitigatedColor = clrSilver; // Цвет для смягченных зон input bool InpShowLabels = true; input group "Alerts" input bool InpEnableAlerts = false; input group "Trading Settings" input bool InpEnableTrading = true; // Разрешить автоторговлю input double InpLotSize = 0.1; // Объем торгового лота input ulong InpMagicNumber = 123456; // Уникальный ID робота input group "Risk Management" input int InpStopLossPoints = 150; // Первоначальный Stop Loss в пунктах (0 - отключен) input int InpTakeProfitPoints = 300; // Первоначальный Take Profit в пунктах (0 - отключен) input group "Trailing Settings" input int InpTrailingStartPoints = 100; // Прибыль в пунктах для старта трейлинга input int InpTrailingStopPoints = 150; // Дистанция трейлинг-стопа в пунктах (0 - отключен) string g_prefix="FVGSCAN_"; datetime g_last_bar=0; datetime g_last_alert_bar=0; //+------------------------------------------------------------------+ int OnInit() { if(InpMaximumBars<20 || InpMinimumGapPoints<0.0 || InpExtensionBars<1) { Print("Invalid scanner settings."); return(INIT_PARAMETERS_INCORRECT); } trade.SetExpertMagicNumber(InpMagicNumber); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0,g_prefix); ChartRedraw(0); } //+------------------------------------------------------------------+ void OnTick() { // 1. УПРАВЛЕНИЕ РИСКАМИ И ТРЕЙЛИНГ НА КАЖДОМ ТИКЕ ПО ВАШЕЙ ЛОГИКЕ if(InpEnableTrading) { ApplyRequestedLoopLogic(); } // 2. СКАНИРОВАНИЕ И ОБНОВЛЕНИЕ ГЭПОВ — СТРОГО ПО ОТКРЫТИЮ СВЕЧИ datetime time[]; if(CopyTime(_Symbol, _Period, 0, 1, time) > 0) { if(time[0] != g_last_bar) { g_last_bar = time[0]; double high[], low[]; int to_copy = InpMaximumBars + 5; if(CopyTime(_Symbol, _Period, 0, to_copy, time) > 0 && CopyHigh(_Symbol, _Period, 0, to_copy, high) > 0 && CopyLow(_Symbol, _Period, 0, to_copy, low) > 0) { int rates_total = ArraySize(time); if(rates_total >= 10) { ArraySetAsSeries(time, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); BuildGaps(rates_total, time, high, low); } } } } } //+------------------------------------------------------------------+ bool IsBullishMitigated(const int formation_index,const double gap_bottom, const double gap_top,const double &low[]) { const double threshold=(InpUseFullFill ? gap_bottom : gap_top); for(int index=formation_index-1; index>=0; index--) if(low[index]<=threshold) return(true); return(false); } //+------------------------------------------------------------------+ bool IsBearishMitigated(const int formation_index,const double gap_bottom, const double gap_top,const double &high[]) { const double threshold=(InpUseFullFill ? gap_top : gap_bottom); for(int index=formation_index-1; index>=0; index--) if(high[index]>=threshold) return(true); return(false); } //+------------------------------------------------------------------+ void DrawGap(const string id,const datetime start_time,const datetime end_time, const double top,const double bottom,const bool bullish, const bool mitigated) { const string rectangle_name=g_prefix+"BOX_"+id; color base_color=(mitigated ? InpMitigatedColor : (bullish ? InpBullishGapColor : InpBearishGapColor)); if(!ObjectCreate(0,rectangle_name,OBJ_RECTANGLE,0,start_time,top,end_time,bottom)) return; ObjectSetInteger(0,rectangle_name,OBJPROP_COLOR,base_color); ObjectSetInteger(0,rectangle_name,OBJPROP_STYLE,STYLE_DOT); ObjectSetInteger(0,rectangle_name,OBJPROP_WIDTH,1); ObjectSetInteger(0,rectangle_name,OBJPROP_FILL,false); ObjectSetInteger(0,rectangle_name,OBJPROP_BACK,true); ObjectSetInteger(0,rectangle_name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,rectangle_name,OBJPROP_HIDDEN,true); if(!InpShowLabels) return; const string label_name=g_prefix+"TEXT_"+id; const double label_price=(top+bottom)*0.5; if(ObjectCreate(0,label_name,OBJ_TEXT,0,start_time,label_price)) { string label=(bullish ? "Bullish FVG" : "Bearish FVG"); if(mitigated) label+=" filled"; ObjectSetString(0,label_name,OBJPROP_TEXT,label); ObjectSetString(0,label_name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,label_name,OBJPROP_FONTSIZE,8); ObjectSetInteger(0,label_name,OBJPROP_COLOR,mitigated ? clrGray : (bullish ? clrForestGreen : clrFireBrick)); ObjectSetInteger(0,label_name,OBJPROP_ANCHOR,ANCHOR_LEFT); ObjectSetInteger(0,label_name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,label_name,OBJPROP_HIDDEN,true); } } //+------------------------------------------------------------------+ void BuildGaps(const int rates_total,const datetime &time[], const double &high[],const double &low[]) { ObjectsDeleteAll(0,g_prefix); const int seconds=PeriodSeconds(_Period); const int bar_seconds=(seconds>0 ? seconds : 60); const int oldest=MathMin(InpMaximumBars,rates_total-3); bool new_bullish=false,new_bearish=false; for(int index=oldest; index>=1; index--) { const double bullish_size=low[index]-high[index+2]; const double bearish_size=low[index+2]-high[index]; const datetime end_time=time[index]+(datetime)(bar_seconds*InpExtensionBars); const string id=IntegerToString((int)time[index]); if(InpShowBullishGaps && bullish_size>=InpMinimumGapPoints*_Point) { const double top=low[index]; const double bottom=high[index+2]; const bool mitigated=IsBullishMitigated(index,bottom,top,low); if(!mitigated || !InpTargetMitigated) DrawGap(id+"_B",time[index+2],end_time,top,bottom,true,mitigated); if(!mitigated && InpEnableTrading) { if(index==1) new_bullish=true; ManageLimitOrder(ORDER_TYPE_BUY_LIMIT, top, end_time, id+"_B"); } } if(InpShowBearishGaps && bearish_size>=InpMinimumGapPoints*_Point) { const double top=low[index+2]; const double bottom=high[index]; const bool mitigated=IsBearishMitigated(index,bottom,top,high); if(!mitigated || !InpTargetMitigated) DrawGap(id+"_S",time[index+2],end_time,top,bottom,false,mitigated); if(!mitigated && InpEnableTrading) { if(index==1) new_bearish=true; ManageLimitOrder(ORDER_TYPE_SELL_LIMIT, bottom, end_time, id+"_S"); } } } if(InpEnableAlerts && time[1]!=g_last_alert_bar) { if(new_bullish) Alert(_Symbol," ",EnumToString(_Period)," new bullish fair value gap"); if(new_bearish) Alert(_Symbol," ",EnumToString(_Period)," new bearish fair value gap"); if(new_bullish || new_bearish) g_last_alert_bar=time[1]; } ChartRedraw(0); } //+------------------------------------------------------------------+ void ManageLimitOrder(ENUM_ORDER_TYPE order_type, double price, datetime expiration, string comment) { for(int i=OrdersTotal()-1; i>=0; i--) { if(OrderGetTicket(i) > 0) { if(OrderGetString(ORDER_COMMENT) == comment && OrderGetInteger(ORDER_MAGIC) == InpMagicNumber) return; } } for(int i=PositionsTotal()-1; i>=0; i--) { if(PositionGetSymbol(i) == _Symbol) { if(PositionGetString(POSITION_COMMENT) == comment && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) return; } } price = NormalizeDouble(price, _Digits); trade.SetTypeFilling(ORDER_FILLING_FOK); // Изменен вызов: теперь лимитный ордер отправляется БЕЗ sl и tp (передаем 0.0, 0.0) trade.OrderOpen(_Symbol, order_type, InpLotSize, 0.0, price, 0.0, 0.0, ORDER_TIME_SPECIFIED, expiration, comment); } //+------------------------------------------------------------------+ //| Интегрированная логика перехвата рисков и трейлинга по ТЗ | //+------------------------------------------------------------------+ void ApplyRequestedLoopLogic() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; // Дополнительный фильтр по MagicNumber, чтобы не трогать чужие сделки if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; double current_sl = PositionGetDouble(POSITION_SL); double current_tp = PositionGetDouble(POSITION_TP); double price_open = PositionGetDouble(POSITION_PRICE_OPEN); ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //=================================================== // Установка первоначального SL и TP //=================================================== if(current_sl == 0 && current_tp == 0) { double initial_sl = 0; double initial_tp = 0; if(pos_type == POSITION_TYPE_BUY) { if(InpStopLossPoints > 0) initial_sl = NormalizeDouble(price_open - InpStopLossPoints * _Point, _Digits); if(InpTakeProfitPoints > 0) initial_tp = NormalizeDouble(price_open + InpTakeProfitPoints * _Point, _Digits); } else { if(InpStopLossPoints > 0) initial_sl = NormalizeDouble(price_open + InpStopLossPoints * _Point, _Digits); if(InpTakeProfitPoints > 0) initial_tp = NormalizeDouble(price_open - InpTakeProfitPoints * _Point, _Digits); } trade.PositionModify(ticket, initial_sl, initial_tp); current_sl = initial_sl; current_tp = initial_tp; } //================================================= if(InpTrailingStopPoints > 0) { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(pos_type == POSITION_TYPE_BUY) { double profit_points = (bid - price_open) / _Point; if(profit_points >= InpTrailingStartPoints) { double new_sl = NormalizeDouble(bid - InpTrailingStopPoints * _Point, _Digits); if(new_sl > current_sl) { trade.PositionModify(ticket, new_sl, current_tp); } } } else if(pos_type == POSITION_TYPE_SELL) { double profit_points = (price_open - ask) / _Point; if(profit_points >= InpTrailingStartPoints) { double new_sl = NormalizeDouble(ask + InpTrailingStopPoints * _Point, _Digits); if(current_sl == 0 || new_sl < current_sl) { trade.PositionModify(ticket, new_sl, current_tp); } } } } } }