English Русский 中文 Deutsch 日本語
preview
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 6): Prevención del cierre de posiciones

Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 6): Prevención del cierre de posiciones

MetaTrader 5Ejemplos |
242 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Es posible que los operadores predigan correctamente la variación futura del precio de un mercado, pero cierren sus posiciones con pérdidas. En los círculos bursátiles, esto se conoce comúnmente como «Getting stopped out» (Quedarse fuera). Este problema surge del hecho de que los niveles de precios no cambian de forma lineal y predecible. 

En la figura 1, le ofrecemos una instantánea de la variación horaria del precio del par EURUSD. Las líneas verticales blancas discontinuas marcan el inicio y el final de la jornada bursátil. Un operador que estuviera convencido de que los precios iban a bajar ese día habría predicho correctamente la futura variación del precio. Desafortunadamente, los niveles de precios subieron considerablemente antes de caer, lo que significa que si el stop loss de nuestro operador se encontraba dentro de la zona roja resaltada en la figura 1, habría cerrado su posición con pérdidas, tras haber pronosticado correctamente la futura variación del precio.

Captura de pantalla 1

Figura 1: Visualización de situaciones en las que los operadores suelen ser eliminados.

A lo largo de los años, los operadores han sugerido diversas soluciones para resolver este problema, pero, como veremos a lo largo de nuestro análisis, la mayoría de ellas no son soluciones válidas. La solución más comúnmente citada es simplemente «ampliar sus stops». 

Esto implica que los operadores deben ampliar sus stop loss en días de negociación especialmente volátiles, para evitar que se les cierre la posición. Pero este es un mal consejo, ya que anima a los operadores a adquirir el hábito de asumir distintos niveles de riesgo en operaciones similares, sin unas reglas fijas y bien definidas que guíen su toma de decisiones. 

Otra solución que se suele citar es «esperar a recibir confirmación antes de realizar tus operaciones». Una vez más, se trata de un mal consejo dada la naturaleza del problema en el que nos centramos. Uno puede descubrir que esperar la confirmación solo pospone el proceso de ser detenido y no resuelve el problema por completo. 

En resumen, el problema de que se produzcan paradas hace que a los operadores les resulte difícil seguir unos principios sólidos de gestión del riesgo, al tiempo que reduce la rentabilidad de las sesiones de negociación, entre otros motivos de preocupación que genera para los operadores. 

Por lo tanto, nuestro objetivo será proporcionarle a usted, lector, reglas más razonables y bien definidas para minimizar la frecuencia con la que se le detiene en sus operaciones ganadoras. 

Nuestra solución propuesta irá en contra de las soluciones comúnmente citadas y le animará a desarrollar hábitos comerciales sólidos, como mantener fijo el tamaño de su stop loss, en lugar de la solución comúnmente citada de «ampliar su stop loss». 



Resumen de la estrategia comercial

Nuestra estrategia comercial será una estrategia de reversión a la media, compuesta por una combinación de niveles de soporte y resistencia junto con análisis técnico. Primero marcaremos nuestros niveles de precios de interés utilizando el máximo y el mínimo del día anterior. A partir de ahí, esperaremos para ver si los niveles de precios del día anterior se rompen durante el día actual. Si por ejemplo el precio máximo del día anterior es roto por un nuevo precio máximo durante el día actual, buscaremos oportunidades para ocupar posiciones cortas en el mercado, apostando a que los niveles de precios volverán a su promedio. La señal para entrar en posiciones cortas se aclarará para nosotros cuando observemos niveles de precios cerrando por encima del indicador de media móvil, después de cerrar exitosamente por encima del máximo anterior. 

Figura 2: Visualizando nuestra estrategia comercial en acción.



Descripción general del período de prueba retrospectiva

Para analizar la efectividad de nuestros cambios propuestos a la estrategia comercial, primero debemos tener un período fijo durante el cual compararemos los cambios que estamos realizando en nuestro sistema. Para esta discusión, comenzaremos nuestra prueba desde el 1 de enero de 2022 hasta el 1 de enero de 2025. El período en cuestión se ha resaltado en la Figura 1, tenga en cuenta que en la figura estamos observando el EURUSD en el marco temporal mensual.

Figura 3: El período durante el cual realizaremos nuestra prueba retrospectiva.

Nuestras pruebas reales se realizarán en el marco temporal M30. En la Figura 2 hemos resaltado el mercado previsto que utilizaremos para nuestras pruebas, así como el período de tiempo que discutimos anteriormente. Estas configuraciones se solucionarán a lo largo del resto del artículo, y de ahí la necesidad de que las analicemos aquí. Para todas nuestras pruebas futuras, dejaremos estas configuraciones sin cambios. Además, asegúrese de seleccionar EURUSD si desea seguirnos, o cualquier símbolo que prefiera operar.

