English Русский Deutsch 日本語
preview
您应该了解的MQL5向导技巧(第六十八部分):结合余弦核网络使用TRIX与威廉百分比范围形态

您应该了解的MQL5向导技巧(第六十八部分):结合余弦核网络使用TRIX与威廉百分比范围形态

MetaTrader 5积分 |
20 0
Stephen Njuki
Stephen Njuki

引言

在前一篇文章中,我们测试了10种信号形态,仅有3种能够实现前向步进。这些形态由两类指标信号组合生成:趋势指标TRIX和支撑/阻力震荡指标WPR。对于EA的训练/优化仅使用 2023年全年数据,前向步进测试则在2024年进行。测试交易品种为瑞郎兑日元(CHFJPY),时间周期为4小时。

在利用机器学习扩展可滚动预测的形态时,我们通常选择Python,因为它能高效地编写代码并训练网络,即使在无GPU的情况下也能流畅运行。在往期文章中,我们会先讲解可滚动预测形态函数的Python实现。而本文会简要介绍指标的Python实现,重点讲解以指标信号为输入的网络结构搭建。该网络是一个一维卷积网络,设计中采用了余弦核。


Python中的指标

为了在Python中将指标信号输入网络,我们可以使用现成的Python库,也可以自行编写代码。我们在Python中实现TRIX函数的代码如下:

def TRIX(df: pd.DataFrame, period: int) -> pd.DataFrame:
    """
    Calculate TRIX indicator and append it as 'TRIX' column to the input DataFrame.
    
    Args:
        df (pd.DataFrame): DataFrame with 'close' column
        period (int): Lookback period for EMA calculation
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'TRIX' column
    """
    # Input validation
    if not all(col in df.columns for col in ['close']):
        raise ValueError("DataFrame must contain 'close' column")
    if period < 1:
        raise ValueError("Period must be positive")
        
    # Create a copy to avoid modifying the input DataFrame
    result_df = df.copy()
    
    # Calculate triple EMA
    ema1 = df['close'].ewm(span=period, adjust=False).mean()
    ema2 = ema1.ewm(span=period, adjust=False).mean()
    ema3 = ema2.ewm(span=period, adjust=False).mean()
    
    # Calculate TRIX: percentage rate of change of triple EMA
    result_df['main'] = ema3.pct_change() * 100
    
    return result_df

TRIX计算三重平滑指数移动平均线(EMA)的变化率。函数输入为包含收盘价列的pandas数据帧,以及用于计算EMA的周期整数;输出为附加了指标列的数据帧。该指标主要通过三重平滑价格数据、凸显动量变化,来识别趋势方向与潜在反转信号。

上述代码首先定义了带类型注解的函数,保证代码清晰且类型安全。随后基于收盘价计算第一层EMA,并对价格数据做平滑处理以保持EMA权重一致。接下来,我们在第一层EMA的基础上计算第二层EMA,进一步平滑数据、降低噪声,将结果赋值给ema2。之后计算第三层EMA,完成三重平滑过程,这也令TRIX对动量变化更加敏感。最终输出结果为ema3。

完成计算后,我们会在输入的pandas数据帧中新增一列TRIX值,该值表示三重EMA(ema3)相对于前一时刻的百分比变化。计算过程中乘以100,是为了将结果转换为百分比的形式,更易于解读。定义好TRIX后,我们接下来实现WPR。其Python代码如下:

def WPR(df: pd.DataFrame, period: int) -> pd.DataFrame:
    """
    Calculate Williams %R indicator and append it as 'WPR' column to the input DataFrame.
    
    Args:
        df (pd.DataFrame): DataFrame with 'high', 'low', 'close' columns
        period (int): Lookback period for calculation
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'WPR' column
    """
    # Input validation
    if not all(col in df.columns for col in ['high', 'low', 'close']):
        raise ValueError("DataFrame must contain 'high', 'low', 'close' columns")
    if period < 1:
        raise ValueError("Period must be positive")
        
    # Create a copy to avoid modifying the input DataFrame
    result_df = df.copy()
    
    # Calculate highest high and lowest low over the period
    high_max = df['high'].rolling(window=period).max()
    low_min = df['low'].rolling(window=period).min()
    
    # Calculate Williams %R
    result_df['main'] = ((high_max - df['close']) / (high_max - low_min)) * -100
    
    return result_df

