Разработка динамического советника на нескольких парах (Часть 4): Корректировка риска на основе волатильности
Введение
В мультипарной торговле одной из проблем, с которыми сталкиваются трейдеры, является непостоянная эффективность, вызванная различной волатильностью на разных валютных парах. Как обсуждалось в предыдущей статье, стратегия, которая хорошо работает на EURUSD, может показывать худшие результаты или стать чрезмерно рискованной на GBPJPY из-за их различных профилей волатильности. Использование фиксированных размеров лота или статических стоп-лоссов может привести к чрезмерным позициям на волатильных рынках или упущенным возможностям на стабильных. Этот недостаток адаптивности часто приводит к неравномерному распределению рисков, увеличенным просадкам и непредсказуемым результатам, особенно во время значительных новостных событий или внезапных рыночных изменений.
Чтобы решить эту проблему, мы добавляем в советник корректировку риска на основе волатильности. Внедряя такие инструменты, как средний истинный диапазон (ATR) и динамическое управление размером сделки исходя из риска, советник автоматически адаптирует параметры торговли к текущим рыночным условиям. Это позволяет пропорционально балансировать каждую позицию в зависимости от ее волатильности, что обеспечивает стабильное управление рисками и улучшает эффективность советника по всем торгуемым парам.
План разработки советника
Логика торговли:

1. Мультисимвольный обработчик:
- Парсер списка символов
- Отслеживание данных предыдущего символа
- Одновременное управление позициями

2. Условие входа:

3. Уровни риска на основе волатильности:

4. Размер позиции:
Risk Amount = Account Equity * Risk %
Position Size = Risk Amount / (SL Distance * Point Value) 5. Идентификация цели V-stop:
[ Current Price ] | v [ Resistance Area ] <-- Previous V-Stop Upper (TP for sells) | v [ Current Price ] | v [ Support Area ] <-- Previous V-Stop Lower (TP for buys)
6. Хронология жизненного цикла сделки:

