以前の記事「市場トレンドを売買ルールに入れる」で、ルールと売買の関数を独立させる方法を用いました。
今回はその応用編として、売買ルールを満たした銘柄の内 優先順位を付けて購入する方法です。
同日に買いシグナルが複数出た場合、通常の記載法では証券コードが小さい順に買処理がなされます。
しかし予算が制限されていると、全ての銘柄を購入できない場合もあるでしょう。
そこで、ある特徴を有した銘柄を優先的に購入をProtraで実現する方法を考えます。
・投資予算に制限を加える
・共通コードを関数化して可読性を向上させる その2
・共通コードを関数化して可読性を向上させる その3
・本ブログの内容において、正当性を保証するものではありません。
・本ブログを利用して損失を被った場合でも一切の責任を負いません。
・最終的な決定は、ご自身の判断(自己責任)でお願い致します。
優先順位の実装法
最初に実装法を考えてみます。
ソーティング
優先的に購入する方法は、「ある変数の昇順/降順ソーティング」で実現します。例えば、
- 株価が小さい順
- RSIが小さい順
- 移動平均線からの乖離率が大きい順
- 出来高が大きい順
などです。
今回実装するストラテジでは、1つの変数に対して、降順/昇順ソーティングを行います。
2つ以上の変数からソーティングする方法は別の機会に…。重み付け等あって処理が複雑になることが予想されますが、基本的に今回の方法の応用で実装できるはずです。
プログラムの構造
以下の模式図の構造に沿って、コード本体を作成します。
Main関数は、コードを数行追加するだけです。
ソート処理は、(私のプログラム知識の無さも相まって)構造は複雑ですが 降順/昇順ソーティングするだけです。
システムトレード特有の売買処理は、Protraの構造に沿って記載法を考える必要があります。
前置きはここまでにして、次章から実装していきます。
実装
Main関数
Main関数で通常のストラテジと異なる点は、「ルールを満たした銘柄の記録」,「ソート対象の変数の記録」だけです。
早速コードを書いてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#loop-type: date-only codes = CodeList #==== 準備 ==== if ! $__INIT__ $buyflag = [$code_num*2] i = 0 while i < $code_num $buyflag[i] = [2] i = i + 1 end $sellflag = [$code_num] $buyCnt = 0 $__INIT__ = 1 end #==== Main関数 ==== def Main(i) # 条件(買条件, 売条件共通部分) #================================================== if !$hold[i] ~~~~~ # 売買(買い) #-------------------------------------------------- $buyflag[i][0] = 1 $buyflag[i][1] = Close $buyCnt = $buyCnt+1 # 条件2 #================================================== elsif $hold[i] ~~~~~~~ # 売買(売り) #-------------------------------------------------- $sellflag[i] = 1 end end #==== ループ処理 ==== $buyCnt = 0 i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Main(i) end |
全体の構造がわかりやすいように必要箇所のみ記載しています。
・・・
詳細を見ていきます。
1 |
#loop-type: date-only |
1行目のこの部分は、お馴染みの銘柄ごとのシステムの実行を停止するコマンドです。
↓以下の記事でも触れています。
このコマンドがないと、グローバル変数の値は銘柄をまたいで引き継がれないため必須です。
1 2 3 4 5 6 7 8 |
$buyflag = [$code_num*2] i = 0 while i < $code_num $buyflag[i] = [2] i = i + 1 end $sellflag = [$code_num] $buyCnt = 0 |
ソート処理のために、3つのグローバル変数を用意しています。
$buyflag
は2次元配列で、「ルールを満たした買い銘柄」と「ソート対象の変数」を記録します。
$sellflag
は、「ルールを満たした売り銘柄」を記録します。
$buyCnt
は、後々の処理のために、買い銘柄を満たした銘柄数を記録します。
1 2 3 4 5 |
$buyflag[i][0] = 1 $buyflag[i][1] = Close $buyCnt = $buyCnt+1 #(中略) $sellflag[i] = 1 |
用意したグローバル変数に、値を代入しています。
買い/売りルールを満たした銘柄には、$buyflag[i][0]
,$sellflag[i]
に1を入れています。
$buyflag[i][1]
には、ソート対象の変数を入れます。今回は例として、株価(終値)= Close
を入れました。
ソート処理
Main関数で買い条件を満たした銘柄を、降順,昇順ソートします。
ソート処理を考える上で、Protraの関数特性を1つ理解する必要があります。
それが、string型@作用素。
string型@作用素は、以下のようにstring型の式を{}で囲って式に対して指定します。対象の式に組み込み関数があると、組み込み関数の対象となる銘柄が値で指定した証券コードの銘柄になります。
ーマニュアルより
実は、これまでのコードで 既にstring型@作用素は使用されていました。
それがループ処理の部分。
1 2 3 4 5 6 7 |
codes = CodeList (略) while i + 1 < $code_num i = i + 1 {codes[i]}Main(i) end |
CodeList
はProtraの組み込み関数で、ストラテジ対象となる銘柄の配列が、証券コードが小さい順に格納されています。
例)CodeListのイメージ
例)対象銘柄をJASDAQと選択した場合
従って{codes[i]}Main(i)
は、証券コードが小さい順にMain関数を実行しており、Main関数の中では、特に明示がなくても組み込み関数(Close
,Volume
等)には対象銘柄の値が用いられます。
同様の手法で、降順/昇順ソーティングが実施できそうです。
- 昇順 / 降順に並べた配列を作る
- 配列の順番の通りに買い実行する(次節)
・・・
早速コードを書いてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
#loop-type: date-only #==== 準備 ==== if ! $__INIT__ $buyflag = [$code_num*2] i = 0 while i < $code_num $buyflag[i] = [2] i = i + 1 end $sellflag = [$code_num] $buyCnt = 0 $sortCnt = 0 $sortList1 = [$code_num] $sortList2 = [$code_num] i = 0 while i < $code_num $sortList1[i] = -999999 //初期値 or 999999 $sortList2[i] = 0 //初期値 i = i + 1 end $__INIT__ = 1 end #==== Main関数 ==== def Main(i) (略) end #==== ソート処理 ==== def Sort(i) a1 = 0 a2 = 0 if $buyflag[i][0] while a1 < $buyCnt if a1 == $sortCnt $sortList1[a1] = $buyflag[i][1] //値を置き換え $sortList2[a1] = Code //証券コードを記録 $sortCnt = $sortCnt + 1 break elsif $buyflag[i][1] > $sortList1[a1] while a2 < $buyCnt - a1 - 1 $sortList1[$buyCnt-a2-1] = $sortList1[$buyCnt-a2-2] //値 並び替え $sortList2[$buyCnt-a2-1] = $sortList2[$buyCnt-a2-2] //証券コード並び替え a2 = a2 + 1 end $sortList1[a1] = $buyflag[j][1] //値を置き換え $sortList2[a1] = Code //証券コードを記録 $sortCnt = $sortCnt + 1 break a1 = a1 + 1 end $buyflag[i][0] = 0 $buyflag[i][1] = 0 end end #==== ループ処理 ==== i = -1 while i + 1 < $buyCnt i = i + 1 $sortList1[i] = -999999 //or999999 $sortList2[i] = 0 end $buyCnt = 0 $sortCnt = 0 i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Main(i) end i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Sort(i) end |
今回は降順ソートとしています。
・・・
詳細を見ていきます。
1 2 3 4 5 6 7 8 9 |
$sortCnt = 0 $sortList1 = [$code_num] $sortList2 = [$code_num] i = 0 while i < $code_num $sortList1[i] = -999999 //初期値 or 999999 $sortList2[i] = 0 //初期値 i = i + 1 end |
新しく3つのグローバル変数を準備しています。
$sortCnt
は、昇順/降順ソーティングで使用します。
$sortList1
は、昇順/降順ソーティングに使用する変数値を保持する配列です。
$sortList2
は、証券コードを記録する配列です。後ほどの買処理で使用します。
whileループ文では初期化をしています。今回は降順ソートのため、$sortList1
の初期値には小さな数を入れています。昇順の場合は大きな数を入れます。
1 2 3 4 5 6 7 |
if $buyflag[i][0] (略) $buyflag[i][0] = 0 $buyflag[i][1] = 0 end |
Sort関数内を見ていきます。
一番外側となる if文では、Main関数で買いルールを満たした銘柄かをチェックしています。
最後に、使用した$buyflag
配列を初期化しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
if $buyflag[i][0] while a1 < $buyCnt if a1 == $sortCnt $sortList1[a1] = $buyflag[i][1] //値を置き換え $sortList2[a1] = Code //証券コードを記録 $sortCnt = $sortCnt + 1 break elsif $buyflag[i][1] > $sortList1[a1] while (略) end $sortList1[a1] = $buyflag[i][1] //値を置き換え $sortList2[a1] = Code //証券コードを記録 $sortCnt = $sortCnt + 1 break a1 = a1 + 1 end $buyflag[i][0] = 0 $buyflag[i][1] = 0 end |
続いて、最初のwhileループ処理とif文です。2つの条件でわけています。
$sortCnt
は、$sortList1,2
配列に、今どれだけの数が入っているかを記録しています。
●最初のif文は、a1
が$sortCnt
と等しい場合です。a1
は$sortList1[]
を0から見ていくための変数です。
その a1
が $sortCnt
と等しくなるということは、今回のソート対象変数が、現在$sortList1
に入っている変数の中でもっとも大きい or 小さい ことを意味します。
イメージはこんな感じ。
●続いての elsif文では、実際にソート対象変数の値を比較しています。
それぞれの if文内の最後で、$sortList1,2
にソート対象変数と証券コードを記録しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
if $buyflag[i][0] while a1 < $buyCnt if (a1 == $sortCnt) && $sortCnt $sortList1[a1] = $buyflag[i][1] //値を置き換え $sortList2[a1] = Code //証券コードを記録 $sortCnt = $sortCnt + 1 break elsif $buyflag[i][1] > $sortList1[a1] while a2 < $buyCnt-a1-1 $sortList1[$buyCnt-a2-1] = $sortList1[$buyCnt-a2-2] //値 並び替え $sortList2[$buyCnt-a2-1] = $sortList2[$buyCnt-a2-2] //証券コード並び替え a2 = a2 + 1 end $sortList1[a1] = $buyflag[j][1] //値を置き換え $sortList2[a1] = Code //証券コードを記録 $sortCnt = $sortCnt + 1 break a1 = a1 + 1 end $buyflag[i][0] = 0 $buyflag[i][1] = 0 end |
ソート関数内の最後の説明は、elsif文内のループについてです。
このwhileループ内で、配列内の値置換を行っています。
これで昇順/降順ソートができました!
実際に必要なのは、証券コードが格納されている、$sortList2[]
となります。
試しにストラテジを動かしてみましょう。コードにPrint文を追加して、動作を確認してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
if a1 == $sortCnt $sortList1[a1] = $buyflag[i][1] $sortList2[a1] = Code $sortCnt = $sortCnt + 1 if $buyCnt > 1 i1 = 0 Print("") Print("Close= " +$buyflag[i][1]) while i1 < $buyCnt Print("Code: " + $sortList2[i1] + " | " + $sortList1[i1]) Print("----------------------------------------") i1 = i1 + 1 end end break elsif $buyflag[i][1] > $sortList1[a1] while a2 < $buyCnt - a1 - 1 $sortList1[$buyCnt-a2-1] = $sortList1[$buyCnt-a2-2] $sortList2[$buyCnt-a2-1] = $sortList2[$buyCnt-a2-2] a2 = a2 + 1 end $sortList1[a1] = $buyflag[i][1] $sortList2[a1] = Code $sortCnt = $sortCnt + 1 if $buyCnt > 1 i1 = 0 Print("") Print("Close= " +$buyflag[i][1]) while i1 < $buyCnt Print("Code: " + $sortList2[i1] + " | " + $sortList1[i1]) Print("----------------------------------------") i1 = i1 + 1 end end break end |
対応する証券コードと共に、値が降順に配列に格納されていることを確認できました。
売買関数
ソート処理のあと実際に売買を行います。
$sortList2[]
に格納された順に買いを実施します。繰り返しになりますが、$sortList2[]
には証券コードが入っています。
コードを書いてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#loop-type: date-only #==== 準備 ==== (略) #==== Main関数 ==== def Main(i) (略) end #==== ソート処理 ==== def Sort(i) (略) end #==== 買い処理 ==== def SortBuy(i) if PricedataExistCheck(Close) return end $long = 0 $long = Num($buyUnit,Close) Buying(i) end #==== 売り処理 ==== def Sell_(i) if PricedataExistCheck(Close) return end if $sellflag[i] Selling(i) $sellflag[i] = 0 end end #==== ループ処理 ==== i = -1 while i + 1 < $buyCnt i = i + 1 $sortList1[i] = -999999 //or9999 $sortList2[i] = 0 end $buyCnt = 0 $sortCnt = 0 i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Main(i) end i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Sort(i) end i = -1 while i + 1 < $buyCnt i = i + 1 {$sortList2[i]}SortBuy(i) end i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Sell_(i) end |
いつものループ処理と異なり、買い処理では string型@作用素に$sortList2[]
を使用します(73行目)。
なおBuying()
とSelling()
は、こちらの記事で作成した売買処理関数です。
・・・
動作を確認してみましょう。
証券コードが小さい順ではなく、ソートした順に買われています!
意図したように動作させることができたようです。
…
……
………
アレ??
よく見ると、買い銘柄と売り銘柄が一致していません!
日経平均構成銘柄を対象としてストラテジを動作させたのですが、買われていないはずの 1332 日本水産が売られています。
1332 日本水産は、日経平均構成銘柄でもっとも証券コードが小さい銘柄です。
どうやらProtra内部で「購入した」情報は、 CodeListと同構成の ストラテジ対象銘柄を証券コードが小さい順に並べた配列に記録しているようです。
そこで、以下のようにコードを書き換えました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
#loop-type: date-only #==== 準備 ==== if ! $__INIT__ (途中略) $order = [9999] $__INIT__ = 1 end #==== Main関数 ==== def Main(i) if ! $order[(int)Code] $order[(int)Code] = i end (途中略) end #==== ソート処理 ==== def Sort(i) (略) end #==== 買い処理 ==== def SortBuy(i) if PricedataExistCheck(Close) return end $long = 0 $long = Num($buyUnit,Close) codeset = $order[(int)Code] Buying(codeset) end #==== 売り処理 ==== def Sell_(i) if PricedataExistCheck(Close) return end if $sellflag[i] Selling(i) $sellflag[i] = 0 end end #==== ループ処理 ==== i = -1 while i + 1 < $buyCnt i = i + 1 $sortList1[i] = -999999 //or9999 $sortList2[i] = 0 end $buyCnt = 0 $sortCnt = 0 i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Main(i) end i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Sort(i) end i = -1 while i + 1 < $buyCnt i = i + 1 {$sortList2[i]}SortBuy(i) end i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Sell_(i) end |
新しく、グローバル変数配列$order[]
を準備しました。
$order[]
には、CodeListと同構成の配列を作成します。
1 2 3 |
if ! $order[(int)Code] $order[(int)Code] = i end |
それを実現しているのが↑の部分。
配列番号は証券コードに対応しています。値には、ストラテジに使用する銘柄の中で 証券コードが何番目に小さいか(=i)が代入されます。
ストラテジに使用しない配列の値は null のまま。nullの列を無視すると、CodeListと同じ構成の配列が得られます。
1 2 |
codeset = $order[(int)Code] Buying(codeset) |
あとは買処理の途中で、$order[]
に格納された「買い対象銘柄は、ストラテジに使用している銘柄の中で 証券コードが何番目に小さいか」を呼び出してくればOk。
↑買い/売りを対応させることができました。
まとめ
ここで示した例では、ソート対象変数を株価(終値),降順に並べました。
$buyflag[i][1]
に代入する変数を変えることで、好きなパラメータをもとにソートできます。
昇順/降順は不等号を入れ替えるだけです。
コードがかなーり読みづらくなってしまったので、次回は処理部分を外部関数化していきます。
それでは、ここまでご覧頂き ありがとうございました。