English Русский 中文 Deutsch 日本語 Português
preview
Escribimos el primer modelo de caja de cristal (Glass Box) en Python y MQL5

Escribimos el primer modelo de caja de cristal (Glass Box) en Python y MQL5

MetaTrader 5Sistemas comerciales |
463 8
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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:

  1. 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.
  2. 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.

AI

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

Marco de datos después de transformar la hora

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

Marco de datos después del preprocesamiento

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.


Creamos Target

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)

Importancia de los características en XGBoost

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

  1. 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. 
  2. 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.
  3. Hacer frente a la no linealidad: resulta adecuado para captar las relaciones no lineales entre los predictores y la respuesta. 
  4. 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

  1. 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.
  2. Problema de las características correlacionadas: puede dar resultados sesgados cuando se estiman características muy correlacionadas.
  3. 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.
  4. 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()

Importancia de permutación

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())


Estado global de la caja de cristal

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))

Explicaciones locales

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)

Gráfico ROC

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

-------

Asesor de prueba basado en Glass Box

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);
                                 }
                           }  
                  }  
            }
}

Asesor-ayudante para nuestra "caja de cristal".

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)


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)
TerminalInfo(community_account=False, community_connection=False, connected=True, dlls_allowed=False, trade_allowed=True, tradeapi_disabled=False, email_enabled=False, ftp_enabled=False, notifications_enabled=False, mqid=True, build=4094, maxbars=100000, codepage=0, ping_last=222088, community_balance=0.0, retransmission=0.030435223698894183, company='MetaQuotes Software Corp.', name='MetaTrader 5', language='English', path='C:\\Program Files\\MetaTrader 5', data_path='C:\\Users\\Westwood\\AppData\\Roaming\\MetaQuotes\\Terminal\\D0E8209F77C8CF37AD8BF550E51FF075', commondata_path='C:\\Users\\Westwood\\AppData\\Roaming\\MetaQuotes\\Terminal\\Common')

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:

Primera apertura de nuestro modelo ONNX

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.

Pruebas del modelo ONNX

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.

Comprobación de errores

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

Comprobación de errores en la pestaña Registro

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.

Ajuste de los parámetros del modelo

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. 

Ajuste incorrecto del formulario de datos de entrada

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.

Mensaje de error 5808

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".

Error de conversión de tipo

Fig. 20. Conversión de tipo incorrecta

Mensaje de error 5808

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".

Error de llamada a ONNX Run

Fig. 22. Error de llamada a ONNX Run

Mensaje de error 5804

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

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 12 ene 2024 en 12:04
linfo2 demo account ) tried one that was there AUDHKD but stuck in disagreement problem line 204 ,
al menos una matriz o dtype se requiere

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.

linfo2
linfo2 | 15 ene 2024 en 04:13

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

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 15 ene 2024 en 19:45
linfo2 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 (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.

Stanislav Korotky
Stanislav Korotky | 4 abr 2024 en 15:11

Sorprendentemente: la frase más importante para entender el material está al final del artículo:

текущие реализации моделей стеклянного ящика основаны на деревьях решений

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 4 abr 2024 en 15:21
Stanislav Korotky # :

Sorprendentemente: la frase más importante para entender el material está al final del artículo:

Sí, tienes razón, mirando hacia atrás esa información debería haberse tratado en la introducción o en la sinopsis, tus comentarios se aplicarán en el futuro.
Patrones de diseño en MQL5 (Parte 3): Patrones conductuales 1 Patrones de diseño en MQL5 (Parte 3): Patrones conductuales 1
En el nuevo artículo de la serie sobre patrones de diseño, nos ocuparemos de los patrones conductuales para comprender cómo crear de forma eficaz métodos de interacción entre los objetos creados. Diseñando estos patrones conductuales, podremos entender cómo construir software reutilizable, extensible y comprobable.
Interpretación de modelos: Una comprensión más profunda de los modelos de aprendizaje automático Interpretación de modelos: Una comprensión más profunda de los modelos de aprendizaje automático
El aprendizaje automático es un campo desafiante y gratificante para cualquiera, independientemente de la experiencia que tenga. En este artículo, nos sumergiremos en el funcionamiento interno de los modelos creados, exploraremos el complejo mundo de las funciones, las predicciones y las soluciones eficientes, y comprenderemos claramente la interpretación de los modelos. Asimismo, aprenderemos el arte de hacer concesiones, mejorar las predicciones, clasificar la importancia de los parámetros y tomar decisiones sólidas. Este artículo le ayudará a mejorar el rendimiento de los modelos de aprendizaje automático y a sacar más partido de sus metodologías.
Marcado de datos en el análisis de series temporales (Parte 4): Descomposición de la interpretabilidad usando el marcado de datos Marcado de datos en el análisis de series temporales (Parte 4): Descomposición de la interpretabilidad usando el marcado de datos
En esta serie de artículos, presentaremos varias técnicas de marcado de series temporales que pueden producir datos que se ajusten a la mayoría de los modelos de inteligencia artificial (IA). El marcado dirigido de datos puede hacer que un modelo de IA entrenado resulte más relevante para las metas y objetivos del usuario, mejorando la precisión del modelo y ayudando a este a dar un salto de calidad.
Características del Wizard MQL5 que debe conocer (Parte 08): Perceptrones Características del Wizard MQL5 que debe conocer (Parte 08): Perceptrones
Los perceptrones, o redes con una sola capa oculta, pueden ser una buena opción para quienes estén familiarizados con los fundamentos del comercio automatizado y quieran sumergirse en las redes neuronales. Paso a paso veremos como se pueden implementar en el ensamblado de clases de señales que forma parte de las clases del Wizard MQL5 para asesores expertos.