English Русский 中文 Deutsch 日本語
preview
Reimaginando las estrategias clásicas (Parte 14): Configuraciones de alta probabilidad

Reimaginando las estrategias clásicas (Parte 14): Configuraciones de alta probabilidad

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

En nuestras discusiones anteriores, hemos analizado cómo se puede replantear el trading basado en cruces de medias móviles fijando los períodos de los dos indicadores de medias móviles en cuestión. Demostramos que, al hacerlo, podemos ejercer un nivel razonable de control sobre la cantidad de retardo en nuestra estrategia de negociación. A continuación, observamos que, al aplicar una media móvil al precio de apertura y otra al precio de cierre, obteníamos una versión mucho más sensible de la estrategia tradicional de cruce de medias móviles. Nuestro nuevo marco ofrece ciertas garantías que no podíamos obtener con la estrategia tradicional. Los lectores que aún no hayan leído ese artículo anterior pueden encontrar el artículo disponible aquí.

Hoy seguimos analizando si merece la pena intentar obtener una mayor productividad de nuestra versión renovada del cruce de medias móviles. Si modelamos cuidadosamente la relación entre nuestra estrategia de cruce de medias móviles y el mercado del EURUSD, esperamos poder descubrir cuál es la diferencia entre las condiciones de mercado en las que nuestra estrategia da mejores resultados y aquellas que resultan demasiado difíciles de manejar para ella. Nuestro objetivo es desarrollar una estrategia de trading que aprenda a dejar de operar cuando detecte condiciones de mercado desfavorables.


Resumen de la estrategia de trading

La mayoría de los miembros de nuestra comunidad coinciden en que los operadores deben buscar activamente oportunidades de trading con una alta probabilidad de éxito. Sin embargo, existen pocas definiciones formales de lo que constituye exactamente una configuración de trading de alta probabilidad. ¿Cómo medimos empíricamente la probabilidad asociada a una estrategia de trading concreta? Dependiendo de a quién le preguntes, obtendrás diferentes definiciones sobre cómo identificar dichas oportunidades y aprovecharlas de manera responsable.

Este artículo pretende abordar estas cuestiones proponiendo un marco algorítmico que nos permita alejarnos de las definiciones antiguas y orientarnos hacia definiciones numéricas basadas en la evidencia, de modo que nuestras estrategias de trading puedan identificarlas y operar con ellas de forma rentable, de manera autónoma y consistente. 

Nuestro objetivo es modelar la relación entre nuestra estrategia de trading particular y cualquier símbolo que hayamos elegido para operar. Podemos lograr esto obteniendo primero datos de mercado que describan completamente el mercado y todos los parámetros que conforman nuestra estrategia de trading, todo ello desde la terminal MetaTrader 5.
Posteriormente, ajustaremos un modelo estadístico para clasificar si la estrategia va a producir señales rentables o si, por el contrario, es muy probable que la señal generada por nuestra estrategia no sea rentable. 

La probabilidad estimada por nuestro modelo se convierte en la probabilidad que asociamos con esa señal en particular. Por lo tanto, ahora podemos empezar a hablar de "escenarios de alta probabilidad" de una manera más científica y empírica, es decir, basada en evidencias y datos de mercado relevantes.

Este marco nos permite, en esencia, escribir estrategias de trading que estén "orientadas a objetivos" y que estén explícitamente instruidas para realizar únicamente acciones que se espera que sean favorables. Estamos empezando a formalizar los componentes necesarios para escribir estrategias de trading algorítmico que intenten estimar las consecuencias más probables de sus acciones. Esto puede conceptualizarse correctamente como enfoques de aprendizaje por refuerzo, abordados de forma supervisada.


Primeros pasos en MQL5

Nuestra tarea de hoy se centra en aprender la relación entre nuestra estrategia de trading y el símbolo con el que deseamos operar. Para lograr este objetivo, analizaremos el crecimiento de las cuatro series de precios (Open, High, Low y Close), así como las variaciones de nuestros dos indicadores de media móvil. 

