【MQL4テクニック】EA制作のテンプレートを大公開【解説付きのサンプルコードあり】
年間200本以上のEAを制作している私が、普段から実際に使用しているテンプレートをそのまま大公開します!
EAを制作する上での基本的なノウハウを揃えておりますので、ここから数十行の制御を追加するだけで簡単に高性能なEAが製作可能です。
EAの製作がまだ不慣れな方や、より効率的に開発を行いたいという方は必見です。
それでは本編に行きましょう。
EAのテンプレート
#property copyright "Codinal Systems"
#property link "https://codinal-systems.com/"
#property version "1.00"
#property strict
//--------------------------------------------------------------------------------------------//
//口座番号
//記述例1 #define ACCOUNT_NUMBER 123456 //口座番号123456のみ動作
//記述例2 #define ACCOUNT_NUMBER 0 //0の場合は口座縛りなし
#define ACCOUNT_NUMBER 0
//制限期間
//""を忘れずに
//記述例1 #define EXPIRY_DATE "2022.12.31 00:00" //2022/12/31 00:00まで使用可能
//記述例2 #define EXPIRY_DATE "0" //0の場合は制限期間なし
#define EXPIRY_DATE "0"
//--------------------------------------------------------------------------------------------//
input string orderSetting = "---------------注文設定---------------"; //▼注文
input int magicNum = 123; //マジックナンバー
input int slippagePips = 20; //スリッページ
input double lots = 0.01; //Lotサイズ
input double slPips = 10; //SL(Pips)
input double tpPips = 10; //TP(Pips)
input double maxSpreadPips = 10; //許容スプレッド(Pips)
input int maxPosition = 5; //最大ポジション数
input string otherMargin=""; //
input string otherSetting = "---------------その他設定---------------"; //▼その他
input int retry = 5; //注文失敗時のリトライ回数
input int interval = 1000; //インターバル秒数(ms)
//--------------------------------------------------------------------------------------------//
int slippage;
datetime lastTime;
bool authed = false;
// EAの挿入時に行う処理を記述する
int OnInit(){
if (TimeCurrent() > StrToTime(EXPIRY_DATE) && EXPIRY_DATE != "0"){
Alert("It has expired.\n有効期限が切れています。");
return INIT_FAILED;
}
slippage = int(slippagePips * Pips() / Point);
EventSetMillisecondTimer(3000);
return INIT_SUCCEEDED;
}
// EAの削除時に行う処理を記述する
void OnDeinit(const int reason){
}
void OnTick(){
if (!IsTradeAllowed()) return;
// 毎tickごとに処理を行う
if (lastTime == iTime(Symbol(), Period(), 0)) return;
lastTime = iTime(Symbol(), Period(), 0);
// 新しい足形成時に一度だけ処理を行う
}
// 注文処理を記述する
void OrderOpen(){
}
// 決済処理を記述する
void OrderExit(){
}
// 〇秒毎に行う処理を記述する
void OnTimer(){
if(AccountNumber() != ACCOUNT_NUMBER && ACCOUNT_NUMBER != 0 && !authed){
Alert("Auth falied.\n許可されていない口座番号です。");
ExpertRemove();
authed = true;
}
EventSetMillisecondTimer(500);
}
// イベント処理を記述する
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam){
if (id == CHARTEVENT_OBJECT_CLICK){
}
}
// 成行買い注文
int OrderSendBuy(){
if (maxSpreadPips < MarketInfo(Symbol(), MODE_SPREAD) / 10) return -1;
int ticket = -1;
double sl = 0, tp = 0;
if (slPips != 0) sl = Ask - slPips * Pips();
if (tpPips != 0) tp = Ask + tpPips * Pips();
for (int retryCnt = 0; retryCnt < retry; retryCnt++){
ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, slippage, sl, tp, "", magicNum);
if (ticket != -1) {
break;
}else {
Print("buy order send error " + (string)GetLastError());
Sleep(interval);
RefreshRates();
}
}
return ticket;
}
// 成行売り注文
int OrderSendSell(){
if (maxSpreadPips < MarketInfo(Symbol(), MODE_SPREAD) / 10) return -1;
int ticket = 0;
double sl = 0, tp = 0;
if (slPips != 0) sl = Bid + slPips * Pips();
if (tpPips != 0) tp = Bid - tpPips * Pips();
for (int retryCnt = 0; retryCnt < retry; retryCnt++){
ticket = OrderSend(Symbol(), OP_SELL, lots, Bid, slippage, sl, tp, "", magicNum);
if (ticket != -1) {
break;
}else {
Print("sell order send error " + (string)GetLastError());
Sleep(interval);
RefreshRates();
}
}
return ticket;
}
// 合計収支を取得する
double OrdersProfitTotal(int orderType){
double profit = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--){
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol() || OrderMagicNumber() != magicNum || OrderType() != orderType) continue;
profit += (OrderProfit() + OrderSwap() + OrderCommission());
}
return profit;
}
// 保有しているポジション数を返す
int OrdersCount(int orderType){
int cnt = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--){
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol() || OrderMagicNumber() != magicNum || OrderType() != orderType) continue;
cnt++;
}
return cnt;
}
// 全決済
void OrdersCloseAll(int orderType){
for(int i = OrdersTotal() - 1; i >= 0; i--){
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol() || OrderMagicNumber() != magicNum || OrderType() != orderType) continue;
for (int retryCnt = 0; retryCnt < retry; retryCnt++){
bool closed = OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 0);
if (closed){
break;
}else{
Print("order close error " + (string)GetLastError());
Sleep(interval);
RefreshRates();
}
}
}
}
// 1Pips辺りの価格を返す関数
double Pips(){
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
if (StringFind(Symbol(), "XAUUSD", 0) != -1 || StringFind(Symbol(), "GOLD", -1) != -1){
return NormalizeDouble(Point * 10, digits - 1);
}
if(digits == 3 || digits == 5){
return NormalizeDouble(Point * 10, digits - 1);
}
if(digits == 4 || digits == 2){
return Point;
}
return 0;
}
// ボタンを作成
void CreateButtonObject(string objName, int posX, int posY, int sizeX, int sizeY, string text, color fontColor, int fontSize, color bgColor, color borderColor){
ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0);
ObjectSetString(0, objName, OBJPROP_TEXT, text);
ObjectSetString (0, objName, OBJPROP_FONT, "Meiryo UI");
ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, objName, OBJPROP_COLOR, fontColor);
ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor);
ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor);
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, posX);
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, posY);
ObjectSetInteger(0, objName, OBJPROP_XSIZE, sizeX);
ObjectSetInteger(0, objName, OBJPROP_YSIZE, sizeY);
ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_LOWER);
ObjectSetInteger(0, objName, OBJPROP_STATE, false);
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, true);
}
テンプレートの解説
少し長くなってしまいましたが安心してください。
その分だけ既に機能を盛り込んでいるということですので、後はたった数十行追加するだけでEAが完成してしまいます。
既に実装されている機能は以下の通りです。
- 成行買い,売り注文 (OrderSenBuy関数, OrderSendSell関数)
- 注文エラー時のリトライ (OrdersProfitTotal関数)
- 許容スプレッドの制御
- 合計収支の計算 (OrdersProfitTotal関数)
- 保有ポジション数のカウント (OrdersCount関数)
- 全決済 (OrdersCloseAll関数)
- 1Pipsあたりの価格を取得 (Pips関数)
- ボタン作成 (CreateButtonObject関数)
- 口座縛り機能
- 期限縛り機能
実際の使用例
どんなEAを作成するか
まずは搭載したい機能と制御を洗い出します。
・RSIのレベルにタッチした状態で足が確定したら逆張り注文
・BUY(SELL)ポジションの収支が合計〇円になれば、BUY(SELL)ポジションを全決済
・BUY, SELLそれぞれポジション数は最大で5
・画面上のボタンから緊急全決済が出来る
・口座番号123456の方が年末までEAを使用できる
これらを実装したEAをお見せ致します。
サンプルコード
#property copyright "Codinal Systems"
#property link "https://codinal-systems.com/"
#property version "1.00"
#property strict
//--------------------------------------------------------------------------------------------//
//口座番号
#define ACCOUNT_NUMBER 123456
//制限期間
#define EXPIRY_DATE "2022.12.31 23:59"
//--------------------------------------------------------------------------------------------//
input string orderSetting = "---------------注文設定---------------"; //▼注文
input int magicNum = 123; //マジックナンバー
input int slippagePips = 20; //スリッページ
input double lots = 0.01; //Lotサイズ
input double slPips = 10; //SL(Pips)
input double tpPips = 10; //TP(Pips)
input double maxSpreadPips = 10; //許容スプレッド(Pips)
input int maxPosition = 5; //最大ポジション数
input string otherMargin = ""; //
input string otherSetting = "---------------その他設定---------------"; //▼その他
input int retry = 3; //注文失敗時のリトライ回数
input int interval = 3000; //インターバル秒数(ms)
input string closeMargin = ""; //
input string closeSetting = "---------------決済設定---------------"; //▼決済
input double exitProfit = 1000; //決済収益(円)
input string rsiMargin = ""; //
input string rsiSetting = "---------------RSI設定---------------"; //▼RSI
input int rsiPeriod = 14; //期間
input ENUM_APPLIED_PRICE rsiPrice = PRICE_CLOSE; //適用価格
input double rsiHighLevel = 70.0; //上レベル
input double rsiLowLevel = 30.0; //下レベル
//--------------------------------------------------------------------------------------------//
int slippage;
datetime lastTime;
// EAの挿入時に行う処理を記述する
int OnInit(){
if (TimeCurrent() > StrToTime(EXPIRY_DATE) && EXPIRY_DATE != "0"){
Alert("It has expired.\n有効期限が切れています。");
return INIT_FAILED;
}
slippage = int(slippagePips * Pips() / Point);
CreateButtonObject("BUTTON OBJECT", 5, 45, 100, 40, "強制全決済", clrWhite, 12, clrSalmon, clrNONE);
EventSetMillisecondTimer(3000);
return INIT_SUCCEEDED;
}
// EAの削除時に行う処理を記述する
void OnDeinit(const int reason){
}
void OnTick(){
if (!IsTradeAllowed()) return;
// 毎tickごとに処理を行う
OrderExit();
if (lastTime == iTime(Symbol(), Period(), 0)) return;
lastTime = iTime(Symbol(), Period(), 0);
// 新しい足形成時に一度だけ処理を行う
OrderOpen();
}
// 注文処理を記述する
void OrderOpen(){
double rsi = iRSI(Symbol(), Period(), rsiPeriod, rsiPrice, 1);
double lastRsi = iRSI(Symbol(), Period(), rsiPeriod, rsiPrice, 2);
if (OrdersCount(OP_BUY) < maxPosition && lastRsi < rsiHighLevel && rsi >= rsiHighLevel){
int ticket = OrderSendBuy();
if (ticket == -1) Sleep(interval);
}
if (OrdersCount(OP_SELL) < maxPosition && lastRsi > rsiLowLevel && rsi <= rsiLowLevel){
int ticket = OrderSendSell();
if (ticket == -1) Sleep(interval);
}
}
// 決済処理を記述する
void OrderExit(){
if (OrdersCount(OP_BUY) > 0 && OrdersProfitTotal(OP_BUY) > exitProfit){
OrdersCloseAll(OP_BUY);
}
if (OrdersCount(OP_SELL) > 0 && OrdersProfitTotal(OP_SELL) > exitProfit){
OrdersCloseAll(OP_SELL);
}
}
// 〇秒毎に行う処理を記述する
void OnTimer(){
if(AccountNumber() != ACCOUNT_NUMBER && ACCOUNT_NUMBER != 0){
Alert("Auth falied.\n許可されていない口座番号です。");
ExpertRemove();
}
EventKillTimer();
}
// イベント処理を記述する
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam){
if (id == CHARTEVENT_OBJECT_CLICK){
if (sparam == "BUTTON OBJECT"){
if (rdersCount(OP_BUY) + OrdersCount(OP_SELL) > 0){
OrdersCloseAll(OP_BUY);
OrdersCloseAll(OP_SELL);
}
Sleep(200);
ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
}
}
}
// 成行買い注文
int OrderSendBuy(){
if (maxSpreadPips < MarketInfo(Symbol(), MODE_SPREAD) / 10) return -1;
int ticket = -1;
double sl = 0, tp = 0;
if (slPips != 0) sl = Ask - slPips * Pips();
if (tpPips != 0) tp = Ask + tpPips * Pips();
for (int retryCnt = 0; retryCnt < retry; retryCnt++){
ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, slippage, sl, tp, "", magicNum);
if (ticket != -1) {
break;
}else {
Print("buy order send error " + (string)GetLastError());
Sleep(interval);
RefreshRates();
}
}
return ticket;
}
// 成行売り注文
int OrderSendSell(){
if (maxSpreadPips < MarketInfo(Symbol(), MODE_SPREAD) / 10) return -1;
int ticket = 0;
double sl = 0, tp = 0;
if (slPips != 0) sl = Bid + slPips * Pips();
if (tpPips != 0) tp = Bid - tpPips * Pips();
for (int retryCnt = 0; retryCnt < retry; retryCnt++){
ticket = OrderSend(Symbol(), OP_SELL, lots, Bid, slippage, sl, tp, "", magicNum);
if (ticket != -1) {
break;
}else {
Print("sell order send error " + (string)GetLastError());
Sleep(interval);
RefreshRates();
}
}
return ticket;
}
// 合計収支を取得する
double OrdersProfitTotal(int orderType){
double profit = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--){
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol() || OrderMagicNumber() != magicNum || OrderType() != orderType) continue;
profit += (OrderProfit() + OrderSwap() + OrderCommission());
}
return profit;
}
// 保有しているポジション数を返す
int OrdersCount(int orderType){
int cnt = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--){
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol() || OrderMagicNumber() != magicNum || OrderType() != orderType) continue;
cnt++;
}
return cnt;
}
// 全決済
void OrdersCloseAll(int orderType){
for(int i = OrdersTotal() - 1; i >= 0; i--){
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderSymbol() != Symbol() || OrderMagicNumber() != magicNum || OrderType() != orderType) continue;
for (int retryCnt = 0; retryCnt < retry; retryCnt++){
bool closed = OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 0);
if (closed){
break;
}else{
Print("order close error " + (string)GetLastError());
Sleep(interval);
RefreshRates();
}
}
}
}
// 1Pips辺りの価格を返す関数
double Pips(){
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
if (StringFind(Symbol(), "XAUUSD", 0) != -1 || StringFind(Symbol(), "GOLD", -1) != -1){
return NormalizeDouble(Point * 10, digits - 1);
}
if(digits == 3 || digits == 5){
return NormalizeDouble(Point * 10, digits - 1);
}
if(digits == 4 || digits == 2){
return Point;
}
return 0;
}
// ボタンを作成
void CreateButtonObject(string objName, int posX, int posY, int sizeX, int sizeY, string text, color fontColor, int fontSize, color bgColor, color borderColor){
ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0);
ObjectSetString(0, objName, OBJPROP_TEXT, text);
ObjectSetString (0, objName, OBJPROP_FONT, "Meiryo UI");
ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, objName, OBJPROP_COLOR, fontColor);
ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor);
ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor);
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, posX);
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, posY);
ObjectSetInteger(0, objName, OBJPROP_XSIZE, sizeX);
ObjectSetInteger(0, objName, OBJPROP_YSIZE, sizeY);
ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_LOWER);
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
}
それでは追加したコードを解説していきます。
まずは#defineを使用して許可する口座番号と有効期限を記載します。
#define ACCOUNT_NUMBER 123456
#define EXPIRY_DATE "2022.12.31 23:59"
今回はRSIを使用して動作するEAなので、RSIのパラメータが変更できるように以下のパラメータを用意しましょう。
input string rsiMargin = ""; //
input string rsiSetting = "---------------RSI設定---------------"; //▼RSI
input int rsiPeriod = 14; //期間
input ENUM_APPLIED_PRICE rsiPrice = PRICE_CLOSE; //適用価格
input double rsiHighLevel = 70.0; //上レベル
input double rsiLowLevel = 30.0; //下レベル
ちなみに以下の行を入れることでパラメータに空白行を作ることができ、比較的見やすくすることが出来ます。
パラメータの個数が増えたときは活用してみましょう。
input string rsiMargin = ""; //
input string rsiSetting = "---------------RSI設定---------------"; //▼RSI
次に決済に用いるパラメータを定義します。
input string closeMargin = ""; //
input string closeSetting = "---------------決済設定---------------"; //▼決済
input double exitProfit = 1000; //決済収益(円)
OnInit関数内で、緊急全決済用のボタンを作成しましょう。
int OnInit(){
//省略
CreateButtonObject("BUTTON OBJECT", 5, 5, 60, 40, "緊急全決済", clrWhite, 12, clrSalmon, clrNONE);
//省略
return INIT_SUCCEEDED;
}
次にOnTick内です。
void OnTick(){
if (!IsTradeAllowed()) return;
// 毎tickごとに処理を行う
OrderExit();
if (lastTime == iTime(Symbol(), Period(), 0)) return;
lastTime = iTime(Symbol(), Period(), 0);
// 新しい足形成時に一度だけ処理を行う
OrderOpen();
}
ここで最も重要なのは下記の制御です。
if (lastTime == iTime(Symbol(), Period(), 0)) return;
この制御より前は価格の更新ごとに行いたい処理を記載し、この制御より後には足形成(足確定)で一度だけ行う処理を記載します。
今回は注文は足確定で行うのでOrderOpen関数を制御より後に記載し、決済は即座に行うのでOrderExit関数を制御以前に記載します。
OrderOpen関数内では、実際の取引ロジックを記載していきます。
void OrderOpen(){
double rsi = iRSI(Symbol(), Period(), rsiPeriod, rsiPrice, 1);
double lastRsi = iRSI(Symbol(), Period(), rsiPeriod, rsiPrice, 2);
if (OrdersCount(OP_BUY) < maxPosition && lastRsi < rsiHighLevel && rsi >= rsiHighLevel){
int ticket = OrderSendBuy();
if (ticket == -1) Sleep(interval);
}
if (OrdersCount(OP_SELL) < maxPosition && lastRsi > rsiLowLevel && rsi <= rsiLowLevel){
int ticket = OrderSendSell();
if (ticket == -1) Sleep(interval);
}
}
ポジション数が5未満かつRSIがレベルにタッチした状態で足確定によるEAを作成するので、OrdersCount関数とiRSI関数を使用してロジックを記述します。
OrdersCount関数の引数は注文タイプを指定することが出来るので、OP_BUYと指定した場合は買いポジションの総数を取得します。
成行注文はOrderSendBuy, OrderSendSell関数を使用することで、リトライ機能とスプレッド制御、SL/TPの計算を自動で行います。
OrderExit関数内には、決済のロジックを記載していきます。
void OrderExit(){
if (OrdersCount(OP_BUY) > 0 && OrdersProfitTotal(OP_BUY) > exitProfit){
OrdersCloseAll(OP_BUY);
}
if (OrdersCount(OP_SELL) > 0 && OrdersProfitTotal(OP_SELL) > exitProfit){
OrdersCloseAll(OP_SELL);
}
}
合計収支が設定したパラメータを上回った場合に全決済を行うので、OrdersProfitTotal関数でポジションの総収支を取得し、OrderCloseAll関数でポジションを全て決済します。
OrdersProfitTotal関数とOrderCloseAll関数は、OrdersCount関数同様に引数で注文タイプを指定することが可能です。
最後にOnChartEvent関数内に、イベント処理を記載していきます。
イベント処理とはオブジェクトのクリックや削除などに反応して行われる処理です。
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam){
if (id == CHARTEVENT_OBJECT_CLICK){
if (sparam == "BUTTON OBJECT"){
if (rdersCount(OP_BUY) + OrdersCount(OP_SELL) > 0){
OrdersCloseAll(OP_BUY);
OrdersCloseAll(OP_SELL);
}
Sleep(200);
ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
}
}
}
OnInit関数内で作成したボタンがクリックされたら、即座にEAからの注文を全決済します。
Sleep関数を使用して処理に遅延を発生させることで、ボタンのクリック感を再現することが出来ます。
まとめ
今回は私が普段から使用しているEAのテンプレートを公開しました。簡単に高性能なEAが作成可能であることをお分かりいただけたかと思います。
また、今回サンプルで紹介したEAはGitHubで公開しましたので良ければ参考にしてみてください。
(※あくまでもコード例であり、利益を保証するものではありません。)
このEAから少しずつ変更をしながら学ぶのもよし、ガッツリいじってもっと強いEAを作成するのもよしです。
テンプレートは全てのEAに共通する機能のみになりますので、トレーリングやナンピンなどの複雑な仕組みを搭載したい場合は以下の記事を参考にしてみてください。
このテンプレートを使用してEAを作成することが出来たら、是非Discordサーバーで共有して頂けると嬉しいです。