Как торговать Fair Value Gaps: правила формирования, сценарии отработки и автоторговля с помощью прерывателей и сдвигов структуры рынка
Введение
Разрывы реальной стоимости — очень распространенное явление сегодня, которое часто называют набором знаний в рамках концепции «умных денег» (smart money concept, SMC) или, скорее, считают происходящим из концепций «умных денег». Кроме того, его очень часто можно встретить в социальных сетях, когда трейдеры общаются на торговых платформах и в тематических чатах по созданию графиков, но большинство людей считают его, скорее, точкой отсчета, чем источником совершенствования их торговли или даже предоставления им инструментов для определения их предвзятости и торговли на основе времени и места возникновения таких разрывов реальной стоимости.
Моя цель — не только устранить разрывы реальной стоимости, но и разобраться, как можно использовать их в интересах трейдера для формирования предубеждений, нарратива и даже заключения сделок. Кроме того, я проанализирую, где эти разрывы реальной стоимости возникнут с наибольшей вероятностью, когда и как они возникнут и как подтвердить их в качестве точек входа в сделку или точек фиксации прибыли, а в конечном итоге — в качестве точек повторного входа для исполнения сделки после отката, который случается после сильного расширения.
После освоения таких тем, как разрывы реальной стоимости и торговой логики, используемой для подтверждения, определения и идентификации базовых предубеждений и нарратива, я объясню формирование факторов прорыва и изменений в структуре рынка, которые обычно подтверждают сделки с учетом базовых предубеждений и нарратива. Эти факторы прорыва и изменения в структуре рынка убедительны и часто оправдывают даже долгосрочные сделки. В этой статье представлено четкое описание и объяснение эффективности сочетания разрывов реальной стоимости и факторов прорывы, особенно учитывая, что они возникают на всех временных интервалах, как краткосрочных, так и долгосрочных.
Знакомство с понятием разрыва реальной стоимости
Разрывы реальной стоимости формируются, когда наблюдаются быстрые ценовые движения в каком-либо направлении, что указывает на сильное давление со стороны покупателей или продавцов, в первую очередь со стороны институциональных игроков. Проще говоря, такое торговое давление часто приводит к ситуации, когда у трейдеров оказываются неравные возможности для размещения или исполнения сделок; поэтому при бычьем разрыве реальной стоимости (FVG), когда цена покинула некую точку с силой и скоростью в направлении более высоких цен и у продавцов не было времени или возможности для их исполнения, а при медвежьем разрыве реальной стоимости (FVG), когда цена покинула некую точку с силой и скоростью в направлении более низких цен и у покупателей не было времени или возможности для их исполнения, эта область называется несбалансированным ценовым диапазоном, поскольку не было никакого движения цены в направлении, противоположном тому, откуда происходило расширение.
Наиболее наглядно это можно объяснить с помощью логики художника: когда художник раскрашивает стену, он должен делать это равными или даже похожими мазками кистью в противоположных направлениях, чтобы краска ложилась ровно и сбалансированно или, скорее качественно. Та же логика применима и к цене. Когда бычья свеча расширяется и пробивает ценовую точку, например с 1.3010 до 1.3030, медвежья свеча тоже должна пробить эту ценовую точку, чтобы сбалансировать цену, а если этого не происходит, то возникает разрыв реальной стоимости (FVG). Часто, если не всегда, цена в кнечном итоге будет возвращаться к этой отметке. Именно здесь и в этот момент нам следует рассмотреть возможность воспользоваться этими разрывами реальной стоимости, поскольку они открывают огромные и прекрасные возможности.
Ниже проиллюстрируем несколько примеров разрывов реальной стоимости и способов их использования.

Как, когда и почему они образуются
Теперь, когда мы определили, что такое разрывы реальной стоимости, гораздо легче понять, как они образуются. Это происходит в очень хорошо определенных ситуациях. Первый сценарий — когда выходит пресс-релиз; это может быть одним из примеров новостей в красной зоне (CPI, FOMC или NFP) или даже другими типами пресс-релизов; все зависит от оказываемого воздействия.
В большинстве случаев такой пресс-релиз появляется после того, как крупные игроки на рынке уже разместили отложенные ордера и, следовательно, ожидали такой волатильности, скорости и силы; следовательно, рынок быстро движется в его пользу и оставляет после себя разрывы реальной стоимости, из которых мы, как я полагаю, и намерены извлечь прибыль. Согласно отраслевым стандартам, не рекомендуется торговать до выхода важного пресс-релиза, однако часто бывает целесообразнее дождаться пресс-релиза и связанной с ним волатильности, прежде чем совершать торговые сделки, поэтому в идеале нам следовало бы избегать столь раннего совершения сделок, поскольку большинство из них даже не предлагают торговых настроек.
Другой сценарий, при котором они формируются, — это когда крупные игроки (например, банки или хедж-фонды) размещают ордера; следовательно, они влияют на движения рынка, поскольку стоимость их ордеров составляет миллиарды долларов.
Эти крупные ордера можно легко определить по огромным свечным графикам и резким, внезапным движениям в одном направлении, даже без учета влияния выпусков новостей. В этой статье я пытаюсь показать, как при отслеживании этих масштабных изменений мы должны натренировать свое внимание, чтобы определять разрывы в реальной стоимости, возникающие во время таких больших движений.
Следующая иллюстрация показывает, как цена быстро и мощно вырвалась из некоей зоны, а следующая свеча не откатилась назад, чтобы перекрыть бычью свечу, оставляя неохваченным участок, который называется разрывом в реальной стоимости.

