全ソース
//+------------------------------------------------------------------+ //| Steadiness3.mq4 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include <stdlib.mqh> #define versionValue "1.00" #property copyright "Copyright 2020, Subaru" #property link "http://snowballrichdad.xyz/" #property version versionValue #property strict extern int MAGIC = 29388; //マジックナンバー extern double ONE_LOT_AMMOUNT = 5000.0; // 0.01ロットポジションを取るための口座残高 extern double LIMIT_RSI_HIGH = 74.0; extern double LIMIT_RSI_LOW = 22.0; int SLIPPAGE = 20; extern double takeProfitBuy = 0.003; extern double stopLossBuy = 0.003; extern double takeProfitSell = 0.003; extern double stopLossSell = 0.003; extern double MARGIN_RATE = 0.00; // 余剰証拠金が口座残高に対してこの割合以下になったら全決済 //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Print("Version =" + versionValue); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- int ordersTotalNum = OrdersTotal(); if(ordersTotalNum == 0) { // 初回発注 bed(); return; } //まずはストップロスチェック if(AccountFreeMargin() < AccountBalance() * MARGIN_RATE ) { handleStopLoss(); return; } } //ストップロス処理 void handleStopLoss() { for(int i=OrdersTotal()-1;i>=0;i--) { if(OrderSelect(i,SELECT_BY_POS)==true) { if(OrderMagicNumber()!=MAGIC || OrderSymbol()!=Symbol())continue; int tmpOrderType = OrderType(); if(OrderCloseTime() == 0) { if(tmpOrderType== OP_BUY) { if(!OrderClose(OrderTicket(),OrderLots(),Bid,1000,clrAliceBlue)) { Print("###checkStopLoss001"); Print(ErrorDescription(GetLastError())); }; } else if(tmpOrderType == OP_SELL) { if(!OrderClose(OrderTicket(),OrderLots(),Ask,1000,clrAliceBlue)) { Print("###checkStopLoss002"); Print(ErrorDescription(GetLastError())); } } Alert("Account acount margin is under the limt.", "Every order was closed.","Do you want to continue trade?"); } } } } // 初回発注 void bed() { // IRI取得 double iRIPrice = iRSI(NULL,NULL,14,PRICE_CLOSE,1); // ロット数計算 double lots = 0; double lastProfit = 0; // まずは基本ロット数をセットする lots = MathFloor((AccountBalance()) / ONE_LOT_AMMOUNT )* MarketInfo(Symbol(),MODE_LOTSTEP); if(selectLastCloseOrder()) { // 前回の取引の利益 lastProfit = OrderProfit(); // 前回の取引負け if(lastProfit < 0) { // 前回のチケット番号 int preTicketNo = OrderTicket(); // チケット番号がグローバル変数に格納されているか // グローバル変数(参照しないのでダミー) double globalDummy; string strTicketNo = IntegerToString(preTicketNo); bool isTicketNoStored = GlobalVariableGet(strTicketNo,globalDummy); // グローバル変数が格納されていなかったらMsgBoxを表示 if(!isTicketNoStored) { MessageBox("You lost the last trade. Will you contiune?"); // チケットNoで済フラグ GlobalVariableSet(strTicketNo,1); } } } // RSIが規定値がよりも高ければ、反転を期待して売り注文 if(iRIPrice > LIMIT_RSI_HIGH) { // まずはSL/TPなしで発注 int ticketNo=OrderSend(Symbol(),OP_SELL,lots,Bid,SLIPPAGE,0,0,NULL,MAGIC,0,clrYellow); if(ticketNo == -1) { Print("###Order Sell 001"); Print(ErrorDescription(GetLastError())); return; } // OrderPriceが確定してから、TP/SLを設定 if(OrderSelect(ticketNo,SELECT_BY_TICKET)==true) { double openPrice = OrderOpenPrice(); double currentSL = openPrice + stopLossSell; double currentTP = openPrice - takeProfitSell; if(!OrderModify(ticketNo,Bid,currentSL,currentTP,0,clrYellow)) { Print("###Order Sell 002"); Print(ErrorDescription(GetLastError())); } } } // RSIが規定値がよりも低ければ反転を期待して、買い注文 else if(iRIPrice < LIMIT_RSI_LOW) { // まずはSL/TPなしで発注 int ticketNo=OrderSend(Symbol(),OP_BUY,lots,Ask,SLIPPAGE,0,0,NULL,MAGIC,0,clrYellow); if(ticketNo == -1) { Print("###Order Buy 001"); Print(ErrorDescription(GetLastError())); return; } // OrderPriceが確定してから、TP/SLを設定 if(OrderSelect(ticketNo,SELECT_BY_TICKET)==true) { double openPrice = OrderOpenPrice(); double currentSL = openPrice - stopLossBuy; double currentTP = openPrice + takeProfitBuy; if(!OrderModify(ticketNo,Ask,currentSL,currentTP,0,clrYellow)) { Print("###Order Buy 002"); Print(ErrorDescription(GetLastError())); } } } } // 同一マジックナンバー、同一通貨での最後の取引をOrderSelect bool selectLastCloseOrder() { bool rtn = false; int oht = OrdersHistoryTotal(); // 決済履歴が1000以上の場合は1000でやめる // 他のEAの履歴が1000も入ることはないだろう if(oht > 1000) oht = 1000; for(int i=oht-1;i>=0;i--) { if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==true) { if(OrderMagicNumber()!=MAGIC || OrderSymbol()!=Symbol()) { continue; }else { rtn = true; break; } } } return rtn; } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- } //+------------------------------------------------------------------+
個別説明
OnTick関数
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- int ordersTotalNum = OrdersTotal(); if(ordersTotalNum == 0) { // 初回発注 bed(); return; } if(AccountFreeMargin() < AccountBalance() * MARGIN_RATE ) { handleStopLoss(); return; } }
OrdersTotal()関数で、現在のポジション数を確認します。
本EAは1つしかポジションを取らないので、1つでもポジションがある場合は、発注判定ロジックに入りません。
ポジションが0の場合はbed関数(独自関数:後述)で、エントリするかどうかの判定をします。onTick()のあとの処理はストップロスのチェックです。本EAはエントリ時にストップロスを指定するのですが、日本のFX業者だと、FreeMargin( = 口座残高 – 必要証拠金 – 含み損)が0になると強制決済になりEAが停止してしまうところが多いので、そうなる前のぎりぎりのところFreeMarginが口座残高の1%未満になったら、強制決済をするようなロジックを入れています。
ストップロス処理
//ストップロス処理 void handleStopLoss() { for(int i=OrdersTotal()-1;i>=0;i--) { if(OrderSelect(i,SELECT_BY_POS)==true) { if(OrderMagicNumber()!=MAGIC || OrderSymbol()!=Symbol())continue; int tmpOrderType = OrderType(); if(OrderCloseTime() == 0) { if(tmpOrderType== OP_BUY) { if(!OrderClose(OrderTicket(),OrderLots(),Bid,1000,clrAliceBlue)) { Print("###checkStopLoss001"); Print(ErrorDescription(GetLastError())); }; } else if(tmpOrderType == OP_SELL) { if(!OrderClose(OrderTicket(),OrderLots(),Ask,1000,clrAliceBlue)) { Print("###checkStopLoss002"); Print(ErrorDescription(GetLastError())); } } Alert("Account acount margin is under the limt.", "Every order was closed.","Do you want to continue trade?"); } } } }
ストップロスの処理はこの関数で実施しています。
すべての発注済みの注文についてループ処理をして決済していきます。
OrdersTotal関数は保留中の注文も取得されるのですが、本EAは成行注文のみなので、基本的にはすべて発注済み注文となります。
万が一、ぎりぎりのタイミングでまだ発注されていない場合で会っても、次のTickで決済されるので問題ありません。
MagicNumberや通貨ペアが同じもののみをキャンセルする処理を一応入れていますが、普通は1つの口座で1EAしか運用しないと思うので、本来は不要です。いつも流用しているので残っているだけです。
買いと売りのエントリで決済処理を分けていますが、多分一緒でもよいと思います。ただ、指定価格のところが現在価格と乖離するとエラーになってしまうので、このようにしています。
※ 結局MQL4には「成行注文」という概念がないようなので、スリッページを思いっきり広くしています。
発注処理
発注準備
// 初回発注 void bed() { // IRI取得 double iRIPrice = iRSI(NULL,NULL,14,PRICE_CLOSE,1); // ロット数計算 double lots = 0; double lastProfit = 0; // まずは基本ロット数をセットする lots = MathFloor((AccountBalance()) / ONE_LOT_AMMOUNT )* MarketInfo(Symbol(),MODE_LOTSTEP);
まずRSIの値を取得します。
次に、口座残高からロット数を決めています。
if(selectLastCloseOrder()) { // 前回の取引の利益 lastProfit = OrderProfit(); // 前回の取引負け if(lastProfit < 0) { // 前回のチケット番号 int preTicketNo = OrderTicket(); // チケット番号がグローバル変数に格納されているか // グローバル変数(参照しないのでダミー) double globalDummy; string strTicketNo = IntegerToString(preTicketNo); bool isTicketNoStored = GlobalVariableGet(strTicketNo,globalDummy); // グローバル変数が格納されていなかったらMsgBoxを表示 if(!isTicketNoStored) { MessageBox("You lost the last trade. Will you contiune?"); // チケットNoで済フラグ GlobalVariableSet(strTicketNo,1); } } }
つぎに、前回の取引で負けていた場合に、ポップアップウィンドウを表示し、取引を続行するかどうかをユーザに尋ねます。と、いうよりもポップアップを出すことにより、取引を「OK」ボタンが押されるまで一時停止するための処理です。
おなじ取引で何度もポップアップが出力されないように、「OK」ボタンが押された場合は、チケット番号を変数名としてグローバル変数(EAやMT4を停止しても4週間くらい保存され続ける変数)に保管しておきます。
逆にそのチケット番号ですでにグローバル変数がある場合は、「OK」ボタンが押されたということなので、ポップアップウィンドウは出力しません。
発注処理
// RSIが規定値がよりも高ければ、反転を期待して売り注文 if(iRIPrice > LIMIT_RSI_HIGH) { // まずはSL/TPなしで発注 int ticketNo=OrderSend(Symbol(),OP_SELL,lots,Bid,SLIPPAGE,0,0,NULL,MAGIC,0,clrYellow); if(ticketNo == -1) { Print("###Order Sell 001"); Print(ErrorDescription(GetLastError())); return; } // OrderPriceが確定してから、TP/SLを設定 if(OrderSelect(ticketNo,SELECT_BY_TICKET)==true) { double openPrice = OrderOpenPrice(); double currentSL = openPrice + stopLossSell; double currentTP = openPrice - takeProfitSell; if(!OrderModify(ticketNo,Bid,currentSL,currentTP,0,clrYellow)) { Print("###Order Sell 002"); Print(ErrorDescription(GetLastError())); } } } // RSIが規定値がよりも低ければ反転を期待して、買い注文 else if(iRIPrice < LIMIT_RSI_LOW) { // まずはSL/TPなしで発注 int ticketNo=OrderSend(Symbol(),OP_BUY,lots,Ask,SLIPPAGE,0,0,NULL,MAGIC,0,clrYellow); if(ticketNo == -1) { Print("###Order Buy 001"); Print(ErrorDescription(GetLastError())); return; } // OrderPriceが確定してから、TP/SLを設定 if(OrderSelect(ticketNo,SELECT_BY_TICKET)==true) { double openPrice = OrderOpenPrice(); double currentSL = openPrice - stopLossBuy; double currentTP = openPrice + takeProfitBuy; if(!OrderModify(ticketNo,Ask,currentSL,currentTP,0,clrYellow)) { Print("###Order Buy 002"); Print(ErrorDescription(GetLastError())); } } } }
RSIが規定値以上だったら売り、規定値以下だったら買いの注文を出します。
発注価格が確定してから、利益確定値と損切り値を確定するため、一度発注してそれが確定してから注文変更するという流れを取っています。
直近の取引取得
// 同一マジックナンバー、同一通貨での最後の取引をOrderSelect bool selectLastCloseOrder() { bool rtn = false; int oht = OrdersHistoryTotal(); // 決済履歴が1000以上の場合は1000でやめる // 他のEAの履歴が1000も入ることはないだろう if(oht > 1000) oht = 1000; for(int i=oht-1;i>=0;i--) { if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==true) { if(OrderMagicNumber()!=MAGIC || OrderSymbol()!=Symbol()) { continue; }else { rtn = true; break; } } } return rtn; }
最後に途中で使った直帰の取引を取得する処理です。同一EAのものを取得するために、マジックナンバーと通貨ペアでフィルタリングしています。
取引履歴を見る場合、特に運用始めは、同一口座で別のEAや手動取引の履歴が入っていることはよくあるので、この処理は必要です。
1,000履歴まで遡って履歴を探します。前回の取引が負けているかどうかなので、今思えばちょっとやりすぎで、1つ前の処理が別のマジックナンバーだったらもう無視して、そのEAでは過去の取引履歴なし状態としていいと思います。
他のロジックで動いていたEAの処理を流用したもので、特に問題ないので今はこのまま運用しています。
コメント