Figura 4: Nuestro período de prueba retrospectiva.

Además, seleccione "Cada tick basado en ticks reales" para reproducir la emulación más precisa de eventos históricos del mercado que podamos crear. Tenga en cuenta que esta configuración obtendrá los datos relevantes de su agente, y esto puede llevar una cantidad de tiempo considerable dependiendo de la provisión de su red.

Figura 5: La configuración de la cuenta que usaremos para nuestra prueba retrospectiva.


Introducción a MQL5

Ahora que nos hemos familiarizado con el período de prueba retrospectiva para nuestra prueba de hoy, establezcamos primero una medición de referencia que superaremos. Comenzaremos construyendo una aplicación comercial para implementar una estrategia comercial de soporte y resistencia que tenga como objetivo negociar rupturas. Primero, importaremos la biblioteca comercial.

//+------------------------------------------------------------------+
//|                                               Baseline Model.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh>
CTrade Trade;

Definir constantes del sistema. Estas constantes nos ayudan a garantizar que tenemos un control definido sobre el comportamiento de nuestra aplicación en todas las pruebas.

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 14                //--- Moving Average Period
#define MA_TYPE   MODE_EMA          //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE        //---- Applied Price of Moving Average
#define TF_1 PERIOD_D1              //--- Our time frame for technical analysis
#define TF_2 PERIOD_M30             //--- Our time frame for managing positions
#define VOL 0.1                     //--- Our trading volume
#define SL_SIZE  1e3 * _Point       //--- The size of our stop loss

También necesitaremos algunas variables globales que nos ayuden a realizar un seguimiento de los niveles de precios de interés de ayer.

//+------------------------------------------------------------------+
//| Our global variables                                             |
//+------------------------------------------------------------------+
int ma_handler,system_state;
double ma[];
double bid,ask,yesterday_high,yesterday_low;
const string last_high = "LAST_HIGH";
const string last_low = "LAST_LOW";

Cuando nuestra aplicación se haya cargado por primera vez, configure todos nuestros indicadores técnicos.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }

Si nuestra aplicación ya no está en nosotros, liberemos los indicadores técnicos que ya no utilizamos.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   release();
  }

Siempre que recibimos precios actualizados, los almacenamos y también recalculamos nuestros valores indicadores.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }

Ahora definiremos cada una de las funciones que hemos llamado en nuestro ciclo de eventos. En primer lugar, nuestra función de actualización funcionará en dos períodos de tiempo diferentes. Tenemos rutinas y procedimientos que deben realizarse una vez al día y otros deben realizarse en intervalos mucho más cortos. Esta separación de preocupaciones está resuelta por las dos constantes del sistema que hemos definido, TF_1 (marco de tiempo diario) y TF_2 (marco de tiempo M30). Tareas como buscar el día anterior, tanto alto como bajo, solo necesitan hacerse una vez al día. Por otro lado, tareas como la búsqueda de posiciones solo deben realizarse una vez cada nueva vela de 30 minutos.

//+------------------------------------------------------------------+
//| Perform our update routines                                      |
//+------------------------------------------------------------------+
void update()
  {
//--- Daily procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_1,0);
      if(time_stamp != current_time)
        {
         yesterday_high = iHigh(Symbol(),TF_1,1);
         yesterday_low = iLow(Symbol(),TF_1,1);
         //--- Mark yesterday's levels
         ObjectDelete(0,last_high);
         ObjectDelete(0,last_low);
         ObjectCreate(0,last_high,OBJ_HLINE,0,0,yesterday_high);
         ObjectCreate(0,last_low,OBJ_HLINE,0,0,yesterday_low);
        }
     }
//--- M30 procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_2,0);
      if(time_stamp != current_time)
        {
         time_stamp = current_time;
         //--- Get updated prices
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         //--- Update our technical indicators
         CopyBuffer(ma_handler,0,0,1,ma);
         //--- Check for a setup
         if(PositionsTotal()==0)  find_setup();
        }
     }
  }

Esta aplicación en particular solo se basa en un indicador técnico. Por lo tanto, nuestro procedimiento para definir la función de configuración es simple.

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   ma_handler    = iMA(Symbol(),TF_2,MA_PERIOD,0,MA_TYPE,MA_PRICE);
  };

Nuestras condiciones para abrir una posición se cumplirán si detectamos que nuestro precio extremo actual supera al precio extremo opuesto que observamos el día anterior. Además de romper los niveles establecidos el día anterior, también deseamos registrar una confirmación adicional de la relación del precio con su media móvil.