WPR是一款支撑/阻力震荡指标,用于判断价格是否处于超买/阻力位还是超卖/支撑位。该函数的输入同样是pandas数据帧,但需要包含最高价、最低价、收盘价三列数据,外加一个整数周期,用于确定指标的回溯窗口。输出依旧是新增一列的pandas数据帧,新增列命名为WPR,其取值范围为-100至0。

和TRIX一样,我们的代码首先为输入参数添加了类型注解。随后计算指定周期内的最高价,作为WPR计算的上边界,再用相同的回溯周期计算最低价。接下来,我们定义要添加到输入数据帧中的新列,并命名为WPR,采用标准WPR公式计算。 

Python可以轻松地将计算结果添加为数据列,像我这样从C类语言转过来的开发者,需要慢慢适应这一点。这一特性非常强大。

我们可以为这两个函数添加额外的校验逻辑:当前代码默认输入数据帧包含所需列,且数据行数充足。我们使用MetaTrader 5 Python模块的pandas数据实现。但添加错误处理不一定是针对缺失列(因为MetaTrader 5会保证列的完整性),而是针对可能存在数据行数不足的异常处理,提升函数的健壮性。此外,针对大数据集,优化重点应放在预计算滚动窗口或使用pandas向量化运算上。在测试阶段,将计算结果与MT5等平台的官方指标值做比对验证,也能确保计算精度。


在一维卷积(Conv1D)架构中使用余弦核的优势

一维卷积(Conv1D)网络是一种特殊的卷积神经网络(CNN),专为序列数据执行一维卷积运算,例如金融时间序列、文本数据等。在此过程中,它会通过卷积滤波器在输入数据上滑动,提取数据中的关键形态、趋势或特征模式。特征提取的输出自然会降低输入序列的维度。然而,每个滤波器都专门用于提取输入数据中某一类特定的或重要的特征。Conv1D网络包含卷积层、激活层、池化层和全连接层。对于存在时间或序列相关性的有序数据,Conv1D的处理效率极高。

余弦核是一种相似度度量方式。它通过计算两个向量间的夹角余弦值,量化二者方向的相似程度。计算时会用向量模长对向量点积做归一化处理。其输出值的范围为-1至+1区间内,-1代表两个向量方向完全相反,+1则代表二者方向完全一致。对于文本这类高维数据(方向特征远比幅值重要),该相似度计算方式效率极高。

在Conv1D中,引入余弦核具备多项优势:第一,核尺寸平滑过渡。余弦函数使卷积核呈现平滑的振荡变化,让网络能够平稳提取不同尺度的特征,避免出现突变。第二,通道数自适应变化。基于余弦规则调整通道数,可使卷积核数量逐步增减,从而在网络各层之间平衡模型复杂度与特征提取能力。

同时,余弦核还能实现稳健的特征提取。其振荡特性与金融等均值回归型时间序列的自然走势相契合,有助于网络捕捉周期性、循环性规律。最后,可变的核尺寸与通道数会带来正则化效果。通过在网络结构中引入可控的变化,能够降低模型过拟合风险。

将余弦核搭配Conv1D使用时,输入数据通常需为三维张量,维度依次对应:批次大小、输入通道数、输入序列长度。本文采用单变量时间序列,因此将输入通道数设置为1。合理的输入序列长度,可在使用不同尺寸卷积核时,避免特征维度过度缩减。 

该模型共有五项核心超参数需要调优:第一,基础通道数,初始值建议偏小,上限一般取16或32。第二,网络层数,常规取值为3至5层。层数过多易引发梯度消失问题,也会大幅增加运算量。第三,最大卷积核尺寸,针对长序列数据可设置为7或更大,为配合填充操作保证对称性,该数值需取奇数。

第四,振荡频率,用于控制卷积核尺寸与通道数的振荡节奏。取值范围在0.1至1.0区间内,0.5为适中档位,数值越小振荡越平缓,数值越大振荡越剧烈。第五,丢弃率,建议取值范围为0.2至0.5区间内,是重要的正则化手段。丢弃率越高,抑制过拟合的效果越强,但也会导致损失函数难以收敛至理想区间。

在模型训练时,可选用Adam等主流优化器,并搭配学习率调度器,加快收敛速度、提升收敛精度。再次强调,尤其在处理小数据集时,充分的批量归一化与丢弃,是防范过拟合的关键。如果基于余弦相似度构建卷积核并用于分类任务,输出层宜设计为二分类结构,如单个神经元搭配Sigmoid激活函数。如果是多分类、回归等其他任务,则需要对全连接层做相应调整。该方案最适用于存在周期性、振荡特征的时间序列数据场景。

