Русский
preview
Trading por pares: negociação algorítmica com auto-otimização baseada na diferença de pontuação Z

Trading por pares: negociação algorítmica com auto-otimização baseada na diferença de pontuação Z

MetaTrader 5Sistemas de negociação |
115 2
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Introdução

No mundo do trading, onde muitos perseguem indicadores complexos e buscam incessantemente o "Santo Graal", às vezes as estratégias mais eficazes são baseadas em princípios estatísticos simples. Hoje, mergulharemos no fascinante universo do trading por pares e veremos como abordagens algorítmicas modernas podem levar essa estratégia clássica a um novo nível de eficiência.

O trading por pares foi originalmente desenvolvido por traders quantitativos de Wall Street na década de 1980 e, por muito tempo, permaneceu como prerrogativa de grandes fundos hedge e investidores institucionais. No entanto, com o avanço da tecnologia e a disponibilidade de plataformas de negociação como o MetaTrader, essa estratégia tornou-se acessível também aos traders de varejo.

A base deste artigo é um EA real de negociação, que combina os princípios clássicos do trading por pares com tecnologias modernas de otimização e adaptação automática às condições de mercado em constante mudança. Nossa abordagem permite não apenas identificar anomalias de curto prazo na relação de preços de ativos correlacionados, mas também reagir de forma flexível a mudanças estruturais do mercado.


Conceito de trading por pares: a estatística a serviço do trader

O trading por pares é uma estratégia neutra em relação ao mercado que utiliza desvios estatísticos no comportamento de ativos correlacionados. A ideia principal é simples e elegante: quando os preços de dois instrumentos interligados se afastam mais do que o normal, há uma alta probabilidade de que eles retornem à sua correlação média histórica.

Base matemática da estratégia

A estratégia se apoia em dois conceitos estatísticos importantes: correlação e estacionaridade. A correlação é uma medida de dependência estatística entre duas variáveis, mostrando o quanto a variação de uma delas está relacionada à variação da outra. No contexto dos mercados financeiros, a correlação entre dois ativos pode variar de -1 (correlação totalmente negativa) a +1 (correlação totalmente positiva).

A estacionaridade é uma propriedade de uma série temporal na qual suas características estatísticas, como média, variância e autocorrelação, permanecem constantes ao longo do tempo. Para o trading por pares, é essencial que a razão de preços entre dois ativos seja estacionária, ou seja, que tenha a tendência de retornar ao seu valor médio.

Para traders de Forex, isso é especialmente relevante, pois os pares de moedas frequentemente demonstram relações correlacionais estáveis. Por exemplo, EURUSD e GBPUSD costumam se mover na mesma direção, mas com diferentes amplitudes e desvios periódicos. São exatamente esses desvios que se tornam a fonte potencial de lucro.

Exemplo prático

Consideremos o par EURUSD e GBPUSD. Historicamente, esses pares de moedas exibem uma correlação positiva elevada, devido à proximidade geográfica e aos fortes vínculos econômicos entre a Zona do Euro e o Reino Unido.

Se, em determinado momento, o EURUSD começar a subir mais rapidamente do que o GBPUSD, a relação EURUSD/GBPUSD também aumentará. Quando essa relação ultrapassa sua média histórica por uma magnitude estatisticamente significativa, podemos supor que há uma alta probabilidade de retorno à média. Nesse caso, a estratégia de trading por pares sugere vender EURUSD (o ativo sobrevalorizado) e comprar GBPUSD (o ativo subvalorizado).

Vantagens da estratégia neutra em relação ao mercado

A principal vantagem do trading por pares está em sua relativa independência da direção geral do mercado. Como abrimos simultaneamente posições de compra e venda em ativos correlacionados, nossa posição líquida em relação ao mercado permanece próxima da neutralidade. Isso significa que a estratégia pode ser lucrativa tanto em mercados de alta quanto de baixa, o que é especialmente valioso em períodos de alta volatilidade e incerteza.

A pontuação Z como indicador-chave

A base da nossa abordagem algorítmica é o uso da pontuação Z (Z-score), uma medida estatística que determina o quanto a relação atual de preços se desvia da média histórica em unidades de desvio padrão:

Z-score = (Текущее соотношение - Среднее соотношение) / Стандартное отклонение

