Escribimos el primer modelo de caja de cristal (Glass Box) en Python y MQL5
Introducción
Los algoritmos de caja de cristal (o blanca) son algoritmos transparentes de aprendizaje automático que consideran los mecanismos internos del sistema. Estos desafían la idea convencional de que el aprendizaje automático se basa en un equilibrio entre la precisión de la predicción y la interpretabilidad. Para ello, estos modelos ofrecen un alto nivel de precisión y transparencia. Y esto significa que son exponencialmente más fáciles de depurar, mantener y mejorar después de otra iteración en comparación con la caja negra más común. Los modelos de caja negra se dan cuando un proceso excesivamente complejo de un sistema queda oculto al entorno externo. Estos modelos suelen representar relaciones multidimensionales y no lineales que los humanos no comprendemos con facilidad.
Por regla general, los modelos de caja negra solo deben utilizarse cuando una caja de cristal no puede proporcionar el mismo nivel de precisión. En este artículo, construiremos un modelo transparente y analizaremos los beneficios potenciales de su uso. Asimismo, consideraremos dos formas de trabajo con el terminal MetaTrader 5:
- El enfoque anticuado será el más sencillo posible. Simplemente conectaremos el modelo al terminal MetaTrader 5 usando la biblioteca Python incorporada en MetaTrader 5. Después de eso, escribiremos un asesor en MQL5 para utilizar este modelo.
- El enfoque actual es la forma recomendada de integrar los modelos de aprendizaje automático en el asesor. Exportaremos nuestro modelo de "caja de cristal" al formato ONNX (Open Neural Network Exchange) y luego lo cargaremos directamente en el asesor como recurso. Esto nos permitirá utilizar todas las funciones útiles disponibles en MetaTrader 5 y combinarlas con todas las características de nuestro modelo de caja de cristal.

Figura 1. Simulación del cerebro humano usando inteligencia artificial
Modelos de caja negra y caja de cristal
La mayoría de los modelos tradicionales de aprendizaje automático son difíciles de interpretar o explicar. Esta clase de modelos se conoce como modelos de caja negra. Los modelos de caja negra pueden incluir todos los modelos con un funcionamiento interno complejo y no fácilmente interpretable. Y esto plantea un grave problema, ya que será muy difícil mejorar de este modo las principales métricas de rendimiento del modelo. Los modelos de caja de cristal, por su parte, son un conjunto de modelos de aprendizaje automático cuyo funcionamiento interno resulta transparente y fácil de entender, sin dejar de ofrecer la misma precisión y fiabilidad en las predicciones.
Investigadores, desarrolladores y un equipo de expertos en la materia de Microsoft Research descubrieron el código fuente y, en el momento de escribir estas líneas, mantienen activamente un paquete Python llamado Interpret ML. El paquete incluye un juego de explicaciones sobre la caja negra y modelos de cajas de cristal. Los explicadores suponen un conjunto de algoritmos que intentan comprender el funcionamiento interno del modelo de caja negra. La mayoría de los algoritmos explicativos de Interpret ML son independientes del modelo, es decir, pueden aplicarse a cualquier modelo de caja negra, sin embargo, estos explicadores solo pueden ofrecer estimaciones de modelos de caja negra. En la siguiente sección del artículo veremos dónde radica el problema. Interpret ML también incluye un conjunto de modelos de "caja de cristal" que rivalizan con las cajas negras en cuanto a la precisión de la predicción, pero con transparencia. Este tipo de enfoque es ideal para cualquiera que use el aprendizaje automático, ya que el valor de la interpretabilidad del modelo es independiente del dominio de aplicación y del nivel de experiencia.
Más información:
1. Para más información, consulte la documentación en Interpret ML.
2. También el será de utilidad familiarizarse con Interpret ML White Paper.
En este artículo, usaremos Interpret ML para construir un modelo de caja de cristal en Python. Asimismo, veremos cómo un modelo de cristal puede proporcionar información importante para ayudar a guiar el proceso de desarrollo de las características y mejorar nuestra comprensión del funcionamiento interno de nuestro modelo.
El problema de los modelos de caja negra: el problema del desacuerdo
Una de las razones para abandonar el uso de modelos de caja negra es el "problema del desacuerdo". En pocas palabras: distintos métodos de explicación pueden producir explicaciones muy diferentes de los modelos, aunque estimen el mismo modelo. Estos métodos tratan de comprender la estructura subyacente del modelo de caja negra. Existen muchos enfoques diferentes, y cada método de explicación puede centrarse en diferentes aspectos del comportamiento del modelo, y por lo tanto cada uno puede derivar diferentes medidas del modelo de caja negra básica. El problema del desacuerdo es un campo de investigación abierto, tenemos que ser consciente del mismo y abordarlo de todas las formas posibles.
En este artículo, veremos una demostración real del problema del desacuerdo, por si aún no se ha topado con él.
Más información:
1. Le recomiendo leer el excelente artículo científico-investigativo de un equipo de antiguos alumnos de Harvard, MIT, Drexel y Carnegie Mellon.
Vamos a ver ahora cómo se manifiesta este problema del desacuerdo:
En primer lugar, importaremos los paquetes de Python que vamos a analizar.
#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
Después procederemos a conectarlos al terminal MetaTrader 5, pero antes necesitaremos especificar las credenciales de acceso.
#Enter your account number login = 123456789 #Enter your password password = '_enter_your_password_' #Enter your Broker's server server = 'Deriv-Demo'
Ahora podremos inicializar el terminal MetaTrader 5 e iniciar sesión en la cuenta comercial.
#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')
Autorización concedida.
Ahora dispondremos de acceso completo al terminal MetaTrader 5 y podremos solicitar los datos de gráficos, los datos de ticks, las cotizaciones actuales y mucho más.
#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
...
Una vez que hemos determinado qué símbolo modelar, podemos consultar los datos del gráfico para el símbolo, pero primero deberemos especificar el intervalo de fechas para el que se van a recuperar los datos.
#We need to specify the dates we want to use in our dataset date_from = datetime(2019,4,17) date_to = datetime.now()Ahora podremos consultar los datos del gráfico por símbolos.
#Fetching historical data
data = pd.DataFrame(mt5.copy_rates_range('Boom 1000 Index',mt5.TIMEFRAME_D1,date_from,date_to))
Necesitaremos formatear la columna de tiempo en nuestro marco de datos para el dibujado.
#Let's convert the time from seconds to year-month-date data['time'] = pd.to_datetime(data['time'],unit='s') data

