「信用取引残高」は 相場の先行きを予測するのに活用できる。
という事で前回システムトレードソフト「Protra」で使えるように試みた。
自分の用途にデータを限定して「400MB」というサイズまで落とすことができたけど、これだと他の人が恩恵を受けないよね?
今回は全銘柄の全データをProtraで読めるようにしてみた。
そのライブラリもGithubにUploadしている。
信用残高のデータは大きく分けて3つある。
- 証券取引所が発表するもの(東京・名古屋の二市場の残高をまとめた「二市場信用取引残高」)
- 証券取引所が発表するもの(各取引所から発表される「銘柄別信用取引残高」)
- 証券金融会社の日証金が発表する「日証金貸借取引残高」
ネットで簡単に入手できる信用残高は「証券取引所」のものなので、一週間単位の更新データとなっている。
このため残りの日付は欠損値として直前の値で埋めていた。機械学習の考え方ね。
1 2 3 4 5 6 7 8 9 10 11 12 |
if (Year == 2001 && Month == 2 && Day == 19) return 2.26 end if (Year == 2001 && Month == 2 && Day == 20) return 2.26 end if (Year == 2001 && Month == 2 && Day == 21) return 2.26 end if (Year == 2001 && Month == 2 && Day == 22) return 2.26 end |
でもファイルサイズが大きくなって読み込めない。
これ欠損値を計算で求めたらサイズも 80% 程度は圧縮できるんじゃない?
日付をシリアル値に変換する
Excelの日付データは、1900年1月1日を「1」とした連番(シリアル値)で管理されている。
例えば、和暦の表示形式を設定してあるセルに「2014/1/1」と入力すると「平成26年1月1日」と表示されるが、実際に入力されているデータの値は「41730」という数値。
これはPythonなどでは変換関数が用意されているが、Protraでは独自で関数を作らないといけない。
閏年の計算とか、月ごとに30日、31日とかあるので、サンプルコードとして日本のブログで紹介されている内容は長くて微妙だった。
海外のサイトに数行で実現しているコードを発見!
本当に こんなに短いコードで実現できるの?実験してみよう。
Pythonでは「datetime.datetime.strptime」を使って次のように数行で記載できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# -*- coding: utf-8 -*- import datetime import functools print = functools.partial(print, flush=True) # 日付をシリアル値で返す def covertDate(year_date): dt = datetime.datetime.strptime(year_date, '%Y/%m/%d') - datetime.datetime(1899, 12, 31) serial = dt.days + 1 return serial if __name__ == '__main__': print(covertDate("2000/1/4")) print(covertDate("2023/1/11")) |
【結果】
1 2 3 |
# # /c/Python311/python convertdate.py 36529 44937 |
Protraでの実現方法はこちら。C言語など関数がない場合も同様の処理で実現できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# loop-type: date-only // シリアル値を年、月、日に変換する def DMYToExcelSerialDate() l = (int)(( 1461 * ( Year + 4800 + (int)(( Month - 14 ) / 12) ) ) / 4) m = (int)(( 367 * ( Month - 2 - 12 * ( ( Month - 14 ) / 12 ) ) ) / 12) n = (int)(( 3 * ( (int)(( Year + 4900 + (int)(( Month - 14 ) / 12) ) / 100) ) ) / 4) serialDate = l + m - n + Day - 2415019 - 32075; return serialDate end //==================== Print("-------------------------------------------------") Print("日付 = "+ Year + "/" + Month + "/" + Day + " => " + DMYToExcelSerialDate()) |
【結果】
1 2 3 4 5 |
------------------------------------------------- 日付 = 2000/1/4 => 36529 ...(中略) ------------------------------------------------- 日付 = 2023/1/11 => 44937 |
確かにできた。
でも数字がたくさん出てきて、これで実現できている理由は分かってない。
とりあえず、こんな感じで出力してみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (daynum >= 44897 && 44904 > daynum) return 6.86 end if (daynum >= 44904 && 44911 > daynum) return 11.86 end if (daynum >= 44911 && 44918 > daynum) return 8.89 end if (daynum >= 44918 && 44925 > daynum) return 13.62 end if (daynum >= 44925 && 44932 > daynum) return 5.53 end |
出来たファイルサイズは全データで
646万行(200 MB)
となった。
作成できたProtra用の信用倍率のデータは下記に置いている。
もし良いストラテジーができた人がいるのであれば、定期的に信用倍率をダウンロードする事をオススメ。
3点チャージ法+信用倍率の期待値検証
信用倍率を取得していたサイトの仕様が変わりダウンロードに時間がかかったので、今回のバックテストは3点チャージ法のみ。
従来の3点チャージ法のバックテスト結果
システムトレード手法として有名な「3点チャージ法」。
【買い】
- 1) 移動平均線の乖離率(26日)の乖離率が-15%以下
- 2) VR(25日)が70%以下
- 3) RSI(14日)が25%以下
【売り】
- 1) 買値の10%を上回った
- 2) 2営業日経過 or 買値5%を下回った
何度も出てるけどバックテスト結果は次のとおり。
計算時間は約30分。
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 |
株価データ: 日足 銘柄リスト: プライム貸借20230110 2000/01/06~2023/01/11における成績です。 ---------------------------------------- 全トレード数 6182 勝ちトレード数(勝率) 3067(49.61%) 負けトレード数(負率) 3115(50.39%) 全トレード平均利率 0.48% 勝ちトレード平均利率 5.20% 負けトレード平均損率 -4.16% 勝ちトレード最大利率 72.73% 負けトレード最大損率 -45.72% 全トレード平均期間 2.98 勝ちトレード平均期間 3.01 負けトレード平均期間 2.95 ---------------------------------------- 必要資金 ¥2,877,600 最大ポジション(簿価) ¥2,998,900 最大ポジション(時価) ¥3,303,300 純利益 ¥13,402,600 勝ちトレード総利益 ¥69,971,410 負けトレード総損失 -¥56,568,810 全トレード平均利益 ¥2,168 勝ちトレード平均利益 ¥22,814 負けトレード平均損失 -¥18,160 勝ちトレード最大利益 ¥368,000 負けトレード最大損失 -¥209,100 プロフィットファクター 1.24 最大ドローダウン(簿価) -¥1,611,700 最大ドローダウン(時価) -¥1,642,000 ---------------------------------------- 現在進行中のトレード数 4 ---------------------------------------- 平均年利 19.41% 平均年利(直近5年) 2.06% 最大連勝 12回 最大連敗 17回 |
利益曲線は次のとおり。
ほぼ単調増加だったのにコロナ禍でトレンドが変わってしまった感がある。
とはいえ、20年以上前から知ってる手法だけど未だに現役で使えそうだ。
3点チャージ法+信用倍率のバックテスト結果
システムトレード手法として有名な「3点チャージ法」。
【買い】
- 1) 移動平均線の乖離率(26日)の乖離率が-15%以下
- 2) VR(25日)が70%以下
- 3) RSI(14日)が25%以下
- 3) 信用倍率が3.0より小さい
【売り】
- 1) 買値の10%を上回った
- 2) 2営業日経過 or 買値5%を下回った
計算時間は約30分。
バックテスト結果は次の通り。
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 |
株価データ: 日足 銘柄リスト: プライム貸借20230110 2000/02/07~2023/01/06における成績です。 ---------------------------------------- 全トレード数 3671 勝ちトレード数(勝率) 1816(49.47%) 負けトレード数(負率) 1855(50.53%) 全トレード平均利率 0.42% 勝ちトレード平均利率 4.94% 負けトレード平均損率 -4.00% 勝ちトレード最大利率 81.13% 負けトレード最大損率 -45.72% 全トレード平均期間 3.00 勝ちトレード平均期間 3.01 負けトレード平均期間 2.98 ---------------------------------------- 必要資金 ¥2,803,500 最大ポジション(簿価) ¥2,995,100 最大ポジション(時価) ¥3,351,900 純利益 ¥6,887,800 勝ちトレード総利益 ¥38,904,100 負けトレード総損失 -¥32,016,300 全トレード平均利益 ¥1,876 勝ちトレード平均利益 ¥21,423 負けトレード平均損失 -¥17,259 勝ちトレード最大利益 ¥272,800 負けトレード最大損失 -¥209,100 プロフィットファクター 1.22 最大ドローダウン(簿価) -¥1,324,300 最大ドローダウン(時価) -¥1,350,600 ---------------------------------------- 現在進行中のトレード数 4 ---------------------------------------- 平均年利 10.24% 平均年利(直近5年) 3.42% 最大連勝 12回 最大連敗 11回 ---------------------------------------- [年度別レポート] 年度 取引回数 運用損益 年利 勝率 PF 最大DD 2023年 5回 ¥33,400円 1.19% 60.00% 2.46倍 -3.54% 2022年 171回 ¥440,400円 15.71% 50.88% 1.34倍 -13.15% 2021年 144回 ¥151,400円 5.40% 52.78% 1.16倍 -33.94% 2020年 198回 -¥723,400円 -25.80% 43.43% 0.70倍 -22.16% 2019年 166回 ¥577,400円 20.60% 59.04% 1.55倍 -18.07% 2018年 268回 -¥42,500円 -1.52% 46.27% 0.98倍 -14.07% 2017年 54回 -¥30,700円 -1.10% 44.44% 0.90倍 -22.26% 2016年 148回 ¥403,900円 14.41% 54.73% 1.42倍 -14.47% 2015年 77回 ¥448,800円 16.01% 58.44% 2.09倍 -16.11% 2014年 94回 ¥418,200円 14.92% 58.51% 1.70倍 -19.41% 2013年 85回 -¥281,400円 -10.04% 45.88% 0.64倍 -13.06% 2012年 156回 -¥46,200円 -1.65% 46.79% 0.97倍 -13.96% 2011年 154回 -¥239,500円 -8.54% 48.05% 0.86倍 -45.72% 2010年 156回 -¥154,100円 -5.50% 48.72% 0.87倍 -9.91% 2009年 172回 ¥160,900円 5.74% 51.16% 1.08倍 -30.61% 2008年 390回 ¥1,096,600円 39.12% 48.72% 1.24倍 -40.00% 2007年 277回 -¥286,400円 -10.22% 48.01% 0.90倍 -31.52% 2006年 181回 ¥609,700円 21.75% 54.14% 1.44倍 -14.39% 2005年 27回 ¥218,300円 7.79% 81.48% 4.17倍 -7.62% 2004年 91回 ¥422,000円 15.05% 58.24% 1.61倍 -16.67% 2003年 105回 ¥486,400円 17.35% 54.29% 1.66倍 -16.00% 2002年 189回 ¥1,194,500円 42.61% 57.14% 1.77倍 -21.68% 2001年 238回 ¥1,536,500円 54.81% 57.98% 1.72倍 -20.21% 2000年 125回 ¥493,600円 17.61% 59.20% 1.48倍 -24.53% |
利益曲線は次の通り。
あれ?
悪くなってない?
うーん、再考が必要な結果となってしまった。
おわりに
Protraで信用倍率を読み出してストラテジーに適用できる事を確認できた。
計算時間も特に悪化は見られない。開始までの読み込みは5分ぐらいかかるかもしれない。
今回 作成したストラテジーでは効果が見られなかった。
ただし信用倍率は今までの「Open、Close、High、Low、Volume」とは異なる指標なので、ストラテジーの幅が広がる可能性があると思ってる。
ソースコード(Pythonでの変換方法)
Python で Protraのライブラリに変換するコードは次のように作成した。
別途CSVとして各銘柄ごとに過去のデータを取得する必要がある。
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 |
# -*- coding: utf-8 -*- import os import re import csv import glob import datetime import numpy as np import pandas as pd from nehori import protra import functools print = functools.partial(print, flush=True) # Protraのパス g_path = 'C:\\Users\\Hoge\\Protra' def atoi(text): return int(text) if text.isdigit() else text def natural_keys(text): return [ atoi(c) for c in re.split(r'(\d+)', text) ] # 日付をシリアル値で返す def covertDate(year_date): dt = datetime.datetime.strptime(year_date, '%Y/%m/%d') - datetime.datetime(1899, 12, 31) serial = dt.days + 1 return serial g_prefix = 0 def set_protra_date(date, value): global g_prefix (year, month, day) = date.split('/') s = '' if ((int)(g_prefix) != (int)(year)): if ((int)(g_prefix) != 0): s += ' end\n' s += ' if (' + str((int)(year) + 1) + ' > (int)Year && (int)Year >= ' + str((int)(year)) + ')\n' g_prefix = year serial = covertDate(date) s += ' if (num >= ' + str(int(serial)) + ' && ' + str(int(serial + 7)) + ' > num)\n' s += ' return ' + str(value) + '\n' s += ' end\n' return s def end_protra_dataset(): global g_prefix s = '' if ((int)(g_prefix) != 0): s += ' end\n' s += ' end\n' return s # 年月日変換+ゼロパディング def date_zeropadding(date): l = re.findall(r'\d+', date) return l[0].zfill(4) + '/' + l[1].zfill(2) + '/' + l[2].zfill(2) #def shinyo_func(code, df_shinyou, df_date): def shinyo_func(code, df_shinyou): global g_flag df_shinyou['日付'] = df_shinyou['日付'].apply(date_zeropadding) df_shinyou = df_shinyou.fillna(method='ffill') # 3) 信用倍率が4.0以上を削除 # 0.0 は倍率が存在しない df_shinyou = df_shinyou.dropna(subset=['信用倍率']) df_shinyou['信用倍率'] = df_shinyou['信用倍率'].astype(float) df_shinyou = df_shinyou[df_shinyou['信用倍率'] != 0.0] # 4) 有効値としてProtraのライブラリに変換する s = "" s += ' if ((int)Code == ' + code + ')\n' for index, data in df_shinyou.iterrows(): tmp = set_protra_date(data['日付'], data['信用倍率']) s += tmp s += end_protra_dataset() return s # Protraから読み込み def read_protra_stock_date(stock_id): global g_path p = protra.PriceList(g_path) l_2d = p.readPriceList(stock_id) df = pd.DataFrame(l_2d, columns=('日付', 'open', 'high', 'low', 'close', 'volume')) return df['日付'] def read_stock_list(file_path): with open(file_path) as f: lines = f.readlines() lines = [line.rstrip('\n') for line in lines] return lines def main(): # 1) 各銘柄のCSVを名前順で読み込む file_list = sorted(glob.glob('out/*.csv'), key=natural_keys) s = '' s += 'def GetSHINYO(num)\n' pre_prefix = 0 for csv_name in file_list: df_shinyou = pd.read_csv(csv_name) df_shinyou = df_shinyou.replace('---', '0') code = os.path.splitext(os.path.basename(csv_name))[0] if(True): prefix = int(int(code) / 1000) if (pre_prefix != prefix): if (pre_prefix != 0): s += ' end\n' pre_prefix = prefix s += ' if (' + str(prefix + 1) + '000 > (int)Code && (int)Code >= ' + str(prefix) + '000)\n' s += shinyo_func(code, df_shinyou) s += ' end\n' s += ' return 9999\n' s += 'end\n' print(s) # メインルーチン if __name__ == '__main__': main() |
ソースコード(Protraでの使い方)
Protraで利用するときは次のようなコードになる。
バックテストには無料OSSの「Protra」を利用した。
TIlib、Utility、TrendCheck、TOPIXライブラリは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 |
# loop-type: date-only //============================== require "TIlib" require "Utility" require "TrendCheck" require "Sinyo2023v2" //============================== // 3点チャージ法 //============================== // 【買い】 // 1) 移動平均線の乖離率(26日)の乖離率が-15%以下 // 2) VR(25日)が70%以下 // 3) RSI(14日)が25%以下 // 【売り】 // 1) 買値の10%を上回った // 2) 2営業日経過 or 買値5%を下回った //============================== 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 = 3 // 最大保有日数 $shortSelling = 0 // 空売り戦略 Yes(1)/No(0) $Interest = 1 // 無制限(0) / 単利(1) / 複利(2) $reverse = 0 // 購入順序 昇順(0) / 降順(1) $udcount = 0 // 騰落レシオ利用数 Init() //------------------------------------------------ // 3点チャージ法 $VR = [$code_num] $Diff = [$code_num] $RSI = [$code_num] // 値上がり銘柄のカウント ------------------------ InitDone() // 騰落レシオ初期化 $__INIT__ = 1 end // シリアル値を年、月、日に変換する def DMYToExcelSerialDate() l = (int)(( 1461 * ( Year + 4800 + (int)(( Month - 14 ) / 12) ) ) / 4) m = (int)(( 367 * ( Month - 2 - 12 * ( ( Month - 14 ) / 12 ) ) ) / 12) n = (int)(( 3 * ( (int)(( Year + 4900 + (int)(( Month - 14 ) / 12) ) / 100) ) ) / 4) serialDate = l + m - n + Day - 2415019 - 32075; return serialDate end def Main(i) //================================================== // 条件(買条件, 売条件共通部分) //================================================== //まだ上場していない銘柄は株価データがないためnullが返る if (Index == null) return end if ($order[(int)Code] == -1) $order[(int)Code] = i end //================================================== if ! ($Diff[i]) // 3点チャージ法 $Diff[i] = DiffMA_new(26) $VR[i] = VR_new(25, 0) $RSI[i] = RSI_new(14, 0) // グローバル変数を初期化 $hold[i] = 0 return end // 3点チャージの指標の計算を1日進める DiffMA_next($Diff[i]) VR_next($VR[i]) RSI_next($RSI[i]) // ここまで ======================================== //================================================== // 保有してない→購入 //================================================== if ! ($hold[i]) // 1) 株価の高い場合はランキングしない[200]円以下 // 2) 売買代金の少ない場合はランキングしない(売買代金の[3]日間平均が[1]千万円以下の場合) if ! ({-1}Close && Close && {-2}Close && {-3}Close) return end if ! (Volume && {-1}Volume && {-2}Volume) return end if ! (SalesValue()/3 + {-1}SalesValue()/3 + {-2}SalesValue()/3 > 50000 && Close > 50) return end // 3点チャージ法 diff = DiffMA_value($Diff[i]) vr = VR_value($VR[i]) rsi = RSI_value($RSI[i]) if ! (diff && vr && rsi && Close) return end // 購入ロジック chage3 = diff <= -15 && vr <= 70 && rsi <= 25 if (chage3) if (GetSHINYO($serialdate) < 3.00) PrintLog("3点チャージ法") $buyflag[i][0] = 1 // 購入フラグ $buyflag[i][1] = diff // 好きなパラメータをもとにソート $buyflag[i][2] = $buyflag[i][0] // 呪文(複数ロジック用) $buyCnt = $buyCnt + 1 end end //================================================== // 保有している→売却 //================================================== elsif ($hold[i]) if $set[i] < 1 $set[i] = 1 return end $set[i] = $set[i] + 1 //================================================== // 売買(売り) //================================================== // 1) 利食い if (Close >= 1.10 * $buy[i]) PrintLog("3点チャージ_利食い") $sellflag[i] = 1 $set[i] = 0 // 2) 損切り elsif (Close <= 0.95 * $buy[i] || $set[i] >= 2) PrintLog("3点チャージ_損切り") $sellflag[i] = 1 $set[i] = 0 end end end //==================== // 売り処理 //==================== def Sell_(i) if ($sellflag[i]) Selling(i) $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() if ! (HasPricedata(Close)) return end $long = 0 $long = Num($buyUnit, Close) codeset = $order[(int)Code] Buying(codeset) end //==================== // 銘柄コードを変えながらMain関数,BuySell関数を実行 //==================== Print("-------------------------------------------------") Print("日付 = "+ Year + "/" + Month + "/" + Day) $serialdate = DMYToExcelSerialDate() $buyCnt = 0 // 購入数初期化 i = 0 while (i < $code_num) {codes[i]}Main(i) i = i + 1 end // ストラテジーによって買いの優先度を変える if ($buyCnt) // 3点チャージ法 sortList = SelectionSort(10, 1) cnt = 0 while (cnt < $buyCnt) // 購入の場合$buyflag[i][0]は0以外 if ! (sortList[cnt]) break end cnt = cnt + 1 end i = 0 while i < cnt {sortList[i]}SortBuy() i = i + 1 end end //---------------------------------------------- i = 0 while i < $code_num {codes[i]}Sell_(i) i = i + 1 end |