English Русский Español Português
preview
配对交易:基于Z值差异的自动优化算法交易

配对交易:基于Z值差异的自动优化算法交易

MetaTrader 5交易系统 |
55 7
Yevgeniy Koshtenko
Yevgeniy Koshtenko

引言

在交易世界里,很多人执着于复杂指标、不停寻找 “终极秘籍”,而往往最有效的策略,源于朴素的统计规律。今天,我们将走进配对交易这一极具魅力的领域,探索现代算法技术如何让这一经典策略实现效率跃升。

配对交易最早由华尔街量化交易员在 20 世纪 80 年代提出,长期以来仅为大型对冲基金和机构投资者所专属。但随着技术进步以及 MT4/MT5 等交易平台的普及,这一策略也开始向个人交易者开放。

本文基于一套真实可用的交易 EA,将经典配对交易理念与现代优化技术相结合,使其能够自适应不断变化的市场环境。这套方法不仅能识别相关资产价格关系中的短期异常,还能灵活应对市场结构性变化。


配对交易理念:统计原理的应用

配对交易是一种市场中性策略,核心是利用高度相关资产走势中的统计偏离获利。其核心思路简洁而精妙:当两个关联品种的价格偏离超出常规区间时,二者的价格关系大概率会向历史均值回归。

策略数学基础

该策略建立在相关性与平稳性两大核心统计概念之上。相关性用来衡量两个变量之间的关联紧密度,即一个变量变动时,另一个变量会随之产生多大程度的联动。在金融市场中,相关系数的取值范围介于 -1(完全负相关)到 +1(完全正相关)之间。

平稳性则是指时间序列的均值、方差和自相关等统计特征不随时间推移而发生显著改变。做配对交易,最关键的一点就是两个资产的价格关系必须具备平稳性。也就是说,价格一旦发生偏离,最终都会向均值回归。

这一点对外汇交易者尤为适用,因为货币对之间通常存在较强相关性。例如欧元兑美元(EURUSD)与英镑兑美元(GBPUSD)走势往往同向,只是波动幅度不同,并会出现阶段性偏离。而这些偏离正是潜在盈利来源。

实际例子

以 EURUSD 和 GBPUSD 为例。由于欧元区与英国地缘相近、经济联系紧密,这两个货币对历史上呈现高度正相关。

若某一阶段 EURUSD 涨幅明显快于 GBPUSD,二者的价格比值就会上升。当这一比值显著偏离历史均值时,便可预判其大概率会向均值回归。此时配对交易的思路就是:做空被高估的 EURUSD,同时做多被低估的 GBPUSD。

市场中性策略的优势

配对交易最核心的优势,在于其对整体市场方向相对不敏感。由于我们在相关资产上同时开立多单与空单,整体净持仓接近市场中性。这意味着无论市场上涨还是下跌,策略都有盈利可能,在高波动、高不确定性行情中价值尤为突出。

Z分数:核心判断指标

本算法体系以Z值(Z-score)为核心指标。Z 是一个统计量,用于衡量当前价格比值相对于历史均值偏离了多少个标准差:

Z-score=(当前比值−平均比值)/标准差

当 Z 值突破某一阈值(例如 ±2.0)时,说明价格关系已出现显著偏离,依据统计规律,其向均值回归的概率极高。

简单来说,Z 值衡量的是当前价格关系在历史走势中 “有多异常”。Z 值为 + 2.0 意味着当前比值偏离均值两个标准差,在正态分布中,这种情况出现的概率仅约 2.3%。

Z 值的解读

Z-score>0:第一个交易品种(品种 1)相对第二个品种(品种 2)被高估。Z-score<0:品种 1 相对品种 2 被低估。|Z-score|<1:价格比值处于正常区间;1<|Z-score|<2:出现中度偏离。2<|Z-score|<3:出现显著偏离(可作为潜在入场信号);|Z-score|>3:出现极端偏离(均值回归概率极高)。

最优计算周期选择

