実際に動かしている動画↓
成果物はこちらになります
はじめに
この記事の目的
これまでココナラで自動売買システムや銘柄監視用のリアルタイムアラートなどの依頼があれば、依頼者様のご要望に合わせてオーダーメイドで作成してきました。自動売買と聞くと詐欺などが連想されて、怪しいことをしているように思われがちですが、あくまで私の仕事は、依頼主様の構想をプロダクトに落とし込むプログラミングのお仕事です。このような成果物は、ポートフォリオの一部のとして、webサイトのように実際に試して評価してもらうことが難しいものですし、ココナラでのNDA契約により、ご依頼で制作した完成品は載せられないのですが、できるだけそれに近い形で自分のこれまで学んできたこと、できることが見せられれば良いかなと思いこの記事を作成しました。
この仕事をしてみての感想などはこちらにまとめています。
主な実績は以下になります。
注意書き
※この解説は投資に関するものではなくあくまでVBAプログラミングに焦点を当てたものです。この記事で使用するトレードルールで稼げるといったものではありませんので注意してください。実際に投資する場合は、損失等責任を負えませんのでそちらの方ご理解よろしくお願いします。
導入
楽天rssを用いてエクセルVBAプログラミングで自動売買を行います。
今回は5分足を用いた自動売買の制作物の紹介とその作り方を簡単に解説していきます。
※実用的に使うには約定がしっかり通ったかなどを確認する機能も入れないといけません。また、複数銘柄を取引する場合は、ブックを指定するようにプログラムしないといけませんが、今回は割愛します。詳しくは以下を参考にしてください。
前提として楽天marketspeedとエクセルの連携、マクロの設定が済んでいることとします。まだの方は以下の記事を参考に連携してください。
今回利用するトレードルールの解説
今回設計したトレードルール(設計)は以下のようになります。
前提条件
5分足チャートを使う
引け5分前からはエントリーはしない
引けの5分前に全て利確、損切りをする
エントリー判定は5分毎に行われ、決済判定もそれぞれ個別に行われる。
→エントリー判定は5分毎に行われ、すでに建て玉があった場合でも、次の5分でエントリー条件を満たせば、追加で発注する。イグジット判定は各建て玉で行い。イグジット条件を満たしていない建て玉は残す。
エントリー条件
5分足がwapにタッチした場合
タッチしたローソク足の確定足の終値が
vwapより高い場合→買いでエントリー
vwapより低い場合→売りでエントリー
同値の場合→何もせず見送る
イグジット条件
※イグジットは時間が経過するごとに条件が変わります。
買いで入った場合の説明
(※売りでエントリーした場合は売り買いと高い低いが逆になる)
・エントリーから5分後の判定
エントリー時の終値が次の5分確定足の終値より低い場合→イグジット
高い場合・同値の場合→継続保有。
・エントリーから10分後の判定
エントリーから5分後の終値がさらに次の5分後の終値より低い場合→イグジット
高い場合・同値の場合→継続保有。
・エントリーして10分後以降の判定
前足(エントリーから6〜10分のローソク足)の始値と終値の丁度半分の所に逆指値の損切り価格を設定し、その値を現在値が下回ったら即イグジット
・エントリーして15分後以降の判定
全て利確
いまいちルールが分かりにくいという方に実際のチャートを使って軽く例で説明します↓
エントリー
イグジット
制作物の紹介と詳細説明
成果物のファイル
成果物はこちらから実際に確認できます↓
5分足情報の取得方法
まず楽天rssの関数であるRssChartを用いて5分足の情報を表示させます。
値はリアルタイムで更新され、5分毎に値が固定、さらに下の行に新たな5分足情報が更新されます。5分足の情報がどんどん下に追加されるイメージです。
現在vwapというセルも楽天rss関数を用いて、リアルタイムでvwapが更新されます。これを5分毎にH列vwapの項目の最新の行に値を貼り付けて5分足毎のvwapを保存しておきます。
エントリーと逆指値による返済はエクセル関数を用いて、成り行き返済はエクセルVBAを用いて行います。
売買の発注方法
売買の発注は「発注」という別のシートで行っています。mainシートのエントリー列と返済の列の情報をこの発注シートに渡し、条件に合致した時に=RssMargin関数が発注を行います。
例えば発注区分には以下のような関数が入っています。
例えば、以下のように「買」の判定が出た場合は発注シートの売買区分のセルに3が自動で入力され、そのセルをさらに=RssMargin関数が参照しています。
制作における難しい点や重要なポイント
今回工夫が必要なところは以下の3つです。
①リアルタイムで更新される終値をどのように5分足の終値として認識させるか
②時間経過毎に変化するイグジットをどのように再現するか
③複数のエントリーがあった場合に決済を個別に行うにはどうするか
①に関して、今回は5分足の確定した終値がvwapより高いか低いかで「買い」か「売り」のどちらでエントリーするかを決めます。しかし、最新の終値というのは値が更新され続けるので、現在値でもあります。単純に5分足の終値を取得するだけでは「確定足」の終値を取得できるわけではないということです。こちらをどのように実現するのかが重要になります。
②に関して、今回の設計では、エントリーから5分毎にイグジットの条件が変化するようになっており、終値で判定する5〜10分の条件(ある時間になった時にイグジットするかどうかを判定する)と、逆指値を置いて判定する10分以降の条件(ある値になった時に即時にイグジットする)の判定対象が違うので、当然それぞれの判定プログラムを組む必要があります。加えて、逆指値でイグジットした後に、成り行きの条件にも合致したりすると、存在しない建玉に対して返済発注を行ってしまい、エラーの原因となったりするので、別々のプログラムを組みつつ、どちらか一方が実行された場合は、もう片方は実行されないようにうまくプログラムを組む必要があります。
③に関して、前提条件にもありますが、100株すでに建てている時に、次の5分足でも100株エントリーすると、合計200株になります。その場合それぞれを分けて考える必要があります。どちらか(もしくは両方)の建て玉が返済条件を満たしたとき、すべての建玉を返済するといったOR(もしくはAND)条件なら設計は簡単ですが、今回のような条件だと、単純に5分足の情報だけを利用して実現するのは難しいように思います。
以上3つが今回のポイントとなります。
これに関しては分かりやすく説明するのが非常に難しいですが、実際に作ってみてぶつかった3つのポイントになります。これらを解決するちょっと捻ったアイデアがないと、変なところで決済されたり、そもそも思った通りに動かない、といったことが起こります。
制作での工夫や問題の解決策
エクセルVBAを上手に使う上で重要な鍵となるのは、エクセル関数による「リアルタイム参照の性質」とVBAによって値を挿入する「条件分岐の柔軟性」のそれぞれ長所と短所をうまく住み分けて使うことだと思います。
VBAでエクセル関数を挿入するといったこともできますが、制作中にはエクセルの関数を使うべきなのか、VBAで記述するべきなのかという状況が何度もあります(上手い方はないのかもしれない)。その際は上記の事柄を頭に入れて考えると徐々に解決策が浮かんでくるかもしれません。
エントリー
エントリーは比較的シンプルで、以下ようなエクセル関数で行っています。
=IF(OR(G7=1125,G7=1455),"",IF(AND(D7>=H7,H7>=E7,F7>H7, H7<>"",G8<>""),"買",IF(AND(D7>=H7,H7>=E7,F7<H7, H7<>"",G8<>""),"売","")))
前項の①の解決策でもありますが、エントリー判定は5分確定足で判定するので、次の行が更新されたときに前の行がエントリー条件を満たしていたかを見れば良いです(次の行が更新され始めた時=その前の行の終値が確定する)。
・もし、IDが1125か1455ならエントリーしない。
もし高値と安値の間にvwapがある、かつ、終値がvwapよりも高いかつ終値が確定している
・もし、IDが1125か1455なら何もしない。
・もし高値と安値の間にvwapがある、かつ、終値がvwapよりも低いかつ終値が確定している
・終値は常に更新され続けるため次のIDが空欄でないことを確認することで確定足の時のみ発動する。
・また、vwapに値が入っていることがエントリートリガーの最低条件となるため、vwap <> “を用いて、vwapに数値が入っていることも確認する。
イグジット
イグジットには、成り行きに加えて10分後からは逆指値も同時に使われるようになることと、建て日や建て単価の入力が必要になるので考えることは多く、少し難しくなります。
これを実現するために、これまでの建て回数と現在の建て玉回数、返済回数をカウントするセルを作ります(G3:O4で実現)。
M,N列で返済のサインが出た回数 + 現在の建て玉枚数 – I列でエントリーのサインが出た回数
上記を計算し、0より大きかった場合に返済を発注するという方法をとっています。
これを発注シートの返済注文の発注トリガーのセルに以下を記述して実現しています。
=IF(OR(main!O3+main!L3-main!H3>0, main!O4+main!L4-main!H4>0),1,0)
現在の建て玉枚数に加えて、返済発注に必要な、建て日と建て単価の情報を取得するために建て玉の詳細を見るシートも必要になります(建玉詳細シートを作成)。ここにさらにfilter関数を用いて、取引している銘柄の建て玉のみを表示させ、最も古い建て玉の情報と、countif関数を用いて建てている枚数の情報を取得します。
建玉詳細シートの絞り込みで最も古い建玉の情報を取得
=FILTER(A6:E50,A6:A50=main!$B$1,"")
発注の返済の建て日や建て単価項目がこれらを取得
=IF(発注!O17=1,建玉詳細!S6,IF(発注!O17=3,建玉詳細!Z6,""))
mainシートの建て玉保有カウントで建て枚数を取得
=COUNTIF(建玉詳細!A6:A50,B1)
これにより③の建玉を別々に決済するというところを実現させられました。また、②に関してもこの後紹介する返済のためのVBAプログラムと逆指値返済列にある関数を合わせて実現させられています。
逆指値の関数は以下のようになっています。
=IF(OR(E11="",O8=""), "",IF(AND(I8="買",E11<O8),"売",IF(AND(I8="売",E11>O8),"買","")))
・10分経っておらず、逆指値価格に数値が入っていない場合は、何もしない
・エントリーが買いで、現在値が逆指値価格を下回ったら売り返済を行う
・エントリーが売りで、現在値が逆指値価格を上回ったら買い返済を行う
・成り行き返済と逆指値価格はVBAを使って入れる。
トレード開始のセットアップや返済のためのVBAのコードは後ほど記載します。
イグジットに関しては理解が難しいかもしれませんが、エントリーのように単純に発注できるわけではなく、経過時間毎に変わるイグジット条件と複数の建玉を個別にイグジットすることなど、それぞれが混在してしまわないようにうまく設計する必要があることを理解しておきましょう。
VBAで記述したコード
スタートとループのためのコードは以下になり、トレード開始ボタンを押すと実行されます。
Sub トレード()
'スタート用。これをまず呼ぶ
'↓初期設定はここに書く
Dim LastRow
'前回挿入されたセルの値をリセット
LastRow = 82
Range("H7:H" & LastRow).Select
Selection.ClearContents
Selection.Value = ""
Range("M7:M" & LastRow).Select
Selection.ClearContents
Selection.Value = ""
Range("O7:O" & LastRow).Select
Selection.ClearContents
Selection.Value = ""
Range("P7:P" & LastRow).Select
Selection.ClearContents
Selection.Value = ""
'↓メインを呼び出す(触らない)
ScheduleTime = Now() + TimeValue("00:00:05")
Application.OnTime ScheduleTime, "Main"
End Sub
'終了用(触らない)
Sub Main_end()
Application.OnTime ScheduleTime, "Main", , False
End Sub
Sub Main()
'ループしたい処理をここへ書く
'行が更新されてvwapが入ってなければvwapを入れる
Dim LastRow
LastRow = Range("B4")
If Cells(LastRow, 8) = "" And Cells(LastRow, 7) <> "" Then
Cells(LastRow, 8) = Range("E3")
End If
'返済判定を行う関数を呼び出す
CloseOrder
'ループ処理(触らない)
ScheduleTime = Now() + TimeValue("00:00:05")
Application.OnTime ScheduleTime, "Main"
End Sub
決済のためのcloseorder関数は以下のようになります。
Function CloseOrder()
Dim LastRow
LastRow = Range("B4")
'時間が11時25分か14時55分ならすべて決済する
If Cells(LastRow, 7) = 1125 Or Cells(LastRow, 7) = 1455 Then
For n = 7 To LastRow
If Cells(n, 9) = "買" Then
Cells(n, 13) = "売"
Cells(n, 16) = Cells(LastRow, 6).Value
ElseIf Cells(n, 9) = "売" Then
Cells(n, 13) = "買"
Cells(n, 16) = Cells(LastRow, 6).Value
End If
Next
GoTo CONTINUE1:
End If
For n = 7 To LastRow
'逆指値価格
Dim stop_order_line
'エントリーがなければなにもしない
If Cells(n, 9) = "" Then
GoTo CONTINUE1:
'エントリー買い&売り返済まだなら決済判定を行う
ElseIf Cells(n, 9) = "買" And Cells(n, 13) <> "売" Then
'エントリー時の終値が5分後の終値より下なら決済のところに売りを入れる
If Cells(n + 2, 7) <> "" And Cells(n + 3, 7) = "" And Cells(n, 6) > Cells(n + 1, 6) Then
Cells(n, 13) = "売"
Cells(n, 16) = Cells(n + 1, 6).Value
'5分後の終値が10分後の終値より下なら決済のところに売りを入れる
ElseIf Cells(n + 3, 7) <> "" And Cells(n + 4, 7) = "" And Cells(n + 1, 6) > Cells(n + 2, 6) Then
Cells(n, 13) = "売"
Cells(n, 16) = Cells(n + 2, 6).Value
'これ以降は10分後の始値と終値の真ん中に逆指値を置き15分後の安値がそれを下回ったら売り(逆指値注文がすでに入っている場合はパス)
ElseIf Cells(n + 4, 7) = "" And Cells(n, 15) = "" And Cells(n + 3, 7) <> "" Then
stop_order_line = (Cells(n + 2, 3).Value + Cells(n + 2, 6).Value) / 2
Cells(n, 15) = stop_order_line
'エントリーから15分後は利確
ElseIf Cells(n + 4, 7) <> "" And Cells(n + 5, 7) = "" And Cells(n, 15) <> "" And Cells(n, 14) = "" Then
Cells(n, 13) = "売"
Cells(n, 16) = Cells(n + 3, 6).Value
End If
'エントリー売り&買い返済まだなら決済判定を行う
ElseIf Cells(n, 9) = "売" And Cells(n, 13) <> "買" Then
'エントリー時の終値が5分後の終値より下なら決済のところに売りを入れる
If Cells(n + 2, 7) <> "" And Cells(n + 3, 7) = "" And Cells(n, 6) < Cells(n + 1, 6) Then
Cells(n, 13) = "買"
Cells(n, 16) = Cells(n + 1, 6).Value
'5分後の終値が10分後の終値より下なら決済のところに売りを入れる
ElseIf Cells(n + 3, 7) <> "" And Cells(n + 4, 7) = "" And Cells(n + 1, 6) < Cells(n + 2, 6) Then
Cells(n, 13) = "買"
Cells(n, 16) = Cells(n + 2, 6).Value
'これ以降は10分後の始値と終値の真ん中に逆指値を置き15分後の安値がそれを下回ったら売り(逆指値注文がすでに入っている場合はパス)
ElseIf Cells(n + 4, 7) = "" And Cells(n, 15) = "" And Cells(n + 3, 7) <> "" Then
stop_order_line = (Cells(n + 2, 3).Value + Cells(n + 2, 6).Value) / 2
Cells(n, 15) = stop_order_line
Cells(n, 16) = stop_order_line
'エントリーから15分後は利確
ElseIf Cells(n + 4, 7) <> "" And Cells(n + 5, 7) = "" And Cells(n, 15) <> "" And Cells(n, 14) = "" Then
Cells(n, 13) = "買"
Cells(n, 16) = Cells(n + 3, 6).Value
End If
End If
CONTINUE1:
Next
End Function
テストトレードの仕方
テストトレードは1株から買える1458や1459で行いました。これによって出た損失(テスト費用)は多くても200円くらいだったのでかなりコストを抑えられます。