プライスアクション分析ツールキットの開発(第40回):Market DNA Passport
内容
はじめに
生物学において、デオキシリボ核酸(DNA)は、すべての生物に固有の遺伝的設計図を記述している分子です。DNAは生物のアイデンティティを定義し、世代を超えて受け継がれます。以前、父親であることを否定していた男性が、DNA検査によって実父であると証明された例を見たことがあります。これがプライスアクション分析とどのように関係するのかと思うかもしれませんが、その共通点は「持続的で識別可能な指紋が残る」という点にあります。
私の研究では、各通貨ペアにはそれぞれ特有のプライスアクションの署名が存在することが分かりました。あるペア同士は似た影響を受けるため似た動きを見せることがあります(例:EURUSDとGBPUSD)。一方で、まったく異なる挙動を示すペアも存在します。こうしたパターンを捉えるため、私は各銘柄を自動スキャンし、ボラティリティ、フラクタル構造、セッションリズム、リトレースメント特性などから抽出したコンパクトな指紋(市場のDNA)を生成する仕組みを構築しました。このMarket DNA Passportはそれらの特徴を定量化し、銘柄の比較、構造変化(突然変異)の検出、そして市場レジームに合った戦略選択や調整を可能にします。
ここでいう突然変異とは、EAが生成する市場の指紋に大きな変化が生じた状態を指し、前回と今回のDNAMetricsのコサイン距離や正規化L2距離が設定した閾値を超えた際にフラグが立ちます。これは通常、レジームシフト(例:ATR、スパイク頻度、セッション主導性、リトレースメント特性などの変化)を意味します。これは早期警告として扱うべきで、どの指標が変動したのかを確認し、執行を引き締めたり一時停止したり(ポジションサイズ縮小、ATR幅の広いストップ、シグナル閾値の引き上げなど)し、ライブ取引を再開する前に、数回の再計算やペーパートレードで新しいプロファイルを検証することを推奨します。
// 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)); }
続くセクションでは、使用した各種指標、MQL5での実装、そしてこのパスポートが堅牢な分析をどのように支援するのかを示す実践例について説明します。
概念的指標
Market DNA Passportは、数百本分の生のプライスアクションを、スパイク、フラクタルスイング、リトレースメント頻度、ATR正規化ボラティリティ、セッションごとのレンジ比率、平滑性など、安定して説明可能な指標へと圧縮します。これにより、プライスアクショントレーダーは「現在価格がどこにあるか」だけでなく、市場が構造的にどのように振る舞っているかを把握し、より素早く一貫した判断を下せるようになります。
これらの指標がプライスアクショントレーダーにとって重要である理由
| 重要性 | 説明 |
|---|---|
| 客観的な市場レジームの検出 | トレンドかレンジかを感覚で判断するのではなく、平滑性、フラクタル密度、ブレイクアウトのフォロースルーといった数値指標によって市場状態を定量化することで、トレンドフォロー型か平均回帰(ミーンリバージョン)型かをデータに基づいて切り替えることができます。 |
| より正確なエントリーバイアス | ブレイクアウトのフォロースルー率や「終値が高値/安値付近で終わったか」といった指標が、プライスアクションのセットアップにおける上位の確認シグナルとなり、ブレイクアウト継続か、フェイクブレイク(反転)かを判断しやすくなります。 |
| リスクを考慮したサイズ調整とフィルタリング | ATRを正規化した値やスパイク頻度を用いることで、適切なストップ幅やポジションサイズを判断しやすくなり、ATRが大きい場面、スパイクが多い場面では取引を控える、ストップを広げるといった判断ができます。 |
| パターンの検証と期待値 | リトレースメントの頻度分布は、銘柄や時間足ごとに「推進波の後、平均的にどれくらい押し戻すのか」を明確にし、現実的な利確設定や「50%押し目が本当に優位性があるのか」の判断材料になります。 |
| 効率的な銘柄選択 | EAが大量の事前分析をパスポートの形で要約してくれるため、複数の銘柄や時間足を素早くスキャンし、自分のエッジに合致する銘柄だけを選ぶことができます。 |
| 説明可能性 | すべてのシグナルに「なぜその判断に至ったか」の数値根拠(例:フォロースルーが高い、スパイクが少ない、市場が滑らか)がログとして残るため、取引理由を監査でき、ルール改善につなげやすいというメリットがあります。 |
ローソク足およびボラティリティ分析の指標
1. ローソク足の構造と終値
wick_body_ratio_avg:ヒゲ全体の長さと実体サイズの平均比率
- 値が高い → ヒゲが長い → 反転の兆候(ピンバー、流れ星)
- 値が低い → トレンドの勢いが強いローソク足 → 安定した買い/売り圧力
- 歪みを避けるため、実体が極端に小さい場合は下限値を設ける
pct_close_near_high:ローソク足がレンジ上位20%で引けた割合
- 値が高い → 強い需要、強気バイアス
- 流動性が薄い市場ではダマしになる場合がある
pct_close_near_low:ローソク足がレンジ下位20%で引けた割合
- 値が高い → 売り圧力が優勢、弱気バイアス
- 特定セッションの癖により結果が歪むことがある
pct_doji:十字線(実体がレンジの約10%未満)の割合
- 値が高い → 市場の不確実性
- 連続する十字線は強いブレイクアウトに先行することがある
nearHigh_count / nearLow_count:高値/安値付近で引けたローソク足の数
- 需給バランスの変化を読み取るために使用
doji_count:十字線の総数
- 相場が動き出す前の溜めのフェーズを捉えるのに役立つ
2. ボラティリティとモメンタム
atr_mean:ATRの平均値(価格単位での絶対的なボラティリティ)
- ストップやターゲット設定に使用
atr_pips:ATRをpipsに変換したもの
- 複数の銘柄を統一的に扱うのに便利
atr_norm:ATRを価格で正規化した値(ATR / Close)
- ボラティリティを相対的に評価
- 価格の低い銘柄では非常に敏感
pct_spikes:ローソク足のレンジがATRの一定倍数を超える割合
- 値が高い → ノイズ/急伸が頻発
spikes_count:インパルス的なローソク足の総数
bigTotal_count:大きなローソク足(> ATR)の総数
bigThenBig_count:大きなローソク足に続いて再度大きなローソク足が出現した回数
- 値が高い → ボラティリティの塊 → トレンド発生時に多い
vol_clustering:大きなローソク足に続いて再度大きなローソク足が出た割合
- 増加したボラティリティの持続性を示す指標
3. フラクタルとサイクル
swing_cycle_bars_avg:フラクタル転換間の平均本数
- スイングサイクルの長さを示す
- レンジ相場では短縮されることがある
fractal_density:フラクタル密度(反転を形成したローソク足の割合)
- 値が高い → ガタついた横ばい相場
- 値が低い → トレンド相場
sw_count:フラクタルポイントの総数
breakout_follow_through:フラクタルレベルがブレイク後にATR以上進んで確定した割合
- 値が高い → ブレイクアウトが信頼できる
- 値が低い → ダマしが多い
4. 調整(リトレース)と押し戻り
retr_38_freq:約38%の押し戻り頻度
- 浅い調整が多い相場で見られる
retr_50_freq:50%押し戻り(44~56%)の頻度
- 最も古典的な調整レベル
retr_62_freq:約62%の深めの押し戻り頻度
- ボラティリティや相場レジームの変化を示すことがある
retr_gt70_freq:70%を超える深い押し戻りの割合
- 値が高い → インパルスが弱く、レンジに回帰する傾向
avg_max_retr:インパルス後の最大押し戻りの平均
- 現実的なストップやターゲット設定に役立つ
retr_count:押し戻り統計の対象となったインパルスの数(信頼性を判断するうえで重要)
5. セッションの活動性
asia_range_share / london_range_share / ny_range_share :各セッションが全体のレンジに占める割合
- 各セッションがどの程度価格変動に寄与しているかを示す
asia_range / london_range / ny_range :セッション内の絶対レンジ(高値–安値)
- 時間帯ごとのボラティリティ特性を表す
6. 複合指数およびサービスパラメータ
smoothness_index (0–1):平滑性指標
- 値が高い → トレンド的でノイズが少ない
- 値が低い → 横ばいでギザギザした市場
atr_cache_used:ATRのキャッシュデータ使用フラグ(内部処理用)
sample_bars:サンプル期間(バー数)
- サンプルが大きいほど安定するが、変化への反応は鈍くなる
MQL5での実装
MQL5 Market DNAエンジンは、選択した銘柄と時間足の過去バーを読み込み、価格の構造(ローソク足、ATR、フラクタルスイング、リトレースメント、セッションごとのレンジ比率)から価格アナトミーの指紋を算出する、コンパクトなシステムとして実装します。チャート上にラベル付きの「DNA Passport」を描画し、オプションで第2銘柄と比較し、指標やシグナルをCSVにログ出力し、指紋が意味のある変化を示した場合は軽量なルールベースの買い/売り提案と突然変異アラートを発行します。この一文で目的は簡潔に示されており、以下のコードは検査可能かつ再現可能な説明可能なメトリクスを生成することに焦点を当てています。
実装を追いやすくするために、本セクションではビルド時とランタイム時の前提、妥当な初期入力の推奨、ファイル中で出会う主要なヘルパーやライフサイクルフック(たとえばOnInit、OnTimer、Recalculate、BuildDNA、DrawPassport)を説明します。これらの基本を先に示すことで、続く5段階のウォークスルーを詳細に迷わず追えるようになります。
開始前に環境要件を満たしていることを確認してください。MetaTrader 5が必要で、EAのコンパイルと実行権限があること(#property strictとTrade/Trade.mqhを使用します)。EAはターミナルの共通ファイルフォルダへ書き込みをおこなうため、ファイルI/Oが許可されていることを確認してください。最後に、統計が安定するように、対象の銘柄/時間足に少なくとも約300本の過去バーがあることを推奨します。安定性を重視する場合は1200本を推奨します。履歴が不足していると、EAは警告を出し、結果がノイズの多いものになります。
#property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ja/users/lynnchris" #property version "1.0" #property strict #include <Trade/Trade.mqh>
最も手早く試したい場合は、Market DNA Passport.mq5をMQL5/Expertsフォルダにコピーし、MetaEditorでコンパイルしてチャートにアタッチします。InpTFとInpBarsを設定します(デフォルトはPERIOD_H1と1200)。必要であればInpLogCSVを有効にすると、指標をCSVとして保存できます。このEAはタイマーを使用し、新しいバーが確定するたびに再構築をおこないます。少し稼働させた後、Files\Common内のMarketDNA_log.csvとMarketDNA_signals.csvを確認し、出力内容を確認してください。
推奨される基本パラメータは、InpTF = PERIOD_H1、InpBars = 1200、InpATRPeriod = 14、InpSpikeATRMult = 2.0、InpDojiBodyPct = 0.10、InpFractalDepth = 5、InpRetrWindowBars = 50、InpFT_ATR_Mult = 0.5です。応答性とCPU負荷のバランスを取るために、InpRecalcSeconds = 10およびInpCacheATR = trueを設定するとよいでしょう。これらは、銘柄や時間足に合わせて調整する際の出発点として扱ってください。
//============================== 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
実務的な注意点として、名称、セーフティ、およびパフォーマンスに関するポイントを挙げます。EAはチャート上のオブジェクト名にMDNA_<symbol>_<TF>_という接頭辞を付け、名称衝突を回避します。CSVは固定ファイル名でFILE_COMMONフォルダに保存されます。安全性とパフォーマンスのため、EAはタイマーの最小間隔(5秒以上)を強制し、最新の確定バーに変化がある場合のみ重い再構築をおこないます。多数のインスタンスを動かす場合や、(5,000本超の)非常に大きな参照範囲で実行する場合は、CPUとメモリ使用量を抑えるためにタイマー間隔を長くするか、InpBarsを減らすことを推奨します。また、複数のEAインスタンスが同じCSVに同時書き込みをおこなうと競合が発生する可能性があるため、堅牢なマルチチャート記録が必要な場合は、銘柄/時間足ごとに一意のファイル名を使用することを検討してください。
このような前提を踏まえたうえで、実装の解説に進みます。ここでは、データ取得と前処理、ローソク足構造とボラティリティ指標、スイング、リトレースメント、ブレイクアウトの構造分析、スナップショットの比較とシグナル生成、そして最後にUI、永続化、ライフサイクル制御へと順に説明していきます。各部分は.mq5ファイル内の関数および論理ブロックに直結しているため、コードを追いながらローカル環境で完全に再現したり、ご自身の研究や取引スタイルに合わせてエンジンを拡張したりすることが可能です。
データ収集と前処理
まず、CopyRatesを使用したLoadRates(sym, tf, bars)により価格履歴を読み込みます。この際、ATRウィンドウやリトレースメントウィンドウなどの先読みスキャンに備えて安全マージンを確保します。その後、BarTR()によって各バーのTrueRangeを計算し、CalcATR(r, period, idx)を使って単純移動平均(SMA)ベースのATRを算出します。InpCacheATRが有効で、保存されているパラメータが一致する場合は、BuildDNA内のキャッシュ判定に基づいてグローバルg_atrs[]キャッシュを再利用し、ATRの再計算を回避します。SafeDiv、Clamp、LTrimといったヘルパー関数を使って入力や中間値を正規化し、ATRをpips/正規化単位(atr_pips、atr_norm)に変換します。また、最低限必要なバー数を確保するため、バー数が300未満の場合には関数がD.valid = falseを設定して処理を停止します。このステージの主な出力は、MqlRates r[]の価格データ配列と、後続処理で使用されるATR配列です。
// 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); }
ローソク足構造およびボラティリティ指標
次に、整えられた時系列データを走査し、ローソク足レベルの記述子およびボラティリティ関連のカウントを生成していきます。BuildDNA内のループでは、平均ヒゲ対実体比(wick_body_ratio_avg)を算出し、高値付近/安値付近で引けたローソク足のカウント(pct_close_near_high、pct_close_near_low)を集計します。また、InpDojiBodyPctを基準として同時線(pct_doji)を検出し、「TR > InpSpikeATRMult × atr_i」の条件を満たすATRスパイクも判定します。さらに、「大型」ATRバーと、それに連続する大型→大型の発生を検知し、ボラティリティ・クラスタリング(vol_clustering)の推定に利用します。これらすべてのバー単位の累積値は、範囲を制限した指標と生データカウント(例:nearHigh_count、spikes_count、bigThenBig_count)として集約され、DNAMetrics構造体へ格納されます。これらの項目は、類似度比較やシグナル判断の基礎として安定的に活用できるよう、意図的にクランプ処理および正規化が施されています。
// 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;
構造分析:スイング、リトレースメント、ブレイクアウトフォロースルー
まずBuildSwings(r, InpFractalDepth, sw[])によりフラクタルスイングポイントを検出します。これは、設定された深さに基づいて、局所的な高値や安値を抽出するものです。その後、スイングサイクル統計、すなわちインパルスあたりの平均バー数(swing_cycle_bars_avg)およびfractal_densityを算出します。リトレースメントの分析ではComputeRetracementHistogram(r, sw, swN, ...)を用います。反対方向のインパルスごとに、最大InpRetrWindowBars先まで反復処理し、インパルスに対する最大押し戻し率を記録します。得られた最大リトレースメント比は38% / 50% / 62% / 70%以上のバケットに分類され、平均化されてretr_38_freq、retr_50_freq、retr_62_freq、retr_gt70_freqを生成します。また、平均最大押し戻し(avg_max_retr)も算出されます。
別途、ComputeBreakoutFollowThroughはスイングイベントをスキャンし、スイングの極値を超えるブレイクアウトがATRスケールのターゲット(InpFT_ATR_Mult * atr)に到達するかどうかを確認し、フォロースルーイベントの割合としてbreakout_follow_throughを生成します。これら一連の構造指標によって、トレンドのリズム、典型的な押し戻しの深さ、およびブレイクアウトの信頼性を数量的に把握することができます。
// 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); }
スナップショット比較、突然変異検出、シグナル生成
新しいDNAMetricsインスタンスが作成されると、まずDNAVector(D, vec[])を用いて、選択された指標を固定の17次元ベクトルに変換します。次に、現在と直前のスナップショットをCosineDistance(A,B)およびNormalizedL2Distance(A,B)で比較します。いずれかがInpMutationThresh / InpMutationL2Threshを超えた場合、チャート上に突然変異バナーを表示し、(有効になっていれば)Alert()による通知もおこないます。また、このバナーは変化の大きさに応じて色分けされます。
突然変異検出と並行して、GenerateSignal(constDNAMetrics &D)が売買スコアを構築します。このスコアは、ブレイクアウトのフォロースルー、平滑性指数、スパイク発生率、高値・安値付近でのクローズ状況、そしてリトレースメントの分布といった複数の指標を基に合成されます。これらの値に対してATR由来のペナルティを適用し、最終的なスコアを適切な範囲に収めたうえで評価します。BUYまたはSELLのシグナルを出す際には、この合成スコアがInpSignalThresholdを満たし、さらに反対方向のスコアとの差がInpSignalGapを上回っていることを条件としています。通知まわりの処理はMaybeNotifyが担当しており、この関数がクールダウン(15分)を管理することで通知の乱発を防ぎます。発生したシグナルと、その判断根拠となった詳細な数値情報はMarketDNA_signals.csvに追記され、後から検証できるようになっています。
// 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(); }
表示、永続化、およびライフサイクル管理
最後に、チャート上のオブジェクトを使って結果を描画します。DrawPassport(title, D, x, y)は、ObjectCreateを使用してOBJ_RECTANGLE_LABELを生成し、その上に各種指標、警告、タイムスタンプを示す複数のOBJ_LABELを組み合わせて、ラベル付きのパネル(「パスポート」)を構成します。比較用の銘柄が指定されている場合は、DrawComparisonが簡潔な類似度ヘッダを描画します。永続化処理としては、WriteCSV(D, sym)とWriteSignalCSV(s, sym)を使用してFILE_COMMONフォルダに結果を書き込みます。ファイルが新規作成された場合には、ヘッダ行も自動的に生成されます。
EAのライフサイクル管理では、OnInitがEventSetTimer(sec)を設定します(secは必ず5以上)。OnTimerはRecalculate()を呼び出し、Recalculate()は最新の確定足の時刻が変化していない場合には(CopyRates(..., 0, 1)によるチェック)、重い再構築処理をスキップするよう最適化されています。OnDeinitではタイマーを停止し、ClearObjects()が接頭辞付きのUIオブジェクトを削除します。オプションのInpSelfTestおよびInpDebugRetrモードでは、スイングおよびリトレースメントのロジックを検証し、デバッグ出力を記録することができます。
// 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 }
結果
EAをEURUSDのH1チャートに適用すると、すぐにMarket-DNAパネルが構築され、選択したサンプルに基づいて銘柄の各種メトリクスが計算されました。このパネルは、ボラティリティ、スパイクやリトレースメントの特徴、各セッションの寄与度、その他の構造的シグナルを要約して表示します。これらの正規化された指標に基づき、EAは買いスコアと売りスコアをそれぞれ計算し、買いスコアが設定されたギャップをもって売りスコアを上回り、かつ閾値を通過したため、BUYシグナルを発しました。パネルにはタイムスタンプ、スコア、診断用のラインが表示され、ブレイクアウトのフォロースルーが高いこと、スパイク頻度が低いこと、平滑性が中程度であることなど、強気バイアスになった理由が示されています。そのため、このビジュアル表示は、単なる取引提案ではなく、意思決定プロセスそのものを文書化する役割を果たします。
1200本のサンプルから、171のスイングと80のリトレースメントが検出されました(スイング全体の約44.2%が測定可能なリトレースメントを伴ったことになります)。レンジの最大の割合を占めているのはロンドンセッションであるため、鋭い値動きを狙う場合はロンドン時間に注目すると良いでしょう。スイングサイクルは平均9.62本で、スイングがどれくらい規則的に発生しているかを示します。最も重要なのは、ブレイクアウトのフォロースルーが非常に高い(約98%)点です。

対応する図はGBPUSD用で、EURUSDと相関する通貨ペアです。Market-DNAはEURUSDと比較して変動が大きく、161のスイングと80のリトレースメントが記録され、平均スイングサイクルは11.28本でした。ブレイクアウトフォロースルーは高く(約94%)、ロンドンセッションがレンジの最大割合(約46%)を供給しています。
結論
初期のアイデアから実装、そして励みになるテスト結果まで、プロセス全体を説明した上で、このツールは各通貨ペアの価格変動に基づく独自の「特性」を効果的に捉えることができると結論付けられます。入力パラメータを試したり、バックテストやデモテストをおこなって、自身の戦略に最適な値を見つけることも可能です。ただし、このEAは主に教育目的で設計されており、各通貨ペアの過去の価格変動からその特性を明らかにすることを目的としています。実際の資金でのライブ取引には使用せず、既存の戦略の補助ツールとして活用することを推奨します。
他の記事もご覧ください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19460
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5での取引戦略の自動化(第32回):プライスアクションに基づくファイブドライブハーモニックパターンシステムの作成
FVGをマスターする:ブレーカーと市場構造の変化によるフォーメーション、ロジック、自動取引
初心者からエキスパートへ:NFP発表後の市場取引におけるフィボナッチ戦略の実装
MQL5における単変量時系列への動的モード分解の適用
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
C.ベンジャミンの脳を活性化させる素晴らしいアイデアと仕事。