Reimaginando las estrategias clásicas: El petróleo
Introducción
El petróleo es el producto básico más importante sobre la faz de la tierra. El petróleo crudo en su forma original es inútil; sin embargo, después de ser refinado, se utiliza en diversas industrias, desde tan simples como la agricultura hasta tan complejas como la farmacéutica. El petróleo es uno de los pocos productos básicos que tienen una verdadera demanda en todas las industrias. El precio del petróleo es un indicador econométrico clave de los niveles de producción mundial y de los niveles de crecimiento económico.
El comercio mundial de crudo está dominado por dos índices de referencia: el West Texas Intermediate (WTI), que es el de referencia norteamericano, y el Brent, que se utiliza para cotizar la mayor parte del crudo del mundo.
En este debate, revisaremos una estrategia clásica de negociación de diferenciales de petróleo crudo, con la esperanza de poder encontrar una estrategia de aprendizaje automático óptima para hacer que esta estrategia clásica sea más aceptable en un mercado petrolero moderno que está dominado por algoritmos.
Comenzaremos nuestro análisis destacando primero las diferencias entre los dos índices de referencia del petróleo mencionados anteriormente. A partir de ahí, comenzaremos a visualizar el spread Brent-WTI en MQL5 y discutiremos la estrategia clásica de negociación de spreads. Esto nos permitirá demostrar cómo se puede utilizar el aprendizaje automático supervisado en el diferencial entre los precios del petróleo West Texas Intermediate y Brent para descubrir potencialmente indicadores principales de cambios en el precio. Después de leer este artículo, tendrá una comprensión clara de lo siguiente:
- La diferencia entre los índices de referencia Brent y WTI y por qué son importantes.
- Cómo utilizar funciones matriciales y vectoriales MQL5 para crear modelos de aprendizaje automático compactos que sean fáciles de mantener e implementar desde cero.
- Cómo emplear la técnica pseudoinversa para encontrar una solución de mínimos cuadrados para pronosticar el precio futuro del Brent, utilizando el spread WTI-Brent.
Petróleo crudo de referencia mundial: Brent
Cuando se extrae petróleo crudo del suelo, es una mezcla de algunas impurezas de oxígeno, carbono, hidrógeno y azufre. Brent es una clasificación dada a las mezclas de petróleo crudo que se consideran ligeras y dulces. Para considerarse dulce, la mezcla debe tener bajas concentraciones de impurezas de azufre. Además se llama ligero porque tiene baja densidad. Estas propiedades son deseables porque nos informan que la mezcla se refinará fácilmente. La última cualidad del Brent que vamos a destacar es que el Brent es de menor calidad que el WTI. El petróleo Brent se extrae principalmente en el Mar del Norte. Una vez extraído, se almacena fácilmente en barriles a bordo de grandes petroleros. Esto le da al Brent una clara ventaja sobre el WTI: es muy accesible. Actualmente el Brent se negocia con una prima respecto del WTI.
Figura 1: Precio histórico del Brent en MQL5.
Petróleo crudo norteamericano de referencia: West Texas Intermediate
El West Texas Intermediate (WTI) es una clasificación que se da a una determinada mezcla de crudo, debe ser un petróleo «dulce ligero». El WTI se extrae en todo Estados Unidos, pero principalmente en Texas. Es más dulce y ligero que el Brent, lo que significa que es más fácil de refinar para obtener productos terminados. Históricamente, se extraía en zonas sin salida al mar de EE. UU. y, por lo tanto, era mucho menos accesible que el Brent. Sin embargo, debido a las inversiones masivas realizadas en la Costa del Golfo y la derogación de la prohibición de exportación de petróleo en 2015, el WTI es ahora más accesible que nunca.
Figura 2: Precio histórico del WTI en MQL5.
Cómo empezar: Visualizar el spread
Para empezar podemos crear un práctico script para visualizar el diferencial (spread) entre las dos materias primas. Podemos utilizar la biblioteca de gráficos MQL5 para ayudarnos a trazar fácilmente cualquier función que deseemos. La biblioteca de gráficos administra el escalamiento por usted, lo cual siempre es útil tener. Después de incluir la biblioteca de gráficos, notará una variable definida como "consumption". Esta variable nos ayuda a seleccionar fácilmente la mitad, un cuarto o cualquier fracción de los datos totales disponibles.
Dado que estamos solicitando datos históricos sobre dos activos diferentes, necesitamos conocer el número total de barras disponibles en cada mercado. A partir de ahí, asumimos que el menor número de barras disponibles es el número total de barras disponibles. Utilizamos un operador ternario para seleccionar el número correcto de barras.
Después de haber determinado el número correcto de barras a utilizar, podemos trazar la dispersión.
//+------------------------------------------------------------------+ //| Brent-WTI Spread.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Graphics\Graphic.mqh> //Set this value between 0 and 1 to control how much data is used double consumption = 1.0; int brent_bars = (int) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0); int wti_bars = (int) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0); //We want to know which symbol has the least number of bars. int max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars; //+------------------------------------------------------------------+ //|This event handler is only triggered when the script launches | //+------------------------------------------------------------------+ void OnStart() { CGraphic graphic; double from = 0; double to = max_bars; double step = 1; graphic.Create(0,"G",0,0,0,600,200); CColorGenerator generator; uint spread = generator.Next(); CCurve *curve = graphic.CurveAdd(SpreadFunction,from,to,step,spread,CURVE_LINES,"Blue"); curve.Name("Spread"); graphic.XAxis().Name("Time"); graphic.XAxis().NameSize(12); graphic.YAxis().Name("Brent-WTI Spread"); graphic.YAxis().NameSize(12); graphic.CurvePlotAll(); graphic.Update(); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //|This function returns the Brent-WTI spread | //+------------------------------------------------------------------+ double SpreadFunction(double x) { return(iClose("UK Brent Oil",PERIOD_CURRENT,(max_bars - x)) - iClose("WTI_OIL",PERIOD_CURRENT,(max_bars - x))); } //+------------------------------------------------------------------+
Figura 3: Visualización del spread Brent-WTI en MQL5.
Visión general de nuestra estrategia de negociación: El empleo del aprendizaje automático supervisado
La premisa de la estrategia clásica del crudo era que; el equilibrio de precios siempre se restablecerá a largo plazo. La estrategia clásica de negociación de diferenciales de petróleo afirmaría que empezamos observando el diferencial actual entre el Brent y el WTI. Si el diferencial está por encima de su línea de base, por ejemplo la línea de base podría ser la media móvil de 20, entonces deduciríamos que el diferencial volverá a su media en un futuro próximo. Por lo tanto, si los precios del Brent subieran, venderíamos. Por el contrario, si los precios del Brent bajaran, compraríamos.
Sin embargo, desde que se desarrolló esta estrategia, el mercado del petróleo ha cambiado considerablemente. Necesitamos un procedimiento objetivo para deducir qué relaciones existen entre el diferencial y el precio futuro del Brent. El aprendizaje automático permite al ordenador aprender sus propias reglas de negociación a partir de cualquier relación que pueda observar analíticamente.
Para que nuestro ordenador pueda crear su propia estrategia de negociación, partimos de una matriz de datos A.
A simboliza los datos históricos de precios que tenemos disponibles sobre el Brent. Utilizaremos el precio de cierre, el diferencial y un intercepto que tiene un valor constante de uno. A continuación, construiremos un vector de columnas separado, x, que tendrá 1 coeficiente por cada columna de A. Este valor se calculará directamente a partir de los datos del mercado, y será utilizado por nuestro modelo para prever el precio futuro.
Figura 4: Encuadre del problema de mínimos cuadrados.
Después de crear nuestra matriz de entradas A, necesitamos saber qué precios de cierre del Brent se emparejaron con cada una de las entradas en A. Almacenaremos el precio de salida en un vector, y. Nuestro objetivo es encontrar una forma de mapear la matriz de datos de entrada A al vector de datos de salida y aproximando el menor error posible a través de todas las observaciones de entrenamiento que tenemos. La respuesta a este problema se denomina solución de mínimos cuadrados.
Figura 5: Nuestro vector de salida y.
Figura 6: Introducción de la solución pseudoinversa.
Las dos ecuaciones anteriores nos dicen primero que estamos buscando un valor de x que minimice el error entre nuestra predicción, A*x , y el precio de cierre real del Brent, y. Observe las líneas verticales dobles alrededor de Ax-y. Estas líneas verticales dobles representan la norma L2. Cuando tratamos con objetos físicos en el mundo real, podemos preguntarnos "¿Qué tan grande es?". Sin embargo, cuando queremos saber qué tan grande es un vector o una matriz, pedimos su norma. Hay diferentes formas de calcular la norma, la más frecuente es la norma L1 o L2. Para nuestra discusión sólo consideraremos la norma L2.
La norma L2 se calcula elevando al cuadrado cada entidad del vector, sumando todos los valores al cuadrado y luego calculando la raíz cuadrada de la suma. También se llama norma euclidiana. En un lenguaje más simple diríamos "Estamos buscando valores de x que reduzcan el tamaño de todos los errores que comete nuestro modelo", y en un lenguaje más técnico diríamos "Encontrar valores óptimos de x que minimicen la norma L2 de los residuos".
El valor de x que satisface nuestras restricciones se denota x*. Para encontrar x* calculamos el producto escalar de la pseudoinversa de A y y. Es muy poco probable que alguna vez necesites implementar la función pseudoinversa tú mismo, a menos que sea como un ejercicio de álgebra lineal. De lo contrario, confiaremos en la función incorporada en MQL5.
//+------------------------------------------------------------------+ //|Demonstrating the pseudo-inverse solution in action. | | //+------------------------------------------------------------------+ void OnStart() { //Training and test data matrix A; //A is the input data. look at the figure above if you need a reminder. matrix y,x; //y is the output data, x is the coefficients. A.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_OHLC,20,1000); y.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,1,1000); A.Reshape(1000,4); y.Reshape(1000,1); Print("Attempting Psuedoinverse Decomposition"); Print("Attempting to calculate the Pseudoinverse Coefficients: "); x = A.PInv().MatMul(y); Print("Coefficients: "); Print("Open: ",x[0][0],"\nHigh: ",x[1][0],"\nLow: ",x[3][0],"\nClose: ",x[3][0]); } //+------------------------------------------------------------------+
Figura 7: Un ejemplo de aplicación de la técnica pseudoinversa.
El código anterior proporciona una demostración sencilla de cómo utilizar la técnica pseudoinversa. En este ejemplo, pretendemos predecir el precio de cierre de un símbolo utilizando sus precios de apertura, máximo, mínimo y cierre actuales. Este sencillo ejemplo resume los principios básicos que debemos comprender. Comenzamos definiendo nuestros datos de entrada, que se almacenan en la matriz A. Para obtener los datos, utilizamos la función CopyRates(), que requiere los siguientes parámetros en el orden especificado:
- Symbol name: El nombre del símbolo con el que deseamos operar.
- Timeframe: El marco temporal que se alinea con nuestros niveles de riesgo.
- Rates mask: Esto especifica qué precios copiar, permitiéndonos seleccionar, por ejemplo, sólo los precios de apertura si lo deseamos.
- From: La fecha de inicio de la copia de los datos, garantizando un desfase entre los datos de entrada y los de salida y que los datos de entrada comiencen a partir de una fecha anterior.
- Count: El número de velas a copiar.
Tras establecer la matriz de datos de entrada A, repetimos el proceso para la matriz de datos de salida y. A continuación, remodelamos ambas matrices para asegurarnos de que tienen el tamaño adecuado y son compatibles para las operaciones que pretendemos realizar.
A continuación, poblamos el vector columna x con valores derivados de A e y. Afortunadamente, la API MQL5 admite el encadenamiento de operaciones matriciales, lo que nos permite calcular la solución pseudoinversa con una sola línea de código. Una vez completado, podemos imprimir los coeficientes en nuestro vector columna x.
Utilizaremos los mismos pasos para desarrollar nuestra estrategia de negociación. El único paso adicional, no demostrado aquí, es utilizar nuestro modelo para hacer predicciones, lo que se explicará más adelante en nuestra discusión. Con esta base, estamos listos para empezar a construir nuestra estrategia de negociación.
Poniéndolo todo junto
Ahora estamos listos para definir el corazón de nuestro algoritmo. En primer lugar, incluimos la biblioteca comercial necesaria para que podamos abrir y gestionar posiciones.
//+------------------------------------------------------------------+ //| Brent EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //Libraries #include <Trade\Trade.mqh> CTrade ExtTrade; #include <TrailingStop\ATRTrailingStop3.mqh> ATRTrailingStop ExtATRTrailingStop;
Posteriormente, definimos el tamaño de nuestra posición de negociación y los parámetros de riesgo. La primera entrada determina cuántas veces mayor que el lote mínimo será cada posición. La segunda entrada establece el nivel de beneficio al que se cerrarán todas las posiciones abiertas. Le sigue el parámetro de entrada que limita la detracción total (drawdown) que permitiremos en esta cuenta. Y por último, establecemos cuántas posiciones queremos abrir cada vez que realizamos una operación.
//Inputs input double lot_multiple = 1.0; input double profit_target = 10; input double max_loss = 20; input int position_size = 2;
Continuando, ahora necesitamos saber cuántas barras están disponibles en cada mercado para asegurarnos de que siempre intentamos copiar el número correcto de barras que estarán disponibles en ambos mercados. El «número correcto» en nuestro caso, es el menor número de barras disponibles. También definimos una variable llamada «consumption» porque nos permite controlar la cantidad de datos que queremos utilizar, en el ejemplo de código siguiente estamos utilizando el 1% de todos los datos históricos disponibles.
//Set this value between 0 and 1 to control how much data is used double consumption = 0.01; //We want to know which symbol has the least number of bars. double brent_bars = (double) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0); double wti_bars = (double) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0);
Aquí es donde realmente determinamos qué mercado tiene menos barras disponibles, y utilizamos ese número de barras como nuestro límite. Si nos saltamos este paso, es posible que las fechas entre los dos mercados no se alineen, a menos que su corredor le garantice conjuntos de datos parejos sobre los precios históricos de ambos activos. «look_ahead» es nuestro horizonte de previsión, o cuántos pasos hacia el futuro estamos previendo.
//Select the lowest double max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars; //How far into the future are we forecasting double look_ahead = NormalizeDouble((max_bars / 4),0); //How many bars should we fetch? int fetch = (int) (max_bars - look_ahead) - 1;
Continuando, ahora necesitamos definir variables para las que definimos en nuestra notación; incluiré una copia de la imagen para que no tenga que desplazarse hacia arriba. Recuerde, A es la matriz que almacena nuestros datos de entrada, podemos elegir tantas o tan pocas entradas como deseemos, en este ejemplo utilizaré 3 entradas. x* representa el valor de x que minimiza la norma L2 de nuestros residuos.
Figura 6: Recordatorio de la notación que hemos definido.
//Matrix A stores our inputs. y is the output. x is the coefficients. matrix A = matrix::Zeros(fetch,6); matrix y = matrix::Zeros(fetch,1); vector wti_price = vector::Zeros(fetch); vector brent_price = vector::Zeros(fetch); vector spread; vector intercept = vector::Ones(fetch); matrix x = matrix::Zeros(6,1); double forecast = 0; double ask = 0; double bid = 0; double min_volume = 0;
Definiremos dos variables de cadena para almacenar los nombres de los símbolos con los que deseamos operar. Tras completar esto, hemos llegado a nuestra función OnInit(). Esta función es sencilla en nuestro caso, sólo necesitamos conocer el volumen mínimo de negociación permitido en el Brent.
string brent = "UK Brent Oil"; string wti = "WTI_OIL"; bool model_initialized = false; int OnInit() { //Initialise trailing stops if(atr_multiple > 0) ExtATRTrailingStop.Init(atr_multiple); min_volume = SymbolInfoDouble(brent,SYMBOL_VOLUME_MIN); return(INIT_SUCCEEDED); //--- }
Ahora estamos trabajando en nuestra función OnTick(). Dentro del cuerpo, primero actualizamos los precios de la oferta y la demanda que estamos siguiendo. A continuación comprobamos si nuestro modelo ha sido inicializado, si no lo ha sido será entrenado y ajustado de lo contrario si lo ha sido pasamos a comprobar si tenemos alguna posición abierta. En el caso de que no tengamos posiciones abiertas, obtendremos una previsión de nuestro modelo y entonces operaremos en la dirección que nuestro modelo esté pronosticando. De lo contrario, si tenemos posiciones abiertas, comprobaremos si nuestras posiciones no han superado el objetivo de beneficios o el nivel máximo de retirada.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- ask = SymbolInfoDouble(brent,SYMBOL_ASK); bid = SymbolInfoDouble(brent,SYMBOL_BID); if(model_initialized) { if(PositionsTotal() == 0) { forecast = 0; forecast = ModelForecast(); InterpretForecast(); } else { ManageTrades(); } } else { model_initialized = InitializeModel(); } } //+------------------------------------------------------------------+
Es la función responsable de comprobar si hemos superado los niveles de riesgo o alcanzado nuestro objetivo de beneficios. Sólo se llama en el manejador de eventos OnTick() en condiciones en las que tenemos operaciones abiertas.
void ManageTrades() { if(AccountInfoDouble(ACCOUNT_PROFIT) > profit_target) CloseAll(); if(AccountInfoDouble(ACCOUNT_PROFIT) < (-1 * max_loss)) CloseAll(); }
Siempre que nuestro modelo haya realizado una previsión, llamaremos a InterpretForecast() para que dé sentido a las predicciones de nuestro modelo y abra las posiciones adecuadas en respuesta.
void InterpretForecast() { if(forecast != 0) { if(forecast > iClose(_Symbol,PERIOD_CURRENT,0)) { check_buy(); } else if(forecast < iClose(_Symbol,PERIOD_CURRENT,0)) { check_sell(); } } }
Disponemos de un procedimiento específico para entrar en posiciones de compra. Observe que el volumen mínimo que determinamos anteriormente se multiplica por el múltiplo de lote introducido, lo que permite al usuario controlar el tamaño del lote utilizado para introducir operaciones.
void check_buy() { if(PositionsTotal() == 0) { for(int i = 0; i < position_size; i++) { ExtTrade.Buy(lot_multiple * min_volume,brent,ask,0,0,"BUY"); } } }
También he incluido procedimientos específicos para entrar en posiciones cortas, lo he hecho por si nos damos cuenta de reglas específicas que se aplican exclusivamente a cualquiera de los dos lados de la posición.
void check_sell() { if(PositionsTotal() == 0) { for(int i = 0; i < position_size; i++) { ExtTrade.Sell(lot_multiple * min_volume,brent,bid,0,0,"SELL"); } } }
Ahora definimos una función que cerrará todas las posiciones abiertas que tengamos. Hace un bucle a través de las posiciones abiertas que tenemos y sólo cierra las posiciones abiertas bajo Brent. Tenga en cuenta que si quiere poder operar tanto con Brent como con WTI utilizando este EA sólo tiene que eliminar las comprobaciones de seguridad que he puesto para asegurarme de que el símbolo es Brent. Recuerde que sólo elegí a Brent a efectos de demostración. Usted es libre de personalizar el EA.
void CloseAll(void) { for(int i=PositionsTotal()-1; i>=0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetSymbol(i) == brent) { ulong ticket; ticket = PositionGetTicket(i); ExtTrade.PositionClose(ticket); } } } }
A continuación definiremos 2 métodos para cerrar posiciones largas y cortas respectivamente. De nuevo como antes, lo conseguimos iterando sobre todas las posiciones y obteniendo el ticket respectivo para cada posición. A continuación, validamos que el tipo de posición coincide con el tipo que buscamos. Si todo va bien, cerraremos la posición.
void close_buy() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal(); i++) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { ExtTrade.PositionClose(ticket); } } } } void close_sell() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal(); i++) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_SELL) { ExtTrade.PositionClose(ticket); } } } }
Ahora definiremos cómo debe inicializarse nuestro modelo:
- Asegúrese de que ambos símbolos están disponibles y se han añadido a la ventana del mercado.
- Copie los datos de salida en la matriz y (El precio de cierre del Brent, a partir de la vela 1).
- Copie los datos de entrada en la matriz A (El precio de cierre del Brent, a partir de 1 más nuestro horizonte de previsión).
- Remodele la matriz de datos A.
- Calcule el diferencial entre el Brent y el WTI y súmelo a A.
- Añada una fila de 1 en A para el intercepto.
- Transponga tanto A como y.
Una vez completados estos pasos comprobaremos si nuestros datos de entrada son válidos, en caso contrario registraremos un mensaje de error. Si es válida, pasaremos a calcular la matriz de coeficientes x.
bool InitializeModel() { //Try select the symbols if(SymbolSelect(brent,true) && SymbolSelect(wti,true)) { Print("Symbols Available. Bars: ",max_bars," Fetch: ",fetch," Look ahead: ",look_ahead); //Get historical data on Brent , our model output y.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch); //model input A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1 + look_ahead),fetch); brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch); wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch); //Calculate the spread spread = brent_price - wti_price; Print("The Current Spread: ",spread); A.Reshape(3,fetch); //Add the spread to the input matrix A.Row(spread,1); //Add a column for the intercept A.Row(intercept,2); //Reshape the matrices A = A.Transpose(); y = y.Transpose(); //Inspect the matrices if((A.Cols() == 0 || y.Cols() == 0)) { Print("Error occured when copying historical data"); Print("A rows: ",A.Rows()," y rows: ",y.Rows()," A columns: ",A.Cols()," y cols: ",y.Cols()); Print("A"); Print(A); Print("y"); Print(y); return(false); } else { Print("No errors occured when copying historical data"); x = A.PInv().MatMul(y); Print("Finished Fitting The Model"); Print(x); return(true); } } Print("Faield to select symbols"); return(false); }
Por último, necesitamos definir una función para prever los valores futuros del precio de cierre del Brent.
double ModelForecast() { if(model_initialized) { //model input A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); //Calculate the spread spread = brent_price - wti_price; Print("The Spread: ",spread); A.Reshape(3,fetch); //Add the spread to the input matrix A.Row(spread,1); //Add a column for the intercept A.Row(intercept,2); //Reshape the matrices A = A.Transpose(); double _forecast = (A[0][0]*x[0][0]) + (A[1][0]*x[1][0]) + (A[2][0]*x[2][0]); return(_forecast); } return(0); }
Juntándolo todo, esto es lo que suma nuestra aplicación.
//+------------------------------------------------------------------+ //| Brent EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //Libraries #include <Trade\Trade.mqh> CTrade ExtTrade; #include <TrailingStop\ATRTrailingStop3.mqh> ATRTrailingStop ExtATRTrailingStop; //Inputs input double atr_multiple = 5.0; input double lot_multiple = 1.0; input double profit_target = 10; input double max_loss = 20; input int position_size = 2; //Set this value between 0 and 1 to control how much data is used double consumption = 0.01; //We want to know which symbol has the least number of bars. double brent_bars = (double) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0); double wti_bars = (double) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0); //Select the lowest double max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars; //How far into the future are we forecasting double look_ahead = NormalizeDouble((max_bars / 4),0); //How many bars should we fetch? int fetch = (int)(max_bars - look_ahead) - 1; //Matrix A stores our inputs. y is the output. x is the coefficients. matrix A = matrix::Zeros(fetch,6); matrix y = matrix::Zeros(fetch,1); vector wti_price = vector::Zeros(fetch); vector brent_price = vector::Zeros(fetch); vector spread; vector intercept = vector::Ones(fetch); matrix x = matrix::Zeros(6,1); double forecast = 0; double ask = 0; double bid = 0; double min_volume = 0; string brent = "UK Brent Oil"; string wti = "WTI_OIL"; bool model_initialized = false; int OnInit() { //Initialise trailing stops if(atr_multiple > 0) ExtATRTrailingStop.Init(atr_multiple); min_volume = SymbolInfoDouble(brent,SYMBOL_VOLUME_MIN); return(INIT_SUCCEEDED); //--- } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- ask = SymbolInfoDouble(brent,SYMBOL_ASK); bid = SymbolInfoDouble(brent,SYMBOL_BID); if(model_initialized) { if(PositionsTotal() == 0) { forecast = 0; forecast = ModelForecast(); InterpretForecast(); } else { ManageTrades(); } } else { model_initialized = InitializeModel(); } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //|This function closes trades if we reach our profit or loss limit | | //+------------------------------------------------------------------+ void ManageTrades() { if(AccountInfoDouble(ACCOUNT_PROFIT) > profit_target) CloseAll(); if(AccountInfoDouble(ACCOUNT_PROFIT) < (-1 * max_loss)) CloseAll(); } //+------------------------------------------------------------------+ //|This function judges if our model is giving a long or short signal| | //+------------------------------------------------------------------+ void InterpretForecast() { if(forecast != 0) { if(forecast > iClose(_Symbol,PERIOD_CURRENT,0)) { check_buy(); } else if(forecast < iClose(_Symbol,PERIOD_CURRENT,0)) { check_sell(); } } } //+------------------------------------------------------------------+ //|This function checks if we can open buy positions | //+------------------------------------------------------------------+ void check_buy() { if(PositionsTotal() == 0) { for(int i = 0; i < position_size; i++) { ExtTrade.Buy(lot_multiple * min_volume,brent,ask,0,0,"BUY"); } } } //+------------------------------------------------------------------+ //|This function checks if we can open sell positions | //+------------------------------------------------------------------+ void check_sell() { if(PositionsTotal() == 0) { for(int i = 0; i < position_size; i++) { ExtTrade.Sell(lot_multiple * min_volume,brent,bid,0,0,"SELL"); } } } //+------------------------------------------------------------------+ //|This function will close all open trades | //+------------------------------------------------------------------+ void CloseAll(void) { for(int i=PositionsTotal()-1; i>=0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetSymbol(i) == brent) { ulong ticket; ticket = PositionGetTicket(i); ExtTrade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ //|This function closes any open buy trades | //+------------------------------------------------------------------+ void close_buy() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal(); i++) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { ExtTrade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ //|This function closes any open sell trades | //+------------------------------------------------------------------+ void close_sell() { ulong ticket; int type; if(PositionsTotal() > 0) { for(int i = 0; i < PositionsTotal(); i++) { ticket = PositionGetTicket(i); type = (int)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_SELL) { ExtTrade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ //|This function initializes our model and fits it onto the data | //+------------------------------------------------------------------+ bool InitializeModel() { //Try select the symbols if(SymbolSelect(brent,true) && SymbolSelect(wti,true)) { Print("Symbols Available. Bars: ",max_bars," Fetch: ",fetch," Look ahead: ",look_ahead); //Get historical data on Brent , our model output y.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch); //model input A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1 + look_ahead),fetch); brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch); wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch); //Calculate the spread spread = brent_price - wti_price; Print("The Current Spread: ",spread); A.Reshape(3,fetch); //Add the spread to the input matrix A.Row(spread,1); //Add a column for the intercept A.Row(intercept,2); //Reshape the matrices A = A.Transpose(); y = y.Transpose(); //Inspect the matrices if((A.Cols() == 0 || y.Cols() == 0)) { Print("Error occured when copying historical data"); Print("A rows: ",A.Rows()," y rows: ",y.Rows()," A columns: ",A.Cols()," y cols: ",y.Cols()); Print("A"); Print(A); Print("y"); Print(y); return(false); } else { Print("No errors occured when copying historical data"); x = A.PInv().MatMul(y); Print("Finished Fitting The Model"); Print(x); return(true); } } Print("Faield to select symbols"); return(false); } //+------------------------------------------------------------------+ //|This function makes a prediction once our model has been trained | //+------------------------------------------------------------------+ double ModelForecast() { if(model_initialized) { //model input A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1); //Calculate the spread spread = brent_price - wti_price; Print("The Spread: ",spread); A.Reshape(3,fetch); //Add the spread to the input matrix A.Row(spread,1); //Add a column for the intercept A.Row(intercept,2); //Reshape the matrices A = A.Transpose(); double _forecast = (A[0][0]*x[0][0]) + (A[1][0]*x[1][0]) + (A[2][0]*x[2][0]); return(_forecast); } return(0); } //+------------------------------------------------------------------+
Ahora estamos listos para volver a probar nuestro algoritmo de negociación utilizando el construido en el Probador de Estrategias de MetaTrader 5.
Figura 7: Prueba retrospectiva de nuestro algoritmo de negociación cuantitativa.
Fig 8: Rentabilidad histórica de nuestra prueba retrospectiva.
Conclusión
Hay margen de mejora en la estrategia que hemos considerado hoy, por ejemplo, el 67% de todas las reservas de petróleo conocidas en el mundo se encuentran en Oriente Próximo, pero no hemos considerado ninguna de las referencias petrolíferas del Golfo Pérsico. Además, hay otros diferenciales perspicaces que pueden tener cualidades predictivas que merecen una mayor investigación, como el diferencial de grietas. El "crack spread", que representa la diferencia entre el precio del crudo y los productos refinados como la gasolina y el diésel, mide la rentabilidad de las refinerías. Históricamente, cuando los diferenciales del crack son altos, la oferta tiende a aumentar y cuando los diferenciales del crack son bajos, la oferta tiende a caer. Si ha leído el artículo hasta aquí, debería ver enseguida las posibles implicaciones que el diferencial del crack puede tener en el precio del crudo.
Nuestra estrategia es rentable, pero es susceptible de sufrir periodos irregulares de detracción. Los mercados del petróleo son notoriamente volátiles, y se lograrán mejoras a pasos agigantados aplicando principios más sólidos de gestión del riesgo que sigan siendo rentables.
Le deseo paz, prosperidad y operaciones rentables.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/14855
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso