コモディティ通貨バスケットでUSD/JPYを読む:初心者向けシンプルEA(MQL4完全版)

FX

結論:「AUDUSD・NZDUSD・USDCAD」の3通貨で構成するコモディティ通貨バスケットの短期変化率を先行指標として用い、USDJPYにエントリーします。AUD・NZDの上昇(=USD売り)とUSDCADの下落(=USD売り)を同時に観測できたとき、USDの弱さが広がりやすく、USDJPYは下落しやすい——この同時性を閾値で捉えて売り、逆にバスケットが弱いときは買いで追随します。

本記事は初心者でもMT4(MQL4)で即運用できる完全EAを収録し、実務的な設計・パラメータ・検証方法まで一気通貫で解説します。必要なのは、USDJPY・AUDUSD・NZDUSD・USDCADの4銘柄が取引できる一般的なFX口座だけです。

1. 戦略のアイデア(なぜコモディティ通貨が先行しやすいのか)

オーストラリア・ニュージーランド・カナダはいずれも資源・一次産品の輸出が経済に占める比率が高く、リスク選好の局面でこれらの通貨が買われやすい傾向があります。米国ドルが売られ、AUD・NZDが買われ、同時にUSDCAD(USD/カナダドル)は下落(=CAD高)しやすい局面が短期で頻出します。USDJPYは「USDの強弱」に最も敏感なメジャー通貨のひとつであり、USD 弱→USDJPY 下落、USD 強→USDJPY 上昇の反応が比較的素直に出ます。

よって、「AUDUSD と NZDUSD が上昇」かつ「USDCAD が下落」という組み合わせを「USD 弱」の同時サインと定義し、USDJPY の戻り売り/押し目買いに繋げます。これを一つの合成指標(バスケット)にまとめることで、ノイズを平均化しつつ、信号の同時性を機械的に捉えられます。

2. バスケットの定義と数式

時間足 T(推奨:M15)で、各ペアの短期乖離(=価格 ÷ EMA – 1)を計算します。

<数式>
R_AUD = Close(AUDUSD, T) / EMA(AUDUSD, n) - 1
R_NZD = Close(NZDUSD, T) / EMA(NZDUSD, n) - 1
R_CAD = Close(USDCAD, T) / EMA(USDCAD, n) - 1

# USD 弱をプラスで表現するため USDCAD はマイナス符号で加算
Basket = w1 * R_AUD + w2 * R_NZD - w3 * R_CAD
Z = (Basket - EMA(Basket, nZ)) / (StdDev(Basket, nZ) + 1e-8)
  

解釈:Z が +k を超えればUSD 弱(=USDJPY 売り)、-k を割れればUSD 強(=USDJPY 買い)と判断します。重みは初期値 w1=w2=w3=1。EMA長さ n=20、Z の標準化窓 nZ=50 が起点です。

3. 売買ルール(シンプルで堅実)

  1. エントリー:Z ≥ +Zup なら USDJPY を新規売り、Z ≤ -Zdn なら USDJPY を新規買い
  2. エグジット:ATR に基づく損切り・利確・トレーリングを併用します。
    ・損切り=SL = kSL × ATR(14)、利確=TP = kTP × ATR(14)
    ・含み益が TrailStart × ATR を超えたら、TrailStep × ATR で追随。
  3. 同時保有数:常に片張り 1 ポジション(買いか売りのどちらか一つ)。新規サインは既存ポジションをクローズ後に反転。
  4. 時間フィルター:流動性が厚いロンドン〜NY時間(日本時間 16:00〜翌 3:00)に限定するのが無難です。
  5. スプレッド制御:USDJPY の現在スプレッドが MaxSpread(pips)を超える場合は売買回避。

4. リスク管理(ロット計算)

1トレードの最大損失を口座残高の RiskPct(例:1%)に制限します。ATR から逆算した損切り幅(pips)と USDJPY の 1pip 価値からロットを計算します。

<ロット計算イメージ>
stop_pips = kSL × ATR(14) [pips換算]
risk_money = AccountBalance × RiskPct
lot = risk_money / (stop_pips × pipValueUSDJPY)
  

5. 実装:MQL4 完全EA

以下をそのまま MetaTrader 4 / MQL4 / Experts に保存し、MT4 を再起動してチャート(USDJPY, M15 推奨)に適用します。ブローカーの銘柄名にサフィックス(例:.m)が付く場合は、パラメータ SymAUD などで調整してください。

