第4回までで自動売買シミュレーションの一発目として、毎日朝7時に買って夜7時に決済する自動売買プログラムを動かしてきました。今後もっと幅広いインジケータとそれに合わせて自動売買するプログラムを作るために、カスタムインジケータとiCustom関数を利用してそのカスタムインジケータを利用したEAを作ってみます。
カスタムインジケータソースコード全体
カスタムインジケータとして、移動平均線で、短期と長期の2本を表示して、クロスしたときに売りシグナル(デッドクロス)、買いシグナル(ゴールデンクロス)を表示するインジケータをまず作ります。
//+------------------------------------------------------------------+
//| FXind_moving_average.mq4|
//| Copyright (c) 2021, Langtainment |
//| https://tsunagaru-info.com/dr |
//+------------------------------------------------------------------+
#property copyright "Copyright (c) 2021, Langtainment"
#property link "https://tsunagaru-info.com/dr"
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_color1 Blue
#property indicator_color2 Red
#property indicator_color3 Blue
#property indicator_color4 Red
//Unique Parameters
extern int FastMA_Period = 10; //Parameter 1
extern int SlowMA_Period = 40; //Parameter 2
//indicator Buffers
double BufFastMA[];
double BufSlowMA[];
double BufBuy[];
double BufSell[];
//+-----------------------------------------------------------------------------------------------------------------------------+
//| |
//| Onnit() works at the time of EA application, time step change, property change, and MT4 restart, as initialization process. |
//| |
//+-----------------------------------------------------------------------------------------------------------------------------+
int OnInit()
{
//allocate indicator buffer
SetIndexBuffer(0, BufFastMA);
SetIndexBuffer(1, BufSlowMA);
SetIndexBuffer(2, BufBuy);
SetIndexBuffer(3, BufSell);
//Setting Indicator Label
SetIndexLabel(0,"FastMA("+FastMA_Period+")");
SetIndexLabel(1,"SlowMA("+SlowMA_Period+")");
SetIndexLabel(2,"BuySignal");
SetIndexLabel(3,"SellSignal");
//Setting Indicator Style (Buy Signal)
SetIndexStyle(2, DRAW_ARROW, STYLE_SOLID, 1, Blue);
SetIndexArrow(2,233);
//Setting indicator style (Sell signal)
SetIndexStyle(3, DRAW_ARROW, STYLE_SOLID, 1, Red);
SetIndexArrow(3,234);
return(0);
}
//+--------------------------------------------------------------------------------------------------------------------------------------+
//| |
//| OnCalculate() works at the timing when the rate of the currency pair on the chart you applied is delivered, as the main processing. |
//| |
//+--------------------------------------------------------------------------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
int i; //counter
int end_index = Bars - prev_calculated; // Get bar count (uncalculated)
if (prev_calculated == 0) end_index -= SlowMA_Period-1;
for(i = end_index -1 ; i >=0 ; i-- ) {
//Fast Moving Average
BufFastMA[i] = iMA(
NULL, // Currency Pair
0, // time axis
FastMA_Period, // MA average period
0, // MA Shift
MODE_SMMA, // MA averaging method
PRICE_CLOSE, // Applicable price
i // Shift
);
//Slow Moving Average
BufSlowMA[i] = iMA(
NULL, // Currency Pair
0, // time axis
SlowMA_Period, // MA average period
0, // MA Shift
MODE_SMMA, // MA averaging method
PRICE_CLOSE, // Applicable price
i // Shift
);
}
if (prev_calculated == 0) end_index -= 2;
for(i = end_index -1 ; i >=0 ; i-- )
{
//Buy Signal
BufBuy[i] = EMPTY_VALUE;
if(BufFastMA[i+2] <= BufSlowMA[i+2] && BufFastMA[i+1] > BufSlowMA[i+1])
{
BufBuy[i] = Open[i];
}
//Sell Signal
BufSell[i] = EMPTY_VALUE;
if(BufFastMA[i+2] >= BufSlowMA[i+2] && BufFastMA[i+1] < BufSlowMA[i+1])
{
BufSell[i] = Open[i];
}
}
//Return value setting: passed to the value of prev_calculated the next time the OnCalculate() is called.
return( rates_total );
}
ソースコード説明
宣言部分
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_color1 Blue ・・①
#property indicator_color2 Red ・・②
#property indicator_color3 Blue ・・③
#property indicator_color4 Red ・・④
//Unique Parameters
extern int FastMA_Period = 10; //Parameter 1
extern int SlowMA_Period = 40; //Parameter 2
//indicator Buffers
double BufFastMA[]; ・・①
double BufSlowMA[]; ・・②
double BufBuy[]; ・・③
double BufSell[]; ・・④
宣言部分では、4つのインジケータの設定を行います。
- 短期移動平均線
- 長期移動平均線
- 買いシグナル
- 売りシグナル
#property indicator_color1 Blue あたりで、インジケータの色を設定し、double BufFastMA[];あたりで、インジケータ用のバッファを確保しています。
FastMA_Period、SlowMA_Periodは、バー何本分の移動平均を出すかで、extern宣言で、外部から設定できるようにパラメータ化をしています。iCustom関数を利用するときに把握しておきたいポイントは、1番目のパラメータがFastMA_Period、2番目のパラメータがSlowMA_Periodとなります。
初期化部分
int OnInit()
{
//allocate indicator buffer
SetIndexBuffer(0, BufFastMA);
SetIndexBuffer(1, BufSlowMA);
SetIndexBuffer(2, BufBuy);
SetIndexBuffer(3, BufSell);
//Setting Indicator Label
SetIndexLabel(0,"FastMA("+FastMA_Period+")");
SetIndexLabel(1,"SlowMA("+SlowMA_Period+")");
SetIndexLabel(2,"BuySignal");
SetIndexLabel(3,"SellSignal");
//Setting Indicator Style (Buy Signal)
SetIndexStyle(2, DRAW_ARROW, STYLE_SOLID, 1, Blue);
SetIndexArrow(2,233);
//Setting indicator style (Sell signal)
SetIndexStyle(3, DRAW_ARROW, STYLE_SOLID, 1, Red);
SetIndexArrow(3,234);
return(0);
}
Oninit()は、前回init()と記載をしていました。MQL4ではどちらでも同じみたいですが、init()はMQL5に将来移植した際に認識されなくなります。そのため、将来MQL5に移植する際を見据えて今後は、Oninit()を使うことにしました。初期化時に1回動く関数です。
ここでは宣言している4つのインジケータを設定しています。SetIndexBufferで
- 0:短期移動平均線
- 1:長期移動平均線
- 2:買いシグナル
- 3:売りシグナル
と、インデックスを振っています。以下の関数の第一引数はこのインデックスを指定しています。
最後のSetting indicator styleは、矢印マークを表示するための設定です。233,234という数字は何かですがこれはWingdingsフォントの文字コード番号になります。番号と文字の対応表は以下のサイトが見やすかったので紹介しておきます。
メイン部分1
int OnCalculate(・・・省略・・・)
{
int i; //counter
int end_index = Bars - prev_calculated; // Get bar count (uncalculated)
if (prev_calculated == 0) end_index -= SlowMA_Period-1;
for(i = end_index -1 ; i >=0 ; i-- ) {
//Fast Moving Average
BufFastMA[i] = iMA(
NULL, // Currency Pair
0, // time axis
FastMA_Period, // MA average period
0, // MA Shift
MODE_SMMA, // MA averaging method
PRICE_CLOSE, // Applicable price
i // Shift
);
//Slow Moving Average
BufSlowMA[i] = iMA(
NULL, // Currency Pair
0, // time axis
SlowMA_Period, // MA average period
0, // MA Shift
MODE_SMMA, // MA averaging method
PRICE_CLOSE, // Applicable price
i // Shift
);
}
・・・省略・・・
}
OnCalculate()は、前回でいうstart()関数と同じで、tick毎に実行される関数です。こちらも今後の移植性を踏まえてOncalculate()を使用することにします。EAの場合は、同様のタイミングで実行される関数はOntick()と書きます。
本関数の前半は、iMA関数より移動平均の値を取得して、短期移動平均線と長期移動平均線の値をバッファに格納しています。メインはfor文の中ですが、その前の数行では何をやっているのでしょうか。
例えば20日移動平均線を計算する場合は過去20日分のデータが必要になります。そのため、データのある一番古いデータから19番目に古いデータまでは、20日分のデータが足りないので移動平均線の値はおかしくなります。そのため、そのような部分は移動平均線のデータは計算しないことにしています。
メイン部分2
int OnCalculate(・・・省略・・・)
{
・・・省略・・・
if (prev_calculated == 0) end_index -= 2;
for(i = end_index -1 ; i >=0 ; i-- )
{
//Buy Signal
BufBuy[i] = EMPTY_VALUE;
if(BufFastMA[i+2] <= BufSlowMA[i+2] && BufFastMA[i+1] > BufSlowMA[i+1])
{
BufBuy[i] = Open[i];
}
//Sell Signal
BufSell[i] = EMPTY_VALUE;
if(BufFastMA[i+2] >= BufSlowMA[i+2] && BufFastMA[i+1] < BufSlowMA[i+1])
{
BufSell[i] = Open[i];
}
}
//Return value setting: passed to the value of prev_calculated the next time the OnCalculate() is called.
return( rates_total );
}
エントリー時刻と決済時刻を最適化するとどうなるかを見てみます。
後半部分は、移動平均線がクロスした際に買いシグナル、売りシグナルをチャート上に表示する部分です。クロスするというのは、「ひとつ前は短期(長期)より長期(短期)のほうが値が大きかったけれど、今回は短期(長期)のほうが値が大きくなった」と表現できるため、そのように条件文を記載しています。
インジケータ表示内容
表示した結果が以下です。2本の移動平均線とクロスした際に売買シグナルが矢印で表示されています。
インジケータをEA化
このインジケータを利用したEAが以下となります。利用といっても単に移動平均線を利用しているだけで、上述のインジケータを流用する必要は正直ありません。インジケータとEAをはっきり連動したいのでインジケータからEAへのインプットを渡すというのがしたいので今回このコードを書いてみました。
移動平均線を利用したEAソースコード全体
//+------------------------------------------------------------------+
//| FXexp_moving_average.mq4|
//| Copyright (c) 2021, Langtainment |
//| https://tsunagaru-info.com/dr |
//+------------------------------------------------------------------+
#property copyright "Copyright (c) 2021, Langtainment"
#property link "https://tsunagaru-info.com/dr"
#include <stdlib.mqh>
//Magic Number
#define MAGIC_NUMBER 20211016
//SIGNAL
#define ENTRY_BUY_SIGNAL 1
#define ENTRY_SELL_SIGNAL 2
#define CLOSE_BUY_SIGNAL 4
#define CLOSE_SELL_SIGNAL 8
//
#define REFERENCE_INDICATOR "original\FXind_moving_average"
//Common Parameters
extern double Lots = 1;
extern int Slippage = 3;
//Unique Parameters
extern int FastMA_Period = 10;
extern int SlowMA_Period = 40;
double FastMA2 = EMPTY_VALUE;
double FastMA1 = EMPTY_VALUE;
double SlowMA2 = EMPTY_VALUE;
double SlowMA1 = EMPTY_VALUE;
int ticket = 0;
int errorcode = 0;
int Adjusted_Slippage = 0;
datetime Bar_Time = 0;
bool closed = false;
//+------------------------------------------------------------------+
//| Adjust Slippage according to Number of digits after the decimal point |
//+------------------------------------------------------------------+
int AdjustSlippage(string Currency,int Slippage_Pips)
{
int Symbol_Digits = MarketInfo(Currency,MODE_DIGITS);
if(Symbol_Digits == 2 || Symbol_Digits == 4)
{
int Calculated_Slippage = Slippage_Pips;
}
else if(Symbol_Digits == 3 || Symbol_Digits == 5)
{
Calculated_Slippage = Slippage_Pips * 10;
}
else
{
//unreachable
}
return(Calculated_Slippage);
}
//+-----------------------------------------------------------------------------------------------------------------------------+
//| |
//| Oninit() works at the time of EA application, time step change, property change, and MT4 restart, as initialization process. |
//| |
//+-----------------------------------------------------------------------------------------------------------------------------+
int Oninit()
{
Adjusted_Slippage = AdjustSlippage(Symbol(),Slippage);
return(0);
}
//+------------------------------------------------------------------+
//| Calculate Current Orders |
//+------------------------------------------------------------------+
int CalculateCurrentOrders()
{
//The number of Current Orders(+:BUY -:SELL)
int pos=0;
for(int i=0; i<OrdersTotal(); i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) == false) break;
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MAGIC_NUMBER)
{
if(OrderType() == OP_BUY) pos++;
if(OrderType() == OP_SELL) pos--;
}
}
return(pos);
}
//+------------------------------------------------------------------+
//| SIGNAL |
//+------------------------------------------------------------------+
int AnalyzeSignal()
{
int flags = 0;
FastMA2 = iCustom(NULL,0,REFERENCE_INDICATOR,FastMA_Period,SlowMA_Period,0,2); //for FastMA_Period
SlowMA2 = iCustom(NULL,0,REFERENCE_INDICATOR,FastMA_Period,SlowMA_Period,1,2); //for SlowMA_Period
FastMA1 = iCustom(NULL,0,REFERENCE_INDICATOR,FastMA_Period,SlowMA_Period,0,1); //for FastMA_Period
SlowMA1 = iCustom(NULL,0,REFERENCE_INDICATOR,FastMA_Period,SlowMA_Period,1,1); //for SlowMA_Period
if(FastMA2 == EMPTY_VALUE || SlowMA2 == EMPTY_VALUE || FastMA1 == EMPTY_VALUE || SlowMA1 == EMPTY_VALUE)
{
return flags;
}
if(FastMA2 >= SlowMA2 && FastMA1 < SlowMA1)
{
flags = CLOSE_BUY_SIGNAL|ENTRY_SELL_SIGNAL;
}
else if(FastMA2 <= SlowMA2 && FastMA1 > SlowMA1)
{
flags = CLOSE_SELL_SIGNAL|ENTRY_BUY_SIGNAL;
}
else
{
//no operation
}
return flags;
}
//+------------------------------------------------------------------+
//| Close Positions |
//+------------------------------------------------------------------+
void ClosePositions()
{
for(int i=0; i<OrdersTotal(); i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) == false) break;
if(OrderMagicNumber() != MAGIC_NUMBER || OrderSymbol() != Symbol()) continue;
//Check Order type
if(OrderType() == OP_BUY)
{
closed = OrderClose(OrderTicket(),OrderLots(),Bid,Adjusted_Slippage,White);
if(closed==False)
{
errorcode=GetLastError();
printf("OrderClose Error! error_code:%d , detail:%s ",errorcode,ErrorDescription(errorcode));
}
break;
}
else if(OrderType() == OP_SELL)
{
closed = OrderClose(OrderTicket(),OrderLots(),Ask,Adjusted_Slippage,White);
if(closed==False)
{
errorcode=GetLastError();
printf("OrderClose Error! error_code:%d , detail:%s ",errorcode,ErrorDescription(errorcode));
}
break;
}
else
{
//unreachable
}
}
}
//+--------------------------------------------------------------------------------------------------------------------------------------+
//| |
//| OnTick() works at the timing when the rate of the currency pair on the chart you applied is delivered, as the main processing. |
//| |
//+--------------------------------------------------------------------------------------------------------------------------------------+
void OnTick(){
//Check if allowable trade at opening-price
if(Volume[0] > 1 || IsTradeAllowed() == false)
{
return;
}
//Calculate Current Orders
int pos = CalculateCurrentOrders();
//Check signal
int signal = AnalyzeSignal();
//BUY signal
if(pos <= 0 && ((signal & ENTRY_BUY_SIGNAL) == ENTRY_BUY_SIGNAL))
{
ClosePositions();
ticket = OrderSend(Symbol(),OP_BUY,Lots,Ask,Adjusted_Slippage,0,0,"",MAGIC_NUMBER,0,Blue);
if(ticket < 0){
errorcode=GetLastError();
printf("Send Error! error_code:%d , detail:%s ",errorcode,ErrorDescription(errorcode));
}
} //SELL signal else if(pos >= 0 && ((signal & ENTRY_SELL_SIGNAL) == ENTRY_SELL_SIGNAL))
{
ClosePositions();
ticket = OrderSend(Symbol(),OP_SELL,Lots,Bid,Adjusted_Slippage,0,0,"",MAGIC_NUMBER,0,Red);
if(ticket < 0){
errorcode=GetLastError();
printf("Send Error! error_code:%d , detail:%s ",errorcode,ErrorDescription(errorcode));
}
} //Close BUY position else if(pos > 0 && ((signal & CLOSE_BUY_SIGNAL) == CLOSE_BUY_SIGNAL))
{
ClosePositions();
}
//Close SELL position
else if(pos < 0 && ((signal & CLOSE_SELL_SIGNAL) == CLOSE_SELL_SIGNAL))
{
ClosePositions();
}
else
{
//no operation
}
return;
}
第2回で毎日定時に自動売買するEAのコードの紹介をしていますので、そのコードからの変更点を主に説明します。
全体的な話としては、冒頭で述べたように、関数名を変えました。
- init() → Oninit()
- start() → Ontick()
シグナル計算部分
//SIGNAL //再掲
#define ENTRY_BUY_SIGNAL 1 //再掲
#define ENTRY_SELL_SIGNAL 2 //再掲
#define CLOSE_BUY_SIGNAL 4 //再掲
#define CLOSE_SELL_SIGNAL 8 //再掲
int AnalyzeSignal()
{
int flags = 0;
FastMA2 = iCustom(NULL,0,REFERENCE_INDICATOR,FastMA_Period,SlowMA_Period,0,2); //for FastMA_Period
SlowMA2 = iCustom(NULL,0,REFERENCE_INDICATOR,FastMA_Period,SlowMA_Period,1,2); //for SlowMA_Period
FastMA1 = iCustom(NULL,0,REFERENCE_INDICATOR,FastMA_Period,SlowMA_Period,0,1); //for FastMA_Period
SlowMA1 = iCustom(NULL,0,REFERENCE_INDICATOR,FastMA_Period,SlowMA_Period,1,1); //for SlowMA_Period
if(FastMA2 == EMPTY_VALUE || SlowMA2 == EMPTY_VALUE || FastMA1 == EMPTY_VALUE || SlowMA1 == EMPTY_VALUE)
{
return flags;
}
if(FastMA2 >= SlowMA2 && FastMA1 < SlowMA1)
{
flags = CLOSE_BUY_SIGNAL|ENTRY_SELL_SIGNAL;
}
else if(FastMA2 <= SlowMA2 && FastMA1 > SlowMA1)
{
flags = CLOSE_SELL_SIGNAL|ENTRY_BUY_SIGNAL;
}
else
{
//no operation
}
return flags;
}
シグナル生成部分については、大きく変わっています。以前のコードは既定の時間になったら売買シグナルを出すようにしていた部分ですが、ここを短期移動平均線と長期移動平均線がクロスする場合にシグナルが出るように書き換えました。
ここで、iCustom関数を使用して、先ほど作成したインジケータからデータを取得します。iCustomを使っている関数の1つ目の行を詳しくコメントを追加しておきます。
#define REFERENCE_INDICATOR "original\FXind_moving_average" // ヘッダに記載しているものの再掲
FastMA2 = iCustom(NULL, //通貨ペア、適用チャートに準ずる場合はNULLとする
0, //時間軸、適用チャートに準ずる場合は0とする
REFERENCE_INDICATOR, //参照するインジケータの名称
FastMA_Period, // 参照するインジケータの1番目のパラメータ
SlowMA_Period, // 参照するインジケータの2番目のパラメータ
0, // 参照するインジケータの0番目のライン
2); // 現在バーを基準にして、指定した時間軸のバー数分を過去方向へシフト
//for FastMA_Period
参照するインジケータの名称はMQL4\indicator\以下のパス名(拡張子は除く)を指定します。
「MQL4\indicator\test.ex4」であれば、”test”とし、「MQL4\indicator\sub\test.ex4」であれば、”sub\test”とします。
if(FastMA2 >= SlowMA2 && FastMA1 < SlowMA1)
{
flags = CLOSE_BUY_SIGNAL|ENTRY_SELL_SIGNAL;
}
この部分についてですが、flagsは買いエントリー、売りエントリー、買い決済、売り決済のフラグを同じ変数の各ビットで分けていてひとまとめの変数にしています。
ENTRY_BUY_SIGNALは1ビット目、ENTRY_SELL_SIGNALは2ビット目、・・といった具合です。
これは2つ前の移動平均線では短期が長期より上にあったが、1つ前の移動平均線では長期が短期の上になった場合に、CLOSE_BUY_SIGNAL(買いエントリーの決済シグナル)及びENTRY_SELL_SIGNAL(売りエントリーシグナル)を同時にONにします。
メイン部分
void OnTick(){
//Check if allowable trade at opening-price
if(Volume[0] > 1 || IsTradeAllowed() == false)
{
return;
}
//Calculate Current Orders
int pos = CalculateCurrentOrders();
//Check signal
int signal = AnalyzeSignal();
//BUY signal
if(pos <= 0 && ((signal & ENTRY_BUY_SIGNAL) == ENTRY_BUY_SIGNAL))
{
ClosePositions();
ticket = OrderSend(Symbol(),OP_BUY,Lots,Ask,Adjusted_Slippage,0,0,"",MAGIC_NUMBER,0,Blue);
if(ticket < 0){ errorcode=GetLastError(); printf("Send Error! error_code:%d , detail:%s ",errorcode,ErrorDescription(errorcode)); } } //SELL signal else if(pos >= 0 && ((signal & ENTRY_SELL_SIGNAL) == ENTRY_SELL_SIGNAL))
{
ClosePositions();
ticket = OrderSend(Symbol(),OP_SELL,Lots,Bid,Adjusted_Slippage,0,0,"",MAGIC_NUMBER,0,Red);
if(ticket < 0){ errorcode=GetLastError(); printf("Send Error! error_code:%d , detail:%s ",errorcode,ErrorDescription(errorcode)); } } //Close BUY position else if(pos > 0 && ((signal & CLOSE_BUY_SIGNAL) == CLOSE_BUY_SIGNAL))
{
ClosePositions();
}
//Close SELL position
else if(pos < 0 && ((signal & CLOSE_SELL_SIGNAL) == CLOSE_SELL_SIGNAL))
{
ClosePositions();
}
else
{
//no operation
}
return;
}
ここは、基本的に前回から変わってないですが、flagsを複数のフラグをまとめた都合で条件文が少し変わっています。
(signal & ENTRY_BUY_SIGNAL) == ENTRY_BUY_SIGNAL
というのは、signalの買いシグナル部分だけを取り出して、買いシグナル部分と一致しているかどうか?という条件文になります。
これで、EAも完成したので次回は動かした結果を報告します。
(第6回に続く)
コメント