コスト・アウェアEAの設計と実装:スプレッド・手数料・スリッページを味方にする初心者向け完全ガイド(MQL4フルEA付き)

FX

本記事は、FXの自動売買をはじめたい初心者が最短で「利益の出る取引の型」を体得するために、まず絶対に押さえるべきテーマ――取引コストに徹底的にフォーカスします。勝てるEA(Expert Advisor)は「勝率が高いEA」ではなく、期待値がコストを上回るEAです。ここを設計段階でクリアするだけで、同じロジックでも結果は別物になります。本記事は設計思想からバックテスト、ブローカー選定、そしてMQL4の完全自動売買EAのフルコードまで、実装可能なレベルで網羅的に解説します。

1. なぜ「コスト・アウェア」なのか

多くの初心者EAは売買シグナル(例:移動平均のクロスやブレイクアウト)だけに着目しがちです。しかし、現実の成績を決める第一要因はスプレッド手数料スリッページ、そしてスワップです。テスター上では+3%の戦略が、実口座ではコストで期待値が0%に近づきます。「エッジ > 取引コスト」かどうかを入場時にチェックし、閾値を超えないなら取引しない――これがコスト・アウェアEAの中核です。

2. 取引コストの内訳と数式

EA設計時に明示的にモデル化すべきコストは次の4つです。

  1. スプレッド:Bid/Ask差。ポイント(point)換算し、ロットに比例。
  2. 手数料(コミッション):ECN口座などで往復固定額/ロット。
  3. スリッページ:指値/成行約定の価格劣化。許容値は入力で管理、期待値には保守的に織り込む。
  4. スワップ:保有超過時に金利差。日跨ぎ戦略では必須。

1回の往復コスト(通貨建て)は概ね次式で近似できます:

往復コスト ≒ (スプレッド[point] + 想定スリッページ[point]×2) × point値 × ロット + 往復手数料 + 期待スワップ

ここでpoint値は1ポイントの貨幣価値(例:USDJPYで0.01円=1pointの時、1lotでの価値)。MT4では MarketInfo(Symbol(), MODE_POINT)MODE_TICKVALUE を組み合わせて計算します。

3. 期待値(エッジ)の設計

コスト・アウェアEAでは、シグナルが出ても即エントリーしません。「このトレードが平均してどれだけ有利に動く見込みか」を簡易に見積もり、エッジ > コスト × 安全係数 を満たす時だけエントリーします。初心者が扱いやすい簡易指標は以下の3つ:

  1. ATRベースの想定伸び代:直近N期間ATRの一定比率(例:0.35×ATR)。
  2. 時間帯優位性:ロンドン/NYの流動性時間は伸び代が増えやすい。
  3. トレンドフィルタ:MA傾きやADX>閾値で順張り時のみ。

たとえば「順張り買い」なら、想定伸び代(pips) × 勝率見込み − 想定押し戻し(pips) × 逆行確率 を簡略化して、≒ 0.35×ATR − 0.15×ATR のように保守的に設定し、これがコストを上回るかをチェックします。

4. コスト・ゲーティング:入場判定の流れ

  1. 現在のスプレッドが許容閾値以下か(例:<= 15 point)。
  2. 時間帯が許容レンジ内か(例:ロンドン~NY)。
  3. シグナルが点灯(例:MA短期が長期を上抜け、かつ傾き>0)。
  4. 想定エッジ(ATR比)が総コスト×安全係数を上回るか。
  5. ストップ・テイクプロフィットを想定ボラに基づき配置(例:SL=0.6×ATR、TP=1.2×ATR)。

この「取らない勇気」がEAの勝ち負けを劇的に変えます。

5. 実例:EURUSDでのコスト・アウェア閾値設計

例として、ECN口座のEURUSD、往復手数料6USD/lot、平均スプレッド0.2pip、想定スリッページ各0.1pipとします。1pip=10USD/lotのため、往復コストは (0.2 + 0.2)×10 + 6 = 10USD です。安全係数1.3を掛け、必要エッジ=13USD。ATRが10pipなら、伸び代の保守見積り0.35×10=3.5pip=35USD。十分に上回るのでエントリー許可、といった判定が可能です。

6. ブローカー/口座選定:ECN vs STP

同じ戦略でも口座で別物になります。一般にECN(低スプレッド+コミッション)は短期EAと相性が良く、STP(やや広いスプレッド、コミッション込)はスキャルでは不利になりがちです。重要なのは、自分のEAのボラと保有時間に最適な「総コスト」が最小化される組み合わせを選ぶこと。デモ口座でスプレッドの時系列を必ず記録し、時間帯ごとの実効コストの分布を確認してください。

7. ログ設計:コストを「見える化」する

バックテスト/フォワードで最低限記録したいカラム:

  • Timestamp / Symbol / Session(Tokyo/London/NY)
  • Spread[point], SlippageAssumed[point], CommissionPerLot
  • ATR, EdgeEstimate[pips], RequiredEdge[pips], SafetyFactor
  • EntryPrice / SL / TP / Lot
  • Outcome(TP/SL/ExitTime) / MFE / MAE / PnL

このログをスキャッタで「EdgeEstimate − RequiredEdge」を横軸、「PnL」を縦軸に描くと、閾値設定の妥当性が一発で見えます。

8. リスク管理:勝つ前に生き残る

リスクは「1トレードの破壊力」と「連敗の連鎖」で来ます。コスト・アウェアEAの推奨は:

  • ロット設計:口座残高の0.5~1.0%を1トレードの最大損失(SL)に。
  • 同時ポジション数:通貨相関を考え、最大2~3に制限。
  • ニュース回避:高インパクト指標前後はコストが急拡大するため休止。

9. バックテストの地雷回避

短期EAのテストは、ティック精度スプレッド/手数料の正確再現が命です。MT4の標準テスターではブローカー実勢のスプレッド変動が再現されにくいため、余裕を持ったコスト上乗せを初期設定に織り込みます(例:平均スプレッド+0.1pip、スリッページ常時0.1pip加算)。

10. MQL4フルEA:CostAware_PivotEA.mq4

以下は、コスト・アウェアの入場ゲートを備えたシンプルな順張りEAです。ロジックは「MA傾きでトレンド判定+直近高安ブレイク」で、入場前に「想定エッジ > 必要エッジ」を満たすかを確認します。初心者でも編集しやすいよう、関数は短く分割し、注釈を多めに入れています。


//+------------------------------------------------------------------+
//|                                                  CostAware_PivotEA.mq4
//|  概要: コスト・アウェアな入場判定(Spread/Commission/Slippage/ATR)を搭載
//|  使い方: EURUSD,M5推奨。ECN口座。ニュース前後は停止。
//+------------------------------------------------------------------+
#property strict

// ====== 入力パラメータ ======
input double   Lots                = 0.10;
input int      Magic               = 240904;
input int      SlippagePoints      = 10;        // 許容スリッページ[points]
input int      MaxSpreadPoints     = 20;        // 許容スプレッド[points]
input double   CommissionPerLot    = 6.0;       // 往復手数料(USD/lot想定)
input int      ATR_Period          = 14;
input double   EdgeATR_Ratio       = 0.35;      // 想定伸び代=Ratio*ATR
input double   PullbackATR_Ratio   = 0.15;      // 想定押し戻し=Ratio*ATR
input double   SafetyFactor        = 1.30;      // 必要エッジの安全係数
input int      FastMA              = 20;
input int      SlowMA              = 60;
input int      LookbackBreak      = 20;         // 高安ブレイク期間
input double   SL_ATR_Ratio        = 0.60;      // SL=Ratio*ATR
input double   TP_ATR_Ratio        = 1.20;      // TP=Ratio*ATR
input int      StartHour           = 8;         // 取引開始(サーバー時間)
input int      EndHour             = 23;        // 取引終了(サーバー時間)

// ====== グローバル ======
datetime LastTradeTime = 0;

// ====== ユーティリティ ======
double GetPointValueUSD() {
   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double tickSize  = MarketInfo(Symbol(), MODE_TICKSIZE);
   double point     = MarketInfo(Symbol(), MODE_POINT);
   // 1pointあたりのUSD価値を近似
   if(tickSize <= 0 || point <= 0) return 0.0;
   return tickValue * (point / tickSize);
}

double CurrentSpreadPoints() {
   double point = MarketInfo(Symbol(), MODE_POINT);
   if(point <= 0) return 0.0;
   return (Ask - Bid) / point;
}