Fig. 2. Nuestro marco de datos muestra ahora la hora en un formato legible. Tenga en cuenta que la columna real_volume se rellena con ceros.
Ahora vamos a crear una función auxiliar que nos ayudará a añadir nuevas características a nuestro marco de datos, calcular indicadores técnicos y limpiar los datos.
#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)
A continuación, llamaremos a la función de preprocesamiento.
preprocess(data) data

Fig. 3. Marco de datos después del preprocesamiento.
Lo siguiente que deberemos determinar es si el próximo precio de cierre será superior al precio de cierre de hoy. Lo escribiremos así: si el precio de cierre de mañana es superior al de hoy, target = 1. En caso contrario, 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. Creamos target
A continuación, definiremos explícitamente el objetivo y los predictores. Después dividiremos los datos en una muestra de entrenamiento y otra de prueba. Tenga en cuenta que se trata de datos de series temporales, por lo que no podremos clasificarlos aleatoriamente en 2 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
Luego crearemos las muestras entrenamiento y prueba.
#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]
Ahora podremos entrenar el modelo.
#Let us fit our model
black_box = XGBClassifier()
black_box.fit(train_x,train_y)
Luego validaremos las predicciones de nuestro modelo en el conjunto de prueba.
#Let's see our model predictions black_box_predictions = pd.DataFrame(black_box.predict(test_x),index=test_x.index)
Ahora evaluaremos la precisión de nuestro modelo.
#Assesing model prediction accuracy black_box_score = precision_score(test_y,black_box_predictions) #Model precision score black_box_score
0.4594594594594595
Nuestro modelo tiene una precisión del 45%. ¿Qué características favorecen la precisión y cuáles no? XGBoost incorpora una función para medir la importancia de las características, lo cual nos facilita la vida. No obstante, esto se aplicará específicamente a esta implementación de XGBoost. No todas las cajas negras facilitan la evaluación de la importancia de las características. Por ejemplo, las redes neuronales y las máquinas de vectores de soporte no tienen una función equivalente, y tendremos que analizar e interpretar nosotros mismos con todo cuidado los pesos del modelo para entenderlo mejor. La función plot_importance de XGBoost nos permite mirar dentro de nuestro modelo.
plot_importance(black_box)