//+------------------------------------------------------------------+
//| Check if we have any trading setups                              |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   if(iHigh(Symbol(),TF_2,1) < yesterday_low)
     {
         if(iClose(Symbol(),TF_2,1) < ma[0])
            {
               Trade.Buy(VOL,Symbol(),ask,(bid - (SL_SIZE)),(bid + (SL_SIZE)));
            }
     }

   if(iLow(Symbol(),TF_2,1) > yesterday_high)
     {
         if(iClose(Symbol(),TF_2,1) > ma[0])
            {
              Trade.Sell(VOL,Symbol(),bid,(ask + (SL_SIZE)),(ask - (SL_SIZE)));
            }
     }
  }

Si no utilizamos nuestro Asesor Experto, deberíamos liberar los recursos del sistema que ya no necesitamos.

//+------------------------------------------------------------------+
//| Free resources we are no longer using up                         |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(ma_handler);
  }

Por último, al final del ciclo de ejecución de nuestro programa, eliminaremos las constantes del sistema que definimos anteriormente.

//+------------------------------------------------------------------+
//| Undefine the system constants we created                         |
//+------------------------------------------------------------------+
#undef TF_1
#undef TF_2
#undef VOL
#undef SL_SIZE
#undef MA_PERIOD
#undef MA_PRICE
#undef MA_TYPE

La curva de capital producida por nuestra estrategia comercial actual no es estable. El saldo producido por nuestro sistema actual muestra una tendencia a seguir cayendo con el tiempo. Deseamos una estrategia que caiga ocasionalmente, pero que tenga tendencia a seguir aumentando con el tiempo. Por lo tanto, mantendremos constantes las reglas que usamos para abrir nuestras posiciones e intentaremos filtrar las operaciones que creemos que alcanzarán el stop loss. Este ejercicio definitivamente será desafiante. Sin embargo, es mejor probar cualquier solución que no probar ninguna.

Figura 6: La curva de capital producida por nuestra versión actual de nuestra estrategia comercial.

Cuando analizamos los resultados detallados de nuestra estrategia comercial, podemos observar que nuestro algoritmo perdió más de $1000 durante nuestro período de prueba retrospectiva de 3 años. Ésta no es una información para nada alentadora. Además, nuestra pérdida promedio y más grande excede nuestra ganancia promedio y más grande. Esto nos da expectativas negativas sobre el desempeño de la estrategia en el futuro. Por lo tanto, no querríamos utilizar la estrategia en su forma actual para operar con una cuenta con capital real.

Figura 7: Análisis de los resultados detallados producidos por nuestra estrategia comercial.


Mejorando la base

La base de nuestra estrategia de detener nuestra prevención se basa en una observación que hicimos anteriormente en nuestra serie de debates. Los lectores que deseen volver a consultar el debate anterior pueden encontrarlo fácilmente disponible aquí. En resumen, observamos que, de entre más de 200 símbolos diferentes en nuestra terminal MetaTrader 5, el indicador técnico de la media móvil parecía ser sistemáticamente más fácil de pronosticar que el precio directamente. 

Podemos aprovechar nuestras observaciones para pronosticar si se espera que el valor futuro de la media móvil supere nuestro nivel de stop loss. Si nuestro ordenador prevé que ese será el caso, entonces no debería realizar ninguna operación mientras espere que la media móvil alcance nuestro stop loss; de lo contrario, nuestra aplicación podrá realizar sus operaciones. 

Esta es la esencia de nuestra solución. Está claramente definido de principio a fin y se basa en principios sólidos y un razonamiento objetivo. El lector debe tener en cuenta que podemos ser aún más específicos y exigir que, además de no esperar que se alcance nuestro stop loss, nuestro ordenador también debe esperar que la media móvil supere nuestro nivel de take profit antes de realizar cualquier operación. De lo contrario, ¿por qué alguien aceptaría una operación si no tiene motivos para creer que su orden de toma de ganancias se ejecutará?

Para empezar, primero necesitamos obtener los datos relevantes del mercado desde nuestra terminal MetaTrader 5, utilizando un script MQL5. 

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- Define our moving average indicator
#define MA_PERIOD 14                //--- Moving Average Period
#define MA_TYPE   MODE_EMA          //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE        //---- Applied Price of Moving Average

//--- Our handlers for our indicators
int ma_handle;

//--- Data structures to store the readings from our indicators
double ma_reading[];

//--- File name
string file_name = Symbol() + " Stop Out Prevention Market Data.csv";

//--- Amount of data requested
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_M30,MA_PERIOD,0,MA_TYPE,MA_PRICE);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,size,ma_reading);
   ArraySetAsSeries(ma_reading,true);

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","Open","High","Low","Close","MA 14");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   iOpen(_Symbol,PERIOD_CURRENT,i), 
                   iHigh(_Symbol,PERIOD_CURRENT,i),
                   iLow(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   ma_reading[i]);
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