Tenga en cuenta que, para etiquetar los datos, también necesitaremos los valores reales originales de los dos indicadores y el precio de cierre. En resumen, introduciremos nuestros datos en un archivo CSV con 10 columnas y, a continuación, analizaremos la relación que nuestra estrategia tiene con este símbolo en particular. En cada paso del proceso, compararemos este resultado con el rendimiento de un modelo idéntico que intenta predecir directamente el precio de mercado.

Esto nos indicará qué objetivo es más fácil de aprender, y la belleza de nuestro enfoque reside en el simple hecho de que, independientemente de qué objetivo sea más fácil de pronosticar, ambos nos informan hacia dónde se dirige el precio.

/+-------------------------------------------------------------------+
//|                                                      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 3                 //--- Moving Average Period
#define MA_TYPE   MODE_SMA          //--- Type of moving average we have
#define  HORIZON 10

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

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

//--- File name
string file_name = Symbol() + " Reward Modelling.csv";

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

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   int fetch = size + (HORIZON * 2);
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,fetch,ma_reading);
   ArraySetAsSeries(ma_reading,true);
   CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
   ArraySetAsSeries(ma_o_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","True Close","True MA C","True MA O","Open","High","Low","Close","MA Close 2","MA Open 2");
        }

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


Análisis de los datos

Para empezar, vamos a importar nuestros datos de mercado a un cuaderno Jupyter para poder realizar nuestro análisis numérico del rendimiento de la estrategia.

import pandas as pd

Defina con cuánta antelación nos gustaría pronosticar nuestras ganancias o pérdidas.

HORIZON = 10

Lee los datos y añade las columnas que necesitamos. La primera columna, 'Target', muestra la rentabilidad tradicional del mercado; en este caso, se trata de la rentabilidad del par EURUSD a 10 días. La columna 'Class' es 1 para días alcistas, de lo contrario es 0. En tercer lugar, la columna 'Action' indica qué acción habría activado nuestra estrategia de trading: 1 corresponde a comprar y -1 a vender. La columna 'Reward' se calcula como el producto elemento a elemento de la columna 'Target' y la columna 'Action', esta multiplicación solo producirá recompensas positivas si nuestra estrategia:

  • Se eligió la acción -1 y el objetivo clásico fue menor que 0 (esto significa que nuestra estrategia vendió y los niveles de precios futuros se depreciaron).
  • Se eligió la acción 1 y el objetivo clásico superó 0 (esto significa que nuestra estrategia compró y los niveles de precios futuros se apreciaron).
Cualquier otra combinación de acciones y objetivos produciría resultados negativos porque nuestra estrategia comercial actuó de forma inapropiada. Si nuestro clasificador puede aprender a predecir si nuestra recompensa será positiva, habremos encontrado una herramienta muy fiable para mantenernos al margen de condiciones de mercado desfavorables y ayudarnos a realizar nuestras operaciones cuando sea más probable que resulten rentables. La última columna, 'Trade Signal', está configurada en 0 y solo se establece en 1 si nuestra recompensa es positiva. Inicialmente, las 5 columnas estarán configuradas a 0.

data = pd.read_csv("..\EURUSD Reward Modelling.csv")

data['Target'] = 0
data['Class']  = 0
data['Action'] = 0
data['Reward'] = 0
data['Trade Signal'] = 0

Ahora, introducimos nuestro objetivo habitual: la la variación a 10 días del precio de cierre del EURUSD.

data['Target'] = data['True Close'].shift(-HORIZON) - data['True Close']
data.dropna(inplace=True)

Tenemos que etiquetar las clases para poder distinguir rápidamente los días alcistas y bajistas en nuestros gráficos.

data.loc[data['Target'] > 0,'Class'] = 1

Veamos ahora qué medidas habría tomado nuestra estrategia.

data.loc[data['True MA C'] > data['True MA O'],'Action'] = 1
data.loc[data['True MA C'] < data['True MA O'],'Action'] = -1

Y las ganancias o pérdidas que nuestra estrategia habría generado al llevar a cabo esas acciones.

data['Reward'] = data['Target'] * data['Action']

Ahora, vamos a introducir la señal de trading que nos indica si debemos actuar o esperar.

data.loc[((data['Target'] < 0) & (data['Action'] == -1)),'Trade Signal'] = 1
data.loc[((data['Target'] > 0) & (data['Action'] == 1)),'Trade Signal'] = 1

