今回は学習用に一般的なインジケータを2つ使ってEAを作成してみます(完全に自分用です)
ちなみに主観で数値を変えれるインジケータは信用していません。
作成前の準備
定義付けをしていきます
⑴順張りか逆張りか
・順張りで
⑵エントリー基準
・BB(ボリンジャーバンド)のσ2、ミドルバンドの期間20を使用
・一目均衡表(遅行線のみ)デフォルト値を使用
・遅行線がBB+σ2を超えたらロングエントリー
・遅行線がBB-σ2を超えたらショートエントリー
⑶クローズ基準
・遅行線がσ2内に潜ったら決済
⑷リミットとストップ
・なし
⑸ポジション情報
・0.1ロット(10000通貨)でエントリー
・複数ポジションを持たない
・増玉はしない
⑹エントリー・クローズ判定のタイミングは?
・ローソク足の終値が確定した瞬間
セーフティをセットしておく
リアル口座でEAが作動しないように、デモ口座でのみ作動するようセーフティを追加しておく。
int OnInit()
{
if ( IsDemo() == false ) { //デモ口座の場合
Print ("※デモ口座でのみ作動します")
return INIT_FAILED; //処理終了
}
return( INIT_SUCCEEDED ); // 戻り値:初期化成功
}
関数IsDemoでEAがデモアカウントで実行されているかチェック。
デモアカウントの場合戻り値はtrueを返すので、
falseになる=デモアカウント以外で実行された→処理終了
INIT_FAILED=初期化失敗の為EAの継続不可能
処理タイミングを作成
⑹で定義した、ローソク足の終値が確定した瞬間の処理を追加。
ローソク足確定時にだけ処理する関数を作成します。
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
void TaskPeriod() {
static datetime lasttime; //最後に記録した時間軸時間
datetime current_time = iTime( Symbol(), Period(), 0 );
//現在の時間軸時間を取得
if ( lasttime == current_time ) { //時間に変化がない場合
return; //処理終了
}
lasttime = current_time; //最後に記録した時間軸の時間を保存
printf( "[%d]ローソク足確定%s", __LINE__, TimeToStr(Time[0] ) );
}
static修飾子が付いているlasttimeは、ファイルロード時に静的領域へ確保される。ロード時に一度だけ行われ、アンロードされるまで保持される。
それに対してcurrent_timeはスタック領域へ確保される。
current_timeには関数iTimeによって表示チャートの時間軸、表示通貨ペア、最新のバーが格納されている。
つまり時間に変化があった場合、そのcurrent_timeを最後に記録した時間軸時間として書き換えていく動作が行われている。
Ontick() 関数イベント処理に追加。
1時間足チャートにロードしてログを確認してみると、現在チャートの更新時間毎に取得出来ている。
datetime time = 0;
if(time !=Time[0]) {
time = Time[0];
}
timeとTime[0](最新ローソク足のオープン時間)を取得したとき、等しくなければ代入。
多分こういう書き方でも問題ない?かと。
Time[ ] =チャートの各バーのオープン時間が含まれている時系列配列。
最新のバーのインデックスは[0]になり、古いバーは[Bars-1]になる。
★エントリー判定処理を作成
⑵エントリー基準のエントリー判定だけ追加します。
・BB(ボリンジャーバンド)のσ2、ミドルバンドの期間20を使用
・一目均衡表(遅行線のみ)デフォルト値を使用
・遅行線がBB+σ2を超えたらロングエントリー
・遅行線がBB-σ2を超えたらショートエントリー
画像で見るとこの↓の瞬間にロングの判定が満たされる。
やることは、
インジケータ情報を取得し、このAの位置関係を不等号を使ってロングエントリーの判定をします。
bool Bands_IchimokuCrossJudge () {
bool ret = false;
double right_ichimoku_rate;//確定した遅行線
double right_bands_upper_rate;//確定したσ2上ライン
double right_bands_lower_rate;//確定したσ2下ライン
double left_ichimoku_rate; //前回の遅行線
double left_bands_upper_rate;////前回のσ2上ライン
double left_bands_lower_rate;////前回のσ2下ライン
//確定した遅行線を取得
right_ichimoku_rate = iIchimoku( NULL,
PERIOD_CURRENT,
9,
26,
52,
MODE_CHIKOUSPAN,
26
);
//確定したσ2上ラインを取得
right_bands_upper_rate = iBands(
NULL,
PERIOD_CURRENT,
20,
2,
0,
PRICE_CLOSE,
MODE_UPPER,
26
);
//確定したσ2下ラインを取得
right_bands_lower_rate = iBands(
NULL,
PERIOD_CURRENT,
20,
2,
0,
PRICE_CLOSE,
MODE_LOWER,
26
);
//前回の遅行線を取得
left_ichimoku_rate = iIchimoku( NULL,
PERIOD_CURRENT,
9,
26,
52,
MODE_CHIKOUSPAN,
27
);
//前回のσ2上ラインを取得
left_bands_upper_rate = iBands(
NULL,
PERID_CURRENT,
20,
2,
0,
PRICE_CLOSE,
MODE_UPPER,
27
);
//確定したσ2下ラインを取得
left_bands_lower_rate = iBands(
NULL,
PERIOD_CURRENT,
20,
2,
0,
PRICE_CLOSE,
MODE_LOWER,
27
);
//遅行線がσ2ラインを上抜け
if ( right_ichimoku_rate > right_bands_upper_rate
&& left_ichimoku_rate <= left_bands_upper_rate )
{
ret = true;
}
if ( ret == true ) {
}
return ret;
}
シフトのインデックスが26とぶっ飛んでますが、これは一目均衡表の遅行スパンは最新のローソク足から26本オフセットした位置に表示されているからです。
なのでボリンジャーバンドも26本オフセットした位置の値と比較しないといけないのでシフト26で合わせてあげます。
次に下抜けエントリー判定を追加していきます。
これも画像で設計したものをそのまま落とし込みます。
//遅行線がσ2ラインを下抜け
else if ( right_ichimoku_rate < right_bands_lower_rate
&& left_ichimoku_rate >= left_bands_lower_rate )
{
ret = true;
}
★エントリー判定結果にenum列挙を使用する
上抜けと下抜けの結果での判断が出来ないので、enum列挙を追加します。
ENUM_ICHIMOKU_CROSS Bands_IchimokuCrossJudge () {
ENUM_ICHIMOKU_CROSS ret = MAC_NULL;
~中略~
//遅行線がσ2ラインを上抜け
if ( right_ichimoku_rate > right_bands_upper_rate
&& left_ichimoku_rate <= left_bands_upper_rate )
{
}
//遅行線がσ2ラインを下抜け
else if ( right_ichimoku_rate < right_bands_lower_rate
&& left_ichimoku_rate >= left_bands_lower_rate )
{
}
現在2021/11/05作成している日付をマジックナンバーとして追加します。
#defineで定義したモノを定数に変換します。
念のためにstatic修飾子を付けて外部からアクセスされないようにしておきます。
static double temp_lot = 0.1 //0.1ロット
◆新規エントリー関数を作成
trueならロング、falseならショートという感じにします。
bool EA_EntryOrder (bool in_long) { //true→ロング、false→ショート
bool ret = false; // 戻り値
int order_type = OP_BUY; // 注文タイプ
double order_lot = temp_lot; // ロット
double order_rate = Ask; // オーダープライスレート
if ( in_long == true ) { // Longエントリー
order_type = OP_BUY;
order_rate = Ask;
} else { // Shortエントリー
order_type = OP_SELL;
order_rate = Bid;
}
int ea_ticket_no = -1 ; //チケットNo
ea_ticket_no = OrderSend ( // 新規エントリー注文
Symbol(), // 通貨ペア
order_type, // オーダータイプ
temp_lot, // ロット[0.01単位]
order_rate, // オーダープライスレート
50, // スリップ上限 (int)[分解能 0.1pips]
0, // ストップレート
0, // リミットレート
"遅行スパンとσ2がクロス", // オーダーコメント
);
}
if ( ea_ticket
_no != -1 ) { //オーダー正常完了
ret = true;
}
OrderSendの仮引数である有効期限 , 色 ,は省略しています。
注文成功時に、OrderSendの戻り値はサーバーによって割り当てられたチケット番号、intで返してくれます。
エラーの場合は-1が返ってくるので、注文が受け付けられなかったとき(-1が返ってきた場合)用のエラー情報を出力させるようにします。
エラー詳細を取得する関数ErrorDescriptionを使えるようにする為に、stdlib.mqhを冒頭にインクルードします。
//ライブラリインクルード
#include <stdlib.mqh>
else { //オーダーエラーの場合
int get_error_code = GetLastError (); //エラーコード取得
string error_detail_str = ErrorDescription (get_error_code); //エラー詳細取得
//エラーログ出力
printf ("[%d]エントリーオーダーエラー。エラーコード=%d エラー内容=%s"
, __LINE__, get_error_code, error_detail_str );
}
■エントリーオーダー中間判定処理を追加
★エントリー判定処理 (Bands_IchimokuCrossJudge)
↓ ■中継(中間処理)
◆新規エントリー(EA_EntryOrder)
この本当にエントリーしていいのかを判定する中間処理の部分を追加します。
//+------------------------------------------------------------------+
//| エントリーオーダー判定
//+------------------------------------------------------------------+
void judgeEntry ( ENUM_ICHIMOKU_CROSS in_ichimoku_cross ) {
bool entry_bool = false; // エントリー判定
bool entry_long = false; // ロングエントリー判定
if ( entry_long == MAC_UP_CHANGE ) { // 上抜け
entry_bool = true;
entry_long = true;
} else if ( entry_long == MAC_DOWN_CHANGE ) { //下抜け
entry_bool = true;
entry_long = false;
}
if ( entry_bool == true ) {
EA_EntryOrder (entry_long); //新規エントリー
}
最初に定義した
⑹エントリー・クローズ判定のタイミングは? = JudgeEntry()
→ ローソク足の終値が確定した瞬間 = TaskPeriod(
準備が整ったので、TaskPeriod()に追加します。
ENUM_ICHIMOKU_CROSS temp_cross; //判定結果
temp_cross = Bands_IchimokuCrossJudge(); //遅行線とσ2ラインのクロス判定
JudgeEntry (temp_cross); //エントリーオーダー判定
これで遅行線が上か下にクロスしたら、その判定結果をtemp_crossに。
その判定結果をJudgeEntryの引数に渡して、新規エントリー情報を執行していいかチェックを行い、サーバーへ新規注文を出すという流れが完成しました。
一度動作チェックをしてみます。
条件通りエントリーは行われています。
しかし⑸ポジション情報で定義した増し玉をしない・複数ポジションを持たないという内容に反しているので、増し玉をしないように処理を追加していきます。
ポジション情報を管理する関数を作成(複数エントリー防止)
構造体を使いポジション情報用の静的グローバル変数を用意します。
- チケットNo
- オーダータイプ(方向)
- 損切り価格
- 利食い価格
リミット・ストップの指定はないが、後々設定したいとき追加できるように一応準備しておきます
構造体(struct)は幾つかの異なる型(void)をまとめて1つのデータ型として扱う事ができ、とても便利なので積極的に使っていきます。
配列や文字列を含む構造体を複雑な構造体、そうでないのは単純な構造体といいます。今回は後者です。
構造体のメンバにアクセスするには構造体型変数の後ろにピリオドを付けて、アクセスしたいメンバ名を書きます。
// 構造体型宣言
struct struct_PositionInfo {
int order_dir; //オーダーの方向
double set_limit; //損切り価格
}
static struct_PositionInfo _SetPositionInfoDate;
//ポジション情報構造体データ
//+------------------------------------------------------------------+
//| ポジション情報を取得 (増玉しない為の関数なので、エントリーオーダー判定へ)
//+------------------------------------------------------------------+
bool GetPosiInfo ( struct_PositionInfo &in_st ) {
bool ret = false;
int position_total = OrdersTotal(); // 保有しているポジション数取得
// 全ポジション分ループ
for ( int icount = 0; icount < position_total; icount++ ) {
// インデックス指定でポジションを選択
if ( OrderSelect (icount, SELECT_BY_POS ) == true )
if ( OederMagicNumber() != MAGIC_NO ) { // マジックナンバー不一致判定
continue; // 次のループ処理へ
}
if ( OrderSymbol () != Symbol() ) { // 通貨ペア不一致判定
continue; // 次のループ処理へ
}
in_st.ticket_no = OrderTicket(); // チケット番号を取得
in_st.order_dir = OrderType(); // オーダータイプを取得
in_st.set_limit = OrderTakeProfit(); // リミットを取得
in_st.set_stop = OrderStopLoss(); // ストップを取得
ret = true;
break; // ループ処理中断
}
}
return ret;
}
エントリーオーダー判定へ処理を追加
増玉、複数ポジションを持たない為の関数なので、作成済みのエントリーオーダー判定に処理を追加します。
void JudgeEntry ( ENUM_ICHIMOKU_CROSS in_ichimoku_cross ) {
bool entry_bool = false; // エントリー判定
bool entry_long = false; // ロングエントリー判定
if ( in_ichimoku_cross == MAC_UP_CHANGE ) { // 上抜け
entry_bool = true;
entry_long = true;
} else if ( in_ichimoku_cross == MAC_DOWN_CHANGE ) { //下抜け
entry_bool = true;
entry_long = false;
}
GetPosiInfo (_SetPositionInfoDate ); //ポジション情報を取得
if (_SetPositionInfoDate.ticket_no > 0 ) { //ポジション保有中の場合
entry_bool = false; //エントリー禁止
}
if ( entry_bool == true ) { //新規エントリー
EA_EntryOrder (entry_long);
}
}
複数ポジションを持たなくなったか動作チェックします。
1回エントリーした以降、エントリー条件を満たしていても執行されていません。
クローズ処理が無いので、一度持ったポジションをずっと保有した状態が続いています。
参照渡しについて
その前に自分が理解するまでに時間がかかった参照渡しについて書き残して置きます。
ネット上でどこ調べても、ふわっと分かるが納得いかなくてモヤモヤしてたんですが、
これは、関数内で仮引数の値を変更すると、呼び出し元で指定した実引数を変更する事を意味します。
この1文で腑に落ちました。
小さくて見にくいが、こういう認識で間違ってないはず。
特に構造体と参照渡しが混ざってて中々理解出来ませんでした。
ポジション情報を表示するデバッグ用コメントを作成
デバッグ用なので完成時の消し忘れに注意。
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
void DispDebugInfo ( struct_PositionInfo &in_st ) {
string temp_str = ""; //表示する文字列
temp_str += StringFormat ("チケットNo :%d¥n" , in_st.ticket_no );
temp_str += StringFormat ("オーダータイプ :%d¥n" , in_st.order_dir );
temp_str += StringFormat ("リミット価格 :%s¥n" , DoubleToStr(in_st.set_limit, Digits) );
temp_str += StringFormat ("ストップ価格 :%s¥n" , DoubleToStr(in_st.set_stop, Digits) );
Comment ( temp_str ); //コメント表示
}
Comment()はチャート上の左上に、定義したコメントを表示します。
Comment()は1つの文字列しか表示できないので、複数行表示させるには¥nという改行コードを書きます。
DoubleToStr()は指定した値の小数点以下の桁数分(8桁まで)を文字列で表示します。
表示したい内容と型から判断して下記に当てはめます。
指定子 |
表示する変数 |
%d |
整数 |
%f |
実数 |
%e |
実数(指数表記) |
%s |
文字列 |
▲ポジション決済処理を作成
//+------------------------------------------------------------------+
//| 注文決済
//+------------------------------------------------------------------+
bool EA_Close_Order (int in_ticket) {
bool ret = false; //結果
bool select_bool; //ポジション選択結果
// ポジションを選択
select_bool = OrderSelect (in_ticket, SELECT_BY_TICKET );
// ポジション選択失敗時
if ( select_bool == false ) {
printf ("[%d]不明なチケットNo = %d", __LINE__, in_ticket );
return ret; // 処理終了
}
// ポジションがクローズ済みの場合
if ( OrderCloseTime() != 0 ) {
printf ("[%d]ポジションクローズ済み チケットNo = %d", __LINE__, in_ticket );
return ret; // 処理終了
}
bool close_bool; // 注文結果
int get_order_type; // エントリー方向
double close_rate = 0; // 決済価格
double close_lot = 0; // 決済数量
get_order_type = OrderType (); // 注文タイプ取得
close_lot = OrderLots (); // ロット数
if ( get_order_type == OP_BUY ) { // 買いの場合
close_rate = Bid;
} else if ( get_order_type == OP_SELL ) { // 売りの場合
close_rate = Ask;
return ret; // 処理終了
}
close_bool = OrderClose ( // 決済オーダー
in_ticket, // チケットNo
close_lot, // ロット数
close_rate, // クローズ価格
20, // スリップ上限 (int)[分解能 0.1pips]
clrWhite // 色
);
if (close_bool == false ) { //クローズ失敗
int get_error_code = GetLastError (); // エラーコード取得
string error_detail_str = ErrorDescription ( get_error_code ); // エラー詳細取得
// エラーログ出力
printf ("[%d]オーダーエラー。エラーコード = %d エラー内容 = %s"
, __LINE__, get_error_code, error_detail_str );
} else {
ret = true; // 戻り値設定 クローズ成功
}
return ret; // 戻り値を返す
}
ポジション情報取得で行ったOrderSelectはインデックス指定だったのに対して、
今回チケット指定なのは他関数との依存度を低める為です。
結合度が高い関数は単体で使用できます。
▲クローズ条件の判定を作成
決済の条件は
・遅行線がσ2内に潜ったら決済
でした。
これも整理しやすいよう一応図で表します。
とはいってもエントリー判定でやった事を似た要領でやるだけです。
enum列挙にクローズ判定用の定数を追加。
MAC_DOWN_CHANGE, //下抜けでショート
MAC_CLOSE_DOWN, //ロング保有中下抜けでクローズ
MAC_CLOSE_UP //ショート保有中上抜けでクローズ
};
//+------------------------------------------------------------------+
//| 遅行線のσ2クローズクロス判定
//+------------------------------------------------------------------+
ENUM_ICHIMOKU_CROSS Bands_Close_IchimokuCrossJudge () {
ENUM_ICHIMOKU_CROSS ret = MAC_NULL;
~中略~
//遅行線がσ2ラインを下抜け
if ( right_ichimoku_rate < right_bands_upper_rate
&& left_ichimoku_rate > left_bands_upper_rate )
{
}
//遅行線がσ2ラインを下抜け
else if ( right_ichimoku_rate > right_bands_lower_rate
&& left_ichimoku_rate < left_bands_lower_rate )
{
}
return ret;
}
▲ポジションの決済判定処理を作成
//+------------------------------------------------------------------+
//| 決済オーダー判定
//+------------------------------------------------------------------+
void JudgeClose ( ENUM_ICHIMOKU_CROSS in_ichimoku_cross ) {
bool close_bool = false; //決済判定
if ( _SetPositionInfoDate.ticket_no > 0 ) { //ポジション保有中の場合
if (_SetPositionInfoDate.order_dir == OP_BUY ) { //買いポジ保有中で
if ( in_ichimoku_cross == MAC_CLOSE_DOWN ) { //遅行線が下抜け
close_bool = true;
}
} else if ( _SetPositionInfoDate.order_dir == OP_SELL ) { //売りポジ保有中で
if ( in_ichimoku_cross == MAC_CLOSE_UP ) { //遅行線が上抜け
close_bool = true;
}
}
}
if (close_bool == true ) {
EA_Close_Order ( _SetPositionInfoDate.ticket_no ); //決済処理
}
}
作成したJudgeClose()は判定タイミング処理のTaskPeriod()に追加。
// ----- 処理はこれ以降に追加 -----------
ENUM_ICHIMOKU_CROSS temp_cross; //判定結果
temp_cross = Bands_IchimokuCrossJudge(); //遅行線とσ2のエントリークロス判定
JudgeEntry (temp_cross); //エントリーオーダー判定
temp_cross = Bands_Close_IchimokuCrossJudge(); //遅行線とσ2のクローズクロス判定
JudgeClose (temp_cross); //クローズオーダー判定
決済が行われるか確認します。
売りエントリー後のσ2上抜けで決済されています。
ポジション情報をリセットする
決済後にこのままEAを動かしてエントリー判定を満たしても、新しい注文は執行されません。
その為にポジション情報をクリアします。
今回は正常に決済されたタイミング+現在チャートの足更新時のタイミングでクリアにします。
//+------------------------------------------------------------------+
//| ポジション情報をクリア(決済済みの場合)
//+------------------------------------------------------------------+
void ClearPosiInfo (struct_PositionInfo &in_st ) {
if ( in_st.ticket_no > 0 ) { //ポジション保有中の場合
bool select_bool = false; //ポジション選択結果
//ポジションを選択
select_bool = OrderSelect ( in_st.ticket_no, SELECT_BY_TICKET );
//ポジション選択失敗時
if ( select_bool == false ) {
printf ("[%d]不明なチケットNo = %d" , __LINE__, in_st.ticket_no );
}
//ポジションがクローズ済みの場合
if ( OrderCloseTime () > 0 ) {
ZeroMemory (in_st); //ゼロクリア
}
}
}
関数ZeroMemory()は参照によって渡された変数をクリアにします。
簡単な構造体をクリアするのに使えます。
このポジション情報をクリアにする関数を先ほど決めたタイミングに追加します。
↓ JudgeCloseに追加
if (close_bool == true ) {
bool close_clear = false;
close_clear = EA_Close_Order ( _SetPositionInfoDate.ticket_no ); //決済処理
if ( close_clear == true ) {
ClearPosiInfo (_SetPositionInfoDate); //ポジション情報をクリア
}
}
↓ TaskPeriodに追加
// ----- 処理はこれ以降に追加 -----------
ENUM_ICHIMOKU_CROSS temp_cross; //判定結果
temp_cross = Bands_IchimokuCrossJudge(); //遅行線とσ2のエントリークロス判定
JudgeEntry (temp_cross); //エントリーオーダー判定
temp_cross = Bands_Close_IchimokuCrossJudge(); //遅行線とσ2のクローズクロス判定
JudgeClose (temp_cross); //クローズオーダー判定
ClearPosiInfo (_SetPositionInfoDate); //決済済みの場合情報クリア
最後に
最初に定義した内容は全て書いたと思うので、最後にちゃんと動作するか確認します。
ポジション決済後も続いてエントリー出来るようになっています。
とりあえずはこれで完成です。
追記:クローズエントリー判定の条件を満たせないパターンが発見できたので修正します。
インジケータの値は基本的に最新の値[0]の変動によって1つ前の値[1]が変動します。
このEAではシフト0と2で比較しています。
画像のように赤ラインの瞬間はエントリー条件のσ2を上抜けと判定出来てロングエントリーしてますが、その次には足1個前以上が変動によりσ2内に潜っており下記クローズ条件を満たすことが出来ないまま保有が続いています。
&& left_ichimoku_rate > left_bands_upper_rate )
次のように書き換えました
if ( (right_ichimoku_rate < right_bands_upper_rate
&& left_ichimoku_rate > left_bands_upper_rate)
||( right_ichimoku_rate < right_bands_upper_rate
&& right_ichimoku_rate < left_ichimoku_rate ) ) {
}
//遅行線がσ2ラインを下抜け
else if ( (right_ichimoku_rate > right_bands_lower_rate
&& left_ichimoku_rate < left_bands_lower_rate)
||( right_ichimoku_rate > right_bands_lower_rate
&& right_ichimoku_rate > left_ichimoku_rate) )
{
}
確認して見るとちゃんとクローズされてます。
追記:某MQL4サイトを参考にしてます