bool TradingSessionOK() {
   int h = TimeHour(TimeCurrent());
   if (StartHour <= EndHour) return (h >= StartHour && h < EndHour);
   // 日跨ぎ設定の場合
   return (h >= StartHour || h < EndHour);
}

bool SpreadOK() {
   return CurrentSpreadPoints() <= MaxSpreadPoints;
}

double ATR(int period) {
   return iATR(Symbol(), PERIOD_CURRENT, period, 0);
}

double MA(int period) {
   return iMA(Symbol(), PERIOD_CURRENT, period, 0, MODE_EMA, PRICE_CLOSE, 0);
}

double MA_prev(int period) {
   return iMA(Symbol(), PERIOD_CURRENT, period, 0, MODE_EMA, PRICE_CLOSE, 1);
}

bool TrendUp() {
   return (MA(FastMA) > MA(SlowMA)) && (MA(FastMA) > MA_prev(FastMA));
}

bool TrendDown() {
   return (MA(FastMA) < MA(SlowMA)) && (MA(FastMA) < MA_prev(FastMA));
}

double HighestHigh(int bars) {
   double hh = iHigh(Symbol(), PERIOD_CURRENT, iHighest(Symbol(), PERIOD_CURRENT, MODE_HIGH, bars, 1));
   return hh;
}

double LowestLow(int bars) {
   double ll = iLow(Symbol(), PERIOD_CURRENT, iLowest(Symbol(), PERIOD_CURRENT, MODE_LOW, bars, 1));
   return ll;
}

// 想定エッジ[pips]を保守的に見積もる
double EstimateEdgePips() {
   double atr = ATR(ATR_Period);
   if(atr <= 0) return 0;
   double expected = EdgeATR_Ratio * atr;
   double pullback = PullbackATR_Ratio * atr;
   double edge = MathMax(expected - pullback, 0);
   // 小数pips(=points*10のブローカー前提)をpips換算
   double point = MarketInfo(Symbol(), MODE_POINT);
   double pip = (Digits == 3 || Digits == 5) ? point * 10.0 : point;
   return edge / pip;
}

// 必要エッジ[pips](コスト×安全係数)
double RequiredEdgePips() {
   double pointValUSD = GetPointValueUSD();
   double spreadPts = CurrentSpreadPoints();
   double slippagePtsTotal = SlippagePoints * 2.0; // 往復
   double costUSD = (spreadPts + slippagePtsTotal) * pointValUSD * Lots + CommissionPerLot * (Lots/1.0);
   // 1pipのUSD価値
   double point = MarketInfo(Symbol(), MODE_POINT);
   double pip = (Digits == 3 || Digits == 5) ? point * 10.0 : point;
   double pipUSD = (pip/point) * pointValUSD * Lots;
   if(pipUSD <= 0) return 9999;
   double reqPips = (costUSD / pipUSD) * SafetyFactor;
   return reqPips;
}

bool PositionExists() {
   for(int i=0;i<OrdersTotal();i++){
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         if(OrderSymbol()==Symbol() && OrderMagicNumber()==Magic) return true;
      }
   }
   return false;
}

void PlaceOrder(int direction) {
   // direction: 1=Buy, -1=Sell
   double atr = ATR(ATR_Period);
   double point = MarketInfo(Symbol(), MODE_POINT);
   double pip = (Digits == 3 || Digits == 5) ? point * 10.0 : point;
   double slDist = SL_ATR_Ratio * atr;
   double tpDist = TP_ATR_Ratio * atr;

   double sl = 0, tp = 0, price = 0;
   int slip = SlippagePoints;

   if(direction > 0) {
      price = Ask;
      sl = price - slDist;
      tp = price + tpDist;
      OrderSend(Symbol(), OP_BUY, Lots, price, slip, sl, tp, "CostAwareEA", Magic, 0, clrNONE);
   } else {
      price = Bid;
      sl = price + slDist;
      tp = price - tpDist;
      OrderSend(Symbol(), OP_SELL, Lots, price, slip, sl, tp, "CostAwareEA", Magic, 0, clrNONE);
   }
}

int OnInit() { return(INIT_SUCCEEDED); }
int OnDeinit() { return(0); }

