投資のためのデータサイエンス

個人の投資活動に役立つデータ分析にまつわる話題を綴ります。

基本編:長期・中期・短期のチャート分析

今回はチャート分析の基本に立ち返り、長期・中期・短期の株価チャートと主要な指標値を描画するPythonコードを掲載します。指標の計算にはpandas-taライブラリを用い、描画にはmplfinanceライブラリを用いています。

まず、データ取得から描画まで行う関数を定義します。尚、RSI(相対力指数)とボリンジャーバンドのパラメータはデータ種別によらず固定しています。

# ライブラリのインポート
import pandas as pd
from pandas_datareader import data as pdr
import mplfinance as mpf
from datetime import date, datetime, timedelta
import yfinance as yf
import pandas_ta as ta
# 関数の定義
def chart_check(ticker, dttype='d'):
    # パラメータ設定
    if dttype=='m':
        dtdays=4000; dtterm='1mo'; sma=6; lma=12; stitle='MONTHLY'
    elif dttype=='w':
        dtdays=900; dtterm='1wk'; sma=13; lma=26; stitle='WEEKLY'
    else:
        dtdays=180; dtterm='1d'; sma=5; lma=25; stitle='DAILY'
    # yahooサイトからデータをダウンロード
    end_date = datetime.today()
    start_date = end_date - timedelta(days=dtdays)
    yf.pdr_override()
    df = pdr.get_data_yahoo(ticker, start_date, end_date, interval=dtterm)
    # 指標値の計算
    df['RSI'] = ta.rsi(df['Adj Close'], length=14)
    df.ta.bbands(close='Adj Close', length=20, std=2, append=True)
    # mplfinanceのmake_addplotメソッドを用いてチャートに付け加えるプロットを作成する
    idx_plot = [
            mpf.make_addplot((df['RSI']), panel=2, ylabel='RSI', secondary_y=False),
            mpf.make_addplot((df['BBU_20_2.0']), panel=3, ylabel='Bollinger', secondary_y=False),
            mpf.make_addplot((df['Adj Close']), panel=3, secondary_y=False),
            mpf.make_addplot((df['BBL_20_2.0']), panel=3, secondary_y=False),
    ]
    # 表題と凡例
    ch_title = stitle+" STOCK PRICE CHART FOR {0}".format(ticker)
    line_titles1 = ['RSI']
    line_titles2 = ['Upper','Close','Lower']
    # チャート描画
    fig, axes = mpf.plot(df,type='candle',figsize =(16,8), style='yahoo',
             addplot=idx_plot,volume=True,mav=(sma,lma), returnfig=True)
    # 表題と凡例の指定
    axes[0].set_title(ch_title)
    axes[4].legend(line_titles1)
    axes[6].legend(line_titles2)

後は、銘柄コードとデータ種別を与えて実行するだけです。

ticker = '9984.T'  # 9984はソフトバンクグループ
chart_check(ticker, dttype='m')  # 月足チャート
chart_check(ticker, dttype='w')  # 週足チャート
chart_check(ticker, dttype='d')  # 日足チャート


図は月足のみ掲載しています。移動平均は目先の上下、ボリンジャーバンドはタイミング、出来高は人気、RSIは過熱感をみる指標となります。

パラボリックSARと順位相関指数を組み合わせたシグナル判定

前々回(パラボリックSAR)と前回(順位相関指数RCI)の投稿の指標を組み合わせた取引戦略がある書籍に載っていました。今回はこれをPythonで実装してみます。
尚、パラボリックSARについてはta-libを用いたコードに変更しました。RCIについては、別のネット記事のコードで計算結果が一致するものを見つけたので、これで正しいということにしておきます。ここではFXを投資対象とします。


まず、投資対象銘柄のリストを与え、両指標の計算関数を定義します。