//+------------------------------------------------------------------+
//|  Commodity Basket USDJPY EA (MQL4)                               |
//|  Simple, beginner-friendly, single-position engine               |
//+------------------------------------------------------------------+
#property strict

input string  SymUSDJPY = "USDJPY";
input string  SymAUD    = "AUDUSD";
input string  SymNZD    = "NZDUSD";
input string  SymCAD    = "USDCAD";
input ENUM_TIMEFRAMES SigTF = PERIOD_M15;

input int     LenEMA    = 20;
input int     LenZ      = 50;
input double  W_AUD     = 1.0;
input double  W_NZD     = 1.0;
input double  W_CAD     = 1.0;    // will be used with negative sign

input double  Zup       =  1.2;   // sell USDJPY when Z >= Zup
input double  Zdn       =  1.2;   // buy  USDJPY when Z <= -Zdn
input double  kSL_ATR   =  2.0;
input double  kTP_ATR   =  3.0;
input double  TrailStart_ATR = 2.0;
input double  TrailStep_ATR  = 0.8;

input double  RiskPct   =  1.0;   // per trade, % of balance
input int     Magic     =  20250905;
input int     Slippage  =  2;
input double  MaxSpread =  2.0;   // in pips for USDJPY

// Session filter (Tokyo=+9). Trade 16:00-03:00 JST by default
input bool    UseSess   = true;
input int     SessStart_HH = 16;  // JST
input int     SessEnd_HH   = 3;   // JST (next day)

// News skip (simple minutes around hour)
input bool    SkipTopOfHour = true;
input int     SkipMinBefore = 2;
input int     SkipMinAfter  = 2;

int OnInit() { return(INIT_SUCCEEDED); }
void OnDeinit(const int reason) {}

//--- utilities
double PipValueJPY(string sym){
   // 1 pip (0.01 for JPY pairs). Adjusts for 3-digit brokers.
   double pt = MarketInfo(sym, MODE_POINT);
   int    dg = (int)MarketInfo(sym, MODE_DIGITS);
   double pip = (dg==3 || dg==5) ? 10*pt : 1*pt; // 0.001 or 0.01 for JPY
   double tickvalue = MarketInfo(sym, MODE_TICKVALUE);
   double ticksize  = MarketInfo(sym, MODE_TICKSIZE);
   if(ticksize<=0) ticksize = pt;
   // Value per pip:
   return (pip / ticksize) * tickvalue;
}

bool SpreadOK(string sym, double max_pips){
   double pt = MarketInfo(sym, MODE_POINT);
   int dg    = (int)MarketInfo(sym, MODE_DIGITS);
   double pip = (dg==3 || dg==5) ? 10*pt : 1*pt;
   double spr = (MarketInfo(sym, MODE_ASK) - MarketInfo(sym, MODE_BID))/pip;
   return (spr <= max_pips + 1e-9);
}

bool TimeOK(){
   if(!UseSess) return true;
   // Convert server time to JST (+9h). Broker times vary; this is an approximation.
   datetime t = TimeCurrent() + 9*60*60;
   int h = TimeHour(t);
   if(SessStart_HH <= SessEnd_HH){
      return (h >= SessStart_HH && h < SessEnd_HH);
   }else{
      // spans midnight
      return (h >= SessStart_HH || h < SessEnd_HH);
   }
}

bool HourEdgeSkip(){
   if(!SkipTopOfHour) return false;
   datetime t = TimeCurrent();
   int m = TimeMinute(t);
   return (m <= SkipMinAfter || m >= (60 - SkipMinBefore));
}

double ema(string s, ENUM_TIMEFRAMES tf, int len, int shift){
   return iMA(s, tf, len, 0, MODE_EMA, PRICE_CLOSE, shift);
}

double atr(string s, ENUM_TIMEFRAMES tf, int len, int shift){
   return iATR(s, tf, len, shift);
}

bool GetReturns(double &rAUD, double &rNZD, double &rCAD){
   double cAUD = iClose(SymAUD, SigTF, 0);
   double mAUD = ema(SymAUD, SigTF, LenEMA, 0);
   double cNZD = iClose(SymNZD, SigTF, 0);
   double mNZD = ema(SymNZD, SigTF, LenEMA, 0);
   double cCAD = iClose(SymCAD, SigTF, 0);
   double mCAD = ema(SymCAD, SigTF, LenEMA, 0);
   if(cAUD==0 || mAUD==0 || cNZD==0 || mNZD==0 || cCAD==0 || mCAD==0) return false;
   rAUD = cAUD/mAUD - 1.0;
   rNZD = cNZD/mNZD - 1.0;
   rCAD = cCAD/mCAD - 1.0;
   return true;
}