Podemos analizar los datos. Podemos ver rápidamente que es difícil diferenciar bien la actividad del mercado. Los puntos naranjas representan señales de trading que deberíamos haber ejecutado, mientras que los azules representan señales que deberíamos haber ignorado. El hecho de que los puntos naranjas y azules se vean superpuestos en este diagrama de dispersión informa matemáticamente al lector de que las señales comerciales rentables y no rentables pueden formarse en condiciones casi idénticas. Nuestros modelos estadísticos podrían detectar las similitudes y diferencias que nosotros, como humanos, no somos lo suficientemente sensibles para reconocer, o que requerirían un esfuerzo considerable para hacerlo.

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

sns.scatterplot(data=data,x='MA Close 2',y='MA Open 2',hue='Trade Signal')
plt.title('Analyzing How Well Moving Average Cross Overs Separate The Market')
plt.grid()

Figura 1: Nuestros cruces de medias móviles parecen tener dificultades para separar la acción del precio.

Calculemos rápidamente si nuestro nuevo objetivo es más fácil de pronosticar que el objetivo tradicional. 

from sklearn.linear_model import RidgeClassifier
from sklearn.model_selection import TimeSeriesSplit,cross_val_score

Los modelos lineales son especialmente útiles para obtener aproximaciones fiables y económicas. Vamos a crear un objeto de división de series temporales para poder realizar una validación cruzada de un clasificador lineal.

tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
model = RidgeClassifier()
scores = []

Validar el modelo de forma cruzada, primero con el objetivo anterior y, por último, con nuestro nuevo objetivo, que consiste en estimar las ganancias o pérdidas generadas por nuestra estrategia. Dado que estamos realizando una tarea de clasificación, asegúrese de configurar el método de puntuación en 'accuracy'.

scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Class'],cv=tscv,scoring='accuracy'))))
scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Trade Signal'],cv=tscv,scoring='accuracy'))))

La creación de un gráfico de barras con los resultados revela rápidamente que el modelo que pronostica las ganancias/pérdidas de la estrategia supera al modelo que intenta pronosticar el mercado directamente. El modelo que intentaba predecir el mercado directamente cayó por debajo del umbral del 50%, mientras que nuestro nuevo objetivo nos está ayudando a superar el punto de referencia del 50%, aunque, para ser honestos, solo ligeramente por encima de la barrera.

sns.barplot(scores,color='black')
plt.axhline(np.max(scores),linestyle=':',color='red')
plt.title('Forcasting Market Returns vs Forecasting Strategy Reward')
plt.ylabel('Percentage Accuracy Levels %')
plt.xlabel('0: Market Return Forecast | 1: Strategy Profit/Loss Forecast')

Figura 2: Nuestros modelos lineales nos sugieren que podríamos obtener mejores resultados pronosticando las ganancias o pérdidas generadas por nuestra estrategia.

Podemos calcular el porcentaje exacto de aumento en el rendimiento que hemos obtenido, y parece que nos beneficiamos de un aumento del 7,6 % en la precisión al pronosticar la relación entre la estrategia y el mercado en lugar de pronosticar el mercado directamente.

scores = (((scores / scores[0]) - 1) * 100)

scores[1]

7.595993322203687

Elimine todos los datos que se superpongan con nuestro período de prueba retrospectiva para que esta represente una simulación fiel de las condiciones reales del mercado.

#Drop all the data that overlaps with your backtest period
data = data.iloc[:-((365 * 4) + (30 * 5) + 17),:]
data

Figura 3: Asegúrese de que las fechas que tiene en su marco de datos no se superpongan con las fechas que vamos a probar retrospectivamente.

El modelo lineal nos dio la confianza de que pronosticar las ganancias/pérdidas generadas por la estrategia podría ser mejor para nosotros que pronosticar el precio directamente. Sin embargo, utilizaremos un modelo de aprendizaje más flexible en nuestras pruebas retrospectivas de operaciones para garantizar que el modelo recoja la mayor cantidad de información útil posible.

from sklearn.ensemble import GradientBoostingRegressor

model = GradientBoostingRegressor()