Z 值计算周期的选择是影响策略效果的关键参数。周期过短容易产生频繁假信号,周期过长则可能错失短期交易机会。

本 EA 通过历史数据自动优化 Z 值计算周期,实现对当前市场环境的动态适配。


算法架构

这套 EA 基于三大核心原则:统计分析,识别异常价差;实时动态参数优化;结合相关性变化的自适应风险管理。

EA 整体结构

算法由多个联动模块构成:数据采集与预处理、统计分析、交易决策、自动优化以及风险管理模块。数据采集模块获取货币对的历史价格与实时价格,并对数据进行筛选和标准化处理。统计分析模块计算价格比值、Z 值以及货币对之间的相关系数。交易决策模块确定开仓点位,并对持仓头寸进行管理。优化模块定期测试不同参数组合,筛选出最优参数。风险管理模块计算最优持仓手数,并对连续亏损进行控制。

比值与 Z 值计算

本算法的核心模块,是用于计算价格比值并将其转化为 Z 值的函数:

void CalculateRatioAndZScore()
{
    // Price ratio calculation
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        if(prices2[i] == 0) continue;
        ratio[i] = prices1[i] / prices2[i];
    }
    
    // Calculate the average
    double mean = 0;
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        mean += ratio[i];
    }
    mean /= CurrentZScorePeriod;
    
    // Calculation of standard deviation
    double stdDev = 0;
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        stdDev += MathPow(ratio[i] - mean, 2);
    }
    stdDev = MathSqrt(stdDev / CurrentZScorePeriod);
    
    // Calculate Z-score
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        if(stdDev == 0)
            zscore[i] = 0;
        else
            zscore[i] = (ratio[i] - mean) / stdDev;
    }
}

这段代码展示了我们如何计算价格比值并将其转化为 Z 值。请注意其中对标准差趋近于零时的边界情况处理,这能确保算法运行的稳定性。

相关性追踪与入场点位判断

我们这套方案的核心创新点之一,是采用动态相关性追踪来确定最优入场点位:

// The logic for opening new positions is to enter when the correlation is at a minimum
if(!isPositionOpen)
{
    double currentCorrelation = correlationHistory[0];
    double minCorrelation = GetMinimumCorrelation();
    
    // If the current correlation is close to the minimum and the Z-score exceeds the threshold
    if(MathAbs(currentCorrelation - minCorrelation) < 0.01 && 
       MathAbs(currentZScore) >= CurrentEntryThreshold)
    {
        // Calculate lot size based on risk
        double riskLot = CalculatePositionSize();
        
        if(currentZScore > 0) // Symbol1 is overvalued, Symbol2 is undervalued
        {
            // Sell Symbol1 and buy Symbol2
            if(OpenPairPosition(POSITION_TYPE_SELL, Symbol1, 
                               POSITION_TYPE_BUY, Symbol2, riskLot))
            {
                Log("Paired position opened: SELL " + Symbol1 + ", BUY " + Symbol2);
                isPositionOpen = true;
            }
        }
        else // Symbol1 is undervalued, Symbol2 is overvalued
        {
            // Buy Symbol1 and sell Symbol2
            if(OpenPairPosition(POSITION_TYPE_BUY, Symbol1, 
                               POSITION_TYPE_SELL, Symbol2, riskLot))
            {
                Log("Paired position opened: BUY " + Symbol1 + ", SELL " + Symbol2);
                isPositionOpen = true;
            }
        }
    }
}

该函数接收两组数值数组(两个交易品种的收盘价),返回取值范围在 -1 到 1 之间的相关系数。

更新相关性历史数据

为了高效追踪相关性的动态变化,我们会存储相关性数值的历史记录:

void UpdateCorrelationHistory()
{
    // Shift the correlation history
    for(int i = CorrelationPeriod-1; i > 0; i--)
    {
        correlationHistory[i] = correlationHistory[i-1];
    }
    
    // Add a new correlation
    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;
}

通过这个模块,我们可以精准捕捉交易品种间相关性触及局部最小值的时刻,将其作为额外的市场入场信号。

核心利器:参数自动优化

传统的配对交易策略普遍存在一个缺陷:使用固定参数,而这些参数会随着时间推移失效。我们的算法通过定期自动优化关键参数,彻底解决了这一问题。

void Optimize()
{
    Print("Starting optimization...");
    
    optimizationResults.Clear();
    
    // Optimization ranges
    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;
    
    // Iterate over all combinations of parameters
    for(int period = zScorePeriodMin; period <= zScorePeriodMax; period += zScorePeriodStep)
    {
        for(double entry = entryThresholdMin; entry <= entryThresholdMax; entry += entryThresholdStep)
        {
            for(double exit = exitThresholdMin; exit <= exitThresholdMax; exit += exitThresholdStep)
            {
                // Test parameters
                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);
            }
        }
    }
    
    // Find the best result and apply new parameters
    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)
    {
        // Update the EA internal parameters
        CurrentZScorePeriod = bestResult.zScorePeriod;
        CurrentEntryThreshold = bestResult.entryThreshold;
        CurrentExitThreshold = bestResult.exitThreshold;
        
        // Update arrays
        ArrayResize(prices1, CurrentZScorePeriod);
        ArrayResize(prices2, CurrentZScorePeriod);
        ArrayResize(ratio, CurrentZScorePeriod);
        ArrayResize(zscore, CurrentZScorePeriod);
        
        Print("Optimization complete. New parameters: ZScorePeriod = ", CurrentZScorePeriod, 
              ", EntryThreshold = ", CurrentEntryThreshold, ", ExitThreshold = ", CurrentExitThreshold);
    }
}

该函数会遍历 Z 值计算周期、入场阈值、离场阈值的各类参数组合,并基于历史数据筛选出最优参数配置。与传统仅执行一次的手动优化不同,我们的算法能够持续适配不断变化的市场环境,显著提升交易结果的长期稳定性。

选择最优的优化频率

优化频率是影响系统运行效果的重要参数。优化过于频繁会导致算法出现过拟合问题,同时造成不必要的算力消耗;而优化频率过低,则无法跟上市场环境的变化。

在我们的算法中,系统会在累计一定数量的报价跳动(tick)后触发优化,以此在策略适应性与运行稳定性之间实现平衡:

// Auto optimization
if(AutoOptimize && ++tickCount >= OptimizationPeriod)
{
    Optimize();
    tickCount = 0;
}

优化周期(OptimizationPeriod)参数的默认值设定为 5000 次报价跳动,你也可以根据交易品种的特性和策略时间周期进行调整。

智能参数测试

针对每一组参数配置,我们都会在历史数据上进行回测验证:

double TestParameters(int period, double entry, double exit)
{
    // Initialize test arrays
    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("Not enough data for testing");
        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;
    
    // Create a correlation history for testing
    double testCorrelations[];
    ArrayResize(testCorrelations, CorrelationPeriod);
    
    // Fill in the initial data
    for(int i = 0; i < period; i++)
    {
        test_prices1[i] = close1[MinDataPoints - 1 - i];
        test_prices2[i] = close2[MinDataPoints - 1 - i];
    }
    
    // Inverse simulation on historical data
    for(int i = period; i < MinDataPoints; i++)
    {
        // Update data, calculate Z-score and simulate trading decisions 
        // ...
        
        double currentZScore = test_zscore[0];
        
        // Simulate closing positions
        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;
            }
        }
        
        // Simulate opening new positions
        if(!inPosition && i > CorrelationPeriod)
        {
            // Check entry conditions based on Z-score and correlation
            // ...
        }
    }
    
    return profit; // Return the resulting profit for the given set of parameters
}

应对过拟合问题

交易系统优化过程中面临的核心难题之一,就是过拟合风险。当系统对历史数据的适配过于精准时,会丧失泛化能力,无法有效应对全新的市场数据,这便是过拟合。

