
Reconocimiento de patrones mediante deformación dinámica del tiempo (Dynamic Time Warping, DTW) en MQL5
Introducción
El reconocimiento de patrones siempre ha sido una herramienta valiosa para los traders. Ya sea identificando combinaciones únicas de velas o dibujando líneas imaginarias en un gráfico, estos patrones se han convertido en una parte integral del análisis técnico. Los seres humanos siempre hemos destacado por encontrar y reconocer patrones, hasta el punto de que se suele decir que a veces vemos patrones donde no los hay. Por lo tanto, nos beneficiaría aplicar técnicas más objetivas a la hora de identificar patrones potencialmente rentables en las series temporales financieras. En este artículo, analizamos la aplicación de la deformación dinámica del tiempo (Dynamic Time Warping, DTW) como una técnica objetiva para encontrar patrones únicos en datos de precios. Exploraremos sus orígenes, su funcionamiento y su aplicación al análisis de series temporales financieras. Además, presentaremos la implementación del algoritmo en MQL5 y demostraremos su uso a través de un ejemplo práctico.
Deformación dinámica del tiempo
La deformación dinámica del tiempo es un sofisticado algoritmo diseñado para medir la similitud entre dos secuencias de datos que evolucionan en el tiempo, incluso cuando sus velocidades o ritmos varían. A diferencia de los métodos tradicionales, que exigen una alineación estricta entre los puntos de datos, DTW ofrece un enfoque más flexible al permitir deformar o estirar el tiempo para encontrar la coincidencia óptima entre las secuencias. Imagina a dos personas caminando por un bosque por senderos diferentes. Ambos empiezan en el mismo lugar y terminan en el mismo lugar, pero uno puede caminar más rápido que el otro y hacer paradas arbitrarias por cualquier motivo. DTW ayuda a encontrar la mejor manera de hacer coincidir los pasos de ambos, aunque hayan tomado caminos diferentes. DTW puede tener en cuenta eficazmente las diferencias de velocidad de marcha, aceleración o desaceleración, proporcionando una medida de similitud. Esta versatilidad lo hace aplicable a una amplia gama de tipos de datos, incluidos audio, vídeo y gráficos. Cualquier dato que pueda transformarse a un formato secuencial es un candidato potencial para el análisis DTW.
DTW fue desarrollado inicialmente para el reconocimiento del habla por Taras Vintsiuk en 1968 y posteriormente perfeccionado por investigadores como Sakoe y Chiba en 1978. El método se ha impuesto en varios campos por su capacidad para comparar secuencias de longitudes diferentes deformando la dimensión temporal para alinear patrones similares. Desde entonces, DTW ha encontrado aplicaciones en diversos ámbitos, como la bioinformática y el análisis de la marcha. Los mercados financieros se caracterizan por series temporales de datos no lineales y no estacionarios. Las medidas de distancia tradicionales, como la distancia euclidiana, pueden no captar las complejidades y distorsiones de estos datos. Sin embargo, DTW destaca en la identificación de similitudes entre series temporales con distorsiones temporales, lo que lo convierte en una herramienta adecuada para analizar los mercados financieros. Al alinear las secuencias basándose en su forma y no en su alineación temporal, DTW puede descubrir patrones únicos en los datos financieros.
¿Cómo funciona?
Al aplicar el algoritmo DTW, el objetivo es determinar si una secuencia candidata es similar a una secuencia de referencia predefinida. La métrica final producida por el algoritmo indica la similitud entre la secuencia candidata y la de referencia: cuanto menor sea este valor, más similares son las secuencias. Si las secuencias son iguales, este valor sería 0. Para encontrar la alineación óptima entre una secuencia candidata y un conjunto de datos de referencia, el algoritmo DTW se adhiere a unos principios específicos. Cada punto de la secuencia candidata debe corresponder al menos a un punto de la secuencia de referencia, y viceversa. El principio de una secuencia debe coincidir con el principio de la otra, y el final de una debe coincidir con el final de la otra. Además, al recorrer las secuencias, los puntos correspondientes deben avanzar sólo hacia delante. Esto garantiza que ningún punto de una secuencia pueda alinearse con un punto que le preceda en la otra secuencia, impidiendo cualquier posibilidad de alineación con puntos anteriores.
El proceso comienza con la construcción de una matriz de distancias que recoge las distancias por pares entre los elementos de las dos secuencias. Cada elemento de esta matriz representa la distancia entre un punto de la primera secuencia y un punto correspondiente de la segunda secuencia. En esta fase, se puede emplear cualquiera de las medidas tradicionales de distancia espacial. Las medidas de distancia espacial son métodos matemáticos utilizados para cuantificar la distancia o disimilitud entre puntos de datos dentro de un espacio determinado. La elección de la medida de distancia depende de las características específicas de los datos y de la tarea a realizar. En la tabla siguiente se enumeran las medidas de distancia espacial más habituales y una breve descripción de cada una de ellas.
Distancia métrica | Descripción |
---|---|
Distancia euclidiana | La distancia euclidiana es la distancia en línea recta entre dos puntos en el espacio euclidiano. Es la medida de distancia más intuitiva y utilizada. |
Geometría del taxista | La geometría del taxista mide la distancia entre dos puntos sumando las diferencias absolutas de sus coordenadas. |
Distancia de Minkowski | La distancia de Minkowski es una forma generalizada de las distancias euclidiana y del taxista. Incluye un parámetro que puede ajustarse para cambiar entre distintas medidas de distancia. |
Distancia de Chebyshov | La distancia de Chebyshov, también conocida como distancia máxima o de tablero de ajedrez, mide la diferencia más significativa entre las coordenadas de un par de puntos. |
Similitud coseno | La similitud coseno mide el coseno del ángulo entre dos vectores distintos de cero, lo que representa su similitud direccional en lugar de su magnitud. |
La elección de la medida de distancia espacial depende de la naturaleza de los datos y de los requisitos específicos de la tarea. Comprender las diferencias y aplicaciones de estas medidas ayuda a seleccionar la más adecuada para un análisis significativo.
En el siguiente paso, la matriz de distancias se utiliza para calcular la matriz de costes acumulados, donde cada elemento representa el coste mínimo de alinear las secuencias. Este proceso de alineación implica transformar la secuencia candidata para que se parezca mucho a la secuencia de referencia. La matriz de costes acumulativos cuantifica en qué medida deben modificarse los puntos individuales de la secuencia candidata para ajustarse lo mejor posible a los valores de referencia. Un coste mayor indica una transformación más significativa, lo que significa una mayor disimilitud entre las secuencias. Estos valores de la matriz de costes acumulados se utilizan para determinar la trayectoria de deformación óptima, que representa la secuencia de alineaciones que asignan una serie temporal a otra minimizando la distancia entre ellas. Esencialmente, la trayectoria de deformación ilustra cómo se estiran o comprimen partes de una secuencia para alinearse lo más estrechamente posible con otra secuencia.
Para encontrar la trayectoria de deformación, imaginemos las secuencias colocadas en una cuadrícula, con una secuencia a lo largo del eje 'x' y la otra a lo largo del eje 'y'. Cada punto de esta cuadrícula representa una posible coincidencia entre un elemento de la primera secuencia y un elemento de la segunda secuencia. La ruta de deformación es la ruta a través de esta cuadrícula que da como resultado la mejor alineación entre las dos secuencias. La ruta comienza en la esquina inferior izquierda de la cuadrícula, correspondiente a los primeros elementos de ambas secuencias, y finaliza en la esquina superior derecha, correspondiente a los últimos elementos de ambas secuencias. A medida que avanza la ruta, puede moverse en tres direcciones: diagonalmente, que coincide con elementos de ambas secuencias; horizontalmente, que repite un elemento de la primera secuencia; o verticalmente, que repite un elemento de la segunda secuencia. Este movimiento garantiza que el proceso de alineación siempre avance en el tiempo y no omita ningún elemento en las secuencias.
La ruta de deformación se elige para minimizar la distancia o el costo acumulativo entre los elementos alineados de las dos secuencias. Para lograr esto, los valores de la matriz de distancias se superponen en la cuadrícula que se utilizará para alinear los puntos. El coste en cada punto de la cuadrícula se calcula sumando la métrica de distancia correspondiente al valor mínimo de las distancias acumuladas adyacentes de los puntos anteriores, siguiendo las tres direcciones posibles (diagonal, horizontal o vertical) que se pueden tomar al avanzar en el tiempo. Dado que cada punto de la cuadrícula está asociado con la métrica de distancia de los valores de secuencia correspondientes, el valor mínimo representa la operación requerida para transformar la secuencia candidata para que coincida con la secuencia de referencia. Si la distancia acumulada mínima se encuentra en diagonal al punto actual, representa una operación de coincidencia. Si la distancia acumulada más pequeña desde los puntos anteriores es horizontal al punto actual, indica una operación de inserción, donde un valor de la secuencia de referencia se inserta efectivamente en la secuencia candidata. Por el contrario, si la distancia acumulada mínima es vertical al punto actual, significa una operación de eliminación, donde se elimina un valor de la secuencia candidata. La elección de los valores de distancia acumulada previos considerados para encontrar el mínimo está gobernada por un patrón de pasos específico.
Los patrones de pasos determinan los movimientos o transiciones permitidos entre puntos en las secuencias durante la alineación. Especifican el número de puntos en cualquiera de las direcciones válidas (diagonal, horizontal, vertical) consideradas al calcular la distancia mínima acumulada. La elección del patrón de pasos influye significativamente en el proceso de alineación y en el cálculo de la distancia general. Existen varios patrones de pasos, cada uno adaptado para aplicaciones específicas. Los practicantes también pueden crear patrones de pasos personalizados, siempre que respeten los principios de movimiento hacia adelante y continuidad. En este texto, nos centraremos en los patrones de pasos más comunes y genéricos, que han demostrado ser eficaces en diferentes dominios. Para quienes estén interesados en explorar otros patrones de pasos, los trabajos de Sakoe-Chiba, Rabiner-Juang y Rabiner-Myers ofrecen perspectivas en profundidad.
El patrón de pasos más comúnmente aplicado es el patrón de pasos estándar, también conocido como patrón de pasos simétrico o clásico. Su principal ventaja es que garantiza que ambas series temporales se recorren en su totalidad. Este patrón considera sólo los valores inmediatos por encima, a la derecha y en diagonal al punto actual, permitiendo movimientos diagonales, horizontales y verticales de un solo paso en la cuadrícula.
Otro patrón de pasos popular es el clásico patrón asimétrico. Similar al patrón de paso estándar, define transiciones en tres direcciones pero introduce un sesgo hacia una secuencia. Este patrón permite que el algoritmo se mueva en diagonal, pero si se omite un punto, favorece avanzar en una secuencia más que en la otra. A menudo se utiliza junto con restricciones adicionales que limitan los movimientos del algoritmo en la cuadrícula. En el caso del patrón de paso asimétrico, se aplica una restricción de pendiente adicional, que restringe la inclinación o planitud de la trayectoria de deformación. Esta restricción es útil cuando se espera que las secuencias sean similares en longitud y desplazamiento, ya que evita que una secuencia se estire o comprima demasiado con respecto a la otra.
Se pueden aplicar restricciones adicionales para evitar alineaciones que sean técnicamente perfectas pero sin sentido, lo que puede provocar un sobreajuste de los datos. Estas restricciones garantizan que la alineación tenga sentido lógico dentro del contexto de los datos que se están analizando, evitando el estiramiento o la compresión excesivos de las secuencias. Estas restricciones se conocen como restricciones globales y mejoran la eficacia y la interpretabilidad del algoritmo DTW en aplicaciones prácticas. Dos restricciones globales comunes son la banda Sakoe-Chiba y el paralelogramo de Itakura.
La banda Sakoe-Chiba restringe la trayectoria de deformación para que permanezca dentro de una banda fija alrededor de la diagonal de la matriz de alineación. Esto evita que la alineación se desvíe demasiado del tiempo o cambio original, lo que resulta útil en tareas en las que se esperan ligeras diferencias de tiempo, pero no grandes desviaciones.
La restricción del paralelogramo de Itakura define una región con forma de paralelogramo dentro de la cual debe permanecer la trayectoria de deformación. Es más estrecho en los extremos y más ancho en el medio, lo que es particularmente útil cuando se espera que las secuencias se alineen más de cerca al principio y al final, pero permitan cierta flexibilidad en el medio. Las restricciones globales son esenciales para que el DTW controle el proceso de deformación y garantice que la alineación resultante sea relevante para la tarea específica. Es importante seleccionar las restricciones más apropiadas en función de las características de los conjuntos de datos y los objetivos del análisis.
Una vez completada la matriz de costos acumulativos, se utiliza para determinar la trayectoria de deformación óptima. El algoritmo rastrea desde el último elemento de la matriz de costos acumulativos hasta el primero, identificando la ruta que minimiza el costo total de alineación. Esta ruta representa la mejor alineación entre las secuencias. El valor final de la matriz de costo acumulativo proporciona la puntuación de distancia general, que también se utiliza para calcular la puntuación de distancia normalizada. La ruta de deformación enumerará los índices emparejados que representan puntos alineados en ambas secuencias. Para evaluar el procedimiento de alineación, es común trazar la trayectoria de deformación óptima. Un resultado satisfactorio debería producir un gráfico aproximadamente diagonal. Este gráfico diagonal indica que las secuencias están bien alineadas, con una línea diagonal perfectamente recta que corresponde a dos secuencias idénticas. En la siguiente sección discutiremos una implementación nativa de MQL5 de deformación temporal dinámica.
Implementación en MQL5
La implementación de DTW en MQL5 se presenta en el archivo de inclusión 'dtw.mqh'. No ofrece una cobertura completa ni extensa de todos los aspectos de la deformación dinámica del tiempo. Más adelante, veremos una implementación en Python del DTW que ofrece muchas más funciones. El objetivo de crear esta biblioteca es proporcionar la funcionalidad básica para calcular la puntuación de distancia. La biblioteca es una bifurcación parcial del módulo 'dtw-python'.
El código comienza definiendo enumeraciones para patrones de pasos y varias medidas de distancia y restricciones globales.
//+------------------------------------------------------------------+ //| step patterns | //+------------------------------------------------------------------+ enum ENUM_STEP_PATTERN { STEP_SYMM1=0,//symmetric1 STEP_SYMM2,//symmetric2 STEP_ASYMM//asymmetric };
ENUM_STEP_PATTERN: Define un conjunto de enumeraciones que restringen los patrones de transición permitidos durante la fase de búsqueda de ruta del algoritmo DTW. Estos patrones guían el proceso de deformación e influyen en los alineamientos permitidos entre las series temporales.
//+------------------------------------------------------------------+ //| distance metric | //+------------------------------------------------------------------+ enum ENUM_DIST_METRIC { DIST_EUCLIDEAN=0,//euclidean DIST_CITYBLOCK,//city block DIST_COSINE,//cosine DIST_CORRELATION,//correlation DIST_CHEBYSHEV,//chebyshev DIST_SQEUCLIDEAN//squared euclidean };
ENUM_DIST_METRIC: Proporciona una colección de enumeraciones correspondientes a las métricas de distancia admitidas. Estas métricas cuantifican la disimilitud entre los puntos de datos de la serie temporal, con opciones como la distancia euclidiana, la distancia City Block, etc.
//+------------------------------------------------------------------+ //| window function | //+------------------------------------------------------------------+ enum ENUM_GLOBAL_CONSTRAINT { CONSTRAINT_NONE=0,// no constrains applied CONSTRAINT_SAKOECHIBA,// sakoe chiba CONSTRAINT_SLATEDBAND,// slated band CONSTRAINT_ITAKURA// itakura };
ENUM_GLOBAL_CONSTRAINT: Abarca diferentes restricciones globales que imponen limitaciones durante la búsqueda de rutas. Éstas pueden influir en la flexibilidad del alabeo y mejorar potencialmente la precisión de la alineación.
//+------------------------------------------------------------------+ //| lists the transitions allowed while searching | //|for the minimum-distance path | //+------------------------------------------------------------------+ class CStepPattern { private: matrix m_mx,m_stepsMatrix; ENUM_HINT m_stephint; public: CStepPattern(matrix &mx, matrix &stepmatrix, ENUM_HINT hint=HINT_NA) { m_mx = mx; m_stepsMatrix = stepmatrix; m_stephint = hint; } ~CStepPattern(void) { } matrix getStepMatrix(void) { return m_stepsMatrix; } ulong getNRows(void) { return m_mx.Rows(); } int getNPatterns(void) { vector vec = m_mx.Col(0); return (int(vec.Max())); } CStepPattern* T(void) { ulong cols[] = {0, 2, 1, 3}; matrix cpy = np::selectMatrixCols(m_mx,cols); ENUM_HINT hint = m_stephint; if(m_stephint == HINT_N) hint = HINT_M; else if(m_stephint == HINT_M) hint = HINT_N; CStepPattern* out = new CStepPattern(cpy,m_stepsMatrix,hint); return out; } matrix extractPattern(vector &sn) { vector col = m_mx.Col(0); matrix out = matrix::Ones(1,1); for(ulong i = 0; i<m_mx.Rows(); i++) { for(ulong j = 0; j<col.Size(); j++) { if(col[j] == sn[j]) { if(!out.Resize(out.Rows()+1,m_mx.Cols()-1,100)) { Print(__FUNCTION__, " error ", GetLastError()); return matrix::Zeros(1,1); } vector v = m_mx.Row(j); vector vv = np::sliceVector(v,1); if(!out.Row(vv,out.Rows()-1)) { Print(__FUNCTION__, " error ", GetLastError()); return matrix::Zeros(1,1); } } } } if(!np::reverseMatrixRows(out)) { Print(__FUNCTION__, " Reverse Matrix failure "); return matrix::Zeros(1,1); } return out; } matrix mkDIrDeltas(void) { matrix out = matrix::Zeros(1,1); vector col = m_mx.Col(3); for(ulong i = 0; i<m_mx.Rows(); i++) { for(ulong j = 0; j<col.Size(); j++) { if(col[j] == -1.0) { if(!out.Resize(out.Rows()+1,m_mx.Cols(),100)) { Print(__FUNCTION__, " error ", GetLastError()); return matrix::Zeros(1,1); } vector v = m_mx.Row(j); if(!out.Row(v,out.Rows()-1)) { Print(__FUNCTION__, " error ", GetLastError()); return matrix::Zeros(1,1); } } } } return np::sliceMatrixCols(out,1,3); } matrix getP(void) { ulong sel[] = {0, 2, 1, 3}; matrix s = np::selectMatrixCols(m_mx,sel); return s; } matrix getMx(void) { return m_mx; } };
CStepPattern es una clase diseñada para manejar diferentes patrones de pasos mediante la gestión de matrices que definen las transiciones y operaciones permitidas. Proporciona métodos para extraer patrones basados en secuencias específicas y manipular matrices para deltas direccionales, esenciales para los cálculos de DTW.
//+------------------------------------------------------------------+ //| Global constraints | //+------------------------------------------------------------------+ class CConstraint { public: CConstraint(void) { } ~CConstraint(void) { } static matrix noConstraint(ulong iw,ulong jw) { matrix mats[]; np::indices(iw,jw,mats); for(ulong i = 0; i<mats[0].Rows(); i++) { for(ulong j = 0; j<mats[0].Cols(); j++) { long value = long(mats[0][i][j]); mats[0][i][j] = (double)(value|1); if(mats[0][i][j]==0.0) mats[0][i][j] = double("inf"); } } return mats[0]; } static matrix sakoeChibaConstraint(ulong iw,ulong jw, ulong qsize, ulong refsize, ulong winsize) { matrix mats[]; np::indices(iw,jw,mats); matrix abs = MathAbs(mats[1]-mats[0]); for(ulong i = 0; i<abs.Rows(); i++) { for(ulong j = 0; j<abs.Cols(); j++) { if(ulong(abs[i][j])<=winsize) abs[i][j] = (double)(1); else abs[i][j] = double("inf"); } } return abs; } static matrix itakuraConstraint(ulong iw,ulong jw, ulong qsize, ulong refsize) { matrix mats[]; np::indices(iw,jw,mats); long a,b,c,d; for(ulong i = 0, k = 0; i<mats[0].Rows() && k<mats[1].Rows(); i++,k++) { for(ulong j = 0; j<mats[0].Cols(); j++) { a = long(mats[1][k][j]) < (2*long(mats[0][i][j]))?1:0; b = long(mats[0][i][j]) <=(2*long(mats[1][k][j]))?1:0; c = long(mats[0][i][j]) >=(long(qsize)-1-2*(long(refsize)-long(mats[1][k][j])))?1:0; d = long(mats[1][k][j]) > (long(refsize)-1-2*(long(qsize)-long(mats[0][i][j])))?1:0; mats[0][i][j] = double(ulong(a&b&c&d)); if(mats[0][i][j]==0.0) mats[0][i][j] = double("inf"); } } return mats[0]; } static matrix slantedBandConstraint(ulong iw,ulong jw, ulong qsize, ulong refsize,ulong winsize) { matrix mats[]; np::indices(iw,jw,mats); matrix diagj = (mats[0]*refsize/qsize); matrix abs = MathAbs(mats[1]-diagj); for(ulong i = 0; i<abs.Rows(); i++) { for(ulong j = 0; j<abs.Cols(); j++) { if(ulong(abs[i][j])<=winsize) abs[i][j] = (double)(1); else abs[i][j] = double("inf"); } } return abs; } };
La clase CConstraint incluye métodos estáticos para aplicar diferentes tipos de restricciones, como Sakoe-Chiba, Itakura y Slanted Band. Estos ayudan a restringir la alineación DTW para mejorar la eficiencia computacional y la relevancia al limitar el espacio de búsqueda en función de criterios predefinidos.
- El método noConstraint(): genera una matriz llena de unos, lo que significa la ausencia de restricciones.
- El método sakoeChibaConstraint(): impone una restricción Sakoe-Chiba basada en el tamaño de ventana especificado y las longitudes de las series de tiempo.
- El método itakuraWindow(): aplica una restricción de Itakura utilizando las longitudes de las series de tiempo.
- El método slantedBandConstraint(): implementa una restricción de banda inclinada basada en el tamaño de la ventana y las longitudes de las series de tiempo.
La clase Cdtw es la clase principal responsable de los cálculos DTW.
//+------------------------------------------------------------------+ //| main interface method for dtw | //+------------------------------------------------------------------+ bool dtw(matrix&x, matrix&y, ENUM_DIST_METRIC dist_method,ENUM_STEP_PATTERN step_pattern=STEP_SYMM2, ENUM_GLOBAL_CONSTRAINT win_type=CONSTRAINT_NONE,ulong winsize=0) { if(y.Cols()!=x.Cols()) { Print(__FUNCTION__, " invalid input parameters, size containers donot match. "); return false; } if(CheckPointer(m_stepPattern)==POINTER_DYNAMIC) delete m_stepPattern; switch(step_pattern) { case STEP_SYMM1: m_stepPattern = new CStepPattern(_symmetric1,Symmetric); m_stephint = HINT_NA; break; case STEP_SYMM2: m_stepPattern = new CStepPattern(_symmetric2,Symmetric,HINT_NM); m_stephint = HINT_NM; break; case STEP_ASYMM: m_stepPattern = new CStepPattern(_asymmetric,Asymmetric,HINT_N); m_stephint = HINT_N; break; } if(CheckPointer(m_stepPattern)==POINTER_INVALID) { Print(__FUNCTION__," failed step pointer initialization ", GetLastError()); return false; } matrix stepsMatrix = m_stepPattern.getStepMatrix(); m_query = x; m_qlen = x.Rows(); m_ref = y; m_reflen = y.Rows(); m_distMetric = dist_method; m_winMethod = win_type; m_winsize = winsize; if(y.Rows()) { if(!m_distance.Resize(m_qlen,m_reflen)) { Print(__FUNCTION__," resize error ", GetLastError()); return false; } for(ulong i = 0; i<m_qlen; i++) for(ulong j =0; j<m_reflen; j++) m_distance[i][j]=dist(m_query.Row(i),m_ref.Row(j)); } else m_distance = m_query; ulong n,m; n=m_distance.Rows(); m=m_distance.Cols(); matrix wm; if(m_winMethod == CONSTRAINT_NONE) wm = matrix::Ones(m_distance.Rows(), m_distance.Cols()); else { switch(m_winMethod) { case CONSTRAINT_ITAKURA: wm = CConstraint::itakuraConstraint(n,m,m_qlen,m_reflen); break; case CONSTRAINT_SAKOECHIBA: wm = CConstraint::sakoeChibaConstraint(n,m,m_qlen,m_reflen,m_winsize); break; case CONSTRAINT_SLATEDBAND: wm = CConstraint::slantedBandConstraint(n,m,m_qlen,m_reflen,m_winsize); break; default: wm = CConstraint::noConstraint(n,m); break; } } if(m_winMethod!=CONSTRAINT_NONE) { for(ulong i = 0; i<wm.Rows(); i++) for(ulong j = 0; j<wm.Cols(); j++) if((i+j)>0 && wm[i][j] != 1.0) m_distance[i][j] = wm[i][j]; } m_costMatrix = matrix::Zeros(m_distance.Rows()+ulong(stepsMatrix.Col(0).Max()),m_distance.Cols()+ulong(stepsMatrix.Col(1).Max())); m_costMatrix.Fill(double("inf")); m_costMatrix[ulong(stepsMatrix.Col(0).Max())][ulong(stepsMatrix.Col(1).Max())] = m_distance[0][0]; m_dirMatrix = matrix::Zeros(m_costMatrix.Rows(),m_costMatrix.Cols()); m_dirMatrix.Fill(double(INT_MIN)); for(ulong i = 0; i<m_dirMatrix.Cols(); i++) m_dirMatrix[0][i] = double(1); for(ulong i = 0; i<m_dirMatrix.Rows(); i++) m_dirMatrix[i][0] = double(2); if(!calCM(m_distance,stepsMatrix,m_costMatrix,m_dirMatrix)) { Print(__FUNCTION__, " computeCM() failed "); return false; } m_jmin = m_costMatrix.Cols() - 1; return true; }
Proporciona el método dtw() como función principal para realizar DTW. Tenga en cuenta que el método está sobrecargado para atender series de tiempo univariadas (vectores) y multivariadas (matrices). Toma dos series de tiempo (representadas como matrices o vectores), una métrica de distancia, un patrón de pasos, una restricción global y un tamaño de ventana como argumentos de entrada. Al invocarse, el método comienza realizando varias comprobaciones sobre la validez de los datos de entrada y el patrón de pasos elegido. A continuación se realiza el cálculo de la matriz de distancia en función de la métrica de distancia seleccionada. Si se especifica una restricción global, las matrices de costo y dirección utilizadas durante el algoritmo DTW se preparan en consecuencia. Luego se invoca la función calCM() para calcular la matriz de costos acumulados. Finalmente, la función dtw() devuelve un valor booleano que indica éxito o fracaso.
//+------------------------------------------------------------------+ //| Get the optimal path: corresponding points from both series | //+------------------------------------------------------------------+ matrix warpPath(bool openEnd=false) { matrix stmatrix = m_stepPattern.getStepMatrix(); return backtrack(m_dirMatrix,stmatrix,openEnd,openEnd?long(m_costMatrix.Row(m_costMatrix.Rows()-1).ArgMin()):-1); }
El método warpPath() recupera la ruta óptima (puntos correspondientes) identificada de ambas series de tiempo basándose en un argumento 'openEnd' opcional que controla la terminación de la ruta (abierta o cerrada).
//+------------------------------------------------------------------+ //| Get the accumulated cost matrix | //+------------------------------------------------------------------+ matrix costMatrix(void) { return m_costMatrix; }
El método costMatrix() proporciona acceso a la matriz de costos acumulados, un resultado clave del algoritmo DTW.
//+------------------------------------------------------------------+ //| Get the cost matrix | //+------------------------------------------------------------------+ matrix localCostMatrix(void) { return m_distance; }
El método localCostMatrix() devuelve la matriz de distancia local, que representa las distancias por pares entre puntos de datos en la serie temporal.
//+------------------------------------------------------------------+ //| Get the direction matrix | //+------------------------------------------------------------------+ matrix directionMatrix(void) { return m_dirMatrix; }
El método directionMatrix() otorga acceso a la matriz de dirección, que desempeña un papel en guiar el proceso de búsqueda de ruta durante DTW.
//+------------------------------------------------------------------+ //| private method implementing accumulated cost calculation | //+------------------------------------------------------------------+ bool calCM(matrix &distMatrix,matrix &stepMatrix,matrix &costMatrix,matrix &dirMatrix) { ulong max0,max1; max0 = ulong(stepMatrix.Col(0).Max()); max1 = ulong(stepMatrix.Col(1).Max()); double curCost,curd; for(ulong i = max0; i<costMatrix.Rows(); i++) { for(ulong j = max1; j<costMatrix.Cols(); j++) { for(ulong k = 0; k<stepMatrix.Rows(); k++) { curd = costMatrix[i-ulong(stepMatrix[k][0])][j-ulong(stepMatrix[k][1])]; curCost = curd + distMatrix[i-max0][j-max1]; if(curCost<costMatrix[i][j]) { costMatrix[i][j] = curCost; dirMatrix[i][j] = double(k); } } } } costMatrix = np::sliceMatrix(costMatrix,max0,END,1,max1); dirMatrix = np::sliceMatrix(dirMatrix,max0,END,1,max1); return true; }
El método privado calCM() calcula la matriz de costos acumulados, un componente central del algoritmo DTW. Utiliza la matriz de distancia, el patrón de pasos, la matriz de costos y la matriz de dirección como entrada.
//+------------------------------------------------------------------+ //| distance metric calculation | //+------------------------------------------------------------------+ double dist(vector &u,vector &v) { switch(m_distMetric) { case DIST_EUCLIDEAN: return euclidean(u,v); case DIST_CITYBLOCK: return cityblock(u,v); case DIST_CHEBYSHEV: return chebyshev(u,v); case DIST_CORRELATION: return correlation(u,v); case DIST_COSINE: return cosine(u,v); case DIST_SQEUCLIDEAN: return sqeuclidean(u,v); default: Print(__FUNCTION__, " invalid parameter "); return EMPTY_VALUE; } }
La función privada dist() calcula la distancia entre dos puntos de datos basándose en una métrica de distancia elegida.
Probando la clase Cdtw
En esta sección, demostraremos las capacidades de la clase Cdtw comparando su salida con la producida por una implementación basada en Python de DTW. El módulo,Dtw-Python, es una de varias implementaciones de DTW en Python. Ejecutaremos un script simple en Python y veremos si los resultados pueden reproducirse mediante nuestra implementación MQL5. Comenzamos enumerando el script de Python.
import numpy as np import matplotlib.pyplot as plt import dtw len = 10 add_noise = True noise = np.random.uniform(size=len)if add_noise else np.zeros((len,)) arx = np.linspace(start = 1, stop = 6.28,num = len) query = np.sin(arx) + noise ref = np.cos(arx) alignment = dtw.dtw(query,ref,dist_method='cosine',step_pattern='symmetric2', window_type=None,keep_internals=True) print( f'Accumulated Cost Matrix is {alignment.costMatrix}') print(f'Distance is {alignment.distance}, \n normalize distance is {alignment.normalizedDistance}') print(f'Warp Path is {alignment.index1[:]}:{alignment.index2[:]}') plt.plot(alignment.index1,alignment.index2) plt.show()
Este script demuestra cómo alinear dos series de tiempo utilizando el algoritmo DTW y visualizar la alineación. El código comienza importando las bibliotecas necesarias: 'numpy' para operaciones numéricas, 'matplotlib.pyplot' para trazar gráficos y 'dtw' para implementar el algoritmo DTW. La longitud de la serie temporal se establece en 10 y se crea una matriz arx de 10 valores espaciados uniformemente entre 1 y 6,28. Una 'consulta' (query) de series de tiempo se genera tomando el seno de 'arx' y agregando algo de ruido aleatorio, mientras que la serie de tiempo de referencia ref se genera tomando el coseno de 'arx'. Luego se realiza la alineación DTW entre las series 'query' y 'ref'. La distancia entre las series temporales se mide utilizando el método de la distancia coseno, y se aplica un patrón de pasos simétricos a la trayectoria de deformación. No se utiliza ninguna restricción de ventana y se conservan detalles internos como la matriz de costes y la trayectoria de deformación.
Luego, el código imprime la matriz de costo acumulado, la distancia de alineación total, la distancia normalizada y los índices de la trayectoria de deformación entre las dos series. Finalmente, la trayectoria de deformación se visualiza trazando los índices de alineación, mostrando cómo se alinean los índices de las dos series de tiempo, y se muestra el gráfico. Este enfoque permite una comparación detallada de dos series de tiempo que pueden estar desfasadas o tener longitudes diferentes, lo que posibilita observar cómo una serie se deforma para alinearse con la otra.
A continuación se muestra el equivalente MQL5 del código Python que se acaba de describir.
//+------------------------------------------------------------------+ //| dtwTest.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" #property script_show_inputs #include<Math\Stat\Uniform.mqh> #include<dtw.mqh> //--- input ulong series_len = 10; input bool AddRandomNoise = false; input ENUM_DIST_METRIC AppliedDistanceMetric = DIST_EUCLIDEAN; input ENUM_STEP_PATTERN AppliedStepPattern = STEP_SYMM2; input ENUM_GLOBAL_CONSTRAINT AppliedGlobalConstraint = CONSTRAINT_NONE; input ulong GlobalConstraintWinSize = 0; input bool WarpPathConstraint = false; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { if(series_len<10) { Alert(" Invalid input for series_len parameter. Should be >=10 "); return; } //--- vector arg = np::linspace(1.0,6.28,series_len); vector noise = vector::Zeros(series_len); if(AddRandomNoise) { double array[]; if(!MathRandomUniform(0.0,1.0,int(series_len),array)) { Print(__LINE__, " MathRandomUniform() failed ", GetLastError()); return; } if(!noise.Assign(array)) { Print(__LINE__, " vector assignment failure ", GetLastError()); return; } } vector q = sin(arg) + noise; // candidate sequence vector rf = cos(arg); // reference sequence Cdtw cdtw; cdtw.dtw(q,rf,AppliedDistanceMetric,AppliedStepPattern,AppliedGlobalConstraint,GlobalConstraintWinSize); Print(" local cm ", cdtw.localCostMatrix()); Print(" final cm ", cdtw.costMatrix()); Print(" direction matrix \n", cdtw.directionMatrix()); matrix path = cdtw.warpPath(); Print(" Warp path \n", cdtw.warpPath()); Print(" Distance metric ", cdtw.distance()); Print(" Normalized Distance metric ", cdtw.normalizedDistance()); vector xx = path.Col(0); vector yy = path.Col(1); CGraphic *g = np::plotXY(xx,yy,"Warp Plot", " Query ", " Reference "); Sleep(20000); g.Destroy(); ChartRedraw(); delete g; } //+------------------------------------------------------------------+
La salida del script de Python:
KQ 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) Accumulated Cost Matrix is [[ 0. 2. 4. 6. 8. 10. 12. 12. 12. 12.] RD 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 0. 2. 4. 6. 8. 10. 12. 12. 12. 12.] FH 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 0. 2. 4. 6. 8. 10. 12. 12. 12. 12.] JL 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 0. 2. 4. 6. 8. 10. 12. 12. 12. 12.] NQ 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 0. 2. 4. 6. 8. 10. 12. 12. 12. 12.] GD 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 2. 0. 0. 0. 0. 0. 0. 2. 4. 6.] QH 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 4. 0. 0. 0. 0. 0. 0. 2. 4. 6.] KL 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 6. 0. 0. 0. 0. 0. 0. 2. 4. 6.] GS 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 6. 2. 2. 2. 2. 2. 2. 0. 0. 0.] PJ 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) [ 6. 4. 4. 4. 4. 4. 4. 0. 0. 0.]] LJ 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) Distance is 0.0, MM 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) normalize distance is 0.0 CH 0 20:52:53.954 DTW (EURUSD DFX 10 Index,Daily) Warp Path is [0 1 2 3 4 5 5 5 5 6 7 8 8 9]:[0 0 0 0 0 1 2 3 4 5 6 7 8 9]
La salida del script MQL5:
NE 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) local cm [[0,2,2,2,2,2,2,0,0,0] LH 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [0,2,2,2,2,2,2,0,0,0] FR 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [0,2,2,2,2,2,2,0,0,0] HE 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [0,2,2,2,2,2,2,0,0,0] RL 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [0,2,2,2,2,2,2,0,0,0] DF 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [2,2.220446049250313e-16,0,0,2.220446049250313e-16,0,0,2,2,2] ND 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [2,0,0,0,0,0,0,2,2,2] FR 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [2,0,0,0,0,0,2.220446049250313e-16,2,2,2] RS 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [0,2,2,2,2,2,2,0,0,0] OH 0 20:56:48.971 dtwTest (EURUSD DFX 10 Index,D1) [0,2,2,2,2,2,2,0,0,0]] ML 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) final cm [[0,2,4,6,8,10,12,12,12,12] JR 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [0,2,4,6,8,10,12,12,12,12] JH 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [0,2,4,6,8,10,12,12,12,12] JN 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [0,2,4,6,8,10,12,12,12,12] JD 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [0,2,4,6,8,10,12,12,12,12] EI 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,2.220446049250313e-16,2.220446049250313e-16,2.220446049250313e-16,4.440892098500626e-16,4.440892098500626e-16,4.440892098500626e-16,2,4,6] CP 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [4,2.220446049250313e-16,2.220446049250313e-16,2.220446049250313e-16,2.220446049250313e-16,2.220446049250313e-16,2.220446049250313e-16,2,4,6] EH 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [6,2.220446049250313e-16,2.220446049250313e-16,2.220446049250313e-16,2.220446049250313e-16,2.220446049250313e-16,4.440892098500626e-16,2,4,6] IN 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [6,2,2,2,2,2,2,4.440892098500626e-16,4.440892098500626e-16,4.440892098500626e-16] HS 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [6,4,4,4,4,4,4,4.440892098500626e-16,4.440892098500626e-16,4.440892098500626e-16]] MO 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) direction matrix QK 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [[-2147483648,1,1,1,1,1,1,1,1,1] GN 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,0,0,0,0,0,0,0,0,0] QQ 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,0,0,0,0,0,0,0,0,0] CK 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,0,0,0,0,0,0,0,0,0] MR 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,0,0,0,0,0,0,0,0,0] OE 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,0,1,1,1,1,1,1,1,1] HO 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,2,0,0,0,1,1,1,0,0] MF 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,2,0,0,0,0,0,0,0,0] CH 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,2,0,0,0,0,0,0,1,1] LN 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,2,0,0,0,0,0,2,0,0]] PK 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) Warp path HM 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [[9,9] GJ 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [8,8] HS 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [8,7] DK 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [7,6] HP 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [6,5] QI 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [6,4] GQ 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [5,3] RN 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [5,2] MG 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [5,1] CO 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [4,0] LD 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [3,0] EL 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [2,0] JE 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [1,0] DO 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) [0,0]] LD 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) Distance metric 4.440892098500626e-16 NR 0 20:56:48.972 dtwTest (EURUSD DFX 10 Index,D1) Normalized Distance metric 2.2204460492503132e-17
Comparando la salida de ambos scripts se confirma que el código parece funcionar bastante bien. Por lo tanto podemos usarlo para algo más práctico. Esto nos lleva a cómo se puede utilizar el DTW en el desarrollo de estrategias.
Aplicación de deformación temporal dinámica
DTW se puede utilizar en el desarrollo de estrategias automatizadas al permitir el reconocimiento de patrones flexibles y técnicas de comparación de series de tiempo financieras. Por ejemplo, DTW permite la identificación de patrones de precios recurrentes, incluso cuando ocurren en diferentes escalas o con dinámicas temporales variables. Al alinear estos patrones con datos históricos, es posible detectar cambios sutiles y anomalías que pueden preceder a movimientos significativos del mercado.
En las pruebas retrospectivas, DTW se puede aplicar para comparar el rendimiento de diferentes estrategias en distintas condiciones de mercado alineando sus respectivos resultados de series de tiempo. Este enfoque ayuda a evaluar qué tan bien se adapta una estrategia a los cambios en el comportamiento del mercado, proporcionando una comprensión más profunda de su solidez. Además, DTW se puede utilizar para agrupar señales comerciales o estados de mercado similares, que luego se pueden analizar para identificar oportunidades comerciales de alta probabilidad. Al reconocer estos grupos, se puede perfeccionar una estrategia para explotar los comportamientos recurrentes del mercado de manera más efectiva.
Además, en la optimización de estrategias algorítmicas, DTW puede ayudar a hacer coincidir los conjuntos de parámetros más prometedores con las condiciones históricas del mercado que se asemejan mucho a la dinámica actual del mercado. Esto permite que la estrategia se adapte de forma más dinámica a las condiciones cambiantes del mercado. Al aprovechar DTW, las estrategias automatizadas pueden volverse más adaptables, conscientes del contexto y capaces de reconocer relaciones temporales complejas en los datos financieros. Aunque el DTW puede ser una herramienta útil, no es una varita mágica. Puede resultar difícil trabajar con él, especialmente para usuarios sin experiencia.
Uno de los mayores problemas del DTW se relaciona con la configuración de sus parámetros de funcionamiento. La combinación correcta de restricciones globales y locales es crucial para aprovechar al máximo el método. DTW es propenso a producir coincidencias falsas y lograr los mejores resultados a menudo requiere mucho ensayo y error. Esto se demuestra en el script MQL5 a continuación.
//+------------------------------------------------------------------+ //| dtwPatternSearch.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" #resource "\\Indicators\\LogReturns.ex5" #property script_show_inputs #include<dtw.mqh> #include<ErrorDescription.mqh> enum ENUM_PRICE { CLOSE=0,//close price MEDIAN,//median price TYPICAL//typical price }; enum ENUM_TRANSFORM_TYPE { PERCENT_DIFF=0,//percent difference LOG_DIFF//log difference }; //--- input parameters input string Pattern = "0.0469,0.0093,0.0697,-0.0699"; input string SymbolName="BTCUSD"; input datetime SearchStartDate=D'2024.06.01'; input datetime SearchStopDate=D'2018.04.22'; input double NormalizedDistanceThreshold=0.01; input ENUM_TIMEFRAMES TimeFrame=PERIOD_D1; input ENUM_DIST_METRIC AppliedDistanceMetric = DIST_EUCLIDEAN; input ENUM_STEP_PATTERN AppliedStepPattern = STEP_SYMM2; input ENUM_GLOBAL_CONSTRAINT AppliedGlobalConstraint = CONSTRAINT_NONE; input ulong GlobalConstraintWinSize = 0; input ENUM_PRICE AppliedPrice=CLOSE; input ENUM_TRANSFORM_TYPE AppliedTransform=LOG_DIFF; input int Lag = 1; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string pattern_values[]; //--- int len = StringSplit(Pattern,StringGetCharacter(",",0),pattern_values); if(pattern_values[len-1]=="") len--; //--- if(len<3) { Alert("Pattern sequence is inadequately defined"); return; } //--- vector pattern(len); for(ulong i = 0; i<pattern.Size(); i++) pattern[i]=StringToDouble(pattern_values[i]); //---set prices handle int handle = INVALID_HANDLE; handle=iCustom(SymbolName!=""?SymbolName:NULL,TimeFrame,"::Indicators\\LogReturns.ex5",AppliedPrice,AppliedTransform,1); if(handle==INVALID_HANDLE) { Print("invalid handle ",ErrorDescription(GetLastError())); return; } //--- vector searchBuffer; if(!searchBuffer.CopyIndicatorBuffer(handle,0,SearchStartDate,SearchStopDate)) { Print("History loading error ",ErrorDescription(GetLastError())); return; } //--- ulong stop = searchBuffer.Size()-pattern.Size(); vector subv; Cdtw cdtw; ulong counter=0; for(ulong i = 0; i<stop; i++) { subv = np::sliceVector(searchBuffer,i,i+pattern.Size()); if(!cdtw.dtw(subv,pattern,AppliedDistanceMetric,AppliedStepPattern,AppliedGlobalConstraint,GlobalConstraintWinSize)) { Print(" dtw failed "); return; } if(cdtw.normalizedDistance()<NormalizedDistanceThreshold) { counter++; Print(" pattern found ", datetime(SearchStopDate+(PeriodSeconds(TimeFrame)*(i+pattern.Size()-1)))); } } //--- Print(" SearchBuffer size ", searchBuffer.Size()); Print(" Reference pattern found ", counter, " times."); } //+------------------------------------------------------------------+
El script permite a un usuario definir un patrón arbitrario compuesto por una secuencia de retornos de registro, calculados por el indicador 'LogReturns.ex5'. El script está diseñado para buscar un patrón específico dentro de los datos de precios de un instrumento financiero, utilizando Dynamic Time Warping (DTW) para la comparación. Incluye las bibliotecas necesarias, como 'dtw.mqh' para el algoritmo DTW y 'ErrorDescription.mqh' para manejar descripciones de errores. El script define dos enumeraciones: ENUM_PRICE, que especifica el tipo de precio a utilizar (cierre, mediano o típico), y ENUM_TRANSFORM_TYPE, que determina cómo se transformarán los datos de precios (diferencia porcentual o diferencia logarítmica).
Los parámetros de entrada permiten al usuario especificar detalles para la búsqueda, incluido el patrón que se va a buscar, el nombre del símbolo (por ejemplo, "BTCUSD"), el rango de fechas para la búsqueda, un umbral para la distancia normalizada, el período de tiempo de los datos, la métrica de distancia, el patrón de paso, la restricción global, el tamaño de la ventana para la restricción, el tipo de precio que se utilizará, el tipo de transformación que se aplicará y un valor de retraso. Cuando se inicia el script, primero divide la cadena de patrón de entrada en una matriz de valores, lo que garantiza que el patrón esté adecuadamente definido con al menos tres valores. Si el patrón es válido, se convierte a un formato vectorial para su posterior procesamiento. Luego, el script intenta cargar los datos de precios utilizando la función iCustom para llamar a un indicador personalizado (LogReturns.ex5) que aplica el tipo de precio y la transformación seleccionados a los datos. Si el identificador devuelto por iCustom no es válido, se imprime un mensaje de error y el script sale.
Suponiendo que los datos de precios se cargan correctamente, se almacenan en un vector 'searchBuffer'. Luego, el script itera a través del 'searchBuffer', dividiéndolo en subvectores más pequeños del mismo tamaño que el patrón y comparando cada subvector con el patrón utilizando el algoritmo DTW. La comparación DTW utiliza la métrica de distancia especificada, el patrón de pasos y la restricción global. Para cada comparación, si la distancia normalizada entre el subvector y el patrón es menor que el umbral especificado, el script considera que el patrón se encuentra en ese punto en los datos de precios. Imprime un mensaje indicando la marca de tiempo donde se encontró el patrón e incrementa un contador. Finalmente, el script imprime el tamaño del 'searchBuffer' y el número total de veces que se encontró el patrón dentro del rango de fechas especificado. Este script permite la detección automatizada de patrones específicos en datos de precios históricos, lo que puede ser útil para desarrollar estrategias comerciales basadas en el reconocimiento de patrones.
La secuencia predeterminada describe el patrón ilustrado a continuación.
Ejecutar el script con diferentes restricciones aplicadas produce una cantidad variable de coincidencias. Esta es la salida usando la configuración predeterminada.
RN 0 22:00:09.210 dtwPatternSearch (BTCUSD,D1) Chosen Parameters IJ 0 22:00:09.210 dtwPatternSearch (BTCUSD,D1) Distance Threshold 0.01 CS 0 22:00:09.210 dtwPatternSearch (BTCUSD,D1) DIST_EUCLIDEAN ND 0 22:00:09.211 dtwPatternSearch (BTCUSD,D1) STEP_SYMM2 CN 0 22:00:09.211 dtwPatternSearch (BTCUSD,D1) CONSTRAINT_NONE HG 0 22:00:09.211 dtwPatternSearch (BTCUSD,D1) WinSize 0 OO 0 22:00:09.211 dtwPatternSearch (BTCUSD,D1) pattern found 2018.04.25 00:00:00 NJ 0 22:00:09.221 dtwPatternSearch (BTCUSD,D1) pattern found 2019.04.28 00:00:00 KD 0 22:00:09.222 dtwPatternSearch (BTCUSD,D1) pattern found 2019.07.01 00:00:00 QO 0 22:00:09.230 dtwPatternSearch (BTCUSD,D1) pattern found 2020.04.27 00:00:00 II 0 22:00:09.234 dtwPatternSearch (BTCUSD,D1) pattern found 2020.10.26 00:00:00 PD 0 22:00:09.237 dtwPatternSearch (BTCUSD,D1) pattern found 2021.02.06 00:00:00 RN 0 22:00:09.250 dtwPatternSearch (BTCUSD,D1) pattern found 2024.01.29 00:00:00 DH 0 22:00:09.250 dtwPatternSearch (BTCUSD,D1) SearchBuffer size 2197 PQ 0 22:00:09.250 dtwPatternSearch (BTCUSD,D1) Reference pattern found 7 times.
El parámetro 'NormalizedDistanceThreshold' obviamente juega un papel importante a la hora de determinar si existe una coincidencia. Aun así, la variación de las restricciones puede llevar a resultados considerablemente diferentes. No son sólo las restricciones; los usuarios también tienen que elegir la métrica de distancia adecuada. Es evidente que un conocimiento significativo del dominio es esencial cuando se emplea el algoritmo DTW.
Conclusión
La deformación temporal dinámica (Dynamic Time Warping, DTW) ofrece un enfoque sofisticado para el reconocimiento de patrones en el análisis de series de tiempo financieras, pero también presenta varias desventajas que deben tenerse en cuenta. En primer lugar, DTW requiere un uso intensivo de recursos computacionales, especialmente cuando se trabaja con series temporales largas o conjuntos de datos de gran tamaño. El algoritmo implica comparar cada punto de una serie temporal con cada punto de otra, lo que puede resultar en un tiempo de procesamiento y un uso de memoria significativos. Esto se vuelve particularmente problemático cuando se intenta analizar datos financieros de alta frecuencia o realizar análisis en tiempo real. En segundo lugar, DTW es sensible al ruido y a los valores atípicos en los datos financieros.
Las series temporales financieras suelen ser ruidosas debido a la volatilidad del mercado, y pequeñas fluctuaciones o anomalías pueden hacer que el algoritmo DTW produzca alineaciones engañosas. Esta sensibilidad puede dar lugar a falsos positivos en el reconocimiento de patrones, donde se identifican patrones que no tienen un poder predictivo significativo. Por último, DTW puede tener dificultades con la interpretabilidad. Si bien proporciona una medida de similitud entre series de tiempo, la trayectoria de deformación y la métrica de distancia resultante pueden ser difíciles de interpretar de manera significativa, especialmente en el contexto del análisis financiero donde es crucial contar con información clara y procesable. Estos desafíos sugieren que si bien DTW puede ser una herramienta útil para el reconocimiento de patrones en series de tiempo financieras, debe aplicarse con cuidado y, a menudo, en combinación con otros métodos analíticos que puedan abordar sus limitaciones.
Archivo | Descripción |
---|---|
Mql5\include\np.mqh | Archivo de encabezado de varias funciones de utilidad de matriz y vector |
Mql5\include\dtw.mqh | Archivo de inclusión que contiene la implementación MQL5 del algoritmo de deformación temporal dinámica |
Mql5\indicators\LogReturns.mq5 | Indicador de los retornos logarítmicos de una serie de precios |
Mql5\scripts\dwTest.mq5 | Script utilizado para probar y demostrar el uso de la implementación en MQL5 de DTW |
Mql5\scripts\dtwPatternSearch.mq5 | Script utilizado para buscar un patrón arbitrario en una muestra de datos |
Mql5\scripts\DTW.py | Un script de Python que utiliza el módulo 'dtw-python' para alinear dos series utilizando el algoritmo DTW |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/15572
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- 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