Fig. 5. Importancia de los características en XGBClassifier. Observe que no hay términos de interacción en la tabla. ¿Significa eso que no existen? No necesariamente.
Veamos ahora la primera técnica de explicación de la caja negra llamada Permutation importance (Importancia de la permutación). Intenta estimar la importancia de cada característica barajando aleatoriamente los valores de cada una de ellas y midiendo después el cambio en la función de pérdida del modelo. La explicación es la siguiente: cuanto más dependa nuestro modelo de este atributo, peor será su rendimiento si barajamos aleatoriamente estos valores. Veamos las ventajas e inconvenientes de la importancia de la permutación.
Ventajas
- Independencia del modelo: la importancia de permutación puede usarse en cualquier modelo de caja negra sin necesidad de preprocesar el modelo o la función de importancia de permutación, lo cual facilita su integración en un flujo de trabajo de aprendizaje automático existente.
- Interpretabilidad: los resultados son fáciles de interpretar de forma coherente, con independencia del modelo subyacente que estemos estimando. Es muy fácil de usar.
- Hacer frente a la no linealidad: resulta adecuado para captar las relaciones no lineales entre los predictores y la respuesta.
- Trabaja con valores atípicos: la importancia de la permutación es independiente de los valores brutos de los predictores; se refiere al efecto de las características en el rendimiento del modelo. Este enfoque lo hace resistente a los valores atípicos que puedan encontrarse en los datos brutos.
Desventajas
- Coste computacional: en el caso de grandes conjuntos de datos con muchas características, calcular la importancia de las permutaciones puede resultar costoso desde el punto de vista computacional, ya que implica pasar por cada característica, reordenarla y estimar el modelo, para luego pasar a la siguiente característica y repetir el proceso.
- Problema de las características correlacionadas: puede dar resultados sesgados cuando se estiman características muy correlacionadas.
- Sensibilidad a la complejidad del modelo: es posible que un modelo demasiado complejo presente una elevada varianza cuando sus características se permutan, lo cual dificultaría la obtención de conclusiones fiables.
- Independencia de las características: el método parte de la base de que los objetos del conjunto de datos son independientes y pueden reordenarse aleatoriamente sin consecuencias. Esto simplificará los cálculos, pero en el mundo real, la mayoría de las características dependen unas de otras y tienen interacciones que no se tienen en cuenta con el método de importancia de permutación.
Ahora calcularemos la importancia de permutación para nuestro clasificador de caja negra.
#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()
A continuación, dibujaremos los valores de importancia de permutación calculados.
#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. Importancia de permutación de nuestra caja negra.
Según los cálculos realizados por el algoritmo de importancia de permutación, las lecturas ATR son la característica más informativa. Pero sabemos que no es cierto: El ATR ha ocupado el sexto lugar. Una característica importante es el aumento de ATR. La segunda más importante es la altura, pero la importancia de la permutación ha calculado que el aumento del ATR es más importante. El tercer signo más importante ha sido el valor RSI, pero el algoritmo ha calculado que la altura ha sido más importante.
Este es el problema de los métodos de explicación de caja negra: ofrecen muy buenas estimaciones de la importancia de las características, pero tienden a equivocarse porque, en el mejor de los casos, son solo estimaciones. Además, pueden discrepar entre sí al evaluar el mismo modelo. Vamos a verlo por nosotros mismos.
Utilizaremos el algoritmo de información mutua como segundo método de explicación. La información mutua mide la reducción de la incertidumbre causada por la detección del valor de una 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 podemos ver, tenemos clasificaciones de importancia completamente diferentes. La información mutua clasifica las características casi en orden inverso, si comparamos con nuestra base y el cálculo de la permutación. Si no tuviéramos una base con valores verdaderos, como en este ejemplo, ¿en qué explicación confiaríamos más? ¿Qué pasaría si utilizáramos 5 métodos de explicación diferentes y cada uno diera estimaciones distintas? Si elegimos clasificaciones que coincidan con nuestras creencias sobre cómo funciona el mundo real, se abriría la puerta a otro problema llamado desplazamiento de la confirmación. El desplazamiento de confirmación se produce cuando se ignora cualquier prueba que contradiga las creencias existentes y se busca activamente confirmar lo que se cree que es cierto, ¡aunque no lo sea!
Ventajas de los modelos con caja de cristal
Los modelos de caja de cristal sustituyen perfectamente la necesidad de métodos de explicación de caja negra porque resultan completamente transparentes y comprensibles. Podrían resolver desacuerdos en muchos ámbitos, incluido el financiero. Depurar un modelo de caja de cristal es exponencialmente más fácil que depurar una caja negra con el mismo nivel de flexibilidad. Asimismo, ahorra el recurso más importante de todos: ¡el tiempo! Y lo mejor es que no compromete la precisión del modelo, ofreciendo lo mejor de ambos mundos. En general, las cajas negras solo deben utilizarse cuando una caja de cristal no pueda ofrecer el mismo nivel de precisión.
Vamos ahora a construir nuestro primer modelo de caja de cristal. Para ello, analizaremos sus características e intentaremos mejorar su precisión. Luego veremos cómo conectar nuestro modelo al terminal MetaTrader 5 y empezar a negociar con modelos blancos. Después, crearemos un asesor basado en el modelo blanco en MQL5. Por último, exportaremos nuestro modelo de caja de cristal al formato ONNX para liberar todo el potencial de MetaTrader 5 y de nuestro modelo.
Creamos el primer modelo de caja de cristal en Python
Para que el código sea fácil de leer, crearemos nuestra caja de cristal en un script Python separado del que utilizaremos para construir el modelo de caja negra. No obstante, la mayoría de las cosas seguirán siendo las mismas, como el inicio de sesión, la recuperación de datos, etc., y el preprocesamiento de datos. Por ello, no los repetiremos, sino que nos centraremos en los pasos exclusivos del modelo glass-box.
En primer lugar, deberemos instalar Interpret ML.
#Installing Interpret ML pip install --upgrade interpret
A continuación, cargaremos nuestras dependencias. En este artículo nos centraremos en tres módulos del paquete de interpretación. El primero será el propio modelo de caja de cristal, el segundo será un módulo que nos permitirá mirar dentro del modelo y presentar esta información en un panel interactivo con una interfaz gráfica, mientras que el último paquete nos permitirá visualizar el rendimiento de nuestro modelo en un único gráfico. De los otros paquetes ya hemos hablado.
#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
Ahora crearemos los datos de acceso e iniciaremos sesión en el terminal MT5 como antes. No nos detendremos en este paso.
Seleccionaremos nuevamente el símbolo que deseamos modelar, como antes. No nos detendremos en este paso.
A continuación especificaremos el intervalo de fechas de los datos que se van a modelar, como hicimos antes. No nos detendremos en este paso.
Entonces podremos obtener los datos históricos. No nos detendremos en este paso.
A continuación, seguiremos los mismos pasos de preprocesamiento descritos anteriormente. No nos detendremos en este paso.
Tras preprocesar los datos, añadiremos el objetivo como antes. No nos detendremos en este paso.
Después, dividiremos los datos en datos de entrenamiento y datos de prueba. No nos detendremos en este paso. La división de los datos en muestras de entrenamiento y de prueba no deberá ser aleatoria. Resulta importante mantener un orden natural del tiempo, de lo contrario los resultados se estropearán y puede que tengamos una imagen demasiado optimista de los resultados futuros.
Luego entrenaremos el 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)
Ahora podemos mirar dentro de nuestro modelo de cristal.
#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. Estado global de Glass Box
La interpretación de las estadísticas resumidas es muy importante. Pero antes, empezaremos con algunos conceptos importantes. El "estado global" resume el estado de todo el modelo. Esto nos permitirá comprender qué características considera informativas el modelo. No debemos confundirlo con un estado localizado. Los estados locales se usan para explicar las predicciones individuales del modelo, ayudando a entender por qué el modelo ha realizado tal predicción y qué características han influido en las predicciones individuales.
Volvamos al estado global de nuestro modelo. Como podemos ver, el modelo ha encontrado que el valor del punto medio desplazado era muy informativo, lo que esperábamos. No solo eso, también ha hallado un posible factor de interacción entre el crecimiento de ATR y el valor de mid_point. La altura ha sido la tercera característica más importante, seguida del factor de interacción entre el precio de cierre y la altura (la distancia entre el punto medio y el cierre). Obsérvese que no necesitamos ninguna herramienta adicional para entender el modelo de caja blanca. De este modo, evitaremos por completo el problema del desacuerdo y el desplazamiento de confirmación. La información sobre el estado global tiene un valor incalculable en términos de desarrollo de características, ya que muestra hacia dónde pueden dirigirse los esfuerzos futuros para desarrollar mejores características. Ahora vamos a ver cómo funciona nuestra caja blanca.
Obtenemos las previsiones de la caja blanca
#Obtaining glass-box predictions
glass_box_predictions = pd.DataFrame(glass_box.predict(test_x))
Y medimos la precisión del modelo.
glass_box_score = precision_score(test_y,glass_box_predictions) glass_box_score
0.49095022624434387
Así, el modelo de caja blanca ha mostrado una precisión del 49%. Obviamente, el modelo EBC (Explainable Boosting Classifier) puede tener más peso en comparación con el XGBClassifier. Esto demuestra el poder de las cajas blancas para ofrecer una gran precisión sin comprometer la comprensión.
También es posible obtener explicaciones separadas para cada previsión a partir de nuestro modelo de caja blanca para comprender qué características han afectado a la previsión a un nivel detallado; son las llamadas explicaciones locales. Son bastante fáciles de conseguir.
#We can also obtain individual explanations for each prediction show(glass_box.explain_local(test_x,test_y))