Analizando nuestros datos en Python

Una vez que haya aplicado el script al mercado de su elección, podemos comenzar a analizar nuestros datos financieros utilizando bibliotecas de Python. Nuestro objetivo es construir una red neuronal que nos ayude a pronosticar el valor futuro del indicador de media móvil y, potencialmente, evitarnos operaciones con pérdidas. 

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

Ahora leemos los datos que hemos extraído de nuestro terminal.

data = pd.read_csv("EURUSD Stop Out Prevention Market Data.csv")
data

Etiqueta los datos.

LOOK_AHEAD = 48
data['Target'] = data['MA 14'].shift(-LOOK_AHEAD)
data.dropna(inplace=True)
data.reset_index(drop=True,inplace=True)

Elimine los periodos de tiempo que se solapan con nuestra prueba retrospectiva.

#Let's entirely drop off the last 2 years of data
data.iloc[-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)):,:]

Sobrescribir los datos originales del mercado con los nuevos datos que no contienen las observaciones de nuestro periodo de backtest.

#Let's entirely drop off the last 2 years of data
_ = data.iloc[-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)):,:]
data = data.iloc[:-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)),:]
data

Ahora cargue nuestras bibliotecas de aprendizaje automático.

from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import train_test_split,TimeSeriesSplit,cross_val_score
Crear un objeto de división de series temporales para poder validar rápidamente nuestro modelo.
tscv = TimeSeriesSplit(n_splits=5,gap=LOOK_AHEAD)

Especifique las entradas y el objetivo.

X = data.columns[1:-1]
y = data.columns[-1:]
Divida los datos en dos partes, para entrenar y probar el nuevo modelo.

train , test = train_test_split(data,test_size=0.5,shuffle=False)

Prepare el entrenamiento y pruebe las particiones que se van a normalizar y escalar.

train_X = train.loc[:,X]
train_y = train.loc[:,y]

test_X = test.loc[:,X]
test_y = test.loc[:,y]

Calcula los parámetros para nuestras puntuaciones z.

mean_scores = train_X.mean()
std_scores = train_X.std()
Normalizar los datos de entrada del modelo.
train_X = ((train_X - mean_scores) / std_scores)
test_X = ((test_X - mean_scores) / std_scores)

Queremos realizar una búsqueda lineal para determinar el número óptimo de iteraciones de entrenamiento para nuestra red neuronal profunda. Iteraremos a través de potencias crecientes de 2. Desde 2 elevado a 0 hasta 2 elevado a 14.

MAX_POWER = 15
results = pd.DataFrame(index=["Train","Test"],columns=[np.arange(0,MAX_POWER)])

Defina un bucle for que nos ayudará a estimar la cantidad óptima de iteraciones de entrenamiento necesarias para ajustar nuestro modelo de red neuronal profunda a los datos que tenemos.

#Classical Inputs
for i in np.arange(0,MAX_POWER):
    print(i)
    model = MLPRegressor(hidden_layer_sizes=(5,10,4,2),solver="adam",activation="relu",max_iter=(2**i),early_stopping=False)
    results.iloc[0,i] = np.mean(np.abs(cross_val_score(model,train_X.loc[:,:],train_y.values.ravel(),cv=tscv)))
    results.iloc[1,i] = np.mean(np.abs(cross_val_score(model,test_X.loc[:,:],test_y.values.ravel(),cv=tscv)))
    results
  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 
 Entrenamiento 19675.492496 19765.297106
19609.7644 19511.588484
19859.734807
19942.30371
18831.617167
10703.554068
 4930.771654
1639.952482
1389.615052 2938.371438
1.536765 2.193895
30.553918
 Prueba 13171.519137 14113.252994 14428.159203 13649.157525 13655.643066 12919.773346 11472.770729 5878.964564 11293.444345
3788.388634  2545.368419 3599.364028 2240.598518
1041.641869  882.696622

Al representar visualmente los datos se ve que necesitamos realizar la cantidad máxima de iteraciones para obtener el resultado óptimo de nuestro modelo. Sin embargo, el lector también debe tener la mente abierta a la posibilidad de que nuestro procedimiento de búsqueda haya terminado prematuramente. Lo que significa que es posible que hubiéramos obtenido mejores resultados si hubiéramos utilizado potencias de 2 mayores que 14. Pero, debido al costo computacional de entrenar estos modelos, nuestra búsqueda no pasó de 2 elevado a 14.