double iStdDevSeries(double arr[], int len){
   if(len<=1) return 0.0;
   double s=0, s2=0;
   for(int i=0;i510) L=510;
   for(int i=0;i=0;i--){
      ema_b = alpha*b[i] + (1.0-alpha)*ema_b;
   }
   // StdDev
   double sd = iStdDevSeries(b, L);
   Z = (latest - ema_b) / (sd + 1e-8);
   return true;
}

int CountPositions(){
   int cnt=0;
   for(int i=OrdersTotal()-1;i>=0;i--){
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()==SymUSDJPY && OrderMagicNumber()==Magic) cnt++;
   }
   return cnt;
}

bool CloseAll(){
   bool ok=true;
   for(int i=OrdersTotal()-1;i>=0;i--){
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=SymUSDJPY || OrderMagicNumber()!=Magic) continue;
      int type = OrderType();
      double lots = OrderLots();
      if(type==OP_BUY){
         ok = ok && OrderClose(OrderTicket(), lots, MarketInfo(SymUSDJPY, MODE_BID), Slippage);
      }else if(type==OP_SELL){
         ok = ok && OrderClose(OrderTicket(), lots, MarketInfo(SymUSDJPY, MODE_ASK), Slippage);
      }
   }
   return ok;
}

void ManageTrailing(){
   for(int i=OrdersTotal()-1;i>=0;i--){
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderSymbol()!=SymUSDJPY || OrderMagicNumber()!=Magic) continue;
      double atr_p = atr(SymUSDJPY, PERIOD_M15, 14, 0);
      double pt = MarketInfo(SymUSDJPY, MODE_POINT);
      int dg = (int)MarketInfo(SymUSDJPY, MODE_DIGITS);
      double pip = (dg==3 || dg==5) ? 10*pt : pt*1.0;
      double atr_pips = atr_p / pip;
      double trailStart = TrailStart_ATR * atr_pips;
      double trailStep  = TrailStep_ATR  * atr_pips;

      if(OrderType()==OP_BUY){
         double price = MarketInfo(SymUSDJPY, MODE_BID);
         double profit_pips = (price - OrderOpenPrice())/pip;
         if(profit_pips > trailStart){
            double newSL = price - trailStep*pip;
            if(newSL > OrderStopLoss()){
               OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0);
            }
         }
      }else if(OrderType()==OP_SELL){
         double price = MarketInfo(SymUSDJPY, MODE_ASK);
         double profit_pips = (OrderOpenPrice() - price)/pip;
         if(profit_pips > trailStart){
            double newSL = price + trailStep*pip;
            if(newSL < OrderStopLoss() || OrderStopLoss()==0){
               OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0);
            }
         }
      }
   }
}

void OnTick(){
   static datetime lastBarTime = 0;
   datetime curBarTime = iTime(SymUSDJPY, SigTF, 0);
   if(curBarTime == 0) return;
   if(lastBarTime == curBarTime) {
      ManageTrailing();
      return;
   }
   lastBarTime = curBarTime;

   if(!TimeOK() || HourEdgeSkip()) return;
   if(!SpreadOK(SymUSDJPY, MaxSpread)) return;

   // compute Z
   double Z=0.0;
   if(!GetZ(Z)) return;

   // ATR for sizing
   double atr_p = atr(SymUSDJPY, PERIOD_M15, 14, 0);
   double pt = MarketInfo(SymUSDJPY, MODE_POINT);
   int dg = (int)MarketInfo(SymUSDJPY, MODE_DIGITS);
   double pip = (dg==3 || dg==5) ? 10*pt : pt*1.0;
   double atr_pips = atr_p / pip;

   double stop_pips = MathMax(5.0, kSL_ATR * atr_pips);
   double take_pips = MathMax(5.0, kTP_ATR * atr_pips);

   double risk_money = AccountBalance() * (RiskPct/100.0);
   double pipValue   = PipValueJPY(SymUSDJPY);
   double lots = MathMax(0.01, NormalizeDouble(risk_money/(stop_pips * pipValue), 2));

   int pos = CountPositions();

   // signal
   if(Z >= Zup){
      // USD weak -> USDJPY sell
      if(pos>0){ CloseAll(); pos=0; }
      if(pos==0){
         double ask = MarketInfo(SymUSDJPY, MODE_ASK);
         double sl  = ask + stop_pips*pip;
         double tp  = ask - take_pips*pip;
         OrderSend(SymUSDJPY, OP_SELL, lots, Bid, Slippage, sl, tp, "Basket Sell", Magic, 0, clrRed);
      }
   }else if(Z <= -Zdn){
      // USD strong -> USDJPY buy
      if(pos>0){ CloseAll(); pos=0; }
      if(pos==0){
         double bid = MarketInfo(SymUSDJPY, MODE_BID);
         double sl  = bid - stop_pips*pip;
         double tp  = bid + take_pips*pip;
         OrderSend(SymUSDJPY, OP_BUY, lots, Ask, Slippage, sl, tp, "Basket Buy", Magic, 0, clrBlue);
      }
   }

   ManageTrailing();
}
//+------------------------------------------------------------------+

