Explorando Técnicas Avançadas de Aprendizado de Máquina na Estratégia de Rompimento da Caixa de Darvas
Introdução
A estratégia de rompimento da Caixa de Darvas, criada por Nicolas Darvas, é uma abordagem de negociação técnica que identifica potenciais sinais de compra quando o preço de uma ação sobe acima de um intervalo definido de "caixa", sugerindo forte momentum de alta. Neste artigo, aplicaremos esse conceito de estratégia como exemplo para explorar três técnicas avançadas de aprendizado de máquina. Estas incluem usar um modelo de aprendizado de máquina para gerar sinais em vez de filtrar negociações, empregar sinais contínuos em vez de discretos, e utilizar modelos treinados em diferentes períodos gráficos para confirmar negociações. Esses métodos oferecem novas perspectivas sobre como o aprendizado de máquina pode aprimorar o trading algorítmico além das práticas tradicionais.
Este artigo se aprofundará nas características e na teoria por trás de três técnicas avançadas que educadores raramente abordam, pois são inovadoras em comparação com métodos tradicionais. Também oferecerá insights sobre tópicos avançados como engenharia de atributos e ajuste de hiperparâmetros durante o processo de treinamento do modelo. No entanto, não cobrirá todas as etapas do fluxo de trabalho de treinamento de modelo de aprendizado de máquina em detalhe. Para leitores curiosos sobre os procedimentos omitidos, consulte este link do artigo para o processo completo de implementação.
Geração de Sinais
O aprendizado de máquina consiste em três tipos principais: aprendizado supervisionado, aprendizado não supervisionado e aprendizado por reforço. No trading quantitativo, os traders utilizam principalmente o aprendizado supervisionado em relação aos outros por duas razões principais. - O aprendizado não supervisionado frequentemente é básico demais para capturar as relações complexas entre os resultados de negociação e as características do mercado. Sem rótulos, ele tem dificuldade em se alinhar aos objetivos de previsão e é mais adequado para prever dados indiretos em vez dos resultados diretos de uma estratégia de negociação.
- O aprendizado por reforço exige a configuração de um ambiente de treinamento com uma função de recompensa voltada para maximizar o lucro de longo prazo, em vez de focar em previsões individuais precisas. Essa abordagem envolve uma configuração complexa para a tarefa simples de prever resultados, tornando-a menos custo-efetiva para traders de varejo.
Ainda assim, o aprendizado supervisionado oferece muitas aplicações no trading algorítmico. Um método comum é utilizá-lo como filtro: você começa com uma estratégia original que gera muitas amostras, depois treina um modelo para identificar quando a estratégia provavelmente terá sucesso ou falhará. O nível de confiança do modelo ajuda a filtrar as negociações ruins que ele prevê.
Outra abordagem, que exploraremos neste artigo, é usar aprendizado supervisionado para gerar sinais. Para tarefas típicas de regressão como previsão de preço, é simples—comprar quando o modelo prevê que o preço subirá, vender quando prevê uma queda. Mas como combinamos isso com uma estratégia base como a Breakout da Caixa de Darvas?
Primeiro, desenvolveremos um EA para coletar os dados de atributos necessários e os dados de rótulos para treinar o modelo posteriormente em Python.
A Estratégia de Breakout da Caixa de Darvas define uma caixa usando uma série de velas de rejeição após uma máxima ou mínima, acionando uma negociação quando o preço rompe esse intervalo. De qualquer forma, precisamos de um sinal para começar a coletar dados de atributos e prever resultados futuros. Assim, definiremos o gatilho como o momento em que o preço rompe o limite inferior ou superior. Esta função detecta se existe uma caixa de Darvas para um determinado período de look back e quantidade de velas de confirmação, atribui o valor do intervalo de máxima/mínima às variáveis e plota a caixa no gráfico.
double high; double low; bool boxFormed = false; bool DetectDarvasBox(int n = 100, int M = 3) { // Clear previous Darvas box objects for (int k = ObjectsTotal(0, 0, -1) - 1; k >= 0; k--) { string name = ObjectName(0, k); if (StringFind(name, "DarvasBox_") == 0) ObjectDelete(0, name); } bool current_box_active = false; // Start checking from the oldest bar within the lookback period for (int i = M+1; i <= n; i++) { // Get high of current bar and previous bar double high_current = iHigh(_Symbol, PERIOD_CURRENT, i); double high_prev = iHigh(_Symbol, PERIOD_CURRENT, i + 1); // Check for a new high if (high_current > high_prev) { // Check if the next M bars do not exceed the high bool pullback = true; for (int k = 1; k <= M; k++) { if (i - k < 0) // Ensure we don't go beyond available bars { pullback = false; break; } double high_next = iHigh(_Symbol, PERIOD_CURRENT, i - k); if (high_next > high_current) { pullback = false; break; } } // If pullback condition is met, define the box if (pullback) { double top = high_current; double bottom = iLow(_Symbol, PERIOD_CURRENT, i); // Find the lowest low over the bar and the next M bars for (int k = 1; k <= M; k++) { double low_next = iLow(_Symbol, PERIOD_CURRENT, i - k); if (low_next < bottom) bottom = low_next; } // Check for breakout from i - M - 1 to the current bar (index 0) int j = i - M - 1; while (j >= 0) { double close_j = iClose(_Symbol, PERIOD_CURRENT, j); if (close_j > top || close_j < bottom) break; // Breakout found j--; } j++; // Adjust to the bar after breakout (or 0 if no breakout) // Create a unique object name string obj_name = "DarvasBox_" + IntegerToString(i); // Plot the box datetime time_start = iTime(_Symbol, PERIOD_CURRENT, i); datetime time_end; if (j > 0) { // Historical box: ends at breakout time_end = iTime(_Symbol, PERIOD_CURRENT, j); } else { // Current box: extends to the current bar time_end = iTime(_Symbol, PERIOD_CURRENT, 0); current_box_active = true; } high = top; low = bottom; ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, time_start, top, time_end, bottom); ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1); boxFormed = true; // Since we're only plotting the most recent box, break after finding it break; } } } return current_box_active; }
Aqui estão alguns exemplos da caixa de Darvas em um gráfico:

Comparado ao uso como filtro, este método tem desvantagens. Precisaríamos prever resultados equilibrados com probabilidades iguais, como se as próximas 10 barras serão mais altas ou mais baixas, ou se o preço atingirá primeiro 10 pips acima ou abaixo. Outra desvantagem é que perdemos a vantagem embutida de uma estratégia base—o benefício depende inteiramente do poder preditivo do modelo. Por outro lado, você não fica limitado pelas amostras que uma estratégia base fornece apenas quando é acionada, oferecendo um tamanho de amostra inicial maior e maior potencial de ganho. Implementamos a lógica de negociação na função onTick() assim:
input int checkBar = 30; input int lookBack = 100; input int countMax = 10; void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; boxFormed = false; bool NotInPosition = true; lastClose = iClose(_Symbol, PERIOD_CURRENT, 1); lastlastClose = iClose(_Symbol,PERIOD_CURRENT,2); for(int i = 0; i<PositionsTotal(); i++){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)NotInPosition = false;} /*count++; if(count >=countMax ){ trade.PositionClose(pos); count = 0;} }}*/ DetectDarvasBox(lookBack,checkBar); if (NotInPosition&&boxFormed&&((lastlastClose<high&&lastClose>high)||(lastClose<low&&lastlastClose>low)))executeBuy(); } }
Para esta estratégia, usar o mesmo tamanho de take profit e stop loss é mais consistente do que acompanhar os resultados das próximas 10 barras. O primeiro vincula nossa previsão diretamente ao lucro final, enquanto o segundo adiciona incerteza com retornos variáveis ao longo de cada período de 10 barras. Vale notar que usamos take profit e stop loss como uma porcentagem do preço, tornando-o mais adaptável entre diferentes ativos e mais adequado para ativos em tendência como ouro ou índices. Os leitores podem testar a alternativa descomentando o código comentado e removendo take profit e stop loss da função de compra.
Para os dados de atributos usados para prever resultados, selecionei os três retornos normalizados passados, a distância normalizada da máxima e mínima do intervalo, e alguns indicadores estacionários comuns. Armazenamos esses dados em um multi-array, que é então salvo em um arquivo CSV usando a classe CFileCSV de um arquivo incluído. Certifique-se de que todos os períodos gráficos e símbolos estejam definidos conforme listado abaixo para alternar facilmente entre os período gráficos e ativos.string data[50000][12]; int indexx = 0; void getData(){ double close = iClose(_Symbol,PERIOD_CURRENT,1); double close2 = iClose(_Symbol,PERIOD_CURRENT,2); double close3 = iClose(_Symbol,PERIOD_CURRENT,3); double stationary = 1000*(close-iOpen(_Symbol,PERIOD_CURRENT,1))/close; double stationary2 = 1000*(close2-iOpen(_Symbol,PERIOD_CURRENT,2))/close2; double stationary3 = 1000*(close3-iOpen(_Symbol,PERIOD_CURRENT,3))/close3; double highDistance = 1000*(close-high)/close; double lowDistance = 1000*(close-low)/close; double boxSize = 1000*(high-low)/close; double adx[]; // Average Directional Movement Index double wilder[]; // Average Directional Movement Index by Welles Wilder double dm[]; // DeMarker double rsi[]; // Relative Strength Index double rvi[]; // Relative Vigor Index double sto[]; // Stochastic Oscillator CopyBuffer(handleAdx, 0, 1, 1, adx); // Average Directional Movement Index CopyBuffer(handleWilder, 0, 1, 1, wilder); // Average Directional Movement Index by Welles Wilder CopyBuffer(handleDm, 0, 1, 1, dm); // DeMarker CopyBuffer(handleRsi, 0, 1, 1, rsi); // Relative Strength Index CopyBuffer(handleRvi, 0, 1, 1, rvi); // Relative Vigor Index CopyBuffer(handleSto, 0, 1, 1, sto); // Stochastic Oscillator //2 means 2 decimal places data[indexx][0] = DoubleToString(adx[0], 2); // Average Directional Movement Index data[indexx][1] = DoubleToString(wilder[0], 2); // Average Directional Movement Index by Welles Wilder data[indexx][2] = DoubleToString(dm[0], 2); // DeMarker data[indexx][3] = DoubleToString(rsi[0], 2); // Relative Strength Index data[indexx][4] = DoubleToString(rvi[0], 2); // Relative Vigor Index data[indexx][5] = DoubleToString(sto[0], 2); // Stochastic Oscillator data[indexx][6] = DoubleToString(stationary,2); data[indexx][7] = DoubleToString(boxSize,2); data[indexx][8] = DoubleToString(stationary2,2); data[indexx][9] = DoubleToString(stationary3,2); data[indexx][10] = DoubleToString(highDistance,2); data[indexx][11] = DoubleToString(lowDistance,2); indexx++; }
O código final para o expert advisor de coleta de dados ficará assim:
#include <Trade/Trade.mqh> CTrade trade; #include <FileCSV.mqh> CFileCSV csvFile; string fileName = "box.csv"; string headers[] = { "Average Directional Movement Index", "Average Directional Movement Index by Welles Wilder", "DeMarker", "Relative Strength Index", "Relative Vigor Index", "Stochastic Oscillator", "Stationary", "Box Size", "Stationary2", "Stationary3", "Distance High", "Distance Low" }; input double lott = 0.01; input int Magic = 0; input int checkBar = 30; input int lookBack = 100; input int countMax = 10; input double slp = 0.003; input double tpp = 0.003; input bool saveData = true; string data[50000][12]; int indexx = 0; int barsTotal = 0; int count = 0; double high; double low; bool boxFormed = false; double lastClose; double lastlastClose; int handleAdx; // Average Directional Movement Index - 3 int handleWilder; // Average Directional Movement Index by Welles Wilder - 3 int handleDm; // DeMarker - 1 int handleRsi; // Relative Strength Index - 1 int handleRvi; // Relative Vigor Index - 2 int handleSto; // Stochastic Oscillator - 2 int OnInit() { trade.SetExpertMagicNumber(Magic); handleAdx=iADX(_Symbol,PERIOD_CURRENT,14);//Average Directional Movement Index - 3 handleWilder=iADXWilder(_Symbol,PERIOD_CURRENT,14);//Average Directional Movement Index by Welles Wilder - 3 handleDm=iDeMarker(_Symbol,PERIOD_CURRENT,14);//DeMarker - 1 handleRsi=iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE);//Relative Strength Index - 1 handleRvi=iRVI(_Symbol,PERIOD_CURRENT,10);//Relative Vigor Index - 2 handleSto=iStochastic(_Symbol,PERIOD_CURRENT,5,3,3,MODE_SMA,STO_LOWHIGH);//Stochastic Oscillator - 2 return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if (!saveData) return; if(csvFile.Open(fileName, FILE_WRITE|FILE_ANSI)) { //Write the header csvFile.WriteHeader(headers); //Write data rows csvFile.WriteLine(data); //Close the file csvFile.Close(); } else { Print("File opening error!"); } } void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; boxFormed = false; bool NotInPosition = true; lastClose = iClose(_Symbol, PERIOD_CURRENT, 1); lastlastClose = iClose(_Symbol,PERIOD_CURRENT,2); for(int i = 0; i<PositionsTotal(); i++){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)NotInPosition = false;} /*count++; if(count >=countMax ){ trade.PositionClose(pos); count = 0;} }}*/ DetectDarvasBox(lookBack,checkBar); if (NotInPosition&&boxFormed&&((lastlastClose<high&&lastClose>high)||(lastClose<low&&lastlastClose>low)))executeBuy(); } } void executeBuy() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); double sl = lastClose*(1-slp); double tp = lastClose*(1+tpp); trade.Buy(lott,_Symbol,ask,sl,tp); if(PositionsTotal()>0)getData(); } bool DetectDarvasBox(int n = 100, int M = 3) { // Clear previous Darvas box objects for (int k = ObjectsTotal(0, 0, -1) - 1; k >= 0; k--) { string name = ObjectName(0, k); if (StringFind(name, "DarvasBox_") == 0) ObjectDelete(0, name); } bool current_box_active = false; // Start checking from the oldest bar within the lookback period for (int i = M+1; i <= n; i++) { // Get high of current bar and previous bar double high_current = iHigh(_Symbol, PERIOD_CURRENT, i); double high_prev = iHigh(_Symbol, PERIOD_CURRENT, i + 1); // Check for a new high if (high_current > high_prev) { // Check if the next M bars do not exceed the high bool pullback = true; for (int k = 1; k <= M; k++) { if (i - k < 0) // Ensure we don't go beyond available bars { pullback = false; break; } double high_next = iHigh(_Symbol, PERIOD_CURRENT, i - k); if (high_next > high_current) { pullback = false; break; } } // If pullback condition is met, define the box if (pullback) { double top = high_current; double bottom = iLow(_Symbol, PERIOD_CURRENT, i); // Find the lowest low over the bar and the next M bars for (int k = 1; k <= M; k++) { double low_next = iLow(_Symbol, PERIOD_CURRENT, i - k); if (low_next < bottom) bottom = low_next; } // Check for breakout from i - M - 1 to the current bar (index 0) int j = i - M - 1; while (j >= 0) { double close_j = iClose(_Symbol, PERIOD_CURRENT, j); if (close_j > top || close_j < bottom) break; // Breakout found j--; } j++; // Adjust to the bar after breakout (or 0 if no breakout) // Create a unique object name string obj_name = "DarvasBox_" + IntegerToString(i); // Plot the box datetime time_start = iTime(_Symbol, PERIOD_CURRENT, i); datetime time_end; if (j > 0) { // Historical box: ends at breakout time_end = iTime(_Symbol, PERIOD_CURRENT, j); } else { // Current box: extends to the current bar time_end = iTime(_Symbol, PERIOD_CURRENT, 0); current_box_active = true; } high = top; low = bottom; ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, time_start, top, time_end, bottom); ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1); boxFormed = true; // Since we're only plotting the most recent box, break after finding it break; } } } return current_box_active; } void getData(){ double close = iClose(_Symbol,PERIOD_CURRENT,1); double close2 = iClose(_Symbol,PERIOD_CURRENT,2); double close3 = iClose(_Symbol,PERIOD_CURRENT,3); double stationary = 1000*(close-iOpen(_Symbol,PERIOD_CURRENT,1))/close; double stationary2 = 1000*(close2-iOpen(_Symbol,PERIOD_CURRENT,2))/close2; double stationary3 = 1000*(close3-iOpen(_Symbol,PERIOD_CURRENT,3))/close3; double highDistance = 1000*(close-high)/close; double lowDistance = 1000*(close-low)/close; double boxSize = 1000*(high-low)/close; double adx[]; // Average Directional Movement Index double wilder[]; // Average Directional Movement Index by Welles Wilder double dm[]; // DeMarker double rsi[]; // Relative Strength Index double rvi[]; // Relative Vigor Index double sto[]; // Stochastic Oscillator CopyBuffer(handleAdx, 0, 1, 1, adx); // Average Directional Movement Index CopyBuffer(handleWilder, 0, 1, 1, wilder); // Average Directional Movement Index by Welles Wilder CopyBuffer(handleDm, 0, 1, 1, dm); // DeMarker CopyBuffer(handleRsi, 0, 1, 1, rsi); // Relative Strength Index CopyBuffer(handleRvi, 0, 1, 1, rvi); // Relative Vigor Index CopyBuffer(handleSto, 0, 1, 1, sto); // Stochastic Oscillator //2 means 2 decimal places data[indexx][0] = DoubleToString(adx[0], 2); // Average Directional Movement Index data[indexx][1] = DoubleToString(wilder[0], 2); // Average Directional Movement Index by Welles Wilder data[indexx][2] = DoubleToString(dm[0], 2); // DeMarker data[indexx][3] = DoubleToString(rsi[0], 2); // Relative Strength Index data[indexx][4] = DoubleToString(rvi[0], 2); // Relative Vigor Index data[indexx][5] = DoubleToString(sto[0], 2); // Stochastic Oscillator data[indexx][6] = DoubleToString(stationary,2); data[indexx][7] = DoubleToString(boxSize,2); data[indexx][8] = DoubleToString(stationary2,2); data[indexx][9] = DoubleToString(stationary3,2); data[indexx][10] = DoubleToString(highDistance,2); data[indexx][11] = DoubleToString(lowDistance,2); indexx++; }
Pretendemos negociar esta estratégia no timeframe de 15 minutos do XAUUSD devido à sólida volatilidade do ativo, e porque 15 minutos oferece um equilíbrio entre redução de ruído e geração de um número maior de amostras. Uma negociação típica seria assim:

Usamos os dados de 2020-2024 como dados de treinamento e validação, e testaremos o resultado em 2024-2025 no terminal MetaTrader 5 posteriormente. Após executar este EA no testador de estratégia, o arquivo CSV será salvo no diretório /Tester/Agent-sth000 na desinicialização do EA.
Além disso, clique com o botão direito para obter o relatório Excel do backtest assim:

Observe o número da linha da linha "Deals", que usaremos como entrada posteriormente.

Depois disso, treinamos nosso modelo em Python.
O modelo que selecionamos para este artigo é um modelo baseado em árvore de decisão, ideal para problemas de classificação, assim como o que usamos em este artigo.
import pandas as pd # Replace 'your_file.xlsx' with the path to your file input_file = 'box.xlsx' # Load the Excel file and skip the first {skiprows} rows data1 = pd.read_excel(input_file, skiprows=4417) # Select the 'profit' column (assumed to be 'Unnamed: 10') and filter rows as per your instructions profit_data = data1["Profit"][1:-1] profit_data = profit_data[profit_data.index % 2 == 0] # Filter for rows with odd indices profit_data = profit_data.reset_index(drop=True) # Reset index # Convert to float, then apply the condition to set values to 1 if > 0, otherwise to 0 profit_data = pd.to_numeric(profit_data, errors='coerce').fillna(0) # Convert to float, replacing NaN with 0 profit_data = profit_data.apply(lambda x: 1 if x > 0 else 0) # Apply condition # Load the CSV file with semicolon separator file_path = 'box.csv' data2 = pd.read_csv(file_path, sep=';') # Drop rows with any missing or incomplete values data2.dropna(inplace=True) # Drop any duplicate rows if present data2.drop_duplicates(inplace=True) # Convert non-numeric columns to numerical format for col in data2.columns: if data2[col].dtype == 'object': # Convert categorical to numerical using label encoding data2[col] = data2[col].astype('category').cat.codes # Ensure all remaining columns are numeric and cleanly formatted for CatBoost data2 = data2.apply(pd.to_numeric, errors='coerce') data2.dropna(inplace=True) # Drop any rows that might still contain NaNs after conversion # Merge the two DataFrames on the index merged_data = pd.merge(profit_data, data2, left_index=True, right_index=True, how='inner') # Save the merged data to a new CSV file output_csv_path = 'merged_data.csv' merged_data.to_csv(output_csv_path) print(f"Merged data saved to {output_csv_path}")Usamos este código para rotular o relatório Excel, atribuindo 1 às negociações com lucro positivo e 0 às que não possuem. Em seguida, combinamos isso com os dados de atributos coletados do arquivo CSV do EA de coleta de dados. Tenha em mente que o valor skiprow corresponde ao número da linha de "Deals".
import numpy as np import pandas as pd import warnings warnings.filterwarnings("ignore") data = pd.read_csv("merged_data.csv",index_col=0) XX = data.drop(columns=['Profit']) yy = data['Profit'] y = yy.values X = XX.values pd.DataFrame(X,y)
Em seguida, atribuimos o array de rótulos à variável y e o data frame de atributos à variável X.
import numpy as np import pandas as pd import warnings import seaborn as sns warnings.filterwarnings("ignore") from sklearn.model_selection import train_test_split import catboost as cb from sklearn.metrics import roc_curve, roc_auc_score import matplotlib.pyplot as plt # Split data X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, shuffle=False) # Identify categorical features cat_feature_indices = [i for i, col in enumerate(XX.columns) if XX[col].dtype == 'object'] # Train CatBoost classifier model = cb.CatBoostClassifier( iterations=5000, # Number of trees (similar to n_estimators) learning_rate=0.02, # Learning rate depth=5, # Depth of each tree l2_leaf_reg=5, bagging_temperature=1, early_stopping_rounds=50, loss_function='Logloss', # Use 'MultiClass' if it's a multi-class problem verbose=1000) model.fit(X_train, y_train, cat_features=cat_feature_indices)
Depois, dividimos os dados na proporção 9:1 em conjuntos de treinamento e validação e iniciamos o treinamento do modelo. A configuração padrão da função train-test split no sklearn inclui embaralhamento, o que não é ideal para dados de séries temporais, portanto certifique-se de definir shuffle=False nos parâmetros. É uma boa ideia ajustar os hiperparâmetros para evitar overfitting ou underfitting, dependendo do tamanho da sua amostra. Pessoalmente, descobri que interromper a iteração em torno de 0.1 de log loss funciona bem.
import numpy as np from sklearn.metrics import roc_curve, roc_auc_score import matplotlib.pyplot as plt # Assuming you already have y_test, X_test, and model defined # Predict probabilities y_prob = model.predict_proba(X_test)[:, 1] # Probability for positive class # Compute ROC curve and AUC (for reference) fpr, tpr, thresholds = roc_curve(y_test, y_prob) auc_score = roc_auc_score(y_test, y_prob) print(f"AUC Score: {auc_score:.2f}") # Define confidence thresholds to test (e.g., 50%, 60%, 70%, etc.) confidence_thresholds = np.arange(0.5, 1.0, 0.05) # From 50% to 95% in steps of 5% accuracies = [] coverage = [] # Fraction of samples classified at each threshold for thresh in confidence_thresholds: # Classify only when probability is >= thresh (positive) or <= (1 - thresh) (negative) y_pred_confident = np.where(y_prob >= thresh, 1, np.where(y_prob <= (1 - thresh), 0, -1)) # Filter out unclassified samples (where y_pred_confident == -1) mask = y_pred_confident != -1 y_test_confident = y_test[mask] y_pred_confident = y_pred_confident[mask] # Calculate accuracy and coverage if len(y_test_confident) > 0: # Avoid division by zero acc = np.mean(y_pred_confident == y_test_confident) cov = len(y_test_confident) / len(y_test) else: acc = 0 cov = 0 accuracies.append(acc) coverage.append(cov) # Plot Accuracy vs Confidence Threshold plt.figure(figsize=(10, 6)) plt.plot(confidence_thresholds, accuracies, marker='o', label='Accuracy', color='blue') plt.plot(confidence_thresholds, coverage, marker='s', label='Coverage', color='green') plt.xlabel('Confidence Threshold') plt.ylabel('Metric Value') plt.title('Accuracy and Coverage vs Confidence Threshold') plt.legend(loc='best') plt.grid(True) plt.show() # Also show the original ROC curve for reference plt.figure(figsize=(8, 6)) plt.plot(fpr, tpr, label=f'ROC Curve (AUC = {auc_score:.2f})', color='blue') plt.plot([0, 1], [0, 1], 'k--', label='Random Guess') plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.title('Receiver Operating Characteristic (ROC) Curve') plt.legend(loc='lower right') plt.grid(True) plt.show()
Em seguida, plotamos as visualizações dos resultados para verificar o teste de validação. Na abordagem típica de treinar-validar-testar, a etapa de validação ajuda a escolher os melhores hiperparâmetros e inicialmente avalia se o modelo treinado possui poder preditivo. Ela serve como uma camada de validação antes do teste final.


# Feature importance feature_importance = model.get_feature_importance() importance_df = pd.DataFrame({ 'feature': XX.columns, 'importance': feature_importance }).sort_values('importance', ascending=False) print("Feature Importances:") print(importance_df) plt.figure(figsize=(10, 6)) sns.barplot(x='importance', y='feature', data=importance_df) plt.title(' Feature Importance') plt.xlabel('Importance') plt.ylabel('Feature') x = 100/len(XX.columns) plt.axvline(x,color = 'red', linestyle = '--') plt.show()

Este bloco de código irá plotar a importância dos atributos, bem como a linha mediana. Existem muitas maneiras de definir a importância dos atributos no campo do aprendizado de máquina, como:
- Importância Baseada em Árvore: Mede a redução de impureza (ex.: Gini) em árvores de decisão ou ensembles como Random Forest e XGBoost.
- Importância por Permutação: Avalia a queda de desempenho quando os valores de um atributo são embaralhados.
- Valores SHAP: Calcula a contribuição de um atributo para as previsões com base nos valores de Shapley.
- Magnitude do Coeficiente: Usa o valor absoluto dos coeficientes em modelos lineares.
Em nosso exemplo, estamos usando CatBoost, um modelo baseado em árvore de decisão. A importância dos atributos mostra quanto de desordem (impureza) cada atributo reduz quando usado para dividir a árvore de decisão nos dados in-sample. É importante perceber que, embora selecionar os atributos mais importantes como conjunto final frequentemente aumente a eficiência do modelo, isso nem sempre melhora a capacidade preditiva pelos seguintes motivos:
- A importância dos atributos é calculada a partir dos dados in-sample, sem conhecimento dos dados out-of-sample.
- A importância dos atributos depende dos outros atributos considerados. Se a maioria dos atributos escolhidos não possui poder preditivo, remover os mais fracos não ajudará.
- A importância reflete o quão eficaz um atributo divide a árvore, não necessariamente o quão crítico ele é para o resultado final da decisão.
Esses insights surgiram quando descobri inesperadamente que selecionar os atributos menos importantes na verdade aumentou a acurácia out-of-sample. Mas, em geral, escolher os atributos mais importantes e remover os menos importantes ajuda a tornar o modelo mais leve e provavelmente melhora a acurácia geral.
from onnx.helper import get_attribute_value import onnxruntime as rt from skl2onnx import convert_sklearn, update_registered_converter from skl2onnx.common.shape_calculator import ( calculate_linear_classifier_output_shapes, ) # noqa from skl2onnx.common.data_types import ( FloatTensorType, Int64TensorType, guess_tensor_type, ) from skl2onnx._parse import _apply_zipmap, _get_sklearn_operator_name from catboost import CatBoostClassifier from catboost.utils import convert_to_onnx_object def skl2onnx_parser_castboost_classifier(scope, model, inputs, custom_parsers=None): options = scope.get_options(model, dict(zipmap=True)) no_zipmap = isinstance(options["zipmap"], bool) and not options["zipmap"] alias = _get_sklearn_operator_name(type(model)) this_operator = scope.declare_local_operator(alias, model) this_operator.inputs = inputs label_variable = scope.declare_local_variable("label", Int64TensorType()) prob_dtype = guess_tensor_type(inputs[0].type) probability_tensor_variable = scope.declare_local_variable( "probabilities", prob_dtype ) this_operator.outputs.append(label_variable) this_operator.outputs.append(probability_tensor_variable) probability_tensor = this_operator.outputs if no_zipmap: return probability_tensor return _apply_zipmap( options["zipmap"], scope, model, inputs[0].type, probability_tensor ) def skl2onnx_convert_catboost(scope, operator, container): """ CatBoost returns an ONNX graph with a single node. This function adds it to the main graph. """ onx = convert_to_onnx_object(operator.raw_operator) opsets = {d.domain: d.version for d in onx.opset_import} if "" in opsets and opsets[""] >= container.target_opset: raise RuntimeError("CatBoost uses an opset more recent than the target one.") if len(onx.graph.initializer) > 0 or len(onx.graph.sparse_initializer) > 0: raise NotImplementedError( "CatBoost returns a model initializers. This option is not implemented yet." ) if ( len(onx.graph.node) not in (1, 2) or not onx.graph.node[0].op_type.startswith("TreeEnsemble") or (len(onx.graph.node) == 2 and onx.graph.node[1].op_type != "ZipMap") ): types = ", ".join(map(lambda n: n.op_type, onx.graph.node)) raise NotImplementedError( f"CatBoost returns {len(onx.graph.node)} != 1 (types={types}). " f"This option is not implemented yet." ) node = onx.graph.node[0] atts = {} for att in node.attribute: atts[att.name] = get_attribute_value(att) container.add_node( node.op_type, [operator.inputs[0].full_name], [operator.outputs[0].full_name, operator.outputs[1].full_name], op_domain=node.domain, op_version=opsets.get(node.domain, None), **atts, ) update_registered_converter( CatBoostClassifier, "CatBoostCatBoostClassifier", calculate_linear_classifier_output_shapes, skl2onnx_convert_catboost, parser=skl2onnx_parser_castboost_classifier, options={"nocl": [True, False], "zipmap": [True, False, "columns"]}, ) model_onnx = convert_sklearn( model, "catboost", [("input", FloatTensorType([None, X.shape[1]]))], target_opset={"": 12, "ai.onnx.ml": 2}, ) # And save. with open("box2024.onnx", "wb") as f: f.write(model_onnx.SerializeToString())
Por fim, exportamos o arquivo ONNX e o salvamos no diretório MQL5/Files.
Agora, vamos voltar ao editor de código do MetaTrader 5 para criar o EA de negociação.
Precisamos apenas ajustar o EA original de coleta de dados importando alguns arquivos include para lidar com o modelo CatBoost.
#resource "\\Files\\box2024.onnx" as uchar catboost_onnx[] #include <CatOnnx.mqh> CCatBoost cat_boost; string data[1][12]; vector xx; vector prob;
Em seguida, ajustaremos a função getData() para retornar um vetor.
vector getData(){ double close = iClose(_Symbol,PERIOD_CURRENT,1); double close2 = iClose(_Symbol,PERIOD_CURRENT,2); double close3 = iClose(_Symbol,PERIOD_CURRENT,3); double stationary = 1000*(close-iOpen(_Symbol,PERIOD_CURRENT,1))/close; double stationary2 = 1000*(close2-iOpen(_Symbol,PERIOD_CURRENT,2))/close2; double stationary3 = 1000*(close3-iOpen(_Symbol,PERIOD_CURRENT,3))/close3; double highDistance = 1000*(close-high)/close; double lowDistance = 1000*(close-low)/close; double boxSize = 1000*(high-low)/close; double adx[]; // Average Directional Movement Index double wilder[]; // Average Directional Movement Index by Welles Wilder double dm[]; // DeMarker double rsi[]; // Relative Strength Index double rvi[]; // Relative Vigor Index double sto[]; // Stochastic Oscillator CopyBuffer(handleAdx, 0, 1, 1, adx); // Average Directional Movement Index CopyBuffer(handleWilder, 0, 1, 1, wilder); // Average Directional Movement Index by Welles Wilder CopyBuffer(handleDm, 0, 1, 1, dm); // DeMarker CopyBuffer(handleRsi, 0, 1, 1, rsi); // Relative Strength Index CopyBuffer(handleRvi, 0, 1, 1, rvi); // Relative Vigor Index CopyBuffer(handleSto, 0, 1, 1, sto); // Stochastic Oscillator data[0][0] = DoubleToString(adx[0], 2); // Average Directional Movement Index data[0][1] = DoubleToString(wilder[0], 2); // Average Directional Movement Index by Welles Wilder data[0][2] = DoubleToString(dm[0], 2); // DeMarker data[0][3] = DoubleToString(rsi[0], 2); // Relative Strength Index data[0][4] = DoubleToString(rvi[0], 2); // Relative Vigor Index data[0][5] = DoubleToString(sto[0], 2); // Stochastic Oscillator data[0][6] = DoubleToString(stationary,2); data[0][7] = DoubleToString(boxSize,2); data[0][8] = DoubleToString(stationary2,2); data[0][9] = DoubleToString(stationary3,2); data[0][10] = DoubleToString(highDistance,2); data[0][11] = DoubleToString(lowDistance,2); vector features(12); for(int i = 0; i < 12; i++) { features[i] = StringToDouble(data[0][i]); } return features; }
A lógica final de negociação na função OnTick() ficará assim:
void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (barsTotal!= bars){ barsTotal = bars; boxFormed = false; bool NotInPosition = true; lastClose = iClose(_Symbol, PERIOD_CURRENT, 1); lastlastClose = iClose(_Symbol,PERIOD_CURRENT,2); for(int i = 0; i<PositionsTotal(); i++){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)NotInPosition = false;} /*count++; if(count >=countMax){ trade.PositionClose(pos); count = 0;} }}*/ DetectDarvasBox(lookBack,checkBar); if (NotInPosition&&boxFormed&&((lastlastClose<high&&lastClose>high)||(lastClose<low&&lastlastClose>low))){ xx = getData(); prob = cat_boost.predict_proba(xx); if(prob[1]>threshold)executeBuy(); if(prob[0]>threshold)executeSell(); } } }
Na lógica de sinal, primeiro verifica se nenhuma posição está atualmente aberta para garantir apenas uma negociação por vez. Em seguida, detecta se há um breakout em qualquer lado do intervalo. Depois disso, chama a função getData() para obter o vetor de atributos. Esse vetor é passado para o modelo CatBoost como entrada, e o modelo gera a confiança de previsão para cada resultado no array prob. Com base nos níveis de confiança para cada resultado, realizamos uma negociação apostando no resultado previsto. Essencialmente, estamos usando o modelo para gerar os sinais de compra ou venda.
Executamos um backtest no testador de estratégia do MetaTrader 5 usando dados in-sample de 2020 a 2024 para verificar se nossos dados de treinamento não tinham erros e se a junção de atributos e resultados estava correta. Se tudo estiver correto, a curva de equity deve parecer quase perfeita, assim:

