Escrevemos o primeiro modelo de caixa de vidro (Glass Box) em Python e MQL5
Introdução
Os algoritmos de caixa de vidro (ou branca) são modelos de aprendizado de máquina transparentes que levam em conta os mecanismos internos do sistema. Eles questionam a noção de que o aprendizado de máquina deve se balancear entre a precisão da previsão e a facilidade de interpretação. Para isso, esses modelos oferecem um alto nível de precisão e transparência, o que os torna significativamente mais simples de depurar, manter e aprimorar em comparação aos modelos de caixa preta. Os modelos de caixa preta são aqueles em que um processo excessivamente complexo em um sistema fica oculto do ambiente externo. Esses modelos geralmente representam relações multidimensionais e não lineares que não são fáceis de entender para nós, seres humanos.
Como regra geral, os modelos de caixa preta só devem ser usados quando um modelo de caixa de vidro não puder entregar o mesmo nível de precisão. Neste artigo, criaremos um modelo transparente e exploraremos os possíveis benefícios de usá-lo. Vamos considerar duas formas de trabalhar com o terminal MetaTrader 5:
- A abordagem tradicional, que é a mais direta. Simplesmente integraremos o modelo ao terminal MetaTrader 5 usando a biblioteca Python embutida no MetaTrader 5. Em seguida, programaremos um Expert Advisor em MQL5 que operará com este modelo.
- A abordagem moderna, que é a maneira recomendada de integrar modelos de aprendizado de máquina em um EA. Exportaremos nosso modelo de caixa de vidro para o formato ONNX (Open Neural Network Exchange) e, em seguida, o carregaremos diretamente no EA como um recurso. Isso nos permitirá aproveitar todas as funcionalidades disponíveis no MetaTrader 5 e integrá-las com as vantagens do nosso modelo de caixa de vidro.
Figura 1. Simulação do cérebro humano por meio de inteligência artificial
Modelos de caixa preta e de caixa de vidro
A maioria dos modelos tradicionais de aprendizado de máquina é difícil de interpretar ou explicar. Essa classe de modelos é conhecida como modelos de caixa preta. Os modelos de caixa preta podem se referir a todos os modelos com funcionamento interno complexo e de difícil interpretação. Isso representa um problema sério, pois será muito difícil melhorar as principais métricas de desempenho do modelo dessa forma. Os modelos de caixa de vidro, por outro lado, são um conjunto de modelos de aprendizado de máquina cujo funcionamento interno é transparente e de fácil compreensão e, ao mesmo tempo, entregam mesma precisão e confiabilidade quanto às previsões.
Dentro deste contexto, a equipe de especialistas da Microsoft Research disponibilizou publicamente o código do Interpret ML, um pacote Python que inclui explicadores de modelos de caixa preta e implementações de modelos de caixa de vidro. Os explicadores de caixa preta do Interpret ML são um conjunto de algoritmos projetados para desvendar o funcionamento desses modelos complexos, sendo em grande parte independentes do modelo específico, permitindo sua aplicação a diversas estruturas de caixa preta. No entanto, estes explicadores apenas estimam as funções dos modelos. Na próxima seção do artigo, veremos onde está o problema. Já os modelos de caixa de vidro incluídos no pacote proporcionam uma vantagem em termos de transparência e precisão, representando uma solução ideal para aqueles que valorizam a interpretabilidade, independentemente da área de aplicação ou experiência.
Informações adicionais:
1. Você pode ler mais sobre isso na documentação do Interpret ML.
2. Também pode ser útil familiarizar-se com o White Paper do Interpret ML.
Neste artigo, usaremos o Interpret ML para criar um modelo de caixa de vidro em Python. Veremos como o modelo de caixa de vidro pode fornecer informações importantes que ajudarão a orientar o processo de desenvolvimento de funções e a melhorar nosso entendimento do funcionamento interno do nosso modelo.
O problema dos modelos de caixa preta: a discordância
Um motivo para abandonar o uso deses modelos é o problema da discordância. Em resumo, diferentes métodos de explicação podem gerar explicações muito variadas, mesmo que avaliem o mesmo modelo. Esses métodos tentam entender a estrutura por trás do modelo de caixa preta. Existem muitas abordagens diferentes, e cada uma delas pode se concentrar em diferentes aspectos do comportamento do modelo e, portanto, cada uma pode resultar em diferentes avaliações do modelo básico de caixa-preta. O problema da discordância representa um campo ainda em aberto para pesquisa, sendo crucial reconhecê-lo e abordá-lo de todas as maneiras possíveis.
Neste artigo, darei uma demonstração real do problema da discordância, caso você ainda não tenha se deparado com ele.
Informações adicionais:
1. Recomendo a leitura de um excelente trabalho de pesquisa de uma equipe de ex-alunos de Harvard, MIT, Drexel e Carnegie Mellon.
Vejamos rapidamente como esse problema de discordância se manifesta:
Primeiro, importamos os pacotes Python para análise.
#Import MetaTrader5 Python package #pip install --upgrade MetaTrader5, if you don't have it installed import MetaTrader5 as mt5 #Import datetime for selecting data #Standard python package, no installation required from datetime import datetime #Plotting Data #pip install --upgrade matplotlib, if you don't have it installed import matplotlib.pyplot as plt #Import pandas for handling data #pip install --upgrade pandas, if you don't have it installed import pandas as pd #Import library for calculating technical indicators #pip install --upgrade pandas-ta, if you don't have it installed import pandas_ta as ta #Scoring metric to assess model accuracy #pip install --upgrade scikit-learn, if you don't have it installed from sklearn.metrics import precision_score #Import mutual information, a black-box explanation technique from sklearn.feature_selection import mutual_info_classif #Import permutation importance, another black-box explanation technique from sklearn.inspection import permutation_importance #Import our model #pip install --upgrade xgboost, if you don't have it installed from xgboost import XGBClassifier #Plotting model importance from xgboost import plot_importance
Depois disso, passamos a integrá-los ao terminal MetaTrader 5, mas, antes disso, precisamos especificar as credenciais de login.
#Enter your account number login = 123456789 #Enter your password password = '_enter_your_password_' #Enter your Broker's server server = 'Deriv-Demo'
Agora podemos inicializar o terminal MetaTrader 5 e fazer login na conta de negociação.
#We can initialize the MT5 terminal and login to our account in the same step if mt5.initialize(login=login,password=password,server=server): print('Logged in successfully') else: print('Failed To Log in')
Autorização bem-sucedida.
Agora temos acesso total ao terminal MetaTrader 5 e podemos solicitar dados de gráficos, de ticks, de cotações atuais e muito mais.
#To view all available symbols from your broker symbols = mt5.symbols_get() for index,value in enumerate(symbols): print(value.name)
Volatility 10 Index
Volatility 25 Index
Volatility 50 Index
Volatility 75 Index
Volatility 100 Index
Volatility 10 (1s) Index
Boom 1000 Index
Boom 500 Index
Crash 1000 Index
Crash 500 Index
Step Index
...
Depois de determinar o símbolo a ser modelado, podemos solicitar os dados do gráfico para o símbolo, mas primeiro precisamos especificar o intervalo de datas para o qual os dados devem ser recuperados.
#We need to specify the dates we want to use in our dataset date_from = datetime(2019,4,17) date_to = datetime.now()Agora você pode solicitar os dados do gráfico para o símbolo.
#Fetching historical data data = pd.DataFrame(mt5.copy_rates_range('Boom 1000 Index',mt5.TIMEFRAME_D1,date_from,date_to))
A coluna de tempo precisa ser formatada em nosso frame de dados para a plotagem.
#Let's convert the time from seconds to year-month-date data['time'] = pd.to_datetime(data['time'],unit='s') data
Fig. 2. Nosso dataframe agora exibe a hora em um formato legível. Note que a coluna real_volume é preenchida com zeros.
Agora vamos criar uma função auxiliar que ajudará a adicionar novas funções ao nosso frame de dados, calcular indicadores técnicos e limpar os dados.
#Let's create a function to preprocess our data def preprocess(df): #All values of real_volume are 0 in this dataset, we can drop the column df.drop(columns={'real_volume'},inplace=True) #Calculating 14 period ATR df.ta.atr(length=14,append=True) #Calculating the growth in the value of the ATR, the second difference df['ATR Growth'] = df['ATRr_14'].diff().diff() #Calculating 14 period RSI df.ta.rsi(length=14,append=True) #Calculating the rolling standard deviation of the RSI df['RSI Stdv'] = df['RSI_14'].rolling(window=14).std() #Calculating the mid point of the high and low price df['mid_point'] = ( ( df['high'] + df['low'] ) / 2 ) #We will keep track of the midpoint value of the previous day df['mid_point - 1'] = df['mid_point'].shift(1) #How far is our price from the midpoint? df['height'] = df['close'] - df['mid_point'] #Drop any rows that have missing values df.dropna(axis=0,inplace=True)
Em seguida, chamamos a função de pré-processamento.
preprocess(data) data
Fig. 3. Frame após o pré-processamento.
Depois, precisamos determinar o target (se o próximo preço de fechamento será maior do que o preço de fechamento de hoje). Vamos programar assim: se o preço de fechamento de amanhã for maior que o de hoje, o target = 1. Caso contrário, o target = 0.
#We want to predict whether tomorrow's close will be greater than today's close #We can encode a dummy variable for that: #1 means tomorrow's close will be greater. #0 means today's close will be greater than tomorrow's. data['target'] = (data['close'].shift(-1) > data['close']).astype(int) data #The first date is 2019-05-14, and the first close price is 9029.486, the close on the next day 2019-05-15 was 8944.461 #So therefore, on the first day, 2019-05-14, the correct forecast is 0 because the close price fell the following day.
Fig. 4. Criamos target
Agora, definimos explicitamente o objetivo e os preditores. Em seguida, dividimos os dados em uma amostra de treinamento e uma amostra de teste. Observe que se trata de dados de série temporal, por isso não podemos dividi-los aleatoriamente em dois grupos.
#Seperating predictors and target predictors = ['open','high','low','close','tick_volume','spread','ATRr_14','ATR Growth','RSI_14','RSI Stdv','mid_point','mid_point - 1','height'] target = ['target'] #The training and testing split definition train_start = 27 train_end = 1000 test_start = 1001
Criamos amostras para treinamento e teste.
#Train set train_x = data.loc[train_start:train_end,predictors] train_y = data.loc[train_start:train_end,target] #Test set test_x = data.loc[test_start:,predictors] test_y = data.loc[test_start:,target]
Agora podemos treinar o modelo.
#Let us fit our model
black_box = XGBClassifier()
black_box.fit(train_x,train_y)
Verificamos as previsões do nosso modelo sobre o conjunto de teste.
#Let's see our model predictions black_box_predictions = pd.DataFrame(black_box.predict(test_x),index=test_x.index)
Avaliamos a precisão do nosso modelo.
#Assesing model prediction accuracy black_box_score = precision_score(test_y,black_box_predictions) #Model precision score black_box_score
0.4594594594594595
Nosso modelo tem uma precisão de 45%. Quais características funcionam para a precisão e quais não funcionam? O XGBoost vem com um recurso integrado para medir a importância das características, o que facilita nossa vida. Entretanto, isso se aplica especificamente a essa implementação do XGBoost. Nem todas as caixas pretas facilitam a medição da importância das características. Por exemplo, as redes neurais e as máquinas de vetor de suporte não têm uma função equivalente, e você terá de analisar e interpretar cuidadosamente os pesos do modelo para entendê-lo melhor. A função plot\_importance do XGBoost nos permite examinar o interior do nosso modelo.
plot_importance(black_box)
Fig. 5. Importância das características no XGBClassifier. Observe que não há termos de interação na tabela. Isso significa que eles não existem? Não necessariamente.
Agora vamos dar uma olhada na primeira técnica de explicação de caixa preta, a importância da permutação (Permutation Importance). Ela procura estimar a importância de cada recurso embaralhando aleatoriamente os valores de cada característica e, em seguida, medindo a alteração na função de perda do modelo. A explicação aqui é que, quanto mais o seu modelo depender desse atributo, pior será o desempenho se embaralharmos aleatoriamente esses valores. Vamos dar uma olhada nas vantagens e desvantagens da importância da permutação.
Vantagens
- Independência do modelo — a importância da permutação pode ser usada em qualquer modelo de caixa preta sem nenhum pré-processamento requerido para o modelo ou recurso de importância da permutação, o que facilita a integração em um fluxo de trabalho de aprendizado de máquina existente.
- Interpretabilidade — os resultados são facilmente interpretados de forma consistente, independentemente do modelo base que está sendo avaliado. É relativamente fácil de usar.
- Tratamento da não linearidade - adequado para capturar relações não lineares entre preditores e resposta.
- Manuseio de outliers — a importância da permutação é independente dos valores brutos dos preditores; isso se refere ao efeito das características no desempenho do modelo. Essa abordagem o torna robusto em relação aos valores discrepantes que podem estar presentes nos dados brutos.
Desvantagens:
- Custo computacional — para grandes conjuntos de dados com muitas características, o cálculo da importância das permutações pode ser computacionalmente caro, pois requer a análise de cada característica, seu reordenamento e a estimativa do modelo, passando para a próxima característica e repetindo o processo.
- Problema de características correlacionadas — pode produzir resultados tendenciosos ao estimar características altamente correlacionadas.
- Sensibilidade à complexidade do modelo — é possível que um modelo muito complexo apresente alta variação quando seus atributos forem permutados, dificultando a obtenção de conclusões confiáveis.
- Independência das características — o método pressupõe que as características do conjunto de dados são independentes e podem ser reorganizadas aleatoriamente sem nenhuma consequência. Isso simplifica os cálculos, mas, no mundo real, a maioria das características depende uma da outra e tem interações que não são levadas em conta pelo método de importância de permutação.
Vamos calcular a importância da permutação para nosso classificador de caixa preta.
#Now let us observe the disagreement problem
black_box_pi = permutation_importance(black_box,train_x,train_y)
# Get feature importances and standard deviations
perm_importances = black_box_pi.importances_mean
perm_std = black_box_pi.importances_std
# Sort features based on importance
sorted_idx = perm_importances.argsort()
Agora vamos plotar os valores calculados da importância da permutação.
#We're going to utilize a bar histogram plt.barh(range(train_x.shape[1]), perm_importances[sorted_idx], xerr=perm_std[sorted_idx]) plt.yticks(range(train_x.shape[1]), train_x.columns[sorted_idx]) plt.xlabel('Permutation Importance') plt.title('Permutation Importances') plt.show()
Fig. 6. A importância de permutar nossa caixa preta.
De acordo com os cálculos feitos pelo algoritmo de importância da permutação, a leitura do ATR é o recurso mais informativo. Mas sabemos que isso não é verdade: o ATR ficou em sexto lugar. A característica importante é o aumento do ATR! O segundo mais importante foi a altura, mas a importância de permutação determinou que o aumento do ATR era mais importante. A terceira característica mais importante foi o valor RSI, mas o algoritmo indicou que a altura era mais importante.
Esse é o problema dos métodos de explicação de caixa preta: eles fornecem estimativas muito boas da importância das características, mas tendem a estar errados porque, na melhor das hipóteses, são apenas estimativas. Além disso, eles podem divergir uns dos outros ao estimar o mesmo modelo. Vamos analisar isso por nós mesmos.
Vamos usar o algoritmo de informações mútuas como um segundo método de explicação. A informação mútua mede a redução da incerteza causada pela percepção do valor de uma característica.
#Let's see if our black-box explainers will disagree with each other by calculating mutual information black_box_mi = mutual_info_classif(train_x,train_y) black_box_mi = pd.Series(black_box_mi, name="MI Scores", index=train_x.columns) black_box_mi = black_box_mi.sort_values(ascending=False) black_box_mi
RSI_14: 0.014579
open: 0.010044
low: 0.005544
mid_point - 1: 0.005514
close: 0.002428
tick_volume : 0.001402
high: 0.000000
spread: 0.000000
ATRr_14: 0.000000
ATR Growth: 0.000000
RSI Stdv: 0.000000
mid_point: 0.000000
height: 0.000000
Name: MI Scores, dtype: float64
Como você pode ver, temos classificações de importância completamente diferentes. A informação mútua classifica os recursos quase na ordem inversa em comparação com nossa base e cálculo de importância de permutação. Se você não tivesse uma base com valores verdadeiros, como neste exemplo, em qual explicação você confiaria mais? E se você usasse cinco métodos diferentes de explicação, e cada um deles desse avaliações diferentes? Se você escolhe vieses que correspondem às suas crenças sobre como o mundo real funciona, isso abre a porta para outro problema, chamado viés de confirmação. O viés de confirmação é quando você ignora quaisquer evidências que contradigam suas crenças existentes e ativamente busca confirmar o que você acredita ser verdade, mesmo que não seja!
Vantagens dos modelos de caixa de vidro
Os modelos de caixa de vidro substituem perfeitamente a necessidade de métodos de explicação de caixa preta, pois são totalmente transparentes e compreensíveis. Eles podem potencialmente resolver o problema de discordâncias em muitas áreas, incluindo o setor financeiro. Depurar um modelo de caixa de vidro é exponencialmente mais fácil do que depurar uma caixa preta do mesmo nível de flexibilidade. Isso economiza o recurso mais importante, o tempo! A melhor parte é que não compromete a precisão do modelo, oferecendo o melhor de ambos os mundos. Em geral, as caixas pretas só devem ser usadas quando a caixa branca não pode fornecer o mesmo nível de precisão.
Vamos agora passar para a criação de nosso primeiro modelo de caixa de vidro. Analisaremos suas características e tentaremos aumentar sua precisão. Em seguida, veremos como integrar nosso modelo ao terminal MetaTrader 5 e começar a negociar usando modelos brancos. Depois, criaremos um Expert Advisor baseado no modelo de caixa branca no MQL5. E, finalmente, exportaremos nosso modelo de caixa de vidro para o formato ONNX, para liberar todo o potencial do MetaTrader 5 e nosso modelo.
Criando o primeiro modelo de caixa de vidro em Python
Para que o código seja fácil de ler, criaremos nossa caixa de vidro em um script Python separado do que usamos para construir o modelo de caixa preta. No entanto, a maioria das coisas permanecerá a mesma, como login, obtenção de dados, etc., bem como o pré-processamento de dados. Portanto, não os repetiremos, mas nos concentraremos nos passos únicos para o modelo de caixa de vidro.
Primeiro, você precisa instalar o Interpret ML.
#Installing Interpret ML pip install --upgrade interpret
Em seguida, carregamos nossas dependências. Neste artigo, nos concentraremos em três módulos do pacote de interpretação. O primeiro é o próprio modelo de caixa de vidro, o segundo é o módulo que nos permite olhar dentro do modelo e apresentar essa informação em um painel informativo interativo com interface gráfica, e o último pacote nos permite visualizar o desempenho de nosso modelo em um único gráfico. Já discutimos os outros pacotes.
#Import MetaTrader5 package import MetaTrader5 as mt5 #Import datetime for selecting data from datetime import datetime #Import matplotlib for plotting import matplotlib.pyplot as plt #Intepret glass-box model for classification from interpret.glassbox import ExplainableBoostingClassifier #Intepret GUI dashboard utility from interpret import show #Visualising our model's performance in one graph from interpret.perf import ROC #Pandas for handling data import pandas as pd #Pandas-ta for calculating technical indicators import pandas_ta as ta #Scoring metric to assess model accuracy from sklearn.metrics import precision_score
Criamos os dados de entrada e fazemos login no terminal MT5, como antes. Neste passo, não paramos.
Escolhemos novamente o símbolo que precisamos modelar, como antes. Neste passo, não paramos.
Em seguida, especificamos o intervalo de datas para os dados que precisamos modelar, como fizemos anteriormente. Neste passo, não paramos.
Depois, podemos obter os dados históricos. Neste passo, não paramos.
Em seguida, executamos os mesmos passos de pré-processamento descritos acima. Neste passo, não paramos.
Após o pré-processamento dos dados, adicionamos o target, como antes. Neste passo, não paramos.
Em seguida, dividimos os dados em conjuntos de treinamento e teste. Neste passo, não paramos. A divisão dos dados em conjuntos de treinamento e teste não deve ser randomizada. É importante manter a ordem natural do tempo, caso contrário, os resultados podem ser prejudicados, e você pode acabar com uma visão excessivamente otimista dos resultados futuros.
Vamos treinar o modelo.
#Let us fit our glass-box model #Please note this step can take a while, depending on your computational resources glass_box = ExplainableBoostingClassifier() glass_box.fit(train_x,train_y)
Agora podemos olhar para dentro do nosso modelo de caixa de vidro.
#The show function provides an interactive GUI dashboard for us to interface with out model #The explain_global() function helps us find what our model found important and allows us to identify potential bias or unintended flaws show(glass_box.explain_global())
Fig. 7. O estado global da caixa de vidro
A interpretação das estatísticas resumidas é muito importante. Mas antes disso, vamos começar com os conceitos importantes. O "estado global" resume o estado de todo o modelo. Isso permite entender quais características o modelo considera informativas. Não deve ser confundido com o estado local. Os estados locais são usados para explicar as previsões individuais do modelo, ajudam a entender por que o modelo fez determinada previsão e quais características influenciaram as previsões individuais.
Vamos voltar ao estado global do nosso modelo. Como podemos ver, o modelo encontrou o valor da média móvel com deslocamento muito informativo, como esperávamos. Além disso, ele também encontrou um possível fator de interação entre o aumento da ATR e o valor da mid_point. A altura foi a terceira característica mais importante, seguida pelo fator de interação entre o preço de fechamento e a altura (distância entre a média móvel e o fechamento). Observe que não precisamos de nenhuma ferramenta adicional para entender o modelo de caixa branca. Com isso, evitamos completamente o problema de discordância e o viés de confirmação. A informação sobre o estado global é inestimável do ponto de vista do desenvolvimento de características, pois mostra onde futuros esforços podem ser direcionados para o desenvolvimento de melhores características. Avançando, vamos ver como funciona nosso caixa branca.
Obtendo previsões do modelo de caixa branca
#Obtaining glass-box predictions
glass_box_predictions = pd.DataFrame(glass_box.predict(test_x))
Agora vamos medir a precisão do modelo.
glass_box_score = precision_score(test_y,glass_box_predictions) glass_box_score
0.49095022624434387
Bem, o modelo de caixa branca mostrou uma precisão de 49%. Obviamente, o modelo EBC (Explainable Boosting Classifier) pode ter mais peso em comparação com o XGBClassifier. Isso demonstra as capacidades dos modelos de caixa branca, proporcionando alta precisão sem comprometer a compreensão.
Também é possível obter explicações individuais para cada previsão de nosso modelo de caixa branca, para entender quais características influenciaram a previsão em um nível detalhado — isso é chamado de explicações locais. Elas são bastante fáceis de obter.
#We can also obtain individual explanations for each prediction show(glass_box.explain_local(test_x,test_y))
Fig. 8. Explicações locais do EBC (Explainable Boosting Classifier).
O primeiro menu suspenso permite percorrer todas as previsões e escolher aquela que queremos entender melhor.
Em seguida, observamos a classe real e a classe prevista. Neste caso, a classe real era 0, o que significa que o preço de fechamento caiu, mas classificamos como 1. Também são apresentadas as probabilidades calculadas para cada classe. Nosso modelo estimou incorretamente uma probabilidade de 53% de que a próxima vela fechasse mais alta. Há também um detalhamento da contribuição de cada característica para a probabilidade prevista. As características destacadas em azul trabalham contra a previsão feita pelo nosso modelo, enquanto as características laranjas formaram a base para a previsão. Acontece que o RSI contribuiu mais para essa classificação incorreta, enquanto a interação entre o spread e a altura apontava na direção correta. Essas características podem merecer mais desenvolvimento, mas é necessário um exame mais detalhado das explicações locais antes que possamos tirar conclusões.
Agora, verifiquemos o desempenho do modelo usando o gráfico ROC. O gráfico ROC permite avaliar o desempenho do nosso classificador. Estamos interessados na área sob a curva ou AUC. Teoricamente, um classificador ideal teria uma área total sob a curva de 1. Isso permite avaliar o classificador com um único gráfico.
glass_box_performance = ROC(glass_box.predict_proba).explain_perf(test_x,test_y, name='Glass Box') show(glass_box_performance)
Fig. 9: Curva ROC do nosso modelo de caixa de vidro.
No modelo, AUC = 0.49. Essa métrica simples permite avaliar o desempenho do modelo usando unidades que são fáceis de entender. Além disso, a curva é independente do modelo e pode ser usada para comparar diferentes classificadores, independentemente dos métodos de classificação subjacentes.
Integrando o modelo de caixa de vidro ao terminal MT5
Agora vamos ao cerne da questão, a integração de nosso modelo ao terminal, começando com uma abordagem mais simples.
Primeiro, determinaremos o estado da conta atual.
#Fetching account Info account_info = mt5.account_info() # getting specific account data initial_balance = account_info.balance initial_equity = account_info.equity print('balance: ', initial_balance) print('equity: ', initial_equity)
balance: 912.11 equity: 912.11
Obteremos todos os símbolos.
symbols = mt5.symbols_get()
Configuraremos variáveis globais.
#Trading global variables #The symbol we want to trade MARKET_SYMBOL = 'Boom 1000 Index' #This data frame will store the most recent price update last_close = pd.DataFrame() #We may not always enter at the price we want, how much deviation can we tolerate? DEVIATION = 100 #For demonstrational purposes we will always enter at the minimum volume #However,we will not hardcode the minimum volume, we will fetch it dynamically VOLUME = 0 #How many times the minimum volume should our positions be LOT_MUTLIPLE = 1 #What timeframe are we working on? TIMEFRAME = mt5.TIMEFRAME_D1
Não especificaremos volumes de negociação no código. Obteremos o volume mínimo permitido da corretora e multiplicaremos por algum coeficiente para garantir o envio de ordens válidas. O tamanho da ordem será definido em relação ao volume mínimo.
No nosso caso, abriremos cada negociação com o volume mínimo ou com um coeficiente de 1.
for index,symbol in enumerate(symbols): if symbol.name == MARKET_SYMBOL: print(f"{symbol.name} has minimum volume: {symbol.volume_min}") VOLUME = symbol.volume_min * LOT_MULTIPLE
O índice Boom 1000 tem um volume mínimo de 0,2.
Agora definiremos uma função auxiliar para abrir negociações.
# function to send a market order def market_order(symbol, volume, order_type, **kwargs): #Fetching the current bid and ask prices tick = mt5.symbol_info_tick(symbol) #Creating a dictionary to keep track of order direction order_dict = {'buy': 0, 'sell': 1} price_dict = {'buy': tick.ask, 'sell': tick.bid} request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_dict[order_type], "price": price_dict[order_type], "deviation": DEVIATION, "magic": 100, "comment": "Glass Box Market Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result
Em seguida, definiremos uma função auxiliar para fechar negociações com base no ticket.
# Closing our order based on ticket id def close_order(ticket): positions = mt5.positions_get() for pos in positions: tick = mt5.symbol_info_tick(pos.symbol) #validating that the order is for this symbol type_dict = {0: 1, 1: 0} # 0 represents buy, 1 represents sell - inverting order_type to close the position price_dict = {0: tick.ask, 1: tick.bid} #bid ask prices if pos.ticket == ticket: request = { "action": mt5.TRADE_ACTION_DEAL, "position": pos.ticket, "symbol": pos.symbol, "volume": pos.volume, "type": type_dict[pos.type], "price": price_dict[pos.type], "deviation": DEVIATION, "magic": 100, "comment": "Glass Box Close Order", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_FOK, } order_result = mt5.order_send(request) print(order_result) return order_result return 'Ticket does not exist'
Para não solicitar constantemente muitos dados do servidor, atualizaremos o intervalo de datas.
#Update our date from and date to date_from = datetime(2023,11,1) date_to = datetime.now()
Também precisaremos de uma função para obter a previsão do modelo de caixa de vidro e usar a previsão como sinais de negociação.
#Get signals from our glass-box model def ai_signal(): #Fetch OHLC data df = pd.DataFrame(mt5.copy_rates_range(market_symbol,TIMEFRAME,date_from,date_to)) #Process the data df['time'] = pd.to_datetime(df['time'],unit='s') df['target'] = (df['close'].shift(-1) > df['close']).astype(int) preprocess(df) #Select the last row last_close = df.iloc[-1:,1:] #Remove the target column last_close.pop('target') #Use the last row to generate a forecast from our glass-box model #Remember 1 means buy and 0 means sell forecast = glass_box.predict(last_close) return forecast[0]
Agora escreveremos a parte principal do robô de negociação em Python.
#Now we define the main body of our Python Glass-box Trading Bot if __name__ == '__main__': #We'll use an infinite loop to keep the program running while True: #Fetching model prediction signal = ai_signal() #Decoding model prediction into an action if signal == 1: direction = 'buy' elif signal == 0: direction = 'sell' print(f'AI Forecast: {direction}') #Opening A Buy Trade #But first we need to ensure there are no opposite trades open on the same symbol if direction == 'buy': #Close any sell positions for pos in mt5.positions_get(): if pos.type == 1: #This is an open sell order, and we need to close it close_order(pos.ticket) if not mt5.positions_totoal(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) #Opening A Sell Trade elif direction == 'sell': #Close any buy positions for pos in mt5.positions_get(): if pos.type == 0: #This is an open buy order, and we need to close it close_order(pos.ticket) if not mt5.positions_get(): #We have no open positions market_order(MARKET_SYMBOL,VOLUME,direction) print('time: ', datetime.now()) print('-------\n') time.sleep(60)
Previsão de AI: Sell
Hora: 2023-12-04 15:31:31.569495
-------
Fig. 10. O robô investidor baseado no modelo de caixa de vidro em Python mostra lucro
Criando um robô de negociação para trabalhar com o modelo
Vamos criar um assistente no MQL5 para nosso modelo de caixa de vidro. Criaremos um robô investidor que ajustará o stop-loss e o take-profit com base nos valores de ATR. O código abaixo atualizará os valores de TP e SL a cada tick. Realizar essa tarefa usando o módulo de integração Python seria um pesadelo e exigiria atualizações frequentes, como a cada minuto ou hora. No entanto, precisamos atualizar SL e TP a cada tick. O usuário deverá especificar a distância entre a entrada e o nível de SL/TP. Multiplicaremos os valores de ATR pelos dados inseridos pelo usuário para determinar a distância do SL ou TP até o ponto de entrada. O segundo parâmetro que o usuário deve especificar é o período de ATR.
//Meta Properties #property copyright "Gamuchirai Ndawana" #property link "https://twitter.com/Westwood267" //Classes for managing Trades And Orders #include <Trade\Trade.mqh> #include <Trade\OrderInfo.mqh> //Instatiating the trade class and order manager CTrade trade; class COrderInfo; //Input variables input double atr_multiple =0.025; //How many times the ATR should the SL & TP be? input int atr_period = 200; //ATR Period //Global variables double ask, bid,atr_stop; //We will use these variables to determine where we should place our ATR double atr_reading[]; //We will store our ATR readings in this arrays int atr; //This will be our indicator handle for our ATR indicator int min_volume; int OnInit(){ //Check if we are authorized to use an EA on the terminal if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)){ Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program"); //Remove the EA from the terminal ExpertRemove(); return(INIT_FAILED); } //Check if we are authorized to use an EA on the terminal else if(!MQLInfoInteger(MQL_TRADE_ALLOWED)){ Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading"); //Remove the EA from the terminal ExpertRemove(); return(INIT_FAILED); } //If we arrive here then we are allowed to trade using an EA on the Terminal else{ //Symbol information //The smallest distance between our point of entry and the stop loss min_volume = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);//SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN) //Setting up our ATR indicator atr = iATR(_Symbol,PERIOD_CURRENT,atr_period); return(INIT_SUCCEEDED); } } void OnDeinit(const int reason){ } void OnTick(){ //Get the current ask ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); //Get the current bid bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); //Copy the ATR reading our array for storing the ATR value CopyBuffer(atr,0,0,1,atr_reading); //Set the array as series so the natural time ordering is preserved ArraySetAsSeries(atr_reading,true); //Calculating where to position our stop loss //For now we'll keep it simple, we'll add the minimum volume and the current ATR reading and multiply it by the ATR multiple atr_stop = ((min_volume + atr_reading[0]) * atr_multiple); //If we have open positions we should adjust the stop loss and take profit if(PositionsTotal() > 0){ check_atr_stop(); } } //--- Functions //This funciton will update our S/L & T/P based on our ATR reading void check_atr_stop(){ //First we iterate over the total number of open positions for(int i = PositionsTotal() -1; i >= 0; i--){ //Then we fetch the name of the symbol of the open position string symbol = PositionGetSymbol(i); //Before going any furhter we need to ensure that the symbol of the position matches the symbol we're trading if(_Symbol == symbol){ //Now we get information about the position ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price double type = PositionGetInteger(POSITION_TYPE); //Position Type double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value //If the position is a buy if(type == POSITION_TYPE_BUY){ //The new stop loss value is just the ask price minus the ATR stop we calculated above double atr_stop_loss = (ask - (atr_stop)); //The new take profit is just the ask price plus the ATR stop we calculated above double atr_take_profit = (ask + (atr_stop)); //If our current stop loss is less than our calculated ATR stop loss //Or if our current stop loss is 0 then we will modify the stop loss and take profit if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)){ trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } //If the position is a sell else if(type == POSITION_TYPE_SELL){ //The new stop loss value is just the bid price plus the ATR stop we calculated above double atr_stop_loss = (bid + (atr_stop)); //The new take profit is just the bid price minus the ATR stop we calculated above double atr_take_profit = (bid - (atr_stop)); //If our current stop loss is greater than our calculated ATR stop loss //Or if our current stop loss is 0 then we will modify the stop loss and take profit if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)){ trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } } } }
Fig. 11. Robô assistente para nosso modelo de caixa de vidro
Exportação do modelo de caixa de vidro para o formato Open Neural Network Exchange (ONNX)
Fig. 12. Logotipo do Open Neural Network Exchange.
ONNX (Open Neural Network Exchange) é um padrão aberto para representação de modelos de redes neurais. É amplamente suportado graças aos esforços coletivos de empresas de todo o mundo e de diferentes setores. Algumas dessas empresas incluem Microsoft, Facebook, MATLAB, IBM, Qualcomm, Huawei, Intel, AMD, entre outras. No momento da escrita deste artigo, o ONNX é uma forma padrão universal para representar qualquer modelo de aprendizado de máquina, independentemente do ambiente em que foi desenvolvido, e, além disso, permite o desenvolvimento e a implantação de modelos de aprendizado de máquina em diferentes linguagens de programação e ambientes. A ideia principal é que qualquer modelo de aprendizado de máquina pode ser representado como um gráfico de nós e arestas. Cada nó representa uma operação matemática, e cada aresta representa um fluxo de dados. Usando essa representação simples, qualquer modelo de aprendizado de máquina pode ser representado, independentemente do ambiente em que foi criado.
Além disso, precisaremos de um mecanismo que execute modelos ONNX. Por isso, a execução ONNX é responsável. O ambiente de execução ONNX é responsável pela execução eficiente e implantação de modelos ONNX em vários dispositivos: de supercomputadores em centros de dados a telefones celulares no seu bolso e tudo o que há entre eles.
No nosso caso, o ONNX permite integrar o modelo de aprendizado de máquina ao nosso robô investidor e, essencialmente, criar um robô investidor com um cérebro próprio. O terminal MetaTrader 5 oferece um conjunto completo de ferramentas para testar robôs investidores de forma segura e confiável em dados históricos. Além disso, ele permite testá-los em um período futuro. O teste futuro é a execução do robô investidor em tempo real ou durante qualquer período antes da última data de treinamento que o modelo viu. Este é o melhor teste da confiabilidade do modelo ao lidar com dados que não viu durante o treinamento.
Como antes, separaremos o código usado para exportar o modelo ONNX do restante do código que usamos até agora neste artigo. Assim fica mais fácil de ler. Além disso, vamos reduzir o número de parâmetros que o modelo requer como dados de entrada para simplificar sua implementação prática. Para fornecer entrada ao modelo ONNX, usaremos apenas estas características:
1. Lag height — altura com deslocamento; a altura é definida como: (((High + Low) / 2) – Close), e o lag height é o valor anterior da altura.
2. Height growth — avaliação da segunda derivada das leituras de altura. Para o cálculo, a diferença entre os valores históricos sucessivos de altura é feita duas vezes. O valor obtido fornece uma ideia sobre a velocidade de mudança. Simplificando, isso ajuda a entender se o crescimento da altura está acelerando ou desacelerando com o tempo.
3. Midpoint — calculado como ((High + Low) / 2)
4. Midpoint growth — crescimento do ponto médio, uma característica derivada das leituras do ponto médio. Para calcular, a diferença entre valores históricos consecutivos do ponto médio é tomada duas vezes. O valor obtido fornece uma ideia sobre a velocidade de mudança do ponto médio. Isso mostra se o crescimento do ponto médio está acelerando ou desacelerando com o tempo. Falando em termos mais simples e menos técnicos, o valor ajuda a entender se o ponto médio está se afastando do zero com velocidade crescente ou se aproximando do zero com uma velocidade que está aumentando cada vez mais.
Além disso, mudamos os símbolos: na primeira metade do artigo, modelávamos o símbolo Boom 1000 Index, e agora modelaremos o Volatility 75 Index.
O EA também colocará automaticamente os níveis de SL/TP dinamicamente, usando as leituras de ATR. Além disso, permitiremos que ele adicione automaticamente outra posição assim que o lucro exceder um determinado limiar.
A maior parte permaneceu a mesma, exceto por duas novas adições: ONNX e ebm2onnx. Esses dois pacotes permitem converter nossa EBM (máquina explicativa de aumento) para o formato ONNX.
#Import MetaTrader5 package import MetaTrader5 as mt5 #Import datetime for selecting data from datetime import datetime #Keeping track of time import time #Import matplotlib import matplotlib.pyplot as plt #Intepret glass-box model from interpret.glassbox import ExplainableBoostingClassifier #Intepret GUI dashboard utility from interpret import show #Pandas for handling data import pandas as pd #Pandas-ta for calculating technical indicators import pandas_ta as ta #Scoring metric to assess model accuracy from sklearn.metrics import precision_score #ONNX import onnx #Import ebm2onnx import ebm2onnx #Path handling from sys import argv
Abaixo repetimos os passos descritos acima para entrar no sistema e obter dados. Adicionalmente, adicionaremos passos para a preparação de características personalizadas.
#Let's create a function to preprocess our data def preprocess(data): data['mid_point'] = ((data['high'] + data['low']) / 2) data['mid_point_growth'] = data['mid_point'].diff().diff() data['mid_point_growth_lag'] = data['mid_point_growth'].shift(1) data['height'] = (data['mid_point'] - data['close']) data['height - 1'] = data['height'].shift(1) data['height_growth'] = data['height'].diff().diff() data['height_growth_lag'] = data['height_growth'].shift(1) data['time'] = pd.to_datetime(data['time'],unit='s') data.dropna(axis=0,inplace=True) data['target'] = (data['close'].shift(-1) > data['close']).astype(int)
Após a coleta de dados, os passos são exatamente os mesmos para dividir os dados em conjuntos de treino e teste e para configurar o modelo.
Após a configuração, estamos prontos para exportar o modelo para o formato ONNX.
Primeiro, precisamos especificar o caminho onde salvaremos o modelo. A cada instalação do MetaTrader 5, uma pasta especial é criada para arquivos que podem ser usados no terminal. É muito fácil obter o caminho absoluto usando a biblioteca Python.
terminal_info=mt5.terminal_info() print(terminal_info)
O caminho desejado é armazenado como data path no objeto terminal_info.
file_path=terminal_info.data_path+"\\MQL5\\Files\\"
print(file_path)
C:\Users\Westwood\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files\
Em seguida, precisamos preparar o caminho. O código aceita o caminho do arquivo que obtivemos do terminal e isola o diretório do caminho, excluindo quaisquer nomes de arquivos.
data_path=argv[0] last_index=data_path.rfind("\\")+1 data_path=data_path[0:last_index] print("data path to save onnx model",data_path)
data path to save onnx model C:\Users\Westwood\AppData\Local\Programs\Python\Python311\Lib\site-packages\
Usamos o pacote ebm2onnx para preparar nosso modelo de vidro para conversão para o formato ONNX. Note que é necessário especificar explicitamente os tipos de dados para cada um dos parâmetros de entrada. É melhor fazer isso dinamicamente, usando a função ebm2onnx.get_dtype_from_pandas. Para isso, passamos a ela o frame de dados de treinamento que usamos anteriormente.
onnx_model = ebm2onnx.to_onnx(glass_box,ebm2onnx.get_dtype_from_pandas(train_x))
#Save the ONNX model in python output_path = data_path+"Volatility_75_EBM.onnx" onnx.save_model(onnx_model,output_path) #Save the ONNX model as a file to be imported in our MetaEditor output_path = file_path+"Volatility_75_EBM.onnx" onnx.save_model(onnx_model,output_path)
Agora estamos prontos para trabalhar com o arquivo ONNX no MetaEditor 5. MetaEditor é um ambiente de desenvolvimento integrado para a linguagem MQL5.
Abrimos o MetaEditor, clicamos duas vezes no Volatility Doctor 75 EBM e vemos:
Fig. 13: Dados de entrada e saída de nosso modelo ONNX.
Agora, vamos criar um Expert Advisor e importar nosso modelo ONNX.
Começaremos especificando informações gerais sobre o arquivo.
//+------------------------------------------------------------------+ //| ONNX.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //Meta properties #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00"
Definiremos variáveis globais.
//Trade Library #include <Trade\Trade.mqh> //We will use this library to modify our positions //Global variables //Input variables input double atr_multiple =0.025; //How many times the ATR should the SL & TP be? int input lot_mutliple = 1; //How many time greater than minimum lot should we enter? const int atr_period = 200; //ATR Period //Trading variables double ask, bid,atr_stop; //We will use these variables to determine where we should place our ATR double atr_reading[]; //We will store our ATR readings in this arrays int atr; //This will be our indicator handle for our ATR indicator long min_distance; //The smallest distance allowed between our entry position and the stop loss double min_volume; //The smallest contract size allowed by the broker static double initial_balance; //Our initial trading balance at the beginning of the trading session double current_balance; //Our trading balance at every instance of trading long ExtHandle = INVALID_HANDLE; //This will be our model's handler int ExtPredictedClass = -1; //This is where we will store our model's forecast CTrade ExtTrade; //This is the object we will call to open and modify our positions //Reading our ONNX model and storing it into a data array #resource "\\Files\\Volatility_75_EBM.onnx" as uchar ExtModel[] //This is our ONNX file being read into our expert advisor //Custom keyword definitions #define PRICE_UP 1 #define PRICE_DOWN 0
Especificamos a função OnInit(). Usamos OnInit para configurar nosso modelo ONNX. Para configurar o modelo ONNX, precisamos realizar 3 passos simples. Primeiro, criamos o modelo ONNX a partir do buffer que usamos nas variáveis globais acima, quando precisávamos do modelo ONNX como um recurso. Lemos o modelo, especificamos a forma de cada parâmetro de entrada e saída individual. Depois disso, verificamos se ocorreram erros ao tentar definir a forma. Se tudo estiver bem, obteremos o volume mínimo de contrato permitido pela corretora, a distância mínima entre o stop-loss e a posição de entrada, e configuraremos o indicador ATR.
int OnInit() { //Check if the symbol and time frame conform to training conditions if(_Symbol != "Volatility 75 Index" || _Period != PERIOD_M1) { Comment("Model must be used with the Volatility 75 Index on the 1 Minute Chart"); return(INIT_FAILED); } //Create an ONNX model from our data array ExtHandle = OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); Print("ONNX Create from buffer status ",ExtHandle); //Checking if the handle is valid if(ExtHandle == INVALID_HANDLE) { Comment("ONNX create from buffer error ", GetLastError()); return(INIT_FAILED); } //Set input shape long input_count = OnnxGetInputCount(ExtHandle); const long input_shape[] = {1}; Print("Total model inputs : ",input_count); //Setting the input shape of each input OnnxSetInputShape(ExtHandle,0,input_shape); OnnxSetInputShape(ExtHandle,1,input_shape); OnnxSetInputShape(ExtHandle,2,input_shape); OnnxSetInputShape(ExtHandle,3,input_shape); //Check if anything went wrong when setting the input shape if(!OnnxSetInputShape(ExtHandle,0,input_shape) || !OnnxSetInputShape(ExtHandle,1,input_shape) || !OnnxSetInputShape(ExtHandle,2,input_shape) || !OnnxSetInputShape(ExtHandle,3,input_shape)) { Comment("ONNX set input shape error ", GetLastError()); OnnxRelease(ExtHandle); return(INIT_FAILED); } //Set output shape long output_count = OnnxGetOutputCount(ExtHandle); const long output_shape[] = {1}; Print("Total model outputs : ",output_count); //Setting the shape of each output OnnxSetOutputShape(ExtHandle,0,output_shape); //Checking if anything went wrong when setting the output shape if(!OnnxSetOutputShape(ExtHandle,0,output_shape)) { Comment("ONNX set output shape error ", GetLastError()); OnnxRelease(ExtHandle); return(INIT_FAILED); } //Get the minimum trading volume allowed min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); //Symbol information //The smallest distance between our point of entry and the stop loss min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //Initial account balance initial_balance = AccountInfoDouble(ACCOUNT_BALANCE); //Setting up our ATR indicator atr = iATR(_Symbol,PERIOD_CURRENT,atr_period); return(INIT_SUCCEEDED); //--- }
A função DeInit remove o manipulador ONNX para não ocupar recursos que não usamos.
void OnDeinit(const int reason) { //--- if(ExtHandle != INVALID_HANDLE) { OnnxRelease(ExtHandle); ExtHandle = INVALID_HANDLE; } }
A função OnTick é o coração do especialista, é chamada toda vez que um novo tick é recebido do corretor. Neste caso, começamos monitorando o tempo. Isso permite separar os processos realizados em cada tick daqueles que precisam ser realizados ao formar uma nova vela. Em cada tick, precisamos atualizar os preços Bid e Ask, bem como as posições de take-profit e stop-loss a cada tick. No entanto, a previsão do modelo só é necessária após a formação de uma nova vela, se não houver posições abertas.
void OnTick() { //--- //Time trackers static datetime time_stamp; datetime time = iTime(_Symbol,PERIOD_M1,0); //Current bid price bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); //Current ask price ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); //Copy the ATR reading our array for storing the ATR value CopyBuffer(atr,0,0,1,atr_reading); //Set the array as series so the natural time ordering is preserved ArraySetAsSeries(atr_reading,true); //Calculating where to position our stop loss //For now we'll keep it simple, we'll add the minimum volume and the current ATR reading and multiply it by the ATR multiple atr_stop = ((min_distance + atr_reading[0]) * atr_multiple); //Current Session Profit and Loss Position current_balance = AccountInfoDouble(ACCOUNT_BALANCE); Comment("Current Session P/L: ",current_balance - initial_balance); //If we have a position open we need to update our stoploss if(PositionsTotal() > 0){ check_atr_stop(); } //Check new bar if(time_stamp != time) { time_stamp = time; //If we have no open positions let's make a forecast and open a new position if(PositionsTotal() == 0){ Print("No open positions making a forecast"); PredictedPrice(); CheckForOpen(); } } }Em seguida, definimos uma função que atualizará a posição de take-profit e stop-loss de acordo com o ATR. A função itera por cada posição aberta e verifica se a posição corresponde ao símbolo negociado. Se corresponder, a função obtém informações adicionais sobre a posição, com base nas quais ajusta o stop-loss e o take-profit de acordo com a direção da posição. Note que, se a negociação for contra a posição, o take-profit e o stop-loss permanecerão no lugar.
//--- Functions //This function will update our S/L & T/P based on our ATR reading void check_atr_stop(){ //First we iterate over the total number of open positions for(int i = PositionsTotal() -1; i >= 0; i--){ //Then we fetch the name of the symbol of the open position string symbol = PositionGetSymbol(i); //Before going any further we need to ensure that the symbol of the position matches the symbol we're trading if(_Symbol == symbol){ //Now we get information about the position ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price long type = PositionGetInteger(POSITION_TYPE); //Position Type double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value //If the position is a buy if(type == POSITION_TYPE_BUY){ //The new stop loss value is just the ask price minus the ATR stop we calculated above double atr_stop_loss = (ask - (atr_stop)); //The new take profit is just the ask price plus the ATR stop we calculated above double atr_take_profit = (ask + (atr_stop)); //If our current stop loss is less than our calculated ATR stop loss //Or if our current stop loss is 0 then we will modify the stop loss and take profit if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)){ ExtTrade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } //If the position is a sell else if(type == POSITION_TYPE_SELL){ //The new stop loss value is just the bid price plus the ATR stop we calculated above double atr_stop_loss = (bid + (atr_stop)); //The new take profit is just the bid price minus the ATR stop we calculated above double atr_take_profit = (bid - (atr_stop)); //If our current stop loss is greater than our calculated ATR stop loss //Or if our current stop loss is 0 then we will modify the stop loss and take profit if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)){ ExtTrade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } } } }Precisamos de mais uma função para abrir uma nova posição. Note que usamos variáveis globais para os valores de Bid e Ask. Isso garante que todo o programa usará o mesmo preço. Além disso, definimos o stop-loss e o take-profit como 0, já que seus valores serão controlados pela função check_atr_stop.
void CheckForOpen(void) { ENUM_ORDER_TYPE signal = WRONG_VALUE; //Check signals if(ExtPredictedClass == PRICE_DOWN) { signal = ORDER_TYPE_SELL; } else if(ExtPredictedClass == PRICE_UP) { signal = ORDER_TYPE_BUY; } if(signal != WRONG_VALUE && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { double price, sl = 0 , tp = 0; if(signal == ORDER_TYPE_SELL) { price = bid; } else { price = ask; } Print("Opening a new position: ",signal); ExtTrade.PositionOpen(_Symbol,signal,min_volume,price,0,0,"ONNX Order"); } }
Finalmente, precisamos de uma função para fazer previsões com nosso modelo ONNX dentro do nosso Expert Advisor. A função também será responsável pelo pré-processamento dos dados, da mesma forma que durante o treinamento. É essencial garantir que os dados sejam processados consistentemente durante o treinamento e a utilização em massa. Observe que cada entrada no modelo é mantida em seu próprio vetor, e cada vetor é então passado para a função de execução ONNX na mesma ordem em que foram fornecidos ao modelo durante o treinamento. É extremamente importante considerar isso ao longo de todo o projeto, caso contrário, podem ocorrer erros durante a operação, que não geram exceções durante a compilação do modelo. Os tipos de dados de cada vetor de entrada devem corresponder ao tipo esperado pelo modelo, assim como o tipo dos dados de saída deve corresponder ao tipo de saída do modelo.
void PredictedPrice(void) { long output_data[] = {1}; double lag_2_open = double(iOpen(_Symbol,PERIOD_M1,3)); double lag_2_high = double(iOpen(_Symbol,PERIOD_M1,3)); double lag_2_close = double(iClose(_Symbol,PERIOD_M1,3)); double lag_2_low = double(iLow(_Symbol,PERIOD_M1,3)); double lag_2_mid_point = double((lag_2_high + lag_2_low) / 2); double lag_2_height = double(( lag_2_mid_point - lag_2_close)); double lag_open = double(iOpen(_Symbol,PERIOD_M1,2)); double lag_high = double(iOpen(_Symbol,PERIOD_M1,2)); double lag_close = double(iClose(_Symbol,PERIOD_M1,2)); double lag_low = double(iLow(_Symbol,PERIOD_M1,2)); double lag_mid_point = double((lag_high + lag_low) / 2); double lag_height = double(( lag_mid_point - lag_close)); double open = double(iOpen(_Symbol,PERIOD_M1,1)); double high = double(iHigh(_Symbol,PERIOD_M1,1)); double low = double(iLow(_Symbol,PERIOD_M1,1)); double close = double(iClose(_Symbol,PERIOD_M1,1)); double mid_point = double( (high + low) / 2 ); double height = double((mid_point - close)); double first_height_delta = (height - lag_height); double second_height_delta = (lag_height - lag_2_height); double height_growth = first_height_delta - second_height_delta; double first_midpoint_delta = (mid_point - lag_mid_point); double second_midpoint_delta = (lag_mid_point - lag_2_mid_point); double mid_point_growth = first_midpoint_delta - second_midpoint_delta; vector input_data_lag_height = {lag_height}; vector input_data_height_grwoth = {height_growth}; vector input_data_midpoint_growth = {mid_point_growth}; vector input_data_midpoint = {mid_point}; if(OnnxRun(ExtHandle,ONNX_NO_CONVERSION,input_data_lag_height,input_data_height_grwoth,input_data_midpoint_growth,input_data_midpoint,output_data)) { Print("Model Inference Completed Successfully"); Print("Model forecast: ",output_data[0]); } else { Print("ONNX run error : ",GetLastError()); OnnxRelease(ExtHandle); } long predicted = output_data[0]; if(predicted == 1) { ExtPredictedClass = PRICE_UP; } else if(predicted == 0) { ExtPredictedClass = PRICE_DOWN; } }
Em seguida, podemos compilar nosso modelo e testá-lo em uma conta demo no terminal MetaTrader 5.
Fig. 14. Teste forward do Expert Advisor Glass-box ONNX.
Para garantir que o modelo esteja funcionando sem erros, precisamos verificar as abas Expert e Diário.
Fig. 15. Verificando erros na aba Expert
Fig. 16. Verificando erros na aba Diário
Como você pode ver, o modelo está funcionando. Lembre-se de que as configurações do Expert Advisor podem ser alteradas a qualquer momento.
Fig. 17. Configuração dos parâmetros do Expert Advisor
Problemas comuns
Vamos olhar para alguns erros que podem ocorrer durante a primeira configuração. Vamos analisar o que causa o erro e ver como esses problemas podem ser resolvidos.
Configuração incorreta de dados de entrada ou saída.
O problema mais comum ocorre devido à forma incorreta dos dados de entrada e saída. É necessário definir a forma de entrada para cada função que o modelo espera. Nesse caso, cada índice deve ser revisado para definir a forma de entrada para cada objeto nesse índice. Se não especificarmos a forma para cada objeto, o modelo ainda pode ser compilado sem erros. Mas ao tentar executar o modelo, receberemos um erro. O código de erro é 5808, descrito na documentação MQL5 como "A dimensão do tensor não é definida ou está incorretamente especificada". Neste exemplo, temos 4 parâmetros de entrada. Por exemplo, vamos definir a forma apenas para um.
Fig. 18. O Expert Advisor é compilado sem exceções
Aqui está como o erro aparece na aba Expert. Observo que o código correto está anexado ao artigo.
Fig. 19. Mensagem de erro 5808
Conversão incorreta de tipos
A conversão incorreta de tipos às vezes pode levar à perda total de dados ou simplesmente ao crash do Expert Advisor. No exemplo abaixo, um array de inteiros foi usado para armazenar os dados de saída do modelo ONNX. Lembre-se, originalmente o modelo ONNX tem saída do tipo int64. Por que você acha que isso levaria a um erro? O erro está relacionado ao fato de que o tipo int não tem memória suficiente para armazenar os dados de saída do nosso modelo. Para a saída do modelo, são necessários 8 bytes, mas um array int fornece apenas 4. A solução é simples: certifique-se de usar o tipo de dados correto para armazenar os dados de entrada e saída, e se for necessário fazer conversão de tipos, assegure-se de que tudo aconteça de acordo com as regras de conversão especificadas na Documentação MQL5. O código de erro é 5807, e a descrição é "Tamanho incorreto do parâmetro".
Fig. 20. Conversão incorreta de tipos
Fig. 21. Mensagem de erro 5807.
Erro na chamada ONNX Run
A função ONNX Run espera que cada uma das entradas do modelo seja passada em um array separado. No exemplo abaixo, combinamos todas as entradas em um único array e passamos esse único array para a função de execução ONNX. O código compila, mas ao executar, um erro é exibido na aba Experts. O código de erro é 5804, e na documentação, ele é brevemente descrito como "Número incorreto de parâmetros passados para OnnxRun".
Fig. 22. Erro na chamada da função ONNXRun
Fig. 23. Mensagem de erro 5804.
Considerações finais
Neste artigo, vimos quais podem ser os benefícios de usar um modelo de caixa de vidro na programação para mercados financeiros. Esse tipo de opção oferecem informações valiosas com pouco esforço em comparação com a quantidade de trabalho que seria necessário para extrair a mesma informação de um modelo de caixa preta. Além disso, modelos de caixa de vidro são mais fáceis de depurar, manter, interpretar e explicar. Não é suficiente supor que os modelos funcionem conforme o esperado. É necessário verificar isso, olhando “por baixo do capô”.
Modelos de caixa de vidro têm uma grande desvantagem que ainda não discutimos: eles são menos flexíveis em comparação com os modelos de caixa preta. Modelos de caixa de vidro representam uma área de pesquisa aberta, e com o tempo, certamente surgirão modelos mais flexíveis no futuro, mas, no momento da redação deste artigo, a questão da flexibilidade permanece relevante. Isso significa que existem certas relações que são melhor modeladas por um modelo de caixa preta. Além disso, as implementações atuais de modelos de caixa de vidro são baseadas em árvores de decisão, portanto, a implementação atual dos BoostingClassifiers explicados no InterpretML herda todas as desvantagens das árvores de decisão.
Até a próxima! Desejo-lhe paz, amor, harmonia e negociações lucrativas.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13842
- 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