mql5/Experts/nik2.mq5
328 lines
No EOL
12 KiB
MQL5
#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);
}
}
}
}
}
}