Приступим
//+------------------------------------------------------------------+ //| MultiSymbolVolatilityTraderEA.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade/Trade.mqh>
Как обычно, мы начинаем с импорта важнейшей торговой библиотеки, необходимой для того, чтобы наш советник исполнял ордера и управлял позициями.
//--- Input settings input string Symbols = "XAUUSD,GBPUSD,USDCAD,USDJPY"; // Symbols to trade input int RSI_Period = 14; // RSI Period input double RSI_Overbought = 70.0; // RSI Overbought Level input double RSI_Oversold = 30.0; // RSI Oversold Level input uint ATR_Period = 14; // ATR Period for Volatility input double ATR_Multiplier = 2.0; // ATR Multiplier for SL input double RiskPercent_High = 0.02; // Risk % High Volatility input double RiskPercent_Mod = 0.01; // Risk % Moderate Volatility input double RiskPercent_Low = 0.005; // Risk % Low Volatility input int Min_Bars = 50; // Minimum Bars Required input double In_Lot = 0.01; // Default lot size input int StopLoss = 100; // SL in points input int TakeProfit = 100; // TP in points
В этом блоке мы определяем входные настройки, которые определяют работу советника по нескольким символам. Входная настройка Symbols позволяет трейдеру указать, какими инструментами торговать, в то время как входы, связанные с RSI (RSI_Period, RSI_Overbought и RSI_Oversold), используются для определения потенциальных точек входа на основе перекупленности или перепроданности. Волатильность учитывается с использованием среднего истинного диапазона (ATR) с настраиваемыми параметрами периода и множителя для динамического масштабирования стоп-лосса.
Советник настраивает свою риск-экспозицию в зависимости от уровней волатильности, используя разные проценты риска для условий высокой, умеренной и низкой волатильности. Дополнительные параметры включают Min_Bars для обеспечения достаточного объема исторических данных, размер по умолчанию In_Lot и фиксированные значения StopLoss и TakeProfit, которые служат резервными вариантами, если динамические уровни не применяются.
//--- Global variables string symb_List[]; int Num_symbs = 0; int ATR_Handles[]; int RSI_Handles[]; double Prev_ATR[]; double Prev_RSI[]; datetime LastBarTime[]; CTrade trade;
В этом разделе объявляются глобальные переменные, используемые в советнике. Symb_List[] — это массив, который хранит список символов для торговли, в то время как Num_symbs содержит общее количество этих символов. Массивы ATR_Handles[] и RSI_Handles[] управляют хэндлами индикаторов для расчета ATR и RSI, позволяя советнику обрабатывать несколько символов одновременно.
Prev_ATR[] и Prev_RSI[] хранят самые последние значения этих индикаторов для каждого символа, используемые в логике принятия решений. LastBarTime[] отслеживает последний обработанный бар для каждого символа, чтобы избежать дублирования операций на одной и той же свече. Наконец, объект CTrade класса trade предоставляет доступ к встроенным торговым функциям MQL5, позволяя исполнять ордера и управлять позициями.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Split symbol list ushort separator = StringGetCharacter(",", 0); StringSplit(Symbols, separator, symb_List); Num_symbs = ArraySize(symb_List); //--- Resize arrays ArrayResize(ATR_Handles, Num_symbs); ArrayResize(RSI_Handles, Num_symbs); ArrayResize(Prev_ATR, Num_symbs); ArrayResize(Prev_RSI, Num_symbs); ArrayResize(LastBarTime, Num_symbs); ArrayInitialize(Prev_ATR, 0.0); ArrayInitialize(Prev_RSI, 50.0); ArrayInitialize(LastBarTime, 0); //--- Create indicator handles for(int i = 0; i < Num_symbs; i++) { string symbol = symb_List[i]; ATR_Handles[i] = iATR(symbol, PERIOD_CURRENT, ATR_Period); RSI_Handles[i] = iRSI(symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE); if(ATR_Handles[i] == INVALID_HANDLE || RSI_Handles[i] == INVALID_HANDLE) { Print("Error creating indicator handles for ", symbol, " - Error: ", GetLastError()); return INIT_FAILED; } } return INIT_SUCCEEDED; }
Функция OnInit() отвечает за инициализацию советника при его загрузке на график. Сначала происходит разбиение входных данных из Symbols, разделенных запятыми, на массив symb_List, и вычисляется общее количество символов, которыми нужно управлять. Затем функция изменяет размер и инициализирует несколько глобальных массивов, таких как массивы для хэндлов ATR и RSI, предыдущих значений индикаторов и времени последней обработанной свечи, чтобы гарантировать, что для каждого символа предусмотрено отдельное хранилище и отслеживание. Начальные значения установлены для предотвращения неопределенного поведения во время первого цикла выполнения советника.
Затем функция проходит через каждый символ и создает хэндлы индикатора для ATR и RSI, используя iATR и iRSI соответственно. Эти хэндлы необходимы для получения значений индикатора в реальном времени во время торговых операций. Если создание любого из хэндлов завершится неудачей (т.е. вернется INVALID_HANDLE), будет выведено сообщение об ошибке, и инициализация будет прервана с возвратом INIT_FAILED. Если все индикаторы настроены успешно, функция возвращает INIT_SUCCEEDED, сигнализируя, что советник готов к началу выполнения.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release indicator handles for(int i = 0; i < Num_symbs; i++) { if(ATR_Handles[i] != INVALID_HANDLE) IndicatorRelease(ATR_Handles[i]); if(RSI_Handles[i] != INVALID_HANDLE) IndicatorRelease(RSI_Handles[i]); } }
Функция OnDeinit() вызывается, когда советник удаляется с графика или повторно инициализируется. Ее главной целью является освобождение ресурсов путем освобождения хэндлов индикаторов, связанных с каждым символом. Вызывая IndicatorRelease() для обоих обработчиков ATR и RSI только в том случае, если они действительны, это гарантирует, что системная память освобождается надлежащим образом, и тем самым предотвращает утечки или ненужное потребление ресурсов. Это помогает поддерживать стабильность платформы, особенно при запуске нескольких советников или индикаторов.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { for(int i = 0; i < Num_symbs; i++) { string symbol = symb_List[i]; //--- Check for new bar datetime currentBarTime = iTime(symbol, PERIOD_CURRENT, 0); if(LastBarTime[i] == currentBarTime) continue; LastBarTime[i] = currentBarTime; //--- Get indicator values double atr[2] = {0.0, 0.0}; double rsi[2] = {50.0, 50.0}; if(CopyBuffer(ATR_Handles[i], 0, 1, 2, atr) < 2 || CopyBuffer(RSI_Handles[i], 0, 1, 2, rsi) < 2) continue; Prev_ATR[i] = atr[0]; // Previous bar's ATR Prev_RSI[i] = rsi[0]; // Previous bar's RSI //--- Get current prices MqlTick lastTick; if(!SymbolInfoTick(symbol, lastTick)) continue; double ask = lastTick.ask; double bid = lastTick.bid; //--- Check existing positions bool hasLong = false, hasShort = false; CheckExistingPositions(symbol, hasLong, hasShort); //--- Calculate volatility-based risk double riskPercent = CalculateRiskLevel(symbol); //--- Get V-Stop levels double vStopUpper = CalculateVStop(symbol, 1, true); // Previous bar's upper band double vStopLower = CalculateVStop(symbol, 1, false); // Previous bar's lower band //--- Trade Entry Logic if(!hasLong && !hasShort) { //--- Sell signal: RSI overbought + price below V-Stop upper band if(rsi[0] > RSI_Overbought && bid < vStopUpper) { double tp = GetProfitTarget(symbol, false); // Previous V-Stop level double sl = ask + ATR_Multiplier * atr[0]; double lots = CalculatePositionSize(symbol, riskPercent, sl, ask); if(lots > 0) ExecuteTrade(ORDER_TYPE_SELL, symbol); } //--- Buy signal: RSI oversold + price above V-Stop lower band else if(rsi[0] < RSI_Oversold && ask > vStopLower) { double tp = GetProfitTarget(symbol, true); // Previous V-Stop level double sl = bid - ATR_Multiplier * atr[0]; double lots = CalculatePositionSize(symbol, riskPercent, sl, bid); if(lots > 0) ExecuteTrade(ORDER_TYPE_BUY, symbol); } } //--- Trailing Stop Logic UpdateTrailingStops(symbol, atr[0]); } }
Функция OnTick() выполняется каждый раз, когда рынок обновляется, проходя по каждому символу в торговом списке для проведения анализа в реальном времени и управления сделками. Сначала проверяется, образовалась ли новая свеча для символа путем сравнения временной метки текущей свечи с последней записанной. Если это новая свеча, функция продолжает извлекать последние значения ATR и RSI с помощью CopyBuffer(), сохраняя их в глобальных массивах, чтобы использовать при принятии решений. Текущие цены Ask и Bid также извлекаются с помощью SymbolInfoTick(), чтобы обеспечить точные уровни входа и выхода.
Затем функция проверяет, есть ли открытые длинные или короткие позиции для текущего символа, используя функцию CheckExistingPositions(). Затем функция вычисляет соответствующий уровень риска на основе волатильности символа с помощью функции CalculateRiskLevel() и определяет самые последние уровни V-Stop для управления входом и трейлингом. На основе этой информации советник применяет свои правила входа в сделку: продажа срабатывает, когда RSI указывает на перекупленность, и цена падает ниже верхнего V-Stop, а покупка срабатывает, когда RSI показывает перепроданность, и цена пробивает нижний V-Stop. В обоих случаях динамические уровни стоп-лосса и тейк-профита рассчитываются с использованием ATR и V-Stop, а размер позиции корректируется в соответствии с определенным уровнем риска.
Наконец, независимо от того, открывается ли новая сделка, советник вызывает функцию UpdateTrailingStops() для управления открытыми позициями при их наличии. Эта функция корректирует стоп-лоссы в соответствии с последними данными о волатильности, помогая зафиксировать прибыль и ограничить убытки по мере изменения рыночных условий. Этот динамический подход обеспечивает то, что стратегия остается отзывчивой и адаптивной на нескольких символах в реальном времени.
//+------------------------------------------------------------------+ //| 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 = StopLoss * point; double tp_distance = TakeProfit * 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, In_Lot, price, sl, tp, NULL); }
Функция ExecuteTrade() обрабатывает фактическое выполнение сделки на основе указанного типа ордера (покупка или продажа) и символа. Сначала определяется правильная цена входа, используя либо текущую цену Ask, либо цену Bid, в зависимости от направления сделки. Затем функция рассчитывает уровни стоп-лосса и тейк-профита, преобразуя заданные пользователем значения пунктов в фактические ценовые расстояния с использованием размера пункта у символа. В зависимости от того, является ли эта сделка покупкой или продажей, устанавливаются SL и TP в соответствующем направлении, а затем используется объект CTrade для открытия позиции с заданным размером лота и параметрами цены.
//+------------------------------------------------------------------+ //| Calculate V-Stop levels | //+------------------------------------------------------------------+ double CalculateVStop(string symbol, int shift, bool isUpper) { double atr[1]; double high[1], low[1], close[1]; if(CopyBuffer(ATR_Handles[GetSymbolIndex(symbol)], 0, shift, 1, atr) < 1 || CopyHigh(symbol, PERIOD_CURRENT, shift, 1, high) < 1 || CopyLow(symbol, PERIOD_CURRENT, shift, 1, low) < 1 || CopyClose(symbol, PERIOD_CURRENT, shift, 1, close) < 1) return 0.0; double price = (high[0] + low[0] + close[0]) / 3.0; // Typical price return isUpper ? price + ATR_Multiplier * atr[0] : price - ATR_Multiplier * atr[0]; }
Функция CalculateVStop() вычисляет динамический уровень V-Stop для данного символа и смещения бара, корректируя его в зависимости от волатильности. Он извлекает значения ATR, максимума, минимума и закрытия для указанной свечи, используя CopyBuffer() и функции ценовых рядов. Затем вычисляется типичная цену как среднее значение максимальной, минимальной и закрывающей цены. В зависимости от того, нужен ли верхний или нижний V-Stop (флаг isUpper), он добавляет или вычитает кратное ATR из этой типичной цены, чтобы сгенерировать уровень поддержки или сопротивления, скорректированный по волатильности, используемый для фильтрации сделок и логики трейлинга.
//+------------------------------------------------------------------+ //| Get profit target from V-Stop history | //+------------------------------------------------------------------+ double GetProfitTarget(string symbol, bool forLong) { int bars = 50; // Look back 50 bars double target = 0.0; for(int i = 1; i <= bars; i++) { double vStop = forLong ? CalculateVStop(symbol, i, false) : // For longs, find support levels CalculateVStop(symbol, i, true); // For shorts, find resistance levels if(vStop != 0.0) { target = vStop; break; } } // Fallback: Use fixed multiplier if no V-Stop found MqlTick lastTick; SymbolInfoTick(symbol, lastTick); return (target == 0.0) ? (forLong ? lastTick.ask + 5*Prev_ATR[GetSymbolIndex(symbol)] : lastTick.bid - 5*Prev_ATR[GetSymbolIndex(symbol)]) : target; }
Функция GetProfitTarget() определяет динамический уровень тейк-профита на основе исторических значений V-Stop. Она проверяет до 50 последних баров, ища ближайший действительный уровень V-Stop – либо нижнюю границу для длинных сделок (действующей как поддержка), либо верхнюю границу для коротких сделок (действующей как сопротивление). Если действительный уровень найден, он используется в качестве целевого уровня прибыли. Если в пределах окна проверки действительных уровней не найдено, функция возвращается к значению по умолчанию, рассчитанному как 5× расстояние от ATR от текущей цены Ask или Bid, и это гарантирует, что советник все равно устанавливает логичный уровень тейк-профита, даже при отсутствии исторических данных V-Stop.
//+------------------------------------------------------------------+ //| Calculate risk level based on volatility | //+------------------------------------------------------------------+ double CalculateRiskLevel(string symbol) { double atrValues[20]; if(CopyBuffer(ATR_Handles[GetSymbolIndex(symbol)], 0, 1, 20, atrValues) < 20) return RiskPercent_Mod; double avgATR = 0.0; for(int i = 0; i < 20; i++) avgATR += atrValues[i]; avgATR /= 20.0; double currentATR = atrValues[0]; // Most recent ATR if(currentATR > avgATR * 1.5) return RiskPercent_High; else if(currentATR < avgATR * 0.5) return RiskPercent_Low; return RiskPercent_Mod; }
Функция CalculateRiskLevel() динамически регулирует риск-экспозицию советника на основе текущей волатильности рынка. Он извлекает последние 20 значений ATR для данного символа и вычисляет их среднее значение для установления базового уровня. Самое последнее значение ATR затем сравнивается с этим средним: если оно значительно больше (выше 1,5×), рынок считается высоковолатильным, и используется более высокий процент риска; если оно значительно меньше (ниже 0,5×), применяется более низкий процент риска. В противном случае выбирается умеренный уровень риска. Это обеспечивает адаптивность размера сделок и работу в соответствии с рыночными условиями в реальном времени.
//+------------------------------------------------------------------+ //| Calculate position size based on risk | //+------------------------------------------------------------------+ double CalculatePositionSize(string symbol, double riskPercent, double sl, double entryPrice) { double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(tickSize == 0 || point == 0) return 0.0; double riskAmount = AccountInfoDouble(ACCOUNT_EQUITY) * riskPercent; double slDistance = MathAbs(entryPrice - sl) / point; double moneyRisk = slDistance * tickValue / (tickSize / point); if(moneyRisk <= 0) return 0.0; double lots = riskAmount / moneyRisk; // Normalize and validate lot size double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lots = MathMax(minLot, MathMin(maxLot, lots)); lots = MathRound(lots / step) * step; return lots; }
Функция CalculatePositionSize() определяет подходящий размер лота для сделки на основе указанного процента риска и расстояния до стоп-лосса. Сначала функция собирает основные параметры торговли, такие как размер тика, значение тика и размер пункта для символа. Используя собственный капитал счета и выбранный процент риска, функция рассчитывает общую сумму денег, которой трейдер готов рискнуть.
Затем функция вычисляет денежный риск на лот, учитывая расстояние стоп-лосса в пунктах, и преобразует его в значение на основе параметров тика. Функция делит общую сумму риска на найденный риск на лот, чтобы получить оптимальный размер лота. Наконец, он корректирует размер лота в соответствии с минимальными, максимальными и шаговыми требованиями брокера, обеспечивая, чтобы размер позиции был действительным и правильно округленным для исполнения.
//+------------------------------------------------------------------+ //| Update trailing stops | //+------------------------------------------------------------------+ void UpdateTrailingStops(string symbol, double currentATR) { double newSL = 0.0; for(int pos = PositionsTotal()-1; pos >= 0; pos--) { if(PositionGetSymbol(pos) != symbol) continue; ulong ticket = PositionGetInteger(POSITION_TICKET); double currentSL = PositionGetDouble(POSITION_SL); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentProfit = PositionGetDouble(POSITION_PROFIT); double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { newSL = currentPrice - ATR_Multiplier * currentATR; if(newSL > currentSL && newSL > openPrice && currentProfit > 0) ModifySL(ticket, newSL); } else { newSL = currentPrice + ATR_Multiplier * currentATR; if((newSL < currentSL || currentSL == 0) && newSL < openPrice && currentProfit > 0) ModifySL(ticket, newSL); } } }
Функция UpdateTrailingStops() активно управляет открытыми позициями, корректируя их стоп-лоссы в зависимости от текущей волатильности рынка. Для каждой позиции, соответствующей данному символу, рассчитывается новый уровень трейлинг-стопа, используя последний ATR, умноженный на предопределенный коэффициент. Для длинных позиций уровень стоп-лосса поднимается, если новый уровень выше текущего стоп-лосса и выше цены открытия, что помогает зафиксировать прибыль, как только сделка оказывается в положительной зоне. Для коротких позиций, наоборот, уровень стоп-лосса корректируется вниз при аналогичных условиях. Такой динамический подход к испольованию трейлинг-стопа защищает прибыль, позволяя сделке дышать на волатильных рынках.
//+------------------------------------------------------------------+ //| Modify stop loss | //+------------------------------------------------------------------+ bool ModifySL(ulong ticket, double newSL) { MqlTradeRequest request = {}; MqlTradeResult result = {}; request.action = TRADE_ACTION_SLTP; request.position = ticket; request.sl = newSL; request.deviation = 5; if(!OrderSend(request, result)) { Print("Modify SL error: ", GetLastError()); return false; } return true; }
Функция ModifySL() обновляет уровень стоп-лосса существующей позиции, определяемой по ее тикету. Она создает запрос на сделку, указывая действие по изменению стоп-лосса (TRADE_ACTION_SLTP), назначает новую цену стоп-лосса и устанавливает максимальное допустимое отклонение цены. Затем запрос отправляется через OrderSend(), и если модификация не удалась, выводится сообщение об ошибке с соответствующим кодом ошибки. Функция возвращает true при успешном выполнении и false, если обновление стоп-лосса не удалось, позволяя советнику корректно обрабатывать ошибки.
//+------------------------------------------------------------------+ //| Check existing positions | //+------------------------------------------------------------------+ void CheckExistingPositions(string symbol, bool &hasLong, bool &hasShort) { hasLong = false; hasShort = false; for(int pos = PositionsTotal()-1; pos >= 0; pos--) { if(PositionGetSymbol(pos) == symbol) { if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) hasLong = true; else hasShort = true; } } }
В этом блоке кода функция CheckExistingPositions() сканирует все открытые позиции, чтобы определить, существуют ли по указанному символу активные длинные или короткие сделки. Она инициализирует выходные флаги hasLong и hasShort как ложные, а затем проходит по всем позициям. Для каждой позиции, соответствующей символу, флагу hasLong присваивается значение true, если позиция является покупкой, либо флагу hasShort присваивается true, если это продажа.
//+------------------------------------------------------------------+ //| Get symbol index | //+------------------------------------------------------------------+ int GetSymbolIndex(string symbol) { for(int i = 0; i < Num_symbs; i++) if(symb_List[i] == symbol) return i; return -1; }
Наконец, функция GetSymbolIndex() предоставляет простую утилиту для получения индекса данного символа из массива symb_List. Она проходит по всем сохраненным символам и возвращает соответствующий индекс, когда символ найден. Если символа нет в списке, возвращается -1. Эта функция необходима для синхронизации данных, специфичных для символа, таких как хэндлы индикаторов или значения, сохраненные в различных массивах, используемых в советнике.
Результаты тестирования на исторических данных
Тестирование на исторических данных было проведено на часовом таймфрейме с использованием тестового окна около двух месяцев (с 11 июня 2025 года по 1 августа 2025 года) со следующими входными настройками:
- Период RSI = 60
- Уровень перекупленности RSI = 70
- Уровень перепроданности RSI = 45
- Период ATR для волатильности = 62
- Множитель ATR для SL = 3.2
- % риска при высокой волатильности = 0.19
- % риска при высокой волатильности = 0.064
- % риска при высокой волатильности = 0.0335
- Минимальное количество баров = 50
- Размер стандартного лота = 0.01
- SL в баллах = 610
- TP в баллах = 980


Заключение
Подведем итог: мы разработали динамический мультипарный советник, который адаптируется к изменяющимся рыночным условиям, интегрируя управление рисками на основе волатильности. Система обрабатывает несколько символов одновременно, применяя технические индикаторы, такие как ATR и RSI, для выявления торговых возможностей. Советник рассчитывает уровни V-Stop для фильтрации входов и управления выходами, одновременно динамически регулируя размеры лотов и стоп-лоссы на основе текущей волатильности. С помощью модульных функций советник обрабатывает исполнение сделок, трейлинг-стопы и категоризацию рисков (высокий, умеренный, низкий), чтобы гарантировать, что каждая сделка соответствует текущему поведению символа.
Наконец, этот подход предоставляет трейдерам надежный и адаптивный инструмент, который поддерживает стабильную эффективность на различных валютных парах. Корректируя риск-экспозицию и параметры торговли в зависимости от волатильности, советник снижает избыточную экспозицию на волатильных рынках и избегает просадки эффективности на спокойных.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18165
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Моделирование рынка (Часть 11): Сокеты (V)
Алгоритм эволюции элитных кристаллов — Elite Crystal Evolution Algorithm (CEO-inspired): Теория
От новичка до эксперта: Утилита для управления параметрами
Нейросети в трейдинге: Рекуррентное моделирование микродвижений рынка (EV-MGRFlowNet)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Здравствуйте,
Очень интересная статья. Я только что ознакомился с ней и решил, что дам ей подробную оценку, поскольку она похожа на советник, который я разрабатываю.
Мой первый вопрос: есть ли какая-то причина, по которой все сделки были покупками и ни одной продажи? Также планируете ли вы дальнейшие статьи на эту тему?
Из моего краткого обзора вашего кода, могу ли я предложить, что GetSymbolIndex и другие переменные, такие как точка, должны быть перемещены в верхнюю часть цикла символа и назначены переменным для повышения эффективности за счет сокращения избыточности. Как больше символов добавляются в список пар, экспоненциально больше времени будет потрачено на избыточное выполнение кода. Вы также можете рассмотреть возможность добавления индекса PairCode в ваши массивы, чтобы они могли быть доступны напрямую.
CapeCoddah
CapeCoddah
Причина того, что все сделки покупаются, зависит от входных настроек, которые вы могли ввести, и если вы использовали те же настройки, что и я в бэк-тесте, причина в том, что я оптимизировал советник. Возможно, со временем появятся другие статьи на эту тему.
Спасибо за ваше предложение, я буду иметь это в виду.
JohnHlomohang
Здравствуйте... добрый вечер!!!
Во-первых, я хотел бы поздравить вас с отличной работой.
Я провожу тесты и очень доволен результатами, но признаюсь, что для проведения теста я использую только одну валюту, так как настройки для каждого актива разные.
Я не понимаю, как работает эта логика размещения нескольких валют одновременно.
Если бы вы могли объяснить мне это, я был бы благодарен.
Автоматический перевод применен модератором. На англоязычном форуме, пожалуйста, пишите на английском языке.