# ライブラリのインポート
import yfinance as yf
from datetime import datetime, timedelta
from datetime import date
import pandas as pd
import numpy as np
from pandas_datareader import data as pdr
import matplotlib.pyplot as plt
import mplfinance as mpf
import seaborn
import mplfinance as mpf
import talib as ta
# シグナル判定する通貨ペアのリスト
list_ticker = ['USDJPY=X', 'EURJPY=X', 'GBPJPY=X', 'AUDJPY=X', 'CADJPY=X', 'NZDJPY=X', 'MXNJPY=X', 'CHFJPY=X', 'NOKJPY=X',\
               'EURUSD=X', 'GBPUSD=X', 'AUDUSD=X', 'NZDUSD=X', 'AUDNZD=X', 'EURGBP=X', 'GBPAUD=X']
# パラボリックSARの戦略関数
def parabolic_sar_strategy(data):   
   # パラボリックSARの計算
   data['SAR'] = ta.SAR(data['High'], data['Low'], acceleration=0.02, maximum=0.2)
   # シグナルの生成
   data['Signal'] = 0  # 0 means no signal
   data.loc[data['Close'] > data['SAR'], 'Signal'] = 1  # Buy signal
   data.loc[data['Close'] < data['SAR'], 'Signal'] = -1  # Sell signal
   # ポジションの生成
   data['Position'] = data['Signal'].diff()
   return data
# RCIの計算関数
def rci(close, timeperiod=9):
    rci = np.full_like(close, np.nan)
    rank_period = np.arange(1, timeperiod + 1)
    for i in range(timeperiod - 1, len(close)):
        rank_price = close[i - timeperiod + 1:i + 1]
        rank_price = np.argsort(np.argsort(rank_price)) + 1
        aa = 6 * sum((rank_period - rank_price)**2)
        bb = timeperiod * (timeperiod**2 - 1)
        rci[i] = (1 - aa / bb) * 100
    return rci

そして、銘柄のリストの要素毎にシグナルを判定して、指標毎に別々のデータフレームに格納します。

# リスト内の銘柄毎にシグナルを判定する
df_sar = pd.DataFrame()
df_rci = pd.DataFrame()
end = datetime.today()
start = end - timedelta(days=180)  # 6 months before today
for ticker in list_ticker:
    end = datetime.today()
    start = end - timedelta(days=182)  # 6 months before today
    yf.pdr_override()
    # yahooサイトからデータをダウンロード
    data = pdr.get_data_yahoo(ticker, start, end) # パラボリック計算用
    df = data.copy() # RCI計算用
    # SAR諸指標の計算
    df_parab = parabolic_sar_strategy(data)
    # 順位相関指数(RCI)
    df['RCI'] = rci(data['Close'])  
    df['ticker'] = ticker
    df_parab['ticker'] = ticker  
    # 最終行を付け足す
    df_sar = pd.concat([df_sar,df_parab.tail(1)],axis=0)
    df_rci = pd.concat([df_rci,df.tail(1)],axis=0)

そして、両指標について、シグナルがあるケースに絞り込んで横連結します。

# SARのシグナルがある行に絞り込む
df1 = df_sar.loc[df_sar['Signal']==1]
df2 = df_sar.loc[df_sar['Signal']==-1]
df3 = pd.concat([df1, df2], axis=0)
# RCIがLB以下かUB以上の行に絞り込む
df11 = df_rci[df_rci['RCI'] <= -50]
df12 = df_rci[df_rci['RCI'] >= 50]
df13 = pd.concat([df11, df12], axis=0)
# SARとRCIの結果を横連結する
df4 = df3.reset_index()[['SAR','Signal','Position','ticker']]
df14 = df13.reset_index()[['RCI','ticker']]
df20 = pd.merge(df4, df14, on='ticker', how='outer')
df20


ここで注目するケースは、パラボリックSARで「買い」シグナルが出てかつRCIが下方バンドを割っているケース、又はパラボリックSARで「売り」シグナルが出てかつRCIが上方バンドを超えているケースです。実際にはバンド絶対値を80にすると該当ケースがほとんどないため、ここでは50に設定しています。

RCI(順位相関指数)による株価データの分析

株価やFX分析におけるオシレーター指標の一つとして、RCI (Rank Correlation Index)があります。これは順位相関に基づく指標で、RSI(相対力指数)と紛らわしいですが全くの別指標です。しかし利用法はRSIに類似しており、80以上で「買われすぎ」、-80以下で「売られすぎ」と判断するほか、異なる時間パラメータの指標による「ゴールデンクロス」や「デッドクロス」も用いられます。しかしRCIの計算機能はTA-LibなどのPythonライブラリには見当たりません。直接計算するコードはネット上にいくつかあります。今回は日本語のブログ記事を参考にしました。
まず、いつものようにYahoo! Financeから変数tickerで指定された銘柄の株価データを取得して、データフレームに格納します。

# ライブラリのインポート
import yfinance as yf
from datetime import datetime, timedelta
from datetime import date
import pandas as pd
import numpy as np
from pandas_datareader import data as pdr
import matplotlib.pyplot as plt
import mplfinance as mpf
# 株価データの獲得
ticker = '4755.T' # 4755は楽天
end = datetime.today()
start = end - timedelta(days=182)  # 6 months before today
yf.pdr_override()
# yahooサイトからデータをダウンロード
df = pdr.get_data_yahoo(ticker, start, end)

次にRCIを計算して、ローソク足チャートの下に表示します。RCIの計算で必要な「終値系列の価格の順位」を、np.argsort()を2度適用して求めているところがポイントのようです。

# subplotとして順位相関指数(RCI)を描画する
# 各種指標の計算関数
def rci(close, timeperiod=9):
    rci = np.full_like(close, np.nan)
    rank_period = np.arange(1, timeperiod + 1)
    for i in range(timeperiod - 1, len(close)):
        rank_price = close[i - timeperiod + 1:i + 1]
        rank_price = np.argsort(np.argsort(rank_price)) + 1
        aa = 6 * sum((rank_period - rank_price)**2)
        bb = timeperiod * (timeperiod**2 - 1)
        rci[i] = (1 - aa / bb) * 100
    return rci
# 指標計算関数を実行して結果をリストに格納する
osc_rci = rci(df['Close'])
# 追加プロットのリストを作成する
osc_plot1 = [
    mpf.make_addplot((osc_rci), panel=2, ylabel='RCI', secondary_y=False)
]
# 表題と凡例
ch_title = 'Stock Price Chart for ' + ticker
line_titles = ['RCI']
# チャート描画:指定可能なtypeは'ohlc'(default), 'candle', 'line', 'renko', 'pnf'
fig, axes = mpf.plot(df,type='candle',figsize =(9,6), style='yahoo',
         addplot=osc_plot1,volume=True,mav=(15,25,75), returnfig=True)
# 表題と凡例の指定
axes[0].set_title(ch_title)
axes[4].legend(line_titles)

利用にあたっては、他のオシレーター指標やファンダメンタルズとの併用が推奨されています。

株価データのパラボリックSARによるトレンド転換の分析

前回の投稿からだいぶ時間が経ってしまいました。有用で信頼に足るコードを掲載している記事がなかなかなくて苦労しています。今回は、トレンド転換の分析に用いられる「パラボリックSAR」(ストップ&リバース)のコードを紹介します。

今回のコードは、データ獲得の部分以外は以下の記事のコードをほぼそのまま利用しています。

raposa.trade

まず、いつものようにYahoo! Financeから変数tickerで指定された銘柄の株価データを取得して、OHLCV形式で"data"という名前のデータフレームに格納します。

# ライブラリのインポート
import yfinance as yf
from datetime import datetime, timedelta
from datetime import date
import pandas as pd
import numpy as np
from pandas_datareader import data as pdr
import seaborn
import matplotlib.pyplot as plt
import mplfinance.original_flavor as mpf
from collections import deque
# 株価データの獲得
ticker = '4755.T' #4755は楽天
end = datetime.today()
start = end - timedelta(days=365)  # 12 months before today
yf.pdr_override()
# yahooサイトからデータをダウンロード
data = pdr.get_data_yahoo(ticker, start, end)

次に、以下のコードはパラボリックSARを計算するためのクラスとその下での関数(メソッド)の定義です。

