「無料で使えるEAのソースコードを全文公開します」。ただし、正直に言っておきたいことがあります。このEAをそのまま実運用に投入すれば、おそらく損をします。それでも公開する理由は、EAの中身がどう動いているかを透明に見せたほうが、ブラックボックスの有料EAよりも役に立つと考えているからです。
本記事では、ドンチャンブレイクアウトという古典的なトレンドフォロー手法をMQL4で実装したミニEAのソースコードを全文掲載します。さらに、Pythonで模擬データを使ったバックテストを行い、期待値・プロフィットファクター・t値などの統計指標を正直に示します。「勝つEA」を売り込む記事ではありません。「どう検証するか」を示す記事です。
ドンチャンブレイクアウトとは
ドンチャンブレイクアウトは、過去N日間の最高値を上に抜けたら買い、最安値を下に抜けたら売り、というシンプルなルールです。1960年代にリチャード・ドンチャンが体系化した、世界最古クラスのシステムトレード手法です。
トレンドが続いているときは利益を乗せやすく、レンジ相場ではダマシに何度も刺されるという特徴があります。ロジックが単純な分、何が起きているかを自分の目で確認しやすく、EAの学習教材として最適です。
EAの仕様
- エントリー: 1本前の足の終値が、その前20本の最高値(最安値)を突破したとき
- 損切り: ATR(14)の2倍の距離にストップを設置
- 決済: ストップアウトのみ(利確はトレーリングストップで追跡)
- トレーリング: 30pips以上の利益で10pips幅のストップに追跡開始
- ポジション: 1ポジション方式(保有中は新規エントリーしない)
- タイムフレーム: 日足推奨(任意の時間足で動作)
MQL4ソースコード(全文)
以下がコンパイル可能な完全なソースコードです。MT4のMetaEditorに貼り付けてコンパイルすれば、すぐにテストできます。
//+——————————————————————+
//| DonchianBreakout.mq4 |
//| rehav trading system |
//| 無料公開用サンプルEA – ドンチャンブレイクアウト |
//+——————————————————————+
#property copyright "rehav trading system"
#property link "https://www.rehavtrading.com"
#property version "1.01"
#property strict
//— 入力パラメータ
input int ChannelPeriod = 20; // チャネル期間(高値・安値の参照本数)
input int ATRPeriod = 14; // ATR期間
input double ATRMultiplier = 2.0; // SLのATR倍率
input double Lots = 0.01; // ロットサイズ
input int MagicNumber = 20260918; // マジックナンバー
input int Slippage = 3; // 許容スリッページ(ポイント)
input bool UseTrailingStop = true; // トレーリングストップ有無
input int TrailStartPips = 30; // トレーリング開始利益(pips)
input int TrailStepPips = 10; // トレーリング幅(pips)
//— グローバル変数
double gPipPoint;
//+——————————————————————+
//| 初期化関数 |
//+——————————————————————+
int OnInit()
{
if(Digits == 3 || Digits == 5)
gPipPoint = Point * 10.0;
else
gPipPoint = Point;
Print("DonchianBreakout EA 初期化完了。チャネル期間=", ChannelPeriod,
" ATR倍率=", ATRMultiplier, " ロット=", Lots);
return(INIT_SUCCEEDED);
}
//+——————————————————————+
//| 終了関数 |
//+——————————————————————+
void OnDeinit(const int reason)
{
Print("DonchianBreakout EA 終了。理由=", reason);
}
//+——————————————————————+
//| OnTick – 毎ティック実行 |
//+——————————————————————+
void OnTick()
{
// — 既存ポジションの管理 —
ManagePositions();
// — 新規エントリー判定(新しい足の始値のみ) —
if(!IsNewBar())
return;
// シグナル判定
int signal = CheckSignal();
if(signal == 0)
return;
// 既存ポジションがあればエントリーしない(1ポジション方式)
if(HasOpenPosition())
return;
// — エントリー実行 —
double atr = GetATR();
double slDistance = atr * ATRMultiplier;
if(signal == 1) // 買いシグナル
{
double price = Ask;
double sl = price – slDistance;
int ticket = OrderSend(Symbol(), OP_BUY, Lots, price, Slippage,
sl, 0, "DonchianBuy", MagicNumber, 0, clrBlue);
if(ticket < 0)
Print("買い注文失敗: ", GetLastError());
else
Print("買いエントリー: ticket=", ticket, " price=", price, " SL=", sl);
}
else if(signal == -1) // 売りシグナル
{
double price = Bid;
double sl = price + slDistance;
int ticket = OrderSend(Symbol(), OP_SELL, Lots, price, Slippage,
sl, 0, "DonchianSell", MagicNumber, 0, clrRed);
if(ticket < 0)
Print("売り注文失敗: ", GetLastError());
else
Print("売りエントリー: ticket=", ticket, " price=", price, " SL=", sl);
}
}
//+——————————————————————+
//| 新しい足かどうかの判定 |
//+——————————————————————+
bool IsNewBar()
{
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(Symbol(), Period(), 0);
if(currentBarTime != lastBarTime)
{
lastBarTime = currentBarTime;
return true;
}
return false;
}
//+——————————————————————+
//| ドンチャンブレイクアウトのシグナル判定 |
//| 1本前の足の終値が、その前N本の高値(安値)を突破したかを判定 |
//| 戻り値: 1=買い, -1=売り, 0=シグナルなし |
//+——————————————————————+
int CheckSignal()
{
// bar1の終値
double prevClose = iClose(Symbol(), Period(), 1);
// bar2〜bar(N+1)の最高値・最安値(直前のN本、bar1は除外)
int highIdx = iHighest(Symbol(), Period(), MODE_HIGH, ChannelPeriod, 2);
int lowIdx = iLowest(Symbol(), Period(), MODE_LOW, ChannelPeriod, 2);
double highest = iHigh(Symbol(), Period(), highIdx);
double lowest = iLow(Symbol(), Period(), lowIdx);
if(prevClose > highest)
return 1;
else if(prevClose < lowest)
return -1;
return 0;
}
//+——————————————————————+
//| ATRの取得 |
//+——————————————————————+
double GetATR()
{
return iATR(Symbol(), Period(), ATRPeriod, 1);
}
//+——————————————————————+
//| オープンポジションの有無 |
//+——————————————————————+
bool HasOpenPosition()
{
for(int i = OrdersTotal() – 1; i >= 0; i–)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
return true;
}
}
return false;
}
//+——————————————————————+
//| ポジション管理(トレーリングストップ) |
//+——————————————————————+
void ManagePositions()
{
if(!UseTrailingStop)
return;
for(int i = OrdersTotal() – 1; i >= 0; i–)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
if(OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber)
continue;
double trailStart = TrailStartPips * gPipPoint;
double trailStep = TrailStepPips * gPipPoint;
if(OrderType() == OP_BUY)
{
if(Bid – OrderOpenPrice() >= trailStart)
{
double newSL = Bid – trailStep;
if(newSL > OrderStopLoss() || OrderStopLoss() == 0)
{
if(!OrderModify(OrderTicket(), OrderOpenPrice(), newSL, 0, 0, clrBlue))
Print("トレーリング変更失敗(Buy): ", GetLastError());
}
}
}
else if(OrderType() == OP_SELL)
{
if(OrderOpenPrice() – Ask >= trailStart)
{
double newSL = Ask + trailStep;
if(newSL < OrderStopLoss() || OrderStopLoss() == 0)
{
if(!OrderModify(OrderTicket(), OrderOpenPrice(), newSL, 0, 0, clrRed))
Print("トレーリング変更失敗(Sell): ", GetLastError());
}
}
}
}
}
//+——————————————————————+
バックテスト結果(2つの市場環境で比較)
実ティックデータが入手できないため、Pythonで幾何ブラウン運動(GBM)による模擬的な日足データを2,520本(約10年分)生成し、EAと同じロジックでトレードを再現しました。これは実口座での利益を保証するものではありません。ロジックの妥当性を統計的に確認するのが目的です。
2つのシナリオを比較しました:
- パターンA(ランダムウォーク): トレンドのない市場。価格は純粋なランダムウォーク。
- パターンB(レジーム混合): 250日ごとに「上昇トレンド」「レンジ」「下降トレンド」が交代する市場。現実のFX相場に近い構造。
検証結果サマリー
| 指標 |
パターンA(トレンドなし) |
パターンB(レジーム混合) |
| 総トレード数 |
329 |
355 |
| 勝率 |
54.4% |
65.9% |
| プロフィットファクター |
0.84 |
1.46 |
| 期待値(1トレード) |
-75円 (-7.5pips) |
154円 (15.4pips) |
| t値 |
-1.5 |
3.22 |
| 最大ドローダウン |
36934円 |
12118円 |
| 平均保有日数 |
2.2日 |
2.2日 |
| 総損益 |
-24517円 |
54536円 |
結果から読み取れること
パターンA(トレンドなし): 損失
プロフィットファクター0.84、期待値は1トレードあたり-75円の損失。t値は-1.5で、統計的に有意な損失システムであることを示しています。トレンドのない市場でトレンドフォロー手法を使うと、構造的に損をします。これはバグではなく、手法と市場環境のミスマッチです。
パターンB(レジーム混合): 利益
プロフィットファクター1.46、期待値は1トレードあたり154円の利益。t値は3.22で、2.0を超えているため統計的に有意な利益システムと言えます。トレンド期にブレイクアウトで乗せた利益が、レンジ期の損失を上回った結果です。
つまり、ドンチャンブレイクアウトは「トレンドがあるときにこそ機能する」ことが数字で裏付けられました。トレンドの有無を判定するフィルタを追加すれば、パターンAのような環境での損失を減らせるはずです。
この検証の限界(正直な注意点)
- 模擬データを使用: 実際のFX価格データではありません。スプレッド・スワップ・スリッページを含んでいません。これらを含めると結果は悪化します。
- スプレッド未考慮: 実運用ではエントリー時点ですでにスプレッド分のマイナスからスタートします。特にレンジ期の頻繁なエントリーでは無視できないコストです。
- 最適化未実施: チャネル期間20・ATR倍率2.0は一般的な値ですが、最適化は行っていません。パラメータの過剰最適化はカーブフィッティングの罠に繋がります。
- 1通貨ペアのみ: ポートフォリオ効果(複数通貨での分散)は考慮していません。
これらの限界を理解した上で「それでもこのEAをベースに改良したい」と考えるなら、それがまさにEA開発と監査の価値がある部分です。
ここからどう改良するか
このEAをベースに、次のような改良が考えられます:
- トレンドフィルタの追加: 200日移動平均線の向きでトレンドの有無を判定し、トレンド時のみエントリー
- スプレッドガード: スプレッドが一定以上のときはエントリーをスキップ(実運用必須)
- 時間フィルタ: 流動性が低い時間帯のエントリーを回避
- 複数通貨のポートフォリオ化: 1通貨のレンジ期間を他通貨のトレンドで補う
これらの改良を設計して実装し、統計的に有意かどうかを検証する。それがEA監査サービスとEA作成代行でやっていることです。
カスタムEAの開発を依頼しませんか?
この記事のEAは学習用サンプルですが、あなたのトレードロジックをMQL4/MQL5で実装できます。検証しやすいログ仕様を標準装備し、納品後の期待値照合もそのまま行えます。
✅ あなたの裁量ルールのEA化
✅ 監査済み戦略の実装
✅ スプレッドガード・ログ出力など実運用仕様を標準装備
MT4インジケーターやEAの作成を依頼する(ココナラ) →(納期3営業日以内)
まずは1,000円のEAクイック診断で、お手持ちのEAの数値診断も可能です。
まとめ: 透明性が信頼に繋がる
無料EAを公開して「そのままでは損をする」と正直に書くのは、販売側としては珍しいかもしれません。しかし、EAの価値はロジックの魔法ではなく、検証と改良のプロセスにあります。
ソースコードを公開し、限界も明記する。その上で「ここをどう直せば良くなるか」を示す。これが、私がEA開発と監査で大切にしている考え方です。
もしあなたが「動くけど中身が分からないEA」を持っているなら、ぜひ10年データで統計監査にチャレンジしてみてください。数字で見える化すれば、次に何をすべきかが必ず見えます。
※ 本記事のバックテストは模擬データによるシミュレーションであり、実取引の結果ではありません。過去の実績が将来の成果を保証するものではありません。