plt.title("Neural Network RMSE Forecasting 14 Period MA")
plt.ylabel("5 CV RMSE")
plt.xlabel("Training Iterations As Powers of 2")
plt.grid()
sns.lineplot(np.array(results.iloc[1,:]).transpose())
plt.axhline(results.min(1)[1],linestyle='--',color='red')
plt.axvline(14,linestyle='--',color='red')

Figura 8: Resultados de la búsqueda del número óptimo de iteraciones de entrenamiento para nuestro modelo de red neuronal profunda.

Ahora que nuestro modelo ha sido entrenado, podemos prepararnos para exportarlo al formato ONNX.

import onnx
import skl2onnx 
from skl2onnx.common.data_types import FloatTensorType  

Prepárese para ajustar el modelo utilizando el número óptimo de iteraciones de entrenamiento que hemos estimado.

model = MLPRegressor(hidden_layer_sizes=(5,10,4,2),solver="adam",activation="relu",max_iter=(2**14),early_stopping=False)

Cargue las puntuaciones z para todo el conjunto de datos.

mean_scores = data.loc[:,X].mean()
std_scores = data.loc[:,X].std()

mean_scores.to_csv("EURUSD StopOut Mean.csv")
std_scores.to_csv("EURUSD StopOut Std.csv")

Transformar todo el conjunto de datos.

data[X] = ((data.loc[:,X] - mean_scores) / std_scores)

Equipamos el modelo con todos los datos que tenemos, excluyendo las fechas de pruebas.

model.fit(data.loc[:,X],data.loc[:,'Target'].values.ravel())

Especifique la forma de entrada de nuestro modelo.

initial_types = [("float_input",FloatTensorType([1,5]))]

Prepárese para convertir el modelo al formato ONNX.

model_proto = skl2onnx.convert_sklearn(model,initial_types=initial_types,target_opset=12)

Guarde el modelo como un archivo ONNX.

onnx.save(model_proto,"EURUSD StopOut Prevention Model.onnx")


Construyendo una versión refinada de nuestra estrategia

Comencemos a construir nuestra nueva versión refinada de nuestra estrategia comercial. Primero, cargue el modelo ONNX que acabamos de crear.

//+------------------------------------------------------------------+
//|                                               Baseline Model.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD StopOut Prevention Model.onnx" as uchar onnx_model_buffer[];

Crearemos algunas constantes de sistema adicionales para esta versión de nuestra aplicación.

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 14                 //--- Moving Average Period
#define MA_TYPE   MODE_EMA           //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE         //---- Applied Price of Moving Average
#define TF_1 PERIOD_D1               //--- Our time frame for technical analysis
#define TF_2 PERIOD_M30              //--- Our time frame for managing positions
#define VOL 0.1                      //--- Our trading volume
#define SL_SIZE  1e3 * _Point        //--- The size of our stop loss
#define SL_ADJUSTMENT 1e-5 * _Point  //--- The step size for our trailing stop
#define ONNX_MODEL_INPUTS 5          //---- Total model inputs for our ONNX model

Además, nuestras puntuaciones z globales deben cargarse en matrices.

//+------------------------------------------------------------------+
//| Our global variables                                             |
//+------------------------------------------------------------------+
int     ma_handler,system_state;
double  ma[];
double  mean_values[ONNX_MODEL_INPUTS]  = {1.157641086508574,1.1581085911361018,1.1571729541088953,1.1576420747040126,1.157640521193191};
double  std_values[ONNX_MODEL_INPUTS]   = {0.04070388112283021,0.040730761156963606,0.04067819202368064,0.040703752648947544,0.040684857239172416};
double  bid,ask,yesterday_high,yesterday_low;
const   string last_high = "LAST_HIGH";
const   string last_low  = "LAST_LOW";
long    onnx_model;
vectorf model_forecast = vectorf::Zeros(1);

Antes de poder utilizar nuestros modelos ONNX, primero debemos configurarlos como corresponde y verificar si se han configurado correctamente.

//+------------------------------------------------------------------+
//| Prepare the resources our EA requires                            |
//+------------------------------------------------------------------+
bool setup(void)
  {
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DEFAULT);

   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create ONNX model: ",GetLastError());
      return(false);
     }

   ulong input_shape[] = {1,ONNX_MODEL_INPUTS};
   ulong output_shape[] = {1,1};

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Failed to set ONNX model input shape: ",GetLastError());
      return(false);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Failed to set ONNX model output shape: ",GetLastError());
      return(false);
     }

   ma_handler    = iMA(Symbol(),TF_2,MA_PERIOD,0,MA_TYPE,MA_PRICE);

   if(ma_handler == INVALID_HANDLE)
     {
      Comment("Failed to load technical indicator: ",GetLastError());
      return(false);
     }

   return(true);
  };