Fig. 8. Explicaciones locales del clasificador EBC (Explainable Boosting Classifier).
El primer menú desplegable nos permite recorrer todas las predicciones y elegir la que queramos entender mejor.
A continuación analizaremos la clase real y la clase proyectada. En este caso, la calificación real ha sido 0, lo cual significa que el precio de cierre ha descendido, pero lo clasificaremos como 1. También se nos presentan las probabilidades estimadas de cada clase. Nuestro modelo ha estimado incorrectamente una probabilidad del 53% de que la próxima vela cierre al alza. También se desglosa la contribución de cada característica a la probabilidad estimada. Las características resaltadas en azul van en contra de la predicción realizada por nuestro modelo, mientras que las características de color naranja han sido la base de la predicción. Resulta que RSI ha contribuido en mayor medida a esta clasificación errónea, con la interacción entre dispersión y altura apuntando en la dirección correcta. Estas características pueden merecer una mayor elaboración, pero será necesario un examen más minucioso de las explicaciones localizadas antes de poder extraer conclusiones.
Ahora vamos a comprobar el rendimiento del modelo usando el gráfico de la curva ROC. El gráfico ROC nos permite estimar el rendimiento de nuestro clasificador. Nos interesa el área bajo la curva o AUC. En teoría, un clasificador ideal deberá tener un área total bajo la curva igual a 1. Esto permitirá evaluar el clasificador con un solo 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 de nuestro modelo de caja de cristal.
En el modelo, AUC = 0,49. Esta sencilla métrica nos permite estimar el rendimiento del modelo utilizando unidades fáciles de entender. Además, la curva es independiente del modelo y puede usarse para comparar distintos clasificadores independientemente de los métodos de clasificación subyacentes.
Conexión del modelo de caja de cristal al terminal MT5
Vamos al grano: ahora conectaremos nuestro modelo al terminal, utilizando primero un enfoque más sencillo.
En primer lugar, determinaremos el estado de la cuenta corriente.
#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
Obtenemos todos los símbolos.
symbols = mt5.symbols_get()
Configuramos las variables globales.
#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
No especificaremos los volúmenes comerciales en el código. Obtendremos dinámicamente el volumen mínimo permitido del bróker y luego lo multiplicaremos por algún coeficiente para garantizar el envío de órdenes válidas. El tamaño de la orden se fijará según el volumen mínimo.
En nuestro caso, abriremos cada transacción con el volumen mínimo o con un 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
El Boom 1000 Index tiene un volumen mínimo de 0,2.
Ahora vamos a definir una función auxiliar para abrir operaciones.
# 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
Luego definiremos una función auxiliar para cerrar operaciones basadas en un 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 evitar solicitar constantemente muchos datos al servidor, actualizaremos el intervalo de fechas.
#Update our date from and date to date_from = datetime(2023,11,1) date_to = datetime.now()
También necesitaremos una función para obtener la previsión del modelo Glass Box y utilizar la previsión como señales comerciales.
#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]
Ahora vamos a escribir la parte principal del robot comercial en 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)
Predicción de IA: Sell
Hora: 2023-12-04 15:31:31.569495
-------

