| #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 <Trade\Trade.mqh>
|
| 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);
|
| }
|
| }
|
| }
|
| }
|
| }
|
| } |