Nuestro procedimiento para encontrar una configuración comercial va a cambiar ligeramente. Primero, obtendremos una predicción de nuestro modelo. Posteriormente, nuestras condiciones para abrir y cerrar posiciones siguen siendo las mismas. Sin embargo, además de que se cumplan estas condiciones, también comprobaremos si se cumplen nuevas condiciones. 

//+------------------------------------------------------------------+
//| Check if we have any trading setups                              |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   if(!model_predict())
     {
      Comment("Failed to get a forecast from our model");
      return;
     }

   if((iHigh(Symbol(),TF_2,1) < yesterday_low) && (iHigh(Symbol(),TF_2,2) < yesterday_low))
     {
      if(iClose(Symbol(),TF_2,1) > ma[0])
        {
         check_buy();
        }
     }

   if((iLow(Symbol(),TF_2,1) > yesterday_high) && (iLow(Symbol(),TF_2,2) > yesterday_high))
     {
      if(iClose(Symbol(),TF_2,1) < ma[0])
        {
         check_sell();
        }
     }
  }

Las nuevas condiciones que necesitamos especificar se aplicarán tanto a nuestras posiciones largas como cortas. En primer lugar, comprobaremos si nuestro pronóstico de la media móvil es mayor que la lectura actual del indicador de media móvil que tenemos disponible. Además, también querremos comprobar si el valor futuro esperado del indicador de media móvil es mayor que la lectura del precio actual que se ofrece.

Esto significa que nuestra computadora sospecha que es probable que la tendencia continúe moviéndose en una dirección. Por último, comprobaremos si nuestro ordenador espera que la media móvil se mantenga por debajo del stop loss. Si se cumplen todas nuestras condiciones, abriremos una posición en el mercado de inmediato.

//+------------------------------------------------------------------+
//| Check if we have a valid buy setup                               |
//+------------------------------------------------------------------+
void check_buy(void)
  {
   if((model_forecast[0] > ma[0]) && (model_forecast[0] > iClose(Symbol(),TF_2,0)))
     {
      if(model_forecast[0] > (bid - (SL_SIZE)))
         Trade.Buy(VOL,Symbol(),ask,(bid - (SL_SIZE)),(bid + (SL_SIZE)));
     }
  }

Nuestras condiciones para abrir posiciones cortas serán las mismas que las condiciones que especificamos para nuestras posiciones largas, pero funcionarán en el orden opuesto.

//+------------------------------------------------------------------+
//| Check if we have a valid sell setup                              |
//+------------------------------------------------------------------+
void check_sell(void)
  {
   if((model_forecast[0] < ma[0]) && (model_forecast[0] < iClose(Symbol(),TF_2,0)))
     {
      if(model_forecast[0] < (ask + (SL_SIZE)))
         Trade.Sell(VOL,Symbol(),bid,(ask + (SL_SIZE)),(ask - (SL_SIZE)));
     }
  }

Una vez que hemos abierto una posición, debemos continuar monitoreándola. Nuestra función de actualización de stop loss tendrá dos propósitos dependiendo de cómo se la llame. Toma un parámetro de bandera que modifica su comportamiento. Si la bandera está establecida en 0, simplemente estamos buscando una oportunidad para empujar nuestros niveles de stop hacia precios más rentables. De lo contrario, si la bandera está establecida en 1, primero queremos obtener un nuevo pronóstico de nuestro modelo y verificar si el valor futuro del promedio móvil puede exceder nuestro nivel de stop loss actual. 

Si se espera que el promedio móvil supere nuestro stop loss pero aún así forme un movimiento rentable, entonces ajustaremos nuestro stop loss al nivel que esperamos que el promedio móvil alcance en su pico. De lo contrario, si se espera que el precio de la operación caiga por debajo de su precio de apertura, entonces queremos indicarle a nuestra computadora que arriesgue menos en operaciones que muestren poco potencial de ganancias.