余弦核卷积神经网络并非对所有数据集都能达到最优效果。需要与标准Conv1D架构做对比测试,以确保性能达标。此外,全局平均池化会限定输出维度。因此,可能不适用于需要输出完整序列的任务。


网络

该网络以TRIX和WPR指标信号作为二元输入向量,正是上文介绍的、采用余弦相似度来设定卷积核尺寸的卷积神经网络。代码实现如下:

class CosineConv1D(nn.Module):
    """
    A 1D Convolutional Neural Network with kernel sizes and channels based on cosine functions.
    Outputs a scalar float in [0,1] using sigmoid activation.
    """
    def __init__(self, input_channels: int, base_channels: int, num_layers: int, input_length: int,
                 max_kernel_size: int = 7, frequency: float = 0.5, dropout_rate: float = 0.3):
        super(CosineConv1D, self).__init__()
        
        if input_channels < 1 or base_channels < 1 or num_layers < 1:
            raise ValueError("Input channels, base channels, and num layers must be positive")
        if input_length < 1:
            raise ValueError("Input length must be positive")
        if max_kernel_size < 1:
            raise ValueError("Max kernel size must be positive")
        if not (0 <= dropout_rate < 1):
            raise ValueError("Dropout rate must be between 0 and 1")
        
        self.layers = nn.ModuleList()
        self.input_length = input_length
        current_length = input_length
        
        for i in range(num_layers):
            kernel_size = int(3 + (max_kernel_size - 3) * (1 + np.cos(2 * np.pi * frequency * i)) / 2)
            kernel_size = max(3, min(kernel_size, max_kernel_size))
            channels = int(base_channels * (1 + 0.5 * np.cos(np.pi * i / num_layers)))
            channels = max(base_channels, channels)
            padding = kernel_size // 2
            
            conv_layer = nn.Sequential(
                nn.Conv1d(
                    in_channels=input_channels if i == 0 else self.layers[-1][0].out_channels,
                    out_channels=channels,
                    kernel_size=kernel_size,
                    padding=padding
                ),
                nn.BatchNorm1d(channels),
                nn.ReLU(),
                nn.Dropout(dropout_rate)
            )
            self.layers.append(conv_layer)
            current_length = (current_length - kernel_size + 2 * padding) // 1 + 1
            
        self.global_pool = nn.AdaptiveAvgPool1d(1)
        self.fc = nn.Sequential(
            nn.Linear(channels, 1),
            nn.Sigmoid()
        )
        
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        for layer in self.layers:
            x = layer(x)
        x = self.global_pool(x)
        x = x.squeeze(-1)
        x = self.fc(x)
        return x

    def get_output_length(self) -> int:
        return self.layers[-1][0].out_channels

综上所述,该1D CNN采用余弦调制的卷积核尺寸与通道数,通过Sigmoid激活函数输出一个0至1区间内的值。该网络基于一个自定义的PyTorch模块,其继承自nn.Module模块。这种动态调制卷积核尺寸与通道数量的架构,使模型能够自适应匹配输入数据的特征模式。Sigmoid激活函数则确保输出为符合概率分布的0至1区间内。

网络的初始化过程自带参数校验,以确保通道数、网络层数、卷积核尺寸、丢弃率等输入参数合法有效。核心校验规则:所有输入参数必须为正数;丢弃率必须在0至1区间内。这是避免配置错误引发运行异常或模型性能低下的关键,同时可自定义参数也提升了网络设计的灵活性。 

在实际应用时,输入通道数应与输入数据维度保持一致。基础通道数用于控制模型容量,网络层数则在模型深度与计算量之间做平衡。可以调整最大卷积核尺寸与振荡频率,以优化前向传播各阶段的特征提取效果。

完成初始化与校验后,进入卷积核尺寸与通道数缩放环节。如前文所述,我们使用余弦函数动态调整每一层的卷积核大小与通道数量。卷积核尺寸在3到设定的最大核尺寸之间变化;通道数从基础通道数值开始向上递增。二者均由余弦函数进行平滑调制。