Fig. 10. El asesor comercial basado en la caja de cristal en Python muestra beneficios
Creación de un asesor para trabajar con el modelo
Vamos a crear un ayudante MQL5 para nuestro modelo de caja de cristal. Crearemos un asesor que desplazará el stop loss y take profit basado en las lecturas de ATR. El siguiente código actualizará los valores TP y SL en cada tick. Realizar esta tarea usando el módulo de integración de Python sería una pesadilla y requeriría actualizaciones más o menos frecuentes, como una vez por minuto o por hora. Y nosotros necesitaremos actualizar SL y TP en cada tick. El usuario deberá especificar la distancia entre la entrada y el nivel SL/TP. Multiplicaremos la lectura de ATR por la entrada del usuario para determinar la distancia desde el SL o TP hasta el punto de entrada. El segundo parámetro que el usuario deberá especificar es el periodo 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. Asesor-ayudante para nuestro modelo de "caja de cristal".
Exportación del modelo de caja de cristal al formato Open Neural Network Exchange (ONNX)

Fig. 12. Logotipo de Open Neural Network Exchange.
ONNX (Open Neural Network Exchange) es un estándar abierto para representar modelos de redes neuronales. Cuenta con un amplio apoyo gracias al esfuerzo colectivo de compañías de todo el mundo y de todos los sectores. Se trata de empresas como Microsoft, Facebook, MATLAB, IBM, Qualcomm, Huawei, Intel, AMD, etcétera. Al momento de escribir estas líneas, ONNX es la forma estándar universal para representar cualquier modelo de aprendizaje automático con independencia del entorno en el que se haya desarrollado y, además, permite desarrollar e implantar modelos de aprendizaje automático en distintos lenguajes de programación y entornos. La idea básica es que cualquier modelo de aprendizaje automático pueda representarse como un grafo de nodos y aristas. Cada nodo representa una operación matemática, mientras que cada arista representa un flujo de datos. Usando esta sencilla representación, podremos representar cualquier modelo de aprendizaje automático independientemente del entorno en el que se cree.
Además, necesitaremos un mecanismo que ejecute modelos ONNX. Esto será responsabilidad del entorno de ejecución de ONNX. El entorno de ejecución de ONNX se encarga de ejecutar y desplegar de forma eficiente los modelos de ONNX en una gran variedad de dispositivos, desde un superordenador de un centro de datos hasta el teléfono móvil de nuestro bolsillo, pasando por todos los dispositivos intermedios.
En nuestro caso, ONNX nos permite integrar un modelo de aprendizaje automático en nuestro asesor y, esencialmente, crear un asesor con su propio "cerebro". El terminal MetaTrader 5 ofrece un conjunto completo de herramientas para probar de forma segura y fiable los robots con datos históricos. Además, ofrece la oportunidad de probarlos en un periodo forward. La prueba forward supone la ejecución de un asesor en tiempo real o durante cualquier periodo anterior a la última fecha de entrenamiento vista por el modelo. Esta es la mejor prueba de la solidez de un modelo cuando se enfrenta a datos que no ha visto antes en el entrenamiento.
Como antes, separaremos el código usado para exportar el modelo ONNX del resto del código que hemos utilizado hasta ahora en este artículo, así será más fácil de leer. Además, reduciremos el número de parámetros que el modelo requiere como parámetros de entrada para simplificar su aplicación práctica. Solo utilizaremos estos características para alimentar el modelo ONNX:
1. Lag height es la altura con desplazamiento; la altura se define como: (((High + Low) / 2) – Close), mientras que lag height es el anterior valor de altura.
2. Height growth — valoración de la segunda derivada de las lecturas de altura. Para el cálculo, se tomará dos veces la diferencia entre los valores históricos sucesivos de altura. El valor obtenido dará una indicación de la tasa de cambio. En pocas palabras, nos ayudará a comprender si el crecimiento de la estatura se está acelerando o desacelerando con el paso del tiempo.
3. Midpoint — se calcula como ((High + Low) / 2)
4. Midpoint growth — crecimiento del punto medio, es un signo derivado de la lectura del punto medio. La diferencia entre los sucesivos valores históricos del punto medio se tomará dos veces para el cálculo. El valor obtenido dará una indicación de la velocidad de cambio del punto medio. Indicará si el crecimiento del punto medio se está acelerando o desacelerando a lo largo del tiempo. En términos más sencillos y menos técnicos, el valor nos ayudará a comprender si el punto medio se aleja de cero a un ritmo creciente o se acerca a cero a un ritmo que aumenta cada vez más rápido.
Además, hemos cambiado los símbolos: en la primera parte del artículo modelábamos el símbolo Boom 1000 Index, y ahora modelaremos el Volatility 75 Index.
El asesor también colocará automáticamente los niveles SL/TP de forma dinámica usando las lecturas de ATR. Además, dejará que añadamos automáticamente otra posición en cuanto el beneficio supere un determinado umbral.
La mayor parte seguirá siendo la misma, salvo dos nuevas incorporaciones: ONNX y ebm2onnx. Estos dos paquetes nos permitirán convertir nuestra EBM (máquina explicativa de aumento) al 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
Más abajo repetiremos los pasos anteriores para conectarnos y recuperar los datos. Además, añadiremos pasos para preparar 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)
Tras la recogida de datos, seguiremos exactamente los mismos pasos para dividir los datos en muestras de entrenamiento y de prueba y para ajustar el modelo.
Después de los ajustes, estaremos listos para pasar a exportar el modelo al formato ONNX.
Primero deberemos especificar la ruta donde guardaremos el modelo. Cada vez que se instala MetaTrader 5, se creará una carpeta especial para los archivos que se pueden utilizar en el terminal. Podrá obtener la ruta absoluta muy fácilmente usando la librería Python.
terminal_info=mt5.terminal_info() print(terminal_info)
La ruta buscada se almacenará como ruta de datos en el objeto terminal_info.
file_path=terminal_info.data_path+"\\MQL5\\Files\\"
print(file_path)
C:\Users\Westwood\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files\
Lo siguiente será preparar la ruta. El código tomará la ruta de archivo obtenida del terminal y aislará el directorio de la ruta, excluyendo cualquier nombre de archivo.
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\
Utilizaremos el paquete ebm2onnx para preparar nuestro modelo de cristal y convertirlo al formato ONNX. Tenga en cuenta que deberemos especificar explícitamente los tipos de datos de cada uno de los parámetros de entrada. Será mejor hacerlo dinámicamente utilizando la función ebm2onnx.get_dtype_from_pandas. Para ello, le transmitiremos el marco de datos de entrenamiento que hemos utilizado antes.
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)
Ahora estaremos listos para trabajar con el archivo ONNX en MetaEditor 5. El MetaEditor es un entorno de desarrollo integrado en MQL5.
Abriremos el MetaEditor, clicaremos dos veces en Volatility Doctor 75 EBM y veremos:

