Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 40): Pasaporte de ADN del mercado
Índice
Introducción
En biología, el ácido desoxirribonucleico (ADN) es la molécula que codifica el plano genético único de cada organismo. El ADN define la identidad biológica y se transmite de generación en generación. Una vez vi un caso en el que un hombre negó la paternidad hasta que una prueba de ADN —realizada después del nacimiento del niño— demostró que, efectivamente, él era el padre. Quizá se pregunte qué relación tiene esto con el análisis de la acción del precio: la conexión radica en la idea de una huella persistente e identificable.
En mi investigación he descubierto que cada par de divisas presenta una firma característica en la acción del precio. Algunos pares pueden comportarse de forma similar bajo influencias comunes (por ejemplo, EURUSD y GBPUSD), mientras que otros muestran un comportamiento distinto. Para captar estos patrones, he creado un sistema automatizado que analiza cada instrumento y genera un ADN del mercado: una huella compacta derivada de la volatilidad, la estructura fractal, los ritmos de las sesiones y el comportamiento de los retrocesos. El Pasaporte de ADN del mercado cuantifica esos rasgos, lo cual le permite comparar instrumentos, detectar cambios estructurales (mutaciones) y seleccionar o adaptar estrategias a los regímenes de mercado vigentes.
Una mutación es un cambio significativo en la huella de mercado del EA que se señala cuando la distancia coseno o L2 normalizada entre los valores anteriores y actuales de DNAMetrics supera los umbrales establecidos, y suele indicar un cambio de régimen (por ejemplo, cambios en el ATR, los picos, el dominio de la sesión o el comportamiento de retroceso). Tómelo como una señal de alerta temprana: compruebe qué métricas han variado, ajuste o pause la ejecución (menor tamaño, stops basados en ATR más amplios, umbral de señal más alto) y valide el nuevo perfil con algunos recálculos u operaciones simuladas antes de reanudar las operaciones reales.
// Show top N metric changes when a mutation is detected. // Call: ShowMutationDetails(gDNA_prev, gDNA, 3); void ShowMutationDetails(const DNAMetrics &prev_in, const DNAMetrics &cur_in, int topN=3) { // make local copies because DNAVector expects a non-const reference DNAMetrics prev = prev_in; DNAMetrics cur = cur_in; // metric names must follow the order in DNAVector() string names[] = { "wick_body_ratio_avg", "pct_close_near_high", "pct_close_near_low", "pct_doji", "atr_norm", "pct_spikes", "vol_clustering", "swing_cycle_bars_avg", "fractal_density", "breakout_follow_through", "retr_38_freq", "retr_50_freq", "retr_62_freq", "asia_range_share", "london_range_share", "ny_range_share", "smoothness_index" }; double va[], vb[]; DNAVector(cur, va); DNAVector(prev, vb); int n = ArraySize(va); if(n != ArraySize(names)) { Print("[ShowMutationDetails] vector/name-size mismatch"); return; } // diffs and indices double diffs[]; int idxs[]; ArrayResize(diffs, n); ArrayResize(idxs, n); for(int i=0; i<n; ++i) { diffs[i] = va[i] - vb[i]; idxs[i] = i; } // simple selection sort by absolute diff (descending) for(int i=0; i<n-1; ++i) { int best = i; for(int j=i+1; j<n; ++j) if(MathAbs(diffs[j]) > MathAbs(diffs[best])) best = j; // swap diffs double td = diffs[i]; diffs[i] = diffs[best]; diffs[best] = td; int ti = idxs[i]; idxs[i] = idxs[best]; idxs[best] = ti; } int show = MathMin(topN, n); string out = ""; for(int k=0; k<show; ++k) { int id = idxs[k]; double d = diffs[k]; double prevVal = vb[id]; double pct = (MathAbs(prevVal) < 1e-12 ? 0.0 : (d / MathAbs(prevVal) * 100.0)); string sign = (d >= 0.0 ? "+" : "-"); string line = StringFormat("%d) %s %s%.4f (%.1f%%)", k+1, names[id], sign, MathAbs(d), MathAbs(pct)); out += line + "\\n"; PrintFormat("[MarketDNA][Mutation] %s", line); } // show on-panel (adjust offsets/sizes if needed) int w = 360; int h = 18 * (show + 1); CreateOrSetRect("mut_detail_bg", InpCorner, InpX + 380, InpY + 340, w, h, BgColor()); CreateOrSetLabel("mut_detail_lbl", InpCorner, InpX + 388, InpY + 344, SafeText(out, 800), 9, MakeColor(200,120,40)); }
En los apartados siguientes, describo las métricas utilizadas, la implementación en MQL5 y algunos ejemplos prácticos que muestran cómo el pasaporte permite realizar análisis sólidos.
Métricas conceptuales
El Pasaporte de ADN del mercado condensa cientos de barras de acción del precio bruta en métricas estables y explicables —picos, oscilaciones fractales, frecuencias de retroceso, volatilidad normalizada por ATR, proporciones del rango de la sesión, suavidad y mucho más—, de modo que los operadores de acción del precio puedan ver cómo se comporta el mercado desde un punto de vista estructural (y no solo dónde está el precio ahora) y tomar decisiones más rápidas y coherentes.
Por qué esas métricas son importantes para los operadores de acción del precio:
| Importancia | Explicación |
|---|---|
| Detección objetiva del régimen de mercado | En lugar de preguntarse: «¿Está tendencial o en rango?», el EA cuantifica la suavidad, la densidad fractal y la continuidad tras las rupturas, para que pueda cambiar de táctica basándose en datos (seguimiento de tendencia frente a reversión a la media). |
| Mejor sesgo de entrada | Métricas como la confirmación posterior a la ruptura y el cierre cerca del máximo o del mínimo ofrecen una confirmación de alto nivel para las configuraciones de acción del precio (continuación de la ruptura frente a velas de ruptura fallida o de reversión). |
| Dimensionamiento ajustado al riesgo y filtrado | Las medidas normalizadas por ATR y la frecuencia de picos ayudan a dimensionar los stops y a decidir cuándo abstenerse de operar (ATR alto o picos frecuentes = stops más amplios o menos operaciones). |
| Validación de patrones y esperanza matemática | Los histogramas de retroceso muestran cuál suele ser la profundidad de las correcciones tras los impulsos en el símbolo/marco temporal, lo cual es esencial para establecer objetivos realistas y decidir si un «retroceso al 50 %» constituye una ventaja estadística probable. |
| Eficiencia en la selección de operaciones | El EA condensa gran parte del trabajo previo a la operación en un pasaporte de lectura rápida, lo que le permite examinar rápidamente símbolos/marcos temporales y seleccionar solo aquellos que se ajusten a su ventaja estadística de acción del precio. |
| Explicabilidad | Cada señal incluye motivos numéricos registrados (por ejemplo, «alta continuidad + baja frecuencia de picos + mercado fluido»), lo que le permite auditar por qué realizó una operación y mejorar el conjunto de reglas de forma iterativa. |
Métricas de análisis de velas japonesas y volatilidad
1. Estructura de las velas japonesas y cierre
wick_body_ratio_avg — relación media entre la longitud total de las sombras y el tamaño del cuerpo de la vela.
- Valor alto → sombras largas → señales de reversión (pin bar, estrella fugaz).
- Valor bajo → velas tendenciales limpias → presión compradora/vendedora sostenida.
- Se ha introducido un umbral mínimo del cuerpo para evitar distorsiones.
pct_close_near_high — porcentaje de velas que cerraron en el 20 % superior del rango.
- Valor elevado → fuerte demanda, sesgo alcista.
- En mercados poco líquidos, puede dar señales falsas.
pct_close_near_low — porcentaje de velas que cerraron en el 20 % inferior del rango.
- Valor alto → presión vendedora dominante, sesgo bajista.
- Las sesiones con patrones especiales pueden distorsionar el resultado.
pct_doji — proporción de velas doji (cuerpo < ~10 % del rango).
- Valor elevado → incertidumbre del mercado.
- Las series de doji pueden preceder a fuertes rupturas.
nearHigh_count / nearLow_count — número de velas que cerraron cerca del máximo o del mínimo.
- Se usa para evaluar el cambio en el equilibrio entre la oferta y la demanda.
doji_count — número absoluto de dojis.
- Ayuda a percibir las fases de anticipación previas al movimiento.
2. Volatilidad y momentum
atr_mean — ATR medio (volatilidad absoluta en unidades de precio).
- Se utiliza para los stops y los objetivos.
atr_pips — ATR convertido a pips.
- Facilita la comparación y la estandarización entre instrumentos distintos.
atr_norm — ATR normalizado respecto al precio (ATR / cierre).
- Proporciona una medida relativa de la volatilidad.
- Muy sensible en instrumentos baratos.
pct_spikes — proporción de velas en las que el rango es superior al multiplicador del ATR.
- Valor alto → impulsos/ruido frecuentes.
spikes_count — número absoluto de velas de impulso.
bigTotal_count — número total de velas grandes (> ATR).
bigThenBig_count — número de veces que una vela grande va seguida de otra vela grande.
- Alto → agrupamientos de volatilidad, mercado tendencial.
vol_clustering — proporción de velas grandes seguidas de otra vela grande.
- Un indicador de la persistencia del aumento de la volatilidad.
3. Fractales y ciclos
swing_cycle_bars_avg — número medio de barras entre los cambios en los extremos fractales.
- Duración del ciclo de oscilación.
- En un movimiento lateral, puede reducirse considerablemente.
fractal_density — densidad fractal (% de velas con giros).
- Alto → el mercado es irregular y lateral.
- Bajo → tendencial.
sw_count — número total de puntos fractales.
breakout_follow_through — proporción de niveles fractales confirmados mediante una ruptura (por ≥ ATR).
- Alto → las rupturas son fiables.
- Bajo → muchas señales falsas.
4. Correcciones y retrocesos
retr_38_freq — frecuencia de retrocesos de alrededor del 38 %.
- Es habitual que se produzcan correcciones leves.
retr_50_freq — frecuencia de los retrocesos del 50 % (44-56 %).
- Nivel de corrección clásico.
retr_62_freq — frecuencia de las correcciones profundas (~62 %).
- A menudo indica un cambio en la volatilidad o en el régimen.
retr_gt70_freq — frecuencia de retrocesos superiores al 70 %.
- Valor alto → impulsos débiles, retorno al rango.
avg_max_retr — retroceso máximo medio tras los impulsos.
- Ayuda a establecer stops y objetivos realistas.
retr_count — número de impulsos sobre los que se contabilizaron los retrocesos (importante para la fiabilidad).
5. Actividad por sesión
asia_range_share / london_range_share / ny_range_share — proporción del rango total generada en la sesión correspondiente.
- Muestra qué sesión es la que más contribuye al movimiento.
asia_range / london_range / ny_range — rango absoluto (máximo–mínimo) dentro de una sesión.
- Describe la volatilidad en un momento concreto del día.
6. Índices compuestos y parámetros de servicio
smoothness_index (0–1) — índice de suavidad.
- Alto → tendencial, con menos ruido.
- Bajo → mercado lateral e irregular.
atr_cache_used — indicador de uso de datos ATR almacenados en caché (servicio).
sample_bars — tamaño de la muestra (número de barras).
- Muestras grandes → mayor estabilidad, pero menor sensibilidad ante los cambios.
Implementación en MQL5
Implementamos el motor Market DNA de MQL5 como un sistema compacto que lee las barras históricas de un símbolo y un marco temporal seleccionados, calcula una huella digital de la anatomía y la estructura del precio (velas, ATR, oscilaciones fractales, retrocesos y proporciones del rango por sesión), muestra en el gráfico un «Pasaporte de ADN» etiquetado, compara opcionalmente un segundo símbolo, registra métricas y señales en un archivo CSV, y emite sugerencias ligeras de COMPRA/VENTA basadas en reglas, además de alertas de mutación cuando la huella cambia de forma significativa. Esa única frase resume el objetivo: el código que viene a continuación se centra en generar métricas reproducibles y explicables que puede examinar o utilizar para análisis posteriores.
Para ayudarle a familiarizarse con la implementación, en esta sección se explican los supuestos de compilación y tiempo de ejecución, se recomiendan valores iniciales adecuados y se destacan los auxiliares clave y los puntos de entrada del ciclo de vida que encontrará en el archivo; por ejemplo, OnInit, OnTimer, Recalculate, BuildDNA y DrawPassport. Una vez que tenga claros estos conceptos básicos, estará mejor preparado para seguir la guía de cinco pasos que viene a continuación sin perderse en los detalles.
Antes de empezar, asegúrese de que su entorno cumple una serie de requisitos previos. Necesita MetaTrader 5 con permiso para compilar y ejecutar EA (se utilizan #property strict y Trade/Trade.mqh). El EA escribe en la carpeta de archivos comunes del terminal, así que compruebe que se permita la E/S de archivos. Por último, elija un símbolo/marco temporal con al menos unas 300 barras históricas; se recomiendan 1.200 barras para obtener estadísticas estables. Si no dispone de historial suficiente, el EA le avisará y los resultados presentarán ruido.
#property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property strict #include <Trade/Trade.mqh>
Si quiere probarlo de la forma más rápida, copie el archivo Market DNA Passport.mq5 en su carpeta MQL5/Experts, compílelo en MetaEditor, adjunte el EA compilado a un gráfico, configure InpTF y InpBars (los valores por defecto son PERIOD_H1 y 1200) y, si lo desea, active InpLogCSV para conservar las métricas. El EA utiliza un temporizador y recalcula el perfil con cada nueva barra cerrada; tras un breve periodo de funcionamiento, compruebe en Files\Common los archivos MarketDNA_log.csv y MarketDNA_signals.csv para revisar la salida registrada.
Una buena configuración inicial de los parámetros recomendados es: InpTF = PERIOD_H1, InpBars = 1200, InpATRPeriod = 14, InpSpikeATRMult = 2.0, InpDojiBodyPct = 0.10, InpFractalDepth = 5, InpRetrWindowBars = 50 y InpFT_ATR_Mult = 0.5. Para obtener una buena capacidad de respuesta y un uso moderado de la CPU, establezca InpRecalcSeconds = 10 y InpCacheATR = true. Mejor tratarlos como puntos de partida para ajustarlos en función de cada símbolo y del horizonte de trading.
//============================== INPUTS ==============================// input ENUM_TIMEFRAMES InpTF = PERIOD_H1; // Analysis timeframe input int InpBars = 1200; // Bars to analyze (>= 300 recommended) input int InpATRPeriod = 14; // ATR period input double InpSpikeATRMult = 2.0; // Spike threshold in ATR multiples input double InpDojiBodyPct = 0.10; // Doji body <= % of candle range input int InpFractalDepth = 5; // Fractal depth (ZigZag-like swings) input int InpRetrLookbackSwings = 80; // Max impulses to evaluate retracements input int InpRetrWindowBars = 50; // How many bars forward to scan for retracement input double InpFT_ATR_Mult = 0.5; // Breakout follow-through threshold (in ATR multiples) input string InpCompareSymbol = ""; // Optional second symbol to compare input int InpRecalcSeconds = 10; // Recalc cadence (seconds) input int InpCorner = 0; // Panel corner (0-3) input int InpX = 12; // Panel X offset input int InpY = 24; // Panel Y offset input bool InpDarkTheme = true; // Dark panel theme input int InpMetricPalette = 1; // Metric color palette (0=Warm Brown,1=DarkGray,2=NearBlack,3=Lilac,4=RichBrown,5=HighContrast) input bool InpAlertsOnMutation = true; // Alert on DNA shifts input double InpMutationThresh = 0.12; // Cosine distance to flag change input double InpMutationL2Thresh = 0.05; // Normalized L2 change to flag mutation input bool InpLogCSV = false; // Append results to CSV in common Files folder input string InpCSVFileName = "MarketDNA_log.csv"; // CSV filename (FILE_COMMON) input bool InpCacheATR = true; // Cache ATR array between runs input bool InpSelfTest = false; // Run small synthetic self-test at init input int InpAsiaStart = 0; // Session hour boundaries (server hours) input int InpAsiaEnd = 7; input int InpLondonStart = 8; input int InpLondonEnd = 15; input int InpNYStart = 16; input int InpNYEnd = 23; input bool InpDebugRetr = false; // Print retracement debug lines to Experts
Notas prácticas sobre nomenclatura, seguridad y rendimiento: el EA antepone a los objetos del gráfico el prefijo MDNA_<symbol>_<TF>_ para evitar colisiones; los archivos CSV se almacenan en la carpeta FILE_COMMON con nombres de archivo fijos. Por motivos de seguridad y rendimiento, el EA impone una cadencia mínima del temporizador (≥5 s) y evita las reconstrucciones intensivas, salvo que haya cambiado la barra cerrada más reciente. Si ejecuta muchas instancias o análisis retrospectivos muy amplios (por ejemplo, más de 5.000 barras), aumente el intervalo del temporizador o reduzca InpBars para limitar el uso de la CPU y la memoria. Tenga en cuenta también que, si varias instancias del EA escriben en el mismo archivo CSV, pueden producirse condiciones de carrera; para un registro robusto en varios gráficos, considere utilizar nombres de archivo únicos por símbolo/marco temporal.
Una vez establecida esta orientación, pasaremos a la implementación: la adquisición y el preprocesamiento de datos, la anatomía de las velas y las métricas de volatilidad, el análisis estructural de oscilaciones, los retrocesos y rupturas, la comparación de instantáneas y la generación de señales y, por último, la interfaz de usuario, la persistencia y los controles del ciclo de vida. Cada parte se corresponde directamente con funciones y bloques lógicos del archivo .mq5, lo que le permite seguir el código, reproducir los resultados de forma local y adaptar el motor a su investigación o a su flujo de trabajo de trading.
Adquisición y preprocesamiento de datos
En primer lugar, cargamos el historial de precios utilizando CopyRates a través de LoadRates(sym, tf, bars) con un margen de seguridad, de modo que las búsquedas hacia adelante (ventanas de ATR, ventanas de retroceso) cuenten con contexto. Luego calculamos el True Range por barra con BarTR() y un ATR de media móvil simple (SMA) para cada índice utilizando CalcATR(r, period, idx). Si InpCacheATR está habilitado y los parámetros almacenados coinciden, reutilizamos la caché global g_atrs[] para evitar volver a calcular los ATR (véase la comprobación de la caché dentro de BuildDNA). Después depuramos las entradas y los valores intermedios con funciones auxiliares como SafeDiv, Clamp y LTrim, convertimos el ATR a pips o unidades normalizadas (atr_pips, atr_norm) y nos aseguramos de contar con un número mínimo de barras; si N < 300, la función establece D.valid = false y se detiene. Los resultados principales de esta etapa son un array de serie MqlRates r[] y un array de ATR que se utiliza en etapas posteriores.
// Load rates with safety margin bool LoadRates(string sym, ENUM_TIMEFRAMES tf, int bars, MqlRates &rates[]) { ArraySetAsSeries(rates, true); int need = MathMax(300, bars + 200); // safety margin for forward scans int got = CopyRates(sym, tf, 0, need, rates); if(got <= 0) return false; return (got >= bars); } // True Range (series layout: 0 newest) double BarTR(MqlRates &r[], int i) { if(i >= ArraySize(r)-1) return 0.0; double prevC = r[i+1].close; double tr1 = r[i].high - r[i].low; double tr2 = MathAbs(r[i].high - prevC); double tr3 = MathAbs(r[i].low - prevC); return MathMax(tr1, MathMax(tr2, tr3)); } // Simple SMA ATR computed over 'period' TRs starting at idx double CalcATR(MqlRates &r[], int period, int idx) { double tr_sum = 0.0; int count = 0; for(int i = idx; i < idx + period && i < ArraySize(r)-1; ++i) { tr_sum += BarTR(r, i); ++count; } return (count > 0 ? tr_sum / count : 0.0); }
Anatomía de las velas y métricas de volatilidad
A continuación, iteramos sobre la serie depurada para construir descriptores a nivel de vela y recuentos de volatilidad. El bucle dentro de BuildDNA calcula una relación media entre mecha y cuerpo (wick_body_ratio_avg), cuenta los cierres cercanos al máximo y al mínimo (pct_close_near_high, pct_close_near_low), detecta velas doji (pct_doji) utilizando InpDojiBodyPct y marca los picos de ATR en los que TR > InpSpikeATRMult * atr_i. También identificamos las barras «grandes» de ATR y los eventos consecutivos «grande seguido de grande» para estimar el agrupamiento de volatilidad. Todos los acumuladores por barra se agregan en métricas acotadas y recuentos brutos (por ejemplo, nearHigh_count, spikes_count, bigThenBig_count) que rellenan la estructura DNAMetrics . Esos campos se han limitado y normalizado de forma intencionada para que constituyan elementos básicos sólidos para las comparaciones de similitud y las heurísticas de señal.
// Example loop computing candle descriptors (place inside BuildDNA after rates & atrs ready) int nearHigh=0, nearLow=0, doji=0, spikes=0; double wickBodyAccum = 0.0; int bigTotal=0, bigThenBig=0; for(int i = 0; i < N; ++i) { double high = r[i].high, low = r[i].low, open = r[i].open, close = r[i].close; double range = high - low; if(range <= 0.0) continue; double body = MathAbs(close - open); if(body < 1e-9) body = 1e-9; double upper = (close >= open ? high - close : high - open); double lower = (close >= open ? open - low : close - low); double minBody = MathMax(body, range * 0.02); // floor to avoid tiny-body noise double wickRatio = (upper + lower) / minBody; wickRatio = MathMin(MathMax(wickRatio, 0.0), 50.0); wickBodyAccum += wickRatio; double pos = (close - low) / range; if(pos >= 0.80) ++nearHigh; else if(pos <= 0.20) ++nearLow; if(body <= InpDojiBodyPct * range) ++doji; double tr = BarTR(r, i); double atr_i = atrs[i]; if(atr_i > 0 && tr > InpSpikeATRMult * atr_i) ++spikes; bool big = (atr_i > 0 && tr > 1.0 * atr_i); if(big) { ++bigTotal; if(i > 0) { double trPrev = BarTR(r, i-1); double atrPrev = atrs[i-1]; if(atrPrev > 0 && trPrev > 1.0 * atrPrev) ++bigThenBig; } } } // Aggregate into DNAMetrics fields (example assignments) D.wick_body_ratio_avg = (N > 0 ? wickBodyAccum / N : 0.0); D.pct_close_near_high = SafeDiv(nearHigh, N); D.pct_close_near_low = SafeDiv(nearLow, N); D.pct_doji = SafeDiv(doji, N); D.pct_spikes = SafeDiv(spikes, N); D.vol_clustering = (bigTotal > 0 ? SafeDiv(bigThenBig, bigTotal) : 0.0); D.nearHigh_count = nearHigh; D.spikes_count = spikes; D.bigThenBig_count = bigThenBig;
Análisis estructural: oscilaciones, retrocesos y continuidad tras la ruptura
Detectamos puntos de oscilación fractales con BuildSwings(r, InpFractalDepth, sw[]), que localiza máximos y mínimos locales usando el entorno de profundidad configurado. A continuación, calculamos las estadísticas del ciclo de oscilación (media de barras por impulso, swing_cycle_bars_avg y fractal_density). Para el comportamiento de los retrocesos, llamamos a ComputeRetracementHistogram(r, sw, swN, ...): para cada impulso en dirección opuesta, exploramos las barras posteriores hasta InpRetrWindowBars y registramos la ratio máxima de retroceso respecto al impulso; los valores máximos se agrupan en bandas del 38 %/50 %/62 %/>70 % y se promedian (avg_max_retr) para obtener retr_38_freq, retr_50_freq, retr_62_freq y retr_gt70_freq.
Por otra parte, ComputeBreakoutFollowThrough analiza los eventos de oscilación y comprueba si una ruptura más allá del extremo de la oscilación alcanza un objetivo escalado por ATR (InpFT_ATR_Mult * atr), generando breakout_follow_through como la fracción de eventos que han tenido continuidad. En conjunto, estas métricas estructurales cuantifican el ritmo de las tendencias, la magnitud habitual de los retrocesos y la fiabilidad de las rupturas.
// Fractal swing builder (returns newest-first series layout) struct Swing { int index; double price; bool isHigh; }; int BuildSwings(MqlRates &r[], int depth, Swing &sw[]) { ArrayResize(sw, 0); int N = ArraySize(r); for(int i = depth; i < N - depth; ++i) { bool highP = true, lowP = true; double h = r[i].high, l = r[i].low; for(int k = 1; k <= depth; ++k) { if(r[i-k].high >= h || r[i+k].high >= h) highP = false; if(r[i-k].low <= l || r[i+k].low <= l) lowP = false; if(!highP && !lowP) break; } if(highP) { int n = ArraySize(sw); ArrayResize(sw, n+1); sw[n].index = i; sw[n].price = h; sw[n].isHigh = true; } if(lowP) { int n = ArraySize(sw); ArrayResize(sw, n+1); sw[n].index = i; sw[n].price = l; sw[n].isHigh = false; } } // sort by index ascending (newest first in series layout) for(int a=0;a<ArraySize(sw);++a) for(int b=a+1;b<ArraySize(sw);++b) if(sw[a].index > sw[b].index) { Swing t = sw[a]; sw[a] = sw[b]; sw[b] = t; } return ArraySize(sw); } // Retracement histogram (core loop) void ComputeRetracementHistogram(MqlRates &r[], Swing &sw[], int swN, double &f38, double &f50, double &f62, double &f70, double &avgRetr, int &counted_out) { int counted=0, c38=0, c50=0, c62=0, c70=0; double sumMaxRetr = 0.0; for(int i=0; i < swN-1 && counted < InpRetrLookbackSwings; ++i) { Swing a = sw[i], b = sw[i+1]; if(a.isHigh == b.isHigh) continue; Swing older = (a.index > b.index ? a : b); Swing newer = (a.index > b.index ? b : a); double impulse = MathAbs(older.price - newer.price); int start = newer.index - 1; int end = MathMax(0, newer.index - InpRetrWindowBars); double maxRetr = 0.0; if(impulse > 0 && start >= 0 && start >= end) { if(older.isHigh && !newer.isHigh) { for(int k = start; k >= end; --k) { double retr = SafeDiv(r[k].high - newer.price, impulse); if(retr > maxRetr) maxRetr = retr; } } else if(!older.isHigh && newer.isHigh) { for(int k = start; k >= end; --k) { double retr = SafeDiv(newer.price - r[k].low, impulse); if(retr > maxRetr) maxRetr = retr; } } } counted++; if(impulse > 0) { sumMaxRetr += maxRetr; if(maxRetr < 0.44) ++c38; else if(maxRetr < 0.56) ++c50; else if(maxRetr < 0.70) ++c62; else ++c70; } } if(counted > 0) { f38 = double(c38) / counted; f50 = double(c50) / counted; f62 = double(c62) / counted; f70 = double(c70) / counted; avgRetr = sumMaxRetr / counted; } else { f38 = f50 = f62 = f70 = avgRetr = 0.0; } counted_out = counted; } // Breakout follow-through check double ComputeBreakoutFollowThrough(MqlRates &r[], Swing &sw[], int swN, int atrPeriod, double ftAtrMult) { if(swN < 2) return 0.0; int events = 0, success = 0; for(int i=0; i < swN-1 && events < 80; ++i) { int s_index = sw[i].index; double s_price = sw[i].price; bool s_isHigh = sw[i].isHigh; int start = s_index - 1; if(start < 0) continue; double atr = CalcATR(r, atrPeriod, s_index); double target = ftAtrMult * atr; if(target <= 0) continue; bool broke = false, followed = false; for(int k = start; k >= 0; --k) { if(s_isHigh) { if(r[k].high > s_price) { broke = true; if((r[k].high - s_price) >= target) { followed = true; break; } } } else { if(r[k].low < s_price) { broke = true; if((s_price - r[k].low) >= target) { followed = true; break; } } } } if(broke) { ++events; if(followed) ++success; } } return (events > 0 ? double(success) / events : 0.0); }
Comparación de instantáneas, detección de mutaciones y generación de señales
Una vez que disponemos de una nueva instancia de DNAMetrics, convertimos los campos seleccionados en un vector fijo de 17 dimensiones mediante DNAVector(D, vec[]). Comparamos la versión actual y la anterior utilizando CosineDistance(A,B) y NormalizedL2Distance(A,B). Si cualquiera de las distancias supera InpMutationThresh / InpMutationL2Thresh, marcamos visualmente un banner de mutación y, si está habilitado, ejecutamos Alert(); además, el código colorea dicho banner en función de la gravedad.
Paralelamente a la detección de mutaciones, GenerateSignal(const DNAMetrics &D) compone las puntuaciones de compra y venta a partir de la continuidad tras la ruptura, el índice de suavidad, la frecuencia de picos, la proximidad al mínimo/máximo y los intervalos de retroceso; aplica una penalización derivada del ATR, acota las puntuaciones y solo genera BUY/SELL cuando la puntuación alcanza InpSignalThreshold y supera la puntuación opuesta en InpSignalGap. MaybeNotify impone un periodo de espera (15 minutos) para las notificaciones; las señales y sus cadenas completas con los motivos de diagnóstico se añaden a MarketDNA_signals.csv para su revisión fuera de línea.
// Build vector for comparison (17-dim) void DNAVector(DNAMetrics &D, double &vec[]) { int n = 17; ArrayResize(vec, n); vec[0] = D.wick_body_ratio_avg; vec[1] = D.pct_close_near_high; vec[2] = D.pct_close_near_low; vec[3] = D.pct_doji; vec[4] = D.atr_norm; vec[5] = D.pct_spikes; vec[6] = D.vol_clustering; vec[7] = D.swing_cycle_bars_avg; vec[8] = D.fractal_density; vec[9] = D.breakout_follow_through; vec[10] = D.retr_38_freq; vec[11] = D.retr_50_freq; vec[12] = D.retr_62_freq; vec[13] = D.asia_range_share; vec[14] = D.london_range_share; vec[15] = D.ny_range_share; vec[16] = D.smoothness_index; } // Cosine distance and normalized L2 double CosineDistance(DNAMetrics &A, DNAMetrics &B) { double va[], vb[]; DNAVector(A, va); DNAVector(B, vb); double dot=0, na=0, nb=0; for(int i=0;i<ArraySize(va);++i) { dot += va[i]*vb[i]; na += va[i]*va[i]; nb += vb[i]*vb[i]; } double denom = MathSqrt(na)*MathSqrt(nb); if(denom <= 0) return 1.0; double cos = dot / denom; return 1.0 - MathMax(-1.0, MathMin(1.0, cos)); } double NormalizedL2Distance(DNAMetrics &A, DNAMetrics &B) { double va[], vb[]; DNAVector(A, va); DNAVector(B, vb); double num=0.0, denom=0.0; for(int i=0;i<ArraySize(va);++i) { double d = va[i] - vb[i]; num += d*d; denom += va[i]*va[i]; } double l2 = MathSqrt(num); double scale = MathSqrt(denom) + 1e-9; return l2 / scale; } // Signal generation (rule-based) Signal GenerateSignal(const DNAMetrics &D) { Signal s; s.type = SIGNAL_NONE; s.score = 0.0; s.reason = ""; double cFT = Clamp(D.breakout_follow_through, 0.0, 1.0); double cSmooth = Clamp(D.smoothness_index, 0.0, 1.0); double cNotSpikes = 1.0 - Clamp(D.pct_spikes, 0.0, 1.0); double cRetr38 = Clamp(D.retr_38_freq, 0.0, 1.0); double cRetrGT70 = Clamp(D.retr_gt70_freq, 0.0, 1.0); // weights double wFT = 0.50, wSmooth = 0.25, wNotSpikes = 0.15, wRetr38 = 0.10; double wSellNearLow = 0.30, wSellSpikes = 0.25, wSellSmoothInv = 0.25, wSellRetrGT70 = 0.20; double buyScore = wFT*cFT + wSmooth*cSmooth + wNotSpikes*cNotSpikes + wRetr38*cRetr38; double sellScore = wSellNearLow*Clamp(D.pct_close_near_low,0,1) + wSellSpikes*Clamp(D.pct_spikes,0,1) + wSellSmoothInv*(1.0 - cSmooth) + wSellRetrGT70*cRetrGT70; double atrPenalty = Clamp(D.atr_norm * 10.0, 0.0, 0.5); double buyScorePen = Clamp(buyScore * (1.0 - atrPenalty), 0.0, 1.0); double sellScorePen = Clamp(sellScore * (1.0 - atrPenalty * 0.5), 0.0, 1.0); double minThreshold = InpSignalThreshold; double minGap = InpSignalGap; s.reason = StringFormat("buy_raw=%.3f sell_raw=%.3f buy=%.3f sell=%.3f", buyScore, sellScore, buyScorePen, sellScorePen); if(buyScorePen - sellScorePen >= minGap && buyScorePen >= minThreshold) { s.type = SIGNAL_BUY; s.score = buyScorePen; } else if(sellScorePen - buyScorePen >= minGap && sellScorePen >= minThreshold) { s.type = SIGNAL_SELL; s.score = sellScorePen; } else { s.type = SIGNAL_NONE; s.score = MathMax(buyScorePen, sellScorePen); } return s; } // Notification cooldown void MaybeNotify(const Signal &s, string sym) { if(s.type == SIGNAL_NONE) return; if(TimeCurrent() - gLastSignalTime < 60*15) return; // 15-minute cooldown string text = StringFormat("MarketDNA %s %s signal score=%.2f reason=%s", sym, SignalTypeToString(s.type), s.score, s.reason); SendNotification(text); Alert(text); gLastSignalTime = TimeCurrent(); }
Controles de visualización, persistencia y ciclo de vida
Por último, generamos los resultados mediante objetos del gráfico. DrawPassport(title, D, x, y) crea un panel rectangular (ObjectCreate con OBJ_RECTANGLE_LABEL) y numerosas líneas etiquetadas (OBJ_LABEL) que muestran cada métrica, las advertencias y las marcas de tiempo; DrawComparison dibuja un encabezado compacto de similitud cuando hay un símbolo de comparación. Guardamos los resultados utilizando WriteCSV(D, sym) y WriteSignalCSV(s, sym) en archivos ubicados en FILE_COMMON (las cabeceras de los archivos se crean cuando el archivo es nuevo).
El ciclo de vida del EA utiliza OnInit para establecer EventSetTimer(sec) (garantizando que sec >= 5), OnTimer para llamar a Recalculate(), y Recalculate() está optimizado para omitir reconstrucciones costosas cuando la hora de la barra cerrada más reciente no ha cambiado (comprobación CopyRates(..., 0, 1)). OnDeinit detiene el temporizador, y ClearObjects() elimina los objetos de la interfaz de usuario con prefijo. Los modos opcionales InpSelfTest e InpDebugRetr nos permiten validar la lógica de oscilación y retroceso, así como imprimir trazas de depuración.
// Create or update label helper void CreateOrSetLabel(string name, int corner, int x, int y, string text, int fontsize=11, color clr=(color)(-1)) { string obj = Pref()+name; color useclr = (clr == (color)(-1) ? TextColor() : clr); if(ObjectFind(0,obj) == -1) { ObjectCreate(0,obj,OBJ_LABEL,0,0,0); ObjectSetInteger(0,obj,OBJPROP_CORNER,corner); ObjectSetInteger(0,obj,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,obj,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,obj,OBJPROP_FONTSIZE,fontsize); ObjectSetInteger(0,obj,OBJPROP_COLOR,useclr); ObjectSetString(0,obj,OBJPROP_FONT,"Arial"); } ObjectSetString(0,obj,OBJPROP_TEXT,text); } // Minimal DrawPassport example (truncated) void DrawPassport(string title, DNAMetrics &D, int x, int y) { CreateOrSetRect(title+"_bg", InpCorner, x, y, 360, 460, BgColor()); CreateOrSetLabel(title+"_hdr", InpCorner, x+10, y+8, title, 12, Accent()); CreateOrSetLabel(title+"_ATR", InpCorner, x+10, y+32, StringFormat("ATR: %.1f pips (%.3f%%)", D.atr_pips, D.atr_norm*100.0), 10, TextColor()); CreateOrSetLabel(title+"_WB", InpCorner, x+10, y+50, StringFormat("Wick/Body avg: %.2f", D.wick_body_ratio_avg), 10, TextColor()); // ... add more labels for other metrics } // CSV writing (append, creates header when empty) void WriteCSV(DNAMetrics &D, string sym) { if(!InpLogCSV) return; string fname = InpCSVFileName; int handle = FileOpen(fname, FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_ANSI); if(handle == INVALID_HANDLE) { PrintFormat("Unable to open CSV '%s'", fname); return; } if(FileSize(handle) == 0) FileWrite(handle, "timestamp","symbol","tf","sample_bars","wick_body_avg","pct_close_high","pct_close_low","pct_doji","atr_mean","atr_pips"); FileSeek(handle, 0, SEEK_END); FileWrite(handle, TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), sym, TFToString(InpTF), D.sample_bars, D.wick_body_ratio_avg, D.pct_close_near_high, D.pct_close_near_low, D.pct_doji, D.atr_mean, D.atr_pips); FileClose(handle); } // Lifecycle hooks (skeleton) int OnInit() { EventSetTimer(MathMax(5, InpRecalcSeconds)); // enforce minimal cadence CreateOrSetLabel("status", InpCorner, InpX, InpY + 380, "Idle: waiting first calculation...", 10, TextColor()); if(InpSelfTest) SelfTest(); Recalculate(); // initial build return INIT_SUCCEEDED; } void OnTimer() { Recalculate(); // optimized to rebuild only on new closed bar } void OnDeinit(const int reason) { EventKillTimer(); ClearObjects(); // cleanup }
Resultados
Cuando adjuntamos el EA al gráfico H1 de EURUSD, este generó inmediatamente el panel Market-DNA y calculó las métricas del instrumento sobre la muestra seleccionada. El panel resume los perfiles de volatilidad, picos y retrocesos, las contribuciones de cada sesión y otras señales estructurales; basándose en esas métricas normalizadas, el EA calculó puntuaciones separadas de compra y venta y emitió una señal de COMPRA, ya que la puntuación de compra superó a la de venta por la diferencia configurada y rebasó el umbral. La marca de tiempo, la puntuación y la línea de diagnóstico del panel explican por qué el sesgo era alcista (alta continuidad tras la ruptura, baja frecuencia de picos y suavidad moderada), por lo que el panel visual documenta el proceso de decisión en lugar de limitarse a mostrar una sugerencia de operación aislada.
De la muestra de 1.200 barras, encontramos 171 oscilaciones y 80 retrocesos (aproximadamente el 44,2 % de las oscilaciones dieron lugar a un retroceso cuantificable). La sesión de Londres representa la mayor parte del rango, por lo que, si quiere operar movimientos bruscos, céntrese en el horario de Londres. Los ciclos de oscilación tienen una duración media de 9,62 barras, lo que indica la regularidad con la que se producen las oscilaciones. Y lo más importante: la continuidad tras la ruptura es extremadamente alta (≈98 %).

El gráfico correspondiente muestra GBPUSD, un par que presenta correlación con EURUSD. El ADN del mercado muestra una mayor variación en comparación con EURUSD: registró 161 oscilaciones y 80 retrocesos, con un ciclo medio de oscilación de 11,28 barras. La continuidad tras la ruptura es alta (≈94 %) y la sesión de Londres aporta la mayor parte del rango (≈46 %).
Conclusión
Tras haberle guiado a lo largo de todo el proceso —desde la idea inicial, pasando por la implementación, hasta llegar finalmente a las pruebas, cuyos resultados han sido alentadores—, ahora podemos concluir que esta herramienta capta de forma eficaz la «composición» única de cada par en función de su acción del precio. Puede probar diferentes valores de los parámetros de entrada, realizar pruebas retrospectivas o hacer pruebas en modo demo para encontrar los valores que mejor se adapten a su estrategia. Tenga en cuenta, sin embargo, que este EA está diseñado principalmente con fines educativos, en concreto para revelar la identidad de cada par a través de su acción del precio histórica. No debe utilizarse para operar en vivo con dinero real, sino más bien como una herramienta de apoyo complementaria a sus estrategias actuales.
Si así lo desea, puede consultar mis otros artículos.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19460
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.
Utilizando redes neuronales en MetaTrader
Automatización de estrategias de trading en MQL5 (parte 31): Creación de un sistema de patrón armónico de 3 Drivesbasado en la acción del precio
Particularidades del trabajo con números del tipo double en MQL4
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 13): Introducción a la teoría de control mediante la factorización de matrices
- 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
Otra vez una idea genial y un trabajo estupendo de C. Benjamin. Gracias, y que tú y tus seres queridos disfrutéis de unos días llenos de salud.