Quando a pontuação Z ultrapassa um determinado limiar (por exemplo, +2.0), isso sinaliza um desvio significativo que, segundo a teoria estatística, possui alta probabilidade de retornar à média.

A pontuação Z, na prática, mostra o quão “anormal” é a relação atual de preços no contexto histórico. Um valor de +2.0 indica que a relação atual está a uma distância de dois desvios padrão da média, o que ocorre apenas em cerca de 2,3% dos casos sob uma distribuição normal.

Interpretação dos valores da pontuação Z

Z-score > 0 significa que o primeiro par de moedas (Symbol1) está sobrevalorizado em relação ao segundo (Symbol2). Z-score < 0 indica que o primeiro par (Symbol1) está subvalorizado em relação ao segundo (Symbol2). Quando |Z-score| < 1, a relação de preços permanece dentro dos limites normais; quando 1 < |Z-score| < 2, observa-se um desvio moderado da norma. Um valor de 2 < |Z-score| < 3 aponta para um desvio significativo (possível ponto de entrada), enquanto |Z-score| > 3 indica um desvio extremo (alta probabilidade de retorno à média).

Escolha do período ideal para o cálculo

A escolha do período usado para calcular a pontuação Z é um parâmetro crítico que afeta diretamente a eficiência da estratégia. Um período muito curto pode gerar sinais falsos com frequência, enquanto um período muito longo pode fazer com que oportunidades de curto prazo sejam perdidas.

Nosso EA resolve esse problema por meio da otimização automática do período de cálculo da pontuação Z com base em dados históricos, adaptando-se às condições atuais do mercado.


Arquitetura do nosso algoritmo

O EA apresentado baseia-se em três princípios fundamentais: análise estatística para identificar divergências anormais, otimização dinâmica de parâmetros em tempo real e gestão adaptativa de risco levando em conta mudanças na correlação.

Estrutura geral do EA

Nosso algoritmo é composto por módulos interligados: coleta e pré-processamento de dados, análise estatística, tomada de decisão de negociação, otimização automática e gestão de risco. O módulo de coleta de dados obtém preços históricos e atuais dos pares de moedas, além de realizar filtragem e normalização. A análise estatística calcula a relação de preços, a pontuação Z e a correlação entre os pares. O módulo de tomada de decisão identifica pontos de entrada e gerencia as posições abertas. O módulo de otimização testa periodicamente diferentes combinações de parâmetros e escolhe as melhores. O módulo de gestão de risco calcula o tamanho ótimo da posição e controla perdas consecutivas.

Cálculo da relação e da pontuação Z

O componente essencial do nosso algoritmo é a função responsável por calcular a relação de preços e convertê-la em pontuação Z:

void CalculateRatioAndZScore()
{
    // Расчет соотношения цен
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        if(prices2[i] == 0) continue;
        ratio[i] = prices1[i] / prices2[i];
    }
    
    // Вычисление среднего
    double mean = 0;
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        mean += ratio[i];
    }
    mean /= CurrentZScorePeriod;
    
    // Вычисление стандартного отклонения
    double stdDev = 0;
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        stdDev += MathPow(ratio[i] - mean, 2);
    }
    stdDev = MathSqrt(stdDev / CurrentZScorePeriod);
    
    // Вычисление Z-score
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        if(stdDev == 0)
            zscore[i] = 0;
        else
            zscore[i] = (ratio[i] - mean) / stdDev;
    }
}

Este trecho demonstra como calculamos a relação de preços e a transformamos em pontuação Z. Observe o tratamento de casos-limite, quando o desvio padrão se aproxima de zero, o que garante a estabilidade do algoritmo.

Monitoramento da correlação e busca de pontos de entrada

Uma das inovações centrais da nossa abordagem é o uso de monitoramento dinâmico da correlação para determinar pontos de entrada ideais:

// Логика открытия новых позиций - входим, когда корреляция на минимуме
if(!isPositionOpen)
{
    double currentCorrelation = correlationHistory[0];
    double minCorrelation = GetMinimumCorrelation();
    
    // Если текущая корреляция близка к минимальной и Z-score превышает порог
    if(MathAbs(currentCorrelation - minCorrelation) < 0.01 && 
       MathAbs(currentZScore) >= CurrentEntryThreshold)
    {
        // Рассчет размера лота на основе риска
        double riskLot = CalculatePositionSize();
        
        if(currentZScore > 0) // Symbol1 переоценен, Symbol2 недооценен
        {
            // Продаем Symbol1 и покупаем Symbol2
            if(OpenPairPosition(POSITION_TYPE_SELL, Symbol1, 
                               POSITION_TYPE_BUY, Symbol2, riskLot))
            {
                Log("Открыта парная позиция: SELL " + Symbol1 + ", BUY " + Symbol2);
                isPositionOpen = true;
            }
        }
        else // Symbol1 недооценен, Symbol2 переоценен
        {
            // Покупаем Symbol1 и продаем Symbol2
            if(OpenPairPosition(POSITION_TYPE_BUY, Symbol1, 
                               POSITION_TYPE_SELL, Symbol2, riskLot))
            {
                Log("Открыта парная позиция: BUY " + Symbol1 + ", SELL " + Symbol2);
                isPositionOpen = true;
            }
        }
    }
}

Essa função recebe dois vetores de valores (preços de fechamento de cada instrumento) e retorna o coeficiente de correlação, que varia entre -1 e 1.

Atualização do histórico de correlações

Para acompanhar de forma eficaz a dinâmica da correlação, armazenamos o histórico de seus valores:

void UpdateCorrelationHistory()
{
    // Сдвигаем историю корреляций
    for(int i = CorrelationPeriod-1; i > 0; i--)
    {
        correlationHistory[i] = correlationHistory[i-1];
    }
    
    // Добавляем новую корреляцию
    correlationHistory[0] = CalculateCurrentCorrelation();
}

double GetMinimumCorrelation()
{
    double minCorr = 1.0;
    for(int i = 0; i < CorrelationPeriod; i++)
    {
        if(correlationHistory[i] < minCorr)
            minCorr = correlationHistory[i];
    }
    return minCorr;
}

Esse módulo nos permite identificar momentos em que a correlação entre os instrumentos atinge mínimos locais, o que serve como um sinal adicional de entrada no mercado.

A arma secreta: otimização automática de parâmetros

As estratégias tradicionais de trading por pares sofrem frequentemente com parâmetros fixos que se tornam obsoletos com o tempo. Nosso algoritmo resolve esse problema através de uma otimização automática e periódica dos principais parâmetros:

void Optimize()
{
    Print("Начало оптимизации...");
    
    optimizationResults.Clear();
    
    // Диапазоны для оптимизации
    int zScorePeriodMin = 50, zScorePeriodMax = 200, zScorePeriodStep = 25;
    double entryThresholdMin = 1.5, entryThresholdMax = 3.0, entryThresholdStep = 0.25;
    double exitThresholdMin = 0.0, exitThresholdMax = 1.0, exitThresholdStep = 0.25;
    
    // Перебор всех комбинаций параметров
    for(int period = zScorePeriodMin; period <= zScorePeriodMax; period += zScorePeriodStep)
    {
        for(double entry = entryThresholdMin; entry <= entryThresholdMax; entry += entryThresholdStep)
        {
            for(double exit = exitThresholdMin; exit <= exitThresholdMax; exit += exitThresholdStep)
            {
                // Тестирование параметров
                double profit = TestParameters(period, entry, exit);
                
                OptimizationResult* result = new OptimizationResult();
                result.zScorePeriod = period;
                result.entryThreshold = entry;
                result.exitThreshold = exit;
                result.profit = profit;
                
                optimizationResults.Add(result);
            }
        }
    }
    
    // Поиск лучшего результата и применение новых параметров
    OptimizationResult* bestResult = NULL;
    for(int i = 0; i < optimizationResults.Total(); i++)
    {
        OptimizationResult* currentResult = optimizationResults.At(i);
        if(bestResult == NULL || currentResult.profit > bestResult.profit)
        {
            bestResult = currentResult;
        }
    }
    
    if(bestResult != NULL)
    {
        // Обновление внутренних параметров советника
        CurrentZScorePeriod = bestResult.zScorePeriod;
        CurrentEntryThreshold = bestResult.entryThreshold;
        CurrentExitThreshold = bestResult.exitThreshold;
        
        // Обновление массивов
        ArrayResize(prices1, CurrentZScorePeriod);
        ArrayResize(prices2, CurrentZScorePeriod);
        ArrayResize(ratio, CurrentZScorePeriod);
        ArrayResize(zscore, CurrentZScorePeriod);
        
        Print("Оптимизация завершена. Новые параметры: ZScorePeriod = ", CurrentZScorePeriod, 
              ", EntryThreshold = ", CurrentEntryThreshold, ", ExitThreshold = ", CurrentExitThreshold);
    }
}