//+------------------------------------------------------------------+
//| Update our stop loss                                             |
//+------------------------------------------------------------------+
void update_sl(int flag)
  {
   //--- First find our open position
   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double open_price = PositionGetDouble(POSITION_PRICE_OPEN);

      //--- Flag 0 means we just want to push the stop loss and take profit forward if its possible
      if(flag == 0)
        {
         //--- Buy Setup
         if(current_tp > current_sl)
           {
            if((bid - SL_SIZE) > current_sl)
               Trade.PositionModify(Symbol(),(bid - SL_SIZE),(bid + SL_SIZE));
           }

         //--- Sell setup
         if(current_tp < current_sl)
           {
            if((ask + SL_SIZE) < current_sl)
               Trade.PositionModify(Symbol(),(ask + SL_SIZE),(ask - SL_SIZE));
           }
        }

      //--- Flag 1 means we want to check if the stop loss may be hit soon, and act accordingly
      if(flag == 1)
        {
         model_predict();

         //--- Buy setup
         if(current_tp > current_sl)
           {
            
            if(model_forecast[0] < current_sl)
              {
               if((model_forecast[0] > ma[0]) && (model_forecast[0] > yesterday_low))
                  Trade.PositionModify(Symbol(),model_forecast[0],current_tp);
              }

            if(model_forecast[0] < open_price)
               Trade.PositionModify(Symbol(),model_forecast[0] * 1.5,current_tp);
           }

         //--- Sell setup
         if(current_tp < current_sl)
           {
            if(model_forecast[0] > current_sl)
              {
               if((model_forecast[0] < ma[0]) && (model_forecast[0] < yesterday_high))
                  Trade.PositionModify(Symbol(),model_forecast[0],current_tp);
              }

            if(model_forecast[0] > open_price)
               Trade.PositionModify(Symbol(),model_forecast[0] * 0.5,current_tp);
           }
        }
     }
  }

Nuestro procedimiento de actualización se modifica ligeramente para llamar a la función de actualización de stop loss.

//+------------------------------------------------------------------+
//| Perform our update routines                                      |
//+------------------------------------------------------------------+
void update()
  {
//--- Daily procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_1,0);
      if(time_stamp != current_time)
        {
         yesterday_high = iHigh(Symbol(),TF_1,1);
         yesterday_low = iLow(Symbol(),TF_1,1);
         //--- Mark yesterday's levels
         ObjectDelete(0,last_high);
         ObjectDelete(0,last_low);
         ObjectCreate(0,last_high,OBJ_HLINE,0,0,yesterday_high);
         ObjectCreate(0,last_low,OBJ_HLINE,0,0,yesterday_low);
        }
     }
//--- M30 procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_2,0);
      if(time_stamp != current_time)
        {
         time_stamp = current_time;
         //--- Get updated prices
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         //--- Update our technical indicators
         CopyBuffer(ma_handler,0,0,1,ma);
         //--- Check for a setup
         if(PositionsTotal()==0)
            find_setup();

         //--- Check for a setup
         if(PositionsTotal() > 0)
            update_sl(1);
        }
     }
//--- Per tick procedures
     {
      //--- These function calls can become expensive and may slow down the speed of your back tests
      //--- Be thoughtful when placing any function calls in this scope
      update_sl(0);
     }
  }

También necesitamos una función dedicada responsable de obtener predicciones de nuestro modelo de red neuronal. Primero prepararemos las entradas en un tipo de vector flotante y luego procederemos a estandarizar las entradas, para que podamos obtener una predicción de nuestro modelo.

//+------------------------------------------------------------------+
//| Get a forecast from our deep neural network                      |
//+------------------------------------------------------------------+
bool model_predict(void)
  {
   double ma_input[] = {0};
   CopyBuffer(ma_handler,0,1,1,ma_input);
   vectorf model_inputs =
     {
      (float) iOpen(Symbol(),TF_2,1),
      (float) iHigh(Symbol(),TF_2,1),
      (float) iLow(Symbol(),TF_2,1),
      (float) iClose(Symbol(),TF_2,1),
      (float) ma_input[0]
     };

   for(int i = 0; i < ONNX_MODEL_INPUTS;i++)
     {
      model_inputs[i] = (float)((model_inputs[i] - mean_values[i]) / std_values[i]);
     }

   if(!OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast))
     {
      Comment("Failed to obtain forecast: ",GetLastError());
      return(false);
     }

   Comment(StringFormat("Expected MA Value: %f",model_forecast[0]));
   return(true);
  }

Por último, cuando nuestra aplicación ya no esté en uso, liberaremos el indicador y el modelo ONNX.

//+------------------------------------------------------------------+
//| Free resources we are no longer using up                         |
//+------------------------------------------------------------------+
void release(void)
  {
   OnnxRelease(onnx_model);
   IndicatorRelease(ma_handler);
  }
//+------------------------------------------------------------------+

Cuando analizamos la curva de capital producida por nuestra nueva versión refinada de nuestro algoritmo comercial, podemos observar rápidamente que la pendiente negativa característica que observamos en la primera implementación de la estrategia se ha rectificado y nuestra estrategia ahora exhibe una tendencia positiva, con caídas ocasionales. Esto es más deseable que el estado inicial de nuestra estrategia.

Figura 9: Visualización de la curva de ganancias producida por nuestra nueva versión refinada de nuestro algoritmo de prevención de parada.