这一点至关重要,因为它在感受野和特征容量中引入了动态变化,使网络能够捕捉多样化的市场形态。余弦调制保证了卷积核之间平滑过渡,不会出现突变。在实现过程中,振荡频率通常在0.1至1.0之间,用于控制卷积核尺寸的振荡节奏。更高的基础通道数更适合复杂/高维度数据集。务必保证最大卷积核尺寸与输入数据长度匹配,避免过度填充。完成上述设置后,我们开始构建卷积层。并配合批量归一化、ReLU激活函数与丢弃,以提升稳定性。

每一层均由一维卷积、批量归一化、ReLU激活和丢弃组合而成。第一层使用输入通道。后续层使用上一层的输出通道。填充有助于保持输入序列长度不变。该方法中的每一层都发挥着至关重要的作用。卷积层负责特征提取。批量归一化提升训练的稳定性。ReLU激活函数引入非线性拟合能力。最后,丢弃实现正则化,防止过拟合。在实现过程中,丢弃率在0.2至0.5区间内可有效管控过拟合风险。确保输入通道数与数据相匹配很重要,可使用“nn.ModuleList”模块实现动态层管理。完成后,我们处理全局池化与输出层。

网络采用自适应平均池化,将空间维度压缩至1。其后衔接线性层,映射到单个输出结果,并通过Sigmoid函数将值限定在0至1区间内。池化操作会对整条序列的特征进行整合,无论输入序列长度如何,都能确保固定维度的输出。线性层与Sigmoid函数生成一个标量,适用于分类任务。在本例中,我们仅通过网络进行单个输出。在此情况下,应该确保最后一层的通道数与线性层的输入相匹配。

接下来,我们定义前向传播函数。该核心函数负责将输入数据依次经过卷积层、池化层,再完成最终输出转换。它定义了网络的前向传播,处理输入x。这是通过卷积层、全局池化(压缩空间维度)以及最后的全连接层完成的。该函数很重要,因为它指定数据流。这样确保从输入到输出的正确变换。压缩额外的空间维度会省略单例维度,以兼容线性层。实现过程中应确保张量形状正确。可以通过检查层输出来调试形状不匹配问题。

最后,我们有一个用于跟踪输出长度的函数。这种跟踪既确保了兼容性,又可以如上所述用于调试。它返回最终卷积层的输出通道数。这样提供了关于网络输出重要的基础数据,对下游任务或调试很有用。该函数还可用于验证与后续层或模型的兼容性。如果需要更多基础数据,也可以扩展。


序列与训练

我们还有一个创建序列的函数,用于将数据预处理为网络所需的格式。该函数用于将输入数据和标签准备成序列,以供1D CNN处理。在Python中的编码如下:

def create_sequences(data, labels, sequence_length):
    num_samples, num_features = data.shape
    sequences = []
    seq_labels = []
    
    # Ensure labels is 1D
    labels = labels.flatten()
    
    for i in range(num_samples - sequence_length + 1):
        sequences.append(data[i:i+sequence_length].T)  # Transpose to (num_features, sequence_length)
        seq_labels.append(labels[i+sequence_length-1])  # Use label of last sample in sequence
    
    sequences = np.array(sequences)  # Shape: (num_sequences, num_features, sequence_length)
    seq_labels = np.array(seq_labels).reshape(-1, 1)  # Shape: (num_sequences, 1)
    
    return torch.tensor(sequences, dtype=torch.float32), torch.tensor(seq_labels, dtype=torch.float32)

在创建序列时,我们首先创建长度与输入参数sequence-length匹配的序列,输入数据形状为[samples, features],以及相应的标签。接下来,对该数据进行转置,以匹配CNN的输入格式[features, sequence-length]。标签从每个序列的最后一个时间步长获取。这一点很重要,因为它通过将数据组织成所谓的“序列”这种相关数据段,为1D CNN准备数据。从而符合我们上述"CosineConv1D"网络的兼容性要求。

序列长度的设定基于时间依赖性,即时间序列中数据呈现可重复、可追溯模式的滞后距离。关键是确保特征数量与输入通道数量相匹配。对于具有复杂数据集的监督任务,标签对齐也需要验证。

定义了创建序列函数后,我们现在来看一下训练和评估函数。这里首先要做的是超参数设置。在函数的前4行代码部分,我们定义训练超参数。需要设置:小批量处理的批量大小;多头和空头条件的输入通道;序列长度、训练轮数和学习率。将序列长度设置为1,意味着价格K线之间不存在滞后,彼此直接相关。我们假设在1周滞后下存在形态内关系。训练函数编码如下:

def train_and_evaluate(x_train, y_train):
    # Hyperparameters
    batch_size = 32
    input_channels = 2  # TRIX and WPR
    sequence_length = 1  # Adjustable based on your needs
    num_epochs = 10
    learning_rate = 0.0005

    # Create sequences
    X_tensor, y_tensor = create_sequences(x_train, y_train, sequence_length)
    num_sequences = X_tensor.shape[0]
    
    # Initialize model
    model = CosineConv1D(
        input_channels=input_channels,
        base_channels=128,
        num_layers=16,
        input_length=sequence_length,
        max_kernel_size=7,
        frequency=0.5,
        dropout_rate=0.03
    )
    
    # Loss and optimizer
    criterion = nn.BCELoss()
    optimizer = optim.Adam(model.parameters(), lr=learning_rate)
    
    # Training loop
    model.train()
    for epoch in range(num_epochs):
        total_loss = 0
        for i in range(0, num_sequences, batch_size):
            batch_X = X_tensor[i:i+batch_size]  # Shape: (batch_size, 2, sequence_length)
            batch_y = y_tensor[i:i+batch_size]
            
            outputs = model(batch_X)
            loss = criterion(outputs, batch_y)
            
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            
            total_loss += loss.item()
        
        avg_loss = total_loss / (num_sequences // batch_size)
        print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}")
    
    # Export model to ONNX
    model.eval()
    dummy_input = torch.randn(1, input_channels, sequence_length)
    torch.onnx.export(
        model,
        dummy_input,
        inp_model_name,
        export_params=True,
        opset_version=11,
        do_constant_folding=True,
        input_names=['input'],
        output_names=['output'],
        dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}
    )
    print("\nModel exported to: ", inp_model_name)

    # Load and verify ONNX model
    try:
        onnx_model = onnx.load(inp_model_name)
        onnx.checker.check_model(onnx_model)
        print(f" ONNX model '{inp_model_name}' has been successfully exported and validated!")

        session = ort.InferenceSession(inp_model_name)

        for i in session.get_inputs():
            print(f" Input: {i.name}, Shape: {i.shape}, Type: {i.type}")

        for o in session.get_outputs():
            print(f" Output: {o.name}, Shape: {o.shape}, Type: {o.type}")

    except onnx.onnx_cpp2py_export.checker.ValidationError as e:
        print(f" ONNX model validation failed: {e}")
    except Exception as e:
        print(f" An error occurred: {e}")
        
    
    # Evaluate on a single sample
    with torch.no_grad():
        single_input = X_tensor[0:1]  # Shape: (1, 2, 50)
        scalar_output = model(single_input).squeeze()
        print(f"\nSingle sample input shape: {single_input.shape}")
        print(f"Single sample output shape: {scalar_output.shape}")
        print(f"Single sample output value: {scalar_output.item():.4f}")

这些步骤至关重要,因为超参数直接影响训练效率、模型容量及收敛速度。较小的序列长度会简化输入,而学习率会影响优化稳定性。根据内存限制,批量大小通常在16-64范围内是不错的选择。如前所述,输入通道应与数据特征匹配。此外,增加训练轮数或调整学习率(理想范围在0.0001至0.001之间),可以获得更好的收敛效果。

接下来,在训练函数中,我们初始化模型实例。我们选择实例化一个具有128个基础通道、16层和0.03低丢弃率的模型。使用典型的CPU而非GPU进行训练。这种网络架构配置处理长度为1的序列输入,并在复杂性和正则化之间取得平衡。

之后我们定义损失函数和优化器。使用二元交叉熵损失进行二元分类,以及具有指定学习率的Adam优化器。这一步很关键,因为BCE-Loss适合模型的sigmoid输出。Adam优化器以自适应学习率高效工作。BCELoss是二分类任务的理想选择,因为此时模型输出的是0至1范围内的单个值,类似于二值化输出。然而,如果收敛太慢,可以考虑其他优化器,如SGD,甚至调整学习率调度。

接下来是训练循环。通过遍历轮数和批量将模型设置为训练模式。它计算预测、计算损失、执行反向传播并更新权重。然后打印每轮的平均损失。这一点很重要,因为训练是通过最小化损失来优化模型的核心逻辑。批量处理大大提高了效率。运行此过程时,监控损失值并评估收敛速度很重要。如果损失平台期普遍出现,可以调整训练轮数或批量大小。务必确保调用zero-grad来重置梯度。

