【MT5】通貨強弱EAにATRの上限を追加|日足フィルターを入れる前にログで検証

作業日記

こんにちは、潤奈です!

今回は、2026年10月7日〜9日の通貨強弱EAの開発記録です。

作っているのは、複数の通貨の強さ・弱さを比較して売買するMT5用のEAです。前回は、相場の値動きに合わせて決済幅を変えるATR連動のv4を作り、建値移動とトレイリングに最低幅を設けました。

その続きをテストし、今回は広がりすぎる値幅への上限、分析用ログの追加、USDJPYとGBPJPYの設定調整を進めました。特に残しておきたいのは、成績が気になる場面で、すぐに売買条件を増やさず、まず確認用のデータを増やしたことです。

ATR連動の幅には、下限だけでなく上限も付ける

ATRは値動きの大きさを見る指標です。ATRに倍率を掛けて決済幅を決めると、相場の変動に合わせて幅を変えられます。

前回追加した下限は、値動きが小さいときに建値移動やトレイリングが早く働きすぎるのを抑えるためのものでした。一方、ATRが大きくなると、損切りの幅や、建値移動が始まるまでの距離も広がります。

10月7日は、この広がり方にも上限を設けました。対象は、初期損切り幅、建値移動の発動幅、トレイリングの開始幅と追従幅です。

以下は、建値移動の発動距離を求める実装の抜粋です。

double be_pips = (atr_val * InpBreakEvenAtrMultiplier) / pips_unit;
be_pips = MathMax(InpMinBreakEvenPips, be_pips);
if(InpMaxBreakEvenPips > 0.0)
   be_pips = MathMin(InpMaxBreakEvenPips, be_pips);
double trigger_dist = be_pips * pips_unit;

この抜粋で使っている変数は、EA内で付けた名前です。Inpで始まるものは、EAの設定画面で指定する入力パラメーターです。

変数名用途・単位
atr_val取得済みのATR。価格単位の値動きの幅です。pips換算前の値が入っています。
InpBreakEvenAtrMultiplier建値移動の発動幅を求めるため、ATRに掛ける倍率です。
pips_unit1pipに相当する価格差です。例えばUSDJPYで1pipを0.01円とする場合は0.01。価格差をこの値で割るとpips、pipsに掛けると価格差になります。
be_pips建値移動を始めるまでに必要な含み益の幅。pips単位で計算し、下限・上限を順に反映します。
InpMinBreakEvenPips建値移動の発動幅の下限。単位はpipsです。
InpMaxBreakEvenPips建値移動の発動幅の上限。単位はpipsで、0なら上限を適用しません。
trigger_distbe_pipsを価格単位へ戻した発動距離。後続の処理で、エントリー価格から有利な方向へ動いた価格差と比較します。

MathMaxとMathMinは、独自に作った関数ではなくMQL5の標準関数です。MathMaxは2つの値の大きい方、MathMinは小さい方を返します。このコードは発動距離を計算する部分で、実際に損切り位置を変更する処理は含めていません。

最初にATRから求めた価格差をpipsへ換算し、MathMaxで下限を、MathMinで上限を適用します。最後に、注文価格と比較するための価格単位へ戻しています。MathMinは2つの値の小さい方を返す関数です。MQL5公式リファレンス

追加時の設定は、建値移動の下限20pips、上限45pips、ATR倍率1.5でした。この場合、ATRが10pipsなら発動幅は20pips、20pipsなら30pips、40pipsなら45pipsになります。上限設定が0の場合は、上限を適用しない実装です。

これは建値移動を開始する条件であって、確保利益の幅とは別です。また、損切りに上限を設定しても、実際の損失額まで保証されるわけではありません。約定価格のずれなどは別に考える必要があります。

抜粋ではATR取得、入力値の検査、保有ポジションの確認、注文変更などを省略しています。単独で動くEAではありません。下限と上限を設定する際は、下限が上限を超えないようにする必要もあります。

横ばいが気になったので、日足の情報をCSVに残す