Etiqueta las entradas y el objetivo.

X = ['Open','High','Low','Close','MA Close 2','MA Open 2']
y = 'Trade Signal'

Entrenar el modelo.

model.fit(data.loc[:,X],data.loc[:,y])

Prepárese para exportar el modelo a ONNX.

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

Defina el tamaño de entrada del modelo.

initial_types = [("float input",FloatTensorType([1,6]))]

Guarda el modelo.

onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)
onnx.save(onnx_proto,"EURUSD Reward Model.onnx")


Primeros pasos en MQL5

Ya estamos listos para comenzar el desarrollo de nuestra aplicación de trading. Construiremos dos versiones de la estrategia para evaluar la efectividad de los cambios que hemos introducido en el procedimiento de entrenamiento de nuestro modelo estadístico. Ambas versiones del algoritmo de negociación se someterán a pruebas retrospectivas en condiciones idénticas. Familiaricémonos con estas condiciones particulares para no tener que duplicar innecesariamente la misma información. 

La primera configuración importante es el símbolo, como ya hemos comentado, en este ejemplo operamos con el par EURUSD. Operaremos con este símbolo en el marco temporal diario durante un período de 5 años, desde el 1 de enero de 2020 hasta el 1 de abril de 2025. Esto nos dará un amplio margen para examinar la pertinencia de nuestra aplicación. El lector debe recordar que en la Figura 3, eliminamos de facto todos los datos de mercado que teníamos posteriores al 29 de diciembre de 2019.

Figura 4: Fechas necesarias para nuestra prueba retrospectiva en ambas versiones de nuestra estrategia de trading.

Por último, las condiciones que utilizaremos para modelar el mercado se establecerán para imitar la naturaleza impredecible de las operaciones comerciales reales. Por lo tanto, hemos optado por configurar nuestro retardo en "Retardo aleatorio" y basar cada tick en ticks reales, para obtener una representación realista del mercado. 

Figura 5: Los ajustes que utilizaremos para simular las condiciones del mercado son de gran importancia.

Comencemos ahora a desarrollar una aplicación que podamos someter a pruebas retrospectivas para evaluar la eficacia de los cambios que hemos propuesto en nuestro procedimiento de capacitación. Comenzaremos midiendo el rendimiento de nuestra estrategia sin utilizar el nuevo enfoque de modelado que hemos formulado para identificar escenarios de alta probabilidad. En esencia, la primera versión de nuestra estrategia de trading consiste simplemente en aplicar la estrategia discrecional de operar con los cruces de medias móviles cuando se producen. Esto nos proporcionará un punto de referencia que utilizaremos para comparar nuestra estrategia de modelado de recompensas propuesta. Para empezar, importaremos la biblioteca de comercio.

//+------------------------------------------------------------------+
//|                                             Reward Modelling.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 we need                                                |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

Defina constantes útiles del sistema; estas constantes coinciden con las demás constantes que utilizamos tanto en el script MQL5 como en el script de Python.

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 3                //--- Moving Average Period
#define MA_TYPE   MODE_SMA         //--- Type of moving average we have
#define HORIZON 10                 //--- How far into the future we should forecast

Configurar nuestras variables globales e indicadores técnicos.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int fetch = HORIZON + 1;

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
int ma_handle,ma_o_handle;
double ma_reading[],ma_o_reading[];
int position_timer;

Cada controlador de eventos se ha emparejado con un método correspondiente que se llamará cuando se active el controlador. Este estilo de diseño facilitará el mantenimiento y la ampliación de tu código en el futuro si se te ocurren nuevas ideas. Escribir el código directamente en el controlador de eventos obligaría al desarrollador a revisar cuidadosamente numerosas líneas de código antes de realizar cualquier cambio para asegurarse de que nada se rompa, mientras que con nuestro patrón de diseño, el desarrollador solo necesita agregar una llamada a la función deseada encima de las que hemos proporcionado, si desea extender la funcionalidad de la aplicación.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!setup())
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
  
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   release();
  }
  
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

