今までのあらすじ。
最新の技術は当然の把握
メールのスレッドも通勤時チェック
強靭な精神で終電までワーク
労働者じゃ当然のルールです。
うっせぇ、うっせぇ、うっせぇわ!
と叫んだところで、日本は資本主義。
資本主義とは
資本家が儲かる仕組みになっている社会
資本家とは、いわば「投資家」
労働者であるサラリーマンでいる限り、いつまでたっても「理不尽さ」との戦いは終わりを迎えない。
これは、システムトレードの構築により資本家へと変貌するサクセスストーリー。
を夢見て中年となった男性の 堕落と破滅までの記録。
システムトレード部門で雇って、投資ファンド様。
前回、明地文男氏の3Days投資法の中身を暴露した。
だけど、氏の書いたVBAが読みたかったわけじゃない。
今回は、理解した3Days投資法をProtraで実装し有効性検証(バックテスト)を行う。
明地文男氏の3Days投資法の有効性検証
前回分かったことは、
実装が超面倒
今まで大量に実装してきたから、実装難易度が事前に分かるようになってきた。
この手法の実装には、体全体で拒否反応を出している。
指が嫌がって、しばらくキーボード上から逃げ出した。
キーボードから逃げて、イチゴに爪楊枝刺して遊んでてたわ。
何をやっとんだ!
案の定、この実装で土曜日と日曜日が潰れた。
僕は老い先短い貴重な週末に一体何をしているのでしょうか……?
【基本設定】
- 対象は「ディフェンシブ テーマ株」
- 終値100円以下は対象外
- 単利利用、通年
【買いルール】
- 始値から○円(AP値)下がったら買い
【手仕舞いルール】
- 3日間中に○円(始値+AP値+BP値)になれば売り
- 上記を満たさなければ3日後の終値で手仕舞い
ストラテジーに書き起こすと超簡単。
だけど、このAP値、BP値を求めるのが超面倒。
多分、バックテストにチャレンジした人は世界初だと思う。
AP値、BP値の範囲検出
久しぶりにProtraの「TIlib.pt」を書き換えた。
ライブラリになっている方が何か格好良いよね!
というだけの理由……。
独特の書き方に慣れてないから何度もハマって土曜日が潰れたわ。
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# 3Days分析法 def Days3_new(days) obj = [15] obj[0] = null // DP max obj[1] = null // DP min obj[9] = null // UP max obj[10] = null// UP min obj[2] = days // days obj[3] = 0 // index of max obj[4] = 0 // index of min obj[11] = null // index of UP max obj[12] = null// index of UP min obj[5] = 0 // traded days of max obj[6] = 0 // traded days of min obj[13] = 0 // traded days of UP max obj[14] = 0 // traded days of UP min obj[7] = 0 obj[8] = Index goback = days while 1 obj[3] = Index - goback obj[4] = obj[3] obj[11] = Index - goback obj[12] = obj[11] obj[7] = - goback while (obj[7] < 0) Days3_next(obj) end if obj[0] != null || obj[9] != null || Index - goback < 0 break end goback = goback * 2 obj[5] = 0 obj[6] = 0 obj[13] = 0 obj[14] = 0 end obj[7] = at obj[8] = {-at}Index return obj end def Days3_next(obj) obj[7] = obj[7] + 1 - Index + obj[8] obj[8] = Index if !{obj[7]}Volume return end obj[5] = obj[5] + 1 obj[6] = obj[6] + 1 obj[13] = obj[13] + 1 obj[14] = obj[14] + 1 if !obj[0] && obj[5] < obj[2] return end if !obj[0] && obj[13] < obj[2] return end i = obj[7] days = 0 if obj[5] == obj[2] || obj[6] == obj[2] || obj[13] == obj[2] || obj[14] == obj[2] obj[0] = null obj[1] = null obj[9] = null obj[10] = null i = Min(Min(obj[3], obj[4]), Min(obj[11], obj[12])) + 1 - Index days = obj[2] - 1 end while i <= obj[7] if {i}Volume && {i + 1}Volume && {i + 2}Volume open = {i}Open close = {i}Close high_1 = {i + 1}High high_2 = {i + 2}High low = {i}Low index = {i}Index // DP MAX if !obj[0] || obj[0] < low - open obj[0] = low - open obj[3] = index obj[5] = days end // DP MIN if !obj[1] || obj[1] > low - open obj[1] = low - open obj[4] = index obj[6] = days end // UP MAX up = Max(close, Max(high_1, high_2)) - low if !obj[9] || obj[9] < up obj[9] = up obj[11] = index obj[13] = days end // UP MIN up = Max(close, Max(high_1, high_2)) - low if !obj[10] || obj[10] > up obj[10] = up obj[12] = index obj[14] = days end days = days - 1 end i = i + 1 end end def Days3_dpmax(obj) return obj[0] end def Days3_dpmin(obj) return obj[1] end def Days3_upmax(obj) return obj[9] end def Days3_upmin(obj) return obj[10] end |
合ってるのかもよく分かってない。
氏のExcelと答え合わせをしようと思ったけど一致しなかった。
氏の実装に不具合があるような……50日計算じゃなく20日計算で重複している部分があるよ?
最適なAP値、BP値を算出
この実装で日曜日が潰れた。
そして、これを真面目に実装すると面倒だ。
今回は利益率と取引数だけの簡易的な算出判定とした。
- 利益率が0.9より大きい
- 取引数が13以下
この閾値は「特A」銘柄を選んだ想定。
なお、氏のExcelでは50日間のバックテストをしているが、時間削減のために20日でAP値、BP値を算出した。
バックテスト結果
「ディフェンシブ テーマ株」は下記の一覧を利用した。ただし原発事故があったので電力会社は抜いている。
なお、高々50銘柄に対してバックテスト計算時間は
21時間……
途中で不具合で止まったときは発狂したわ。
3回止まったからね!
合計3日かかったわ!
で、期待のバックテスト結果は次の通り。
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 |
株価データ: 日足 銘柄リスト: ディフェンシブ(電力抜き) 1998/01/05~2021/04/16における成績です。 ---------------------------------------- 全トレード数 874 勝ちトレード数(勝率) 577(66.02%) 負けトレード数(負率) 297(33.98%) 全トレード平均利率 1.49% 勝ちトレード平均利率 3.91% 負けトレード平均損率 -3.22% 勝ちトレード最大利率 16.35% 負けトレード最大損率 -19.04% 全トレード平均期間 2.97 勝ちトレード平均期間 3.02 負けトレード平均期間 2.88 ---------------------------------------- 必要資金 ¥2,195,100 最大ポジション(簿価) ¥2,970,100 最大ポジション(時価) ¥3,005,100 純利益 ¥5,410,600 勝ちトレード総利益 ¥9,256,000 負けトレード総損失 -¥3,845,400 全トレード平均利益 ¥6,191 勝ちトレード平均利益 ¥16,042 負けトレード平均損失 -¥12,947 勝ちトレード最大利益 ¥77,000 負けトレード最大損失 -¥76,800 プロフィットファクター 2.41 最大ドローダウン(簿価) -¥360,100 最大ドローダウン(時価) -¥434,900 ---------------------------------------- 現在進行中のトレード数 0 ---------------------------------------- 平均年利 11.20% 平均年利(直近5年) 6.78% 最大連勝 14回 最大連敗 5回 ---------------------------------------- [年度別レポート] 年度 取引回数 運用損益 年利 勝率 PF 最大DD 2021年 3回 ¥11,700円 0.53% 33.33% 1.70倍 -3.56% 2020年 101回 ¥428,900円 19.54% 62.38% 1.80倍 -10.88% 2019年 10回 ¥70,800円 3.23% 70.00% 3.73倍 -2.68% 2018年 18回 ¥91,900円 4.19% 66.67% 2.41倍 -4.68% 2017年 10回 ¥141,000円 6.42% 90.00% 471.00倍 -0.06% 2016年 38回 ¥229,200円 10.44% 73.68% 3.31倍 -8.06% 2015年 42回 ¥254,000円 11.57% 66.67% 2.68倍 -6.85% 2014年 16回 ¥194,500円 8.86% 81.25% 9.42倍 -4.63% 2013年 55回 ¥286,100円 13.03% 69.09% 2.07倍 -9.02% 2012年 5回 ¥11,400円 0.52% 60.00% 2.02倍 -2.09% 2011年 12回 ¥178,400円 8.13% 91.67% 7.66倍 -5.64% 2010年 1回 ¥17,700円 0.81% 100.00% ∞倍 0.00% 2009年 21回 ¥346,800円 15.80% 85.71% 21.40倍 -2.13% 2008年 131回 ¥449,900円 20.50% 59.54% 1.49倍 -19.04% 2007年 18回 ¥91,700円 4.18% 66.67% 2.56倍 -4.45% 2006年 46回 ¥282,400円 12.87% 73.91% 3.61倍 -4.02% 2005年 48回 ¥413,200円 18.82% 70.83% 6.98倍 -2.34% 2004年 9回 ¥70,500円 3.21% 66.67% 4.49倍 -3.37% 2003年 29回 ¥263,200円 11.99% 82.76% 4.62倍 -5.77% 2002年 31回 ¥234,600円 10.69% 61.29% 2.76倍 -8.56% 2001年 78回 ¥569,500円 25.94% 70.51% 2.78倍 -11.42% 2000年 152回 ¥773,200円 35.22% 61.18% 1.86倍 -18.73% |
おや?単調増加?
利益曲線は次の通り。
おおーーーー!
まさかの単調増加じゃん!
プロフィットファクターも高いし、平均利益も高い。
勝率は想定より低いけど、これは氏の購入候補判定ロジックを正しく実装すれば上がるかもしれない。
20年間で取引数が800回程度なので、閾値が厳しすぎかなぁ……。
でも50銘柄だし、こんなものなのかなぁ……。
まとめ
ソースコードだらけの日記になった。
まぁ、いつもの事だけどさ。
テクニカルトレードで1銘柄を分析するより、全対象銘柄からシグナルを見つけ出すシステムトレードの方が網羅的かつ大局的で好きだわ。
他にも「まろ流3D投資法」など、明地氏の手法をカスタマイズした手法も提案されてる。
- 50日前と比べて急騰していないこと
- 乖離率・RSIからみて過熱していないこと
- 寄り付きが高い場合は見送るか・前日終値よりAPを算出
- BP勝率は原則85%以上になるようにする
- BPは黄金分割比も参考にする
今の取引数を増やすような閾値に変更した上で、乖離率などを追加すれば、より実践的な手法になるかもしれない。
試行錯誤したいけど21時間は待てないなぁ……。
ソースコード
バックテストには無料OSSの「Protra」を利用した。
TIlib、Utility、TrendCheckライブラリはGitHubに置いている。
今までで一番長い。
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# loop-type: date-only //============================== require "TIlib" require "Utility" require "TrendCheck" // 3Days投資法 codes = CodeList if ($code_num && $code_num != Length(codes)) Print("前回と異なる銘柄リストでは実行できません。") Dummy end $code_num = Length(codes) //グローバル変数を初期化 if (!$__INIT__) $budgetIni = 3000000 $buyUnit = 500000 // 1回の購入資金 (50万円) $MaxHoldDay = 0 // 最大保有日数 $shortSelling = 0 // 空売り戦略 Yes(1)/No(0) $Interest = 1 // 無制限(0) / 単利(1) / 複利(2) $reverse = 0 // 購入順序 昇順(0) / 降順(1) $udcount = 0 // 騰落レシオ利用数 Init() // テクニカル指標初期化 -------------------------- $Days3 = [$code_num] // 騰落レシオ初期値代入($udcountの数) ---------- //------------------------------------------------ InitDone() // 騰落レシオ初期化 $__INIT__ = 1 end // 刻み def Kizami(val) val = Abs(val) if (val >= 30000000.1) return 10000 elsif (val >= 10000000.1) return 5000 elsif (val >= 3000000.1) return 1000 elsif (val >= 1000000.1) return 500 elsif (val >= 300000.1) return 100 elsif (val >= 100000.1) return 50 elsif (val >= 30000.1) return 10 elsif (val >= 10000.1) return 5 elsif (val >= 3000.1) return 1 elsif (val >= 1000.1) return 0.5 end return 0.1 end // i 日前の 買い/売り による儲け確認 // [Return] // -1: 買えなかった / 0: 売れなかった / 1: 売れた def Buy3Days(ap, bp, i) if ! ({i}Open && {i}Low && {i}Close && {i + 2}Close && {i + 1}High && {i + 2}High) return -1 end buy = {i}Open + ap sell = buy + bp // low がこの数値より大きいか? if ! ({i}Low <= buy) return -1 end // 3日以内にこの数値になるか? if ! ({i}Close > sell || {i + 1}High > sell || {i + 2}High > sell) // ならないなら 3日後の Close で売る return 0 // {i + 2}Close - buy end // 利益はBP return 1 // bp end // 結果の最頻値を求める def Days3_mode(dpmax, dpmin, upmax, upmin) kizami = Kizami(Close) dp_max = Min(dpmax, -1 * kizami) dp_min = Min(dpmin, -1 * kizami) dp_inc = Min((dp_min - dp_max) / 20, -1 * kizami) up_max = Max(upmax, kizami) up_min = Max(upmin, kizami) up_inc = Max((up_max - up_min) / 20, kizami) ap1 = (int)(dp_max) ap2 = (int)((dp_inc * 20 + dp_max)/kizami ) * kizami apinc = (int)(dp_inc) bp1 = (int)(up_min) bp2 = (int)((up_inc * 20 + up_min)/kizami ) * kizami bpinc = (int)(up_inc) // 20日間の400通りのバックテストを行う max = 401 array_profit = [max] array_cnt = [max] // 取引数 i = 0 while i < max array_profit[i] = 0 array_cnt[i] = 0 i = i + 1 end array_ap = [max] array_bp = [max] i = 0 while i < 20 // AP j = 0 while j < 20 // BP m = 0 while m < 20 // 20日 if ({i + 2}Close && {i}Open) flag = Buy3Days(ap1 + apinc * i, bp1 + bpinc * j, -1 * m) array_ap[i * 20 + j] = ap1 + apinc * i array_bp[i * 20 + j] = bp1 + bpinc * j profit = 0 if (flag == 0) profit = {i + 2}Close - {i}Open + ap1 + apinc * i // {i + 2}Close - buy array_cnt[i * 20 + j] = array_cnt[i * 20 + j] + 1 end if (flag == 1) profit = bp1 + bpinc * j // bp end if (profit) array_profit[i * 20 + j] = array_profit[i * 20 + j] + profit end end m = m + 1 end j = j + 1 end i = i + 1 end // 最も利益率が高いAP/BP結果を返す(Sort不要) most_profit = -999999 most_ap = 0 most_bp = 0 most_cnt = 0 i = 0 while i < max // AP if (most_profit < array_profit[i]) most_profit = array_profit[i] most_ap = array_ap[i] most_bp = array_bp[i] most_cnt = array_cnt[i] end i = i + 1 end $Most_AP = most_ap $Most_BP = most_bp $Most_PF = -9999 if (most_cnt && (most_ap + Open) != 0) $Most_PF = (float)most_profit / ((most_ap + Open) * Unit) * 100 end $Most_CNT = most_cnt end def Main(i) //================================================== // 条件(買条件, 売条件共通部分) //================================================== //まだ上場していない銘柄は株価データがないためnullが返る if (Index == null) return end if ! ($order[(int)Code]) $order[(int)Code] = i end if ! ($Days3[i]) // 銘柄ごとのグローバル変数を初期化する $Days3[i] = Days3_new(20) $hold[i] = 0 return end // 指標の計算を1日進める Days3_next($Days3[i]) //================================================== // 保有してない→購入 //================================================== if (! $hold[i]) dpmax = Days3_dpmax($Days3[i]) dpmin = Days3_dpmin($Days3[i]) upmax = Days3_upmax($Days3[i]) upmin = Days3_upmin($Days3[i]) // AP・BPを求める $Most_AP = 0 $Most_BP = 0 $Most_PF = 0 $Most_CNT = 0 if (upmax) Days3_mode(dpmax, dpmin, upmax, upmin) end if ! (Close) return end //================================================== // 売買(買い) //================================================== if ! (Close >= 100) return end if ($Most_PF >= 0.9 && 13 >= $Most_CNT) PrintLog("購入候補") $buyflag[i][0] = $Most_AP + Close $buyflag[i][1] = $Most_PF $buyflag[i][2] = $Most_AP + Close + $Most_BP // 売り予定額 $buyCnt = $buyCnt + 1 end //================================================== // 保有している→売却 //================================================== elsif ($hold[i]) if ($set[i] < 1) $set[i] = 1 return end $set[i] = $set[i] + 1 if ! (Close) return end //================================================== // 売買(売り) //================================================== if ($set[i] >= $MaxHoldDay) PrintLog("利益確定") $sellflag[i] = 1 $set[i] = 0 end end end def CheckHighLow2(t) if ! ({1}High > t && t > {1}Low) return 0 end return t end //================================================== // 買い(指値) //================================================== def Buying2(i) if (HasPricedata({1}Open) && HasPricedata(Close) _ && HasPricedata({1}Low) && HasPricedata({1}High)) t = 0 if ($buyflag[i][0]) t = CheckHighLow2($buyflag[i][0]) end if (t) BuyingLimitedPrice(i, 1, t) end end end //==================== // 売り処理(デイトレ模倣) //==================== def Sell_(i) if ($sellflag[i]) // 売り予定額 sell = $buyflag[i][2] // 同一株購入が発生するので、翌日売りとして表現しておく // 当日 if ({-1}Close > sell) // 当日のClose、翌日の高値、翌々日の高値まで SellingLimitedPrice(i, 1, (int)sell) // 翌日 elsif (High > sell) // 当日のClose、翌日の高値、翌々日の高値まで SellingLimitedPrice(i, 1, (int)sell) // 翌々日 elsif ({1}High > sell) // 当日のClose、翌日の高値、翌々日の高値まで SellingLimitedPrice(i, 1, (int)sell) // 翌々日のCloseで売る elsif ({1}Close) // 超えてなければ、2日後のCloseで売る SellingLimitedPrice(i, 1, {1}Close) else PrintLog("売れない") end $sellflag[i] = 0 $buyflag[i][2] = 0 end // 使用した$buyflag 配列を初期化 if ($buyflag[i][0]) $buyflag[i][0] = 0 $buyflag[i][1] = 0 end end //==================== // 買い処理 //==================== def SortBuy(i) if (PricedataExistCheck(Close)) return end $long = 0 $long = Num($buyUnit, Close) codeset = $order[(int)Code] Buying2(codeset) end //==================== // 銘柄コードを変えながらMain関数,BuySell関数を実行 //==================== Print("-------------------------------------------------") Print("日付 = "+ Year + "/" + Month + "/" + Day) SortInit() // ソート初期化 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 if ($buyCnt) while i + 1 < $buyCnt i = i + 1 {$sortList2[i]}SortBuy(i) end end i = -1 while i + 1 < $code_num i = i + 1 {codes[i]}Sell_(i) end |