我们的算法采用多重方案规避这一问题:第一,仅使用部分历史数据进行参数优化,保留另一部分数据用于模型验证(样本外测试)。第二,仅针对核心关键参数进行优化,降低参数搜索空间的维度。第三,不遍历所有可能的参数值,而是采用合理的步长(例如 Z 值周期步长设为 25),降低模型过度贴合历史数据特定特征的概率。最后,通过定期更新参数,确保系统能够持续适配不断变化的市场环境。

优化后的分批加仓策略

本算法的另一项核心创新,是在交易品种相关性下降时,采用自适应仓位管理机制:

// Logic for averaging positions when correlation drops
if(isPositionOpen && EnableAveraging)
{
    double currentCorrelation = correlationHistory[0];
    
    // Check for a drop in correlation from the moment a position is opened
    if(averagingCount == 0)
    {
        // If this is the first check after opening a position
        initialCorrelation = currentCorrelation;
        averagingCount++;
    }
    else if(initialCorrelation - currentCorrelation > CorrelationDropThreshold)
    {
        // If the correlation has fallen below the threshold, add an averaging counter trade 
        double averagingLot = lastLotSize * AveragingLotMultiplier;
        
        // Check the type of our current positions
        ENUM_POSITION_TYPE posType1 = POSITION_TYPE_BUY;
        string posSymbol = "";
        bool foundPos1 = false, foundPos2 = false;
        ENUM_POSITION_TYPE posType2 = POSITION_TYPE_BUY;
        
        // Check our open positions
        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)
        {
            // Open counter trades with an increased lot
            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("Averaging counter position opened: " + 
                    (reverseType1 == POSITION_TYPE_BUY ? "BUY " : "SELL ") + Symbol1 + ", " +
                    (reverseType2 == POSITION_TYPE_BUY ? "BUY " : "SELL ") + Symbol2);
                
                // Update the initial correlation for the next check
                initialCorrelation = currentCorrelation;
                averagingCount++;
            }
        }
    }
}

这种方法不仅能在相关性下降时有效规避亏损,还能在市场走势向有利方向发展时,进一步提升潜在收益。

分批加仓机制及其合理性

传统交易中,分批加仓通常被视为风险极高的操作,因为它会在价格朝不利方向运行时扩大持仓规模。但在配对交易场景下,情况截然不同:我们并非在价格走势不利时分批加仓,而是在交易品种间的相关性发生变化时执行该操作。

当一对交易品种的相关性下降时,通常意味着两种可能:一是短期偏离,很快会自行修复;二是市场价格关系出现结构性改变。第一种情况下,反向开立额外加仓头寸(放大手数)能够在相关性回归正常水平时大幅提升收益。第二种情况下,加仓头寸会对冲初始持仓的风险,帮助我们规避进一步亏损。

需要重点说明的是,我们的算法内置了风控机制,对分批加仓的幅度进行严格限制。AveragingLotMultiplier 参数决定了每次加仓的手数放大比例,而加仓总次数则通过追踪相关性历史数据进行管控。

// If the correlation has fallen below the threshold, we add an averaging counter trade
double averagingLot = lastLotSize * AveragingLotMultiplier;

// Open counter trades with an increased lot
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;

保护性止损与止盈的应用

除核心交易逻辑外,本算法还支持设置保护性止损与止盈,从单笔持仓层面实现风险管控。

// Calculate protective stop losses and take profits, if enabled
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;
    }
    
    // Similar calculations for the second instrument
    // ...
}

// Take profit calculation
if(EnableTakeProfit)
{
    // Calculations for take profits
    // ...
}

这些风控保护机制在高波动市场或经济不稳定时期尤为重要,即便高度相关的交易品种,此时也可能出现显著的走势背离。

动态风险管理