Ahora analizaremos cada método por separado. La primera función que configuraremos es la encargada de cargar nuestros indicadores técnicos y reiniciar nuestro temporizador de posición. El temporizador de posición es necesario para garantizar que mantengamos cada operación durante el tiempo que esté configurado en la constante del sistema de horizonte.

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);
   position_timer = 0;

   return(true);
  }

El método de liberación simplemente libera los indicadores técnicos que no estamos utilizando; en MQL5 es una buena práctica limpiar los recursos que ya no se usan.

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(ma_handle);
   IndicatorRelease(ma_o_handle);
   return;
  }

El método de actualización obtendrá los niveles de precios actuales y los copiará en nuestros búferes de indicadores. Además, también hará un seguimiento del tiempo que nuestra posición actual ha estado abierta para cerrarla a tiempo.

//+------------------------------------------------------------------+
//| Update system parameters                                         |
//+------------------------------------------------------------------+
void update(void)
  {
   //--- Time stamps
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_D1,0);

   //--- We are on a new day
   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      if(PositionsTotal() == 0)
        {
         //--- Copy indicator values
         CopyBuffer(ma_handle,0,0,fetch,ma_reading);
         CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
         //---Set the values as series
         ArraySetAsSeries(ma_reading,true);
         ArraySetAsSeries(ma_o_reading,true);
         find_setup();
         position_timer = 0;
        }

      //--- Forecasts are only valid for HORIZON days
      if(PositionsTotal() > 0)
        {
         position_timer += 1;
        }

      //--- Otherwise close the position
      if(position_timer == HORIZON)
         Trade.PositionClose(Symbol());
     }
     return;
  }

Y por último, la función de búsqueda de configuración. Nuestras configuraciones se identifican siempre que se producen cruces de medias móviles. Si el precio de apertura cruza por encima del precio de cierre, eso se registra como una señal de venta. De lo contrario, tenemos una señal larga.

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
         double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID) , ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         double vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);

         vector ma_o,ma_c;

         ma_o.CopyIndicatorBuffer(ma_o_handle,0,0,1);
         ma_c.CopyIndicatorBuffer(ma_handle,0,0,1);

         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
     return;
  }    

No olvides anular la definición de las constantes del sistema que definimos anteriormente.

//+------------------------------------------------------------------+
//| Undefine system constatns                                        |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD
#undef MA_TYPE

Ya hemos explicado anteriormente la configuración que utilizaremos para nuestra prueba retrospectiva. Nuestro objetivo es observar la eficacia de nuestra estrategia, sin utilizar las técnicas de modelado estadístico que hemos desarrollado. Simplemente cargue el asesor experto y luego inicie la prueba retrospectiva utilizando la configuración que analizamos en las figuras 4 y 5.

Figura 6: Establecimiento de un punto de referencia.

Nuestra versión discrecional de la estrategia de trading resultó rentable, aunque la rentabilidad parece ser marginal. La diferencia entre la ganancia y la pérdida promedio es de solo 0,9 dólares y solo el 51% de todas las operaciones que realiza son rentables, lo cual no es muy alentador. Nuestro índice de Sharpe es de 0,62 y podría mejorar si renováramos nuestro sistema.

Figura 7: Análisis detallado del rendimiento de nuestra estrategia de trading discrecional.

Ahora bien, al analizar la curva de saldo y de capital generada por esta versión de la estrategia de trading, podemos observar de inmediato fallos evidentes. La estrategia es inestable y volátil. De hecho, cuatro años después de iniciada la prueba retrospectiva, en febrero de 2024, la estrategia prácticamente había recuperado su saldo inicial al comienzo de la prueba. Le costó salir de una serie de operaciones no rentables que se prolongaron desde finales de 2020 durante cuatro años, hasta bien entrado 2024. Esto no nos resulta atractivo como operadores algorítmicos. 

Figura 8: Visualización de la curva de ganancias y pérdidas generada por nuestra versión discrecional de la estrategia de trading.


Mejorando el desempeño de nuestro asesor experto

Ahora, mejoremos nuestra estrategia de trading dotando a nuestra aplicación de la capacidad de imitar el proceso de pensamiento humano, que consiste en considerar las consecuencias de nuestras acciones antes de comprometernos con ellas. 

