こんにちは、潤奈です( ゚Д゚)!
前回の記事では、これまでに学んだ「注文」「決済」「インジケーター」の3つの要素を組み合わせて、実際に動く「ゴールデンクロスEA」を作成しました。
EAの基本サイクルができるようになったら、次に絶対に実装すべきなのが「ストップロス(損切り:SL)」と「テイクプロフィット(利食い:TP)」の設定です!
自動売買において、損切りを設定しないことは「ブレーキの壊れた車」で高速道路を走るようなものです。急激な相場の逆行から大切な資金を守るためにも、SL/TPの設定方法は必ずマスターしておきましょう。
今回も標準ライブラリの CTrade を使って、新規注文と同時にSL/TPを設定する正しい書き方と、注文が拒否されないための「価格の正規化」について分かりやすく解説します!
1. MQL5のSL/TP設定はなぜ難しい?(MQL4との違い)
まずはMQL4とMQL5の違いを理解しておきましょう。
MQL4では、注文を送信する OrderSend 関数の引数に、直接SLとTPの「価格」を指定することができました。
|
1 2 |
// MQL4:注文時に直接SL/TP価格を指定(シンプル!) OrderSend(Symbol(), OP_BUY, 0.1, Ask, 10, Ask - 0.15, Ask + 0.30, "MyEA", Magic); |
MQL5でも、標準ライブラリの CTrade を使うことで同様に注文時にSL/TPを指定できますが、以下の「MQL5ならではの注意点」があります。
trade.Buyなどの注文関数に渡すSL/TPは、pips(ピップス)やポイント数ではなく、「具体的なレート価格(例: 150.50)」で渡さなければならない。- 計算した価格を、ブローカーの価格桁数に合わせて「正規化(丸め処理)」しないと、注文が拒否されてエラー(
INVALID_STOPS)になる。
特に2つ目の「価格の正規化」は、MQL5で最も注文失敗が発生しやすいバグの原因ですので、正しい書き方をしっかりと身につけましょう。
2. 新規注文時にSL/TPを設定する方法
CTrade ライブラリの Buy や Sell 関数には、引数の渡し方がいくつか用意されています。
これまで使っていた trade.Buy(0.1) などのシンプルな書き方は、SL/TPなどを省略した「省略形」でした。
注文時にSL/TPを指定するには、以下のように引数をしっかり指定するフル形式の関数を使用します。
買い注文でのSL/TP指定方法
|
1 |
trade.Buy(Lots, Symbol, Price, SL, TP, Comment); |
Lots: ロット数(例:0.1)Symbol: 通貨ペア名(例:_Symbol)Price: エントリー価格。成行買いの場合は0または現在の買値(Ask)を指定します。(通常は0を指定するとブローカー側で最新のAskで成行約定してくれます)SL: 損切り価格(レート)TP: 利食い価格(レート)Comment: 注文コメント(任意の文字列)
⚠️ 超重要:SL/TP価格の計算と「正規化」
例えば、現在のドル円のAskが 150.000 だとして、「15pips(150ポイント)の損切り(SL)」と「30pips(300ポイント)の利食い(TP)」を設定したい場合、価格は以下のように計算します。
- 買い注文
SL価格=Ask - (150 * _Point)👉 現在地より下に設定TP価格=Ask + (300 * _Point)👉 現在地より上に設定
- 売り注文
SL価格=Bid + (150 * _Point)👉 現在地より上に設定TP価格=Bid - (300 * _Point)👉 現在地より下に設定
そして、この計算した価格をそのまま送信してはいけません。PC内部の計算では浮動小数点数の微小な誤差(例: 150.0000000001 など)が含まれており、そのままブローカーに送ると「無効なストップ価格」として注文が拒否されてしまいます。
そこで、必ず NormalizeDouble() 関数を使って、ブローカーが定義している小数点桁数(_Digits)に端数を丸めます。
|
1 2 3 4 5 6 7 |
// 買い注文の価格計算と正規化の例 double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double slPrice = NormalizeDouble(ask - (150 * _Point), _Digits); // 15pips下 double tpPrice = NormalizeDouble(ask + (300 * _Point), _Digits); // 30pips上 // 注文の送信 trade.Buy(0.1, _Symbol, ask, slPrice, tpPrice); |
この NormalizeDouble(価格, _Digits) は、MQL5プログラミングにおける必須のコーディングルールです!
[!IMPORTANT]
ブローカーの「ストップレベル」に注意!
エラー(INVALID_STOPS)が発生するもう一つの大きな原因が「ストップレベル」です。ブローカーや通貨ペアによっては、「現在価格から最低○ポイント以上離してSL/TPを設定しなければならない」という制限(ストップレベル)が設定されています。あまりに現在地に近い価格を指定すると注文が拒否されるため、スキャルピングEAなどを作る際は注意が必要です。
3. 保有中のポジションのSL/TPを変更する(PositionModify)
エントリー時にはSL/TPを設定せず、後から手動や特定のロジック(トレーリングストップなど)でSL/TPを設定・変更したい場合は、trade.PositionModify() を使用します。
基本コード
|
1 |
trade.PositionModify(Ticket, SL, TP); |
Ticket: 変更したいポジションのチケット番号(ID)SL: 新しい損切り価格(レート)TP: 新しい利食い価格(レート)
【実践向け】ポジションの存在と選択を明示的にチェックする
すでに決済されて存在しないポジションに対して PositionModify を実行するとエラーになるため、前回のループ処理同様、PositionSelectByTicket() を使ってポジションの存在を確認してから処理を実行するのがベストプラクティスです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
ulong ticket = 123456; // 変更したいポジションチケット double newSL = NormalizeDouble(ask - (100 * _Point), _Digits); // ポジションが選択でき、マジックナンバーが一致しているか確認 if(PositionSelectByTicket(ticket) && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { // 現在のTP価格を取得してそのまま引き継ぎ、SLだけを変更する double currentTP = PositionGetDouble(POSITION_TP); if(!trade.PositionModify(ticket, newSL, currentTP)) { Print("SL変更失敗。エラーコード: ", trade.ResultRetcode()); } } |
4. コピペで動く!SL/TP設定機能付きサンプルEA
それでは、実際の挙動を確認するためのサンプルコードを紹介します。
このコードは、「EAをチャートにセットした瞬間に、0.1ロットの買い注文を出し、同時に15pips(150ポイント)のSLと30pips(300ポイント)のTPを自動で設定する」というEAテンプレートです。
前回の「新バー判定」を取り入れた、より堅牢な設計になっています(新規注文処理に特化しているため、ポジション選択処理は含めていません)。
|
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 |
//+------------------------------------------------------------------+ //| SLTPSampleEA.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 = 999999; // マジックナンバー input double TradeLots = 0.1; // 取引ロット数 input int Slippage = 100; // 許容スリッページ (ポイント) input group "--- SL/TP設定(pips単位) ---" input int StopLossPips = 15; // 損切り幅 (1pips = 10ポイント) input int TakeProfitPips = 30; // 利食い幅 (1pips = 10ポイント) // --- グローバル変数 --- 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; Print("成行買い注文を送信します(SL/TP自動設定付き)..."); // 現在の買値を取得 double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // SLおよびTP価格を計算し、NormalizeDoubleで価格をブローカーの桁数(_Digits)に正規化 double slPrice = NormalizeDouble(ask - (StopLossPips * 10 * _Point), _Digits); double tpPrice = NormalizeDouble(ask + (TakeProfitPips * 10 * _Point), _Digits); // 新規注文の発注(成行買い、Ask価格、計算したSL、計算したTP) if(trade.Buy(TradeLots, _Symbol, ask, slPrice, tpPrice)) { // 注文の送信(リクエスト)自体が成功した時点で、次の足での多重発注を防ぐためフラグを立てる isOrdered = true; // 注文リクエストの結果を確認 ulong retcode = trade.ResultRetcode(); if(retcode == TRADE_RETCODE_DONE || retcode == TRADE_RETCODE_PLACED) { Print("注文とSL/TPの設定が成功しました!"); } else { Print("発注送信はされましたが、ブローカーに拒否されました。リターンコード: ", retcode); } } else { Print("注文の送信自体に失敗しました。エラーコード: ", trade.ResultRetcode()); // 送信自体に失敗した場合は、次の足で再送を試みられるよう isOrdered = true にはしません } // 処理完了したバーの時間を記憶 lastBarTime = currentBarTime; } //+------------------------------------------------------------------+ |
このコードを MetaEditor でコンパイルしてチャートにセットすると、即座に発注が行われます。MT5の「取引」ターミナルを確認すると、注文されたポジションにしっかりと赤い破線(SL/TPの価格)が表示されます!
5. まとめ&次のステップ
今回は、MQL5における損切り(SL)と利食い(TP)の自動設定方法を解説しました。
- 注文時にSL/TPを設定する際は、省略形ではなくフル引数形式の
trade.Buy/trade.Sellを使用する。 - SL/TPはポイント数ではなく具体的なレート価格を計算して渡す。
- 浮動小数点エラーによる注文拒否を防ぐため、
NormalizeDouble(価格, _Digits)による正規化は必須。 - 後から変更する場合は
trade.PositionModify()を使用する。
これで、安全に資金を保護しながら運用できるEAのプログラムが書けるようになりました。
次回は、リスク管理をさらに一歩進め、「口座残高の○%の損失に収まるように、エントリー時のロット数を自動で計算・調整する資金管理ロジック」について解説します。ロット数を固定するのではなく、資金量に合わせて自動変更する本格的な実践向けEA作りに挑戦しましょう!
今回のコードで「ストップエラー(エラーコード 10016)が出て注文が通らない」「pips換算の計算がうまくいかない」などの質問があれば、YouTubeのコメント欄やXでお気軽にご連絡くださいね!


コメント