今回は、MT4のEAサンプルソース(FX用)を公開します。
トレードに使う分析はゴトー日で、逆張りエントリーをしています。
また、当サイトには珍しくマジックナンバーも考慮した(複数EAで同時運用可能)EAとなっております。
はじめに
FXや投資関連でゴトー日というものがあります。それにちなんだ仲値系のEAも最近は多く出回っていますが、近年中々成績を上げる事が難しくなってきています。
私が使っているゴトー日系のEAはSLに引っかかってから戻すパターンが多いので、今回は思い切ってゴトー日から仲値の動きで逆張りしてやろうと思いEAを作ってみました。
試行回数が少ないため安定はしないEAですが、最近のゴトー日仲値の動きを見ていてイライラしている場合は利用してみてもいいかも?しれません。もし良ければ自己責任でどうぞ!
今回は時間が関係してくるので、標準時間が違う業者だとうまく動かない可能性があります。
※日本時間が-7時間となっているMT4(FXTF)での動作確認済みです
EAの仕様
それでは、今回のソースコードのエントリー/決済/パラメータ設定についての仕様を説明します。
エントリータイミング
ショートエントリー
ゴトー日のAM6:45にショートエントリーします。
※サマータイム時はAM5:45
設定でエントリー時間を変更する事も可能です。
ロングエントリー
ゴトー日のAM9:55にロングエントリーします。ただし、ショートエントリー側で30pipsを超える利益が出ている場合に限ります。
また、ショートエントリー側がすでにTPに到達している場合で、AM9:55の段階でTPから10pips以上戻している場合はエントリーしません。
エントリー例
早朝にショートエントリーし、AM9:55に決済しています。利益が30pipsを超えているため、そこからすぐにロングエントリーしPM2:00に決済している動きとなっています。
利益が30pips未満の場合、ロングエントリーはしません。
決済タイミング
TP(指値)とSL(逆指値)を65pipsで管理していますが、時間で決済管理をしています。
ショートエントリー側
ショートポジションを持っている場合、AM9:55に強制決済します。
ロングエントリー側
ロングポジションを持っている場合、PM2:00に強制決済します。
その他の仕様
このEAは米ドル円の5分足で動かして確認しました。
設定値は以下の通りです。
- MagicNumber= 123456 ⇒ マジックナンバーです
- LotSize= 0.01 ⇒ ロット数です(0.01=1000通貨)
- A_Spread= 20 ⇒ エントリー時に許容するスプレッドの値です(20 = 2pips)
- Order_TP= 650 ⇒ ストップロスまでのポイントです(1000=100pips)
- Order_SL= 650 ⇒ ストップロスまでのポイントです(1000=100pips)
- Entry_Hour_S= 22 ⇒ 早朝エントリーする時間(サマータイム時) ※日本時間換算は+7時間
- Entry_Minute_S= 45 ⇒ 早朝エントリーする分(サマータイム時)
- Entry_Hour_W= 23 ⇒ 早朝エントリーする時間 ※日本時間換算は+7時間
- Entry_Minute_W= 45 ⇒ 早朝エントリーする分
保持するポジションは最大1つです。
今回はマジックナンバーも見るようにしています。
EAレポート結果
2023年のトレード結果です。トレード回数は12回と少ないですがプラスの結果となっています。
ダウンロード
上記のEAファイルになります。
“ゴトー日逆張りEA” をダウンロード 21_Gotobi-Gyakubari-3.ex4 – 2054 回のダウンロード – 23.83 KBソースコード
以下サンプルソースコードになります。
※丸々コピペでコンパイルできます
//+------------------------------------------------------------------+
//| 21_Gotobi-Gyakubari.mq4 |
//| Copyright 2020, mef Software. |
//| https://fx-prog.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, mef Software."
#property link "https://fx-prog.com/"
#property version "1.00"
input int MagicNo = 123456; //マジックナンバー
input double Lots = 0.01; //ロット数です(0.01=1000通貨)
input int A_SPREAD = 20; //エントリー及び決済時に許容するスプレッドの値です(10 = 1pips)
input int Order_SL = 650; //ストップロスの値(1000=100pips)
input int Order_TP = 650; //プロフィット(利確)の値(1000=100pips)
input int Entry_Hour_S = 22; //早朝エントリーする時間(サマータイム時)
input int Entry_Minute_S = 45;//早朝エントリーする分(サマータイム時)
input int Entry_Hour_W = 23; //早朝エントリーする時間
input int Entry_Minute_W = 45;//早朝エントリーする分
bool entryflg=false;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int init()
{
//---
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
int start()
{
int orderPtn=0; //0:何もしない 1:買い 2:売り
int total=0;
double ea_order_stop_price=0,ea_order_good_price=0; //ストップロスレート,利確レート,エントリーレート
bool OrderKekka;
//---
if(IsSummerTime()==false){
//ショートエントリー側///////////////////////
if(IsTradeDate()==true){
if(Hour() == Entry_Hour_W && Minute() == Entry_Minute_W){
orderPtn=2;
}
}
total=funcOrder_Select(0,MagicNo);
//9時55分に決済
if(OrderType()==OP_SELL && total ==1 && Hour() == 2 && Minute() == 55){
OrderKekka = funcOrder_Close(A_SPREAD,MagicNo,clrGray);
total=funcOrder_Select(0,MagicNo);
}
//ショートエントリー側///////////////////////
//ロングエントリー側///////////////////////
if(entryflg==true){
//9時55分にロング
if(Hour() == 2 && Minute() == 55){
entryflg=false;
if(getProfit(1,MagicNo)> 30 && OrderClosePrice() > Ask - 100*Point ){
orderPtn=1;
}
}
}
//14時に決済
if(OrderType()==OP_BUY && total ==1 && Hour() == 7 && Minute() == 00){
OrderKekka = funcOrder_Close(A_SPREAD,MagicNo,clrGray);
}
}else if(IsSummerTime()==true){
//ショートエントリー側///////////////////////
if(IsTradeDate()==true){
if(Hour() == Entry_Hour_S && Minute() == Entry_Minute_S){
orderPtn=2;
}
}
total=funcOrder_Select(0,MagicNo);
//9時55分に決済
if(OrderType()==OP_SELL && total ==1 && Hour() == 3 && Minute() == 55){
OrderKekka = funcOrder_Close(A_SPREAD,MagicNo,clrGray);
total=funcOrder_Select(0,MagicNo);
}
//ショートエントリー側///////////////////////
//ロングエントリー側///////////////////////
if(entryflg==true){
//9時55分にロング
if(Hour() == 3 && Minute() == 55){
entryflg=false;
if(getProfit(1,MagicNo)> 30 && OrderClosePrice() > Ask - 100*Point ){
orderPtn=1;
}
}
}
//14時に決済
if(OrderType()==OP_BUY && total ==1 && Hour() == 8 && Minute() == 00){
OrderKekka = funcOrder_Close(A_SPREAD,MagicNo,clrGray);
}
}
//新規注文
if(total ==0 && orderPtn > 0)
{
if(orderPtn == 1)
{
ea_order_stop_price = Ask - Order_SL * Point;
ea_order_good_price = Ask + Order_TP * Point;
}
else if(orderPtn == 2)
{
ea_order_stop_price = Bid + Order_SL * Point;
ea_order_good_price = Bid - Order_TP * Point;
entryflg=true;
}
//新規注文
OrderKekka = funcOrder_Send(orderPtn - 1,ea_order_stop_price,ea_order_good_price,0,MagicNo);
}
return(0);
}
//+------------------------------------------------------------------+
//|【関数】オーダーセレクト関数
//|
//|【引数】 mode 0:最新ポジション選択(ポジション数も取得) 1:1つ前の決済ポジ 2:2つ前の決済ポジ ・・・
//|【引数】 マジックナンバー
//|【引数(参照)】 mode 0の場合、最新のチケット番号群を更新する
//|
//|【戻値】ポジション数(過去の場合はあったINDEX)
//|
int funcOrder_Select(int mode,int ea_order_MagicNo){
int total=0,cnt=0,ticket_buf=0;
bool OrderKekka =false;
total = 0;
if(mode==0){
for(cnt = 0; cnt < OrdersTotal(); cnt++){
if(OrderSelect(cnt,SELECT_BY_POS,MODE_TRADES) == true){
if(OrderMagicNumber() == ea_order_MagicNo){
ticket_buf = OrderTicket();
total = total+1;
}
}
}
//最新のポジションを選択する
if(ticket_buf !=0){
OrderKekka = OrderSelect(ticket_buf,SELECT_BY_TICKET,MODE_TRADES);
}
}else{
for(cnt = OrdersHistoryTotal(); cnt > 0; cnt--){
if(OrderSelect(cnt-1,SELECT_BY_POS,MODE_HISTORY) == true){
if(OrderMagicNumber() == ea_order_MagicNo){
total = total+1;
if(mode <= total){
break;
}
}
}
}
}
return(total);
}
//+------------------------------------------------------------------+
//|【関数】新規注文関数 |
//| |
//|【引数】 ea_order_entry_Type:売買(0:買 1:売) |
//|【引数】 ea_order_stop_price:損切値 ea_order_good_price:利確値 |
//|【引数】 orderComment:オーダーコメント ea_order_MagicNo:マジックNo |
//| |
//|【戻値】True:成功 |
//| |
//|
bool funcOrder_Send(int ea_order_entry_Type, double ea_order_stop_price, double ea_order_good_price,int orderComment,int ea_order_MagicNo)
{
int order_resend_num; // エントリー試行回数
int ea_ticket_res; // チケットNo
int errorcode; // エラーコード
double ea_order_entry_price; // エントリーレート
color order_Color;
bool kekka;
for( order_resend_num = 0; order_resend_num < 10; order_resend_num++ ) { // エントリー試行回数上限:10回
if(MarketInfo(NULL,MODE_SPREAD) < A_SPREAD){
if(ea_order_entry_Type == OP_BUY){
ea_order_entry_price = Ask; // 現在の買値でエントリー
order_Color = clrBlue;
}else if(ea_order_entry_Type == OP_SELL){
ea_order_entry_price = Bid; // 現在の売値でエントリー
order_Color = clrRed;
}
// FXCMでは新規エントリー時にストップ/リミットを設定出来ない。
ea_ticket_res = OrderSend( // 新規エントリー注文
NULL, // 通貨ペア
ea_order_entry_Type, // オーダータイプ[OP_BUY / OP_SELL]
Lots, // ロット[0.01単位](FXTFは1=10Lot)
ea_order_entry_price, // オーダープライスレート
20, // スリップ上限 (int)[分解能 0.1pips]
ea_order_stop_price, // ストップレート
ea_order_good_price, // リミットレート
orderComment, // オーダーコメント
ea_order_MagicNo, // マジックナンバー(識別用)
0, // オーダーリミット時間
order_Color // オーダーアイコンカラー
);
}
if ( ea_ticket_res == -1) { // オーダーエラー
errorcode = GetLastError(); // エラーコード取得
if( errorcode != ERR_NO_ERROR) { // エラー発生
printf("エラー");
}
Sleep(2000); // 1000msec待ち
RefreshRates(); // レート更新
printf("再エントリー要求回数:%d, 更新エントリーレート:%g",order_resend_num+1 ,ea_order_entry_price);
} else { // 注文約定
Print("新規注文約定。 チケットNo=",ea_ticket_res," レート:",ea_order_entry_price);
Sleep(300); // 300msec待ち(オーダー要求頻度が多過ぎるとエラーになる為)
break;
}
}
return kekka;
}
//+------------------------------------------------------------------+
//|【関数】決済関数
//|
//|【引数】 kessaiSpread:許容する決済時のスプレッド
//|
//|【戻値】True:成功 False:失敗
//|
//|
bool funcOrder_Close(int kessaiSpread, int ea_order_MagicNo, color order_Color)
{
bool kekka,OrderKekka;
int total,order_resend_num;
double ea_order_entry_price; // エントリーレート
double KessaiBuff;
kekka=false;
total=funcOrder_Select(0,ea_order_MagicNo);
if(total >0){
for(order_resend_num = 0; order_resend_num < 100; order_resend_num++ ) {
if(MarketInfo(NULL,MODE_SPREAD) <= kessaiSpread){
if(OrderType() == OP_BUY){
ea_order_entry_price = Bid; // 現在の売値で決済
}else if(OrderType() == OP_SELL){
ea_order_entry_price = Ask; // 現在の買値で決済
}
KessaiBuff = OrderProfit();
OrderKekka = OrderClose(OrderTicket(),OrderLots(),ea_order_entry_price,10,order_Color);
if(OrderKekka==false){
Print("決済注文失敗 エラーNo:",GetLastError()," リトライ回数:",order_resend_num+1);
RefreshRates();
Sleep(1000);
}else{
Print("決済注文完了 レート:",ea_order_entry_price," 決済結果:",KessaiBuff,"円"," Magic:",ea_order_MagicNo);
break;
}
}else{
Print("スプレッド拡大中(銭)=",MarketInfo(NULL,MODE_SPREAD) / 10," リトライ回数=",order_resend_num+1);
Sleep(5000); // msec待ち
RefreshRates(); // レート更新
}
}
}
kekka=true;
return kekka;
}
//+------------------------------------------------------------------+
//|【関数】前回トレードの損益をpips数で取得する
//|
//|【引数】 0:現在のプロフィット 1:過去の1つ前のプロフィット
//|
//|【戻値】pips数
//|
double getProfit(int mode,int ea_order_MagicNo){
int ticket = 0,total=0,cnt=0;
double ans = 0.0;
bool OrderKekka =false;
if(mode==0){
if(funcOrder_Select(0,ea_order_MagicNo) > 0){
if(OrderType()==OP_SELL){
ans = OrderOpenPrice()-Ask;
}else if(OrderType()==OP_BUY){
ans = Bid-OrderOpenPrice();
}
if(Digits==2 || Digits==3){
ans *= 100;
}else if(Digits==4 || Digits==5){
ans *= 10000;
}
}
}else{
if(funcOrder_Select(mode,ea_order_MagicNo) > 0){
if(OrderType()==OP_SELL){
ans = OrderOpenPrice()-OrderClosePrice();
}else if(OrderType()==OP_BUY){
ans = OrderClosePrice()-OrderOpenPrice();
}
if(Digits==2 || Digits==3){
ans *= 100;
}else if(Digits==4 || Digits==5){
ans *= 10000;
}
}
}
return(ans);
}
//+------------------------------------------------------------------+
//|【関数】サマータイム確認 |
//| |
//|【引数】 なし |
//| |
//|【戻値】True:サマータイム |
//|【備考】3月第2日曜日~11月第1日曜日までをサマータイムとする |
//| |
bool IsSummerTime(){
switch(Year()){
case 2020: if(StringToTime("2020.3.8") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2020.11.1"))return true; break;
case 2021: if(StringToTime("2021.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2021.11.7"))return true; break;
case 2022: if(StringToTime("2022.3.13")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2022.11.6"))return true; break;
case 2023: if(StringToTime("2023.3.12")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2023.11.5"))return true; break;
case 2024: if(StringToTime("2024.3.10")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2024.11.3"))return true; break;
case 2025: if(StringToTime("2025.3.9") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2025.11.2"))return true; break;
case 2026: if(StringToTime("2026.3.8") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2026.11.1"))return true; break;
case 2027: if(StringToTime("2027.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2027.11.7"))return true; break;
case 2028: if(StringToTime("2028.3.12")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2028.11.5"))return true; break;
case 2029: if(StringToTime("2029.3.11")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2029.11.4"))return true; break;
case 2030: if(StringToTime("2030.3.10")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2030.11.3"))return true; break;
case 2031: if(StringToTime("2031.3.9") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2031.11.2"))return true; break;
case 2032: if(StringToTime("2032.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2032.11.7"))return true; break;
case 2033: if(StringToTime("2033.3.13")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2033.11.6"))return true; break;
case 2034: if(StringToTime("2034.3.12")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2034.11.5"))return true; break;
case 2035: if(StringToTime("2035.3.11")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2035.11.4"))return true; break;
case 2036: if(StringToTime("2036.3.9") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2036.11.2"))return true; break;
case 2037: if(StringToTime("2037.3.8") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2037.11.1"))return true; break;
case 2038: if(StringToTime("2038.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2038.11.7"))return true; break;
case 2039: if(StringToTime("2039.3.13")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2039.11.6"))return true; break;
case 2040: if(StringToTime("2040.3.11")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2040.11.4"))return true; break;
}
return false;
}
//+------------------------------------------------------------------+
//|【関数】取引する日確認 |
//| |
//|【引数】 なし |
//| |
//|【戻値】True:取引する日 |
//| |
//| |
bool IsTradeDate(){
switch(Month()){
case 1: //1月
if(Day()==4)return true;
if(Day()==9)return true;
if(Day()==12)return true;
if(Day()==19)return true;
if(Day()==24)return true;
if(Day()==29)return true;
break;
case 2: //2月
if(Day()==2)return true;
if(Day()==9)return true;
if(Day()==14)return true;
if(Day()==19)return true;
if(Day()==23)return true;
break;
case 3: //3月
if(Day()==2)return true;
if(Day()==9)return true;
if(Day()==14)return true;
if(Day()==19)return true;
if(Day()==23)return true;
if(Day()==29)return true;
break;
}
return false;
}
処理自体はいたってシンプルです。今回は自作EAでよく使っているOrderSelect()系やPips数取得の自作関数もあるのでよければ参考にしてみてください。
あと、最後のほうにIsTradeDate()関数がありますがここで取引する日(ゴトー日)を指定しています。2023年4月以降のゴトー日を追加される場合はここに追記していってください。
※ゴトー日が1日早いのは、MT4の時間が7時間前であるため
さいごに
以上、ゴトー日仲値で逆張りする系EAのサンプルソースでした。
現在ゴトー日仲値系のトレードが全然機能しない(ゴトー日に実需の買売が少ない?)状態となっていますが、逆に利益をあげる事はできないのか?と本来の考えと逆張り的な考えで作ってみました。
次の仲値が2023年3月3日(金)なのでそこで1度試してみたいと思います。
また、ソースコードがある程度わかる方であれば、このソースコードから逆張りではないゴトー日EAも(処理を反転したら)簡単に作る事ができるので参考にして頂ければと思います。
※ EAのサンプルソースを一覧表にまとめました
※オンラインレッスンやってます
コメント
いつもお世話になっております。
ソースコードをコピペしてバックテストを試しましたが、
パラメータ設定で0.01ロットにしても、1ロットでのエントリーになってしまう状態です。
私の方の問題なんでしょうか?
すみません、ソースコードのロット数の所が間違ってたので直しました!