Essa função percorre diferentes combinações do período de cálculo da pontuação Z, bem como os limiares de entrada e saída, escolhendo a combinação ideal com base em dados históricos. Diferentemente da otimização manual clássica, realizada apenas uma vez, nosso algoritmo se adapta continuamente às condições variáveis do mercado, aumentando a estabilidade dos resultados a longo prazo.

Escolha da frequência ideal de otimização

A frequência de otimização é um parâmetro importante que afeta o desempenho do sistema. Uma otimização muito frequente pode levar ao "sobreajuste" do algoritmo e ao uso excessivo de recursos computacionais, enquanto uma otimização pouco frequente pode não acompanhar as mudanças nas condições de mercado.

Em nosso algoritmo, a otimização é iniciada após um determinado número de ticks, o que garante um equilíbrio entre adaptabilidade e estabilidade:

// Автооптимизация
if(AutoOptimize && ++tickCount >= OptimizationPeriod)
{
    Optimize();
    tickCount = 0;
}

O valor padrão do parâmetro OptimizationPeriod é definido em 5000 ticks, mas pode ser ajustado conforme as características dos instrumentos negociados e o horizonte temporal da estratégia.

Testes inteligentes de parâmetros

Para cada conjunto de parâmetros, realizamos testes com base em dados históricos:

double TestParameters(int period, double entry, double exit)
{
    // Инициализация тестовых массивов
    double test_prices1[], test_prices2[], test_ratio[], test_zscore[];
    ArrayResize(test_prices1, period);
    ArrayResize(test_prices2, period);
    ArrayResize(test_ratio, period);
    ArrayResize(test_zscore, period);
    
    double close1[], close2[];
    ArraySetAsSeries(close1, true);
    ArraySetAsSeries(close2, true);
    
    int copied1 = CopyClose(Symbol1, PERIOD_CURRENT, 0, MinDataPoints, close1);
    int copied2 = CopyClose(Symbol2, PERIOD_CURRENT, 0, MinDataPoints, close2);
    
    if(copied1 < MinDataPoints || copied2 < MinDataPoints)
    {
        Print("Недостаточно данных для тестирования");
        return -DBL_MAX;
    }
    
    double profit = 0;
    bool inPosition = false;
    double entryPrice1 = 0, entryPrice2 = 0;
    ENUM_POSITION_TYPE posType1 = POSITION_TYPE_BUY, posType2 = POSITION_TYPE_BUY;
    
    // Создание истории корреляций для тестирования
    double testCorrelations[];
    ArrayResize(testCorrelations, CorrelationPeriod);
    
    // Заполнение начальных данных
    for(int i = 0; i < period; i++)
    {
        test_prices1[i] = close1[MinDataPoints - 1 - i];
        test_prices2[i] = close2[MinDataPoints - 1 - i];
    }
    
    // Обратная симуляция на исторических данных
    for(int i = period; i < MinDataPoints; i++)
    {
        // Обновление данных, расчет Z-score и симуляция торговых решений
        // ...
        
        double currentZScore = test_zscore[0];
        
        // Симуляция закрытия позиций
        if(inPosition)
        {
            double currentProfit = 0;
            
            if(posType1 == POSITION_TYPE_BUY)
                currentProfit += (close1[MinDataPoints - 1 - i] - entryPrice1) * 10000;
            else
                currentProfit += (entryPrice1 - close1[MinDataPoints - 1 - i]) * 10000;
                
            if(posType2 == POSITION_TYPE_BUY)
                currentProfit += (close2[MinDataPoints - 1 - i] - entryPrice2) * 10000;
            else
                currentProfit += (entryPrice2 - close2[MinDataPoints - 1 - i]) * 10000;
                
            if(currentProfit >= ProfitTarget)
            {
                profit += currentProfit;
                inPosition = false;
            }
        }
        
        // Симуляция открытия новых позиций
        if(!inPosition && i > CorrelationPeriod)
        {
            // Проверка условий входа на основе Z-score и корреляции
            // ...
        }
    }
    
    return profit; // Возвращаем полученную прибыль для данного набора параметров
}

