ブレイクイーブンで損失ゼロに
ある程度利益が出たら、損切りラインを仕掛け価格に移動して、損失ゼロにするブレイクイーブン機能を読者の方から作って欲しいと頼まれましたので早速作ってみました。
一般的なトレイリングストップ関数をベースに作ることができます。
//+------------------------------------------------------------------+
//|【関数】ブレイクイーブン |
//| |
//|【引数】 IN OUT 引数名 説明 |
//| --------------------------------------------------------- |
//| ○ aMagic マジックナンバー |
//| ○ aMoveExecPips ストップ変更開始位置(pips) |
//| ○ aMoveStopPips ストップ変更幅(pips) |
//| |
//|【戻値】なし |
//| |
//|【備考】なし |
//+------------------------------------------------------------------+
void breakeven(int aMagic, double aMoveExecPips, double aMoveStopPips)
{
for(int i = 0; i < OrdersTotal(); i++){
// オーダーが1つもなければ処理終了
if(OrderSelect(i, SELECT_BY_POS) == false){
break;
}
string oSymbol = OrderSymbol();
// 別EAのオーダーはスキップ
if(oSymbol != Symbol() || OrderMagicNumber() != aMagic){
continue;
}
int oType = OrderType();
// 待機オーダーはスキップ
if(oType != OP_BUY && oType != OP_SELL){
continue;
}
double digits = MarketInfo(oSymbol, MODE_DIGITS);
double oPrice = NormalizeDouble(OrderOpenPrice(), digits);
double oStopLoss = NormalizeDouble(OrderStopLoss(), digits);
double oTakeProfit = NormalizeDouble(OrderTakeProfit(), digits);
int oTicket = OrderTicket();
double exec = NormalizeDouble(aMoveExecPips * gPipsPoint, digits);
double stop = NormalizeDouble(aMoveStopPips * gPipsPoint, digits);
if(oType == OP_BUY){
double price = MarketInfo(oSymbol, MODE_BID);
if(price >= oPrice + exec){
// 何度もmodifyしないためのif文
if(NormalizeDouble(oStopLoss, digits) != NormalizeDouble(oPrice + stop, digits)){
Print("ロングポジションの損切りライン変更:", DoubleToStr(oStopLoss, digits), " ⇒ ", DoubleToStr(oPrice + stop, digits));
orderModifyReliable(oTicket, 0.0, NormalizeDouble(oPrice + stop, digits), oTakeProfit, 0, gArrowColor[oType]);
}
}
}else if(oType == OP_SELL){
price = MarketInfo(oSymbol, MODE_ASK);
if(price <= oPrice - exec){
// 何度もmodifyしないためのif文
if(NormalizeDouble(oStopLoss, digits) != NormalizeDouble(oPrice - stop, digits)){
Print("ショートポジションの損切りライン変更:", DoubleToStr(oStopLoss, digits), " ⇒ ", DoubleToStr(oPrice - stop, digits));
orderModifyReliable(oTicket, 0.0, NormalizeDouble(oPrice - stop, digits), oTakeProfit, 0, gArrowColor[oType]);
}
}
}
}
}
最初に、オーダーの有無をチェックした上で、別EAの注文や待機注文は対象外なのでスキップするようにしています。
aMoveExecPipsだけ順行したら(利益が出たら)、仕掛け価格からaMoveStopPipsだけ順行した価格に損切り価格を変更します。aMoveStopPips=0にすれば、ブレイクイーブンになります。汎用性を持たせるために、パラメータ化しています。
ポイントは、何度もmodifyしないように、現在の損切り価格と変更しようとしている損切り価格が同じ場合はmodifyしないようにif文で制御することです。oStopLossとoPriceの比較の部分ですね。これを入れておかないと、「OrderModify error 1」が大量発生することになります。
私の環境ではこれで問題なく動作するのですが、人によっては(FX業者によっては)、このコードでもOrderModify error 1が発生することがあるようです。NormalizeDoubleでMQL4内部の浮動小数点数の丸め誤差を排除しているはずなのに、なぜ発生するのか理由がよく分かっていません。

サンプルEA
関数だけではイメージが沸きづらいと思いますので、サンプルEAを載せておきます。1本の足で1回だけエントリーする機能も混ざっていますが、ティックごとにエントリーするのを避けるためだけに入れていますので、無視してください。
//+------------------------------------------------------------------+
//| Sample.mq4 |
//| Copyright (c) 2015, りゅーき |
//| https://autofx100.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright (c) 2015, りゅーき"
#property link "https://autofx100.com/"
#property version "1.00"
//+------------------------------------------------------------------+
//| ライブラリ |
//+------------------------------------------------------------------+
#include <stderror.mqh>
#include <stdlib.mqh>
#include <WinUser32.mqh>
#include <Original/Basic.mqh>
#include <Original/DateAndTime.mqh>
#include <Original/LotSizing.mqh>
#include <Original/OrderHandle.mqh>
#include <Original/OrderReliable.mqh>
//+------------------------------------------------------------------+
//| EAパラメータ設定情報 |
//+------------------------------------------------------------------+
extern string Note01 = "=== General ==================================================";
extern int MagicNumber = 7777777;
extern int SlippagePips = 5;
extern string Comments = "";
extern double FixLotSize = 0.01;
extern string Note02 = "=== Entry ====================================================";
extern string Note02_1 = "--- entry only once per 1 bar --------------------------------";
extern bool UseEntryPer1Bar = true;
extern string Note03 = "=== Exit =====================================================";
extern string Note03_1 = "--- BreakEven ------------------------------------------------";
extern double MoveExecPips = 10.0;
extern double MoveStopPips = 0.0;
//+------------------------------------------------------------------+
//| グローバル変数 |
//+------------------------------------------------------------------+
// 共通
double gPipsPoint = 0.0;
int gSlippage = 0;
color gArrowColor[6] = {Blue, Red, Blue, Red, Blue, Red}; //BUY: Blue, SELL: Red
int gPrvBars = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
gPipsPoint = currencyUnitPerPips(Symbol());
gSlippage = getSlippage(Symbol(), SlippagePips);
gPrvBars = Bars;
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
int currentBars = Bars;
// -----------------------------------------------------------------
// 仕切り
// -----------------------------------------------------------------
// エントリー位置からMoveExecPips順行したら、エントリー位置から
// MoveStopPips順行した位置にストップを移動し、それ以降変更しない
breakeven(MagicNumber, MoveExecPips, MoveStopPips);<br />
// -----------------------------------------------------------------
// 仕掛けフィルター(1本の足で1回だけ仕掛ける)
// -----------------------------------------------------------------
if(UseEntryPer1Bar){
// 新しい足を生成した時ではない場合は、仕掛けない
if(currentBars == gPrvBars){
gPrvBars = currentBars;
return;
}
}
// -----------------------------------------------------------------
// 仕掛け
// -----------------------------------------------------------------
// 成行注文
int ticket = orderSendReliable(Symbol(), OP_BUY, FixLotSize, Ask, gSlippage, 0.0, 0.0, Comments, MagicNumber, 0, gArrowColor[OP_BUY]);
// 本来はticketの値によって後続の処理を制御する必要があるが、簡単のため、ここでは無視
gPrvBars = currentBars;
}
