Автоматизация индикатора настроений рынка (индикатора сентимента)
Введение
В предыдущей статье мы разработали собственный индикатор рыночных настроений, который объединяет сигналы из нескольких таймфреймов в единую, легко читаемую панель. Мы проанализировали тренды на более высоких таймфреймах наряду со структурами на более низких таймфреймах, изучив динамику цен, максимумы/минимумы колебаний, скользящие средние и волатильность, чтобы разделить рынок на пять четких типов в соответствии с его настроениями: бычий, медвежий, склонный к риску, не склонный к риску, нейтральный.
Автоматизация пользовательского индикатора дает ряд ключевых преимуществ в торговле. Она обеспечивает непрерывный и объективный мониторинг настроений без эмоциональной предвзятости, что позволяет быстрее адаптировать сделки к изменениям на рынке. Можно поддерживать согласованность анализа на разных таймфреймах, выявлять ранние возможности благодаря систематическому обнаружению сигналов прорыва или подтверждения тренда, а также более эффективно распределять риски. В итоге автоматизация превращает трудоемкий и подверженный ошибкам процесс в эффективный экспертный советник, поддерживающий принятие решений на основе данных.
Логика выполнения
Бычьи настроения / Склонность к риску
Бычьи настроения отражают сильный восходящий импульс, когда тренды на более высоких таймфреймах и ценовые структуры подтверждают, что покупатели контролируют ситуацию, в то время как склонность к риску указывает на повышение уверенности инвесторов и их готовность к риску, что часто приводит к росту цен. В обоих случаях рыночная конъюнктура поддерживает восходящее движение, и наша стратегия заключается в совершении сделки на покупку всякий раз, когда индикатор сигнализирует о бычьем настроении или склонности к риску, поскольку в этом случае более вероятно продолжение роста цены.

Медвежьи настроения / Неприятие риска
Медвежьи настроения отражают сильный нисходящий импульс, когда тренды на более высоких таймфреймах и ценовые структуры подтверждают доминирование продавцов на рынке, в то время как снижение склонности к риску сигнализирует о переходе к более безопасным действиям, когда инвесторы избегают рискованных активов, что приводит к снижению цен. В обоих случаях рыночная конъюнктура поддерживает нисходящее движение, и наша стратегия заключается в совершении сделки на продажу всякий раз, когда индикатор сигнализирует о медвежьих настроениях или неприятии риска, поскольку в этом случае более вероятно продолжение падения цены.

Нейтральное настроение
Нейтральные или часто меняющиеся настроения возникают, когда рынку недостает четкого направления, при этом цены консолидируются в некотором диапазоне или непредсказуемо колеблются между уровнями. В такие периоды неопределенности вероятность ложных сигналов и резких колебаний цены выше, поэтому используем показатель нейтральности в качестве фильтра, чтобы избежать входа в сделки. В подобных условиях, воздерживаясь от выхода на рынок, мы защищаем капитал и, прежде чем предпринимать какие-либо действия, дожидаемся более четкого сигнала о бычьем или медвежьем тренде, склонности к риску или неприятии риска.