Em seguida, realizamos o backtest do teste out-of-sample de 2024 a 2025 para verificar se a estratégia possui lucratividade no período mais recente. Definimos o limiar em 0.7, então o modelo só realizará uma negociação em uma direção se o nível de confiança para o take-profit dessa direção ser atingido for de 70% ou mais, com base nos dados de treinamento.




Podemos ver que o modelo teve um desempenho excepcional na primeira metade do ano, mas começou a apresentar queda de desempenho com o passar do tempo. Isso é comum em modelos de aprendizado de máquina, porque a vantagem obtida a partir de dados passados geralmente é temporária, e essa vantagem tende a se deteriorar ao longo do tempo. Isso sugere que uma proporção menor entre teste e treinamento pode funcionar melhor para futuras implementações em trading ao vivo. No geral, o modelo demonstra alguma previsibilidade, já que permaneceu lucrativo mesmo após considerar os custos de negociação.
Sinal Contínuo
No trading algorítmico, os traders normalmente seguem um método simples de uso de sinais discretos—ou comprar ou vender com um risco fixo por negociação. Isso torna as coisas mais fáceis de gerenciar e melhores para analisar o desempenho da estratégia. Alguns traders tentaram refinar esse método de sinal discreto usando um sinal aditivo, onde ajustam o risco da negociação com base na intensidade com que as condições da estratégia são atendidas. Sinais contínuos levam essa abordagem aditiva adiante, aplicando-a a condições mais abstratas da estratégia e produzindo um nível de risco entre zero e um.
A ideia básica por trás disso é que nem todas as negociações que atendem aos critérios de entrada são iguais. Algumas parecem mais propensas a ter sucesso porque seus sinais são mais fortes, com base em regras não lineares vinculadas à estratégia. Isso pode ser visto como uma ferramenta de gestão de risco—apostar alto quando a confiança é elevada e reduzir quando uma negociação parece menos promissora, mesmo que ainda tenha um retorno esperado positivo. No entanto, precisamos lembrar que isso adiciona outro fator ao desempenho da estratégia, e que o viés de antecipação e os riscos de overfitting ainda são problemáticos se não tivermos cuidado durante a implementação.
Para aplicar esse conceito em nosso EA de negociação, primeiro precisamos ajustar as funções de compra/venda para calcular o tamanho do lote com base no risco que estamos dispostos a perder caso o stop loss seja atingido. A função de cálculo de lote é assim:
double calclots(double slpoints, string symbol, double risk) { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmount = balance* risk / 100; double ticksize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tickvalue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double lotstep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double moneyperlotstep = slpoints / ticksize * tickvalue * lotstep; double lots = MathFloor(riskAmount / moneyperlotstep) * lotstep; lots = MathMin(lots, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX)); lots = MathMax(lots, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN)); return lots; }Em seguida, atualizamos as funções de compra/venda para que chamem essa função calclots() e recebam o multiplicador de risco como entrada:
void executeSell(double riskMultiplier) { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); double sl = lastClose*(1+slp); double tp = lastClose*(1-tpp); double lots = 0.1; lots = calclots(slp*lastClose,_Symbol,risks*riskMultiplier); trade.Sell(lots,_Symbol,bid,sl,tp); } void executeBuy(double riskMultiplier) { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); double sl = lastClose*(1-slp); double tp = lastClose*(1+tpp); double lots = 0.1; lots = calclots(slp*lastClose,_Symbol,risks*riskMultiplier); trade.Buy(lots,_Symbol,ask,sl,tp); }Como nosso modelo de aprendizado de máquina já fornece o nível de confiança, podemos usá-lo diretamente como entrada do multiplicador de risco. Se quisermos ajustar o quanto o nível de confiança afeta o risco de cada negociação, podemos simplesmente escalar o nível de confiança para cima ou para baixo conforme necessário.
if(prob[1]>threshold)executeBuy(prob[1]); if(prob[0]>threshold)executeSell(prob[0]);
Por exemplo, se quisermos amplificar a importância das diferenças no nível de confiança, poderíamos multiplicar a probabilidade por ela mesma três vezes. Isso aumentaria a diferença de proporção entre probabilidades, tornando o impacto dos níveis de confiança mais pronunciado.
if(prob[1]>threshold)executeBuy(prob[1]*prob[1]*prob[1]); if(prob[0]>threshold)executeSell(prob[0]*prob[0]*prob[0]);
Agora, tentamos observar o resultado no backtest.