Al analizarlo más de cerca, descubrimos que nuestra nueva estrategia ahora es rentable. La versión inicial de nuestra estrategia perdió aproximadamente $1000 y nuestra versión actual ganó un poco más de $1000. Esta es una mejora importante. Nuestro ratio de Sharpe inicial fue -0,39 y nuestro nuevo ratio de Sharpe es 0,79. El lector también notará que nuestra ganancia promedio en operaciones aumentó de $98 a $130, mientras que la pérdida promedio en operaciones cayó de $102 a $63. Esto demuestra que nuestras ganancias promedio crecen a un ritmo significativamente mayor que nuestras pérdidas promedio. Estas métricas nos dan expectativas positivas si consideramos usar esta versión de nuestra estrategia de trading.

Aunque hemos logrado avances importantes, el problema de que nos saquen del mercado sigue siendo ciertamente un problema difícil de resolver. Esto nos resulta evidente por el hecho de que aproximadamente el 60% de todas las posiciones que abrimos fueron operaciones perdedoras. Es un desafío intentar filtrar por completo todas las operaciones que pueden hacer que un operador se detenga, pero hoy logramos filtrar la mayoría de las operaciones grandes y no rentables.

Figura 10: Un análisis detallado de los resultados que obtuvimos utilizando nuestro nuevo algoritmo de prevención de stop out.



Conclusión

En este artículo, hemos guiado al lector a través de una posible solución al antiguo problema de quedarse sin operaciones ganadoras. Este problema es fundamental para tener éxito en el trading y puede que nunca se resuelva por completo. Cada nueva solución introduce su propio conjunto de vulnerabilidades en nuestra estrategia. Después de leer este artículo, el lector se marcha con un marco más cuantitativo para gestionar sus niveles de stop loss. Identificar y filtrar operaciones que reducirán innecesariamente su cuenta es un componente crucial de cualquier estrategia comercial. 

Nombre del archivo Descripción del archivo
Baseline Model.mq5 Nuestra estrategia comercial original cuyo objetivo era superar.
Stop Out Prevention Model.mq5 Nuestra versión refinada de la estrategia comercial, impulsada por una red neuronal profunda.
EURUSD Stop Out Moving Average Model.ipynb El Jupyter Notebook que usamos para analizar los datos financieros que extrajimos de nuestro terminal MetaTrader 5.
EURUSD Stop Out Prevention Model.onnx Nuestra red neuronal profunda.
Fetch Data MA.mq5 El script MQL5 que usamos para obtener los datos de mercado necesarios.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17213

Automatización de estrategias de trading en MQL5 (Parte 8): Creación de un Asesor Experto con patrones armónicos Butterfly Automatización de estrategias de trading en MQL5 (Parte 8): Creación de un Asesor Experto con patrones armónicos Butterfly
En este artículo, creamos un Asesor Experto MQL5 para detectar patrones armónicos Butterfly. Identificamos los puntos pivote y validamos los niveles de Fibonacci para confirmar el patrón. A continuación, visualizamos el patrón en el gráfico y ejecutamos automáticamente las operaciones cuando se confirman.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 14): Herramienta Parabolic SAR (Stop and Reverse) Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 14): Herramienta Parabolic SAR (Stop and Reverse)
Incorporar indicadores técnicos en el análisis de la acción del precio es un enfoque muy eficaz. Estos indicadores suelen resaltar niveles clave de reversiones y retrocesos, lo que ofrece información valiosa sobre la dinámica del mercado. En este artículo, mostramos cómo desarrollamos una herramienta automatizada que genera señales utilizando el indicador Parabolic SAR.
Automatización de estrategias de trading en MQL5 (Parte 9): Creación de un asesor experto para la estrategia de ruptura asiática Automatización de estrategias de trading en MQL5 (Parte 9): Creación de un asesor experto para la estrategia de ruptura asiática
En este artículo, creamos un Asesor Experto en MQL5 para la estrategia de ruptura asiática calculando los máximos y mínimos de la sesión y aplicando un filtro de tendencia con una media móvil. Implementamos estilos dinámicos para objetos, entradas de tiempo definidas por el usuario y una sólida gestión de riesgos. Por último, mostramos técnicas de pruebas retrospectivas y optimización para perfeccionar el sistema.
Redes neuronales en el trading: Clusterización doble de series temporales (DUET) Redes neuronales en el trading: Clusterización doble de series temporales (DUET)
El framework DUET ofrece un enfoque innovador del análisis de series temporales, combinando la clusterización temporal y por canales para revelar patrones ocultos en los datos analizados. Esto permite a los modelos adaptarse a los cambios a lo largo del tiempo y mejorar la calidad de las previsiones eliminando el ruido.