任何交易系统想要实现长期盈利,合理的风险管理都是核心要素。本算法会根据账户资金的预设风险比例,自动计算持仓规模:

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);
    
    // Risk-based lot calculation (using an approximate stop loss of 100 pips for calculation)
    double virtualStopLoss = 100;
    double riskPerPoint = tickValue1 * (point1 / tickSize1);
    double lotSizeByRisk = riskAmount / (virtualStopLoss * riskPerPoint);
    
    // Round to the nearest lot step
    lotSizeByRisk = MathFloor(lotSizeByRisk / lotStep) * lotStep;
    
    // Check for minimum and maximum lot size
    lotSizeByRisk = MathMax(minLot, MathMin(maxLot, lotSizeByRisk));
    
    return lotSizeByRisk;
}

该机制会随着资金增长相应扩大仓位,在资金回撤时缩小仓位,符合稳健的资金管理原则。

连续亏损限制机制

此外,算法还内置了风控保护,限制最大连续亏损交易次数:

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

if(consecutiveLosses >= MaxConsecutiveLosses)
{
    Log("Reached maximum number of consecutive losses. Trading suspended.");
    // Implementation of logic for temporary suspension of trading
}

当市场环境与策略预设条件不匹配时(例如货币对相关性结构发生改变),该机制可避免继续交易造成更大亏损。

自适应风险管理

本方案的一大特色是根据历史波动率与历史交易结果动态调整风险等级。若近期交易盈利,算法会适度放大仓位;连续亏损后,则自动降低持仓规模。

这一效果通过动态调整风险比例参数(RiskPercent)实现:

// Example of implementing adaptive risk management
double GetAdaptiveRiskPercent()
{
    double baseRisk = RiskPercent;
    
    // Reduce risk in case of consecutive losses
    if(consecutiveLosses > 0)
        baseRisk = baseRisk * (1.0 - 0.1 * consecutiveLosses);
    
    // Limit the minimum and maximum risk
    return MathMax(0.2, MathMin(baseRisk, 2.0));
}


基于真实数据的配对交易效果评估

为客观评估算法效果,我们在多组货币对的历史数据上进行了多次测试。以下为三组代表性品种的回测结果。

EURUSD/AUDUSD 测试结果

该经典品种组合历史相关性极高。近 5 年回测表现如下:

  • 总收益:初始资金盈利 +14%
  • 最大回撤:0.33%
  • 盈利交易占比:59%
  • 平均持仓时长:3 小时 42 分钟
  • 夏普比率:5.3

该策略在高波动时期表现尤为突出,如英国脱欧、新冠疫情期间,相关性短暂偏离带来了大量交易机会。

AUDUSD/NZDUSD 测试结果

澳元与纽元货币对因同属商品型经济体,结构相近,相关性同样极高。测试结果:

  • 总收益:初始资金盈利 +17%
  • 最大回撤:0.21%
  • 盈利交易占比:56%
  • 平均持仓时长:3 小时 26 分钟
  • 夏普比率:7.82

该组合的特点是平均持仓时间更短,原因是其相关性向均值回归的速度更快。

根据时间周期设置参数

最优周期的选择取决于交易时间框架。中期交易(持仓数天至数周):推荐日线(D1)或 4 小时(H4)周期。短期交易(持仓数小时至数天):适合 1 小时(H1)或 15 分钟(M15)周期。

切换周期时需同步调整参数:

  • 高周期(D1、H4):应增大 Z 值计算周期,降低入场 / 离场阈值。
  • 低周期(H1、M15):应减小 Z 值计算周期,提高入场 / 离场阈值,以过滤市场噪音。


发展前景与后续优化方向

当前版本算法已表现出色,但仍有多方面可进一步提升。

引入机器学习方法

一个重要方向是集成机器学习模型来预测品种间相关性走势。例如长短期记忆网络(LSTM)等算法,可有效捕捉相关性中的复杂规律,并高精度预测其变化。

扩展至多货币投资组合

目前版本的算法仅针对一组货币对运行,但这一思路可以扩展至多货币投资组合,实现同时监控多个交易品种之间的相关性。这种方式能让我们更高效地实现分散投资,并在更广泛的市场环境中捕捉交易机会。