Fig. 13: Entradas y salidas de nuestro modelo ONNX.
Ahora vamos a crear un asesor e importar nuestro modelo ONNX.
Empezaremos dando información general sobre el archivo.
//+------------------------------------------------------------------+ //| 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"
Especificaremos las variables globales.
//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
Indicaremos la función OnInit(). Luego utilizaremos OnInit para configurar nuestro modelo ONNX. Existen 3 sencillos pasos para configurar el modelo ONNX. En primer lugar, crearemos el modelo ONNX a partir del búfer que hemos utilizado en las variables globales anteriores cuando necesitábamos el modelo ONNX como recurso. Luego leeremos el modelo, y especificaremos la forma de cada parámetro individual de entrada y salida. A continuación, comprobaremos si se produce algún error al intentar instalar el formulario. Si todo va bien, obtendremos el volumen mínimo de contrato permitido por el bróker, la distancia mínima entre el stop loss y la posición de entrada, y ajustaremos el 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); //--- }
La función DeInit eliminará el manejador ONNX para no ocupar recursos que no utilizamos.
void OnDeinit(const int reason) { //--- if(ExtHandle != INVALID_HANDLE) { OnnxRelease(ExtHandle); ExtHandle = INVALID_HANDLE; } }
La función OnTick será el corazón del asesor, y será llamada cada vez que un nuevo tick llegue del bróker. En nuestro caso, empezaremos con el control del tiempo. Esto nos permitirá separar los procesos realizados en cada tick de los que deben realizarse cuando se forma una nueva vela. En cada tick, deberemos actualizar los precios de compra y venta, así como las posiciones de take profit y stop loss en cada tick. Pero el pronóstico del modelo deberá hacerse solo después de la formación de una nueva vela, si no hay posiciones abiertas.
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(); } } }A continuación, definiremos una función que actualizará la posición del take profit y el stop loss por ATR. La función iterará cada posición abierta y comprueba si la posición coincide con el símbolo negociado. Si coincide, la función obtendrá información adicional sobre la posición, y basándose en ella, corregirá el Stop Loss y el Take Profit dependiendo de la dirección de la posición. Nota: si la transacción se mueve en contra de la posición, se mantendrán el take profit y el stop loss.
//--- 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); } } } } }Necesitaremos otra función para abrir una nueva posición. Tenga en cuenta que estamos usando variables globales para los valores Bid y Ask. Así se garantizará que todo el programa utilice el mismo precio. Además, estableceremos el stop loss y el take profit en 0, ya que sus valores serán gestionados por la función 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"); } }
Por último, necesitaremos una función para realizar predicciones utilizando nuestro modelo ONNX dentro de nuestro asesor. La función también se encargará de preprocesar los datos del mismo modo que durante el entrenamiento. Resulta esencial velar por que los datos se procesen de forma coherente durante el entrenamiento y la explotación. Tenga en cuenta que cada entrada del modelo se almacenará en su propio vector, y que cada vector se transmitirá a la función de ejecución ONNX en el mismo orden en el que se pasaron al modelo durante el entrenamiento. Debemos necesariamente considerar esto a lo largo de todo el proyecto, ya que de lo contrario pueden producirse errores durante el funcionamiento, que en este caso podrían no generar excepciones al compilar el modelo. El tipo de datos de cada vector de entrada deberá coincidir con el tipo esperado por el modelo, y el tipo de datos de salida también deberá coincidir con el tipo de datos de salida del 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; } }
A continuación, podremos compilar nuestro modelo y probarlo en una cuenta demo en el terminal MetaTrader 5.