Comenzaremos importando nuestro modelo ONNX a la aplicación como un recurso desde el directorio de archivos del sistema. 

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Reward Model.onnx" as uchar onnx_proto[];

Para utilizar nuestro modelo, se requerirán algunas variables globales adicionales. Principalmente necesitamos variables que representen el controlador del modelo y la cantidad de datos que debemos obtener.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long onnx_model;
int fetch = HORIZON + 1;

A continuación, debemos configurar el modelo ONNX en la función de configuración. En el ejemplo de código que aparece a continuación, hemos omitido deliberadamente los segmentos de código que no han cambiado en ambas versiones de la aplicación. Simplemente creamos el modelo ONNX a partir de su búfer, validamos el modelo y luego especificamos el tamaño de su entrada y salida en consecuencia. Si se produjera algún fallo en cualquiera de los pasos del proceso, se interrumpirá por completo el procedimiento de inicialización.

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Omitted code that hasn't changed

//--- Setup the ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_proto,ONNX_DEFAULT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create ONNX model");
      return(false);
     }

//--- Register the ONNX model I/O parameters
   ulong input_shape[] = {1,6};
   ulong output_shape[] = {1,1};

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

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

   return(true);
  }

Además, cuando se lance la aplicación, necesitaremos liberar los recursos del sistema que ya no utilizamos.

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
//--- Omitted code segments that haven't changed
   OnnxRelease(onnx_model);
   return;
  }

Indicaremos a la aplicación que primero obtenga una previsión de nuestro modelo de negociación antes de decidir si debe realizar una operación. Si la previsión de nuestro modelo es superior a 0,5, significa que nuestro algoritmo prevé que la señal generada por nuestra estrategia sea rentable y nos da permiso para operar. Si ese no es el caso, entonces esperaremos a que las condiciones desfavorables del mercado sigan su curso.

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//--- Skipped parts of the code base that haven't changed

