【MT4】今更、MT4でリピート売買プログラムを書く(3/4、プログラミングのメイン処理編)
これまで、システム自動売買のわりとやりやすい例として、MT4プラットフォームを使ったリピート売買プログラムの前提条件と プログラムの前半に書く定数と変数について 説明してきました。
【MT4】今更、MT4でリピート売買プログラムを書く(1/4、基礎編)
【MT4】今更、MT4でリピート売買プログラムを書く(2/4、プログラミング最初の定数、変数編)
今回はいよいよメインの部分を説明します。
とはいえ、ループイフダンの設定のためにやっている処理はとても簡単で、大きく分けて3つの処理しかしていません。
まずは、ポジション数をチェックして、まだゼロであれば、買値をチェックして、設定した上限より下であれば最初の買いポジションを建てる処理。
このとき、最初に設定したインターバルの分、上に動いたら利確、または設定した割合分下落したら損切りする、という指定も一緒にできるので、発注した時点で大部分の処理ができてしまいます。
次に、すでにポジションがあれば、現在並んでいるオープンポジションを順番に調べて最安値を確認します。
というのは、上記のようにすでにポジションを建てた時点で利確のポイントも指示してはいるものの、利確されると、直近のlatestPositionの値(直前に利確したポイント)がズレてしまいます。
たとえばある通貨Aが100円から99.5円、99.0円と値段が急落してそれを0.5円刻みで拾った場合、最新の基準値latestPriceは99.0がセットされます。
その後、99円から0.5円上がって99.5円で利確された場合、latestPriceの値も99.5円に更新されないと、また99.0円に下がっても、本来ならここで入れないといけないはずが、(直近が99.0円で今も99.0円だから次は98.5円だな)とプログラムが判断し(かなり擬人化していますが・・・)、買いが入りません。
ということで、最初にオープンポジション数を調べて、すでにポジションが1つ以上あれば、必ずその買値を全部チェックして一番安い値を最新の基準値としてセットします。
そして、そこから設定したインターバル分下に通貨Aの現在価格が下がっていれば、次の買い処理をすることになります。
買い注文時に利確と損切りのポイントを指定してしまうのが、初回の処理と同じです。
注文のOrderSend()関数を使いこなす
MT4の開発言語であるMQL4では、発注の際にはOrderSend()という関数を使って、何という通貨を、どのくらいの分量を買うのか、また成行で買うのか指値で買うのか、指値ならいくらで買うのかといった発注する際の情報を指定してから、証券会社のサーバに注文を送ります。
このとき、さらに対象の通貨が何円になったら利確するのか(英語でTake Profitなので、T/Pと略されることがあります)、あるいは見込みに反して、いくらに下がったら損切りするか(同様にLoss Cutで、L/Cと略されることがあります)をオプションとして指定できます。
「オプションで」というのがカギで、指定しなくてもいいし、してもいいということです。
たとえばドル円で0.3円刻みで112円以下で取引する設定でリピート型自動売買をする場合に、最初に110円だったとします。
このとき、110円で指値で買い注文を入れると同時に、110.3円になったら利確するよう、Take Profitの値を設定してしまいます。
これで買い注文と同時に、利確のための決済注文もなされたことになります。
さらに、このときの直近の買値をlatestPriceという変数に入れておきます。
以降はlatestPriceと現在の買値(Ask)を比較して、直近の買値よりあらかじめ定数priceIntervalで設定した値分、値が下がったら、追加のポジションを建てる処理をします。
たとえば先ほどの例だと、110円で最初のポジションを建てると直近の買値110円からあらかじめ設定した3千円分安いところ、つまり109.7円で次の買いの処理が実行されることになります。
初期化の関数で直近値を設定する
それではプログラム本体の続きを見ていきます。
まず最初に初期化のために起動時に1回実行されるOnInit()関数のところで、最新の買値(Ask)を取得しておきます。
2回目以降はプログラムの中で取得しますが、起動時の最初の分を真っ先に取得しておかないと、プログラムが適切なレンジか判断できないので、まず最初にlatetPrice = Ask; として取得しています。
初期化(initialization)という言葉はよく聞きますが、要はあるプログラムを実行する前の、事前準備をするところと理解していただければよいと思います。
もう1つ重要なのは、MT4のプログラムから買値(Ask)を取得したいときは、もうすでにAskという名前で参照できるようになっていることです。
Askというのは、「その通貨の現在の買値」としてシステムに組み込まれているということであり、逆にいうと我々ユーザーはすでにプリセットされている変数Askを使うことで、面倒なコードを書かなくてもあっさり最新の買値が分かるということです。
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
latestPrice = Ask ;
return(INIT_SUCCEEDED);
}
同様に、売値(Bid)についても、変数Bidとしてシステムにあらかじめ組み込まれています。
ちなみに前回も少し説明していますが、証券会社の手数料代わりに、買値(Ask)は少し高めになります。110円のものを110.2円で買い手に買ってもらうことで、0.2円(20銭)分が証券会社の利益になります。
ですから、売値(Bid)についても、少し安めになります。110円で売りたいと言っている人から109.8円で引き取ることで、こちらもやはり0.2円分の利益を得ることになります。
考え方としては少し値切って買った、ということになります。
最初はポジションがないので、この値をベースに最初の買いが入ることになります。
ポジションがゼロで設定した上限以下なら最初の買いを入れる
いよいよプログラム本体の入るOnTick()関数の中身です。
城攻めにたとえると、ようやく本丸の門を突破したところです。
void OnTick()
{// まず現在のポジション数を取得
total = OrdersTotal() ;
通常、MT4ではこのOnTick()関数の中に、メインとなる処理のコードを書いていきます。
Tick(ティック)というのは、値が変わる刻みのことで、選択中の通貨の値動きのことです。
値が動くたびに、このOnTick()関数が起動することになるわけです。
FXや仮想通貨は通常は1秒間に何度も値が動くので、この関数も1秒間に何度か実行されるということです。
void OnTick()
{
// まず現在のポジション数を取得
total = OrdersTotal() ;
// ポジション数が0で所定の上限以下なら、初回の買いを成行で入れる
if (total == 0 && Ask <= UpperPrice)
{
ticket = OrderSend(MyCurrency, OP_BUY, Lots, Ask, Slpg,
Ask * LossCutRatio, Ask + PriceInterval, "Repeat-If-done-0",
MagicNumber,0, clrBlue) ;
if (ticket > 0)
{
Print("Get a new position: ", Ask, ", Pos: ", total+1, " Latest Price: ", latestPrice) ;
latestPrice = Ask ;
Sleep(1000) ;
}else
{
Print("New purchase order failed(0)!") ;
}
}
まず冒頭のOrderTotal()関数で、現在買ったものの、まだ利確なり損切りなりの決済が終わっていない仕掛り中の注文、つまりオープンポジションの合計数を取得しています。
次に、現在のポジションがなければ、つまりプログラムの最初の動作時、あるいは運用の途中で手持ちポジションが全部約定して(相場が急騰した場合にそうなります)なくなった場合にここの処理が実行されます。
もう1つ、以下のようにif文の中にAND条件(&&)で書いていますが、高値掴みを避けるため、あらかじめ設定しておいたしきい値UpperPriceよりAsk値が小さい場合のみ、次のポジションを建てることにしています。
if (total == 0 && Ask <= UpperPrice)
この値を超えてしまったら・・・、しばらくお休みです。
けっこう長く感じますが、株にしろFXにしろ仮想通貨にしろ、高値で買ってさらなる高値を狙うという手法は(ブレイクアウト戦略としてはあり得ますが)難しく、個人的には高値掴みで失敗したことのほうが多いので、高値で買うのを避ける設定にしています。
これは資金量に応じたポジション数の上限設定と併せ、私なりのリスク管理というわけです。
この間、商いがなく利益が入らないので精神的には辛いですが、あとで急落したときに含み損を抱えて苦しくなるよりはマシ・・・と思ってください。
高値圏から値を戻すまでの辛抱です。
そしていよいよ買い注文の処理です。上のif文の条件が成立すると、以下の買い処理が実行されます。
OrderSend()関数に慣れればEAは動く
MT4で買い処理をするということは、OrderSend()関数の使い方に慣れる、ということとほぼ同義かと思います。
ticket = OrderSend(MyCurrency, OP_BUY, Lots, Ask, Slpg, Ask * LossCutRatio, 0, Ask + PriceInterval, "Repeat-If-done-0", MagicNumber,0, clrBlue) ;
if (ticket > 0)
{
Print("Get a new position: ", Ask, ", Pos: ", total+1, " Latest Price: ", latestPrice) ;
latestPrice = Ask ;
Sleep(1000) ;
}else
{
Print("New purchase order failed(0): ", GetLastError() ) ;
}
まずはOrderSend()関数の書式に慣れていただきたいのですが、書式のパラメータ(コードに記載する、動作を指定する設定値)は、最初から順に、以下のようになっています。
MyCurrency: 最初に指定した取引対象の通貨
OP_BUY: 指値か成行か、買いか売りかの取引種別を指定
Ask: 買値(または売値)を指定。ここでは現在の買値(Ask)での売買を指定
Slpg: スリッページ。売買時にどのくらいまで値段のズレを許容するか指定
ロスカット: 損切りを事前に指定。ここではその時の買値の何%になったらと指定
利確: このときの買値から設定したインターバル分上がったら利確するよう指定
コメント: MT4のログに何かメモを残すことができる
マジックナンバー: このEAの固有のID番号。複数のEAを実行する場合の識別コードとなる
上記のリストのような条件を指定して、買い注文を出しています。
基本は条件が揃った時点のAsk値をベースに、そこからいくら上がったら利確、その値段の何%まで価格が下がったら損切り、という指定をしています(実際は計算式の手間を省くため小数で指定しています)。
1つ分かりにくいのはスリッページですが、これは注文成立時の値のズレをどこまで許容するか、というものです。
指定した値より不利な条件で成立することもありますが、あまり狭くすると買いたいところで買えないという不都合もあるので、少しは幅を持たせておくのが現実的です。
今回は多少の価格のズレより確実に買えることを重視して、成行で買う設定にしていますが。
また、最初の行のticket =という形式ですが、OrderSend()関数の実行が成功すると、言い換えれば注文処理が通ると、「注文番号」(MT4で注文毎に取引サーバから割り振られる一意の番号)が返ってくるので、それを入れるための受け皿だと思ってください。
細かい注文管理とかする場合は、この番号をプログラムの中で記録しておいて後からチェックする際のキーにしたりしますが、今回は単純な処理なのであまり関係ありません。
とはいえ、その次で、ticket > 0、つまり注文が通って何かしらの番号が割り振られた場合は、「ポジションが新しくできました」というメッセージを出す処理をしています。
いちいち自分でメッセージを出力するコードを書かなくても、MT4のログには新規ポジションとして書き出されるので、ここは必須ではないのですけどね(汗)。
むしろ、その時点で成立したポジションが直近の一番下の価格になるので、latestPriceの値をその時点のAsk値で更新していますが、そちらの方が重要です。
また最後にSleep()命令を噛ませています。これは、最初に作った試作品では同じ値のところで続けざまに複数の注文が入ってしまう不具合があり、その対策です。
階段状に買い下がって各価格帯をそれぞれ1人の兵士が守るイメージなので、同じ価格帯に複数の兵士が張り付くと、リソース配分が、もっと正確にいうと資金配分=資金管理がおかしくなってしまいます。
最初にこのバグが発生したときは、思わず「持ち場を離れるんじゃない!」と叱ってしまいました(ウソ)。
推測ですが、注文を手元PC(またはMT4を実行させている外のレンタル・クラウドサーバ)してから処理結果が返ってくるまでのタイムラグの問題で、待っている間もif文の条件が成立したままなので再度注文を出してしまうのではないかと思います。
Sleep()はミリ秒単位なので1,000ミリ秒、つまり1秒待つ設定になっています。
急激に下がった下ヒゲを買い下がり、戻りで利益を出す、なんてこともよくあるので、あまり遅いのはまずいですが、高頻度売買のようなシビアさは求められないので、注文成立後に1秒待つくらいは許容範囲かと思っています。
またあまりに頻繁に発注を繰り返して業者側の取引サーバに負担を掛けると、回線を細くされたり、取引制限される恐れもあるかもしれないので、その意味でも1秒くらいの「待ち」を入れるのは合理的かな、と思います。
次回は、最終回ということで、後半の閉めの処理のところ、そして今後どのように発展させていく可能性があるのかを説明したいと思います。
※言うまでもありませんが、投資はご自身の判断と責任で行ってください。本稿は参考情報として提供するものであり、特定の銘柄や市場全般の今後の動きを予測するものではありません。ここで提供する情報やソフトウェアを元に読者の方が行ったいかなる結果についても、筆者は責任は負えません。