与基本面分析相结合

另一个极具潜力的方向,是将经济基本面指标融入算法的决策逻辑中。例如,纳入利率差异、通胀水平及其他宏观经济指标,能够更准确地预判货币对之间相关性的长期变化趋势。


结论

本文介绍的自动优化配对交易算法表明,借助现代算法交易技术,简单的统计原理即可转化为高效的交易系统。

该方案的核心优势在于自适应能力:与固定参数系统不同,本算法可持续适配市场变化,保障长期有效性。

需要注意的是,即便交易实现自动化,深入理解算法运行逻辑并定期监控,仍是策略成功应用的必要前提。

在下一篇文章中,我们将继续优化算法,包括使用机器学习预测相关性走势,以及通过遗传算法进行参数优化。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/17800

附加的文件 |
PairsTradingOpt.mq5 (71.85 KB)
最近评论 | 前往讨论 (7)
Solomon Anietie Sunday
Solomon Anietie Sunday | 31 3月 2026 在 21:43
Hao T #:
一种改进的方法是将算法应用于 Renko 图表。

你是说这篇文章中的策略对 Renko 类型的图表最有效?

还有,上面那些图片是怎么回事?你想告诉我们什么?

Alamgir Malek
Alamgir Malek | 1 4月 2026 在 10:05
它在任何图表、任何货币对上都有效吗?
Solomon Anietie Sunday
Solomon Anietie Sunday | 1 4月 2026 在 17:03
亲爱的作者,我对均值回复策略了解不多。如果可以,它对输入参数和配置有什么影响?
anandpandya
anandpandya | 5 4月 2026 在 09:00
这是曲线拟合。你的想法很有趣...
Ryan L Johnson
Ryan L Johnson | 7 4月 2026 在 19:17
Solomon Anietie Sunday #:
[本文中的策略最适用于 Renko 类型的图表

可以说,Renko 图表是永恒的。尽管每块 Renko 砖仍有时间戳,但自定义 Renko 图表的时间刻度不是均匀递增的。因此,使用 Renko 可以省去交易过程中手动选择最佳时间框架的臆测工作。取而代之的是选择最佳的砖块大小。此外,Renko 本身就能消除图表噪音--每块砖的大小都一样。

Solomon Anietie Sunday#:
[W]上面这些图片是关于什么的?你想告诉我们什么?

虽然我看不懂中文,但我能看懂象形文字。在测试器中,启用该代码的 "以点数计算利润以加快计算速度 "似乎是有利可图的,而禁用该代码则是无利可图的。

珊瑚礁优化算法(CRO) 珊瑚礁优化算法(CRO)
本文对珊瑚礁优化(CRO)算法进行了全面分析,该算法是一种受珊瑚礁形成与发育生物过程启发的元启发式方法。该算法对珊瑚进化的关键环节进行了建模,包括广播产卵(群体产卵)、体内受精(抱卵孵化)、幼虫附着、无性繁殖以及有限礁区空间的竞争。尤其关注该算法的改进版本。
风险管理(第五部分):将风险管理系统集成到 EA 中 风险管理(第五部分):将风险管理系统集成到 EA 中
在本文中,我们将实现在先前文章中开发的风险管理系统,并添加在其他文章中描述的订单区块指标。此外,我们将进行一次回测,以便比较启用风险管理系统前后的结果,并评估动态风险的影响。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
价格行为分析工具包开发(第三十部分):商品通道指数(CCI)零线的EA 价格行为分析工具包开发(第三十部分):商品通道指数(CCI)零线的EA
价格行为分析的自动化是未来发展趋势。在本文中,我们将运用双CCI指标、零线交叉策略、指数移动平均线(EMA)以及价格行为分析,开发一款能够生成交易信号,并利用平均真实波幅(ATR)设定止损(SL)和止盈(TP)水平的工具。请阅读本文,了解我们如何开发这款CCI零线的EA。