前回は「Backtrader」を使って学習済みデータでProtra風バックテストを試み断念しました。
そもそもProtra風にバックテスト結果を出力したいなら、Protraを使うべきでは?
いやいや・・。
Protraは学習データ「pickle」は読み込めないし、そもそも機械学習の機能なんて無いでしょ?
無理無理。
考えるだけ無駄無駄・・・。
[引用] ジョジョの奇妙な冒険
だから、違う方法を模索してるんだよ?
次は、どのライブラリ試そうかなぁ〜♪
ん?・・・まてよ。
- 【4回目】機械学習で株価予測(Backtraderでバックテスト調査)
- 【3回目】機械学習で株価予測(Pythonのバックテストライブラリ調査)
- 【2回目】機械学習で株価予測(TA-LibとLightGBMを使った学習モデル構築)
- 【1回目】機械学習で株価予測(Two SigmaのKaggleコンペを確認)
今回はようやく解決編です。
Protraで学習済みデータのバックテスト
Protraで学習済みデータ「pickle」のロードは無理です。
じゃあ、どうするか?
購入銘柄、購入日をProtraに直接渡してあげる
再利用性はありませんが、例えば次のようなファイルを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 |
def IsBUYDATE if ((int)Code == 7203) if ( \ (Year == 2000 && Month == 2 && Day == 12) || \ (Year == 2020 && Month == 1 && Day == 17) || \ (Year == 3000)) // ダミー return 1 else return 0 end end end |
これは学習済データを使ってアルゴリズムが「買い」と判断した日付の条件式リストです。
これを「LightGBM.pt」とでも名前を付けて「lib」フォルダの中に配置します。
次に、この関数を読み込みます。
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 |
# loop-type: date-only //============================== require "TIlib" require "Utility" require "TrendCheck" require "TOPIX500" require "LightGBM" codes = CodeList if ($code_num && $code_num != Length(codes)) Print("前回と異なる銘柄リストでは実行できません。") Dummy end $code_num = Length(codes) //グローバル変数を初期化 if (!$__INIT__) $budgetIni = 10000000 $buyUnit = 1000000 // 1回の購入資金 (100万円) $MaxHoldDay = 3 // 最大保有日数 $shortSelling = 0 // 空売り戦略 Yes(1)/No(0) $Interest = 1 // 無制限(0) / 単利(1) / 複利(2) $reverse = 0 // 購入順序 昇順(0) / 降順(1) $udcount = 0 // 騰落レシオ利用数 Init() //------------------------------------------------ InitDone() // 騰落レシオ初期化 $__INIT__ = 1 end def Main(i) //================================================== // 条件(買条件, 売条件共通部分) //================================================== //まだ上場していない銘柄は株価データがないためnullが返る if (Index == null) $hold[i] = 0 return end if ! ($order[(int)Code]) $order[(int)Code] = i end //================================================== // 保有してない→購入 //================================================== if (! $hold[i]) //================================================== // 売買(買い) //================================================== if (IsBUYDATE()) $buyflag[i][0] = 1 $buyflag[i][1] = Close // 好きなパラメータをもとにソート $buyCnt = $buyCnt + 1 end //================================================== // 保有している→売却 //================================================== elsif ($hold[i]) if ($set[i] < 1) $set[i] = 1 return end $set[i] = $set[i] + 1 //================================================== // 売買(売り) //================================================== if ($set[i] >= $MaxHoldDay) PrintLog("手仕舞い") $sellflag[i] = 1 $set[i] = 0 end end 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 ($sellflag[i]) Selling(i) $sellflag[i] = 0 end // 使用した$buyflag 配列を初期化 if ($buyflag[i][0]) $buyflag[i][0] = 0 $buyflag[i][1] = 0 end 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 |
ソースコードが長いように感じますが、今までのストラテジーのテンプレートそのままです。
新規変更箇所はたった2箇所です。
1 2 3 4 5 6 |
require "LightGBM" //================================================== // 売買(買い) //================================================== if (IsBUYDATE()) |
これで、バックテストが実施可能です。
シンプル学習モデルの有効性検証
過去のストラテジーテンプレートを使ったので次の通りです。
【資金管理条件】
- 1) 銘柄選定(トヨタ)
- 2) 1回の購入資金 (100万円)
- 3) 投資総額 (1000万円)
- 4) 単利利用
【買いルール】
- 1) LightGBMがSMA、RSI、BB、MACD から自動買い選定
【手仕舞いルール】
- 1) 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 |
株価データ: 日足 銘柄リスト: トヨタ 1998/01/05~2020/01/24における成績です。 ---------------------------------------- 全トレード数 1050 勝ちトレード数(勝率) 530(50.48%) 負けトレード数(負率) 520(49.52%) 全トレード平均利率 0.19% 勝ちトレード平均利率 2.59% 負けトレード平均損率 -2.26% 勝ちトレード最大利率 19.87% 負けトレード最大損率 -15.51% 全トレード平均期間 4.51 勝ちトレード平均期間 4.60 負けトレード平均期間 4.42 ---------------------------------------- 必要資金 ¥1,104,000 最大ポジション(簿価) ¥1,116,000 最大ポジション(時価) ¥1,146,000 純利益 ¥1,371,100 勝ちトレード総利益 ¥10,450,800 負けトレード総損失 -¥9,079,700 全トレード平均利益 ¥1,306 勝ちトレード平均利益 ¥19,718 負けトレード平均損失 -¥17,461 勝ちトレード最大利益 ¥189,000 負けトレード最大損失 -¥126,000 プロフィットファクター 1.15 最大ドローダウン(簿価) -¥903,000 最大ドローダウン(時価) -¥921,000 ---------------------------------------- 現在進行中のトレード数 0 ---------------------------------------- 平均年利 5.91% 平均年利(直近5年) 9.65% 最大連勝 24回 最大連敗 11回 ---------------------------------------- [年度別レポート] 年度 取引回数 運用損益 年利 勝率 PF 最大DD 2020年 2回 ¥26,000円 2.36% 100.00% ∞倍 0.00% 2019年 31回 ¥401,200円 36.34% 90.32% 41.53倍 -0.87% 2018年 39回 ¥218,800円 19.82% 66.67% 2.56倍 -2.47% 2017年 55回 ¥22,300円 2.02% 50.91% 1.09倍 -3.89% 2016年 51回 -¥135,700円 -12.29% 47.06% 0.69倍 -9.91% 2015年 26回 ¥21,800円 1.97% 50.00% 1.13倍 -4.42% 2014年 54回 ¥28,600円 2.59% 51.85% 1.09倍 -7.94% 2013年 54回 ¥381,000円 34.51% 55.56% 2.09倍 -8.26% 2012年 60回 ¥305,100円 27.64% 55.00% 1.61倍 -6.30% 2011年 59回 -¥181,800円 -16.47% 40.68% 0.71倍 -12.81% 2010年 59回 -¥92,200円 -8.35% 50.85% 0.85倍 -10.83% 2009年 57回 ¥14,500円 1.31% 42.11% 1.02倍 -7.88% 2008年 59回 -¥411,000円 -37.23% 49.15% 0.66倍 -15.51% 2007年 33回 ¥18,000円 1.63% 57.58% 1.08倍 -5.76% 2006年 55回 ¥144,000円 13.04% 58.18% 1.62倍 -4.70% 2005年 58回 ¥196,000円 17.75% 62.07% 1.93倍 -2.54% 2004年 60回 ¥160,000円 14.49% 55.00% 1.41倍 -5.11% 2003年 58回 ¥219,500円 19.88% 56.90% 1.45倍 -5.26% 2002年 60回 ¥101,000円 9.15% 46.67% 1.15倍 -6.50% 2001年 60回 -¥97,000円 -8.79% 51.67% 0.88倍 -13.13% 2000年 60回 ¥31,000円 2.81% 46.67% 1.05倍 -8.54% |
利益曲線は次のとおりです。
グニャグニャな曲線です。
久しぶりにここまで曲がったグラフを見ました。
が、なぜか近年の上昇率が極めて高いです。
まとめ
機械学習の学習モデルを使ってProtraでバックテストが可能なことが分かりました。
その結果、やはりExcel等でシュミレーションした時以上に悪い結果になりました。
ただし、落胆していません。
ここから機械学習の知識とシステムトレードの知識を融合すれば、確実にストラテジーが進化していきます。
今まではストラテジーを作っては捨てていましたが、それが無くなります。
すなわち、無駄と思われたストラテジーも一つの特徴データとして活用されます。
ストラテジー作りは、パラメータチューニングして新しいテクニカル指標を試して試行錯誤する・・・と言う作業の連続でした。
機械学習を使えば、学習モデルの構築に工数を割くことになります。
要するに「のめり込めば機械学習にも詳しくなり、株でも儲かる可能性が出てくる」という事です。
すなわち一挙両得!!
これは人類にとっては小さな一歩だが、一人の人間 にとっては偉大な飛躍である。
が、いつもの通り・・・仕組みができたら満足して飽きが・・・。
ソースコード
機械学習側のProtra変換する部分の抜粋です。
さすがに、「Protra + 機械学習」という異色の組み合わせで検討してきるユーザは世の中にいないと思うので全コードは不要かなと思ってます。
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 |
import time import talib import pickle import numpy as np import pandas as pd from lightgbm import LGBMClassifier # テクニカル指標 def add_new_features(df): # Simple Moving njAverage close = df['close'] df['sma3'] = talib.SMA(close, timeperiod=3) df['sma5'] = talib.SMA(close, timeperiod=5) df['sma25'] = talib.SMA(close, timeperiod=25) df['sma50'] = talib.SMA(close, timeperiod=50) df['sma75'] = talib.SMA(close, timeperiod=75) df['sma100'] = talib.SMA(close, timeperiod=100) #display_chart( [[df['sma5'],"sma5"],[df['sma25'],"sma25"],[df['sma50'],"sma50"], # [df['sma75'],"sma75"],[df['sma100'],"sma100"]], df['date']) # Bollinger Bands df['upper1'], middle, df['lower1'] = talib.BBANDS(close, timeperiod=25, nbdevup=1, nbdevdn=1, matype=0) df['upper2'], middle, df['lower2'] = talib.BBANDS(close, timeperiod=25, nbdevup=2, nbdevdn=2, matype=0) df['upper3'], middle, df['lower3'] = talib.BBANDS(close, timeperiod=25, nbdevup=3, nbdevdn=3, matype=0) #display_chart( [[df['upper1'],"upper1"],[df['lower1'],"lower1"],[df['upper2'],"upper2"], # [df['lower2'],"lower2"],[df['upper3'],"upper3"],[df['upper1'],"lower3"]], df['date']) # MACD - Moving Average Convergence/Divergence df['macd'], df['macdsignal'], df['macdhist'] = talib.MACD(close, fastperiod=12, slowperiod=26, signalperiod=9) #display_chart( [[df['macd'],"macd"],[df['macdsignal'],"macdsignal"],[df['macdhist'],"macdhist"]], df['date']) # RSI - Relative Strength Index df['rsi9'] = talib.RSI(close, timeperiod=9) df['rsi14'] = talib.RSI(close, timeperiod=14) #display_chart( [[df['rsi9'],"rsi9"],[df['rsi14'],"rsi14"]], df['date']) return df # Protraファイルの作成 def create_protra_dataset(code, date, y): # 利益が高いと判定したものだけ残す y = np.where(y < 100, False, True) s = "def IsBUYDATE\n" s += " if ((int)Code == " + code + ")\n" s += " if ( \\\n" for i in range(len(y)): if(y[i]): (year, month, day) = date[i].split('/') s += "(Year == " + str(int(year)) + " && Month == " + str(int(month)) + " && Day == " + str(int(day)) + ") || \\\n" s += " (Year == 3000))\n" s += " return 1\n" s += " else\n" s += " return 0\n" s += " end\n" s += " end\n" s += "end\n" with open("LightGBM.pt", mode='w') as f: f.write(s) def load_model(): clf = None with open('model.pickle', mode='rb') as fp: clf = pickle.load(fp) return clf def main(): df_train = pd.read_csv("7203.csv", skiprows=0, names=("date", "open", "high", "low", "close", "volume")) # 予測値(3日後の始値の上昇値) df_train['target'] = df_train['open'].shift(-3) - df_train['open'].shift(-1) # 曜日追加 df_train['day'] = pd.to_datetime(df_train['date']).dt.dayofweek # 新特徴データ df_train = add_new_features(df_train) # 欠損値を列の1つ手前の値で埋める df_train = df_train.fillna(method='ffill') model = load_model() # dateは不要 date = df_train["date"] df_train = df_train.drop("date", axis=1) df_train = df_train.drop("target", axis=1) y_pred = model.predict(df_train) print(y_pred) create_protra_dataset("7203", date, y_pred) if __name__ == '__main__': main() |