void OnTick() {
   if(PositionExists()) return;
   if(!TradingSessionOK()) return;
   if(!SpreadOK()) return;

   // シグナル:トレンド & 高安ブレイク
   double hh = HighestHigh(LookbackBreak);
   double ll = LowestLow(LookbackBreak);

   // コスト・アウェア判定
   double edge = EstimateEdgePips();
   double req  = RequiredEdgePips();
   if(edge <= req) return; // エッジ不足は入場拒否

   if(TrendUp() && Ask > hh) {
      PlaceOrder(1);
   } else if(TrendDown() && Bid < ll) {
      PlaceOrder(-1);
   }
}
//+------------------------------------------------------------------+

コードのポイント

  • EstimateEdgePips():ATRから伸び代を保守的に推定。
  • RequiredEdgePips():スプレッド+想定スリッページ+手数料をUSD換算し、1pip価値で割って必要エッジに変換。
  • edge > req を満たすまで入場しないため、スプレッドが悪化する相場では自然と沈黙。

11. 推奨初期設定とチューニング

項目 初期値 解説
MaxSpreadPoints 20 EURUSDで0.2pip相当。ブローカー実勢に合わせる。
CommissionPerLot 6.0 往復6USD/lot想定。自口座の実額に置換。
SafetyFactor 1.30 楽観を排し、必要エッジを3割上乗せ。
SL/TP比 0.6/1.2×ATR RR=2:1を意識。ボラで自動スケール。
時間帯 8–23 サーバー時間。ロンドン~NYに重心。

12. 具体的バックテスト手順(MT4)

  1. シンボル:EURUSD、時間足:M5。
  2. モデル:すべてのティック(精度優先)。
  3. スプレッド:ブローカー実勢+0.1pipを手動設定。
  4. パラメータ:初期値のまま、1年~3年。
  5. レポート:総取引回数、平均損益/回、PF、ドローダウン、勝率。
  6. パラ最適化:MaxSpreadPointsSafetyFactor を中心に。

必ずテスト後に損益が「エッジ差」(edge−req)に相関することを確認してください。関係が薄い場合、エッジ推定式が甘い可能性があります。

13. ブローカー差と実運用の注意

同一EAでも、約定方式・LP・サーバー位置で結果は変わります。VPSを取引サーバーに地理的近接させ、Ping<=10msを目指しましょう。さらに、まとめスリッページが起きやすい高ボラ時は、ロットを分割して発注するのが安全です。

14. 改良アイデア

  • ニュースフィルタ:経済指標カレンダー連携で自動停止。
  • ダイナミック安全係数:時間帯やボラに応じて SafetyFactor を可変。
  • 学習的エッジ推定:直近のMFE/MAE分布からベイズ更新。
  • コスト予測:時刻×シンボルのスプレッド分位点モデル。

15. 口座開設の実務ポイント(要点のみ)

  • ECN口座の往復手数料平均スプレッドを両方開示している会社を選ぶ。
  • 約定品質(リクオート、約定拒否率)、VPS連携、通貨ペア数、出金手数料も確認。
  • デモとリアルでスプレッド分布に乖離がないか、1週間のログで比較。

16. よくある失敗と対策

  • 勝率だけで判断:RRとコストで期待値がマイナスになる典型。
  • スプレッド固定前提:実口座では時間帯で2~5倍に拡大。
  • 過剰最適化:MaxSpreadや時間帯を一点当てすると脆い。分位点で閾値化。

17. まとめ

コスト・アウェアEAは、「勝てるときだけ戦う」EAです。スプレッドや手数料を入場の条件に組み込むだけで、同じシグナルでもR曲線の形は大きく変わります。まずは本稿のEAをM5で動かし、ログを取り、エッジとコストの差が利益の源泉であることを体で理解してください。そこから先は、あなたの検証と工夫の領域です。

付録A:パラメータ早見表

相場の荒さに合わせて、下表を叩き台にしてください。

市場状態 MaxSpread SafetyFactor SL/TP
ボラ低 15 1.20 0.5/1.0
標準 20 1.30 0.6/1.2
ボラ高 25 1.45 0.8/1.6

付録B:用語ミニ辞典

スプレッド
BidとAskの価格差。短期売買では最大のコスト。
スリッページ
発注価格と約定価格のズレ。高ボラや薄商いで増加。
ATR
Average True Range。直近の平均的な値動き幅。
期待値
平均的な損益の見込み。正の期待値がなければ取引しない。

コメント

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