Как анализировать разрывы в реальной стоимости
В этой главе рассмотрим, как анализировать разрывы в реальной стоимости. В предыдущих главах мы объяснили, что они собой представляют, когда они возникают и как формируются. Теперь сосредоточимся на том, как анализировать те же самые разрывы для определения, каких сделок нужно дождать и исполнить.
Первое базовое правило состоит в том, что в большинстве случаев нам следует избегать торговлю на разрывах в реальной стоимости, но мы должны принять необходимость торговли вне зоны их влияния, поскольку они действуют в качестве движущей силы, если используются эффективно в направлении тренда, необъективности и хроники событий. Так зачем торговать против движущего блока, который спустя несколько мгновений обрушится на нас со всей силой? И это совершенно верно и точно как в краткосрочной, так и в долгосрочной перспективе; они все действуют одинаковым образом. Единственное различие заключается во времени, которое необходимо для того, чтобы сделка состоялась, но в большинстве (90%) случаев, действуют именно так.
В идеале, первым шагом является анализ сверху вниз, от самых больших до самых маленьких таймфреймов, с наблюдением и выделением всех разрывов в реальной стоимости. Следующим важнейшим шагом является анализ наблюдаемых направления тренда, смещений и нарратива. Например, тренд, смещения и общая картина цен на золото, в частности на MN, W1 и D1, указывают на необходимость совершения сделок на покупку. Это можно подтвердить тремя основными способами: цена торгуется выше скользящих средних (простых и экспоненциальных); другой способ — структура рынка явно демонстрирует покупательный импульс; кроме того, зеленых (бычьих) свечей гораздо больше, чем красных (медвежьих).
После того как тренд и общая ценовая картина (нарратив) определены, необходимо рассмотреть еще один важный аспект. Цена постоянно движется, стремясь прийти к ликвидности для достижения целевых показателей, а нарратив, направление и сдвиги определяются на более старших таймфреймах. Ликвидность в основном формируется на основе предшествующих дневных, недельных и месячных минимумов и максимумов, и именно здесь следует ожидать значимых реакций. Поэтому, получив представление о ликвидности и тренде, мы можем с легкостью реализовать задуманное.
Примером такого сценария может служить тот факт, что теперь мы выявили приближение цены на золото к максимуму предыдущей недели, что подтверждает структура рынка (скользящие средние). В течение недели или в ходе текущих торговых сессий цена продолжает двигаться к максимуму предыдущей недели, а разрывы реальной стоимости продолжают формироваться. Эти разрывы в реальной стоимости будут обозначены как бычий сигнал, а если они возникают на более длинных таймфреймах (например, на H4, D1, H1), то это еще лучше, их точно надо обозначить как бычий сигнал. Трейдерам всегда следует ожидать возможность совершения сделок на покупку, когда цена вернется к этим разрывам; таким образом, они могут искать возможности исполнения сделок.
Важно отметить, что в большинстве случаев, если не всегда, цена всегда восстанавливает баланс в разрывах реальной стоимости. Однако, если цена близка к целевому уровню, как упомянуто выше (например, недельные, месячные или дневные максимумы), то после достижения этого целевого уровня и очистки ликвидности вероятность того, что разрыв в реальной стоимости не будет использован для новых сделок в значительной степени снижается, и это может привести к неудачной покупке. Поэтому нам следует подобных сделок избегать.
Как они влияют на сделки и их адрес
В сущности, идея заключается в том, что при наличии предопределенных тренда и направления (например, медвежьего) разрывы в реальной стоимости выявляют и отмечают при помощи нисходящего анализа. Следует отметить, что цена вернётся к этим разрывам реальной стоимости (ценам), чтобы предложить варианты торговли и входа, пока поддерживаются недельные, дневные или месячные целевые уровни. В целом, цена должна восстановить равновесие и заполнить разрывы в реальной стоимости. Поэтому она действует как фактор, усиливающий пару (актив), когда цена возвращается как вторичный покупатель, поскольку в этот момент больше трейдеров открывают новые позиции, что объясняет скорость и силу рынка. Это вызывает сильные положительные колебания рынка. Кроме того, это усиливает направленность, перевесы и нарратив данного тренда.
Как успешно использовать разрывы в реальной стоимости для совершения торговых входов
Это важнейшая и ценнейшая часть информации в данной статье, поскольку после тщательных проверки и анализа всех наших критериев мы понимаем, как определить, где и как нужно подтверждать или опровергать разрывы в реальной стоимости, а также — как они формируются и где, вероятнее всего, возникнут. Эта часть упрощена, поскольку вся сложная работа уже проделана.
Тем все, что требуется, — это терпение, дисциплина и сосредоточенность. Мы в первую очередь будет проводить анализ на основе графика D1, поскольку он является справочным (эталонным) и позволяет нам выявлять разрывы в реальной стоимости. Если обнаружим разрыв в реальной стоимости и цена вернется к нему, то, оказавшись внутри, мы быстро опустимся до таймфрейма H4 и будем искать бычью свечу поглощения (если тренд, сдвиги и нарратив указывают на покупателей) или медвежью свечу поглощения (если сдвиги, тренд и нарратив указывают на покупателей). Это может произойти в любой момент в пределах разрыва реальной стоимости, но в идеале это должно происходить вблизи начала торговой сессии, например, в Лондоне или Нью-Йорке. После обнаружения свечи поглощения мы отступаем к уровню H1; здесь мы ищем прорыв в структуре рынка.
Прорыв в структуре рынка означает изменение в статусе предоставления цены; если вы продавали, а затем стали покупателем, это означает изменение статуса предоставления цены и, следовательно, изменение структуры рынка. Поэтому на графике H1 вы ищете именно это. Стоп-лоссы следует размещать на противоположных концах пробитий, а входы в сделки можно осуществлять на пробитиях в небольших, обоснованных по цене разрывах, которые могут возникать при пробитии на графике H1 или на уровне коррекции 60%, если таковой будет иметь место.
Прорывы относятся к превышению последнего максимума или снижению ниже последнего минимума перед тем, как происходит изменение в структуре рынка; если цена является ценой продажи и пробитие последнего максимума происходит быстро и сильно, это будет бычий прорыв, а если цена представляет собой цену покупки и пробивает быстро и сильно последний минимум, это будет медвежий прорыв.
Схожее явление наблюдается на других таймфреймах. При обнаружении прорыва реальной стоимости на графике H4 будем ждать появления свечи поглощения на графике H1, а затем изменения структуры рынка на графике M15. Эту стратегию можно применять и к трейдерам, торгующим на более коротких таймфреймах. Повторяется то же самое явление: разрыв в реальной стоимости возникает на графике H1, а свеча поглощения — на графике M15; поэтому изменение в структуре рынка происходит на таймфрейме M5. Кроме того, при скальпинге разрывы в реальной стоимости появлялись на графике M15. Можно искать свечи поглощения на графике M5, а изменения в структуре рынка — на графике M1.
Как и когда следует избегать торговли с использованием разрывов в реальной стоимости
Большинство разрывов в реальной стоимости часто предоставляют возможности для торговли, но, как объясняется выше, некоторые их формы не идеальны для торговли.
1. Разрывы реальной стоимости, которые возникают против тренда, сдвигов и направления нарратива вообще не будут приниматься во внимание; например, если тренд или сдвиг указывает на покупку, а цена разворачивается вниз, оставляя разрыв в реальной стоимости и, возможно, для коррекций, не следует ожидать, что цена будет учитывать разрыв в реальной стоимости выше себя, когда возобновит восходящий тренд; разрыв будет быстро проигнорирован и пройден.
2. Второй сценарий, при котором эти разрывы в реальной стоимости оказываются неэффективными, — это когда достигаются общие недельные, дневные или месячные целевые показатели. Разрывы реальной стоимости могут не срабатывать, потому что у цены нет оснований продолжать тренд в том же направлении, учитывая, что ликвидность уже исчерпана; следовательно, больше нет доступной силы или скорости, поскольку прибыль крупных компаний уже учтена. Таким образом, разрывы реальной стоимости могут оказаться несостоятельными, потому что цена может начать снижаться или даже разворачиваться.
3. Третий и последний сценарий, при котором это явление снова потерпит неудачу, — это когда, например, изменения в свече поглощения и структуре рынка не происходят на более низких таймфреймах, как было показано выше. Кроме того, они должны происходить во время очень активных торговых сессий и именно в таком порядке.
Сначала образуются разрывы в реальной стоимости, затем — свечи поглощения, а затем меняется структура рынка. Следовательно, только после того, как они произойдут в указанной последовательности, мы сможем совершать сделки, используя отборные, высококачественные настройки. Это четко указывает, что трейдеру не следует использовать этот метод или какой-либо разрыв в реальной стоимости, если они не соответствуют всем вышеупомянутым условиям, поскольку существует высокая вероятность неудачи.
Разрывы в реальной стоимости на более длительных таймфреймах
К разрывам в более длительных временных интервалах относятся разрывы реальной стоимости на графиках H4, D1 и W1, которые легко использовать за длительные или долгосрочные колебания цен, даже в объеме 200-500 пунктов, потому что они стабильнее и медленнее, не имеют краткосрочной волатильности и не склонны к движениям в одном направлении, что делает их более подходящими для трейдеров, предпочитающих долгосрочную торговлю, которые могут без нервозности и страха терпеливо удерживать свои позиции.
Разрывы в реальной стоимости на более коротких временных промежутках
Разрывы на более низких таймфреймах относятся к разрывам в реальной стоимости на графиках с таймфреймами H1, M15 и M5. Эти разрывы в основном и в идеале используются трейдерами, предпочитающими краткосрочную, а не долгосрочную торговлю, от скальперов до предпочитающих краткосрочную торговлю, которые удерживают позиции в течение нескольких часов, а затем закрывают их. Они могут возникать в условиях трендового рынка, особенно на графике M15, а в сессиях с использованием кредитного плеча они очень надежны. Они предназначены для быстрого исполнения и легко обеспечивают доходность в 100-150 пунктов. В зависимости от силы и скорости движения цен, они характеризуются краткосрочными колебаниями цен и высокой волатильностью. Для них требуются трейдеры, способные быстро принимать торговые решения и преуспевать в подобных условиях.
Автоматизация торговли с применением разрывов в реальной стоимости на MQL5
Для автоматизации этой стратегии я создал
- Индикатор FVG для обнаружения разрывов и их нанесения на карту.
- Советник FVG + MSS для торговли на основе обнаруженных факторов с использованием MSS для определения точек входа.
Исходный код индикатора FVG
Этот индикатор определяет разрывы (FVG), отслеживая большие, не перекрывающие друг друга свечи (дисбаланс > минимумы). Он рисует прямоугольники для обнаружения видимых разрывов.
#property copyright "Eugene Mmene" #property link "EMcapital" #property version "1.0" input int minPts = 100; // Minimum points for FVG gap to be valid input int FVG_Rec_Ext_Bars = 10; // Length of FVG rectangle in bars input bool DrawFVG = true; // Draw FVG rectangles on chart #define FVG_Prefix "FVG_REC_" #define CLR_UP clrLime #define CLR_DOWN clrRed struct TimeframeData { ENUM_TIMEFRAMES tf; datetime lastBar; }; TimeframeData tfs[]; string eaSymbol; void CreateRec(string objName, datetime time1, double price1, datetime time2, double price2, color clr) { Print("CreateRec called: objName=", objName, ", time1=", TimeToString(time1), ", price1=", DoubleToString(price1, _Digits)); if(DrawFVG && ObjectFind(0, objName) < 0) { if(ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2)) { ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1); ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1); ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2); ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2); ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); ObjectSetInteger(0, objName, OBJPROP_FILL, true); ObjectSetInteger(0, objName, OBJPROP_BACK, false); Print("CreateRec: Rectangle created successfully: ", objName); } else { Print("CreateRec: Failed to create rectangle: ", objName, ", Error=", GetLastError()); } } else { Print("CreateRec: Skipped - DrawFVG=", DrawFVG, ", Object exists=", ObjectFind(0, objName) >= 0); } } void DetectFVGs() { Print("DetectFVGs started"); int maxObjects = 100; // Limit total objects to prevent overload int currentObjects = ObjectsTotal(0, 0, OBJ_RECTANGLE); if(currentObjects >= maxObjects) { Print("DetectFVGs: Object limit reached (", currentObjects, "/", maxObjects, "), skipping FVG creation"); return; } for(int i = 0; i < ArraySize(tfs); i++) { if(!NewBar(tfs[i].tf)) continue; double low0 = iLow(eaSymbol, tfs[i].tf, 0); double high2 = iHigh(eaSymbol, tfs[i].tf, 2); double gap_L0_H2 = NormalizeDouble((low0 - high2) / _Point, 0); double high0 = iHigh(eaSymbol, tfs[i].tf, 0); double low2 = iLow(eaSymbol, tfs[i].tf, 2); double gap_H0_L2 = NormalizeDouble((low2 - high0) / _Point, 0); if(gap_L0_H2 > minPts) { string fvgName = FVG_Prefix + EnumToString(tfs[i].tf) + "_" + TimeToString(iTime(eaSymbol, tfs[i].tf, 0), TIME_DATE|TIME_MINUTES); Print(StringFormat("%s Bullish FVG detected: Low=%s, High[2]=%s, Gap=%s points", EnumToString(tfs[i].tf), DoubleToString(low0, _Digits), DoubleToString(high2, _Digits), DoubleToString(gap_L0_H2, 0))); CreateRec(fvgName, iTime(eaSymbol, tfs[i].tf, 0), high2, iTime(eaSymbol, tfs[i].tf, 0) + PeriodSeconds(tfs[i].tf) * FVG_Rec_Ext_Bars, low0, CLR_UP); } if(gap_H0_L2 > minPts) { string fvgName = FVG_Prefix + EnumToString(tfs[i].tf) + "_" + TimeToString(iTime(eaSymbol, tfs[i].tf, 0), TIME_DATE|TIME_MINUTES); Print(StringFormat("%s Bearish FVG detected: High=%s, Low[2]=%s, Gap=%s points", EnumToString(tfs[i].tf), DoubleToString(high0, _Digits), DoubleToString(low2, _Digits), DoubleToString(gap_H0_L2, 0))); CreateRec(fvgName, iTime(eaSymbol, tfs[i].tf, 0), low2, iTime(eaSymbol, tfs[i].tf, 0) + PeriodSeconds(tfs[i].tf) * FVG_Rec_Ext_Bars, high0, CLR_DOWN); } } if(DrawFVG) ChartRedraw(0); // Single redraw per tick Print("DetectFVGs completed"); } bool NewBar(ENUM_TIMEFRAMES tf) { int idx = TimeframeIndex(tf); if(idx < 0) return false; datetime cur = iTime(eaSymbol, tf, 0); if(cur != tfs[idx].lastBar) { tfs[idx].lastBar = cur; return true; } return false; } int TimeframeIndex(ENUM_TIMEFRAMES tf) { for(int i = 0; i < ArraySize(tfs); i++) { if(tfs[i].tf == tf) return i; } return -1; } int OnInit() { eaSymbol = _Symbol; if(!SymbolSelect(eaSymbol, true)) { Print("Error: Failed to select ", eaSymbol, " in Market Watch"); return(INIT_FAILED); } ArrayResize(tfs, 3); tfs[0].tf = PERIOD_M5; tfs[1].tf = PERIOD_M15; tfs[2].tf = PERIOD_H1; for(int i = 0; i < 3; i++) { tfs[i].lastBar = 0; } Print("FVG Detector initialized for ", eaSymbol, ". Timeframes: M5, M15, H1"); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if(DrawFVG) ObjectsDeleteAll(0, FVG_Prefix); Print("FVG Detector stopped: ", reason); } void OnTick() { DetectFVGs(); }
Установка: скомпилируйте в MetaEditor и прикрепите к графику. Рисует зеленые прямоугольники для бычьих FVG и красные прямоугольники для медвежьих.
Пример использования: на графике GOLD M15 определите разрывы для визуального анализа.
Исходный код для советника FVG + MSS
Этот советник обнаруживает разрывы FVG, ожидает коррекции в направлении разрыва, проверяет наличие последнего максимума/минимума (LH) и начинает входить в сделки. Он включает в себя управление рисками (риск 2% на сделку).
#property copyright "Eugene Mmene" #property link "EMcapital" #property version "2.27.2" #include <Trade\Trade.mqh> input double RiskPct = 2.0; // Base risk per trade % input double MaxLossUSD = 110.0; // Maximum loss per trade in USD input double RecTgt = 7000.0; // Equity recovery target input int ATR_Prd = 14; // ATR period input int Brk_Prd = 10; // Breakout period input int EMA_Prd = 20; // EMA period input string GS_Url = ""; // Google Sheets webhook URL input bool NewsFilt = true; // News filter input int NewsPause = 15; // Pause minutes input double MinBrkStr = 0.1; // Min breakout strength (x ATR) input int Vol_Prd = 1; // Volume period input bool Bypass = true; // Bypass volume, breakout, HTF trend filters for testing input bool useHTF = false; // Use D1 or H4 EMA trend filter input string NewsAPI_Url = "https://www.alphavantage.co/query?function=NEWS_SENTIMENT&apikey="; // Alpha Vantage API URL input string NewsAPI_Key = "pub_3f54bba977384ac19b6839a744444aba"; // Alpha Vantage API key input double DailyDDLimit = 2.5; // Daily drawdown limit (%) input double OverallDDLimit = 5.5; // Overall drawdown limit (%) input double TargetBalanceOrEquity = 6600.0; // Target balance or equity to pass challenge ($) input bool ResetProfitTarget = false; // Reset target to resume trading input int minPts = 100; // Minimum points for FVG gap to be valid input int FVG_Rec_Ext_Bars = 10; // Length of FVG rectangle in bars input bool DrawFVG = true; // Draw FVG rectangles on chart double CurRisk = RiskPct; double OrigRisk = RiskPct; double LastEqHigh = 0; double StartingBalance = 0; double DailyBalance = 0; datetime LastDay = 0; bool ProfitTargetReached = false; bool DailyDDPaused = false; CTrade trade; int h_ema_d1 = INVALID_HANDLE; int h_ema_h4 = INVALID_HANDLE; int winStreak = 0; int lossStreak = 0; string eaSymbol = _Symbol; struct TimeframeData { ENUM_TIMEFRAMES tf; int h_atr; int h_vol; int h_vol_ma; datetime lastSig; datetime lastBar; }; TimeframeData tfs[]; struct NewsEvt { datetime time; string evt; int impact; }; NewsEvt newsCal[]; int newsCnt = 0; struct TradeLog { ulong ticket; bool isWin; double profit; double brkStr; double vol; double risk; ENUM_TIMEFRAMES tf; }; TradeLog tradeHistory[]; int tradeCnt = 0; double dynBrkStr = MinBrkStr; #define FVG_Prefix "FVG_REC_" #define CLR_UP clrLime #define CLR_DOWN clrRed void CreateRec(string objName, datetime time1, double price1, datetime time2, double price2, color clr) { Print("CreateRec called: objName=", objName, ", time1=", TimeToString(time1), ", price1=", DoubleToString(price1, _Digits)); if(DrawFVG && ObjectFind(0, objName) < 0) { if(ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2)) { ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1); ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1); ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2); ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2); ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); ObjectSetInteger(0, objName, OBJPROP_FILL, true); ObjectSetInteger(0, objName, OBJPROP_BACK, false); Print("CreateRec: Rectangle created successfully: ", objName); } else { Print("CreateRec: Failed to create rectangle: ", objName, ", Error=", GetLastError()); } } else { Print("CreateRec: Skipped - DrawFVG=", DrawFVG, ", Object exists=", ObjectFind(0, objName) >= 0); } } void DetectFVGs() { Print("DetectFVGs started"); int maxObjects = 100; // Limit total objects to prevent overload int currentObjects = ObjectsTotal(0, 0, OBJ_RECTANGLE); if(currentObjects >= maxObjects) { Print("DetectFVGs: Object limit reached (", currentObjects, "/", maxObjects, "), skipping FVG creation"); return; } for(int i = 0; i < ArraySize(tfs); i++) { if(!NewBar(tfs[i].tf)) continue; double low0 = iLow(eaSymbol, tfs[i].tf, 0); double high2 = iHigh(eaSymbol, tfs[i].tf, 2); double gap_L0_H2 = NormalizeDouble((low0 - high2) / _Point, 0); double high0 = iHigh(eaSymbol, tfs[i].tf, 0); double low2 = iLow(eaSymbol, tfs[i].tf, 2); double gap_H0_L2 = NormalizeDouble((low2 - high0) / _Point, 0); if(gap_L0_H2 > minPts) { string fvgName = FVG_Prefix + EnumToString(tfs[i].tf) + "_" + TimeToString(iTime(eaSymbol, tfs[i].tf, 0), TIME_DATE|TIME_MINUTES); Print(StringFormat("%s Bullish FVG detected: Low=%s, High[2]=%s, Gap=%s points", EnumToString(tfs[i].tf), DoubleToString(low0, _Digits), DoubleToString(high2, _Digits), DoubleToString(gap_L0_H2, 0))); CreateRec(fvgName, iTime(eaSymbol, tfs[i].tf, 0), high2, iTime(eaSymbol, tfs[i].tf, 0) + PeriodSeconds(tfs[i].tf) * FVG_Rec_Ext_Bars, low0, CLR_UP); } if(gap_H0_L2 > minPts) { string fvgName = FVG_Prefix + EnumToString(tfs[i].tf) + "_" + TimeToString(iTime(eaSymbol, tfs[i].tf, 0), TIME_DATE|TIME_MINUTES); Print(StringFormat("%s Bearish FVG detected: High=%s, Low[2]=%s, Gap=%s points", EnumToString(tfs[i].tf), DoubleToString(high0, _Digits), DoubleToString(low2, _Digits), DoubleToString(gap_H0_L2, 0))); CreateRec(fvgName, iTime(eaSymbol, tfs[i].tf, 0), low2, iTime(eaSymbol, tfs[i].tf, 0) + PeriodSeconds(tfs[i].tf) * FVG_Rec_Ext_Bars, high0, CLR_DOWN); } } if(DrawFVG) ChartRedraw(0); // Single redraw per tick Print("DetectFVGs completed"); } int OnInit() { if(AccountInfoDouble(ACCOUNT_BALANCE) < 10.0) { Print("Low balance: ", DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2)); return(INIT_FAILED); } string sym = Symbol(); bool selected; if(!SymbolExist(sym, selected)) { Print("Error: Symbol ", sym, " not found in Market Watch. Available: ", _Symbol); eaSymbol = _Symbol; } else { eaSymbol = sym; Print("Symbol validated: ", eaSymbol, ", Selected in Market Watch: ", selected); } if(!SymbolSelect(eaSymbol, true)) { Print("Error: Failed to select ", eaSymbol, " in Market Watch"); return(INIT_FAILED); } Print("Please ensure ", NewsAPI_Url, " is added to allowed WebRequest URLs in MT5 settings"); StartingBalance = AccountInfoDouble(ACCOUNT_BALANCE); LastEqHigh = AccountInfoDouble(ACCOUNT_EQUITY); DailyBalance = StartingBalance; LastDay = TimeCurrent() / 86400 * 86400; ProfitTargetReached = ResetProfitTarget ? false : ProfitTargetReached; DailyDDPaused = false; ArrayResize(newsCal, 100); ArrayResize(tradeHistory, 100); ArrayResize(tfs, 3); tfs[0].tf = PERIOD_M5; tfs[1].tf = PERIOD_M15; tfs[2].tf = PERIOD_H1; for(int i = 0; i < 3; i++) { tfs[i].h_atr = iATR(eaSymbol, tfs[i].tf, ATR_Prd); tfs[i].h_vol = iVolumes(eaSymbol, tfs[i].tf, VOLUME_TICK); tfs[i].h_vol_ma = iMA(eaSymbol, tfs[i].tf, Vol_Prd, 0, MODE_SMA, PRICE_CLOSE); tfs[i].lastSig = 0; tfs[i].lastBar = 0; if(tfs[i].h_atr == INVALID_HANDLE || tfs[i].h_vol == INVALID_HANDLE || tfs[i].h_vol_ma == INVALID_HANDLE) { Print("Indicator init failed for ", EnumToString(tfs[i].tf)); return(INIT_FAILED); } } h_ema_d1 = iMA(eaSymbol, PERIOD_D1, EMA_Prd, 0, MODE_EMA, PRICE_CLOSE); h_ema_h4 = iMA(eaSymbol, PERIOD_H4, EMA_Prd, 0, MODE_EMA, PRICE_CLOSE); if(h_ema_d1 == INVALID_HANDLE || h_ema_h4 == INVALID_HANDLE) { Print("EMA init failed"); return(INIT_FAILED); } if(NewsFilt) FetchNewsCalendar(); Print("EA initialized. Timeframes: M5, M15, H1, News events: ", newsCnt, ", Bypass: ", Bypass, ", UseHTF: ", useHTF, ", Time时间: EAT (UTC+3), Server: ", TerminalInfoString(TERMINAL_NAME), ", Starting Balance: ", DoubleToString(StartingBalance, 2), ", Target Balance/Equity: ", DoubleToString(TargetBalanceOrEquity, 2)); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if(h_ema_d1 != INVALID_HANDLE) IndicatorRelease(h_ema_d1); if(h_ema_h4 != INVALID_HANDLE) IndicatorRelease(h_ema_h4); for(int i = 0; i < ArraySize(tfs); i++) { if(tfs[i].h_atr != INVALID_HANDLE) IndicatorRelease(tfs[i].h_atr); if(tfs[i].h_vol != INVALID_HANDLE) IndicatorRelease(tfs[i].h_vol); if(tfs[i].h_vol_ma != INVALID_HANDLE) IndicatorRelease(tfs[i].h_vol_ma); } if(DrawFVG) ObjectsDeleteAll(0, FVG_Prefix); Print("EA stopped: ", reason); } void CloseAllPositions() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket) || PositionGetString(POSITION_SYMBOL) != eaSymbol) continue; long magic = PositionGetInteger(POSITION_MAGIC); if(magic == MagicNumber(PERIOD_M5) || magic == MagicNumber(PERIOD_M15) || magic == MagicNumber(PERIOD_H1)) { trade.PositionClose(ticket); Print("Closed position: Ticket=", ticket, ", Symbol=", eaSymbol, ", Magic=", magic); } } } void OnTick() { datetime currentDay = TimeCurrent() / 86400 * 86400; if(currentDay > LastDay) { DailyBalance = AccountInfoDouble(ACCOUNT_BALANCE); LastDay = currentDay; DailyDDPaused = false; Print("Daily balance reset: ", DoubleToString(DailyBalance, 2), " at ", TimeToString(currentDay, TIME_DATE)); } double equity = AccountInfoDouble(ACCOUNT_EQUITY); double balance = AccountInfoDouble(ACCOUNT_BALANCE); if(balance >= TargetBalanceOrEquity || equity >= TargetBalanceOrEquity) { CloseAllPositions(); ProfitTargetReached = true; Print("Trading paused: Target balance or equity reached. Balance=", DoubleToString(balance, 2), ", Equity=", DoubleToString(equity, 2), ", Target=", DoubleToString(TargetBalanceOrEquity, 2), ". All positions closed. Set ResetProfitTarget=true or restart EA to resume trading."); return; } double dailyDD = (DailyBalance - equity) / DailyBalance * 100; double overallDD = (StartingBalance - equity) / StartingBalance * 100; if(dailyDD >= DailyDDLimit) { CloseAllPositions(); DailyDDPaused = true; Print("Trading paused until next trading day: Daily DD=", StringFormat("%.2f", dailyDD), "% reached (Limit: ", DoubleToString(DailyDDLimit, 2), "%), Equity=", DoubleToString(equity, 2), ", Daily Balance=", DoubleToString(DailyBalance, 2), ". All positions closed."); return; } if(overallDD >= OverallDDLimit) { CloseAllPositions(); ProfitTargetReached = true; Print("Trading paused: Overall DD=", StringFormat("%.2f", overallDD), "% reached (Limit: ", DoubleToString(OverallDDLimit, 2), "%), Equity=", DoubleToString(equity, 2), ", Starting Balance=", DoubleToString(StartingBalance, 2), ". All positions closed. Set ResetProfitTarget=true or restart EA to resume trading."); return; } if(ProfitTargetReached) { Print("Trading paused: Target or overall drawdown previously reached. Balance=", DoubleToString(balance, 2), ", Equity=", DoubleToString(equity, 2), ", Target=", DoubleToString(TargetBalanceOrEquity, 2)); return; } if(DailyDDPaused) { Print("Trading paused: Daily drawdown limit previously reached. Waiting for next trading day. Equity=", DoubleToString(equity, 2), ", Daily Balance=", DoubleToString(DailyBalance, 2)); return; } static datetime lastNewsFetch = 0; if(NewsFilt && TimeCurrent() >= lastNewsFetch + 4 * 3600) { FetchNewsCalendar(); lastNewsFetch = TimeCurrent(); } for(int i = 0; i < ArraySize(tfs); i++) { if(!NewBar(tfs[i].tf)) continue; bool hasPosition = false; for(int j = PositionsTotal() - 1; j >= 0; j--) { ulong ticket = PositionGetTicket(j); if(PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == eaSymbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber(tfs[i].tf)) { hasPosition = true; break; } } if(hasPosition) { ManageTrades(tfs[i].tf); continue; } if(TimeCurrent() <= tfs[i].lastSig + PeriodSeconds(PERIOD_H1)) { Print(EnumToString(tfs[i].tf), " Trade skipped: Within 1-hour cooldown"); continue; } if(NewsFilt && IsNews()) { Print(EnumToString(tfs[i].tf), " Trade skipped: News pause active"); continue; } double eq = AccountInfoDouble(ACCOUNT_EQUITY); if(eq > LastEqHigh) LastEqHigh = eq; if(eq < LastEqHigh && lossStreak >= 2) CurRisk = MathMax(OrigRisk * 0.25, 0.1); else if(winStreak >= 3) CurRisk = MathMin(OrigRisk * 1.25, 5.0); else CurRisk = OrigRisk; if(eq >= RecTgt) { CurRisk = OrigRisk; winStreak = 0; lossStreak = 0; } bool bullHTF = !useHTF || (BullTrend(PERIOD_D1) || BullTrend(PERIOD_H4)); bool bearHTF = !useHTF || (BearTrend(PERIOD_D1) || BearTrend(PERIOD_H4)); bool buyBrk = BuyBrk(tfs[i].tf); bool sellBrk = SellBrk(tfs[i].tf); Print(EnumToString(tfs[i].tf), " Signal check: BullHTF=", bullHTF, ", BearHTF=", bearHTF, ", BuyBrk=", buyBrk, ", SellBrk=", sellBrk, ", Bid=", DoubleToString(SymbolInfoDouble(eaSymbol, SYMBOL_BID), _Digits), ", Ask=", DoubleToString(SymbolInfoDouble(eaSymbol, SYMBOL_ASK), _Digits)); double atr[1], vol[2], vol_ma[1]; if(CopyBuffer(tfs[i].h_atr, 0, 0, 1, atr) < 1 || CopyBuffer(tfs[i].h_vol, 0, 0, 2, vol) < 2 || CopyBuffer(tfs[i].h_vol_ma, 0, 1, 1, vol_ma) < 1) { Print(EnumToString(tfs[i].tf), " Trade skipped: Indicator copy failed"); continue; } double slPips = atr[0] * 2 / _Point; double lots = CalcLots(eq, CurRisk, slPips); double margReq = SymbolInfoDouble(eaSymbol, SYMBOL_MARGIN_INITIAL) * lots; double freeMarg = AccountInfoDouble(ACCOUNT_MARGIN_FREE); Print(EnumToString(tfs[i].tf), " Lot size: ", DoubleToString(lots, 2), ", Margin required: ", DoubleToString(margReq, 2), ", Free margin: ", DoubleToString(freeMarg, 2), ", SL Pips: ", DoubleToString(slPips, 2), ", Contract size: ", DoubleToString(SymbolInfoDouble(eaSymbol, SYMBOL_TRADE_CONTRACT_SIZE), 0)); if(freeMarg < margReq * 1.2) { Print(EnumToString(tfs[i].tf), " Trade skipped: Margin low (", DoubleToString(freeMarg, 2), " < ", DoubleToString(margReq * 1.2, 2), ")"); continue; } double brkStr = MathAbs(buyBrk ? SymbolInfoDouble(eaSymbol, SYMBOL_ASK) - iHigh(eaSymbol, tfs[i].tf, iHighest(eaSymbol, tfs[i].tf, MODE_HIGH, Brk_Prd, 1)) : iLow(eaSymbol, tfs[i].tf, iLowest(eaSymbol, tfs[i].tf, MODE_LOW, Brk_Prd, 1)) - SymbolInfoDouble(eaSymbol, SYMBOL_BID)) / atr[0]; if(!Bypass && brkStr < dynBrkStr) { Print(EnumToString(tfs[i].tf), " Trade skipped: Breakout strength too low (", DoubleToString(brkStr, 2), " < ", DoubleToString(dynBrkStr, 2), ")"); continue; } if(bullHTF && buyBrk) { double price = SymbolInfoDouble(eaSymbol, SYMBOL_ASK); double sl = price - slPips * _Point; double tp = price + slPips * 2 * _Point; Print(EnumToString(tfs[i].tf), " Attempting Buy: Price=", DoubleToString(price, _Digits), ", SL=", DoubleToString(sl, _Digits), ", TP=", DoubleToString(tp, _Digits), ", Lots=", DoubleToString(lots, 2)); trade.SetExpertMagicNumber(MagicNumber(tfs[i].tf)); if(trade.Buy(lots, eaSymbol, price, sl, tp)) { tfs[i].lastSig = TimeCurrent(); LogTrd(trade.ResultOrder(), eaSymbol, price, sl, tp, "Open", brkStr, vol[1], CurRisk, tfs[i].tf); Print(EnumToString(tfs[i].tf), " Buy opened: Ticket=", trade.ResultOrder()); } else { Print(EnumToString(tfs[i].tf), " Buy failed: Retcode=", trade.ResultRetcode(), ", Error=", GetLastError(), ", Comment=", trade.ResultComment()); } } else if(bearHTF && sellBrk) { double price = SymbolInfoDouble(eaSymbol, SYMBOL_BID); double sl = price + slPips * _Point; double tp = price - slPips * 2 * _Point; Print(EnumToString(tfs[i].tf), " Attempting Sell: Price=", DoubleToString(price, _Digits), ", SL=", DoubleToString(sl, _Digits), ", TP=", DoubleToString(tp, _Digits), ", Lots=", DoubleToString(lots, 2)); trade.SetExpertMagicNumber(MagicNumber(tfs[i].tf)); if(trade.Sell(lots, eaSymbol, price, sl, tp)) { tfs[i].lastSig = TimeCurrent(); LogTrd(trade.ResultOrder(), eaSymbol, price, sl, tp, "Open", brkStr, vol[1], CurRisk, tfs[i].tf); Print(EnumToString(tfs[i].tf), " Sell opened: Ticket=", trade.ResultOrder()); } else { Print(EnumToString(tfs[i].tf), " Sell failed: Retcode=", trade.ResultRetcode(), ", Error=", GetLastError(), ", Comment=", trade.ResultComment()); } } else { Print(EnumToString(tfs[i].tf), " Trade skipped: No valid signal"); } } // Run FVG detection after trade logic to ensure non-interference DetectFVGs(); } void ManageTrades(ENUM_TIMEFRAMES tf) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket) || PositionGetString(POSITION_SYMBOL) != eaSymbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber(tf)) continue; double openPrice, sl, tp, currPrice, lots, profit; if(!PositionGetDouble(POSITION_PRICE_OPEN, openPrice) || !PositionGetDouble(POSITION_SL, sl) || !PositionGetDouble(POSITION_TP, tp) || !PositionGetDouble(POSITION_VOLUME, lots) || !PositionGetDouble(POSITION_PROFIT, profit)) continue; currPrice = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY ? SymbolInfoDouble(eaSymbol, SYMBOL_BID) : SymbolInfoDouble(eaSymbol, SYMBOL_ASK); int idx = TimeframeIndex(tf); if(idx < 0) continue; double atr[1]; if(CopyBuffer(tfs[idx].h_atr, 0, 0, 1, atr) < 1) continue; if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && currPrice >= openPrice + (openPrice - sl) * 3) || (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && currPrice <= openPrice - (sl - openPrice) * 3)) { if(lots > SymbolInfoDouble(eaSymbol, SYMBOL_VOLUME_MIN) * 2) { trade.PositionClosePartial(ticket, lots / 2); trade.PositionModify(ticket, openPrice, tp); Print(EnumToString(tf), " Partial close at 1:3 RR: Ticket=", ticket, ", Lots=", DoubleToString(lots / 2, 2)); } } double trail = atr[0] * 1.5 / _Point; if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && currPrice > openPrice + trail * _Point && sl < currPrice - trail * _Point) { trade.PositionModify(ticket, currPrice - trail * _Point, tp); Print(EnumToString(tf), " Trailing stop updated: Ticket=", ticket, ", New SL=", DoubleToString(currPrice - trail * _Point, _Digits)); } else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && currPrice < openPrice - trail * _Point && sl > currPrice + trail * _Point) { trade.PositionModify(ticket, currPrice + trail * _Point, tp); Print(EnumToString(tf), " Trailing stop updated: Ticket=", ticket, ", New SL=", DoubleToString(currPrice + trail * _Point, _Digits)); } if(profit != 0 && !PositionSelectByTicket(ticket)) { LogTrd(ticket, eaSymbol, openPrice, sl, tp, "Close", 0, 0, CurRisk, tf); Print(EnumToString(tf), " Position closed: Ticket=", ticket, ", Profit=", DoubleToString(profit, 2)); } } } void FetchNewsCalendar() { string url = NewsAPI_Url + NewsAPI_Key; string headers = ""; char post[], result[]; string result_headers; int timeout = 5000; int res = WebRequest("GET", url, headers, timeout, post, result, result_headers); if(res != 200) { Print("News API request failed: HTTP ", res, ", Error=", GetLastError()); newsCal[0].time = StringToTime("2025.07.15 14:30"); newsCal[0].evt = "CPI"; newsCal[0].impact = 90; newsCal[1].time = StringToTime("2025.07.23 20:00"); newsCal[1].evt = "FOMC"; newsCal[1].impact = 90; newsCnt = 2; Print("Using fallback news calendar with ", newsCnt, " events"); return; } string response = CharArrayToString(result); newsCnt = 0; ArrayResize(newsCal, 100); int pos = 0; while(pos >= 0 && newsCnt < 100) { pos = StringFind(response, "\"items\":", pos); if(pos < 0) break; pos = StringFind(response, "{", pos); if(pos < 0) break; int end = StringFind(response, "}", pos); if(end < 0) break; string item = StringSubstr(response, pos, end - pos + 1); string evtName = ExtractJsonField(item, "\"title\":"); string evtTime = ExtractJsonField(item, "\"time_published\":"); string relevance = ExtractJsonField(item, "\"relevance_score\":"); if(evtName != "" && evtTime != "") { string dt = StringSubstr(evtTime, 0, 4) + "." + StringSubstr(evtTime, 4, 2) + "." + StringSubstr(evtTime, 6, 2) + " " + StringSubstr(evtTime, 9, 2) + ":" + StringSubstr(evtTime, 11, 2); newsCal[newsCnt].time = StringToTime(dt); newsCal[newsCnt].evt = evtName; newsCal[newsCnt].impact = (relevance != "") ? (int)(StringToDouble(relevance) * 100) : 80; if(newsCal[newsCnt].impact > 80 && newsCal[newsCnt].time > TimeCurrent() - 7 * 86400) { newsCnt++; Print("News event loaded: ", evtName, " at ", TimeToString(newsCal[newsCnt-1].time), ", Impact=", newsCal[newsCnt-1].impact); } } pos = end + 1; } Print("Loaded ", newsCnt, " high-impact news events from API"); } string ExtractJsonField(string json, string field) { int pos = StringFind(json, field); if(pos < 0) return ""; pos += StringLen(field); if(StringFind(json, "\"", pos) == pos) pos++; int end = StringFind(json, "\"", pos); if(end < 0) return ""; return StringSubstr(json, pos, end - pos); } bool NewBar(ENUM_TIMEFRAMES tf) { int idx = TimeframeIndex(tf); if(idx < 0) return false; datetime cur = iTime(eaSymbol, tf, 0); if(cur != tfs[idx].lastBar) { tfs[idx].lastBar = cur; return true; } return false; } bool BullTrend(ENUM_TIMEFRAMES tf) { int handle = (tf == PERIOD_D1) ? h_ema_d1 : h_ema_h4; double ema[2]; if(CopyBuffer(handle, 0, 1, 2, ema) < 2) { Print("EMA copy failed for ", EnumToString(tf)); return false; } Print("EMA ", EnumToString(tf), ": ", DoubleToString(ema[1], _Digits), " vs ", DoubleToString(ema[0], _Digits)); return ema[1] > ema[0]; } bool BearTrend(ENUM_TIMEFRAMES tf) { int handle = (tf == PERIOD_D1) ? h_ema_d1 : h_ema_h4; double ema[2]; if(CopyBuffer(handle, 0, 1, 2, ema) < 2) { Print("EMA copy failed for ", EnumToString(tf)); return false; } Print("EMA ", EnumToString(tf), ": ", DoubleToString(ema[1], _Digits), " vs ", DoubleToString(ema[0], _Digits)); return ema[1] < ema[0]; } bool BuyBrk(ENUM_TIMEFRAMES tf) { double high = iHigh(eaSymbol, tf, iHighest(eaSymbol, tf, MODE_HIGH, Brk_Prd, 1)); double price = SymbolInfoDouble(eaSymbol, SYMBOL_ASK); Print(EnumToString(tf), " BuyBrk check: Price=", DoubleToString(price, _Digits), ", High=", DoubleToString(high, _Digits)); return price > high; } bool SellBrk(ENUM_TIMEFRAMES tf) { double low = iLow(eaSymbol, tf, iLowest(eaSymbol, tf, MODE_LOW, Brk_Prd, 1)); double price = SymbolInfoDouble(eaSymbol, SYMBOL_BID); Print(EnumToString(tf), " SellBrk check: Price=", DoubleToString(price, _Digits), ", Low=", DoubleToString(low, _Digits)); return price < low; } double CalcLots(double eq, double riskPct, double slPips) { double pipVal = SymbolInfoDouble(eaSymbol, SYMBOL_TRADE_TICK_VALUE); double riskAmt = MathMin(eq * (riskPct / 100), MaxLossUSD); double lots = riskAmt / (slPips * pipVal); double minLot = SymbolInfoDouble(eaSymbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(eaSymbol, SYMBOL_VOLUME_MAX); lots = NormalizeDouble(MathMax(minLot, MathMin(maxLot, lots)), 2); Print("CalcLots: Equity=", DoubleToString(eq, 2), ", Risk%=", DoubleToString(riskPct, 2), ", SL Pips=", DoubleToString(slPips, 2), ", PipVal=", DoubleToString(pipVal, 2), ", Lots=", DoubleToString(lots, 2), ", MinLot=", DoubleToString(minLot, 2), ", MaxLot=", DoubleToString(maxLot, 2)); return lots; } bool IsNews() { if(newsCnt == 0 && NewsFilt) { Print("No news events loaded, bypassing news filter"); return false; } datetime now = TimeCurrent(); Print("News check: Current time=", TimeToString(now, TIME_DATE|TIME_MINUTES)); for(int i = 0; i < newsCnt; i++) { if(now >= newsCal[i].time - NewsPause * 60 && now <= newsCal[i].time + NewsPause * 60 && newsCal[i].impact > 80) { Print("News event active: ", newsCal[i].evt, " at ", TimeToString(newsCal[i].time, TIME_DATE|TIME_MINUTES)); return true; } } Print("No active news events"); return false; } void LogTrd(ulong ticket, string sym, double price, double sl, double tp, string stat, double brkStr, double vol, double risk, ENUM_TIMEFRAMES tf) { string data = StringFormat("T=%I64u,S=%s,Tm=%s,P=%f,SL=%f,TP=%f,St=%s,BrkStr=%f,Vol=%f,Risk=%f,TF=%s", ticket, sym, TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES), price, sl, tp, stat, brkStr, vol, risk, EnumToString(tf)); if(StringLen(GS_Url) > 0) Print("Webhook pending: ", data); Print("Trd: ", data); if(stat == "Close" && tradeCnt < ArraySize(tradeHistory)) { double profit; if(PositionSelectByTicket(ticket)) { PositionGetDouble(POSITION_PROFIT, profit); tradeHistory[tradeCnt].ticket = ticket; tradeHistory[tradeCnt].isWin = profit > 0; tradeHistory[tradeCnt].profit = profit; tradeHistory[tradeCnt].brkStr = brkStr; tradeHistory[tradeCnt].vol = vol; tradeHistory[tradeCnt].risk = risk; tradeHistory[tradeCnt].tf = tf; tradeCnt++; UpdateWinLossStreak(); AdjustBreakoutStrength(); } } } void UpdateWinLossStreak() { if(tradeCnt > 0) { if(tradeHistory[tradeCnt-1].isWin) { winStreak++; lossStreak = 0; } else { lossStreak++; winStreak = 0; } } } void AdjustBreakoutStrength() { if(tradeCnt < 10) return; int lossCnt = 0; double avgBrkStr = 0; for(int i = tradeCnt - 10; i < tradeCnt; i++) { if(!tradeHistory[i].isWin) lossCnt++; avgBrkStr += tradeHistory[i].brkStr; } avgBrkStr /= 10; if(lossCnt >= 5 && avgBrkStr < MinBrkStr * 1.5) dynBrkStr = MinBrkStr * 1.5; else if(lossCnt <= 2) dynBrkStr = MinBrkStr; Print("Breakout strength adjusted: dynBrkStr=", DoubleToString(dynBrkStr, 2)); } long MagicNumber(ENUM_TIMEFRAMES tf) { if(tf == PERIOD_M5) return 1005; if(tf == PERIOD_M15) return 1015; if(tf == PERIOD_H1) return 1060; return 0; } int TimeframeIndex(ENUM_TIMEFRAMES tf) { for(int i = 0; i < ArraySize(tfs); i++) { if(tfs[i].tf == tf) return i; } return -1; }
Установка и тестирование на истории: скомпилируйте и прикрепите к графику. Тестирование на истории по GOLD M15 (2025) с риском 2%.
Тестирование стратегий
Эта стратегия наиболее эффективна для золота из-за его относительно быстрых колебаний и высокой волотильности, что выгодно для внутридневной розничной торговли. Протестируем эту стратегию, торгую золотом с 1 января 2025 года до 29 июля 2025 года на 15-минутном (M15) таймфрейме. Вот параметры, которые я выбрал для этой стратегии.
Результаты тестера стратегий
Вот результаты тестирования в тестере стратегий.
- График баланса/эквити:
- Результаты тестирования на истории:
Выводы
Я написал эту статью, чтобы попытаться объяснить советник в MetaTrader 5, который сочетает в себе использование обнаружения разрывов в реальной стоимости (Fair Value Gaps, FVG) и сдвиги в структуре рынка (Market Structure Shift, MSS) для выявления высоковероятных торговых настроек на золоте. Разрывы в реальной стоимости — одна из наиболее ценных и стандартных стратегий типа Smart Money Concepts (концепции «умных денег»), используемая для выявления проявлений неэффективности цен и смещений трендов.
Я протестировал советника на золоте, и он показал свою способность эффективно и точно обнаруживать FVG на таймфреймах M15 и H1, но обнаружение разрывов реальной стоимости — это лишь часть уравнения, потому что если сдвиг рыночной структуры не происходит, то предполагается, что сделки не будут совершаться, даже если разрывы в реальной стоимости действительны. Эти изменения в структуре рынка являются подтверждениями, которые помогают повысить точность и качество торговых операций во время волатильных сессий.
Для внедрения этой стратегии настройте входные параметры советника, как показано ниже, чтобы получить желаемые результаты. Советник предназначен для сканирования графиков M15 или H1 в поисках FVG, обеспечивая соответствие сдвигов в структуре рынка трендам более высоких таймфреймов (например, H4 или D1). Заинтересованным пользователям следует провести тестирование этого советника на истории своих демо-счетов по золоту. Мои главные задача и цель при создании советника заключались в том, чтобы оптимизировать его для выбранных высоковероятных настроек торговли, включающих управление рисками — поддержание риска на уровне 0.5-2% нна сделку, — и трейлинг-стопы.
Кроме того, я бы посоветовал пользователям регулярно просматривать журналы производительности, чтобы корректировать настройки и входные параметры в зависимости от своих целей или склонности к риску. Предупреждение. Всякому, кто использует этот советник, следует сначала протестировать его и начать торговлю на своем демо-счете, чтобы освоить такой институциональный подход и получать стабильную прибыль, прежде чем рискнуть реальными средствами.
Заключение
Основная идея и акцент в этой статье состоят в том, чтобы попытаться четко объяснить, что представляют собой разрывы в реальной стоимости, где они возникают, как они возникают, почему они возникают и, наконец, как их можно оптимально использовать для анализа, понимания и даже совершения сделок с их применением.
Большинство начинающих трейдеров и даже некоторые трейдеры среднего уровня понятия не имеют, как ориентироваться в этом запутанном мире разрывов в реальной стоимости, и даже испытывают разочарование, поскольку не могут по-настоящему осознать, что там происходит, или даже испытывают недостаток проницательности, чтобы понять, как это цена всегда возвращается и как это она использует такие разрывы для совершения классических сделок и исполнения ордеров, которые действительно могут быть для них прибыльными. Или, даже если трейдеры не совершают сделок на основе разрывов реальной стоимости, они смогут подтвердить правильность собственных сделок, собсттвенных торговых стратегий и даже тренда и направления, исходя из того, что я описал в этой статье, и им будет очень интересно узнать, какую колоссальную и важную роль играют эти самые разрывы в реальной стоимости.
Автоматизация на языке MQL5 позволяет трейдерам снизить эмоциональную предвзятость, обеспечивая стабильное выполнение стратегий FVG + Breaker/MSS.
Ниже прилагаетс весь код, упомянутый в этой статье. В следующей таблице описаны все файлы исходного кода, прилагаемые к статье.| Название файла | Описание |
|---|---|
| Fvg_detector.mq5 | Файл, содержащий код для обнаружения разрыва в реальной стоимости |
| fvg-mss.mq5 | Файл, содержащий код для всего комбинированного советника, обнаружения разрывов в реальной стоимости и сдвигов в структуре рынка |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18669
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Реализация механизма безубыточности в MQL5 (Часть 2): Безубыток на основе ATR и RRR
Математика волатильности: Почему индикатор GRI достоин возвращения в ваш торговый терминал
От новичка до эксперта: Алгоритмическая дисциплина трейдера — советник Risk Enforcer вместо эмоций
Алгоритм поисковой оптимизации Эбола — Ebola Optimization Search Algorithm (EOSA)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Мило 👍.