class PSAR:

  def __init__(self, init_af=0.02, max_af=0.2, af_step=0.02):
    self.max_af = max_af
    self.init_af = init_af
    self.af = init_af
    self.af_step = af_step
    self.extreme_point = None
    self.high_price_trend = []
    self.low_price_trend = []
    self.high_price_window = deque(maxlen=2)
    self.low_price_window = deque(maxlen=2)

    # Lists to track results
    self.psar_list = []
    self.af_list = []
    self.ep_list = []
    self.high_list = []
    self.low_list = []
    self.trend_list = []
    self._num_days = 0

  def calcPSAR(self, high, low):
    if self._num_days >= 3:
      psar = self._calcPSAR()
    else:
      psar = self._initPSARVals(high, low)

    psar = self._updateCurrentVals(psar, high, low)
    self._num_days += 1

    return psar

  def _initPSARVals(self, high, low):
    if len(self.low_price_window) <= 1:
      self.trend = None
      self.extreme_point = high
      return None

    if self.high_price_window[0] < self.high_price_window[1]:
      self.trend = 1
      psar = min(self.low_price_window)
      self.extreme_point = max(self.high_price_window)
    else: 
      self.trend = 0
      psar = max(self.high_price_window)
      self.extreme_point = min(self.low_price_window)

    return psar

  def _calcPSAR(self):
    prev_psar = self.psar_list[-1]
    if self.trend == 1: # Up
      psar = prev_psar + self.af * (self.extreme_point - prev_psar)
      psar = min(psar, min(self.low_price_window))
    else:
      psar = prev_psar - self.af * (prev_psar - self.extreme_point)
      psar = max(psar, max(self.high_price_window))

    return psar

  def _updateCurrentVals(self, psar, high, low):
    if self.trend == 1:
      self.high_price_trend.append(high)
    elif self.trend == 0:
      self.low_price_trend.append(low)

    psar = self._trendReversal(psar, high, low)

    self.psar_list.append(psar)
    self.af_list.append(self.af)
    self.ep_list.append(self.extreme_point)
    self.high_list.append(high)
    self.low_list.append(low)
    self.high_price_window.append(high)
    self.low_price_window.append(low)
    self.trend_list.append(self.trend)

    return psar

  def _trendReversal(self, psar, high, low):
    # Checks for reversals
    reversal = False
    if self.trend == 1 and psar > low:
      self.trend = 0
      psar = max(self.high_price_trend)
      self.extreme_point = low
      reversal = True
    elif self.trend == 0 and psar < high:
      self.trend = 1
      psar = min(self.low_price_trend)
      self.extreme_point = high
      reversal = True

    if reversal:
      self.af = self.init_af
      self.high_price_trend.clear()
      self.low_price_trend.clear()
    else:
        if high > self.extreme_point and self.trend == 1:
          self.af = min(self.af + self.af_step, self.max_af)
          self.extreme_point = high
        elif low < self.extreme_point and self.trend == 0:
          self.af = min(self.af + self.af_step, self.max_af)
          self.extreme_point = low

    return psar

そして、以下のコードでは"indic"という、PSARクラスのインスタンスを作成し、株価データをベースに、そのインスタンスから呼び出せるメソッドを用いて、パラボリックSARの計算と描画を行っています。

# パラボリックSARの計算
indic = PSAR()
data['PSAR'] = data.apply(
    lambda x: indic.calcPSAR(x['High'], x['Low']), axis=1)
# Add supporting data
data['EP'] = indic.ep_list
data['Trend'] = indic.trend_list
data['AF'] = indic.af_list
# パラボリックSARの描画
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
psar_bull = data.loc[data['Trend']==1]['PSAR']
psar_bear = data.loc[data['Trend']==0]['PSAR']
plt.figure(figsize=(9, 6))
plt.plot(data['Close'], label='Close', linewidth=1)
plt.scatter(psar_bull.index, psar_bull, color=colors[1], label='Up Trend')
plt.scatter(psar_bear.index, psar_bear, color=colors[3], label='Down Trend')
plt.xlabel('Date')
plt.ylabel('Price ($)')
plt.title(f'{ticker} Price and Parabolic SAR')
plt.legend()
plt.show()