As negociações realizadas ainda são as mesmas da versão de sinal discreto, mas o fator de lucro e o índice de Sharpe melhoraram ligeiramente. Isso sugere que, neste cenário específico, o sinal contínuo melhorou o desempenho geral do teste out-of-sample, que está livre de viés de antecipação, já que testamos apenas uma vez. No entanto, é importante observar que essa abordagem só supera o método de risco fixo se a precisão das previsões do modelo for maior quando seu nível de confiança for mais alto. Caso contrário, a abordagem original de risco fixo pode ser melhor. Além disso, como reduzimos o tamanho médio do lote ao aplicar multiplicadores de risco entre zero e um, precisaríamos aumentar o valor da variável de risco se quisermos alcançar um lucro total semelhante ao anterior.
Validação Multi-Timeframe
Treinar modelos de aprendizado de máquina separados, cada um utilizando um timeframe diferente de atributos para prever o mesmo resultado, pode oferecer uma forma poderosa de melhorar a filtragem de negociações e a geração de sinais. Ao ter um modelo focado em dados de curto prazo, outro em médio prazo e talvez um terceiro em tendências de longo prazo, você obtém insights especializados que, quando combinados, podem validar previsões de forma mais confiável do que um único modelo. Essa abordagem de múltiplos modelos pode aumentar a confiança nas decisões de negociação ao cruzar sinais, reduzir o risco de agir com base em ruído específico de um timeframe e apoiar a gestão de risco ao permitir ponderar a saída de cada modelo para ajustar o tamanho da negociação ou os stops com base na força do consenso.
Por outro lado, essa estratégia pode complicar o sistema, especialmente quando você atribui diferentes pesos às previsões de múltiplos modelos. Isso pode introduzir seus próprios vieses ou erros se não for cuidadosamente ajustado. Cada modelo também pode sofrer overfitting ao seu timeframe específico, ignorando dinâmicas mais amplas do mercado, e discrepâncias entre suas previsões podem gerar confusão, atrasando decisões ou reduzindo a confiança.
Essa abordagem depende de duas suposições principais: nenhum viés de antecipação é introduzido no timeframe superior (devemos usar o valor da última barra, não o atual), e o modelo de timeframe superior possui sua própria capacidade preditiva (ele tem desempenho melhor que o acaso em testes out-of-sample).
Para implementar isso, primeiro modificamos o código no EA de coleta de dados alterando todos os timeframes relacionados à extração de atributos para um timeframe superior, como 1 hora. Isso inclui indicadores, cálculos de preço e quaisquer outros atributos utilizados.
int OnInit() { trade.SetExpertMagicNumber(Magic); handleAdx = iADX(_Symbol, PERIOD_H1, 14); // Average Directional Movement Index - 3 handleWilder = iADXWilder(_Symbol, PERIOD_H1, 14); // Average Directional Movement Index by Welles Wilder - 3 handleDm = iDeMarker(_Symbol, PERIOD_H1, 14); // DeMarker - 1 handleRsi = iRSI(_Symbol, PERIOD_H1, 14, PRICE_CLOSE); // Relative Strength Index - 1 handleRvi = iRVI(_Symbol, PERIOD_H1, 10); // Relative Vigor Index - 2 handleSto = iStochastic(_Symbol, PERIOD_H1, 5, 3, 3, MODE_SMA, STO_LOWHIGH); // Stochastic Oscillator - 2 return(INIT_SUCCEEDED); } void getData() { double close = iClose(_Symbol, PERIOD_H1, 1); double close2 = iClose(_Symbol, PERIOD_H1, 2); double close3 = iClose(_Symbol, PERIOD_H1, 3); double stationary = 1000 * (close - iOpen(_Symbol, PERIOD_H1, 1)) / close; double stationary2 = 1000 * (close2 - iOpen(_Symbol, PERIOD_H1, 2)) / close2; double stationary3 = 1000 * (close3 - iOpen(_Symbol, PERIOD_H1, 3)) / close3; }
Depois disso, seguimos os mesmos passos de antes: coleta de dados, treinamento do modelo e exportação, exatamente como discutido anteriormente.
Em seguida, no EA de negociação, criamos uma segunda função para obter os atributos de entrada, que será alimentada ao segundo modelo de ML que importamos para obter a saída do nível de confiança.
vector getData2() { double close = iClose(_Symbol, PERIOD_H1, 1); double close2 = iClose(_Symbol, PERIOD_H1, 2); double close3 = iClose(_Symbol, PERIOD_H1, 3); double stationary = 1000 * (close - iOpen(_Symbol, PERIOD_H1, 1)) / close; double stationary2 = 1000 * (close2 - iOpen(_Symbol, PERIOD_H1, 2)) / close2; double stationary3 = 1000 * (close3 - iOpen(_Symbol, PERIOD_H1, 3)) / close3; double highDistance = 1000 * (close - high) / close; double lowDistance = 1000 * (close - low) / close; double boxSize = 1000 * (high - low) / close; double adx[]; // Average Directional Movement Index double wilder[]; // Average Directional Movement Index by Welles Wilder double dm[]; // DeMarker double rsi[]; // Relative Strength Index double rvi[]; // Relative Vigor Index double sto[]; // Stochastic Oscillator CopyBuffer(handleAdx, 0, 1, 1, adx); // Average Directional Movement Index CopyBuffer(handleWilder, 0, 1, 1, wilder); // Average Directional Movement Index by Welles Wilder CopyBuffer(handleDm, 0, 1, 1, dm); // DeMarker CopyBuffer(handleRsi, 0, 1, 1, rsi); // Relative Strength Index CopyBuffer(handleRvi, 0, 1, 1, rvi); // Relative Vigor Index CopyBuffer(handleSto, 0, 1, 1, sto); // Stochastic Oscillator data[0][0] = DoubleToString(adx[0], 2); // Average Directional Movement Index data[0][1] = DoubleToString(wilder[0], 2); // Average Directional Movement Index by Welles Wilder data[0][2] = DoubleToString(dm[0], 2); // DeMarker data[0][3] = DoubleToString(rsi[0], 2); // Relative Strength Index data[0][4] = DoubleToString(rvi[0], 2); // Relative Vigor Index data[0][5] = DoubleToString(sto[0], 2); // Stochastic Oscillator data[0][6] = DoubleToString(stationary, 2); data[0][7] = DoubleToString(boxSize, 2); data[0][8] = DoubleToString(stationary2, 2); data[0][9] = DoubleToString(stationary3, 2); data[0][10] = DoubleToString(highDistance, 2); data[0][11] = DoubleToString(lowDistance, 2); vector features(12); for(int i = 0; i < 12; i++) { features[i] = StringToDouble(data[0][i]); } return features; }
Supondo que queremos atribuir o mesmo peso à saída dos dois modelos, simplesmente calculamos a média de suas saídas e a tratamos como a única saída usada anteriormente.
if (NotInPosition&&boxFormed&&((lastlastClose<high&&lastClose>high)||(lastClose<low&&lastlastClose>low))){ xx = getData(); xx2 = getData2(); prob = cat_boost.predict_proba(xx); prob2 = cat_boost.predict_proba(xx2); double probability_buy = (prob[1]+prob2[1])/2; double probability_sell = (prob[0]+prob2[0])/2; if(probability_buy>threshold)executeBuy(probability_buy); if(probability_sell>threshold)executeSell(probability_sell); } }
Com essas duas variáveis calculadas como acima, agora podemos combiná-las em um único nível de confiança e usá-lo para validação, seguindo a mesma abordagem utilizada anteriormente.
Conclusão
Neste artigo, primeiro exploramos a ideia de usar um modelo de aprendizado de máquina como gerador de sinais em vez de filtro, demonstrado por meio de uma estratégia de breakout da caixa de Darvas. Apresentamos brevemente o processo de treinamento do modelo e discutimos a importância dos limiares de nível de confiança e da relevância dos atributos. Em seguida, introduzimos o conceito de sinais contínuos e comparamos seu desempenho com sinais discretos. Descobrimos que, neste exemplo, os sinais contínuos melhoraram o desempenho do backtest porque o modelo tendia a ter maior precisão de previsão conforme os níveis de confiança aumentavam. Por fim, abordamos o conceito de utilizar múltiplos modelos de aprendizado de máquina treinados em diferentes timeframes para validar sinais em conjunto.
No geral, este artigo teve como objetivo apresentar ideias não convencionais sobre a aplicação de modelos de aprendizado de máquina supervisionado no trading CTA. Seu objetivo não é afirmar definitivamente qual abordagem funciona melhor, pois tudo depende do cenário específico, mas sim inspirar os leitores a pensar de forma criativa e expandir conceitos iniciais simples. No final, nada é totalmente novo—a inovação frequentemente surge da combinação de ideias existentes para criar algo novo.
Tabela de Arquivos
| Nome do Arquivo | Uso dos Arquivos |
|---|---|
| Darvas_Box.ipynb | O arquivo Jupyter Notebook para treinamento do modelo de ML |
| Darvas Box Data.mq5 | O EA para coleta de dados para treinamento do modelo |
| Darvas Box EA.mq5 | O EA de negociação no artigo |
| CatOnnx.mqh | Um arquivo include para processamento do modelo CatBoost |
| FileCSV.mqh | Um arquivo include para salvar dados em CSV |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17466
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Redes neurais em trading: Desvendando os componentes estruturais da série (SCNN)
Está chegando o novo MetaTrader 5 e MQL5
Do iniciante ao especialista: Criação de um EA de notícias animado em MQL5(V): sistema de lembretes de eventos
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Obrigado por lembrar. A parte da instalação do pip foi ignorada, mas os usuários precisam instalar a biblioteca relacionada, caso ainda não o tenham feito.
Seu erro pode ser causado pelo fato de as dimensões usadas no treinamento do modelo serem diferentes das usadas no seu EA. Por exemplo, se você treinou um modelo com 5 recursos, também deve inserir 5 recursos no seu EA, e não 4 ou 6. Um passo a passo mais detalhado está no link deste artigo. Espero que isso ajude. Caso contrário, forneça mais contexto.