O problema do sobreajuste e sua solução

Um dos principais desafios na otimização de sistemas de negociação é o risco de sobreajuste (overfitting), quando o sistema é ajustado com tanta precisão aos dados históricos que perde a capacidade de generalizar e operar com novos dados.

Para combater esse problema, nosso algoritmo aplica várias abordagens. Primeiro, utilizamos apenas parte dos dados históricos para otimização, reservando outra parte para validação (teste fora da amostra, ou out-of-sample testing). Em segundo lugar, concentramos o ajuste apenas nos parâmetros mais relevantes, a fim de reduzir a dimensionalidade do espaço de busca. Em terceiro, em vez de testar todas as combinações possíveis, utilizamos incrementos racionais (por exemplo, zScorePeriodStep = 25), o que diminui a probabilidade de ajuste excessivo às particularidades específicas dos dados históricos. E, por fim, por meio da atualização periódica dos parâmetros, garantimos que o sistema permaneça adaptado às condições de mercado em constante mudança.

Aprimoramento do método de média das posições

Uma das inovações fundamentais do nosso algoritmo é o gerenciamento adaptativo das posições quando ocorre uma queda na correlação entre os instrumentos negociados:

// Логика для усреднения позиций при падении корреляции
if(isPositionOpen && EnableAveraging)
{
    double currentCorrelation = correlationHistory[0];
    
    // Проверка на падение корреляции с момента открытия позиции
    if(averagingCount == 0)
    {
        // Если это первая проверка после открытия позиции
        initialCorrelation = currentCorrelation;
        averagingCount++;
    }
    else if(initialCorrelation - currentCorrelation > CorrelationDropThreshold)
    {
        // Если корреляция упала больше порога, добавляем усредняющую встречную сделку
        double averagingLot = lastLotSize * AveragingLotMultiplier;
        
        // Проверяем тип наших текущих позиций
        ENUM_POSITION_TYPE posType1 = POSITION_TYPE_BUY;
        string posSymbol = "";
        bool foundPos1 = false, foundPos2 = false;
        ENUM_POSITION_TYPE posType2 = POSITION_TYPE_BUY;
        
        // Проверка наших открытых позиций
        for(int i = 0; i < ArraySize(posTickets); i++)
        {
            if(PositionSelectByTicket(posTickets[i]))
            {
                string symbol = PositionGetString(POSITION_SYMBOL);
                ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
                
                if(symbol == Symbol1)
                {
                    posType1 = type;
                    foundPos1 = true;
                }
                else if(symbol == Symbol2)
                {
                    posType2 = type;
                    foundPos2 = true;
                }
                
                if(foundPos1 && foundPos2) break;
            }
        }
        
        if(foundPos1 && foundPos2)
        {
            // Открываем встречные сделки с увеличенным лотом
            ENUM_POSITION_TYPE reverseType1 = (posType1 == POSITION_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;
            ENUM_POSITION_TYPE reverseType2 = (posType2 == POSITION_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;
            
            if(OpenPairPosition(reverseType1, Symbol1, reverseType2, Symbol2, averagingLot))
            {
                Log("Открыта усредняющая встречная позиция: " + 
                    (reverseType1 == POSITION_TYPE_BUY ? "BUY " : "SELL ") + Symbol1 + ", " +
                    (reverseType2 == POSITION_TYPE_BUY ? "BUY " : "SELL ") + Symbol2);
                
                // Обновляем начальную корреляцию для следующей проверки
                initialCorrelation = currentCorrelation;
                averagingCount++;
            }
        }
    }
}

Essa abordagem permite não apenas se proteger contra perdas durante períodos de correlação reduzida, mas também potencialmente aumentar o lucro, caso a situação de mercado evolua de maneira favorável para nossa estratégia.

Mecanismo de média e sua justificativa

Tradicionalmente, a técnica de média de posições é considerada uma prática arriscada e perigosa, pois aumenta o tamanho da posição quando o preço se move contra nós. No entanto, no contexto do trading por pares, a lógica é diferente, pois realizamos a média das posições não devido a um movimento adverso de preço, mas por conta de alterações na correlação entre os instrumentos.

Quando a correlação entre o par de instrumentos diminui, isso pode significar uma de duas coisas: ou uma divergência temporária, que logo se corrigirá, ou uma mudança estrutural nas relações de mercado. No primeiro caso, abrir posições adicionais na direção oposta (com lote aumentado) pode elevar significativamente o lucro quando a correlação retornar aos valores normais. No segundo caso, essa abordagem nos protege contra perdas adicionais, pois, na prática, abrimos uma posição que funciona como hedge do risco inicial.

É importante observar que nosso algoritmo inclui mecanismos de proteção que limitam o grau de média. O parâmetro AveragingLotMultiplier determina em quantas vezes o tamanho do lote aumenta a cada nova operação de média, e o número total de médias é controlado por meio do histórico de correlações.

// Если корреляция упала больше порога, добавляем усредняющую встречную сделку
double averagingLot = lastLotSize * AveragingLotMultiplier;

// Открываем встречные сделки с увеличенным лотом
ENUM_POSITION_TYPE reverseType1 = (posType1 == POSITION_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;
ENUM_POSITION_TYPE reverseType2 = (posType2 == POSITION_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;

Aplicação de ordens de proteção stop-loss e take-profit

Além da lógica principal de negociação, nosso algoritmo também oferece a possibilidade de configurar ordens de proteção do tipo stop-loss e take-profit para o gerenciamento de risco em nível de posição individual:

// Расчет защитных стоп-лоссов и тейк-профитов, если они включены
double sl1 = 0, sl2 = 0, tp1 = 0, tp2 = 0;
double point1 = SymbolInfoDouble(symbol1, SYMBOL_POINT);
double point2 = SymbolInfoDouble(symbol2, SYMBOL_POINT);

if(EnableProtectiveStops)
{
    if(type1 == POSITION_TYPE_BUY)
    {
        double ask1 = SymbolInfoDouble(symbol1, SYMBOL_ASK);
        sl1 = ask1 - ProtectiveStopPips * point1;
    }
    else
    {
        double bid1 = SymbolInfoDouble(symbol1, SYMBOL_BID);
        sl1 = bid1 + ProtectiveStopPips * point1;
    }
    
    // Аналогичные расчеты для второго инструмента
    // ...
}

// Расчет тейк-профитов
if(EnableTakeProfit)
{
    // Расчеты для тейк-профитов
    // ...
}

Esses mecanismos de proteção são especialmente importantes quando se opera em mercados de alta volatilidade ou em períodos de instabilidade econômica, em que até mesmo instrumentos com alta correlação podem apresentar desvios significativos em seu comportamento.

Gestão dinâmica de risco

Um dos elementos mais críticos para o sucesso de longo prazo de qualquer sistema de negociação é o gerenciamento eficiente do risco. Nosso algoritmo calcula automaticamente o tamanho da posição com base em uma porcentagem predefinida de risco em relação ao saldo da conta:

double CalculatePositionSize()
{
    double balance = AccountInfoDouble(ACCOUNT_BALANCE);
    double riskAmount = balance * RiskPercent / 100.0;
    
    double tickValue1 = SymbolInfoDouble(Symbol1, SYMBOL_TRADE_TICK_VALUE);
    double tickSize1 = SymbolInfoDouble(Symbol1, SYMBOL_TRADE_TICK_SIZE);
    double point1 = SymbolInfoDouble(Symbol1, SYMBOL_POINT);
    
    double lotStep = SymbolInfoDouble(Symbol1, SYMBOL_VOLUME_STEP);
    double minLot = SymbolInfoDouble(Symbol1, SYMBOL_VOLUME_MIN);
    double maxLot = SymbolInfoDouble(Symbol1, SYMBOL_VOLUME_MAX);
    
    // Расчет лота на основе риска (используем приблизительный стоп-лосс в 100 пунктов для расчета)
    double virtualStopLoss = 100;
    double riskPerPoint = tickValue1 * (point1 / tickSize1);
    double lotSizeByRisk = riskAmount / (virtualStopLoss * riskPerPoint);
    
    // Округление до ближайшего шага лота
    lotSizeByRisk = MathFloor(lotSizeByRisk / lotStep) * lotStep;
    
    // Проверка на минимальный и максимальный размер лота
    lotSizeByRisk = MathMax(minLot, MathMin(maxLot, lotSizeByRisk));
    
    return lotSizeByRisk;
}

Essa abordagem garante um aumento proporcional do tamanho das posições conforme o capital cresce, e uma redução quando ocorre rebaixamento, mantendo-se fiel aos princípios de uma gestão de capital equilibrada e responsável.

Mecanismo de limitação de perdas consecutivas

Além disso, nosso algoritmo inclui um mecanismo de proteção para limitar o número máximo de operações consecutivas com prejuízo:

if(totalProfit < 0)
    consecutiveLosses++;
else
    consecutiveLosses = 0;

if(consecutiveLosses >= MaxConsecutiveLosses)
{
    Log("Достигнуто максимальное количество последовательных убытков. Торговля приостановлена.");
    // Реализация логики временной приостановки торговли
}

Esse mecanismo impede que a negociação continue durante períodos em que as condições de mercado não correspondem às premissas da estratégia, como ocorre em situações de mudanças estruturais nas relações de correlação entre os pares de moedas.

Gestão adaptativa de risco

Uma característica importante da nossa abordagem é a adaptação do nível de risco com base na volatilidade histórica e nos resultados das operações anteriores. Se as últimas operações foram lucrativas, o algoritmo pode aumentar ligeiramente o tamanho da posição; por outro lado, após uma sequência de operações perdedoras, o tamanho da posição é reduzido.

Isso é implementado por meio do ajuste dinâmico do parâmetro RiskPercent:

// Пример реализации адаптивного управления риском
double GetAdaptiveRiskPercent()
{
    double baseRisk = RiskPercent;
    
    // Уменьшаем риск при последовательных убытках
    if(consecutiveLosses > 0)
        baseRisk = baseRisk * (1.0 - 0.1 * consecutiveLosses);
    
    // Ограничиваем минимальный и максимальный риск
    return MathMax(0.2, MathMin(baseRisk, 2.0));
}


Avaliação da eficiência do trading por pares em dados reais

Para avaliar de forma objetiva a eficiência do algoritmo proposto, foi conduzida uma série de testes em dados históricos de diferentes pares de moedas. A seguir, analisamos os resultados obtidos em três pares particularmente representativos.

Resultados dos testes no par EURUSD/AUDUSD

Esse par clássico de instrumentos demonstra uma alta correlação histórica. Os testes realizados ao longo dos últimos cinco anos apresentaram os seguintes resultados:

  • Lucro total: +14% sobre o depósito inicial
  • Máxima retração: 0,33%
  • Percentual de operações lucrativas: 59%
  • Duração média das operações: 3 horas e 42 minutos
  • Índice de Sharpe: 5,3

A estratégia mostrou-se especialmente eficaz em períodos de alta volatilidade, como durante o Brexit e a pandemia de COVID-19, quando desvios temporários na correlação criaram diversas oportunidades de negociação.

Resultados no par AUDUSD/NZDUSD

Os pares das moedas da Austrália e da Nova Zelândia também apresentam alta correlação devido à semelhança estrutural de suas economias, ambas voltadas para a exportação de commodities. Resultados dos testes:

  • Lucro total: +17% sobre o depósito inicial
  • Máxima retração: 0,21%
  • Percentual de operações lucrativas: 56%
  • Duração média das operações: 3 horas e 26 minutos
  • Índice de Sharpe: 7,82

Uma característica interessante observada nesse par foi a duração média mais curta das operações, o que se explica pelo retorno mais rápido à média do valor da correlação.

Ajuste de parâmetros conforme o timeframe

A escolha do timeframe ideal depende do horizonte de tempo da negociação. Para operações de médio prazo (posições mantidas de alguns dias a algumas semanas), recomenda-se o uso dos timeframes diário ou de 4 horas. Já para operações de curto prazo (posições mantidas de algumas horas a alguns dias), são mais adequados os timeframes de 1 hora ou 15 minutos.

Ao alterar o timeframe, é necessário ajustar os parâmetros de forma correspondente:

  • Em timeframes mais altos (D1, H4), deve-se aumentar o período de cálculo da pontuação Z e reduzir os limiares de entrada e saída.
  • Em timeframes mais baixos (H1, M15), deve-se diminuir o período de cálculo da pontuação Z e aumentar os limiares de entrada e saída, a fim de filtrar o ruído do mercado.


Perspectivas de desenvolvimento e aprimoramentos adicionais

Embora a versão atual do algoritmo já apresente resultados sólidos, existem diversas direções promissoras para seu aperfeiçoamento.

Aplicação de métodos de aprendizado de máquina

Uma das áreas mais promissoras é a integração de métodos de aprendizado de máquina para prever a dinâmica da correlação entre instrumentos. Algoritmos como as redes neurais LSTM (Long Short-Term Memory) podem capturar de forma eficaz padrões complexos nas relações de correlação e prever suas mudanças com alta precisão.

Expansão para portfólios multimoeda

A versão atual do algoritmo opera com um par de moedas, mas o conceito pode ser estendido para um portfólio multimoeda, no qual as correlações entre vários instrumentos são monitoradas simultaneamente. Essa abordagem possibilita um uso mais eficiente da diversificação e permite identificar oportunidades de negociação em um espectro mais amplo de condições de mercado.

Integração com análise fundamental

Outro caminho promissor é a integração de indicadores econômicos fundamentais no processo de tomada de decisão do algoritmo. Por exemplo, levar em conta diferenças nas taxas de juros, inflação e outros indicadores macroeconômicos pode auxiliar na previsão de mudanças estruturais de longo prazo nas relações de correlação entre pares de moedas.


Considerações finais

O algoritmo de trading por pares com auto-otimização apresentado demonstra como princípios estatísticos relativamente simples podem ser transformados em um sistema de negociação eficaz por meio de métodos modernos de trading algorítmico.

A principal vantagem da nossa abordagem está em sua adaptabilidade: diferentemente dos sistemas com parâmetros fixos, nosso algoritmo se ajusta continuamente às mudanças nas condições de mercado, garantindo sua viabilidade no longo prazo.

É importante ressaltar que, apesar da automatização do processo de negociação, o entendimento profundo dos princípios de funcionamento do algoritmo e o monitoramento regular de sua execução continuam sendo condições indispensáveis para a aplicação bem-sucedida dessa estratégia.

No próximo artigo, analisaremos aprimoramentos adicionais do nosso algoritmo, incluindo a aplicação de métodos de aprendizado de máquina para previsão da dinâmica da correlação e a otimização de parâmetros com o uso de algoritmos genéticos.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/17800

Arquivos anexados |
PairsTradingOpt.mq5 (71.97 KB)
Últimos Comentários | Ir para discussão (2)
Hao T
Hao T | 27 abr. 2025 em 02:25
Uma abordagem aprimorada é aplicar o algoritmo aos gráficos Renko.
Desenvolvendo um Expert Advisor de Breakout Baseado em Eventos de Notícias do Calendário em MQL5 Desenvolvendo um Expert Advisor de Breakout Baseado em Eventos de Notícias do Calendário em MQL5
A volatilidade tende a atingir picos em torno de eventos de notícias de alto impacto, criando oportunidades significativas de breakout. Neste artigo, iremos delinear o processo de implementação de uma estratégia de breakout baseada em calendário. Abordaremos tudo, desde a criação de uma classe para interpretar e armazenar dados do calendário, o desenvolvimento de backtests realistas utilizando esses dados e, por fim, a implementação do código de execução para negociação ao vivo.
Redes neurais em trading: Ator–Diretor–Crítico (Conclusão) Redes neurais em trading: Ator–Diretor–Crítico (Conclusão)
O framework Actor–Director–Critic representa uma evolução da arquitetura clássica de aprendizado por agentes. O artigo apresenta uma experiência prática de sua implementação e adaptação às condições dos mercados financeiros.
Análise espectral singular unidimensional Análise espectral singular unidimensional
O artigo aborda os aspectos teóricos e práticos do método de análise espectral singular (SSA), que constitui um método eficaz de análise de séries temporais e permite representar a estrutura complexa da série como uma decomposição em componentes simples, tais como tendência, oscilações sazonais (periódicas) e ruído.
Critérios de tendência. Conclusão Critérios de tendência. Conclusão
Neste artigo, analisaremos as particularidades da aplicação prática de alguns critérios de tendência. Além disso, tentaremos desenvolver alguns novos critérios. A principal atenção será dada à eficácia desses critérios na análise de dados de mercado e no trading.