パラボリックSARは株価だけでなくFXでも人気の分析手法となっています。tickerの値を"(コード番号).T"から"USDJPY=X"に変更すればドル円の分析ができます。

ChatGPTのCode Interpreterを用いた株価データの分析・予測

今年初めにChatGPTが登場した時は「ある言葉の次に何が来やすいかをモデル化した自然言語モデルをベースとしてこんなことまでできるのか」と驚愕しました。それから、自分の書いた英文Eメールの添削や本ブログで紹介した株価テクニカル分析などにChatGPTを活用していくうちに「なるほど、これは便利」ということで、必須のツールとなってきました。

ところが最近、以下の記事にあるChatGPTのCode Interpreter (現在はAdvanced Data Analysisというようです) を使ってみて再び驚愕しました。

medium.datadriveninvestor.com

これはもう、分析アシスタントを24時間雇用しているのと変わらないとも言えます。小生は自分のデータ分析以外にも、他の人のデータ分析の支援も数多く請け負って飯の種にしてきましたが、後者の仕事はもうAIに置き換わったということを痛感しました。

ここでは、上記英文記事に基づいて、ChatGPT Code Interpreterの使い方を概説します。尚、Code Interpreterは有料の"ChatGPT Plus"でのみ利用できる機能です。また、以下ではChatGPTの機能をフルに発揮させるために、すべて英語でやりとりしています。

まず、いつものようにYahoo! Financeから変数symbolで指定された銘柄の株価データを取得して、OHLCV形式で"stock_data"という名前のデータフレームに格納します。そして以下のPython文によってCSVファイルに出力します(日本企業を想定。データ獲得のPythonコードは当ブログの最近の大部分の記事に掲載されています)。

stock_data.to_csv('stock_data_'+symbol[0:4]+'.csv')

次にChatGPTを立ち上げ、画面下のメッセージ入力欄にあるクリップマークをクリックして上記のCSVファイルをChatGPTにアップロードし、さらに以下のプロンプトを入力します。


You are an expert in the analysis of stocks. I want you to analyze this data and provide the following information:
1. List and describe the data elements in this file.
2. Are there missing data values or does the data need to be cleaned?
3. Show me two data analysis visualizations.
4. Do you have any recommendations on how to further analyze this data to better understand the trends?

すると、ChatGPTは一つ一つについて、アップロードされたデータに基づいて回答してくれます。以下はChatGPTが描いたグラフです。

今度は、株価の予測値を求めるために、以下のプロンプトを入力します。


Use two machine learning models to predict future stock prices based on historical data.
Test using 80% of this data and show me the results for each machine learning model.

するとChatGPTは、機械学習の2つの手法(採用する手法は常に同じとは限らない)をデータにあてはめ、「平均二乗誤差」によって各手法の性能を評価した結果を返してきます。

尚、参照した英文記事は(出し惜しみで)ここで終わっていますが、肝心の予測値を得るためには、例えば以下のようなプロンプトを入力します。


Create a chart of the next 5 days' stock price forecasts using the random forest model.

すると、以下のような図が返ってきました。この場合は単なる直線外挿ですが、そうでないケースもあるようです。

このように、自らは何らコーディングをすることなく、アシスタントに仕事を依頼するような感覚で、株価の分析と予測を行うことができます。

ただし、これまで使ってみた範囲では、時々グラフの数字が不可解なものがあったり、作図を試みるも度重なるエラーでギブアップするといったケースもあります。使い方のコツについてはさらなるリサーチが必要なようです。

Pythonによるカギ足チャートの描画

(2023/12/11) それまでのコードでは、一部のケースで最後のトレンド反転時点以降の株価が全く表示されないため、データの最新時点の値は必ず描画対象とするようにコードを修正しました。

「カギ足チャート」はローソク足等に比べてユーザは少ないと思われますが、愛用者はある程度はいるらしく、カギ足チャートの見方に関するネット記事は多く見かけます。しかし、カギ足チャートをPythonで描画する方法となるとネット検索してもほとんど見かけません。唯一、海外のStackOverflowの投稿でPythonによるカギ足チャート描画コードの原案のようなものを見つけたので、そのコードをベースに独自に開発することにしました。

まずいつものように必要なライブラリをインポートします。

import matplotlib.pyplot as plt
import pandas as pd
import yfinance as yf
import matplotlib.dates as mdates
from datetime import datetime, timedelta
import numpy as np
from pandas_datareader import data as pdr
import matplotlib.dates as mdates

同じくYahoo! Financeから株価データを取得します。

# Define the stock symbol and timeframe
symbol = '7203.T' # 7203はトヨタ自動車
end_date = datetime.today()
start_date = end_date - timedelta(days=180)  # 6 months before today
yf.pdr_override()
# yahooサイトからデータをダウンロード
stock_data = pdr.get_data_yahoo(symbol, start_date, end_date)
stock_data.head()

次に、カギ足チャートの描画のベースとなるデータフレームを作成する関数を定義します。新しいデータフレームにはトレンドが反転した日付のデータ行のみを残します。描画に必要な「直近の天底」の値は新たにExtremeという名前の列を作ってそこで保持します。

def KagiChart(df,delta):
    # カギ足描画のためのデータフレームの初期化
    kagi_df = pd.DataFrame(columns=['Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Extreme', 'Direc'])

    # データの出発点における設定
    trend = 0
    kagi = df.iloc[0]['Close']
    kagi_df.loc[0] = [df.iloc[0]['Date'], df.iloc[0]['Open'], df.iloc[0]['High'], df.iloc[0]['Low'],
                      df.iloc[0]['Close'], df.iloc[0]['Volume'], df.iloc[0]['Close'], 'Line']

    # 2行目以降のデータの扱い
    m = df.shape[0] - 1
    k = 1
    for i in range(1, m):
        # 下降トレンド中で価格変化が上昇反転ルールを満たした場合
        if trend <= 0 and df.iloc[i]['Close'] > kagi+delta:
            kagi_df.loc[k] = [df.iloc[i]['Date'], df.iloc[i]['Open'], df.iloc[i]['High'], df.iloc[i]['Low'],
                              df.iloc[i]['Close'], df.iloc[i]['Volume'], kagi, 'up']
            kagi = df.iloc[i]['Close']
            trend = 1
            k += 1
        # 上昇トレンド中で価格変化が下降反転ルールを満たした場合
        elif trend >= 0 and df.iloc[i]['Close'] < kagi-delta:
            kagi_df.loc[k] = [df.iloc[i]['Date'], df.iloc[i]['Open'], df.iloc[i]['High'], df.iloc[i]['Low'],
                              df.iloc[i]['Close'], df.iloc[i]['Volume'], kagi, 'down']
            kagi = df.iloc[i]['Close']
            trend = -1
            k += 1
        # 下降トレンド中で引き続き下降を示した場合
        elif trend <= 0 and df.iloc[i]['Close'] <= kagi:
            kagi = df.iloc[i]['Close']
        # 上昇トレンド中で引き続き上昇を示した場合
        elif trend >= 0 and df.iloc[i]['Close'] >= kagi:
            kagi = df.iloc[i]['Close']
        # 上記以外の場合は何もしない
        
    # データの最終行における設定
    kagi_df.loc[k] = [df.iloc[m]['Date'], df.iloc[m]['Open'], df.iloc[m]['High'], df.iloc[m]['Low'],
                      df.iloc[m]['Close'], df.iloc[m]['Volume'], df.iloc[m]['Close'], 'End']
    return kagi_df

次に株価データを準備して上記の関数を呼び出し、実際に描画します。ここでのポイントはグラフのX軸は行番号(インデックス)を用いますが、描画に際してX軸のティックマークには対応する日付を表示させることです。また、手作業で描く場合には現在のトレンドと同方向の新値はX軸の同じ位置で線を延長しますが、ここでは直近時点以外は反転してX軸上で一つ前に進んでから1時点前の位置で線を繋ぎたしています。