6. パラメータ選定の起点

  • 時間足:M15(ノイズがやや少なく、約定コストに対して十分な値幅を取りやすい)。
  • LenEMA:20、LenZ:50。
  • Zup/Zdn:1.2(過度な頻発を抑えつつ、機会を逃しにくい)。
  • kSL_ATR / kTP_ATR:2.0 / 3.0(リスクリワード 1:1.5 を目安)。
  • RiskPct:1%(資金曲線の安定化)。
  • 時間フィルター:16:00〜翌3:00 JST。

7. バックテストのやり方(手順書)

  1. MT4 の「履歴センター」で USDJPY・AUDUSD・NZDUSD・USDCAD の M15 ヒストリを更新。
  2. ストラテジーテスターでUSDJPY, M15、モデルは「毎ティック」または「1分足OHLC」を選択。
  3. 期間は 3〜5 年を推奨。スプレッドは固定ではなく、変動スプレッド相当に近づけるため、手元の平均値+α(例:2.0〜3.0pips)を指定。
  4. 「Visual mode」をONにし、シグナル点灯のタイミングを目視で確認。過剰な連打や薄商い時間の約定を避けられているかをチェック。
  5. 次にパラメータを1変数ずつ微調整(LenEMA, LenZ, Zup/Zdn, kSL/kTP)。曲線が滑らかで、過去・将来で過適合が少ない領域を選ぶ。
  6. ウォークフォワード:2019-2022 で最適化 → 2023-2024 で検証、などの分割で安定性を確認。

8. 実運用の注意(失敗しやすいポイント)

  • シンボル名の相違:ブローカーにより USDJPY.m 等のサフィックスが付く場合があります。パラメータで正確な銘柄名に変更してください。
  • 必要データの未読み込み:MT4 は他シンボルのデータが未ロードだとインジケータが 0 を返すことがあります。必ず各ペアのチャートを一度表示してデータを読み込ませてください。
  • スプレッド拡大時の約定:ニュース直後や薄商いでスプレッドが広がると期待値が崩れます。MaxSpread で制御。
  • 日本時間の休日朝などの流動性低下:時間フィルターを守るだけでドローダウンが減ることがあります。
  • ロット過大:ATRで損切りを広く取るとロットは必然的に小さくなります。逆らわないこと。

9. 拡張アイデア(中級者向け)

  • 重み最適化:分散最小化や主成分で w を決める(固定1:1:1からの小調整)。
  • 順張り/逆張りの切替:Z の符号と USDJPY の短期トレンド(例:価格 vs EMA)を掛け合わせ、トレンド時は順張り、レンジ時は逆張り。
  • ボラティリティ・レジーム認識:ATR やパーセンタイルで市場の騒音レベルに応じて Zup/Zdn を自動調整。
  • マルチタイムフレーム:M5 の点灯は M15 でも半分以上同方向、のような整合性条件でダマシを低減。

10. チェックリスト(導入前の最終確認)

  • USDJPY・AUDUSD・NZDUSD・USDCAD の銘柄が取引可能である。
  • 各シンボルのヒストリが十分に読み込まれている。
  • パラメータは初期値で開始。微調整は検証後に。
  • 1トレードのリスクは 1% 以内。複利は緩やかに
  • VPS や常時起動環境を用意し、再接続・回線落ち対策を行う。

まとめ

コモディティ通貨バスケットは、USD の強弱を同時性で測り、USDJPY に手早く反映させるシンプルなアプローチです。EA化すれば裁量判断のブレを排し、初心者でも手順通りに運用できます。まずは小ロットで実践し、自分の口座条件での期待値を把握しながらスケールしてください。

コメント

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