Начало работы
//+------------------------------------------------------------------+ //| Mark_Sent_Auto.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #include <Trade/Trade.mqh> CTrade trade;
В этом коде строка #include <Trade/Trade.mqh> включает в себя торговую библиотеку MetaTrader 5, которая предоставляет встроенный класс CTrade. Этот класс содержит все важнейшие функции для автоматизации исполнения торговых операций, такие как открытие, закрытие, изменение или управление позициями и ордерами. После подключения библиотеки создаем экземпляр класса с классом CTrade trade;. Этот объект служит нашим интерфейсом для торговой системы — через него мы можем вызывать такие методы, как trade.Buy(), trade.Sell() или trade.PositionClose(). Поскольку наша цель — автоматизировать принятие торговых решений на основе индикатора настроения рынка, для фактической отправки и управления сделками программным способом необходимо подключить эту библиотеку и определить объект CTrade.
//+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ input group "Timeframe Settings" input ENUM_TIMEFRAMES HigherTF = PERIOD_H4; // Higher Timeframe input ENUM_TIMEFRAMES LowerTF1 = PERIOD_H1; // Lower Timeframe 1 input ENUM_TIMEFRAMES LowerTF2 = PERIOD_M30; // Lower Timeframe 2 input group "Indicator Settings" input int MAPeriod = 200; // EMA Period input int SwingLookback = 5; // Swing Lookback Bars input double ATRThreshold = 0.002; // ATR Threshold for Neutral input group "Trading Settings" input double LotSize = 0.1; // Trade Lot Size input int StopLossPips = 50; // Stop Loss in Pips input int TakeProfitPips = 100; // Take Profit in Pips input int MagicNumber = 12345; // Magic Number for trades input int Slippage = 3; // Slippage in points input group "Trailing Stop Parameters" input bool UseTrailingStop = true; // Enable trailing stops input int BreakEvenAtPips = 500; // Move to breakeven at this profit (pips) input int TrailStartAtPips = 600; // Start trailing at this profit (pips) input int TrailStepPips = 100; // Trail by this many pips input group "Risk Management" input bool UseRiskManagement = true; // Enable Risk Management input double RiskPercent = 2.0; // Risk Percentage per Trade input int MinBarsBetweenTrades = 3; // Minimum bars between trades input group "Visual Settings" input bool ShowPanel = true; // Show Information Panel input int PanelCorner = 1; // Panel Corner: 0=TopLeft,1=TopRight,2=BottomLeft,3=BottomRight input int PanelX = 10; // Panel X Position input int PanelY = 10; // Panel Y Position input string FontFace = "Arial"; // Font Face input int FontSize = 10; // Font Size input color BullishColor = clrLimeGreen; // Bullish Color input color BearishColor = clrRed; // Bearish Color input color RiskOnColor = clrDodgerBlue; // Risk-On Color input color RiskOffColor = clrDarkGray; // Risk-Off Color input color NeutralColor = clrGold; // Neutral Color
В первом разделе сгруппированы входные данные, относящиеся к настройке таймфрейма и параметрам индикаторов. Определив уровни HigherTF, LowerTF1 и LowerTF2, советник может анализировать рыночные настроения на нескольких уровнях ценового движения, например на более высоком таймфрейме для определения направления тренда и на более низких таймфреймах для подтверждения структуры. В настройках индикатора такие параметры, как MAPeriod (период для экспоненциальной скользящей средней), SwingLookback (количество баров для проверки максимумов и минимумов колебаний) и ATRThreshold (используется для выявления состояния низкой волатильности рынка), предоставляют трейдерам возможность гибко настраивать способы определения рыночных настроений.
Второй раздел регулирует торговое поведение и управление рисками. Такие входные характеристики, как LotSize, StopLossPips и TakeProfitPips, определяют основные параметры исполнения сделок, MagicNumber помогает советнику отличать свои сделки от других, а Slippage регулирует допустимые отклонения при исполнении ордера. Параметры трейлинг-стопа (UseTrailingStop, BreakEvenAtPips, TrailStartAtPips, TrailStepPips) позволяют динамически защищать прибыль при благоприятном движении цены сделки. Кроме того, блок управления рисками (UseRiskManagement, RiskPercent, MinBarsBetweenTrades) обеспечивает соответствие размера позиции капиталу счета, предотвращает чрезмерную подверженность риску и устанавливает минимальный интервал между сделками для обеспечения торговой дисциплины.
В заключительном разделе основное внимание уделяется визуальному представлению индикатора рыночных настроений. Такие опции, как ShowPanel, PanelCorner, PanelX и PanelY, позволяют трейдеру контролировать местоположение панели мониторинга настроений на графике. Возможность пользовательской настройки шрифта (FontFace, FontSize) обеспечивает удобочитаемость, а цветовая кодировка (BullishColor, BearishColor, RiskOnColor, RiskOffColor, NeutralColor) мгновенно выдает визуальные подсказки для каждого типа рыночных настроений. Такая пользовательская настройка повышает ясность и удобство использования, позволяя трейдерам быстро оценивать рыночную ситуацию с первого взгляда.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int higherTFHandle, lowerTF1Handle, lowerTF2Handle; double higherTFMA[], lowerTF1MA[], lowerTF2MA[]; datetime lastUpdateTime = 0; string indicatorName = "MarketSentEA"; int currentSentiment = 0; int previousSentiment = 0; datetime lastTradeTime = 0; double global_PipValueInPoints; // Stores pip value in points //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { indicatorName = "MarketSentEA_" + IntegerToString(ChartID()); // Create handles for MA indicators on different timeframes higherTFHandle = iMA(_Symbol, HigherTF, MAPeriod, 0, MODE_EMA, PRICE_CLOSE); lowerTF1Handle = iMA(_Symbol, LowerTF1, MAPeriod, 0, MODE_EMA, PRICE_CLOSE); lowerTF2Handle = iMA(_Symbol, LowerTF2, MAPeriod, 0, MODE_EMA, PRICE_CLOSE); // Set array as series ArraySetAsSeries(higherTFMA, true); ArraySetAsSeries(lowerTF1MA, true); ArraySetAsSeries(lowerTF2MA, true); //global_PipValueInPoints = GetPipValueInPoints(); // Create panel if enabled if(ShowPanel) CreatePanel(); // Check for trading permission if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) Print("Error: Trading is not allowed! Check AutoTrading permission."); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Delete all objects ObjectsDeleteAll(0, indicatorName); IndicatorRelease(higherTFHandle); IndicatorRelease(lowerTF1Handle); IndicatorRelease(lowerTF2Handle); }
Здесь мы определяем глобальные переменные, которые советник будет использовать на протяжении всего своего исполнения. Дескрипторы (higherTFHandle, lowerTF1Handle и lowerTF2Handle) создаются для хранения ссылок на индикаторы скользящих средних (MA) на разных таймфреймах. Массивы (higherTFMA, lowerTF1MA, lowerTF2MA) содержат вычисленные значения этих показателей для последующего анализа. Такие переменные, как lastUpdateTime, lastTradeTime и currentSentiment/previousSentiment, помогают отслеживать, когда в последний раз были выполнены расчеты или совершены сделки, и управлять изменениями в настроении рынка. Строка indicatorName однозначно идентифицирует объекты, отображаемые на графике, а global_PipValueInPoints будет хранить данные о переводе пипсов в пункты для расчета риска и размера сделки.
Функция OnInit() инициализирует советника. Она задает уникальное имя индикатора (привязанное к идентификатору графика), создает хэндлы скользящих средних для выбранных таймфреймов и настраивает массивы для данных из временных рядов. Кроме того, она включает в себя логику для создания панели. Функция OnDeinit() отвечает за очистку при удалении советника — удаление объектов на графике, связанных с индикатором, и освобождение дескрипторов скользящих средних для высвобождения ресурсов. Вместе эти функции обеспечивают корректный запуск советника и завершение его работы без образования лишних файлов и ненужного потребления памяти.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update only on new bar or every 10 seconds if(TimeCurrent() - lastUpdateTime < 10) return; lastUpdateTime = TimeCurrent(); // Get MA values if(CopyBuffer(higherTFHandle, 0, 0, 3, higherTFMA) < 3 || CopyBuffer(lowerTF1Handle, 0, 0, 3, lowerTF1MA) < 3 || CopyBuffer(lowerTF2Handle, 0, 0, 3, lowerTF2MA) < 3) { Print("Error copying indicator buffers"); return; } // Get current prices double higherTFPrice = iClose(_Symbol, HigherTF, 0); double lowerTF1Price = iClose(_Symbol, LowerTF1, 0); double lowerTF2Price = iClose(_Symbol, LowerTF2, 0); // Determine higher timeframe bias int higherTFBias = GetHigherTFBias(higherTFPrice, higherTFMA[0]); // Determine lower timeframe structures bool lowerTF1Bullish = IsBullishStructure(LowerTF1, SwingLookback); bool lowerTF1Bearish = IsBearishStructure(LowerTF1, SwingLookback); bool lowerTF2Bullish = IsBullishStructure(LowerTF2, SwingLookback); bool lowerTF2Bearish = IsBearishStructure(LowerTF2, SwingLookback); // Check for breakouts (BOS) bool lowerTF1Breakout = HasBreakout(LowerTF1, SwingLookback, higherTFBias); bool lowerTF2Breakout = HasBreakout(LowerTF2, SwingLookback, higherTFBias); // Determine final sentiment previousSentiment = currentSentiment; currentSentiment = DetermineSentiment( higherTFBias, lowerTF1Bullish, lowerTF1Bearish, lowerTF1Breakout, lowerTF2Bullish, lowerTF2Bearish, lowerTF2Breakout ); // Update panel if enabled if(ShowPanel) UpdatePanel(higherTFBias, currentSentiment); // Check if we should trade CheckForTradingOpportunity(); if(UseTrailingStop) ManageOpenTrades(); }
Функция OnTick() — это основной цикл выполнения советника, который запускается каждый раз, когда рынок генерирует новый тик. Для оптимизации производительности обновление происходит только раз в 10 секунд или при появлении нового бара, что предотвращает ненужные вычисления. Сначала с помощью функции CopyBuffer извлекаются значения скользящих средних из заданных таймфреймов, а затем сравниваются с текущими ценами, полученными с помощью функции iClose(). На основе этих данных определяется направленность более высокого таймфрейма, в то время как на более низких таймфреймах выявляются бычьи и медвежьи структуры, а также потенциальные пробои.
Затем функция определяет общее рыночное настроение, комбинируя эти условия, обновляет панель на графике, если она включена, и проверяет наличие потенциальных торговых возможностей. Наконец, если активны трейлинг-стопы, функция динамически управляет открытыми сделками для защиты прибыли. Это обеспечивает, чтобы советник постоянно, в структурированном порядке и автоматически осуществлял анализ рыночных настроений, обновление визуальных элементов и совершение сделок.
//+------------------------------------------------------------------+ //| Determine higher timeframe bias | //+------------------------------------------------------------------+ int GetHigherTFBias(double price, double maValue) { double deviation = MathAbs(price - maValue) / maValue; if(price > maValue && deviation > ATRThreshold) return 1; // Bullish else if(price < maValue && deviation > ATRThreshold) return -1; // Bearish else return 0; // Neutral } //+------------------------------------------------------------------+ //| Check for bullish structure (HH, HL) | //+------------------------------------------------------------------+ bool IsBullishStructure(ENUM_TIMEFRAMES tf, int lookback) { // Get swing highs and lows int swingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback * 2, 1); int swingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback * 2, 1); // Get previous swings for comparison int prevSwingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback, lookback + 1); int prevSwingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback, lookback + 1); if(swingHighIndex == -1 || swingLowIndex == -1 || prevSwingHighIndex == -1 || prevSwingLowIndex == -1) return false; double swingHigh = iHigh(_Symbol, tf, swingHighIndex); double swingLow = iLow(_Symbol, tf, swingLowIndex); double prevSwingHigh = iHigh(_Symbol, tf, prevSwingHighIndex); double prevSwingLow = iLow(_Symbol, tf, prevSwingLowIndex); // Check for higher high and higher low return (swingHigh > prevSwingHigh && swingLow > prevSwingLow); } //+------------------------------------------------------------------+ //| Check for bearish structure (LH, LL) | //+------------------------------------------------------------------+ bool IsBearishStructure(ENUM_TIMEFRAMES tf, int lookback) { // Get swing highs and lows int swingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback * 2, 1); int swingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback * 2, 1); // Get previous swings for comparison int prevSwingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback, lookback + 1); int prevSwingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback, lookback + 1); if(swingHighIndex == -1 || swingLowIndex == -1 || prevSwingHighIndex == -1 || prevSwingLowIndex == -1) return false; double swingHigh = iHigh(_Symbol, tf, swingHighIndex); double swingLow = iLow(_Symbol, tf, swingLowIndex); double prevSwingHigh = iHigh(_Symbol, tf, prevSwingHighIndex); double prevSwingLow = iLow(_Symbol, tf, prevSwingLowIndex); // Check for lower high and lower low return (swingHigh < prevSwingHigh && swingLow < prevSwingLow); } //+------------------------------------------------------------------+ //| Check for breakout of structure | //+------------------------------------------------------------------+ bool HasBreakout(ENUM_TIMEFRAMES tf, int lookback, int higherTFBias) { // Get recent swing points int swingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback, 1); int swingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback, 1); if(swingHighIndex == -1 || swingLowIndex == -1) return false; double swingHigh = iHigh(_Symbol, tf, swingHighIndex); double swingLow = iLow(_Symbol, tf, swingLowIndex); double currentPrice = iClose(_Symbol, tf, 0); // Check for breakout based on higher timeframe bias if(higherTFBias == 1) // Bullish bias - look for breakout above swing high return (currentPrice > swingHigh); else if(higherTFBias == -1) // Bearish bias - look for breakout below swing low return (currentPrice < swingLow); return false; }
Функция GetHigherTFBias() сравнивает текущую цену со значением скользящей средней на более высоком таймфрейме, чтобы определить преобладающий тренд. Вычисление относительного отклонения между этими двумя значениями позволяет ей гарантировать, что небольшие колебания вблизи скользящей средней рассматриваются как нейтральные. Если цена значительно выше скользящей средней, функция возвращает 1, указывая на бычий тренд, а если цена значительно ниже, возвращает -1, указывая на медвежий тренд. Если ни одно из условий не выполняется, смещение по умолчанию равно 0, что соответствует нейтральному состоянию. Это позволяет эксперту получить четкое представление о направлении развития рынка перед анализом более коротких таймфреймов.
Функции IsBullishStructure() и IsBearishStructure() анализируют максимумы и минимумы колебаний на указанном таймфрейме для подтверждения структурных паттернов. Для бычьей структуры код проверяет, превышает ли последний максимум колебания предыдущий максимум колебаний, а также превышает ли последний минимум колебаний предыдущий минимум колебаний — это указывает на более высокие максимумы и более высокие минимумы (HH, HL). И наоборот, медвежий сигнал подтверждает более низкие максимумы и более низкие минимумы (LH, LL). Эти структурные проверки добавляют еще один уровень подтверждения, гарантируя, что сделки соответствуют не только направлению движения скользящих средних, но и фактической структуре рынка.
Функция HasBreakout() отслеживает прорыв цены из недавних уровней колебаний в направлении тренда более высокого таймфрейма. Например, бычий тренд подтверждает, что текущая цена пробила последний максимум колебаний, сигнализируя о продолжении роста. При медвежьем тренде проверяется, пробила ли цена последний минимум колебаний. Если явного смещения нет, ни один из пробоев не считается подтвержденным. Эта функция предотвращает преждевременные сделки, гарантируя, что входы в сделки совершаются только тогда, когда движение цены соответствует как структуре, так и импульсу на более высоком таймфрейме, что повышает надежность сигналов.
//+------------------------------------------------------------------+ //| Determine final sentiment | //+------------------------------------------------------------------+ int DetermineSentiment(int higherTFBias, bool tf1Bullish, bool tf1Bearish, bool tf1Breakout, bool tf2Bullish, bool tf2Bearish, bool tf2Breakout) { // Bullish sentiment if(higherTFBias == 1 && tf1Bullish && tf2Bullish) return 1; // Bullish // Bearish sentiment if(higherTFBias == -1 && tf1Bearish && tf2Bearish) return -1; // Bearish // Risk-on sentiment if(higherTFBias == 1 && (tf1Breakout || tf2Breakout)) return 2; // Risk-On // Risk-off sentiment if(higherTFBias == -1 && (tf1Breakout || tf2Breakout)) return -2; // Risk-Off // Neutral/choppy sentiment return 0; // Neutral } //+------------------------------------------------------------------+ //| Check for trading opportunity | //+------------------------------------------------------------------+ void CheckForTradingOpportunity() { // Check if enough time has passed since last trade if(TimeCurrent() - lastTradeTime < PeriodSeconds(_Period) * MinBarsBetweenTrades) return; // Check if sentiment has changed if(currentSentiment == previousSentiment) return; // Close existing positions if sentiment changed significantly if((previousSentiment > 0 && currentSentiment < 0) || (previousSentiment < 0 && currentSentiment > 0)) { CloseAllPositions(); } // Execute new trades based on sentiment switch(currentSentiment) { case 1: // Bullish - Buy case 2: // Risk-On - Buy ExecuteTrade(ORDER_TYPE_BUY, _Symbol); lastTradeTime = TimeCurrent(); break; case -1: // Bearish - Sell case -2: // Risk-Off - Sell ExecuteTrade(ORDER_TYPE_SELL, _Symbol); lastTradeTime = TimeCurrent(); break; case 0: // Neutral - Close all positions CloseAllPositions(); break; } } //+------------------------------------------------------------------+ //| Execute trade with risk parameters | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); // Convert StopLoss and TakeProfit from pips to actual price distances double sl_distance = StopLossPips * point; double tp_distance = TakeProfitPips * point; double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance : price + sl_distance; double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance : price - tp_distance; trade.PositionOpen(symbol, tradeType, LotSize, price, sl, tp, NULL); }
Функция DetermineSentiment() объединяет сигналы из более высоких и более низких таймфреймов в единую классификацию рыночных настроений. Если на более высоком таймфрейме преобладает бычий настрой и оба более низких таймфрейма подтверждают бычьи структуры, функция возвращает бычье настроение рынка. Аналогично, если все условия складываются в пользу медвежьего тренда, это сигнализирует о медвежьих настроениях. Когда на более высоком таймфрейме наблюдается бычий тренд, но на более низких таймфреймах происходят только пробои, это указывает на склонность к риску, а медвежий тренд с пробоями сигнализирует о снижении склонности к риску. Если ни одно из этих условий не выполняется, функция по умолчанию принимает нейтральное значение, что указывает на колеблющийся или нерешительный рынок. Эта структурированная иерархия обеспечивает согласованность настроений, учитывает контекст и отражает как тренды, так и условия пробоя.
Функция CheckForTradingOpportunity() действует в соответствии с сигналами настроения рынка для автоматизации сделок. Она обеспечивает соблюдение таких правил, как распределение сделок во времени (MinBarsBetweenTrades) и реагирование только при изменении настроения рынка по сравнению с предыдущим состоянием. Если настроения рынка меняют направление, существующие позиции закрываются до открытия новых сделок, что обеспечивает соответствие последним рыночным прогнозам. В зависимости от настроения рынка, советник исполняет ордера на покупку при бычьих или склонных к риску условиях, ордера на продажу при медвежьих или не склонных к риску условиях или закрывает все сделки в нейтральных фазах. Функция ExecuteTrade() управляет размещением ордеров с учетом рисков, рассчитывая уровни стоп-лосса и тейк-профита в ценовом выражении, а вспомогательная функция PipsToPrice() обеспечивает совместимость с символами, использующими различные форматы пипсов (например, 3—5-значные цены). Вместе эти функции создают замкнутый цикл от определения настроений рынка до автоматизированного исполнения торговых операций с управлением рисками.
//+------------------------------------------------------------------+ //| Trailing stop function | //+------------------------------------------------------------------+ void ManageOpenTrades() { if(!UseTrailingStop) return; int total = PositionsTotal(); for(int i = total - 1; i >= 0; i--) { // get ticket (PositionGetTicket returns ulong; it also selects the position) ulong ticket = PositionGetTicket(i); // correct usage. :contentReference[oaicite:3]{index=3} if(ticket == 0) continue; // ensure the position is selected (recommended) if(!PositionSelectByTicket(ticket)) continue; // Optional: only operate on same symbol or your EA's magic number if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; // if(PositionGetInteger(POSITION_MAGIC) != MyMagicNumber) continue; // read position properties AFTER selecting double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price= PositionGetDouble(POSITION_PRICE_CURRENT); // current price for the position. :contentReference[oaicite:4]{index=4} double current_sl = PositionGetDouble(POSITION_SL); double current_tp = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // pip size double pip_price = PipsToPrice(1); // profit in pips (use current_price returned above) double profit_price = (pos_type == POSITION_TYPE_BUY) ? (current_price - open_price) : (open_price - current_price); double profit_pips = profit_price / pip_price; if(profit_pips <= 0) continue; // get broker min stop distance (in price units) double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double stop_level_points = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); // integer property. :contentReference[oaicite:5]{index=5} double stopLevelPrice = stop_level_points * point; // get market Bid/Ask for stop-level checks double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // ------------------------- // 1) Move to breakeven // ------------------------- if(profit_pips >= BreakEvenAtPips) { double breakeven = open_price; // small adjustment to help account for spread/commissions (optional) if(pos_type == POSITION_TYPE_BUY) breakeven += point; else breakeven -= point; // Check stop-level rules: for BUY SL must be >= (bid - stopLevelPrice) distance below bid if(pos_type == POSITION_TYPE_BUY) { if((bid - breakeven) >= stopLevelPrice) // allowed by server { if(breakeven > current_sl) // only move SL up { if(!trade.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp)) PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError()); } } } else // SELL { if((breakeven - ask) >= stopLevelPrice) { if(current_sl == 0.0 || breakeven < current_sl) // move SL down { if(!trade.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp)) PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError()); } } } } // end breakeven // ------------------------- // 2) Trailing in steps after TrailStartAtPips // ------------------------- if(profit_pips >= TrailStartAtPips) { double extra_pips = profit_pips - TrailStartAtPips; int step_count = (int)(extra_pips / TrailStepPips); // compute desired SL relative to open_price (as per your original request) double desiredOffsetPips = (double)(TrailStartAtPips + step_count * TrailStepPips); double new_sl_price; if(pos_type == POSITION_TYPE_BUY) { new_sl_price = open_price + PipsToPrice((int)desiredOffsetPips); // ensure new SL respects server min distance from current Bid if((bid - new_sl_price) < stopLevelPrice) new_sl_price = bid - stopLevelPrice; if(new_sl_price > current_sl) // only move SL up { if(!trade.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp)) PrintFormat("PositionModify failed (Trail Buy) ticket %I64u error %d", ticket, GetLastError()); } } else // SELL { new_sl_price = open_price - PipsToPrice((int)desiredOffsetPips); // ensure new SL respects server min distance from current Ask if((new_sl_price - ask) < stopLevelPrice) new_sl_price = ask + stopLevelPrice; if(current_sl == 0.0 || new_sl_price < current_sl) // only move SL down (more profitable) { if(!trade.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp)) PrintFormat("PositionModify failed (Trail Sell) ticket %I64u error %d", ticket, GetLastError()); } } } } } //+------------------------------------------------------------------+ //| Close all positions | //+------------------------------------------------------------------+ void CloseAllPositions() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber) { MqlTradeRequest request = {}; MqlTradeResult result = {}; request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = PositionGetDouble(POSITION_VOLUME); request.type = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; request.price = SymbolInfoDouble(_Symbol, (request.type == ORDER_TYPE_BUY) ? SYMBOL_ASK : SYMBOL_BID); request.deviation = Slippage; request.magic = MagicNumber; request.comment = "MarketSentEA Close"; if(OrderSend(request, result)) { if(result.retcode == TRADE_RETCODE_DONE) Print("Position closed successfully. Ticket: ", ticket); else Print("Error closing position. Code: ", result.retcode); } } } } }
В этом разделе советника основное внимание уделяется двум ведущим механизмам управления рисками: управлению трейлинг-стопом и принудительному закрытию позиций. Функция ManageOpenTrades() сначала обеспечивает применение трейлинг-стопов только при их включении, а затем перебирает все открытые позиции в цикле. Она проверяет прибыльность в пипсах, устанавливает стоп-лосс на уровне безубыточности после достижения минимальной прибыли и продолжает использовать ступенчатый трейлинг по мере того, как позиция приближается к прибыльной. Ограничения брокеров на уровне стоп-ордеров и необходимость учитывать спреды интегрированы для обеспечения обоснованности и безопасности всех изменений стоп-ордеров.
В совокупности эти функции создают надежную систему выхода и защиты для сделок, снижая риски и доводя прибыли до максимума. Благодаря сочетанию логики безубыточности, ступенчатого трейлинга и надежного механизма закрытия всех позиций, когда этого требуют настроения рынка или правила стратегии, советник может защитить капитал в неблагоприятных условиях и зафиксировать прибыль, когда сделки движутся в благоприятном направлении. В конечном итоге это повышает стабильность, устойчивость и долгосрочную прибыльность стратегии.
//+------------------------------------------------------------------+ //| Calculate lot size with risk management | //+------------------------------------------------------------------+ double CalculateLotSize(double stopLossPrice) { if(!UseRiskManagement) return LotSize; double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmount = accountBalance * RiskPercent / 100.0; double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); // Calculate stop loss in points double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double slPoints = MathAbs(currentPrice - stopLossPrice) / point; // Calculate lot size double lotSize = riskAmount / (slPoints * tickValue); // Normalize lot size double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); lotSize = MathMax(minLot, MathMin(maxLot, lotSize)); lotSize = MathRound(lotSize / lotStep) * lotStep; return lotSize; } //+------------------------------------------------------------------+ //| Helper: convert timeframe to string | //+------------------------------------------------------------------+ string TFtoString(int tf) { switch(tf) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN"; default: return "TF?"; } } //+------------------------------------------------------------------+ //| Create information panel | //+------------------------------------------------------------------+ void CreatePanel() { //--- background panel string bg = indicatorName + "_BG"; ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, PanelX); ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, PanelY); ObjectSetInteger(0, bg, OBJPROP_XSIZE, 200); ObjectSetInteger(0, bg, OBJPROP_YSIZE, 120); ObjectSetInteger(0, bg, OBJPROP_CORNER, PanelCorner); ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, clrBlack); ObjectSetInteger(0, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, bg, OBJPROP_BORDER_COLOR, clrWhite); ObjectSetInteger(0, bg, OBJPROP_BACK, true); ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, bg, OBJPROP_HIDDEN, true); ObjectSetInteger(0, bg, OBJPROP_ZORDER, 0); //--- title string title = indicatorName + "_Title"; ObjectCreate(0, title, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, title, OBJPROP_XDISTANCE, PanelX + 10); ObjectSetInteger(0, title, OBJPROP_YDISTANCE, PanelY + 10); ObjectSetInteger(0, title, OBJPROP_CORNER, PanelCorner); ObjectSetString (0, title, OBJPROP_TEXT, "Market Sentiment EA"); ObjectSetInteger(0, title, OBJPROP_COLOR, clrWhite); ObjectSetString (0, title, OBJPROP_FONT, FontFace); ObjectSetInteger(0, title, OBJPROP_FONTSIZE, FontSize); ObjectSetInteger(0, title, OBJPROP_BACK, false); ObjectSetInteger(0, title, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, title, OBJPROP_HIDDEN, true); ObjectSetInteger(0, title, OBJPROP_ZORDER, 1); //--- timeframe labels + sentiment placeholders string timeframes[3] = { TFtoString(HigherTF), TFtoString(LowerTF1), TFtoString(LowerTF2) }; for(int i=0; i<3; i++) { string tfLabel = indicatorName + "_TF" + IntegerToString(i); ObjectCreate(0, tfLabel, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, tfLabel, OBJPROP_XDISTANCE, PanelX + 10); ObjectSetInteger(0, tfLabel, OBJPROP_YDISTANCE, PanelY + 30 + i * 20); ObjectSetInteger(0, tfLabel, OBJPROP_CORNER, PanelCorner); ObjectSetString (0, tfLabel, OBJPROP_TEXT, timeframes[i] + ":"); ObjectSetInteger(0, tfLabel, OBJPROP_COLOR, clrLightGray); ObjectSetString (0, tfLabel, OBJPROP_FONT, FontFace); ObjectSetInteger(0, tfLabel, OBJPROP_FONTSIZE, FontSize); ObjectSetInteger(0, tfLabel, OBJPROP_BACK, false); ObjectSetInteger(0, tfLabel, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, tfLabel, OBJPROP_HIDDEN, true); ObjectSetInteger(0, tfLabel, OBJPROP_ZORDER, 1); string sentLabel = indicatorName + "_Sentiment" + IntegerToString(i); ObjectCreate(0, sentLabel, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, sentLabel, OBJPROP_XDISTANCE, PanelX + 100); ObjectSetInteger(0, sentLabel, OBJPROP_YDISTANCE, PanelY + 30 + i * 20); ObjectSetInteger(0, sentLabel, OBJPROP_CORNER, PanelCorner); ObjectSetString (0, sentLabel, OBJPROP_TEXT, "N/A"); ObjectSetInteger(0, sentLabel, OBJPROP_COLOR, NeutralColor); ObjectSetString (0, sentLabel, OBJPROP_FONT, FontFace); ObjectSetInteger(0, sentLabel, OBJPROP_FONTSIZE, FontSize); ObjectSetInteger(0, sentLabel, OBJPROP_BACK, false); ObjectSetInteger(0, sentLabel, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, sentLabel, OBJPROP_HIDDEN, true); ObjectSetInteger(0, sentLabel, OBJPROP_ZORDER, 1); } //--- final sentiment label string fnl = indicatorName + "_Final"; ObjectCreate(0, fnl, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, fnl, OBJPROP_XDISTANCE, PanelX + 10); ObjectSetInteger(0, fnl, OBJPROP_YDISTANCE, PanelY + 100); ObjectSetInteger(0, fnl, OBJPROP_CORNER, PanelCorner); ObjectSetString (0, fnl, OBJPROP_TEXT, "Final: Neutral"); ObjectSetInteger(0, fnl, OBJPROP_COLOR, NeutralColor); ObjectSetString (0, fnl, OBJPROP_FONT, FontFace); ObjectSetInteger(0, fnl, OBJPROP_FONTSIZE, FontSize + 2); ObjectSetInteger(0, fnl, OBJPROP_BACK, false); ObjectSetInteger(0, fnl, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, fnl, OBJPROP_HIDDEN, true); ObjectSetInteger(0, fnl, OBJPROP_ZORDER, 1); } //+------------------------------------------------------------------+ //| Update information panel | //+------------------------------------------------------------------+ void UpdatePanel(int higherTFBias, int sentiment) { // Update higher timeframe sentiment string higherTFSentiment = "Neutral"; color higherTFColor = NeutralColor; if(higherTFBias == 1) { higherTFSentiment = "Bullish"; higherTFColor = BullishColor; } else if(higherTFBias == -1) { higherTFSentiment = "Bearish"; higherTFColor = BearishColor; } ObjectSetString(0, indicatorName + "_Sentiment0", OBJPROP_TEXT, higherTFSentiment); ObjectSetInteger(0, indicatorName + "_Sentiment0", OBJPROP_COLOR, higherTFColor); // Update final sentiment string finalSentiment = "Neutral"; color finalColor = NeutralColor; switch(sentiment) { case 1: finalSentiment = "Bullish"; finalColor = BullishColor; break; case -1: finalSentiment = "Bearish"; finalColor = BearishColor; break; case 2: finalSentiment = "Risk-On"; finalColor = RiskOnColor; break; case -2: finalSentiment = "Risk-Off"; finalColor = RiskOffColor; break; default: finalSentiment = "Neutral"; finalColor = NeutralColor; } ObjectSetString(0, indicatorName + "_Final", OBJPROP_TEXT, "Final: " + finalSentiment); ObjectSetInteger(0, indicatorName + "_Final", OBJPROP_COLOR, finalColor); }
Функция CalculateLotSize обеспечивает динамическое определение размера позиции на основе баланса счета и процента риска. Вместо использования фиксированного размера лота, она рассчитывает соответствующий объем, измеряя расстояние от точки входа до стоп-лосса, переводя его в пункты и деля рисковый капитал на потенциальный убыток на пипс. Это гарантирует, что каждая сделка рискует фиксированной долей капитала независимо от волатильности или цены инструмента, а также корректирует окончательный размер лота в соответствии с такими брокерскими ограничениями, как минимальный уровень, максимальный уровень и шаг изменения.
Вспомогательная функция TFtoString преобразует числовые константы таймфреймов в удобочитаемые для пользователей строки. Это крайне важно для отображения значимой информации на панелях, в окнах мониторинга или в журналах, позволяя трейдерам быстро понять, к какому таймфрейму относятся сигналы или тенденции. Преобразование этих целочисленных кодов в понятный текст, например, «M5» или «H1», делает код более удобным для пользователя и позволяет избежать путаницы при отслеживании нескольких таймфреймов.
Функции CreatePanel и UpdatePanel отвечают за визуальный интерфейс советника. При этом CreatePanel инициализирует чистую панель мониторинга при помощи фона, заголовка и оценки настроения для каждого отслеживаемого таймфрейма, а также итоговым статусом оценки настроения. UpdatePanel динамически обновляет эти метки, учитывая текущую тенденцию к изменению на более высоком таймфрейме и окончательно рассчитанные настроения рынка, изменяя текст и цвет для визуального представления бычьего, медвежьего, склонного к риску, не склонного к риску или нейтрального состояний. Совместно они создают интуитивно понятную панель мониторинга в режиме реального времени, которая позволяет трейдерам быстро оценивать рыночную ситуацию, не просматривая необработанные данные.
Результаты тестирования на истории
Тестирование на истории проводилось на основе данных на таймфрейме 1H в течение примерно двухмесячного периода (с 17 февраля 2025 года по 6 мая 2025 года) со следующими настройками:



Заключение
Резюмируя, можно сказать, что мы разработали и внедрили комплексную систему для автоматизации индикатора рыночных настроений. Это включало в себя разработку логики определения рыночных настроений на разных таймфреймах, определение четких категорий состояний рынка, таких как бычий, медвежий, склонный к риску, не склонный к риску и нейтральный, а также написание правил исполнения сделок для реагирования на эти сигналы. Кроме того, мы интегрировали такие важные функции, как управление рисками с помощью динамического определения размера лота, функции трейлинг-стопа для фиксации прибыли и информационную панель для визуального отображения настроений рынка в реальном времени. В совокупности эти компоненты превратили индикатор в полностью автоматизированную торговую систему, способную как к анализу, так и к исполнению сделок.
В заключение следует отметить, что такая автоматизация помогает трейдерам, исключая принятие решений на основе эмоций и обеспечивая единообразие в совершении сделок и управлении ими. Сочетая анализ настроений рынка, структурированные торговые правила и встроенную систему управления рисками, трейдеры могут более дисциплинированно ориентироваться на рынке. Система обеспечивает понятный визуальный интерфейс для прозрачности, автоматически совершая сделки в соответствии с настроениями рынка, что позволяет трейдерам экономить время, минимизировать ошибки и работать с профессиональным инструментом, адаптирующимся к меняющимся рыночным условиям.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19609
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Искусство ведения логов (Часть 7): Как отображать логи на графике
От начального до среднего уровня: Struct (VII)
Разрабатываем мультивалютный советник (Часть 32): Секреты шага создания проекта оптимизации (II)
Пользовательские инструменты отладки и профилирования для разработки на MQL5 (Часть I): Расширенное логирование
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования