[MT4][プログラミング][入門] 3. OnTick()の実装例を元にした取引処理の書き方

ここでは、mql4のEvent handlerから呼び出されるOnTick()に関して実装例を元に、どのように取引処理を記述していくかについて確認していきます。
OnTick()を実装する上での参考にになればと思います。

また、mql4を実装する上での、フォーマットとなるサンプルコードや、概要に関してはまず、Expert Advisorのフォーマットファイルと概要をご覧ください。

Contents

OnTick()の実装例と取引処理の書き方

1. OnTick()の実装例

まずは初めにOnTick()の実装例のサンプルを以下に紹介します。
こちらは、ある程度動作出来る直前までのコードになるようにOnInit()等も記述しています。

サンプルコード

NOTE 1:各自実装したい条件や値を設定する必要があります。このままですと設定値や条件が記述れていない為、コンパイルエラーになります。

/*****************************/
/* propety setting  ・・・ A */
/*****************************/
#property copyright   "2020-2021, x corp."
#property version     "1.00"
#property strict

/*****************************/
/* Input variables  ・・・ B */
/*****************************/
input double	Lots		= 1.0;

/******************************/
/* Global variables  ・・・ C */
/******************************/

/************************************/
/* Event Handing Functions ・・・ D */
/************************************/
int OnInit(void)
{
	int ret = INIT_SUCCEEDED;
	
	// 自動取引のロット数が0以下の場合は取引出来ないので、EAを停止する。
	if(Lots <= 0)	{ ret = INIT_PARAMETERS_INCORRECT; }
	
	return ret;
}

void OnDeinit(const int reason)
{
	// (今回は特に実装無し)
	
	return;
}

void OnTick(void)
{
	// 現在の取得済みポジション数と、指値、逆指値注文の総数取得
	int total	= OrdersTotal();
	
	// 新規ポジション取得処理
	if(total == 0)	{ CheckNewOrder();			}
	// 取得済みのポジションに関しての処理
	else			{ CheckOpendOrder(total);	}
	
	return;
}

void CheckNewOrder(void)
{
	if(/* 新規発注条件を指定(NOTE 1) */)
	{
		bool	isLong		= /* どちらのポジション持つか設定(NOTE 1) */;
		double	takeProfit	= /* テイクプロフィット値を設定(NOTE 1) */;
		double	stopLoss	= /* ストップロス値を設定(NOTE 1) */;
		
		// LONG ポジション発注の場合
		if(isLong == true)
		{
			if(OrderSend(Symbol(),OP_BUY,Lots,Ask,3,stopLoss,takeProfit,"EA sample",16384,0,Green) <= 0)
			{
				Print("Error opening BUY order : ",GetLastError());
			}
		}
		// SHORT ポジション発注
		else
		{
			if(OrderSend(Symbol(),OP_SELL,Lots,Bid,3,stopLoss,takeProfit,"EA sample",16384,0,Red) <= 0)
			{
				Print("Error opening SELL order : ",GetLastError());
			}
		}
		
	}
	
	return;
}

void CheckOpendOrder(int total)
{
	int cnt;
	
	// 上記で取得したtotal数分のループ処理(1つの注文番号ごとの処理)
	for(cnt=0; cnt < total; cnt++)
	{
		// 取り引き中のオーダーテーブルから一つのオーダーを選択します。
		if(OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES) == false) { continue; }
		
		if((OrderType() <= OP_SELL) &&		// 取得済みのポジションか?
			(OrderSymbol() == Symbol()))	// EAを追加しているチャートの通貨ペアか?
		{
			/**************************************/
			/* 取得済みのポジションに関しての処理 */
			/**************************************/
			// 実装例:
			// 1. 決済条件を満たした場合は決済
			// 2. 一定の利益が出たらストップロスを少し現在値に近づけるトレール処理
			// etc
			
			// LONGポジションの場合の決済処理
			if(OrderType()==OP_BUY)
			{
				/* 1. 決済条件を満たした場合は決済 */
				if(/* 決済条件を指定(NOTE 1) */)
				{
					// ポジションのクローズ要求送信
					if(OrderClose(OrderTicket() ,OrderLots(),Bid,3,Violet) == false)
					{
						// Close処理失敗した場合のError情報出力処理
						Print("Error in OrderClose. Error code=",GetLastError());
					}
				}
				
				/* 2. トレール処理 */
				if(/* トレール条件を指定(NOTE 1) */)
				{
					double newStopLoss = /* ここにストップロス値を設定(NOTE 1) */;
					
					if(OrderModify(OrderTicket(), OrderOpenPrice(), newStopLoss, OrderTakeProfit(), 0, CLR_NONE) == false)
					{
						Print("Error in OrderModify. Error code=",GetLastError());
					}
				}
			}
			// SHORTポジションの場合の決済処理
			else
			{
				/* 1. 決済条件を満たした場合は決済 */
				if(/* 決済条件を指定(NOTE 1) */)
				{
					// ポジションのクローズ要求送信
					if(OrderClose(OrderTicket() ,OrderLots(),Ask,3,Violet) == false)
					{
						// Close処理失敗した場合のError情報出力処理
						Print("OrderClose error ",GetLastError());
					}
				}
				
				/* 2. トレール処理 */
				if(/* トレール条件を指定(NOTE 1) */)
				{
					double newStopLoss = /* ここにストップロス値を設定(NOTE 1) */;
					
					if(OrderModify(OrderTicket(), OrderOpenPrice(), newStopLoss, OrderTakeProfit(), 0, CLR_NONE) == false)
					{
						Print("Error in OrderModify. Error code=",GetLastError());
					}
				}
			}
		}
	}
	
	return;
}

2. OnTick()の実装例の説明

それでは、上記のサンプルコードを元に、それぞれコードの確認をしていきましょう。

2-1. OnInit()関数の内容説明

今回は、OnTick()だけでなく、OnInit()関数も参考までに実装しています。
OnInit()関数には下記のような処理が実装されています。

// 自動取引のロット数が0以下の場合は取引出来ないので、EAを停止する。
if(Lots <= 0)	{ ret = INIT_PARAMETERS_INCORRECT; }

これは、Expert Advisorの「パラメータ入力画面」で入力できるパラメータのチェックを行っています。
今回の例では、「Lots」というパラメータを入力できるようにしており、これは自動売買する際のLot数を指定するパラメータとして用意してみました。

自動売買する際にLot数が0となっていると取引する数がないためそもそもExpert Advisorを動作させる必要がなくなる為、OnInit()でこのパラメータの入力値チェックを入れています。

0以下を指定した場合は、戻り値として「INIT_PARAMETERS_INCORRECT」を返却しているので、その後OnDeinit()が呼ばれた後、Expert Advisorは動作を停止します。

2-2. OnTick()関数の内容説明

次に、この記事の目的のOnTick()関数の説明になります。
Expert Advisorのフォーマットファイルと概要でも説明している通り、OnTick()では、自動売買のコアとなる取引の処理を実装します。

想定される実装内容としては、以下の処理あたりかと思います。
・新規ポジションの発注処理
・保有中のポジションの決済処理
・保有中のポジションのトレール処理

今回、OnTick()関数では、上記のような処理を可読性をよくするために、下記のように自作関数に分けて、実装しております。

void OnTick(void)
{
	// 現在の取得済みポジション数と、指値、逆指値注文の総数取得
	int total	= OrdersTotal();
	
	// 新規ポジション取得処理
	if(total == 0)	{ CheckNewOrder();			}
	// 取得済みのポジションに関しての処理
	else			{ CheckOpendOrder(total);	}
	
	return;
}

CheckNewOrder()
こちらの関数は、「新規ポジションの発注処理」を実行する関数として用意しました。
現在保有中のポジションや指値/逆指値の注文がないときに呼び出すようにしています。

CheckOpendOrder()
こちらの関数は、「保有中のポジションの決済処理」と「保有中のポジションのトレール処理」を実行する関数として用意しました。
現在保有中のポジションや指値/逆指値の注文があるときに呼び出すようにしています。

上記の自作関数は、MT4で用意されているライブラリにある、OrdersTotal()関数の結果をもとに振り分けるようにしています。
OrdersTotal()関数は、現在のアカウントで「保有しているポジション数」と「指値/逆指値の未約定の注文数」を足した数を返してくれる関数です。
(MT4の「取引」タブで見れる注文の総数と同じです。)

それでは、それぞれの自作関数の中身を見ていきましょう。

2-2-1. CheckNewOrder()関数の内容説明

CheckNewOrder()関数では、Longポジション、Shortポジションそれぞれ発注できるような実装にしています。

新しいポジションを取る際の条件は、各自どのような条件に合致した際に発注処理を行うかを考え実装してみてください。
また、その際は、LONGで発注するのか、SHORTで発注するのか、TakeProfit/StopLossはどうするかなども検討するようにしましょう。
サンプルコード中では、条件式などは実装しておらず、コメントを記載しています。

LONGポジション発注サンプルコード
if(OrderSend(Symbol(),OP_BUY,Lots,Ask,3,stopLoss,takeProfit,"EA sample",16384,0,Green) <= 0)
{
	Print("Error opening BUY order : ",GetLastError());
}
SHORTポジション発注サンプルコード
if(OrderSend(Symbol(),OP_SELL,Lots,Bid,3,stopLoss,takeProfit,"EA sample",16384,0,Red) <= 0)
{
	Print("Error opening SELL order : ",GetLastError());
}

2-2-2. CheckOpendOrder()関数の内容説明

最後に、CheckOpendOrder()関数の説明です。
こちらの関数は、「保有中のポジション数」と「指値/逆指値の注文数」の合計数分のループでそれぞれの注文ごとに、「トレール処理を行うのか?」や「決済するタイミングか?」を確認するような処理になります。

2-2-2-1. まずはOrderの選択 / コピー

各注文ごとのループの中の最初の処理ではまずは一つの注文を選択するためにOrderSelect()関数を使用しています。

// 取り引き中のオーダーテーブルから一つのオーダーを選択します。
if(OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES) == false) { continue; }

OrderSelect()はMQL4 Referenceでは下記のように説明されています。

第一引数:
第二引数のMODEによって、「注文チケット番号」を指定するか、「注文を0オリジンでインデックス化された番号を指定」するかが変わってきます。
「注文チケット番号」を指定する場合は、各注文でユニークな注文番号が割り当てられているので、その注文番号を指定します。
「注文を0オリジンでインデックス化された番号を指定」は、特に注文番号に固執せずに全ての注文に対してループ処理で全注文をチェックするような際に使用します。
今回は、すべての注文に対してループ処理で、全注文をチェックするような処理にしていますので、第一引数は、「0」~「最大注文数-1」までループ処理するようになっています。

第二引数:
第一引数で渡した番号が「注文チケット番号」なのか「注文をインデックス化された番号」なのかを指定します。
「注文チケット番号」を指定する場合は、SELECT_BY_TICKET を指定します。
「注文をインデックス化された番号」を指定する場合は、SELECT_BY_POS を指定します。
今回のサンプルは、「注文をインデックス化された番号」を指定させたいので、SELECT_BY_POS を指定しています。

第三引数:
第一引数、第二引数の情報と一致する注文を、MT4の「取引」タブにある情報からとってくるか、または「口座履歴」タブにある情報から取ってくるかを指定します。
使い分けの方法は、「保有中のポジションや、注文中の指値/逆指値」から情報を取ってきたい場合は「取引」タブから、「決済済みのポジションや、キャンセルした指値/逆指値」から情報を取ってきたい場合は、「口座履歴」タブから情報を取得するようにしています。
「取引」タブから情報を取得したい場合は、MODE_TRADES を指定し、「口座履歴」タブから情報を取得したい場合は、MODE_HISTORY  を指定してください。

このOrderSelect()を一度実行すると、一つの注文情報を内部にコピーします。
OrderSelect()を実行したタイミングで、注文情報がコピーされるので、処理のタイミング次第では、少し古い情報のままになっている可能性もあります。注文情報を参照するような実装をする場合は、情報を参照する直前にOrderSelect()を実行して最新の情報を取得できるように心がけましょう。
コピーした情報は下記関数を実行することで、それぞれの情報を読み込むことが出来ます。

関数名コピーした注文の何を読み取れるか
OrderClosePrice()決済時のプライスを取得できます。
OrderCloseTime()決済時の時刻を取得できます。
OrderComment()コメントを取得できます。
OrderCommission()手数料を取得できます。
OrderExpiration()保留中の注文の有効期限を取得できます。
OrderLots()ロット数を取得できます。
OrderMagicNumber()マジックナンバーを取得できます。
OrderOpenPrice()ポジション保有開始時のプライスを取得できます。
OrderOpenTime()ポジション保有開始時の時刻を取得できます。
OrderPrint()下記取引情報すべてを、MT4の「エキスパート」タブ上に出力します。
ticket number; open time; trade operation; amount of lots; symbol; open price; Stop Loss; Take Profit; close time; close price; commission; swap; profit; comment; magic number; pending order expiration date.
OrderProfit()損益を取得できます。
OrderStopLoss()設定されているストップロス値を取得できます。
OrderSwap()スワップを取得できます。
OrderSymbol()通貨ペアを取得できます。
OrderTakeProfit()テイクプロフィットを取得できます。
OrderTicket()チケットの注文番号を取得できます。
OrderType()オーダータイプを取得できます。
取得できるオーダータイプの種類は下記です。
OP_BUY : buy order,
OP_SELL : sell order,
OP_BUYLIMIT : buy limit pending order,
OP_BUYSTOP : buy stop pending order,
OP_SELLLIMIT : sell limit pending order,
OP_SELLSTOP : sell stop pending order.
OrderSelect()でコピーした情報を読み出す関数群

2-2-2-2.保有中の注文か?

ここまでで、一つの注文を選択 / コピーした状態になりました。
次に、選択した注文が、保有中の注文なのかどうかのチェックを行います。

保有中の注文なのかどうかの確認は「OrderSelect()でコピーした情報を読み出す関数群」で紹介した、OrderType() を使用します。

if((OrderType() <= OP_SELL) &&		// 取得済みのポジションか?
	(OrderSymbol() == Symbol()))	// EAを追加しているチャートの通貨ペアか?
{
	//処理
}

OrderType()の結果がOP_SELL以下であるかどうかという記述になっています。
なぜこのような記述で「保有中の注文」と判断できるかは、OrderType()で返却される値のバリエーションを確認するとわかります。

OrderType()で返却される値は下記となります。

返却define名define値意味
OP_BUY0buy order
OP_SELL1sell order
OP_BUYLIMIT2buy limit pending order
OP_SELLLIMIT3sell limit pending order
OP_BUYSTOP4buy stop pending order
OP_SELLSTOP5sell stop pending order
OrderType()の返却値 from https://docs.mql4.com/constants/tradingconstants/orderproperties

上記を見ると、「保有中の注文」に合致するのは、「OP_BUY」と「OP_SELL」の2つになります。
この2つのdefine値を見てみると「0」と「1」が定義されていますので、「1以下であること」を確認すれば「保有中の注文」であるかどうかを確認することが出来ますね。

その為、1で定義されている「OP_SELL以下であること」をチェックすれば、「保有中の注文」であるかどうかをチェックできることになります。

2-2-2-3. 通貨ペアのチェック

次に通貨ペアのチェックです。
通常はEAを追加しているChartの通貨ペアでのみ動作させるような作りにする方が多いかと思いますので、そのチェックを行います。

EAが追加されているChartの通貨ペアを取得するには、Symbol()関数を使用します。

if((OrderType() <= OP_SELL) &&		// 取得済みのポジションか?
	(OrderSymbol() == Symbol()))	// EAを追加しているチャートの通貨ペアか?
{
	//処理
}

通貨ペアの情報はstring型(文字列)で返却されるため、選択している注文の通貨ペア名とチャートの通貨ペア名が一致しているかをチェックしています。

2-2-2-4. 決済処理

ここまで来たら、「保有中の注文」かつ「EAが追加されている通貨ペア」の注文となっている為、この注文に対して、決済処理や、トレール処理が必要かの確認を行い、必要であればそれらを実行します。

決済する条件になった場合は、OrderClose()関数を使用して選択中の注文の決済を行います。
選択中の注文の決済を行うために、OrderClose()の第一引数にOrderTicket()を設定し選択中の注文のチケット番号を通知しています。

// ポジションのクローズ要求送信
if(OrderClose(OrderTicket() ,OrderLots(),Bid,3,Violet) == false)
{
	// Close処理失敗した場合のError情報出力処理
	Print("Error in OrderClose. Error code=",GetLastError());
}
// ポジションのクローズ要求送信
if(OrderClose(OrderTicket() ,OrderLots(),Ask,3,Violet) == false)
{
	// Close処理失敗した場合のError情報出力処理
	Print("OrderClose error ",GetLastError());
}

2-2-2-5. トレール処理

最後にトレール処理です。
保有中の注文で、ある程度利益が出た場合に「どうなったら」「いくらにストップロスを再設定する」かは、各々条件を構築 / 実装してみてくださいね。
トレール処理を行う際は、現在保有中の注文の情報を変更する関数、OrderModify()関数を使用します。
こちらも、選択中の注文の変更を行うために、OrderModify()の第一引数にOrderTicket()を設定し選択中の注文のチケット番号を通知しています。

double newStopLoss = /* ここにストップロス値を設定(NOTE 1) */;

if(OrderModify(OrderTicket(), OrderOpenPrice(), newStopLoss, OrderTakeProfit(), 0, CLR_NONE) == false)
{
	Print("Error in OrderModify. Error code=",GetLastError());
}

2. 自動売買ボタンとの関係

ここまで読まれている方は、EAを動作させるにはMT4の「自動売買」ボタンも有効にする必要があることはご存じかと思います。
この「自動売買」ボタンはEAの処理で詳細には何を制限しているかといいますと、mql4のClient Terminal Eventsに下記の記述があります。

The NewTick event is generated irrespective of whether automated trade is allowed or not (“Allow/prohibit Auto trading” button). The prohibition of automated trading denotes only that sending of trade requests from an Expert Advisor is not allowed, while the Expert Advisor keeps working.

https://docs.mql4.com/runtime/event_fire

「denotes only that sending of trade requests」とあるので、実際には、OrderSend(), OrderClose(), OrderModify()あたりの注文操作系の要求のみ制限されるような仕様となっているようですね。

3. 最後に

ここまでで、OnTick()の中身に、「新規注文」や「決済」、「トレール」処理の実装仕方がある程度わかったかと思います。
これらをベースに、皆さん独自のEAを作成し裁量トレードの助けになればと思います。

[お知らせ]MT4使いのあなたへ

日本の金融商品取引業者にも登録されているゴールデンウェイ・ジャパン社が運営するFXTF のご紹介です。

国内FX業者の中でも最狭水準のスプレッドで有名なFXTFでは、口座開設と2つの指定通貨ペアでの取引を行うだけで1万円のキャッシュバックがあります。

1万円キャッシュバックキャンペーンキャッシュバック受け取り方法:

キャッシュバックの受け取り方法は以下の2つの条件をクリアすることで取引口座へ振り込まれます。

条件1:
FXTFでの新規口座開設
※ここまでだけでも、3000円のキャッシュバックが受け取れます。

条件2:
以下の2通貨ペアそれぞれで1取引、合計2取引を口座開設から10日以内に行うこと。
USDJPY(スプレッド : 0.1銭 原則固定)
GBPJPY(スプレッド : 0.6銭 原則固定)
※取引量は問われないようです。
※この条件を達成すると追加で7000円キャッシュバックされます。(条件1と合わせて合計1万円になります)

その他補足情報:

MT4:利用可
関東財務局長(金商)登録番号:第258号

FXTFはMT4が使えて、スプレッドもとても小さいFX国内業者の為、このキャッシュバックキャンペーン期間中にメイン/サブ口座として開設するのもいいかもしれませんね。
※MT4での指定の取引量の取引を行うと、さらに+5万円がキャッシュバックされるキャンペーンもやっていますがこちらは、指定の取引量がかなり大きいので紹介は割愛させていただきます。