Entwicklung des Price Action Analysis Toolkit (Teil 40): Markt-DNA-Pass
Inhalt
Einführung
In der Biologie ist die Desoxyribonukleinsäure (DNA) das Molekül, das den genetischen Bauplan eines jeden Organismus kodiert. Die DNA bestimmt die biologische Identität und wird über Generationen weitergegeben. Ich habe einmal einen Fall beobachtet, in dem ein Mann die Vaterschaft bestritt, bis ein DNA-Test – der nach der Geburt des Kindes durchgeführt wurde – bewies, dass er tatsächlich der Vater war. Sie fragen sich vielleicht, was das mit der Preisaktionsanalyse zu tun hat: Die Verbindung ist die Idee eines dauerhaften, identifizierbaren Fingerabdrucks.
Bei meinen Untersuchungen habe ich festgestellt, dass jedes Währungspaar seine eigene charakteristische Preis-Aktions-Signatur aufweist. Einige Paare können sich unter gemeinsamen Einflüssen ähnlich verhalten (z. B. EURUSD und GBPUSD), während andere ein unterschiedliches Verhalten zeigen. Um diese Muster zu erfassen, habe ich ein automatisiertes System entwickelt, das jedes Instrument scannt und eine Markt-DNAerstellt – einen kompakten Fingerabdruck, der sich aus Volatilität, fraktaler Struktur, Sitzungsrhythmen und Retracement-Verhalten ableitet. Der Markt-DNA-Passquantifiziert diese Merkmale und ermöglicht es Ihnen, Instrumente zu vergleichen, strukturelle Verschiebungen (Mutationen) zu erkennen und Strategien auszuwählen oder an die vorherrschenden Marktbedingungen anzupassen.
Eine Mutation ist eine signifikante Änderung im Markt-Fingerabdruck des EA, die gekennzeichnet wird, wenn der Kosinus- oder normalisierte L2-Abstand zwischen der vorherigen und der aktuellen DNA-Metrik die festgelegten Schwellenwerte überschreitet – und bedeutet in der Regel einen Regimewechsel (z. B. ATR, Spikes, Session-Dominanz oder Retracement-Verhalten geändert). Behandeln Sie es als Frühwarnung: Prüfen Sie, welche Kennzahlen sich verändert haben, straffen Sie die Ausführung oder pausieren Sie sie (kleinere Größe, breitere ATR-Stopps, höhere Signalschwelle), und validieren Sie das neue Profil mit einigen Neuberechnungen oder dem Papierhandel, bevor Sie den Live-Handel wieder aufnehmen.
// 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)); }
In den folgenden Abschnitten beschreibe ich die verwendeten Metriken, die MQL5-Implementierung und praktische Beispiele, die zeigen, wie der Pass robuste Analysen unterstützt.
Konzeptionelle Metriken
Market DNA Pass komprimiert Hunderte von Balken roher Kursbewegungen in stabile, erklärbare Metriken – Spitzen, fraktale Schwankungen, Retracement-Frequenzen, ATR-normalisierte Volatilität, Anteile an der Sitzungsspanne, Glattheit und mehr – damit Händler, die mit Kursbewegungen zu tun haben, sehen können, wie sich der Markt strukturell verhält (und nicht nur, wo der Kurs gerade steht), und schnellere, konsistentere Entscheidungen treffen können.
Warum diese Metriken für Preis-Aktions-Händler wichtig sind:
| Bedeutung | Erklärung |
|---|---|
| Objektive Erkennung von Marktordnungen | Anstatt zu raten, ob es sich um einen Trend oder einen Bereich handelt, quantifiziert der EA die Glattheit, die fraktale Dichte und die Ausbruchsverfolgung, sodass Sie die Taktik anhand der Daten ändern können (Trendverfolgung vs. Mittelwertumkehr). |
| Bessere Einstiegschancen | Metriken wie „Breakout Follow-Through“ und „Close-Near-High/Low“ geben eine Bestätigung für Preisaktions-Setups auf hohem Niveau (Kerzen der Breakout-Fortsetzung versus einer „Failed-Break/Reversal“). |
| Risikobewusste Dimensionierung und Filterung | ATR-normalisierte Messwerte und Spike-Häufigkeit helfen bei der Festlegung der Stopps und der Entscheidung, wann man aussetzen sollte (hohe ATR oder häufige Spikes = breitere Stopps oder weniger Handelsgeschäfte). |
| Mustervalidierung und Erwartungshaltung | Retracement-Histogramme zeigen, wie tief Korrekturen nach Impulsen auf dem Symbol/Zeitrahmen typischerweise sind – wichtig, um realistische Ziele zu setzen und zu entscheiden, ob ein „Rücksetzer auf 50 %“ möglich ist. |
| Effizienz der Handelsauswahl | Der EA kondensiert eine Menge der Hausaufgaben vor der Eröffnung eines Handelsgeschäfts in einem Pass auf einen Blick, sodass Sie Symbole/Zeitrahmen schnell scannen und nur diejenigen auswählen können, die Ihrem Preis-Aktions-Vorteil entsprechen. |
| Erklärbarkeit | Für jedes Signal werden numerische Gründe protokolliert (z. B. „hohes Follow-Through + niedrige Spikes + glatter Markt“), sodass Sie überprüfen können, warum Sie einen Handel getätigt haben, und das Regelwerk iterativ verbessern können. |
Metriken der Kerzen- und Volatilitätsanalyse
1. Kerzen-Struktur und Abschluss
docht_body_ratio_avg – das durchschnittliche Verhältnis zwischen der Gesamtlänge der Schatten und der Größe des Kerzenkörpers.
- Hoher Wert → lange Schatten → Zeichen einer Umkehr (Pin Bar, Shooting Star).
- Niedriger Wert → saubere Trendkerzen → stetiger Kauf-/Verkaufsdruck.
- Um Verzerrungen zu vermeiden, wurde eine Mindestschwelle für den Kerzenkörper eingeführt.
pct_close_near_high – Prozentsatz der Kerzen, die in den oberen 20% des Bereichs geschlossen wurden.
- Hoher Wert → starke Nachfrage, Aufwärtstrend.
- Auf dünnen Märkten kann es falsche Signale geben.
pct_close_near_low – Prozentsatz der Kerzen, die in den unteren 20% des Bereichs geschlossen wurden.
- Hoher Wert → dominanter Verkaufsdruck, Abwärtstrend.
- Sitzungen mit speziellen Mustern können das Ergebnis verfälschen.
pct_doji – der Anteil der Doji-Kerzen (Körper < ~10% des Bereichs).
- Hoher Wert → Marktunsicherheit.
- Doji-Serien können starken Ausbrüchen vorausgehen.
nearHigh_count / nearLow_count – die Anzahl der Kerzen, die in der Nähe des Hochs/Tiefs geschlossen wurden.
- Wird verwendet, um die Verschiebung des Gleichgewichts zwischen Angebot und Nachfrage zu bewerten.
doji_count – absolute Anzahl der Dojis.
- Es hilft, die Phasen der Antizipation vor der Bewegung zu erkennen.
2. Volatilität und Momentum
atr_mean – durchschnittliche ATR (absolute Volatilität in Preiseinheiten).
- Wird für Anschläge und Ziele verwendet.
atr_pips – ATR umgerechnet in Pips.
- Praktisch für die Vereinheitlichung von verschiedenen Werkzeugen.
atr_norm – ATR normiert auf den Preis (ATR / Close).
- Liefert ein relatives Maß für die Volatilität.
- Sehr empfindlich für billige Instrumente.
pct_spikes – der Anteil der Kerzen, bei denen der Bereich > ATR-Multiplikator ist.
- Hoher Wert → häufige Impulse/Rauschen.
spikes_count – die absolute Anzahl der Impulskerzen.
bigTotal_count – Gesamtzahl der großen Kerzen (> ATR).
bigThenBig_count – die Anzahl der Fälle, in denen auf eine große Kerze eine weitere folgt.
- Hoch → Volatilitätscluster, Tendenz steigend.
vol_clustering – der Anteil der großen Kerzen, auf die eine weitere große Kerze folgt.
- Ein Maß für die Persistenz der erhöhten Volatilität.
3. Fraktale und Zyklen
swing_cycle_bars_avg – die durchschnittliche Anzahl der Balken zwischen den Veränderungen der fraktalen Extrema.
- Länge des Schwingungszyklus.
- In seitlicher Richtung kann sie stark reduziert werden.
fractal_density – fraktale Dichte (% der Kerzen mit Umkehrungen).
- Hoch → der Markt ist zerklüftet, seitlich.
- Niedrig → Trend.
sw_count – Gesamtzahl der fraktalen Punkte.
breakout_follow_through – der Anteil der fraktalen Ebenen, die durch einen Ausbruch bestätigt wurden (um ≥ ATR).
- Hoch → Rücksetzer sind zuverlässig.
- Niedrig → viele falsche Signale.
4. Korrekturen und Rücksetzer
retr_38_freq – die Häufigkeit eines Rücksetzers beträgt etwa 38 %.
- Typisch sind flache Korrekturen.
retr_50_freq – Häufigkeit eines Rücksetzers beträgt etwa 50 % (44-56%).
- Klassische Korrekturstufe.
retr_62_freq – Häufigkeit von tiefen Korrekturen (~62%).
- Weist oft auf eine Veränderung der Volatilität/des Regimes hin.
retr_gt70_freq – Die Rate eines Rücksetzers >70%.
- Hoher Wert → schwache Impulse, Rückkehr zum Bereich.
avg_max_retr – durchschnittlicher maximaler Rücksetzer nach Impulsen.
- Hilft bei der Festlegung realistischer Stopps/Ziele.
retr_count – die Anzahl der Impulse, nach denen Rollbacks gezählt wurden (wichtig für die Zuverlässigkeit).
5. Aktivität der Sitzung
asia_range_share / london_range_share / ny_range_share – der Anteil der in der entsprechenden Sitzung erzeugten Gesamtreichweite.
- Zeigt an, welche Sitzung am meisten zur Bewegung beiträgt.
asia_range / london_range / ny_range – absolute Spanne (Hoch-Tief) innerhalb einer Sitzung.
- Kennzeichnet die Volatilität zu einer bestimmten Tageszeit.
6. Zusammengesetzte Indizes und Leistungsparameter
smoothness_index (0-1) – Glättungsindex.
- Hoch → Trend, geringes Rauschen.
- Niedrig → seitwärts laufender, zerklüfteter Markt.
atr_cache_used – Kennzeichen für die Verwendung zwischengespeicherter ATR-Daten (Dienst).
sample_bars – Stichprobenumfang (Anzahl der Balken).
- Große Stichproben → stabiler, aber weniger sensitiv für Veränderungen.
MQL5-Implementierung
Wir implementieren die MQL5 Market DNA-Engine als kompaktes System, das historische Balken für ein ausgewähltes Symbol und einen Zeitrahmen liest, einen Fingerabdruck der Preisanatomie und -struktur (Kerzen, ATR, fraktale Schwankungen, Retracements und Session-Range-Aktien) berechnet, einen beschrifteten „DNA-Pass“ auf dem Chart ausgibt, optional ein zweites Symbol vergleicht, Metriken und Signale im CSV-Format protokolliert und leichtgewichtige regelbasierte KAUFEN/VERKAUFEN-Vorschläge sowie Mutationswarnungen ausgibt, wenn sich der Fingerabdruck signifikant ändert. Dieser eine Satz fasst das Ziel zusammen: Der folgende Code konzentriert sich auf die Erstellung reproduzierbarer, erklärbarer Metriken, die Sie überprüfen oder in weitere Analysen einfließen lassen können.
Um Sie bei der Implementierung zu unterstützen, erklärt dieser Abschnitt die Build- und Laufzeitannahmen, empfiehlt sinnvolle Starteingaben und hebt die wichtigsten Hilfsprogramme und Lebenszyklus-Hooks hervor, die Sie in der Datei finden werden – zum Beispiel OnInit, OnTimer, Recalculate, BuildDNA und DrawPass. Wenn Sie diese Grundlagen kennen, sind Sie besser darauf vorbereitet, den folgenden fünf Schritten zu folgen, ohne sich in Details zu verlieren.
Bevor Sie beginnen, sollten Sie sicherstellen, dass Ihre Umgebung einige Voraussetzungen erfüllt. Sie benötigen MetaTrader 5 mit Berechtigung zum Kompilieren und Ausführen von EAs (#property strict und Trade/Trade.mqh werden verwendet). Der EA schreibt in den gemeinsamen Datei-Ordner des Terminals, sodass die Dateieingabe und -ausgabe erlaubt ist. Wählen Sie schließlich ein Symbol/Zeitrahmen mit mindestens ~300 historischen Balken; 1200 Balken werden für stabile Statistiken empfohlen. Wenn Sie nicht über eine ausreichende Historie verfügen, wird der EA Sie warnen, und die Ergebnisse werden verrauscht sein.
#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>
Wenn Sie ihn am schnellsten ausprobieren möchten, kopieren Sie Market DNA Pass.mq5 in Ihren MQL5/Experts-Ordner, kompilieren Sie ihn im MetaEditor, starten Sie den kompilierten EA an einen Chart an, setzen Sie InpTF und InpBars (Standardwerte sind PERIOD_H1 und 1200) und aktivieren Sie optional InpLogCSV, um Metriken zu erhalten. Der EA verwendet einen Timer und baut bei jedem neuen geschlossenen Balken neu auf. Prüfen Sie nach einem kurzen Durchlauf Files\Common auf MarketDNA_log.csv und MarketDNA_signals.csv, um die aufgezeichneten Ausgaben zu untersuchen.
Eine gute Grundlage für die empfohlenen Eingaben ist InpTF = PERIOD_H1, InpBars = 1200, InpATRPeriod = 14, InpSpikeATRMult = 2.0, InpDojiBodyPct = 0.10, InpFractalDepth = 5, InpRetrWindowBars = 50 und InpFT_ATR_Mult = 0.5. Für eine gute Reaktionsfähigkeit und eine moderate CPU-Auslastung setzen Sie InpRecalcSeconds = 10 und InpCacheATR = true. Betrachten Sie diese als Ausgangspunkte für die Abstimmung pro Symbol und Handelshorizont.
//============================== 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
Praktische Hinweise zu Namensgebung, Sicherheit und Leistung: EA stellt den Chart-Objekten das Präfix MDNA_<Symbol>_<TF>_ voran, um Kollisionen zu vermeiden; CSVs befinden sich im Ordner FILE_COMMON unter festen Dateinamen. Aus Sicherheits- und Leistungsgründen erzwingt der EA eine minimale Timer-Kadenz (≥5s) und vermeidet schwerwiegende Änderungen, es sei denn, der neueste geschlossene Balken wurde geändert. Wenn Sie viele Instanzen oder massive Rückblicke (z. B. >5k Balken) ausführen, erhöhen Sie das Zeitintervall oder reduzieren Sie InpBars, um die CPU- und Speichernutzung zu begrenzen. Beachten Sie auch, dass mehrere EA-Instanzen, die dieselbe CSV-Datei schreiben, zu Wettlaufbedingungen führen können – für eine robuste Multi-Chart-Protokollierung sollten Sie eindeutige Dateinamen pro Symbol/Zeitrahmen in Betracht ziehen.
Mit dieser Orientierung gehen wir zur Implementierung über: Datenerfassung und -vorverarbeitung, Kerzenanatomie und Volatilitätsmetriken, Strukturanalyse von Schwankungen/Retracements/Ausbrüchen, Snapshot-Vergleich und Signalerzeugung und schließlich die Nutzeroberfläche, Persistenz und Lebenszykluskontrollen. Jeder Teil ist direkt den Funktionen und logischen Blöcken in der .mq5-Datei zugeordnet, sodass Sie den Code nachvollziehen, die Ergebnisse lokal reproduzieren und die Engine für Ihre Forschungs- oder Handelsabläufe anpassen können
Datenerfassung und -vorverarbeitung
Zunächst laden wir die Kurshistorie mit CopyRates über LoadRates(sym, tf, bars) mit einer Sicherheitsmarge, damit zukunftsorientierte Scans (ATR-Fenster, Retracement-Fenster) einen Kontext haben. Wir berechnen die True Range pro Bar mit BarTR() und eine einfache SMA ATR für jeden Index mit CalcATR(r, period, idx). Wenn InpCacheATR aktiviert ist und die gespeicherten Parameter übereinstimmen, wird der globale g_atrs[]-Cache wiederverwendet, um eine Neuberechnung der ATRs zu vermeiden (siehe die Cache-Prüfung in BuildDNA). Wir bereinigen Eingaben und Zwischenwerte mit Helfern wie SafeDiv, Clamp und LTrim, konvertieren ATR in Pips/normalisierte Einheiten (atr_pips, atr_norm) und stellen sicher, dass wir eine Mindestanzahl von Balken haben – wenn N < 300 ist, kennzeichnet die Funktion D.valid = false und stoppt. Die wichtigsten Ergebnisse dieser Stufe sind ein Zeitreihen-Array MqlRates r[] und ein nachgeschaltetes ATR-Array.
// 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); }
Anatomie der Kerze und Volatilitätsmetriken
Anschließend werden die bereinigten Zeitreihen iteriert, um Deskriptoren auf Kerzenebene und Volatilitätswerte zu erstellen. Die Schleife in BuildDNA berechnet ein durchschnittliches Docht-zu-Körper-Verhältnis (wick_body_ratio_avg), zählt Abschlüsse in der Nähe von Hoch/Tief (pct_close_near_high, pct_close_near_low), erkennt Doji-Kerzen (pct_doji) anhand von InpDojiBodyPct und markiert ATR-Spikes, wenn TR > InpSpikeATRMult * atr_i. Wir identifizieren auch „große“ ATR-Balken und aufeinanderfolgende „big-then-big“-Ereignisse, um vol_clustering zu schätzen. Alle Akkumulatoren pro Balken werden zu begrenzten Metriken und Rohwerten (z. B. nearHigh_count, spikes_count, bigThenBig_count) aggregiert, die die DNAMetrics-Struktur auffüllen. Diese Felder werden absichtlich geklammert und normalisiert, damit sie robuste Bausteine für Ähnlichkeitsvergleiche und Signalheuristiken bilden.
// 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;
Strukturelle Analyse: Schwankungen, Rückschritte und Ausbrüche nach einem Durchbruch
Wir erkennen fraktale Schwingungspunkte mit BuildSwings (r, InpFractalDepth, sw[]), das lokale Hochs und Tiefs unter Verwendung der konfigurierten Tiefen-Nachbarschaft findet. Anschließend werden die Statistiken für den Swing-Zyklus berechnet (durchschnittliche Balken pro Impuls, swing_cycle_bars_avg und fractal_density). Für das Retracement-Verhalten rufen wir ComputeRetracementHistogram (r, sw, swN, ...): Für jeden gegenläufigen Impuls wird bis zu InpRetrWindowBars vorwärts gescannt und das maximale Retracement-Verhältnis relativ zum Impuls aufgezeichnet; die Maxima werden in die Bänder 38 % / 50 % / 62 % / >70 % eingeteilt und gemittelt (avg_max_retr), um retr_38_freq, retr_50_freq, retr_62_freq und retr_gt70_freq zu erzeugen.
Getrennt davon scannt ComputeBreakoutFollowThrough die Swing-Ereignisse und prüft, ob ein Ausbruch über das Swing-Extrem hinaus ein ATR-skaliertes Ziel (InpFT_ATR_Mult * atr) erreicht, wobei breakout_follow_through als der Anteil der Ereignisse, denen gefolgt wird, ermittelt wird. Zusammen quantifizieren diese strukturellen Metriken den Trendrhythmus, typische Pullback-Größen und die Zuverlässigkeit von Ausbrüchen.
// 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); }
Vergleich von Schnappschüssen, Mutationserkennung und Signalerzeugung
Sobald wir eine neue DNAMetrics-Instanz haben, verwandeln wir ausgewählte Felder in einen festen 17-dimensionalen Vektor mit DNAVector (D, vec[]). Wir vergleichen die aktuelle und die vorherige Version mit Hilfe von CosineDistance (A,B) und NormalizedL2Distance (A,B). Wenn einer der beiden Abstände InpMutationThresh / InpMutationL2Thresh überschreitet, wird ein Mutationsbanner angezeigt und (falls aktiviert) Alert() aufgerufen; der Code färbt dieses Banner auch nach Schweregrad.
Parallel zur Mutationserkennung setzt GenerateSignal(constDNAMetrics &D) Kauf- und Verkaufsbewertungen aus Breakout-Follow-Through, Glättungsindex, Spike-Häufigkeit, Nähe zu Tief/Hoch und Retracement-Buckets zusammen; es wendet eine von der ATR abgeleitete Strafe an, klammert Bewertungen und gibt nur BUY/SELL aus, wenn die Bewertung InpSignalThreshold erfüllt und die gegenüberliegende Bewertung um InpSignalGap übersteigt. MaybeNotify erzwingt einen Cooldown (15 Minuten) für Benachrichtigungen; Signale und ihre vollständigen Diagnosegründe werden zur Offline-Überprüfung an MarketDNA_signals.csv angehängt.
// 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(); }
Anzeige, Persistenz und Lebenszykluskontrolle
Schließlich werden die Ausgaben über On-Chart-Objekte dargestellt. DrawPass (title, D, x, y) erstellt eine rechteckige Tafel (ObjectCreate mit OBJ_RECTANGLE_LABEL) und zahlreiche beschriftete Linien (OBJ_LABEL), die jede Metrik, Warnungen und Zeitstempel zeigen; DrawComparison zeichnet eine kompakte Ähnlichkeitsüberschrift, wenn ein Vergleichssymbol vorhanden ist. Die Ergebnisse werden mit WriteCSV (D, sym) und WriteSignalCSV (s, sym) in Dateien in DATEI_COMMON gespeichert (Dateikopfzeilen werden erstellt, wenn die Datei neu ist).
Der EA-Lebenszyklus verwendet OnInit, um EventSetTimer (sec) zu setzen (wodurch sec >= 5 erzwungen wird), OnTimer, um Recalculate() aufzurufen, und Recalculate() ist so optimiert, dass schwere Rebuilds übersprungen werden, wenn sich die letzte geschlossene Barzeit nicht geändert hat (CopyRates(..., 0, 1) check). OnDeinit beendet den Timer, und ClearObjects() entfernt die vorangestellten UI-Objekte. Die optionalen Modi InpSelfTest und InpDebugRetr ermöglichen die Validierung der Swing/Retracement-Logik und den Ausdruck von Debug-Spuren.
// 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 }
Ergebnisse
Als wir den EA mit dem EURUSD-H1-Chart verbunden haben, hat er sofort das Market-DNA-Panel erstellt und die Metriken des Instruments über die gewählte Stichprobe berechnet. Das Panel fasst Volatilität, Spike- und Retracement-Profile, Session-Beiträge und andere strukturelle Signale zusammen. Auf der Grundlage dieser normalisierten Metriken berechnet der EA separate Kauf- und Verkaufs-Scores und gibt ein KAUF-Signal aus, weil der Kauf-Score den Verkaufs-Score um den konfigurierten Gap übersteigt und den Schwellenwert überschreitet. Der Zeitstempel des Panels, die Punktzahl und die Diagnoselinie erklären, warum der Trend gestiegen ist (hohe Ausbruchsfolge, niedrige Spike-Frequenz, mäßige Glattheit), sodass das visuelle Chart den Entscheidungsprozess dokumentiert und nicht nur einen isolierten Handelsvorschlag zeigt.
In der Stichprobe mit 1.200 Balken fanden wir 171 Schwankungen und 80 Rücksetzer (etwa 44,2 % der Schwankungen führten zu einem messbaren Rücksetzer). Die Londoner Sitzung hat den größten Anteil an der Handelsspanne. Wenn Sie also auf starke Bewegungen setzen wollen, sollten Sie sich auf die Londoner Stunden konzentrieren. Die Schwingungszyklen liegen im Durchschnitt bei 9,62 Takten, was angibt, wie regelmäßig die Schwankungen auftreten. Am wichtigsten ist, dass der Durchbruch extrem hoch ist (≈98%).

Das entsprechende Chart bezieht sich auf GBPUSD, ein Paar, das mit EURUSD korreliert. Der Market-DNA weist im Vergleich zum EURUSD eine größere Schwankungsbreite auf: Er verzeichnete 161 Ausschläge und 80 Rücksetzer, mit einem durchschnittlichen Ausschlagszyklus von 11,28 Balken. Die Durchbruchsquote ist hoch (≈94 %), und die Londoner Sitzung liefert den größten Anteil der Spanne (≈46 %).
Schlussfolgerung
Nachdem wir Sie durch den gesamten Prozess geführt haben – von der anfänglichen Idee über die Implementierung bis hin zum Testen mit ermutigenden Ergebnissen – können wir nun feststellen, dass dieses Tool das einzigartige „Make-up“ jedes Paares auf der Grundlage seiner Preisbewegung effektiv erfasst. Sie können mit den Eingaben experimentieren, einen Backtest oder einen Demotest durchführen, um die für Ihre Strategie am besten geeigneten Werte zu finden. Beachten Sie jedoch, dass dieser EA in erster Linie zu Lehrzwecken entwickelt wurde, insbesondere um die Identität jedes Paares anhand seiner historischen Kursentwicklung zu erkennen. Es sollte nicht für den Live-Handel mit echtem Geld verwendet werden, sondern eher als unterstützendes Werkzeug neben Ihren bestehenden Strategien.
Siehe meine anderen Artikel.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19460
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Aufbau von KI-gesteuerten Handelssystemen in MQL5 (Teil 2): Entwicklung eines ChatGPT-integrierten Programms mit Nutzeroberfläche
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (XI) – Korrelation im Nachrichtenhandel
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Wieder eine schöne Idee und Arbeit von C. Benjamin, danke und gesunde Tage für dich und deine Lieben.