之后,我们需要处理验证并将模型导出为ONNX格式。训练完成后,我们使用虚拟输入将模型导出为ONNX格式,验证模型,然后创建ONNX运行时会话。这里支持动态批量大小。导出为ONNX很重要,因为它允许模型跨各种平台部署,其中最相关的是MQL5。另外,我们还要在此步骤验证导出的完整性,以免模型在后续使用时出现大量下游错误。导出时,务必确保opset-version与目标平台的兼容性。目前,12与MQL5配合良好。可以通过检查模型兼容性来处理验证错误。

接下来,我们有了评估或测试训练后模型的代码。在不进行梯度计算的情况下对单个序列执行评估,打印输入/输出形状和标量输出。基于训练权重以及单个样本的输出格式验证模型功能。这样有助于调试。也可用于确认模型行为。使用时,确保输入形状与训练数据匹配很重要。还应检查输出范围,确保其在0至1范围内。

总结我们网络类的这两个辅助函数:create-sequences和train-and-evaluate函数用于准备数据并训练CosineConv1D模型进行二元分类。这是通过余弦调制架构完成的。关键步骤包括:创建序列、调整超参数、训练模型、将模型导出为ONNX格式以及评估。在执行这些步骤时,还需采取额外措施,如调整序列长度、学习率和训练轮数,以获得最优性能。导出前验证ONNX模型也至关重要。


在MQL5中的实现

在以往讨论机器学习应用于扩展指标信号模式使用的文章中,我们总是因为篇幅所限而略过MQL5的实现。对于本文,由于Python中的函数实现变得过于重复,我认为应该涵盖在导入和使用导出的ONNX模型时需要在MQL5端考虑的一些方面。 

在导入ONNX模型并通过MQL5向导组装成EA的自定义信号类中,多头与空头条件的实现如下:

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalML_TRX_WPR::LongCondition(void)
{  int result  = 0, results = 0;
   vectorf _x;
   _x.Init(2);
   _x.Fill(0.0);
//--- if the model 1 is used
   if(((m_patterns_usage & 0x02) != 0) && IsPattern_1(POSITION_TYPE_BUY))
   {  _x[0] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_BUY, _x);
      if(_y > 0.0)
      {  result += m_pattern_1;
         results++;
      }
   }
//--- if the model 4 is used
   if(((m_patterns_usage & 0x10) != 0) && IsPattern_4(POSITION_TYPE_BUY))
   {  _x[0] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_BUY, _x);
      if(_y > 0.0)
      {  result += m_pattern_4;
         results++;
      }
   }
//--- if the model 5 is used
   if(((m_patterns_usage & 0x20) != 0) && IsPattern_5(POSITION_TYPE_BUY))
   {  _x[0] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_BUY, _x);
      if(_y > 0.0)
      {  result += m_pattern_5;
         results++;
      }
   }
//--- return the result
//if(result > 0)printf(__FUNCSIG__+" result is: %i",result);
   if(results > 0 && result > 0)
   {  return(int(round(result / results)));
   }
   return(0);
}
//+------------------------------------------------------------------+
//| "Voting" that price will fall.                                   |
//+------------------------------------------------------------------+
int CSignalML_TRX_WPR::ShortCondition(void)
{  int result  = 0, results = 0;
   vectorf _x;
   _x.Init(2);
   _x.Fill(0.0);
//--- if the model 1 is used
   if(((m_patterns_usage & 0x02) != 0) && IsPattern_1(POSITION_TYPE_SELL))
   {  _x[1] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_SELL, _x);
      if(_y < 0.0)
      {  result += m_pattern_1;
         results++;
      }
   }
//--- if the model 4 is used
   if(((m_patterns_usage & 0x10) != 0) && IsPattern_4(POSITION_TYPE_SELL))
   {  _x[1] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_SELL, _x);
      if(_y < 0.0)
      {  result += m_pattern_4;
         results++;
      }
   }
//--- if the model 5 is used
   if(((m_patterns_usage & 0x20) != 0) && IsPattern_5(POSITION_TYPE_SELL))
   {  _x[1] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_SELL, _x);
      if(_y < 0.0)
      {  result += m_pattern_5;
         results++;
      }
   }
//--- return the result
//if(result > 0)printf(__FUNCSIG__+" result is: %i",result);
   if(results > 0 && result > 0)
   {  return(int(round(result / results)));
   }
   return(0);
}

由以上两个函数明显可见,我们大量引用了"RunModel"函数。其代码如下:

//+------------------------------------------------------------------+
//| Forward Feed Network, to Get Forecast State.                     |
//+------------------------------------------------------------------+
double CSignalML_TRX_WPR::RunModel(int Index, ENUM_POSITION_TYPE T, vectorf &X)
{  vectorf _y(1);
   _y.Fill(0.0);
   ResetLastError();
   if(!OnnxRun(m_handles[Index], ONNX_NO_CONVERSION, X, _y))
   {  printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError());
      return(double(_y[0]));
   }
   //printf(__FUNCSIG__ + " y: "+DoubleToString(_y[0],5));
   if(T == POSITION_TYPE_BUY && _y[0] > 0.5f)
   {  _y[0] = 2.0f * (_y[0] - 0.5f);
   }
   else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f)
   {  _y[0] = 2.0f * (0.5f - _y[0]);
   }
   return(double(_y[0]));
}

这些函数所属的核心类继承自 MQL5 中用于 EA 信号处理的基类"CExpertSignal",可与向导生成的EA类文件体系无缝集成。我们为每个能够进行前向步进测试的形态训练了3个模型:形态1、4和5,导入至MQL5后,测试运行为我们提供了以下报告:

 r1

c1

对于形态1

r4

c4

对于形态4

r5

c5

对于形态5

对于新读者,此处有一份入门指南,其中包含关于如何使用附件代码通过MQL5向导组装EA的随附链接。我们创建的自定义信号类旨在与MQL5向导轻松集成,使其可以在不同的EA中重复使用。机器学习集成现在已成为现实,因为我们可以使用ONNX模型来增强EA的预测能力。这确实需要一套稳健的训练数据。通过样本外测试验证模型对于避免过拟合很重要,这就是为什么出于展示目的,我们对数据采用50/50分割,即训练一年并在下一年进行前向步进测试。

从测试运行来看,形态1、4和5似乎都能前向步进,尽管只有形态4的表现似乎更“令人信服”。除了测试窗口较短之外,这些测试运行还有重大注意事项。其中最主要的是,测试是在持仓具有止盈价格目标但没有止损的情况下进行的。使用限价订单入场也使这些结果比实际情况略显乐观。在解读或评估是否进一步开发所附源代码时,读者应牢记所有这些考虑因素。


结论

我们探讨了如何将TRIX震荡指标与WPR震荡指标信号相结合,并通过机器学习模型处理以做出预测。由于所考察的形态本身已经能够通过前向步进测试,因此机器学习模型本质上是对这些已知具备前向步进能力的交易信号再做一层过滤。性能略有提升,在后面的文章中,我们将考虑测试那些无法进行前向步进的机器学习形态。

名称 描述
wz-68.mq5 根据向导组装的EA,其标题概述了组装中使用的文件
SignalWZ_68.mqh 向导组装中使用的自定义信号类文件
68_1.mqh 形态1的导出ONNX模型
68_4.mqh 形态4的导出ONNX模型
68_5.mqh 形态5的导出ONNX模型

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18305

附加的文件 |
wz-68.mq5 (6.89 KB)
SignalWZ_68.mqh (13.98 KB)
68_1.onnx (6538.3 KB)
68_4.onnx (6538.3 KB)
68_5.onnx (6538.3 KB)
趋势判定标准:结论 趋势判定标准:结论
在本文中,我们将探讨在实践中应用某些趋势标准的具体细节。我们还将尝试制定几个新的标准。重点将放在将这些标准应用于市场数据分析和交易的效率上。
市场模拟(第 17 部分):套接字(十一) 市场模拟(第 17 部分):套接字(十一)
在 MetaTrader 5 中运行的那部分代码的实现没有任何困难。然而,有几点需要考虑。这是必要的,这样你才能让系统正常工作。记住一件重要的事情:不会只有一个程序在运行。事实上,我们必须同时运行三个程序。重要的是,要确保每个部分都能以一种能够相互交流和沟通的方式实施和构建,并且每个部分都能理解其他部分正在尝试或打算做什么。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
金融时间序列中的保形预测探索 金融时间序列中的保形预测探索
本文将介绍保形预测(conformal predictions)及其实现库MAPIE。这是一种较新的机器学习方法,重点不在于发现数据规律,而在于为现有模型提供风险管理与不确定性量化能力。保形预测本身并非用于挖掘数据中的规律,而仅用于评估现有模型对特定样本预测的置信度,并筛选出可靠的预测结果。