Fig. 14. Prueba del asesor Glass-box ONNX.
Para asegurarnos de que el modelo se ejecuta sin errores, deberemos comprobar las pestañas Expertos y Registro.

Fig. 15. Comprobación de errores en la pestaña Expertos

Fig. 16. Comprobación de errores en la pestaña Registro
Como podemos ver, el modelo funciona. Recuerde que podrá cambiar la configuración del asesor en cualquier momento.

Fig. 17. Ajuste de los parámetros del asesor
Problemas frecuentes
Echemos un vistazo a algunos de los errores que podremos encontrar al configurarlo por primera vez. Vamos a analizar las causas del error y veamos cómo solucionar estos problemas.
Ajuste incorrecto de los datos de entrada o salida.
El problema más frecuente se debe a que las entradas y salidas no tienen la forma adecuada. Tendremos que definir un formulario de entrada para cada función que espera el modelo. Asimismo, deberemos iterar cada índice y determinar el formulario de entrada para cada objeto en ese índice. Si no especificamos un formulario para cada objeto, el modelo podrá compilarse sin errores de todas formas, pero obtendremos un error al intentar ejecutar el modelo. El código de error será 5808: se describe en la documentación de MQL5 como "La dimensionalidad del tensor no ha sido establecida o se ha indicado incorrectamente". En este ejemplo, tenemos 4 parámetros de entrada. Por el bien del ejemplo, estableceremos el formulario para uno solo.

Fig. 18. El asesor compila sin excepciones
Este será el aspecto del error en la pestaña Expertos. Aclaremos enseguida que el código correcto está adjunto al artículo.

Fig. 19. Mensaje de error 5808
Conversión de tipo incorrecta
Una conversión de tipo incorrecta a veces provoca la pérdida completa de datos o simplemente el bloqueo del asesor. En el ejemplo siguiente hemos utilizado un array entero para almacenar los datos de salida del modelo ONNX. Permítame recordarle que inicialmente el modelo ONNX tiene datos de salida de tipo int64. ¿Qué piensa usted, por qué cree que esto daría lugar a un error? El error se deberá a que el tipo int no tiene memoria suficiente para almacenar los datos de salida de nuestro modelo. La salida del modelo requiere 8 bytes, pero el array int solo proporciona 4. La solución es simple: deberemos asegurarnos de utilizar el tipo de datos correcto para almacenar los datos de entrada y salida, y si es necesaria la conversión de tipos, nos aseguraremos de que todo suceda de acuerdo con las reglas de conversión especificadas en la Documentación MQL5. El código de error es 5807 y la descripción será "Tamaño de parámetro incorrecto".

Fig. 20. Conversión de tipo incorrecta

Fig. 21. Mensaje de error 5807.
Error al llamar a ONNX Run
La función ONNX Run espera que cada una de las entradas del modelo se transmita en un array aparte. En el siguiente ejemplo, combinaremos todos los datos de entrada en un único array y pasamos este array único a la función de inicio de ONNX. El código se compilará, pero se generará un error al ejecutarlo en la pestaña Expertos. El código de error es 5804, y se resume en la documentación como "El número de parámetros transmitidos a OnnxRun es incorrecto".