# 株価データの準備
df = stock_data.copy().reset_index()
delta = 10 # 反転基準価格差の設定
df_kagi = KagiChart(df,delta)
print(df_kagi.tail(10))
Date = df_kagi['Date']
Close = df_kagi['Close']
extreme = df_kagi['Extreme']
direction = df_kagi['Direc']
# X軸変数として用いるインデックスの生成
indices = range(len(Date))
fig, ax = plt.subplots(figsize =(10,6))
# 軸目盛には日付を対応させる
plt.xticks(indices, [date.strftime('%Y-%m-%d') for date in Date])
fig.autofmt_xdate()
# 上又は下方向の垂直線を描画し、同じくその起点から左方向へ水平線を描画し、一つ前の時点で垂直線を付け足す
for i in range(1,len(Date)):
    if direction[i] == 'Line':
        ax.plot([indices[i], indices[i]], [extreme[i], Close[i]], color='b')
    elif direction[i] == 'End':
        ax.plot([indices[i-1], indices[i-1]], [extreme[i], Close[i-1]], color='b')
    elif direction[i] == 'up':
        ax.plot([indices[i], indices[i]], [extreme[i], Close[i]], color='b')
        ax.plot([indices[i-1], indices[i-1]], [extreme[i], Close[i-1]], color='b')
        plt.hlines(extreme[i], xmin=indices[i-1], xmax=indices[i], color='b')
    else:
        ax.plot([indices[i], indices[i]], [extreme[i], Close[i]], color='b')
        ax.plot([indices[i-1], indices[i-1]], [extreme[i], Close[i-1]], color='b')
        plt.hlines(extreme[i], xmin=indices[i-1], xmax=indices[i],color='b')
# グラフタイトルと軸ラベルを設定する
ax.set_title('Kagi Chart for '+symbol+' (delta='+str(delta)+')')
ax.set_xlabel('Date')
ax.set_ylabel('Close')
plt.show()

反転ルールは固定値幅以外にも騰落率を用いる方法などがありますが、コード上の修正は容易と思われます。

米国の株価データ取得における注意点

これまで当ブログで紹介した手法をアレンジして米国の株価データに適用してみようとしたところ、Python実行に用いているJupyter Labのカーネルが落ちるというトラブルに見舞われました。

これはどうやらデータが利用できない銘柄についてデータを取りに行ったときに起こるものだということが推察されましたが、解決策がなかなか見つからず、いろいろ調べた結果、なんとか解決しました。以下ではその解決策について記述します。

まずいつものように必要なライブラリをインポートします。

import numpy as np
from datetime import datetime
import pandas as pd
import yfinance as yf
from datetime import datetime, timedelta
from pandas_datareader import data as pdr

次に、"S&P 500 Companies with Financial Information"というサイトからS&P500の銘柄リスト(CSV形式)をダウンロードして、それを読み込み、(一例として)ヘルスケア業界のみに絞り込みます。

df_tickers = pd.read_csv('constituents.csv')
df_healthcare = df_tickers[df_tickers['Sector'] == 'Health Care']
df_healthcare.head()

次に、上記で作成したデータフレームから銘柄コードのリストを作成し、そのリストの銘柄についてYahoo! Financeからデータを取得します。ここでは取得データフレームが空の場合の処理をしています。これにより、当該銘柄のデータが存在しない場合のトラブルは回避できるようです。

# 抽出する銘柄リストと抽出期間を指定
symbols = df_healthcare['Symbol'].tolist()
start = datetime(2023,10,1)
end = datetime(2023,10,31)
# Yahoo! Financeからデータを取得
df0 = pd.DataFrame()
for symbol in symbols:
    yf.pdr_override()
    df1 = pdr.get_data_yahoo(symbol, start, end)
    df1['ticker'] = symbol
    # 取得したデータフレームが空の場合の処理
    if df1.empty:
        print(symbol + " data is empty")
    else:
        df1['close'] = df1['Adj Close']
        df0 = pd.concat([df0, df1], axis=0)
df0.head()