English Deutsch 日本語
preview
Разработка динамического советника на нескольких парах (Часть 4): Корректировка риска на основе волатильности

Разработка динамического советника на нескольких парах (Часть 4): Корректировка риска на основе волатильности

MetaTrader 5Примеры |
277 3
Hlomohang John Borotho
Hlomohang John Borotho

Введение

В мультипарной торговле одной из проблем, с которыми сталкиваются трейдеры, является непостоянная эффективность, вызванная различной волатильностью на разных валютных парах. Как обсуждалось в предыдущей статье, стратегия, которая хорошо работает на 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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
CapeCoddah
CapeCoddah | 11 авг. 2025 в 11:29

Здравствуйте,


Очень интересная статья. Я только что ознакомился с ней и решил, что дам ей подробную оценку, поскольку она похожа на советник, который я разрабатываю.

Мой первый вопрос: есть ли какая-то причина, по которой все сделки были покупками и ни одной продажи? Также планируете ли вы дальнейшие статьи на эту тему?

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


CapeCoddah

Hlomohang John Borotho
Hlomohang John Borotho | 12 авг. 2025 в 15:52
CapeCoddah экспоненциально больше времени будет потрачено на выполнение избыточного кода. Вы также можете рассмотреть возможность добавления индекса PairCode в ваши массивы, чтобы к ним можно было получить прямой доступ.


CapeCoddah

Привет,

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

Спасибо за ваше предложение, я буду иметь это в виду.

JohnHlomohang
Ademir Temoteo De Vasco
Ademir Temoteo De Vasco | 6 окт. 2025 в 01:28

Здравствуйте... добрый вечер!!!

Во-первых, я хотел бы поздравить вас с отличной работой.

Я провожу тесты и очень доволен результатами, но признаюсь, что для проведения теста я использую только одну валюту, так как настройки для каждого актива разные.

Я не понимаю, как работает эта логика размещения нескольких валют одновременно.

Если бы вы могли объяснить мне это, я был бы благодарен.

Автоматический перевод применен модератором. На англоязычном форуме, пожалуйста, пишите на английском языке.

Моделирование рынка (Часть 11): Сокеты (V) Моделирование рынка (Часть 11): Сокеты (V)
Мы приступаем к реализации связи между Excel и MetaTrader 5, но сначала необходимо понять некоторые важные моменты, так вам не придется ломать голову, пытаясь понять, почему что-то работает или нет. И прежде, чем вы нахмуритесь, глядя на интеграцию Python и Excel, давайте посмотрим, как с помощью xlwings можно (в некоторой степени) управлять MetaTrader 5 через Excel. То, что мы покажем здесь, будет в основном сконцентрировано на образовательных задачах. Но не думайте, что мы можем делать только то, что будет рассмотрено здесь.
Алгоритм эволюции элитных кристаллов — Elite Crystal Evolution Algorithm (CEO-inspired): Теория Алгоритм эволюции элитных кристаллов — Elite Crystal Evolution Algorithm (CEO-inspired): Теория
Представлен новый авторский популяционный алгоритм ECEA, вдохновлённый процессом замерзания воды и адаптирующий идеи алгоритма Crystal Energy Optimizer, (CEO) с поиском на графах, для общих задач оптимизации. Алгоритм использует динамическую элитную группу, три стратегии поиска и механизм периодической диверсификации.
От новичка до эксперта: Утилита для управления параметрами От новичка до эксперта: Утилита для управления параметрами
Представьте, что вы преобразовали традиционные входные свойства советника или индикатора в интерфейс управления графиком в режиме реального времени. Это обсуждение основано на нашей фундаментальной работе над индикатором Market Period Synchronizer, что знаменует собой значительную эволюцию в том, как мы визуализируем рыночные структуры на старших таймфреймах (HTF) и управляем ими. Здесь мы превращаем эту концепцию в полностью интерактивную утилиту — информационная панель, которая обеспечивает динамический контроль и улучшенную многопериодную визуализацию ценового движения непосредственно на графике. Присоединяйтесь к нам, и мы узнаем, как это нововведение меняет способ взаимодействия трейдеров со своими инструментами.
Нейросети в трейдинге: Рекуррентное моделирование микродвижений рынка (EV-MGRFlowNet) Нейросети в трейдинге: Рекуррентное моделирование микродвижений рынка (EV-MGRFlowNet)
В статье рассматривается перенос архитектуры EV-MGRFlowNet, изначально разработанной для обработки событийных видеоданных, в область финансовых временных рядов. Представленный подход раскрывает новый взгляд на рынок как на поток микродвижений, где цена, объём и ликвидность образуют динамическую структуру, поддающуюся рекуррентному анализу без явного надзора.