上限を追加した後も、テストの途中に数年単位の横ばいがありました。そこで候補になったのが、日足の移動平均線を使った売買方向のフィルターです。

例えば、価格が日足EMA200より上なら売りを見送る、という考え方です。ただ、それで減るのが負け取引だけなのかは、まだ分かりません。

10月8日は、まずエントリー条件を変えず、取引時点の日足情報をCSVへ記録する改修を行いました。

追加した項目確認したい内容
D1_EMA20・D1_EMA75・D1_EMA200日足の各EMAの値
D1_Diff_EMA200エントリー価格とEMA200の差をpipsで記録
D1_Slope_EMA200確定日足5本分のEMA200の変化量
D1_Trendエントリー価格がEMA200以上か、未満か

EMA200を取得する部分は、次のようになっています。こちらも実装の一部を抜粋したものです。

double d1_buf_curr[1];
double d1_buf_prev[1];
if(CopyBuffer(g_d1_ema200_handle, 0, 1, 1, d1_buf_curr) > 0)
  {
   d1_ema200 = NormalizeDouble(d1_buf_curr[0], digits);
   double open_p = g_current_record.open_price;
   if(open_p > 0.0 && pips_unit > 0.0)
     {
      d1_diff_ema200 = NormalizeDouble((open_p - d1_ema200) / pips_unit, 1);
      d1_trend = (open_p >= d1_ema200) ? 1 : -1;
     }
   if(CopyBuffer(g_d1_ema200_handle, 0, 6, 1, d1_buf_prev) > 0 && pips_unit > 0.0)
     {
      d1_slope_ema200 = NormalizeDouble((d1_buf_curr[0] - d1_buf_prev[0]) / pips_unit, 1);
     }
  }

この抜粋は、日足EMA200の値を受け取り、エントリー価格との位置関係を記録するための処理です。変数名のD1は日足を表します。g_はこのEAで全体から参照する変数に付けた接頭辞で、MQL5の特別な命令ではありません。

変数名用途・単位
g_d1_ema200_handle日足EMA200の指標ハンドル。別の場所で作成した指標を指定するための識別子で、EMAの価格そのものではありません。
d1_buf_curr直前に確定した日足のEMA200を受け取る、要素数1の配列です。d1_buf_curr[0]で取得した価格の値を参照します。
d1_buf_prev開始位置6の日足のEMA200を受け取る、要素数1の配列です。開始位置1の値との差を計算するために使います。
digits対象銘柄の価格の小数点以下の桁数。EMAの価格を丸める桁数として使います。
d1_ema200取得したEMA200を銘柄の価格桁数に丸めた値。単位は価格です。
g_current_record.open_price現在記録している取引情報の中の、エントリー価格です。ドットの前が取引情報をまとめた構造体の変数、後ろがその項目名です。
open_pg_current_record.open_priceを、この処理内で使うために受け取った変数。単位は価格です。
pips_unit上の抜粋と同じ、1pipに相当する価格差。価格差をpipsに換算するために使います。
d1_diff_ema200エントリー価格からEMA200を引いた差。単位はpipsで、プラスならEMAより上、マイナスなら下です。
d1_trendエントリー価格がEMA200以上なら1、未満なら-1を入れる分類値。この変数自体が売買を実行するわけではありません。
d1_slope_ema200開始位置1と6のEMA200の差をpipsに換算した値。プラスならその5本分で上昇、マイナスなら下降したことを表します。

CopyBufferとNormalizeDoubleもMQL5の標準関数です。CopyBufferは指標から値を配列へ取得し、戻り値は取得した要素数を表します。今回は1個を要求しているので、戻り値が0より大きい場合に、その値を使います。NormalizeDoubleは指定した小数桁数へ丸める関数で、価格にはdigits、pipsの差や変化量には1桁を指定しています。

最初のCopyBuffer呼び出しの引数は、順に「指標ハンドル」「指標内のバッファ番号0」「開始位置1」「取得数1」「受け取り先の配列」です。開始位置は0が現在の足なので、1を指定すると直前に確定した日足のEMAを読みます。2回目は開始位置を6にしています。傾きは開始位置1と6の差なので、厳密には暦の5日間ではなく日足5本分の変化量です。MQL5公式リファレンス

ハンドルの作成・有効性確認、変数の初期化、CSVへの書き込みは省略しています。また、取得に失敗した値を正常な観測値と混同しないことも、ログを分析するときの確認事項です。

日足フィルターは、今回は追加しなかった

2010年〜2019年の取引ログを日足EMA200との位置関係で分けると、「EMA200より上で売った取引」にも利益が残っていました。当日の集計では、この区分は145取引、合計+631.1pipsです。

この区分を一律に除けば、負けた取引だけでなく、その利益も除くことになります。後の期間で売りが苦戦していたとしても、それだけで「この条件の売りを全部止めよう」とは決められませんでした。

そこで、日足フィルターの追加は見送りました。

ここで行ったのは、既存の取引ログを条件で分類する事後分析です。実際にフィルターを入れると、その後に保有できるポジションや再エントリーの機会も変わり得るため、行を除いた集計が、そのまま変更後EAのバックテスト結果になるわけではありません。

今回は、条件を追加する前の判断材料として使いました。

USDJPYは、動かす設定を分けて調整する

続いて、v4をベースに設定の最適化を進めました。一度に全部のパラメーターを動かすと、組み合わせが増え、どの変更が効いたのかも追いにくくなります。

今回は、次の順番で対象を分けました。

順番調整する部分
1建値移動の発動条件
2トレイリングの開始幅・追従幅
3初期損切りと利確の幅
4上位足H4の通貨強弱差の条件
5損失後に再エントリーを止める時間

この順番であれば必ず最適になる、という話ではありません。固定している他の設定との組み合わせで結果は変わります。今回は、基準設定を残しながら変更点を追うための進め方として採用しました。

USDJPYは5段階の調整後、2019年〜2025年の期間でも確認しました。保存CSVのうち、この期間にエントリーした取引を再集計した値は次のとおりです。取引数と合計pips、累積pipsの最大下落幅は、10月8日の保存出力とも一致しています。

項目USDJPY・v4の集計
取引数393回
合計損益+1,396.1pips
pipsベースのPF約1.22
確定取引の累積pipsの最大下落幅373.7pips

PFは利益pipsの合計7,783.0を、損失pipsの絶対値の合計6,386.9で割った値です。口座通貨で計算したPFとは区別しています。最大下落幅も決済済みの取引を決済時刻順に並べた集計で、含み損を含む口座のドローダウンではありません。

これらは開発中のバックテストの記録で、実運用成績ではありません。また、調整期間の2010〜2019年と比較期間の2019〜2025年には、2019年の重複があります。後の期間の結果を見て設計を変更してきた経緯もあるため、完全に未使用のデータで優位性を確認できた、とは扱っていません。

GBPJPYでは、最適化の前に基準設定を見直す

10月8日夜からは、USDJPYの設定をGBPJPYにも当てて確認しました。

ここで見直したのが、最適化を始める前の設定です。建値移動だけを調整しても、固定している損切り幅やトレイリング幅が合っていなければ、その制約を受けた結果になります。

そのため10月9日は、まずGBPJPY用の基準設定を全体に置き、そこから順番に調整する方針にしました。損失後の待機時間も、最初から制限を掛けず、0時間で確認しています。

9日中に確認できたのは、建値移動、トレイリング、損切り・利確の3段階の最適化結果までです。その後にH4条件を調整する案が出ていますが、10月10日以降の結果は今回の記事には含めていません。

今回の開発では、機能を追加するだけでなく、追加を見送る判断もありました。気になる結果が出たら、何を記録すれば判断できるかを考える。そして調整するときは、変更する設定だけでなく、固定している設定も確認する。この2つを、今後の検証でも大切にしていきたいと思います。

コメント

タイトルとURLをコピーしました