含み益が伸びているとき、「もう少し伸ばせたのに」と後悔した経験はないでしょうか。
かといって、伸びるたびに無条件でロットを増やす「ピラミッディング」
は、一歩間違えると特攻ラッパのような危険な手法にもなります。
この記事では、価格が一定幅動くたびに機械的にロットを増減させる、
「デルタ段階調整」型のピラミッディングEAをMQL5で作ります。
単純な「買い増し」の何が危険なのか
含み益が出るたびに無条件でロットを増やす設計は、一見合理的に見えて、
実はマーチンゲール(ナンピン)と同じ弱点を抱えています。
なぜ普通のピラミッディングは危険なのか
注意: 買い増しの上限がなく、反落時に縮める仕組みもない設計は、
一度のトレンド転換で含み益を吐き出すだけでなく、
含み損に転落するリスクさえあります。
このサイトで既に警鐘を鳴らした「ナンピンをやめさせるEA」の記事と、
根っこの問題は同じです。
この記事の設計方針: 状態ではなく「価格帯の段階」を管理する
この記事のEAは、買い増しの回数に上限(`InpMaxLevels`)を設け、
反落時は自動的にロットを縮める処理も持たせます。
「価格がどの段階(レベル)にいるか」を管理する設計にすることで、
増やす条件と減らす条件を同じロジックで対称に扱えます。
オプションのペイオフに着想した「デルタ段階調整」という考え方
なぜ段階的に増減させるのか、着想元を先に説明します。
コールオプションのペイオフはなぜ右肩上がりに凸型なのか
コールオプション(買う権利)は、
原資産価格が上がるほど利益が加速度的に増える凸型のペイオフを持ちます。
補足: この凸型を再現する手法の1つが「デルタヘッジ」です。
価格が上がるほど保有量(デルタ)を増やし、下がるほど減らすことで、
オプションを使わずに似た損益カーブを作れます。
MQL5の公式記事群でも扱われているテーマです。
この記事ではオプション理論を簡略化して実装する
本格的なデルタヘッジは、ボラティリティ等から毎回再計算する必要があります。
この記事はその考え方だけを借り、
「価格が一定幅(`InpStepPoints`)動くたびに固定ロットずつ増減させる」
という単純化したルールに落とし込みます。
CTrade・PositionGetDouble等の関数の引数
実装に使う関数の引数を確認します。
CTrade::Buy()の引数
bool Buy( double volume, string symbol=NULL, double price=0, double sl=0, double tp=0, string comment=NULL );
`volume`だけが必須引数で、
`price`を省略(0のまま)すると成行で発注されます。
同じ銘柄に対して`Buy()`を複数回呼ぶと、
ネッティング口座では既存ポジションの数量に加算されます。
PositionGetDouble/PositionGetIntegerの引数
double PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property_id); long PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property_id);
事前に`PositionSelect(symbol)`で、
ポジションを選択しておく必要があります。
`POSITION_VOLUME`で保有ロット、
`POSITION_PRICE_OPEN`で建値、
`POSITION_MAGIC`でマジックナンバーを取得できます。
CTrade::PositionClosePartial()の引数
bool PositionClosePartial( const string symbol, const double volume, ulong deviation=ULONG_MAX );
要点: `volume`に実際の保有ロットより大きい値を渡すと、
自動的に「全決済」として扱われる仕様です。
この記事のコードは、この仕様を利用して
「全決済」と「一部決済」を同じ関数呼び出しで済ませます。
実際に動くコード全体(デルタ段階調整ピラミッディングEA)
価格が`InpStepPoints`分上がるたびに`InpLots`だけ買い増す、
実際にMetaEditorでコンパイルできるEAです。
先に注意: このコードはMQL5既定のネッティング口座を前提にしています。
ヘッジ口座では意図通りに動かないため、
コピー前に必ず後述のネッティング/ヘッジの違いも読んでください。
//+------------------------------------------------------------------+
//| DeltaStepPyramidingEA.mq5 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.00"
#include <Trade\Trade.mqh>
CTrade trade;
input double InpLots = 0.01; //1段階あたりの増減ロット数
input int InpStepPoints = 300; //段階を判定する価格の刻み幅(ポイント)
input int InpMaxLevels = 5; //最大の買い増し段階数
input ulong InpMagic = 20260928;
double g_basePrice = 0;
int g_level = 0;
datetime g_lastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(InpMagic);
trade.SetTypeFillingBySymbol(_Symbol);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
datetime barTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(barTime == g_lastBarTime)
return; //1本の確定足につき1回だけ判定する
g_lastBarTime = barTime;
if(!PositionSelect(_Symbol))
{
//ポジションが無ければ最初の1段階目を建てる
if(trade.Buy(InpLots, _Symbol))
{
if(PositionSelect(_Symbol))
g_basePrice = PositionGetDouble(POSITION_PRICE_OPEN);
g_level = 1;
}
return;
}
if(PositionGetInteger(POSITION_MAGIC) != InpMagic)
return; //自分が建てたポジションでなければ何もしない
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double stepPrice = InpStepPoints * _Point;
int reachedLevel = (int)MathFloor((currentPrice - g_basePrice) / stepPrice);
if(currentPrice <= g_basePrice - stepPrice)
{
//建値を1段階分以上下回った 全決済して仕切り直す(ハードストップ)
double volume = PositionGetDouble(POSITION_VOLUME);
trade.PositionClosePartial(_Symbol, volume);
g_level = 0;
g_basePrice = 0;
}
else if(reachedLevel > g_level && g_level < InpMaxLevels)
{
//新しい価格帯に達した 段階的に買い増す
if(trade.Buy(InpLots, _Symbol))
g_level = reachedLevel;
}
else if(reachedLevel < g_level - 1)
{
//1段階分以上、価格が反落した 直近の買い増し分だけ一部利確する
double volume = PositionGetDouble(POSITION_VOLUME);
double closeVolume = MathMin(InpLots, volume);
if(trade.PositionClosePartial(_Symbol, closeVolume))
g_level = reachedLevel + 1;
}
}
//+------------------------------------------------------------------+
MetaEditorで実際に動かす手順
- MetaTrader 5の「ツール」→「MetaQuotes Language Editor」でMetaEditorを開く
- 「ファイル」→「新規作成」→「エキスパートアドバイザー(テンプレートを使用)」で「DeltaStepPyramidingEA」を作成する
- ウィザードで生成された中身をすべて削除し、上のコードを丸ごと貼り付ける
- F7キーでコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- 必ずデモ口座のチャートに適用し、`InpStepPoints`分価格が動くたびにポジションの保有ロットが増減することを確認する
よくあるエラーと注意点
- 買い増しが発生しない: `InpStepPoints`が大きすぎないか確認してください
- すぐ全決済される: `g_basePrice`のズレが原因のことが多いです。スプレッドの影響を確認してください
- 証拠金不足になる: `InpMaxLevels`×`InpLots`の最大保有ロットを事前に試算してください
- 1段階の損失額が増える: ロットが増えるほど1段階分の損益額も比例して大きくなる点に注意してください
ネッティング口座とヘッジ口座の違い(拡張のヒント)
このコードは、そのままではヘッジ口座で意図通りに動きません。
なぜこの記事はネッティング口座を前提にしているか
補足: ネッティング口座では、`Buy()`の呼び出しが、
自動的に1つのポジションへ合算されるため、
`PositionSelect(_Symbol)`だけで、
保有状況を把握でき、シンプルに書けます。
ヘッジ口座で拡張する場合のヒント
ヘッジ口座では、買い増しのたびに別々のチケットが並存します。
`PositionsTotal()`と`PositionGetTicket(i)`で、
1件ずつ取り出し、マジックナンバーと建値で選別してください。
よくある質問
デルタ段階調整ピラミッディングEAの実装でよく検索されている疑問を、
3つにまとめて回答します。
この設計はマーチンゲールとどう違いますか?
マーチンゲールは含み損が出るほどロットを増やしますが、
この記事は含み益が出るほど増やし、反落したら縮める点が逆です。
ただし上限(`InpMaxLevels`)がなければ、同じ危険があるため注意してください。
利益確定はどこで行っていますか?
このコードに明示的な利益確定は実装していません。
`InpMaxLevels`到達後の手動決済か、
トレーリングストップの追加実装を想定しています。
ロット数以外の判定条件にできますか?
`reachedLevel`の計算式を、
ATR(相場の変動幅)に応じた可変の刻み幅に置き換えることもできます。
【MQL5】含み益が乗るたびに少しずつ買い増す「デルタ段階調整」ピラミッディングEAの作り方(オプションのペイオフに着想):まとめ
この記事では、価格が一定幅動くたびにロットを段階的に増減させる、
「デルタ段階調整」型のピラミッディングEAを作りました。
無条件に買い増すのではなく、
段階(レベル)を対称に管理することが、
マーチンゲールと同じ轍を踏まないためのポイントです。
このコードはネッティング口座を前提にしています。
ヘッジ口座で使う場合は、
`PositionsTotal()`と`PositionGetTicket()`で、
書き換えてください。
コメント