こんにちは、潤奈です( ゚Д゚)!
前回の記事では、急激な値動きから大切な資金を守るための「ストップロス(SL)」と「テイクプロフィット(TP)」の自動設定方法を解説しました。
安全にトレードをする準備が整ったら、次に実装したいのが「自動でのロット数(取引数量)計算ロジック」です!
EA開発を始めた最初のうちは、まずは分かりやすく「ロット数を0.1ロットで固定」したコードから作ることが多いと思います。しかし、固定ロットのままだと口座資金が増減した際のリスク管理が難しくなりますし、複利(資金の増加に合わせてロットを増やす)の恩恵も得られません。
今回は、設定した「許容リスク%」と「損切り幅」から、自動的に最適な取引ロット数を算出する安全で実践的な資金管理ロジックの書き方を分かりやすく解説します!
1. なぜロット数の自動計算(資金管理)が必要なのか?
自動売買において、ロット数を固定(例: 常に0.1ロット)して運用することには、以下のようなリスクがあります。
- 損切り幅によって損失額がバラバラになる
損切り幅が10pipsの時と50pipsの時で同じ0.1ロットでエントリーすると、負けた時の損失額が5倍も変わってしまいます。 - 資金効率が悪くなる
口座資金が10万円の時と100万円の時で同じ0.1ロットのままだと、資金が増えても利益の伸びが鈍くなってしまいます。
これを解決するのが、「1回の負けで失う金額を、口座残高の○%(例: 2%)に抑える」という資金管理方法(2%ルールなど)です。
これをEAに組み込むことで、損切り幅が広くても狭くても、また口座資金がいくらであっても、負けたときのダメージを常に一定に保つことができます。
2. ロット数を求めるための計算式
MQL5でロット数を自動計算するには、以下の数式を使用します。
取引ロット数 = 許容損失額 ÷ ( 損切り幅[ポイント換算] × 1ポイントあたりの価格価値 )
MQL5では、この計算に必要な情報を以下の関数を使って動的に取得します。
① 許容損失額の計算
まずは、現在の口座残高から1トレードで失ってよい最大金額を計算します。
|
1 2 |
double balance = AccountInfoDouble(ACCOUNT_BALANCE); // 口座残高の取得 double lossAmount = balance * (RiskPercent / 100.0); // 許容損失額 (例: 残高の2%) |
② 損切り幅をポイントに変換
設定したpips単位の損切り幅を、プログラムが処理できる「ポイント数」に変換します。
この時、「pipsをポイントに換算する倍率は通貨ペアやブローカーによって変わるのか?」という疑問が湧くかもしれません。
結論として、現在主流の「3桁(クロス円)・5桁(ドルストレート)表示」のブローカーでは 1pips=10ポイント ですが、一部の特殊な「2桁・4桁表示」のブローカーでは 1pips=1ポイント になります。また、ゴールド(GOLD)や仮想通貨(BTC)などの銘柄では単位が大きく異なります。
そのため、以下のように小数点桁数(_Digits)に応じて動的に倍率(10倍または1倍)を自動判定・調整する処理を入れておくのが、バグを防ぐための最も安全な書き方です。
|
1 2 3 |
// 3桁・5桁表示ブローカーなら10倍、それ以外なら1倍に調整 double pipAdjust = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0; double slPoints = StopLossPips * pipAdjust; // pipsを安全にポイント数へ換算 |
[!WARNING]
ゴールドやCFDなどの特殊銘柄での注意点
このpipAdjustの自動調整(3桁・5桁を10倍にする処理)は、一般的なFXの通貨ペア(クロス円やドルストレート)を想定した簡易的なものです。
ゴールド(XAUUSD:通常は小数点以下2桁または3桁表示)や、日経平均などの株価指数、ビットコイン(BTCUSD)といったCFD銘柄では、ブローカーによって小数点桁数が異なるうえに、pipsの定義自体が通貨ペアとは大きく異なります。そのため、FX以外の特殊な銘柄でEAを動かす場合は、この簡易判定では計算が狂ってしまうため、各銘柄に合わせた個別の換算処理が必要になる点に注意してください。
③ 1ポイントあたりの最小価格価値の計算
ここが少し複雑ですが、通貨ペアやブローカーの仕様が異なっても正しく計算するために、以下の情報から「1ポイント動いた時の口座通貨ベースの価値」を算出します。
|
1 2 3 |
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); // 最小値動きの価値 double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); // 最小値動きの幅 double pointValue = tickValue * (_Point / tickSize); // 1ポイントあたりの価値 |
これで、必要なパーツが揃いました!
3. ブローカーのルール(制限)に合わせる丸め処理
計算式で導き出されたロット数(例: 0.12345ロット)をそのまま注文に使うことはできません。ブローカーごとに「最小ロット」「最大ロット」「ロットの刻み幅(ステップ)」が厳格に決まっているためです。
そのため、必ず以下の処理を行って「注文可能なロット数」に整形(丸め処理)する必要があります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// ブローカーの契約ルールを取得 double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); // ロットの最小刻み幅(例: 0.01) double minVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); // 最小ロット数(例: 0.01) double maxVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); // 最大ロット数(例: 100.0) // 計算されたロット数を刻み幅に合わせて端数を切り捨てる double rawLot = lossAmount / (slPoints * pointValue); // ★浮動小数点の演算誤差(極小のズレ)によるロット数消失(0ロットになるバグ)を防ぐため、 // 非常に小さな値(0.00001)を足してから MathFloor で切り捨てます double calculatedLot = MathFloor((rawLot + 0.00001) / step) * step; // 最小・最大ロットの範囲内に収める(安全対策) if(calculatedLot < minVol) calculatedLot = minVol; if(calculatedLot > maxVol) calculatedLot = maxVol; |
PCの数値計算ではごくわずかな演算誤差が発生するため、本来0.10ロットになる計算が 0.099999... のように極小に下回ることがあります。これをそのまま MathFloor で切り捨てると 0.0 ロット(最小ロット未満)になってしまうため、微小値(0.00001)を足してから処理をするのが、予期せぬ計算バグを防ぐための安全な書き方です。
4. コピペで動く!ロット自動計算関数付きサンプルEA
それでは、実際の挙動を確認するためのサンプルコードを紹介します。
このコードは、「EAを起動した瞬間に、現在の口座残高から『許容リスク2.0%・損切り幅15pips』に基づいて最適なロット数を自動計算し、同時にSL/TPを設定して買いエントリーする」EAテンプレートです。
安全性を考慮し、ロット計算処理を再利用しやすいカスタム関数 CalculateLotSize() として綺麗に切り分けて実装しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
//+------------------------------------------------------------------+ //| MoneyManagementEA.mq5 | //| 潤奈FX | //+------------------------------------------------------------------+ #property copyright "潤奈FX" #property link "https://zyunafx.com" #property version "1.00" // --- 注文ライブラリの読み込み --- #include <Trade\Trade.mqh> CTrade trade; // --- 入力パラメータ --- input group "--- 取引設定 ---" input int MagicNumber = 666666; // マジックナンバー input int Slippage = 100; // 許容スリッページ (ポイント) input group "--- 資金管理設定 ---" input double RiskPercent = 2.0; // 1トレードの許容リスク率 (残高の%) input int StopLossPips = 15; // 損切り幅 (pips単位) input int TakeProfitPips = 30; // 利食い幅 (pips単位) // --- グローバル変数 --- datetime lastBarTime; // 新バー判定用 bool isOrdered = false; //+------------------------------------------------------------------+ //| 初期化関数 | //+------------------------------------------------------------------+ int OnInit() { // CTradeの初期設定 trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(Slippage); Print("資金管理EAが起動しました。"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 後片付け関数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Print("EAが停止しました。"); } //+------------------------------------------------------------------+ //| チック毎の処理関数 | //+------------------------------------------------------------------+ void OnTick() { // 注文がすでに完了している場合は何もしない if(isOrdered) return; // 新しい足が確定したかチェック(新バー開始時のみ実行してチャタリング防止) datetime currentBarTime = iTime(_Symbol, _Period, 0); if(currentBarTime == lastBarTime) return; // 処理を実行したことを記録するため、そのバーの時間を記憶(同じバーで何度も送信処理が走るのを防ぐ) lastBarTime = currentBarTime; // ロット数を動的に自動計算 double tradeLots = CalculateLotSize(StopLossPips, RiskPercent); PrintFormat("算出されたロット数: %.2f ロット", tradeLots); Print("成行買い注文を送信します(資金管理機能付き)..."); // 現在の買値を取得 double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // 3桁・5桁表示ブローカーなら10倍、それ以外なら1倍に調整してポイント換算(FX通貨ペア向けの簡易調整) double pipAdjust = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0; // SLおよびTP価格を計算し、価格をブローカーの桁数(_Digits)に正規化 double slPrice = NormalizeDouble(ask - (StopLossPips * pipAdjust * _Point), _Digits); double tpPrice = NormalizeDouble(ask + (TakeProfitPips * pipAdjust * _Point), _Digits); // 新規注文の発注 if(trade.Buy(tradeLots, _Symbol, ask, slPrice, tpPrice)) { // 注文リクエストの結果を確認 ulong retcode = trade.ResultRetcode(); if(retcode == TRADE_RETCODE_DONE || retcode == TRADE_RETCODE_PLACED) { Print("注文とSL/TPの設定が成功しました!"); isOrdered = true; // 注文成功時のみフラグを立てて、以降の処理を完全に停止 } else { Print("発注送信はされましたが、ブローカーに拒否されました。リターンコード: ", retcode); // 拒否された場合(ストップレベル違反など)は isOrdered = true をセットしないため、 // 設定を修正すれば次の足(バー)で再送が試みられます } } else { Print("注文の送信自体に失敗しました。エラーコード: ", trade.ResultRetcode()); // 送信自体に失敗した場合(回線切断など)も isOrdered = true はセットせず、次の足で再送を試みます } } //+------------------------------------------------------------------+ //| 口座残高と損切り幅から安全なロット数を自動計算する関数 | //+------------------------------------------------------------------+ double CalculateLotSize(double slPips, double riskPercent) { // ブローカーの最小・最大ロット、ステップ幅の制限を取得 double minVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); // 現在の口座残高と許容損失額を計算 double balance = AccountInfoDouble(ACCOUNT_BALANCE); double lossAmount = balance * (riskPercent / 100.0); // 3桁・5桁表示ブローカーなら10倍、それ以外なら1倍に調整してポイント換算 double pipAdjust = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0; double slPoints = slPips * pipAdjust; if(slPoints <= 0) return(minVol); // 万が一のゼロ除算防止 // 1ポイントあたりの価格価値を取得 double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(tickSize <= 0) return(minVol); // ゼロ除算防止 double pointValue = tickValue * (_Point / tickSize); // ロット数の計算 double rawLot = lossAmount / (slPoints * pointValue); // 契約ステップ(刻み幅)に合わせて四捨五入(切り捨て) // 浮動小数点誤差によるロット消失バグを防ぐため、微小値(0.00001)を加えて丸めます double calculatedLot = MathFloor((rawLot + 0.00001) / step) * step; // 最小・最大ロットの範囲に収める if(calculatedLot < minVol) calculatedLot = minVol; if(calculatedLot > maxVol) calculatedLot = maxVol; return(calculatedLot); } //+------------------------------------------------------------------+ |
このコードを MetaEditor でコンパイルしてデモチャートにセットしてみてください。
口座残高が多い状態と少ない状態でそれぞれセットすると、自動的に算出されるロット数(Calculated lot size)が資金量に合わせて変動する様子がログで確認できます!
5. まとめ&次のステップ
今回は、EAの安全な長期運用において極めて重要な「ロット数自動計算による資金管理ロジック」を解説しました。
- 固定ロット運用は、口座資金の増減や損切り幅によって想定外の損失を招く。
- 残高に対する%(リスク比率)と損切り幅から、取引ロット数を動的に計算する。
- 計算結果はブローカーの制限(最小・最大ロット、ステップ幅)に合わせて必ず丸め処理を行う。
- ロット計算を関数化しておくことで、どのようなEAにも簡単に資金管理機能を組み込める。
これまでに学んだ「注文」「決済」「インジケーター」「SL/TP設定」、そして今回の「資金管理」があれば、実用レベルのEA作成に必要なコア部分はすべてマスターしたことになります!
次回は、EAの安定性をさらに高めるための「複数通貨ペアを1つのチャートから監視・トレードするマルチ通貨監視EAの基本」、または実践で役立つテクニックについて解説します。
今回のロット計算式や、口座の通貨設定(日本円口座・ドル口座など)による挙動の違いなどで疑問があれば、YouTubeのコメント欄やXでお気軽にご連絡くださいね!



コメント