2026-10-18 09:17:39 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| FVG Grid.mq5 |
|
| | | //| Copyright 2026, Shahrukh & AI |
|
| | | //| https://mql5.io |
|
| | | //+------------------------------------------------------------------+
|
2026-10-17 15:09:28 +03:00 | | | #property copyright "Copyright 2026, Shahrukh & AI"
|
| | | #property link "https://mql5.io"
|
2026-10-18 11:44:32 +03:00 | | | #property version "5.20"
|
| | | #property description "FVG Grid: Уникальные короткие комментарии + Изолированный SL и Трейлинг"
|
2026-10-17 14:35:25 +03:00 | | |
|
| | | #include <Trade\Trade.mqh>
|
| | | CTrade trade;
|
| | | input group "--- Риск и ММ ---"
|
2026-10-17 15:09:28 +03:00 | | | input double InpLotSize = 0.01; // Размер лота для каждого ордера
|
2026-10-18 09:17:39 +03:00 | | | input int InpStopLoss = 150; // Стоп-Лосс в пунктах от границы гэпа (0 - отключен)
|
2026-10-17 15:09:28 +03:00 | | | input int InpTakeProfit = 300; // Тейк-Профит (в пунктах, 0 - отключен)
|
2026-10-17 14:35:25 +03:00 | | | input ulong InpMagicNumber = 123456; // Мэджик номер советника
|
2026-10-18 09:17:39 +03:00 | | | input int InpMaxSpreadPoints = 35; // Максимальный спред в пунктах для входа (0 - отключен)
|
2026-10-17 14:35:25 +03:00 | | |
|
| | | input group "--- Детектор FVG ---"
|
| | | input int InpMaximumBars = 400; // Сколько бар сканировать для отрисовки
|
| | | input double InpMinimumGapPoints = 20.0; // Минимальный размер гэпа в пунктах
|
2026-10-18 09:17:39 +03:00 | | | input bool InpHideMitigated = true; // Скрывать протестированные гэпы
|
2026-10-17 15:09:28 +03:00 | | | input group "--- Настройки Сетки Ордеров ---"
|
2026-10-18 09:17:39 +03:00 | | | input double InpOrderOffsetPoints = 10.0; // Смещение уровней ордеров в пунктах
|
2026-10-17 14:35:25 +03:00 | | | input group "--- Визуализация (Пунктир) ---"
|
| | | input int InpExtensionBars = 80; // Длина прямоугольника в барах
|
| | | input color InpBullishGapColor = clrGreen; // Яркий цвет контура для Buy
|
| | | input color InpBearishGapColor = clrRed; // Яркий цвет контура для Sell
|
| | | input color InpMitigatedColor = clrSilver; // Цвет для смягченных зон
|
| | | input bool InpShowLabels = true; // Показывать текст
|
2026-10-18 09:17:39 +03:00 | | | input group "--- Индивидуальный Трейлинг-Стоп ---"
|
| | | input bool InpUseTrailing = true; // Включить трейлинг?
|
| | | input int InpTrailingStart = 150; // Активация трейлинга (в пунктах от безубытка гэпа)
|
2026-10-17 14:35:25 +03:00 | | | input int InpTrailingDist = 50; // Расстояние от текущей цены (в пунктах)
|
| | | input int InpTrailingStep = 10; // Шаг изменения стоп-лосса (в пунктах)
|
2026-10-18 11:44:32 +03:00 | | | // Глобальные переменные
|
2026-10-17 15:09:28 +03:00 | | | string g_prefix = "FVGSCAN_";
|
2026-10-17 14:35:25 +03:00 | | | datetime g_last_bar = 0;
|
2026-10-17 15:09:28 +03:00 | | | bool g_first_run = true;
|
2026-10-18 09:17:39 +03:00 | | | string g_file_name;
|
2026-10-17 15:09:28 +03:00 | | |
|
2026-10-18 09:17:39 +03:00 | | | // База данных отторгованных FVG в ОЗУ
|
2026-10-17 15:17:52 +03:00 | | | string g_traded_gaps[];
|
| | |
|
2026-10-17 15:09:28 +03:00 | | | // Предварительное объявление функций
|
2026-10-17 15:17:52 +03:00 | | | void BuildGapsAndTrade(const int rates_copied, const datetime &time[], const double &high[], const double &low[]);
|
2026-10-17 15:09:28 +03:00 | | | void CheckAndPlaceFvgGrid(string gap_id, double top, double bottom, bool is_bullish);
|
| | | bool IsGridAlreadyPlaced(string gap_id);
|
2026-10-17 15:17:52 +03:00 | | | bool WasGapTraded(string gap_id);
|
| | | void MarkGapAsTraded(string gap_id);
|
2026-10-18 09:17:39 +03:00 | | | void ApplyIsolatedTrailing();
|
| | | void ModifyIsolatedGridSL(string target_comment, long position_type, double new_sl);
|
2026-10-18 11:44:32 +03:00 | | | void CancelLimitOrdersOnPositionClose();
|
2026-10-17 15:17:52 +03:00 | | | bool IsBullishMitigated(const int formation_index, const double gap_bottom, const double gap_top, const double &low[]);
|
| | | bool IsBearishMitigated(const int formation_index, const double gap_bottom, const double gap_top, const double &high[]);
|
| | | 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);
|
2026-10-18 09:17:39 +03:00 | | | void LoadGapsFromFile();
|
| | | void SaveGapToFile(string gap_id);
|
2026-10-18 11:44:32 +03:00 | | | string GetShortUniqueId(datetime bar_time);
|
2026-10-17 14:35:25 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Expert initialization function |
|
| | | //+------------------------------------------------------------------+
|
| | | int OnInit()
|
| | | {
|
| | | if(InpMaximumBars < 20 || InpMinimumGapPoints < 0.0 || InpExtensionBars < 1)
|
| | | {
|
| | | Print("Invalid scanner settings.");
|
| | | return(INIT_PARAMETERS_INCORRECT);
|
| | | }
|
| | |
|
| | | trade.SetExpertMagicNumber(InpMagicNumber);
|
| | | trade.SetMarginMode();
|
| | | g_last_bar = 0;
|
| | | g_first_run = true;
|
2026-10-17 15:17:52 +03:00 | | | ArrayResize(g_traded_gaps, 0);
|
2026-10-18 09:17:39 +03:00 | | |
|
| | | g_file_name = "FVG_Grid_Storage_" + _Symbol + "_" + EnumToString(_Period) + ".txt";
|
| | | LoadGapsFromFile();
|
| | |
|
2026-10-17 14:35:25 +03:00 | | | return(INIT_SUCCEEDED);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Expert deinitialization function |
|
2026-10-22 10:08:40 +03:00 | | |
|
| | |
|
2026-10-17 14:35:25 +03:00 | | | //+------------------------------------------------------------------+
|
| | | void OnDeinit(const int reason)
|
| | | {
|
| | | ObjectsDeleteAll(0, g_prefix);
|
| | | ChartRedraw(0);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
2026-10-17 15:17:52 +03:00 | | | //| Expert tick function |
|
2026-10-17 14:35:25 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-17 15:17:52 +03:00 | | | void OnTick()
|
2026-10-17 14:35:25 +03:00 | | | {
|
2026-10-17 15:17:52 +03:00 | | | datetime current_bar_time = iTime(_Symbol, _Period, 0);
|
| | | if(current_bar_time == 0) return;
|
2026-10-17 14:35:25 +03:00 | | |
|
2026-10-18 09:17:39 +03:00 | | | double high[], low[];
|
| | | datetime time[];
|
| | | ArraySetAsSeries(high, true);
|
| | | ArraySetAsSeries(low, true);
|
| | | ArraySetAsSeries(time, true);
|
| | |
|
| | | int copied_time = CopyTime(_Symbol, _Period, 0, InpMaximumBars, time);
|
| | | int copied_high = CopyHigh(_Symbol, _Period, 0, InpMaximumBars, high);
|
| | | int copied_low = CopyLow(_Symbol, _Period, 0, InpMaximumBars, low);
|
| | |
|
| | | if(copied_time < 10 || copied_high < 10 || copied_low < 10) return;
|
| | | int rates_total = MathMin(copied_time, MathMin(copied_high, copied_low));
|
| | |
|
2026-10-18 11:44:32 +03:00 | | | // 1. Сканирование и выставление новых сеток на открытии бара
|
2026-10-17 15:17:52 +03:00 | | | if(current_bar_time != g_last_bar || g_first_run)
|
| | | {
|
| | | g_last_bar = current_bar_time;
|
| | | BuildGapsAndTrade(rates_total, time, high, low);
|
2026-10-22 10:08:40 +03:00 | | |
|
| | |
|
2026-10-17 15:17:52 +03:00 | | | }
|
| | |
|
2026-10-18 11:44:32 +03:00 | | | // 2. Контроль закрытия рыночных ордеров и очистка оставшихся лимиток текущего гэпа
|
| | | CancelLimitOrdersOnPositionClose();
|
2026-10-18 09:17:39 +03:00 | | |
|
| | | // 3. Индивидуальный трейлинг-стоп отдельно для каждого гэпа
|
2026-10-17 15:17:52 +03:00 | | | if(InpUseTrailing)
|
| | | {
|
2026-10-18 09:17:39 +03:00 | | | ApplyIsolatedTrailing();
|
2026-10-17 15:17:52 +03:00 | | | }
|
2026-10-17 14:35:25 +03:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
2026-10-17 15:17:52 +03:00 | | | //| ОБРАБОТКА ИЗМЕНЕНИЙ ГРАФИКА |
|
2026-10-17 14:35:25 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-17 15:17:52 +03:00 | | | void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
2026-10-17 14:35:25 +03:00 | | | {
|
2026-10-17 15:17:52 +03:00 | | | if(id == CHARTEVENT_CLICK || id == CHARTEVENT_CHART_CHANGE)
|
2026-10-17 14:35:25 +03:00 | | | {
|
2026-10-17 15:17:52 +03:00 | | | double high[], low[];
|
| | | datetime time[];
|
| | | ArraySetAsSeries(high, true);
|
| | | ArraySetAsSeries(low, true);
|
| | | ArraySetAsSeries(time, true);
|
| | |
|
| | | int copied_time = CopyTime(_Symbol, _Period, 0, InpMaximumBars, time);
|
| | | int copied_high = CopyHigh(_Symbol, _Period, 0, InpMaximumBars, high);
|
| | | int copied_low = CopyLow(_Symbol, _Period, 0, InpMaximumBars, low);
|
| | |
|
| | | if(copied_time >= 10 && copied_high >= 10 && copied_low >= 10)
|
| | | {
|
| | | int rates_total = MathMin(copied_time, MathMin(copied_high, copied_low));
|
| | | BuildGapsAndTrade(rates_total, time, high, low);
|
2026-10-22 10:08:40 +03:00 | | |
|
| | |
|
2026-10-17 15:17:52 +03:00 | | | }
|
2026-10-17 14:35:25 +03:00 | | | }
|
| | | }
|
| | |
|
2026-10-18 11:44:32 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Генерация короткого ID для соблюдения лимита в 31 символ коммента|
|
| | | //+------------------------------------------------------------------+
|
| | | string GetShortUniqueId(datetime bar_time)
|
| | | {
|
| | | // Используем последние 6 цифр таймштампа (этого достаточно для уникальности в рамках файла)
|
| | | ulong long_id = (ulong)bar_time;
|
| | | return IntegerToString((int)(long_id % 1000000));
|
| | | }
|
| | |
|
2026-10-17 14:35:25 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-17 15:09:28 +03:00 | | | //| Сканирование истории, построение объектов и выставление ордеров |
|
2026-10-17 14:35:25 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-17 14:39:15 +03:00 | | | void BuildGapsAndTrade(const int rates_copied, const datetime &time[], const double &high[], const double &low[])
|
2026-10-17 14:35:25 +03:00 | | | {
|
| | | ObjectsDeleteAll(0, g_prefix);
|
| | |
|
| | | const int seconds = PeriodSeconds(_Period);
|
| | | const int bar_seconds = (seconds > 0 ? seconds : 60);
|
2026-10-17 14:39:15 +03:00 | | |
|
| | | const int oldest = MathMin(InpMaximumBars, rates_copied - 3);
|
| | | if(oldest < 1) return;
|
2026-10-17 14:35:25 +03:00 | | |
|
| | | 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);
|
2026-10-18 11:44:32 +03:00 | | |
|
| | | // Генерируем компактный ID, который гарантированно не обрежется в комментарии ордера
|
| | | const string short_id = GetShortUniqueId(time[index]);
|
2026-10-17 14:35:25 +03:00 | | |
|
2026-10-17 15:09:28 +03:00 | | | // 1. Проверяем Бычьи гэпы (Buy зоны)
|
2026-10-17 14:35:25 +03:00 | | | if(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 || !InpHideMitigated)
|
2026-10-18 11:44:32 +03:00 | | | DrawGap(short_id + "_B", time[index + 2], end_time, top, bottom, true, mitigated);
|
2026-10-17 14:35:25 +03:00 | | |
|
2026-10-17 15:09:28 +03:00 | | | if(!mitigated)
|
2026-10-18 11:44:32 +03:00 | | | CheckAndPlaceFvgGrid(short_id + "_B", top, bottom, true);
|
2026-10-17 14:35:25 +03:00 | | | }
|
2026-10-17 15:17:52 +03:00 | | |
|
2026-10-17 15:09:28 +03:00 | | | // 2. Проверяем Медвежьи гэпы (Sell зоны)
|
2026-10-17 14:35:25 +03:00 | | | if(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 || !InpHideMitigated)
|
2026-10-18 11:44:32 +03:00 | | | DrawGap(short_id + "_S", time[index + 2], end_time, top, bottom, false, mitigated);
|
2026-10-17 14:35:25 +03:00 | | |
|
2026-10-17 15:09:28 +03:00 | | | if(!mitigated)
|
2026-10-18 11:44:32 +03:00 | | | CheckAndPlaceFvgGrid(short_id + "_S", top, bottom, false);
|
2026-10-17 14:35:25 +03:00 | | | }
|
2026-10-22 10:08:40 +03:00 | | |
|
| | |
|
2026-10-17 14:35:25 +03:00 | | | }
|
| | |
|
2026-10-17 14:39:15 +03:00 | | | if(g_first_run) g_first_run = false;
|
2026-10-17 14:35:25 +03:00 | | | ChartRedraw(0);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
2026-10-18 11:44:32 +03:00 | | | //| Функция выставления 3-х ордеров со СВОИМ индивидуальным Стоп-Лоссом|
|
2026-10-17 14:35:25 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-17 15:09:28 +03:00 | | | void CheckAndPlaceFvgGrid(string gap_id, double top, double bottom, bool is_bullish)
|
2026-10-18 09:25:32 +03:00 | | | {
|
2026-10-18 11:44:32 +03:00 | | | if(InpMaxSpreadPoints > 0)
|
| | | {
|
2026-10-18 09:25:32 +03:00 | | | int current_spread = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
2026-10-18 11:44:32 +03:00 | | | if(current_spread > InpMaxSpreadPoints) return;
|
| | | }
|
2026-10-18 09:25:32 +03:00 | | | if(IsGridAlreadyPlaced(gap_id) || WasGapTraded(gap_id)) return;
|
| | | double center = NormalizeDouble((top + bottom) * 0.5, _Digits);
|
| | | double offset = InpOrderOffsetPoints * _Point;
|
| | | double grid_levels[3];
|
2026-10-18 09:17:39 +03:00 | | | if(is_bullish)
|
| | | {
|
2026-10-18 11:44:32 +03:00 | | | grid_levels[0] = NormalizeDouble(top + offset, _Digits);
|
| | | grid_levels[1] = NormalizeDouble(center + offset, _Digits);
|
| | | grid_levels[2] = NormalizeDouble(bottom + offset, _Digits);
|
2026-10-18 09:17:39 +03:00 | | | }
|
| | | else
|
| | | {
|
| | | grid_levels[0] = NormalizeDouble(top - offset, _Digits);
|
| | | grid_levels[1] = NormalizeDouble(center - offset, _Digits);
|
| | | grid_levels[2] = NormalizeDouble(bottom - offset, _Digits);
|
| | | }
|
| | | double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
| | | double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
2026-10-18 11:44:32 +03:00 | | | // Комментарий теперь короткий и уникальный (Пример: FVG_123456_B)
|
2026-10-18 09:17:39 +03:00 | | | string comment = StringFormat("FVG_%s", gap_id);
|
2026-10-17 19:54:25 +03:00 | | | bool grid_placed = false;
|
2026-10-18 11:44:32 +03:00 | | | // Вычисляем Стоп-Лосс для каждого ордера от дальней границы гэпа
|
2026-10-18 09:17:39 +03:00 | | | double common_sl = 0;
|
| | | if(InpStopLoss > 0)
|
| | | {
|
| | | if(is_bullish)
|
| | | common_sl = NormalizeDouble(bottom - InpStopLoss * _Point, _Digits);
|
| | | else
|
| | | common_sl = NormalizeDouble(top + InpStopLoss * _Point, _Digits);
|
| | | }
|
2026-10-17 19:54:25 +03:00 | | | for(int i = 0; i < 3; i++)
|
| | | {
|
| | | if(is_bullish)
|
| | | {
|
2026-10-18 09:17:39 +03:00 | | | double tp = (InpTakeProfit > 0) ? NormalizeDouble(grid_levels[i] + InpTakeProfit * _Point, _Digits) : 0;
|
| | | if(ask <= grid_levels[i])
|
2026-10-17 15:09:28 +03:00 | | | {
|
2026-10-18 09:17:39 +03:00 | | | if(trade.Buy(InpLotSize, _Symbol, ask, common_sl, tp, comment)) grid_placed = true;
|
2026-10-17 15:09:28 +03:00 | | | }
|
2026-10-17 15:17:52 +03:00 | | | else
|
2026-10-17 15:09:28 +03:00 | | | {
|
2026-10-18 09:17:39 +03:00 | | | if(trade.BuyLimit(InpLotSize, grid_levels[i], _Symbol, common_sl, tp, ORDER_TIME_GTC, 0, comment)) grid_placed = true;
|
2026-10-17 15:09:28 +03:00 | | | }
|
| | | }
|
| | | else // BEARISH FVG (SELL)
|
| | | {
|
2026-10-18 09:17:39 +03:00 | | | double tp = (InpTakeProfit > 0) ? NormalizeDouble(grid_levels[i] - InpTakeProfit * _Point, _Digits) : 0;
|
| | | if(bid >= grid_levels[i])
|
2026-10-17 15:09:28 +03:00 | | | {
|
2026-10-18 09:17:39 +03:00 | | | if(trade.Sell(InpLotSize, _Symbol, bid, common_sl, tp, comment)) grid_placed = true;
|
2026-10-17 15:09:28 +03:00 | | | }
|
2026-10-17 15:17:52 +03:00 | | | else
|
2026-10-17 15:09:28 +03:00 | | | {
|
2026-10-18 09:17:39 +03:00 | | | if(trade.SellLimit(InpLotSize, grid_levels[i], _Symbol, common_sl, tp, ORDER_TIME_GTC, 0, comment)) grid_placed = true;
|
2026-10-17 15:17:52 +03:00 | | | }
|
2026-10-17 15:09:28 +03:00 | | | }
|
| | | }
|
2026-10-17 15:17:52 +03:00 | | | if(grid_placed)
|
| | | {
|
| | | MarkGapAsTraded(gap_id);
|
2026-10-17 15:09:28 +03:00 | | | }
|
| | | }
|
2026-10-17 14:51:35 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-18 11:44:32 +03:00 | | | //| Функция отмены лимиток ТОЛЬКО при закрытии позиций этого гэпа |
|
2026-10-17 14:51:35 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-18 11:44:32 +03:00 | | | void CancelLimitOrdersOnPositionClose()
|
2026-10-17 15:17:52 +03:00 | | | {
|
| | | int total_gaps = ArraySize(g_traded_gaps);
|
2026-10-17 19:54:25 +03:00 | | | if(total_gaps == 0) return;
|
| | | if(!HistorySelect(0, TimeCurrent())) return;
|
| | | int total_deals = HistoryDealsTotal();
|
2026-10-17 15:17:52 +03:00 | | | for(int g = 0; g < total_gaps; g++)
|
| | | {
|
2026-10-18 09:17:39 +03:00 | | | string gap_id = g_traded_gaps[g];
|
| | | string target_comment = "FVG_" + gap_id;
|
2026-10-18 11:44:32 +03:00 | | | bool has_open_positions = false;
|
2026-10-18 09:17:39 +03:00 | | | bool has_limit_orders = false;
|
2026-10-17 15:17:52 +03:00 | | | for(int i = PositionsTotal() - 1; i >= 0; i--)
|
| | | {
|
2026-10-18 11:44:32 +03:00 | | | // Безопасное чтение свойств позиции в MQL5 по индексу
|
2026-10-17 15:17:52 +03:00 | | | if(PositionGetSymbol(i) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
|
| | | {
|
| | | if(PositionGetString(POSITION_COMMENT) == target_comment)
|
2026-10-18 11:44:32 +03:00 | | | {
|
| | | has_open_positions = true;
|
| | | break;
|
| | | }
|
2026-10-17 15:17:52 +03:00 | | | }
|
| | | }
|
2026-10-18 09:17:39 +03:00 | | | for(int i = OrdersTotal() - 1; i >= 0; i--)
|
| | | {
|
| | | ulong ticket = OrderGetTicket(i);
|
| | | if(ticket > 0 && OrderGetInteger(ORDER_MAGIC) == InpMagicNumber)
|
| | | {
|
| | | if(OrderGetString(ORDER_COMMENT) == target_comment)
|
| | | {
|
| | | long type = OrderGetInteger(ORDER_TYPE);
|
| | | if(type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_SELL_LIMIT)
|
| | | {
|
| | | has_limit_orders = true;
|
| | | break;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
2026-10-18 11:44:32 +03:00 | | | if(has_limit_orders && !has_open_positions)
|
2026-10-17 15:17:52 +03:00 | | | {
|
2026-10-18 11:44:32 +03:00 | | | bool was_executed_and_closed = false;
|
2026-10-17 15:17:52 +03:00 | | | for(int d = total_deals - 1; d >= 0; d--)
|
| | | {
|
| | | ulong deal_ticket = HistoryDealGetTicket(d);
|
2026-10-17 19:54:25 +03:00 | | | if(deal_ticket > 0 && HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == InpMagicNumber)
|
2026-10-17 15:17:52 +03:00 | | | {
|
2026-10-17 19:54:25 +03:00 | | | if(HistoryDealGetString(deal_ticket, DEAL_COMMENT) == target_comment)
|
2026-10-17 15:17:52 +03:00 | | | {
|
2026-10-18 11:44:32 +03:00 | | | was_executed_and_closed = true;
|
2026-10-17 15:17:52 +03:00 | | | break;
|
| | | }
|
| | | }
|
| | | }
|
2026-10-18 11:44:32 +03:00 | | | if(was_executed_and_closed)
|
2026-10-17 14:51:35 +03:00 | | | {
|
2026-10-17 15:09:28 +03:00 | | | for(int i = OrdersTotal() - 1; i >= 0; i--)
|
| | | {
|
| | | ulong ticket = OrderGetTicket(i);
|
2026-10-17 15:17:52 +03:00 | | | if(ticket > 0 && OrderGetInteger(ORDER_MAGIC) == InpMagicNumber)
|
2026-10-17 15:09:28 +03:00 | | | {
|
2026-10-17 15:17:52 +03:00 | | | if(OrderGetString(ORDER_COMMENT) == target_comment)
|
| | | {
|
| | | long type = OrderGetInteger(ORDER_TYPE);
|
| | | if(type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_SELL_LIMIT)
|
| | | {
|
| | | trade.OrderDelete(ticket);
|
2026-10-18 11:44:32 +03:00 | | | Print("Лимитный ордер удален. Причина: Исполненные позиции данного FVG закрылись.");
|
2026-10-17 15:17:52 +03:00 | | | }
|
| | | }
|
2026-10-17 15:09:28 +03:00 | | | }
|
| | | }
|
2026-10-17 15:17:52 +03:00 | | | }
|
2026-10-17 14:39:15 +03:00 | | | }
|
2026-10-17 15:01:33 +03:00 | | | }
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
2026-10-18 09:17:39 +03:00 | | | //| Индивидуальный трейлинг-стоп отдельно для каждого гэпа |
|
2026-10-17 15:01:33 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-18 09:17:39 +03:00 | | | void ApplyIsolatedTrailing()
|
2026-10-17 15:09:28 +03:00 | | | {
|
2026-10-18 09:17:39 +03:00 | | | int total_gaps = ArraySize(g_traded_gaps);
|
| | | if(total_gaps == 0) return;
|
| | | double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
| | | double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
| | | for(int g = 0; g < total_gaps; g++)
|
2026-10-17 14:35:25 +03:00 | | | {
|
2026-10-18 09:17:39 +03:00 | | | string gap_id = g_traded_gaps[g];
|
| | | string target_comment = "FVG_" + gap_id;
|
| | | double total_vol_buy = 0, total_vol_sell = 0;
|
2026-10-17 14:35:25 +03:00 | | | double weighted_price_buy = 0, weighted_price_sell = 0;
|
| | | for(int i = PositionsTotal() - 1; i >= 0; i--)
|
| | | {
|
| | | if(PositionGetSymbol(i) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
|
| | | {
|
2026-10-18 09:17:39 +03:00 | | | if(PositionGetString(POSITION_COMMENT) == target_comment)
|
| | | {
|
2026-10-17 14:35:25 +03:00 | | | double vol = PositionGetDouble(POSITION_VOLUME);
|
| | | double open = PositionGetDouble(POSITION_PRICE_OPEN);
|
| | | long type = PositionGetInteger(POSITION_TYPE);
|
| | | if(type == POSITION_TYPE_BUY)
|
| | | {
|
| | | weighted_price_buy += open * vol;
|
2026-10-18 09:17:39 +03:00 | | | total_vol_buy += vol;
|
2026-10-17 14:35:25 +03:00 | | | }
|
| | | if(type == POSITION_TYPE_SELL)
|
| | | {
|
| | | weighted_price_sell += open * vol;
|
2026-10-18 09:17:39 +03:00 | | | total_vol_sell += vol;
|
2026-10-17 14:35:25 +03:00 | | | }
|
| | | }
|
| | | }
|
2026-10-18 09:17:39 +03:00 | | | }
|
| | | if(total_vol_buy > 0)
|
2026-10-17 14:35:25 +03:00 | | | {
|
2026-10-18 09:17:39 +03:00 | | | double breakeven_buy = weighted_price_buy / total_vol_buy;
|
2026-10-17 14:35:25 +03:00 | | | if(bid - breakeven_buy >= InpTrailingStart * _Point)
|
| | | {
|
| | | double new_sl = NormalizeDouble(bid - InpTrailingDist * _Point, _Digits);
|
2026-10-17 15:09:28 +03:00 | | | if(new_sl > breakeven_buy)
|
2026-10-18 09:17:39 +03:00 | | | ModifyIsolatedGridSL(target_comment, POSITION_TYPE_BUY, new_sl);
|
2026-10-17 14:35:25 +03:00 | | | }
|
| | | }
|
2026-10-18 09:17:39 +03:00 | | | if(total_vol_sell > 0)
|
2026-10-17 14:35:25 +03:00 | | | {
|
2026-10-18 09:17:39 +03:00 | | | double breakeven_sell = weighted_price_sell / total_vol_sell;
|
2026-10-17 14:35:25 +03:00 | | | if(breakeven_sell - ask >= InpTrailingStart * _Point)
|
| | | {
|
| | | double new_sl = NormalizeDouble(ask + InpTrailingDist * _Point, _Digits);
|
2026-10-17 15:09:28 +03:00 | | | if(new_sl < breakeven_sell)
|
2026-10-18 09:17:39 +03:00 | | | ModifyIsolatedGridSL(target_comment, POSITION_TYPE_SELL, new_sl);
|
| | | }
|
2026-10-17 14:35:25 +03:00 | | | }
|
| | | }
|
| | | }
|
2026-10-17 15:09:28 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-18 09:17:39 +03:00 | | | //| Модификация Стоп-Лосса внутри изолированной сетки конкретного FVG|
|
2026-10-17 15:09:28 +03:00 | | | //+------------------------------------------------------------------+
|
2026-10-18 09:17:39 +03:00 | | | void ModifyIsolatedGridSL(string target_comment, long position_type, double new_sl)
|
2026-10-17 14:35:25 +03:00 | | | {
|
| | | for(int i = PositionsTotal() - 1; i >= 0; i--)
|
2026-10-17 15:09:28 +03:00 | | | {
|
| | | ulong ticket = PositionGetTicket(i);
|
| | | if(ticket > 0 && PositionGetSymbol(i) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
|
| | | {
|
2026-10-18 09:17:39 +03:00 | | | if(PositionGetString(POSITION_COMMENT) == target_comment && PositionGetInteger(POSITION_TYPE) == position_type)
|
2026-10-17 15:09:28 +03:00 | | | {
|
| | | double current_sl = PositionGetDouble(POSITION_SL);
|
| | | if(position_type == POSITION_TYPE_BUY)
|
| | | {
|
| | | if(current_sl == 0 || (new_sl - current_sl >= InpTrailingStep * _Point))
|
| | | trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
| | | }
|
| | | else if(position_type == POSITION_TYPE_SELL)
|
| | | {
|
| | | if(current_sl == 0 || (current_sl - new_sl >= InpTrailingStep * _Point))
|
| | | trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
2026-10-17 15:17:52 +03:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
2026-10-18 09:17:39 +03:00 | | | //| Исторический учет отторгованных зон |
|
| | | //+------------------------------------------------------------------+
|
| | | bool WasGapTraded(string gap_id)
|
| | | {
|
| | | int size = ArraySize(g_traded_gaps);
|
| | | for(int i = 0; i < size; i++)
|
| | | if(g_traded_gaps[i] == gap_id) return(true);
|
| | | return(false);
|
| | | }
|
| | | void MarkGapAsTraded(string gap_id)
|
| | | {
|
| | | int size = ArraySize(g_traded_gaps);
|
| | | for(int i = 0; i < size; i++) if(g_traded_gaps[i] == gap_id) return;
|
| | | ArrayResize(g_traded_gaps, size + 1);
|
| | | g_traded_gaps[size] = gap_id;
|
| | | SaveGapToFile(gap_id);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| РАБОТА С ФАЙЛАМИ (Запись/Чтение с диска) |
|
| | | //+------------------------------------------------------------------+
|
| | | void SaveGapToFile(string gap_id)
|
| | | {
|
| | | int file_handle = FileOpen(g_file_name, FILE_WRITE|FILE_READ|FILE_TXT|FILE_ANSI);
|
| | | if(file_handle != INVALID_HANDLE)
|
| | | {
|
| | | FileSeek(file_handle, 0, SEEK_END);
|
| | | FileWriteString(file_handle, gap_id + "\r\n");
|
| | | FileClose(file_handle);
|
| | | }
|
| | | }
|
| | | void LoadGapsFromFile()
|
| | | {
|
2026-10-18 11:44:32 +03:00 | | | if(!FileIsExist(g_file_name)) return;
|
2026-10-18 09:17:39 +03:00 | | | int file_handle = FileOpen(g_file_name, FILE_READ|FILE_TXT|FILE_ANSI);
|
| | | if(file_handle != INVALID_HANDLE)
|
| | | {
|
| | | while(!FileIsEnding(file_handle))
|
| | | {
|
2026-10-18 11:44:32 +03:00 | | | // Исправлено: Сдвиг указателя (параметр 0) предотвращает бесконечный цикл
|
| | | string line = FileReadString(file_handle, 0);
|
2026-10-18 09:17:39 +03:00 | | | StringTrimLeft(line);
|
| | | StringTrimRight(line);
|
| | | if(line != "")
|
| | | {
|
| | | int size = ArraySize(g_traded_gaps);
|
| | | ArrayResize(g_traded_gaps, size + 1);
|
| | | g_traded_gaps[size] = line;
|
| | | }
|
| | | }
|
| | | FileClose(file_handle);
|
2026-10-18 11:44:32 +03:00 | | | Print("Успешно загружено ", ArraySize(g_traded_gaps), " ID зон FVG из дискового файла хранилища.");
|
2026-10-18 09:17:39 +03:00 | | | }
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Проверка: были ли уже выставлены ордера по ID данного гэпа |
|
| | | //+------------------------------------------------------------------+
|
| | | bool IsGridAlreadyPlaced(string gap_id)
|
| | | {
|
| | | string target_comment = "FVG_" + gap_id;
|
| | | for(int i = OrdersTotal() - 1; i >= 0; i--)
|
| | | {
|
| | | ulong ticket = OrderGetTicket(i);
|
| | | if(ticket > 0 && OrderGetInteger(ORDER_MAGIC) == InpMagicNumber && OrderGetString(ORDER_COMMENT) == target_comment)
|
| | | return(true);
|
| | | }
|
| | | for(int i = PositionsTotal() - 1; i >= 0; i--)
|
| | | {
|
| | | ulong ticket = PositionGetTicket(i);
|
| | | if(ticket > 0 && PositionGetSymbol(i) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber && PositionGetString(POSITION_COMMENT) == target_comment)
|
| | | return(true);
|
| | | }
|
| | | return(false);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
2026-10-17 15:17:52 +03:00 | | | //| Проверка на смягчение бычьего гэпа |
|
2026-10-22 10:08:40 +03:00 | | |
|
2026-10-17 15:17:52 +03:00 | | | //+------------------------------------------------------------------+
|
| | | bool IsBullishMitigated(const int formation_index, const double gap_bottom, const double gap_top, const double &low[])
|
| | | {
|
| | | for(int index = formation_index - 1; index >= 0; index--)
|
2026-10-18 11:44:32 +03:00 | | | if(low[index] <= gap_bottom)
|
2026-10-17 15:17:52 +03:00 | | | return(true);
|
| | | return(false);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Проверка на смягчение медвежьего гэпа |
|
2026-10-22 10:08:40 +03:00 | | |
|
2026-10-17 15:17:52 +03:00 | | | //+------------------------------------------------------------------+
|
| | | bool IsBearishMitigated(const int formation_index, const double gap_bottom, const double gap_top, const double &high[])
|
| | | {
|
| | | for(int index = formation_index - 1; index >= 0; index--)
|
2026-10-18 11:44:32 +03:00 | | | if(high[index] >= gap_top)
|
2026-10-17 15:17:52 +03:00 | | | return(true);
|
| | | return(false);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| Отрисовка пунктирных прямоугольников без заливки |
|
2026-10-22 10:08:40 +03:00 | | |
|
2026-10-17 15:17:52 +03:00 | | | //+------------------------------------------------------------------+
|
| | | 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);
|
| | | }
|
2026-10-22 10:08:40 +03:00 | | |
|
2026-10-17 14:35:25 +03:00 | | | }
|