MQL4 実践2

ランダムにエントリーする

動作:ローソクが確定したタイミングでのみ動作する。
   乱数を生成し、偶数なら買い、奇数なら売り注文を出す。

int oldBars = 0;
int ticket = 0;

int OnInit(){
   oldBars = Bars;
   MathSrand(GetTickCount());     //Seed値を入れる
   return(0);
}

void OnTick(){
   if(oldBars == Bars)return;
   
   if(MathRand()%2==0)    //生成した乱数を2で割った余りが0(=偶数)なら
      ticket = OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"",1234,0,Red);
   else
      ticket = OrderSend(Symbol(),OP_SELL,0.1,Bid,10,0,0,"",1234,0,Blue);

   oldBars = Bars;
}

乱数を生成するときはシード値を決めてから乱数生成関数を呼び出します。
MQL4の場合はMathSrand()にシード値を入れると、MathRand()を呼び出すたびに0~32767内の整数を1つランダムで返します。完全ランダムにしたい場合、MathSrand()に入れるシード値をGetTickCount()にすることが多いです。GetTickCount()はシステムが起動してからの経過時間を1/1000秒単位で数えている関数です。


指定した時間経過したら決済する

動作:ローソクが確定したタイミングでのみ動作する。
   ポジションがなければ買いエントリーする。
   ポジションがあるなら、約定してから指定秒数を経過後、次の足で決済する。

input int time = 3600;  //秒

int oldBars = 0;
int ticket = 0;

int OnInit(){
   oldBars = Bars;
   return(0);
}

void OnTick(){
   if(oldBars == Bars)return;
   if(OrdersTotal()==0){
      ticket = OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"",1234,0,Red);
      OrderSelect(ticket,SELECT_BY_TICKET);
   }
   else{
      if(TimeCurrent() > OrderOpenTime() + time)   //約定時間+指定秒数を超えたら
         ticket = OrderClose(ticket,0.1,Bid,10,Blue);
   }

   oldBars = Bars;
}

何かが起こってからの時間を計測する方法は主に2つあります。
1つはローソクの本数を数える方法で、もう1つは上のようにシステムの時間を利用する方法です。
TImeCurrent()は現在の時刻(厳密には最後にサーバーと通信した時刻)を返します。
TimeCurrent()やOrderOpenTime()はdatetime型ですが、これにint型の値を加算すると加算した値が秒数として処理されます。
したがって約定から最低3600秒経過後に決済することができます。


指定した本数経過したら決済する
input int waitBars = 3;  //本

int entryBars = 0;
int oldBars = 0;
int ticket = 0;

int OnInit(){
   oldBars = Bars;
   return(0);
}

void OnTick(){
   if(oldBars == Bars)return;
   if(OrdersTotal()==0){
      ticket = OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"",1234,0,Red);
      entryBars = Bars;   //約定時のローソクの数を記憶
   }
   else{
      if(Bars > entryBars + waitBars)   //約定時のローソクの数+指定数を超えたら
         ticket = OrderClose(ticket,0.1,Bid,10,Blue);
   }

   oldBars = Bars;
}

今回はBarsを使ってローソクの本数を数えています。
約定時のBarsの数を記憶しておき、そこに任意の数を足した数をBarsが超えたら決済という仕組みです。
ローソクの本数を数える方法は他にもたくさんあるのでお好みのやり方でよいと思います。


MQL4 実践1

1度だけ買い注文を入れる

動作:EA起動直後に0.1ロット買う

#property strict    //以降この文は省略します

int OnInit()
{
   int ticket = OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"",1234,0,Red);
   return(0);
}

通常OnInit()の戻り値はINIT_SUCCEEDEDですが0を返しても意味は同じです。
赤矢印の位置がローソクに対して若干に上にずれているのは、チャートがBid表示なのに対して矢印はAsk表示のためです。



ティックごとに買い注文を入れる

動作:ティック毎に0.1ロット買う

void OnTick()
{
   int ticket = OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"",1234,0,Red);
}

先ほどと同じ文でも、OnTick()の中に入れると一つの足の中で大量に発注する動作に変わります。



ローソク足が確定するごとに買い注文を入れる

動作:ローソク足が確定するごとに次の足の始値で0.1ロット買う

int oldBars = 0;

int OnInit()
{
   oldBars = Bars;  //EA起動時、現在の本数をoldBarsに代入
   return(0);
}

void OnTick()
{
   if(oldBars == Bars)return;  //ローソクの本数が前のティックと同じなら処理終了
   int ticket = OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"",1234,0,Red);

   oldBars = Bars;  //現在のローソクの本数に更新
}

注文を分散できました。

ちなみに、私はなんとなくBarsを使用していますが、Barsには欠点が2つあります。
1つはBarsの値は「チャートの最大バー数」を超えることができないという問題です。
「チャートの最大バー数」はツール>オプション>チャートで設定できるので最大にしておきます。
また、EA動作中にチャートを左の方に動かすと、新しい(過去の)バーが生成されるので、ローソクが確定したと誤判断してしまいます。
これを防ぐにはEA動作前に一度Homeキーでチャートを左端まで移動させておく必要があります。
実際に稼働させるEAを作るのであれば、Volume[]を使った方が安全です。

long oldVolume = 0;

void OnTick()
{
   if(Volume[0] < oldVolume){
      oldVolume = 0;
      int ticket = OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"",1234,0,Red);
   }

   oldVolume = Volume[0];
}   

Volume[]はそのローソク内での出来高(取引量)を表します。
Volume[0]は現在のローソク出来高なので、前のティックの出来高より小さい=ローソクが変わったと判断できます。


ポジションを持っていれば決済する

動作:ポジションが0のとき、ローソク確定時に0.1ロット買う
   ポジションが1のとき、ローソク確定時に0.1ロット決済する

int oldBars = 0;
int ticket = 0;

int OnInit()
{
   oldBars = Bars;
   return(0);
}

void OnTick()
{
   if(oldBars == Bars)return;
   if(OrdersTotal()==0)    //ポジションが0個なら
      ticket = OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"",1234,0,Red);
   else
      bool close = OrderClose(ticket,0.1,Bid,10,Blue);

   oldBars = Bars;
}

ポジションを持っているときは売るように場合分けしました。

MQL4 基礎6

EAの構成
#property
#include
#define
外部パラメータ
グローバル変数
OnInit()
OnDeinit()
OnTick()
自作関数1
自作関数2

プログラムの書き方は人それぞれですが、よく見かけるのはこの構成です。
それぞれ簡単に説明していきます。

#property
#property strict

EAに作成者の情報を付加したり、EAに特殊な設定をしたりする部分です。
コンパイル時プログラムを厳密にチェックする設定 #property strict は必ず入れます。
その他は必要になったら書きましょう。

#include
#include <sample1.mqh>

#includeを使うと別のプログラム内の関数を使えるようになります。
includeされたファイルとincludeするファイルの中に同じ名前の関数があった場合、#includeと記述した側の処理が優先されます。
よく使う関数をひとつのファイルにまとめておき、それぞれのEAでファイルを読み込ませることで自作関数1,2....の記述を省略でき、全体のコードが短くなります。
また、あらかじめMT4の標準ライブラリに入っている関数を使いたいときにも記述します。

#define
#define MAGICNUMBER 1234

#defineを使うと数字や文字列に名前を付けることができます。
上は1234という数字にMAGICNUMBERという名前をつけています。
以降MAGICNUMBERと書くと、1234という定数として処理されます。
数字をポンと置くより意味のある文字列にした方が読みやすいので、共同開発するときよくつかわれます。
また大本を1ヶ所変えればすべての定数が置き変わるので、後々修正が容易になります。
使うと便利ですが必須ではありません。

外部パラメータ
input double lots = 0.01;

コンパイル後、ストラテジーテスター上で値を変更できる変数(外部パラメータ)を宣言する部分です。
inputの代わりにexternも使えます。
inputで宣言した変数は以降のプログラムで値の書き換えができませんが、externの場合は値の書き換えが可能です。特に理由がなければinputだけ覚えればよいです。

グローバル変数
int slippage = 30;

プログラム上のどの関数からでもアクセスできる変数を宣言する部分です。
OnTick関数内で使いたいが、EA起動時にOnInit関数内で一度だけ初期化をしたいときなどに使います。
便利なのですが、複数の関数に渡って使用できるという特性上、バグの原因になりやすいです。
また一つ一つの関数が独立しなくなるので一部の機能だけ切り離すのが難しくなることがあります。
使わなくても済むのであれば極力使いたくない変数です。

自作関数

OnInit関数やOnTick関数で呼び出したい関数をここに書きます。
ロット計算をする、エントリー条件を満たしているか調べる、注文エラーが起きたらリトライする、といった処理をすべてOnTick()内に詰め込むと長く読みにくいコードになります。
そこでそれぞれの処理を小分けにして関数化しておき、必要になったら関数名を書いて呼び出すことでコードを短くできます。
また一部の機能を別のEAに移すことが簡単になり、作業効率が上がります。
つくると便利ですが必須ではありません。

MQL4 基礎5

保有ポジションの情報を取得する

大雑把な説明をすると、MQL4にはポジションという概念がありません。
コードを組むときは「まだ約定していない注文」「約定したが決済していない注文」「キャンセルされた未約定注文」「決済済みの注文」という4つの注文を軸に考える必要があります。

トレーディングプール

「まだ約定していない注文」「約定したが決済していない注文」がまとめられている場所をトレーディングプールといいます。
トレーディングプールは直訳で注文がたまる場所という意味です。
トレーディングプール内の注文は注文自体がキャンセルされるか、決済されるとトレーディングプールから削除されます。
OrdersTotal()はトレーディングプール内にあるすべての注文の数を返します。
保有ポジションの数を知りたければOrdersTotal()の戻り値から未約定の待機注文の数を除外する必要があります。

int PositionTotal(){
   int count = 0;
   for(int i = OrdersTotal()-1; i>=0; i--){
      OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
      if(OrderType() == OP_BUY || OrderType() == OP_SELL){
         count++;
      }
   }
   return count;
}


上記のPositonTotal関数は現在の保有ポジションの数を返します。
買いポジションの数が知りたければif文の中をOrderType()==OP_BUYのみにすれば計算できます。

ヒストリープール

トレーディングプールに一度入った注文は、そこから削除されるとヒストリープールに移動します。
ヒストリープールには入出金履歴やスワップ履歴などの口座情報も保存されています。
OrdersHistoryTotal()はヒストリープール内にある注文履歴の合計を返します。
ヒストリープール内の注文の数を知りたい場面は基本的にありませんが、上記のPositionTotal関数のようにヒストリープール内を検索するためのforループをかけるとき、OrdersHistoryTotal()-1 という数字をよく使います。

losses = 0;
for(int i=OrdersHistoryTotal()-1;i>=0;i--) 
   {
   if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
     {
      Print("Error in history!");
      break;   
     }
   if(OrderSymbol() != Symbol() || OrderType()>OP_SELL) continue;
   if(OrderProfit()>0) break;
   if(OrderProfit()<0)
   losses++;
}

Comment ("連続で負けた回数は",losses);


上記は前回の取引から順に過去の損益を調べて、連続で負けた回数を数えるプログラムです。ヒストリープール内の注文のうち、決済したポジションだけを調べたければ

if(OrderType==OP_BUY && OP_SELL){ ... }

のように条件文を作ります。OP_BUY=0 / OP_SELL=1 なので、

if(OrderType<=OP_SELL){ ... }

でも意味は同じです。
上で書いたOrderType()>OP_SELLは逆に決済したポジション以外の注文を指定しています。

OrderSelect関数

OrderSelect関数はトレーディングプールとヒストリープール内のすべての注文の中から、1つを選んでマウスクリックした状態(下画像の青色にした状態)にする関数です。



OrderSelect関数で任意の注文を指定すると、その注文の情報が以下の関数の戻り値として取得できます。これらの関数は()が付いたこの形のまま使います。使用感としては変数に近いです。

【よく使う】

OrderTicket() チケット番号
OrderMagicNumber() マジックナンバー
OrderSymbol() 通貨ペア
OrderOpenPrice() 注文価格
OrderLots() ロット数
OrderType() 注文タイプ
OrderTakeProfit() 利確ライン
OrderStopLoss() 損切りライン
OrderOpenTime() 注文時間


【たまに使う】

OrderCommission() 手数料
OrderClosePrice() 決済価格
OrderCloseTime() 決済時間
OrderComment() 注文についたコメント
OrderProfit() スワップや手数料を除く損益


【あまり使わない】

OrderPrint() 注文に関する情報をログに出力
OrderSwap() スワップ損益
OrderExpiration() 有効期限



OrderSelect関数で注文を指定する方法は2つあります。
ひとつはチケット番号を使用する方法です。

OrderSelect(ticket, SELECT_BY_TICKET);    //ticket=チケット番号


チケット番号を使うとコードはシンプルですが、欠点もあります。
まず、保有ポジションの一部を部分決済した場合、チケット番号が変わってしまうので残ったポジションのチケット番号を探すプログラムが必要になるという点です。
なんらかのトラブルでEAが再起動してしまってチケット番号を保存していた変数が初期化されてしまうケースも考えられます。
過去検証用のプログラムに使うならアリという感じでしょうか。


もうひとつは注文インデックス(番号)を指定する方法です。

OrderSelect(i, SELECT_BY_POS, MODE_TRADE);    //i=番号 ヒストリープールならMODE_HISTORY

トレーディングプールとヒストリープール内の注文には、それぞれプールごとに番号が振られています。
一番古い注文を0とし、最新の注文にはOrdersTotal()-1, またはOrdersHsitoryTotal()-1 の番号がついています。

これらの番号はチケット番号とは違い、注文が追加、削除されるたびに振り直されます。
そのため任意の注文を指定するためには、その注文を特定できるif文を書き、forループでプール内を検索する必要があります。

for(int i = OrdersTotal()-1; i>=0; i--){
   OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
   if( 注文を特定する条件 ) break;
}

注文を見つけた後はbreakを使いループを抜けましょう。

MQL4 基礎4

成行注文する
int ticket = OrderSend(NULL,OP_BUY,1,Ask,20,0,0,"",1234,0,Red);


OrderSend関数

エントリー注文を出すときはOrderSend関数を使います。
注文に成功すると戻り値としてチケット番号を返します。注文に失敗すると-1を返します。
int ticket = の部分は省略可能ですが、注文が成功したかどうかの判断ができなくなります。
OrderSend関数は指値、逆指値注文も可能です。しかし、EAで待機注文を出すことはあまりないので今回は省略します。

OrderSend(通貨ペア,  // "USDJPY" / NULL / Symbol()
     注文タイプ, // OP_BUY(成行買) / OP_SELL(成行売)
     ロット数,  // 1
     注文価格,  // Ask / Bid
     スリッページ, // 20 (=2pips)
     損切価格, // 0 (推奨)
     利確価格, // 0 (推奨)
     コメント, // "EAによる注文"
     マジックナンバー, // 1234
     有効期限,  // 0 (固定)
     色) // Red

EAをセットしたチャートを選択したい場合はNULLまたはSymbol()と書きます。
注文価格はOP_BUYのときAsk / OP_SELLのときBidで固定です。
スリッページとは注文価格と約定価格の許容誤差です。
損切価格と利確価格はOrderModify関数で設定することを推奨します。
マジックナンバーはEAを区別するためにEA製作者が任意でつける番号です。
有効期限は成行注文の場合0で固定です。
色とは約定時にチャートに表示される矢印の色のことです。

チケット番号

MT4では、注文送信時に各注文に対してチケット番号が発行されます。
その後注文が約定してもチケット番号は変わりません。
ただし、その後ポジションを決済するとチケット番号は削除されます。また未約定注文をキャンセルした場合もチケット番号は削除されます。
ポジションの一部を部分決済した場合、チケット番号は一度削除され、残ったポジションに対して新しいチケット番号が割り振られます。

チケット番号を取得する方法は主に2つあります。
ひとつはOrderSend関数の戻り値として返ってくるチケット番号を変数に格納しておく方法です。
OnTick関数が起動するごとに変数が初期化されるのを防ぐにはstaticをつけるか、変数をグローバルエリアで宣言する必要があります。

static int ticket = OrderSend(NULL,OP_BUY,1,Ask,20,0,0,"",1234,0,Red);

 
  
もうひとつはOrderSelect関数で保有ポジションの中からポジションを指定し、OrderTicket関数でチケット番号を取得する方法です。

for(int i=OrdersTotal()-1; i>=0; i--){
   if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break;
   if(OrderMagicNumber() != MagicNumber) continue;
   if(OrderSymbol()  != Symbol()) continue;

 int ticket = OrderTicket();
}

1行目:最新のポジションから一番古いポジションまでループをかけます。
2行目:OrderSelect関数で現在選択しているポジションのチケット番号を取得します。取得に失敗したら処理を終了します。
3行目:マジックナンバーが本EAのものと一致していなければ次のループに移ります。
4行目:通貨ペアが一致していなければ次のループに移ります。
6行目:変数ticketにチケット番号を代入します。

ポジションを決済する
bool closed = OrderClose(ticket, 1, Bid, 30, Blue);

ポジションを決済するときはOrderClose関数を使用します。
OrderClose関数は引数としてチケット番号を求めてきます。
上記の手順でチケット番号を入手しておきましょう。

OrderClose(チケット番号,  ロット数, 決済価格, スリッページ, 色)


保有ポジションの利確、損切りラインを決める
OrderSelect(ticket, SELECT_BY_TICKET,MODE_TRADES);
bool modify = OrderModify(ticket, OrderOpenPrice(), 0, OrderOpenPrice()-100*Point, 0, 0);

保有ポジションに利確、損切ラインを追加するにはOrderModify関数を使います。
Pointに乗算する数字は業者ごとに違います。XMtradingの場合は100*Point = 10pipsです。

OrderModify(チケット番号, 注文時の価格, 損切価格, 利確価格, 有効期限, 色)

保有ポジションを変更する場合、注文時の価格はOrderOpenPrice()で固定です。
OrderOpenPrice()を使用するには事前にOrderSelect()でポジションを指定する必要があります。
利確、損切価格は少数以下の桁数が正しくないとエラーが出るので注意してください。
また損切り価格が現在価格に近すぎる場合もコンパイルは通りますが注文に失敗します。
EAであれば有効期限はほとんど使わないので0で固定です。

MQL4 基礎3

現在の価格を取得する
double A, B;

A = Bid;  //現在のBid値を取得
B = Ask;  //現在のAsk値を取得

 
 

一つ前のローソクの情報を取得する
double A, B, C, D;

A = iOpen(NULL,0,1);  //一つ前の始値
B = iClose(NULL,0,1);  //一つ前の終値
C = iHigh(NULL,0,1);  //一つ前の高値
D = iLow(NULL,0,1);  //一つ前の安値

 
iOpen関数

iOpen(銘柄、時間軸、ローソク足)

銘柄を指定するときは "USDJPY" と入れます。現在のチャートを指定するときはNULLと書きます。
時間軸を指定するときは PERIOD_M5 と入れます。現在の足を指定するときは0と書きます。
ローソク足は最新を0とし、過去へ1本戻るごとに+1します。


定義済み配列を使った場合。

double A, B, C, D;

A = Open[1];  //一つ前の始値
B = CLose[1];  //一つ前の終値
C = High[1];  //一つ前の高値
D = Low[1];  //一つ前の安値


上の時間足のローソクの情報を取得する
double A
A = iOpen(NULL,PERIOD_H4,0);  //4時間足の最新の始値


一つ前のローソクの実体とヒゲを調べる
double ue, jittai, sita;

if(Close[1]-Open[1]>=0){
      ue = High[1] - Close[1];
      jittai = Close[1] - Open[1];
      sita = Open[1] - Low[1];
} else{
      ue = High[1] - Open[1];
      jittai = Open[1] - Close[1];
      sita = Close[1] - Low[1];
}


陽線か陰線かによってケース分けする必要があります。
MathMax関数(2つの値の最大値を返す)とMathMin関数(2つの値の最小値を返す)を使うと少し短くなりますね。下のコードも動作は同じです。

double ue, jittai, sita;

ue  =  High[1] - MathMax(Close[1], Open[1]);
jittai = MathMax(Close[1], Open[1]) - MathMin(Close[1], Open[1]);
sita = MathMin(Close[1], Open[1]) - Low[1];


ローソクの確定を判断する

ローソクの確定を判断するときはBarsを使います。
Barsには現在チャートに表示されているローソクの本数が格納されています。
Barsの本数に変化があれば新しいローソクが生成されたと判断できます。

int oldBars = 0;  //前のティックのBarsを格納する変数を宣言

int OnInit()
{
   oldBars = Bars;  //EA起動時、現在の本数をoldBarsに代入
   return(INIT_SUCCEEDED);
}

void OnTick()
{
   if(oldBars == Bars) return; //ローソクの本数が前のティックと同じなら処理終了
   
   //ローソクが確定したときに行う処理を記述

   oldBars = Bars; //現在のローソクの本数に更新
}

MQL4 基礎2

チャート上に縦線を引く

datetime time01 = Time[0];

ObjectsDeleteAll();
ObjectCreate("VLine",OBJ_VLINE,0,time01,0);

1行目:変数time01に現在の時刻(EAを起動した時刻)を代入
2行目:チャート上のオブジェクト(線)をすべて消す
3行目:time01の位置に縦線(名前をVLineとする)を描画

ObjectCreate関数
bool  ObjectCreate("オブジェクト名", オブジェクトの種類, チャート, 横位置, 縦位置);

「オブジェクト名」はその図形を後から修正するときに区別するために決める。
「オブジェクトの種類」は縦線、横線、フィボナッチなどから選ぶ。
「チャート」はどのチャートに描くか。EAを挿入したチャートに描くなら0。
「横位置」はdatetime変数。
「縦位置」はdouble変数。

図形の種類
この他にも色々あります。
図形によっては「横位置」「縦位置」を2点以上指定する必要があります。
2点以上指定する場合、ObjectCreate(.... ,横位置1, 縦位置1, 横位置2, 縦位置2); です。

OBJ_VLINE 垂直線
OBJ_HLINE 水平線
OBJ_TREND トレンドライン
OBJ_CHANNEL チャネル
OBJ_FIBO フィボナッチトレースメント
OBJ_CYCLES 等間隔の連続した垂直線
OBJ_RECTANGLE 長方形
OBJ_TRIANGLE 三角形
OBJ_ARROW_UP 上向き矢印
OBJ_ARROW_DOWN 下向き矢印

 
 

チャート上に水平線を描く

  double price01 = Close[0];  

  ObjectsDeleteAll();
  ObjectCreate("HLine",OBJ_HLINE,0,0,price01);


図形の色を変える
ObjectCreate("HLine",OBJ_HLINE,0,0,price01);
ObjectSet("HLine",OBJPROP_COLOR,Blue); //線の色を青に変える


図形の太さを変える
ObjectSet("HLine",OBJPROP_WIDTH,3); //線の太さを3に変える


図形の位置を変える
ObjectSet("VLine",OBJPROP_TIME1,time02); //横位置をtime02の位置に変更
ObjectSet("HLine",OBJPROP_PRICE1,price02); //縦位置をprice02の位置に変更


指定した図形のみ消す
ObjectDelete(0,"VLine"); //メインチャートのVLineという図形を消す