//--- Prepare the model's inputs
   vectorf model_input(6);
   model_input[0] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0)   - iOpen(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[1] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0)   - iHigh(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[2] = (float)(iLow(_Symbol,PERIOD_CURRENT,0)    - iLow(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[3] = (float)(iClose(_Symbol,PERIOD_CURRENT,0)  - iClose(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[4] = (float)(ma_reading[0] - ma_reading[(HORIZON)]);
   model_input[5] = (float)(ma_o_reading[0] - ma_o_reading[(HORIZON)]);

//--- Prepare the model's output
   vectorf model_output(1);

//--- We failed to run the model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_input,model_output))
      Comment("Failed to obtain a forecast");

//--- Everything went fine
   else
     {
      Comment("Forecast: ",model_output[0]);

      //--- Our model forecasts that our strategy is likely to be profitable
      if(model_output[0] > 0.5)
        {
         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
        }
     }
   return;
  }

Ejecute la aplicación en una prueba retrospectiva utilizando la configuración especificada en las figuras 4 y 5 para realizar una comparación justa entre las dos estrategias.

Parámetros del backtest

Figura 9: Ejecución de nuestra segunda prueba retrospectiva utilizando nuestra versión revisada de la estrategia de trading que modela ganancias y pérdidas.

Nuestro índice de Sharpe aumentó de 0,62 en el índice de referencia discrecional que aplicaba la misma estrategia, a 1,07 en nuestra versión actual, lo que representa un incremento del 72%. Nuestro beneficio neto total aumentó un 38%, pasando de 117,13 dólares a 162,75 dólares, al seguir la nueva estrategia de "configuración de alta probabilidad" definida algorítmicamente. La proporción de operaciones perdedoras disminuyó un 17%, pasando del 48,78% al 40,48%. Por último, el número total de operaciones realizadas por la estrategia se redujo de 164 a 126, lo que significa que nuestro nuevo sistema generó un 38% más de beneficios con solo el 76% del número total de operaciones utilizadas por el sistema anterior. Esto implica que somos más eficaces que antes, ya que obtenemos mayores rendimientos asumiendo menos riesgos.

Figura 10: Resumen detallado del rendimiento de nuestra nueva estrategia de trading.

He proporcionado al lector la curva de equilibrio y de capital de la versión revisada de nuestra estrategia de trading, y he colocado debajo la curva generada por la versión original de la estrategia para que el lector pueda compararlas sin tener que desplazarse de un lado a otro. Podemos observar claramente que, antes de que finalizara el primer año de pruebas retrospectivas, la versión original de nuestra estrategia de trading estaba prácticamente volviendo al punto de equilibrio, mientras que nuestra nueva estrategia gestionó bien ese período. 

Resulta interesante observar que ambas estrategias tuvieron dificultades para obtener buenos resultados durante el período comprendido entre mayo y diciembre de 2022. Esto podría ser un indicio de períodos de mercado particularmente inestables que requerirán un mayor esfuerzo para abordarlos eficazmente.

Figura 11: Curva de ganancias y pérdidas producida por nuestra nueva estrategia de trading que tiene la capacidad de considerar las consecuencias de sus acciones.

Figura 12: La curva de capital generada por la versión original de nuestra estrategia de trading se ha copiado para facilitar la comparación con los nuevos resultados que hemos obtenido.


Conclusión

Tras leer este artículo, el lector habrá aprendido una forma novedosa de abordar la tarea de operar algorítmicamente utilizando modelos estadísticos supervisados. El lector ahora cuenta con los conocimientos necesarios para modelar las relaciones que existen entre sus estrategias de trading privadas y los mercados en los que operan. Esto proporciona al lector una ventaja competitiva sobre los participantes ocasionales del mercado que intentan pronosticar el mercado directamente, lo cual, como hemos demostrado, no siempre es la mejor opción disponible para el lector. 

Nombre del archivo  Descripción del archivo
Reward Modelling Benchmark.mq5 Esta es la versión tradicional de nuestra estrategia de trading, que no intenta considerar las consecuencias de sus acciones. Este sistema otorga el mismo peso a todas las oportunidades de negociación y parte de la base de que cada operación debe ser rentable.
Reward Modelling.mq5 Esta es la versión perfeccionada de nuestra estrategia de trading, que intenta explícitamente estimar las consecuencias de sus acciones antes de realizar cualquier operación.
EURUSD Reward Model.onnx Nuestro modelo estadístico ONNX estima la probabilidad de que la señal generada por nuestra estrategia sea rentable.
Reward Modelling.ipynb El cuaderno Jupyter que utilizamos para analizar nuestros datos históricos de mercado y ajustar nuestro modelo estadístico.

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

Indicador del modelo CAPM en el mercado Forex Indicador del modelo CAPM en el mercado Forex
Adaptación del modelo CAPM clásico para el mercado de divisas Forex en MQL5. El indicador calcula el retorno esperado y la prima de riesgo según la volatilidad histórica. Los indicadores suben en los picos y valles, lo que refleja los principios fundamentales de fijación de precios. Aplicación práctica de estrategias de contra-tendencia y seguimiento de tendencia, considerando la dinámica de la relación riesgo-retorno en tiempo real. Incluye aparato matemático e implementación técnica.
Negociamos con opciones sin opciones (Parte 1): Teoría básica y emulación a través de activos subyacentes Negociamos con opciones sin opciones (Parte 1): Teoría básica y emulación a través de activos subyacentes
El artículo describe una variante de emulación de opciones a través de un activo subyacente, implementada en el lenguaje de programación MQL5. Asimismo, se comparan las ventajas y desventajas del enfoque elegido con opciones bursátiles reales utilizando el ejemplo del mercado de futuros FORTS de la bolsa de Moscú MOEX y la bolsa de criptomonedas Bybit.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Operando con el Calendario Económico MQL5 (Parte 7): Preparación para la prueba de estrategias con análisis de eventos noticiosos basado en recursos Operando con el Calendario Económico MQL5 (Parte 7): Preparación para la prueba de estrategias con análisis de eventos noticiosos basado en recursos
En este artículo, preparamos nuestro sistema de trading en MQL5 para la prueba de estrategias utilizando datos del Calendario económico almacenados como recurso, lo que permite analizarlos fuera del entorno en vivo. Implementamos la carga y el filtrado de eventos por tiempo, moneda e impacto, y luego lo validamos en el Probador de Estrategias. Esto permite realizar pruebas retrospectivas efectivas de estrategias basadas en noticias.