Fig. 22. Error de llamada a ONNX Run

Fig. 23. Mensaje de error 5804.
Conclusión
En este artículo hemos visto las ventajas de usar el modelo de caja de cristal para programar los mercados financieros. Estas opciones ofrecerán información valiosa con pocos gastos en comparación con la cantidad de esfuerzo que sería necesario para extraer con precisión la misma información de un modelo de caja negra. Además, los modelos de caja de cristal son más fáciles de depurar, mantener, interpretar y explicar. No bastará con suponer que los modelos se comportan como se espera: debemos asegurarnos de ello mirando bajo el capó.
Los modelos de caja de cristal tienen un gran inconveniente del que aún no hemos hablado: resultan menos flexibles que los modelos de caja negra. Los modelos de caja de cristal son un campo de investigación abierto y, con el tiempo, seguramente habrá modelos más flexibles; sin embargo, en el momento presente, el problema de la flexibilidad persiste, lo cual significa que habrá relaciones que se modelan mejor con el modelo de caja negra. Además, las implementaciones actuales de los modelos de caja de cristal se basan en árboles de decisión, por lo que la implementación actual de las clases BoostingClassifiers explicadas en InterpretML heredará todos los inconvenientes de los árboles de decisión.
¡Hasta la vista! Les deseo paz, amor, armonía y transacciones rentables.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13842
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Patrones de diseño en MQL5 (Parte 3): Patrones conductuales 1
Interpretación de modelos: Una comprensión más profunda de los modelos de aprendizaje automático
Marcado de datos en el análisis de series temporales (Parte 4): Descomposición de la interpretabilidad usando el marcado de datos
Características del Wizard MQL5 que debe conocer (Parte 08): Perceptrones
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
ValueError: se requiere al menos un array o dtype
probado con NZDCNH parece funcionar a través de algunas iteraciones, pero falla en el sklern\multiclass en la línea 167 con un datahandling
debug me dice valueerror en la línea 204 una matriz o dtype es necesario - en puede ser que necesito para comprobar mi entorno de demostración como yo sólo lo creó hoy :)
en el Boom1000 Index por defecto el problema es la línea 100 con la fecha y la hora. raise KeyError(key)
KeyError: 'time' . Posiblemente un problema ya que mi zona horaria es Nueva Zelanda
Hoy no tengo tiempo para hacer pruebas, volveré a intentarlo mañana.Hola Linfo, espero que esto te ayude:
1) La columna 'time' era el nombre que mi broker le daba a una marca de tiempo UNIX que marca cada una de las filas de los datos que he obtenido. Tal vez tu broker utiliza un nombre diferente en su lugar, como 'date' es común. Comprueba el marco de datos que obtienes después de llamar a copy_rates_range. El hecho de que obtengas un "KeyError" puede significar que el marco de datos está totalmente vacío o que no hay ninguna columna con el nombre "time", probablemente tenga un nombre diferente.
2) Valida la salida de copy_rates_range, por lo que has descrito creo que es ahí donde las cosas pueden estar fallando. Comprueba los nombres de columna de los datos que se te devuelven después de realizar la llamada.
Si estos pasos no funcionan, házmelo saber.
Gracias por los rápidos comentarios y consejos.
Actualizar aquí, ya que puede ser útil a los demás . Mis problemas ;
1) He creado una nueva cuenta de demostración para probar esto y no todas las monedas donde disponible para Esto se resuelve mediante la apertura de la cuenta y asegurar las monedas que desea están activos (de color oro)
2) No había Boom1000 Índice (datos) que me proporciona el servidor, que estaba en la lista, pero no en contra de mi cuenta (asegúrese de cambiar el valor predeterminado a ser algo que usted tiene acceso y que puede dar un resultado).
3) En mi caso los resultados de la interpretación no se mostraban en std python , sólo pude conseguir que funcionara con anaconda instalado (Hubiera sido más fácil si lo hubiera instalado primero).
Después de este contratiempo la documentación era clara y útil, todavía estoy digiriendo los resultados hasta el momento por lo que aún no han pasado a la parte mql5
Gracias de nuevo por publicar y espero entender mejor el proceso. Saludos Neil
2) No había Boom1000 Índice (datos) que me proporciona el servidor, que estaba en la lista, pero no en contra de mi cuenta (asegúrese de cambiar el valor predeterminado a ser algo que usted tiene acceso y que puede dar un resultado).
3) En mi caso, los resultados de la interpretación no se mostraban en std python, sólo pude conseguir que funcionara con anaconda instalado (habría sido más fácil si lo hubiera instalado primero).
Después de este contratiempo la documentación era clara y útil, todavía estoy digiriendo los resultados hasta el momento por lo que aún no se han trasladado a la parte mql5
Gracias de nuevo por publicar y espero entender mejor el proceso. Saludos Neil
Me alegra ver que estás haciendo progresos materiales Neil.
Sorprendentemente: la frase más importante para entender el material está al final del artículo:
текущие реализации моделей стеклянного ящика основаны на деревьях решений
Sorprendentemente: la frase más importante para entender el material está al final del artículo: