目次。
- 目次。
- はじめに。
- 株式の流動性(出来高)が高い銘柄を選んだ方がいい?
- 株式の流動性(出来高)を元に約4000種類の銘柄を4グループに分ける。
- 移動平均線のペアを変えて色々調べてみると…。
- まとめとか。
この文章を読んで、面白い!役に立った!...と思った分だけ、投げ銭していただけると嬉しいです。
【宣伝】ギターも歌も下手だけど、弾き語りをやっているので、よければ聴いてください。
はじめに。
株式自動売買プログラム開発関連の文章は、以下のカテゴリーにまとめているので、興味のある方はどうぞ。
前回、移動平均線ペアの選び方が利益率・株式保有日数・取引回数に与える影響を調べてみた。解析は、約4000社の上場企業の5年分の株価データを一緒くたにして行った。
一方で、、、
「移動平均線などといったテクニカル分析で株式売買するのであれば、株式の流動性(出来高)が高い銘柄を選んだ方がいい」
という話も聞く。
株式の流動性(出来高)が高い銘柄を選んだ方がいい?
売買がすぐに成立する。
言うまでもなく、株式は、いつでも買えて、いつでも売れるわけではない。株式を買うためには、その株式を売っている人がいないと買えないし、株式を売るためには、その株式を買いたい人がいないと売れない。
ある銘柄の出来高が高いということは、その銘柄を取引している人が多いということ。取引している人が多ければ、その銘柄の売買がすぐに成立する可能性は高い。
僕は、シストレをやっている一般的な人とは違って、株式の売買注文も全て自動化しようと思っている。「売買がすぐに成立する」というのは、売買を全自動化するにあたって、とても重要な点。
大数の法則。
コインを2回、投げると全て表が出ることがある。確率は、1/4。そして、表と裏が同じぐらいだけ出る確率は、1/2。しかし、コインを100回投げると全て表が出る確率は1/4から限りなく0に近づく。一方で、表と裏が同じぐらいだけ出る確率は、1/2から限りなく1に近づく。
基本的に株式自動売買に用いようと考えているアルゴリズムは、確率モデルである。過去の確率分布を学習して、未来の株価を予想する。例えば、「ある局面になれば、取引に参加している全体の60%が買い注文、40%が売り注文を行い、買い注文が売り注文よりも多くなれば、株価が上昇する」ということが過去のデータから得られていたとする。仮に、取引に参加している人数が2人だったとしよう。そうすると、2人とも株式を買って、株価が上昇する確率は、モデルが正しくても、たった36%である。もし、これが100人になると、株式を買う人の割合は限りなく60%に近づき、株価は、モデルが正しければ、ほとんど100%の確率で上昇する。
つまり、取り扱う銘柄が十分量取引されているということは、確率モデルベースの株式売買アルゴリズムを構築するにあたって、とても重要。
株式の流動性(出来高)を元に約4000種類の銘柄を4グループに分ける。
最新から遡って、100営業日までのデータを用い、各銘柄の出来高を比較し、それぞれの営業日における出来高ランキングを作成した。その後、100個(100営業日)のランキングをそれぞれの銘柄で、足し合わせ、その値が低い順に銘柄を並び替え、最終的な出来高ランキングとした。(あまりにも取引されていない、もしくは上場して間もない銘柄は取り除いてある)
新型コロナウイルスの影響で2020年の最初に多くの銘柄の出来高が急激に上昇した(売り注文が増えて、株価が暴落)。100営業日間の出来高の平均のランキングを指標に用いると、この時期だけのデータが過大評価されて、100営業日間のデータを用いる意味がなくなる。
グループは、以下の通り。
①出来高:1位~1000位
②出来高:1001位~2000位
③出来高:2001位~3000位
④出来高:3001位~
ソースコード。
import glob import datetime import re import numpy as np import seaborn as sns import pandas as pd import matplotlib.pyplot as plt storage_uri = 'YYYYY' gl = glob.glob(storage_uri+'outputs\\stock_price_data\\day\\*.csv') stock_volume_list = [] for f in gl[:]: stock_code = f.replace(storage_uri+'outputs\\stock_price_data\\day\\', '') stock_code = re.sub('-.*', '', stock_code) print(stock_code) temp_list = [stock_code] tmp = pd.read_csv(f, engine='python').values if tmp.shape[0] < 100: continue if np.sum(tmp[-100:,-2]) < 100: continue temp_list.extend(tmp[-100:,-2]) stock_volume_list.append(temp_list) np.save(storage_uri + 'outputs\\stock_volume_list.npy', np.array(stock_volume_list)) stock_volume_list = np.load(storage_uri + 'outputs\\stock_volume_list.npy').astype(np.float) stock_volume_ranking = np.zeros((len(stock_volume_list[:,0]), 2), dtype=int) stock_volume_ranking[:, 0] = stock_volume_list[:,0] for i in range(100): print(i) print(stock_volume_list[:,i+1]) stock_volume_ranking[:, 1] += np.argsort(np.argsort(stock_volume_list[:,i+1])) stock_volume_ranking_code = stock_volume_ranking[np.argsort(stock_volume_ranking[:, 1]), 0][::-1]
移動平均線のペアを変えて色々調べてみると…。
前とほぼ同様。前回は、全銘柄を一緒くたに解析したが、今回は全銘柄を4つのグループに分けて、それぞれ解析した。
5年間・1銘柄あたりの平均利益率。
結果は、以下の通り。
2つ目のグラフは、n日・m日の短期・長期移動平均線ペアを使ったときにどのグループをターゲットに売買を行えば、一番、利益が出るかを表している。
見ての通り、2番目のグラフは、2という文字がたくさん並んでいる。つまり、②出来高:1001位~2000位の銘柄をターゲットに移動平均線ペアを使って、株式売買すれば、一番利益が出るということを表している。
これは、ちょっと意外だった。移動平均線ペアを使う場合、株式の流動性は高いといいが、流動性が高すぎると、逆に利益が出なくなる。
1銘柄あたりの平均株式保有日数。
2つ目のグラフは、n日・m日の短期・長期移動平均線ペアを使ったときにどのグループをターゲットに売買を行えば、一番、平均株式保有日数が少ないかを表している。
結果、②出来高:1001位~2000位の銘柄をターゲットにすれば、株式の保有期間が少なくて済むということが分かった。
短期移動平行線に1日、2日のものを使うと、④出来高:3001位~の銘柄をターゲットにすれば、株式の保有期間が少なくなる。取引量が少ないと、少額の取引が移動平均線に大きな影響を及ぼすので、このような結果になったのだと思う。
5年間・1銘柄あたりの平均取引回数。
2つ目のグラフは、n日・m日の短期・長期移動平均線ペアを使ったときにどのグループをターゲットに売買を行えば、一番、平均取引回数が多くなるかを表している。
結果、①出来高:1位~1000位の銘柄をターゲットにすれば、株式の取引回数が多くなるということが分かった。
ソースコード。
#前回のプログで紹介したプログラムの出力結果は、profit_holdingPeriod_numberBuyingSelling.txtに保存されている。 profit_holdingPeriod_numberBuyingSelling = None with open(storage_uri + 'outputs\\profit_holdingPeriod_numberBuyingSelling.txt',encoding="utf-8_sig") as f: profit_holdingPeriod_numberBuyingSelling = [s.replace('\n','').split(',') for s in f.readlines()] profit_holdingPeriod_numberBuyingSelling_2 = np.zeros((4, 25, 25)) profit_holdingPeriod_numberBuyingSelling_3 = np.zeros((4, 25, 25)) profit_holdingPeriod_numberBuyingSelling_4 = np.zeros((4, 25, 25)) profit_holdingPeriod_numberBuyingSelling_div = np.array([np.triu(np.ones((25, 25)).T, k=1).T]*4) for phn in profit_holdingPeriod_numberBuyingSelling[:]: print(phn, flush=True) if phn[-1] == '0': continue cl = -1 if int(phn[0]) in stock_volume_ranking_code[:1000]: cl = 0 if int(phn[0]) in stock_volume_ranking_code[1000:2000]: cl = 1 if int(phn[0]) in stock_volume_ranking_code[2000:3000]: cl = 2 if int(phn[0]) in stock_volume_ranking_code[3000:]: cl = 3 profit_holdingPeriod_numberBuyingSelling_2[cl, int(phn[1])-1,int(phn[2])-6] += float(phn[3]) profit_holdingPeriod_numberBuyingSelling_3[cl, int(phn[1])-1,int(phn[2])-6] += float(phn[4]) profit_holdingPeriod_numberBuyingSelling_4[cl, int(phn[1])-1,int(phn[2])-6] += float(phn[5]) profit_holdingPeriod_numberBuyingSelling_div[cl, int(phn[1])-1,int(phn[2])-6] += 1 for i in range(25): for j in range(25): for cl in range(4): profit_holdingPeriod_numberBuyingSelling_2[cl, i, j] = profit_holdingPeriod_numberBuyingSelling_2[cl, i, j]/profit_holdingPeriod_numberBuyingSelling_div[cl, i, j] profit_holdingPeriod_numberBuyingSelling_3[cl, i, j] = profit_holdingPeriod_numberBuyingSelling_3[cl, i, j]/profit_holdingPeriod_numberBuyingSelling_div[cl, i, j] profit_holdingPeriod_numberBuyingSelling_4[cl, i, j] = profit_holdingPeriod_numberBuyingSelling_4[cl, i, j]/profit_holdingPeriod_numberBuyingSelling_div[cl, i, j] np.save(storage_uri + 'outputs\\profit2.npy', profit_holdingPeriod_numberBuyingSelling_2) np.save(storage_uri + 'outputs\\holdingPeriod2.npy', profit_holdingPeriod_numberBuyingSelling_3) np.save(storage_uri + 'outputs\\numberBuyingSelling2.npy', profit_holdingPeriod_numberBuyingSelling_4) profit2 = np.load(storage_uri + 'outputs\\profit2.npy') holdingPeriod2 = np.load(storage_uri + 'outputs\\holdingPeriod2.npy') numberBuyingSelling2 = np.load(storage_uri + 'outputs\\numberBuyingSelling2.npy') tmp_profit = np.zeros((25, 25)) tmp_holdingPeriod = np.zeros((25, 25)) tmp_numberBuyingSelling = np.zeros((25, 25)) for i in range(25): for j in range(i, 25): tmp_profit[i, j] = np.argsort(profit2[:,i,j])[-1] + 1 tmp_holdingPeriod[i, j] = np.argsort(holdingPeriod2[:,i,j])[0] + 1 tmp_numberBuyingSelling[i, j] = np.argsort(numberBuyingSelling2[:,i,j])[-1] + 1 plt.figure(figsize=(10, 10)) tmp = pd.DataFrame(data=tmp_profit, index=[i for i in range(1, 26)], columns=[i for i in range(6, 31)]) sns.heatmap(tmp, square=True, vmax=4, annot=True, cbar=False, cmap='gist_heat') plt.xlabel('長期移動平均線(m日)') plt.ylabel('短期移動平均線(n日)') plt.title('5年間・1銘柄あたりの平均利益率') plt.savefig('5年間・1銘柄あたりの平均利益率3') plt.figure(figsize=(10, 10)) tmp = pd.DataFrame(data=tmp_holdingPeriod, index=[i for i in range(1, 26)], columns=[i for i in range(6, 31)]) sns.heatmap(tmp, square=True, vmax=4, annot=True, cbar=False, cmap='gist_heat') plt.xlabel('長期移動平均線(m日)') plt.ylabel('短期移動平均線(n日)') plt.title('平均株式保持日数(日)') plt.savefig('平均株式保持日数3') plt.figure(figsize=(10, 10)) tmp = pd.DataFrame(data=tmp_numberBuyingSelling, index=[i for i in range(1, 26)], columns=[i for i in range(6, 31)]) sns.heatmap(tmp, square=True, vmax=4, annot=True, cbar=False, cmap='gist_heat') plt.xlabel('長期移動平均線(m日)') plt.ylabel('短期移動平均線(n日)') plt.title('5年間・1銘柄あたり平均取引回数(日)') plt.savefig('5年間・1銘柄あたり平均取引回数3') plt.figure(figsize=(20, 20)) for i in range(4): tmp = pd.DataFrame(data=profit2[i,:,:], index=[i for i in range(1, 26)], columns=[i for i in range(6, 31)]) ax = plt.subplot(2,2,i+1) sns.heatmap(tmp, square=True, vmax=0.25) plt.xlabel('長期移動平均線(m日)') plt.ylabel('短期移動平均線(n日)') if i == 0: plt.title('①出来高:1位~1000位', fontsize=24) if i == 1: plt.title('②出来高:1001位~2000位', fontsize=24) if i == 2: plt.title('③出来高:2001位~3000位', fontsize=24) if i == 3: plt.title('④出来高:3001位~', fontsize=24) plt.suptitle('5年間・1銘柄あたりの平均利益率', fontsize=36) plt.savefig('5年間・1銘柄あたりの平均利益率2') plt.figure(figsize=(20, 20)) for i in range(4): tmp = pd.DataFrame(data=holdingPeriod2[i,:,:], index=[i for i in range(1, 26)], columns=[i for i in range(6, 31)]) ax = plt.subplot(2,2,i+1) sns.heatmap(tmp, square=True, annot=True, fmt="3.0f", cbar=False) plt.xlabel('長期移動平均線(m日)') plt.ylabel('短期移動平均線(n日)') if i == 0: plt.title('①出来高:1位~1000位', fontsize=24) if i == 1: plt.title('②出来高:1001位~2000位', fontsize=24) if i == 2: plt.title('③出来高:2001位~3000位', fontsize=24) if i == 3: plt.title('④出来高:3001位~', fontsize=24) plt.suptitle('平均株式保持日数(日)', fontsize=36) plt.savefig('平均株式保持日数2') plt.figure(figsize=(20, 20)) for i in range(4): tmp = pd.DataFrame(data=numberBuyingSelling2[i,:,:], index=[i for i in range(1, 26)], columns=[i for i in range(6, 31)]) ax = plt.subplot(2,2,i+1) sns.heatmap(tmp, square=True, annot=True, fmt="3.0f", cbar=False) plt.xlabel('長期移動平均線(m日)') plt.ylabel('短期移動平均線(n日)') if i == 0: plt.title('①出来高:1位~1000位', fontsize=24) if i == 1: plt.title('②出来高:1001位~2000位', fontsize=24) if i == 2: plt.title('③出来高:2001位~3000位', fontsize=24) if i == 3: plt.title('④出来高:3001位~', fontsize=24) plt.suptitle('5年間・1銘柄あたり平均取引回数(回)', fontsize=36) plt.savefig('5年間・1銘柄あたり平均取引回数2')
まとめとか。
②出来高:1001位~2000位の銘柄を選べば、「5年間・1銘柄あたりの平均利益率」は大きくなり、「1銘柄あたりの平均株式保有日数」は短くなる。「5年間・1銘柄あたりの平均取引回数」は、出来高:1位~1000位の銘柄をターゲットにすれば、一番大きくなる。そもそも取引回数が少なすぎたら嫌だなと思ってこの指標を調べ始めたが、よく考えたら別に対象の銘柄数を増やせば、「5年間・1銘柄あたりの平均取引回数」が大きい必要はない。逆に手数料が取られないので、取引回数が少ない方が得。
移動平均線などといったテクニカル分析をするにあたって、出来高は多ければ、多いほどよいと思っていたが、どうやらそうではなさそう。ある程度、出来高はないといけないが、取引されすぎると、かえって利益が取りにくくなる。
②出来高:1001位~2000位の銘柄をターゲットにして、もうちょっと色々と調べてみよう。
この文章を読んで、面白い!役に立った!...と思った分だけ、投げ銭していただけると嬉しいです。