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

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

「共和分」を用いたペアトレード

(2023-11-05) 売買戦略に関する記述を追加しました。

本ブログの9年前(まだ投資がテーマではなかった)の記事で、時系列データの「共和分」についての解説をしました。

datapowernow.hatenablog.com
2つの単位根過程(階差をとると定常になる)の時系列が共和分の関係にあるとは、2つの系列の線形結合が定常になるような線形結合の重み係数が存在することでした。

一方、類似した価格推移をする2つの金融商品に注目し、双方の価格差が開いたり狭まったりした時に売買して、超過収益を狙う戦略を「ペアトレード」といいます。最近、上記「共和分」の性質を用いたペアトレード(統計的裁定取引)について少し学習したので、その基本的な内容とコードを記載します。

原理としては、共和分の関係にある2つの株価の系列は、一旦その差が開いてもまた元にもどる(平均回帰)傾向があることを利用するものです。

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

import pandas as pd
import numpy as np
import statsmodels.api as sm
import yfinance as yf
from pandas_datareader import data as pdr

次に、動きが似ていそうな2つの銘柄の株価データを取得します。

symbol1 = '9201.T' # 9201は日本航空
symbol2 = '9202.T' # 9202はANAホールディングス
start_date = '2020-01-01'
end_date = '2023-10-31'
yf.pdr_override()
# yahooサイトからデータをダウンロード
security1_data = pdr.get_data_yahoo(symbol1, start_date, end_date)
security2_data = pdr.get_data_yahoo(symbol2, start_date, end_date)

次に、statsmodelsライブラリを用いて共和分に関する検定(帰無仮説は共和分なし)を実施します。p値が0.05よりも小さければ共和分ありとみなし、回帰係数をもとにスプレッド(残差)を計算して標準化します。

# 共和分についての検定の実施
result = sm.tsa.stattools.coint(security1_data['Close'], security2_data['Close'])
# 線形回帰モデルのあてはめ
model = sm.OLS(security1_data['Close'], security2_data['Close']).fit()
if result[1] < 0.05:
    # 共和分が存在する場合に、スプレッド(残差)とそのZスコアを求める
    spread = security1_data['Close'] - model.params[0] * security2_data['Close']
    mean = np.mean(spread)
    std = np.std(spread)
    zscore = (spread - mean) / std
    zscore.plot()

この銘柄ペアの場合、共和分に関する検定のP値は0.034となり、共和分ありと判定されました。実際の売買戦略は、上記のZスコアとしきい値に基づいて立てることになります。例えば、最新のzscore < -1.5ならばsecurity1を買ってsecurity2を空売り、そのあと平均値に回帰する価格変動を狙います。もちろん、いわゆる「裁定取引」のように必ず儲かるわけではありません。また銘柄のペアの見つけ方も課題となります。特に上記データの期間には「新型コロナ」という特殊要因が含まれているため、その点にも注意が必要と思われます。

Backtesting.pyによるバックテストの実行

本ブログでは主として株式投資に役立つデータ分析を扱っていますが、株価データをもとに株式の売買戦略を決めるにあたっては、その戦略のパフォーマンスの評価が重要になります。過去の株価データを用いてその売買戦略を適用した結果どのような損益になるかを評価する手法がバックテストです。

Pythonでバックテストを行えるツールはいくつかあるようですが、最も有名なものは"Backtesting.py"ライブラリでしょう。これに関しては海外のみならず日本語の記事も非常に多くあります。ここでは"Backtesting.py"ライブラリを用いた基本的なバックテストの方法について述べます。

まず必要なライブラリをインポートします。

import pandas as pd
import yfinance as yf
import talib as ta
from datetime import datetime, timedelta
import numpy as np
from pandas_datareader import data as pdr
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA, GOOG, EURUSD
import warnings #警告をコントロールするライブラリを追加
warnings.filterwarnings("ignore") #警告を非表示に設定

次に、日本の株価データを取得します。

# Define the stock symbol and timeframe
symbol = '9432.T' # 9432はNTT
end_date = datetime(2023, 9, 30, 0, 0, 0)
start_date = end_date - timedelta(days=1800)  # 5 years before today
yf.pdr_override()
# yahooサイトからデータをダウンロード
stock_data = pdr.get_data_yahoo(symbol, start_date, end_date)

次に必要になるのは取引戦略のクラスの作成です。これはBacktesting.pyに備わっているStrategyというクラスを継承する形で記述します。クラスの中には指標を定義する関数init()と、売買戦略を定義する関数next()を記述します。ここでは指標の計算にTA-Libライブラリを用いていますが、自分で関数を定義することもできます。ここでの売買戦略は、長短移動平均ゴールデンクロスで買い、買いポジションがある時のデッドクロスで売りです。空売りは行わないので、売りについてはsell()でなくposition_close()を用いています。

class TalibCross(Strategy):
    tp1 = 10
    tp2 = 20
    def init(self):
        close = self.data.Close
        self.sma1 = self.I(ta.SMA, close, self.tp1)
        self.sma2 = self.I(ta.SMA, close, self.tp2)
    def next(self):
        if crossover(self.sma1, self.sma2):
            self.buy()
        elif crossover(self.sma2, self.sma1):
            self.position.close()    

次に、バックテスト実行のためのBacktestインスタンスを生成します。

bt = Backtest(stock_data, TalibCross, cash=1000000)

そして、run()で生成したインスタンスを実行し、結果を出力します(出力結果は一部のみ)。

output = bt.run()
print(output)

plot()メソッドでインタラクティブなチャートが表示されます。

bt.plot()

もう一つの重要な機能が「最適化」です。移動平均の窓の長さや(0-100という値をとる指標について)しきい値といったパラメータの値を動かして、目的関数を最適化するパラメータ値を探ります。以下では2つの移動平均のウィンドウの長さを動かして、出力される指標の一つである'Sharpe Ratio'を最大にするパラメータ値を探索しています。constraint=で探索時の制約条件を設けています。

output = bt.optimize(
    tp1 = range(4, 20, 1),
    tp2 = range(14, 30, 1),
    constraint=lambda p: p.tp1 < p.tp2,
    maximize = 'Sharpe Ratio')
print(output)

以下では最適化されたパラメータの値を確認しています。

print(output._strategy)

バックテストによるチューニングが将来の儲けには必ずしも結びつくわけではありませんが、売買戦略を評価するための強力なツールであるとはいえます。

テクニカル分析パターンの自動選別

8月下旬の本ブログの記事以降に、Pythonの代表的テクニカル分析ライブラリであるTA-Libに関する記事を掲載し、TA-Libのパターン識別機能についても取り上げました。しかし、このパターン識別機能が対象とするパターンは60以上あり、実際どれが有効なのかは判断しづらいところです。

最近、そのTA-Libのパターンのバックテストとしてのデータから機械学習を用いて有効なパターンを探り出す記事を見つけました。ただしMediumの記事なので、有料購読していないと閲覧回数に制限があります。
medium.datadriveninvestor.com

medium.datadriveninvestor.com

このうちの二つ目の記事の終わりに、この著者が開発した関数の全コードが掲載されています。二つ目の記事の本文の機械学習の適用の部分には一部手続きを省略している部分がありますが、基盤となる関数は全て網羅されており、可読性も高く非常に品質が高いコードです。ここでは、これらの関数を用いて、回帰分析により各指標の有効性を分析してみることとしました。

まず、必要なライブラリをインポートします。ここでは使いませんが、機械学習ライブラリPyCaretもインポートします。尚、現時点でPyCaretはPython3.8より後のバージョンには対応しておらず、機械学習も行うためには別途Python3.8の環境を構築する必要があります。その環境でのTA-Libのインストールは、Python3.8対応のwhlファイルをTA-Libのサイトからダウンロードして、pipによりインストールします。またGoogle Colabでは環境構築の必要はありませんが、TA-Libのインストールが特殊で手間がかかります。

# ライブラリのインポート
import pandas as pd
import datetime
from datetime import date
from datetime import timedelta
import matplotlib.pyplot as plt
import yahoo_fin.stock_info as si
import talib
import numpy as np
import mplfinance as mpf
from talib import abstract
import warnings
warnings.filterwarnings('ignore')
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
import statsmodels.api as sm
from pycaret.classification import *

次に、分析対象の銘柄コードのリストを用意します(デモ用に銘柄数を絞っています)。

top_100 = ['AAPL','MSFT','GOOGL','AMZN','NVDA','TSLA','META','V','XOM','JPM',\
           'WMT','JNJ','MA','AVGO','PG','ORCL','COST','ADBE','KO','SHEL','CSCO',\
           'BAC','AZN','NVS','CRM','PFE','MCD','NFLX','DIS','INTC','NKE']

以下の関数の呼び出しにより、2000日前から10日前までの株価データを獲得します。

data = get_hist_data(tickers=top_100, time_window=2000, delta=10)

次に、バックテスト評価に用いる指標を現データに追加して、compiled_dataというデータフレームに格納します。

print(data.shape)
compiled_data = compile_data(data=data, mute=True)
print(compiled_data.shape)

ここで列数が増えたのはパターン識別結果の列と株価変化の列を追加したためであり、行数が減ったのは移動平均で該当データがないために計算できない時点のケースを除外したためです。
次に、機械学習や統計分析等に用いるために、元の株価データの列を削除します。

df1 = compiled_data.drop(["open","high","low","close","adjclose","volume","ticker"],axis=1)
df1 = df1.reset_index(drop=True)

以下ではこのデータをもとに回帰分析を実施して、有効な指標を探っています。尚、ここではオリジナルの関数を一部修正して、二値データに加工する前の応答変数を用いています。機械学習の場合には元のコードのままで実行します。

X = df1.drop('Change', axis=1)
y = df1['Change']
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
print(model.summary())

Pythonによる「三角持ち合い」パターンの自動識別

前回は、Pythonテクニカル分析ライブラリであるTA-Libを用いてローソク足チャートのパターンの識別を試みました。このパターン識別は「明けの明星」といったローソク3本位までのパターン識別はできるようですが、「三角持ち合い」といったより時間幅の広いパターン識別はできないようです。

この「三角持ち合い(又は三角保ち合い)」をPythonで自動的に識別する方法とコードが、以下のYouTubeチュートリアルビデオとして公開されていました。

https://www.youtube.com/watch?v=b5m7BZAHysk

この動画のコメント欄にはPythonコードへのリンクも掲載されています。今回、このコードを利用して「三角持ち合い」の識別を試してみました。コード自体はオリジナルを参照していただくとして、以下に修正上のいくつかのポイントを挙げます。
まず、このコードでは例として為替データを用いていますが、基本的に株価データと形式が同じなので、以前の投稿のように日本の株価データをダウンロードできるコードでデータを取り込み、列名を対象コード向けに変更する等を行います。

# ライブラリのインポート
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from scipy.stats import linregress
import matplotlib.pyplot as plt
import yfinance as yf
import pandas_ta as ta
import matplotlib.dates as mdates
from datetime import datetime, timedelta
from pandas_datareader import data as pdr
import mplfinance as mpf
# yahooサイトからデータをダウンロード
# Define the stock symbol and timeframe
symbol = '9254.T'
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)
df = stock_data.reset_index().drop(columns=['Adj Close'])
df.columns = ['time','open','high','low','close','volume']

このコードにおける「三角持ち合い」の判定アルゴリズムは、(1)各データ点について、その点がその近傍の点群の中で最も大きい(小さい)場合に、上側(下側)の「ピボット点」とする、(2)上側・下側それぞれのピボット点をデータとする回帰線をひき、その傾きから三角持ち合いかどうかを判定する、というものです。このコードは主として以下の関数からなります。

# ピボットを識別する
def pivotid(df1, l, n1, n2):
    """ データフレームdf1のローソク足データについて、
      位置lの点のn1前と n2後からなる近傍で
       最大(最小)の点をピボットと判定する """
# 三角持ち合いパターンを識別する
def detect_flag(df1, candle, backcandles, window, plot_flag=False):
    """ ピボットの識別がなされている
       データフレームdf1のローソク足データについて、
      位置candleからスタートして長さbackcandlesの系列について
      順次「三角持ち合い」かどうかを判定する """

基本的にはどちらの関数もほぼそのまま利用可能です。ただし、detect_flag()については、オリジナルのコードでは、その引数に株価やピボット点を含むデータフレームが入っていません。しかしこのデータフレームが不要ということはもちろんなく、関数の中のコードでグローバル参照しています。これは元の著者の修正のし忘れと思われるため、引数を加える形で修正しました。
これも前回投稿と同じく、これだけで株の購入を決めることはできませんが、「三角持ち合い」を効率的に判定することができるようになります。

TA-Libによる株価テクニカル分析(その2)ローソク足パターン認識

前々回の投稿ではPythonの株価テクニカル分析ライブラリの一つであるTA-Libを各種オシレーターの計算に用いました。今回はTA-Libにあるローソク足パターン認識機能を利用してみます。尚、TA-Libのインストールはconda-install以外にも「pip install ライブラリのwhlファイル」という方法もあり、PCの環境により特定の方法がうまくいかない場合もあるので注意が必要です。

今回もまず、必要なライブラリをインポートし、株価データをダウンロードします。

# ライブラリのインポート
import talib
import yfinance as yf
import datetime
from datetime import date
import pandas as pd
import numpy as np
import mplfinance as mpf
from pandas_datareader import data as pdr
# Yahoo! Financeからのデータ獲得
start = "2023-08-01"
end = datetime.date.today()
ticker = "3237.T"
yf.pdr_override()
# yahooサイトからデータをダウンロード
df_stock = pdr.get_data_yahoo(ticker, start, end)
df_stock.head()


次に、TA-Libにあるパターン認識機能のうちのいくつかを呼び出して自動判定します。

# 株価データの系列を取り出す
open = df_stock['Open']
high = df_stock['High']
low = df_stock['Low']
close = df_stock['Adj Close']
# パターンを判定する
engulfing = talib.CDLENGULFING(open,high,low,close)
hammer = talib.CDLHAMMER(open,high,low,close)
piercingLine = talib.CDLPIERCING(open,high,low,close)
# 結果を新しいデータフレームに保存する
df_pattern = pd.concat([engulfing, hammer, piercingLine], axis=1)
df_pattern.columns = ['Engulfing','Hammer','PiercingLine']
df_pattern.tail()


判定結果の数値で「100」は強気、「-100」は弱気となります。実際にローソク足チャートで当該日の部分をハイライトしてみると、そのパターンになっていることがわかります。

現実にはローソク足のパターン判定だけでは株購入を判断するのには不十分ですが、作業を自動化して効率を上げることはできそうです。

PythonとChatGPTを利用した株価テクニカル分析

先日、Pythonによる株価テクニカル分析に関連して大変興味深い記事を見つけました。Pythonで株価のテクニカル分析の各種指標を計算し、その結果を定型のプロンプトの後につけてChatGPTに入力すると、ChatGPTが株価のテクニカル分析を行ってくれるというものです。

Stocks Technical Analysis (TA) with Python & ChatGPT: A Comprehensive Guide
medium.com

ただし上記記事は英文で、Mediumの有料購読をしていないと閲覧に制限があります。

ここでは上記記事のリンクのみ掲載しますが、上記記事のコードは、一か所を修正すれば日本株の分析にも使えます。修正箇所はこれまでにも本ブログに掲載した、Yahoo! Financeから株価データをダウンロードする部分です。
(05Sep23) コードにライブラリのインポート部分を追加しました。

# ライブラリのインポート
import matplotlib.pyplot as plt
import pandas as pd
import yfinance as yf
import pandas_ta as ta
import matplotlib.dates as mdates
from datetime import datetime, timedelta
import numpy as np
from pandas_datareader import data as pdr
# Define the stock symbol and timeframe
symbol = '4755.T'  # 4755は楽天グループ
end_date = datetime.today()
start_date = end_date - timedelta(days=120)  # 4 months before today
yf.pdr_override()
# yahooサイトからデータをダウンロード
stock_data = pdr.get_data_yahoo(symbol, start_date, end_date)

本記事に掲載されているChatGPTに入力するためのプロンプトは英文で最適化されているはずなので、このままの日本語への直訳は(たぶん)意味がありません。しかし株価テクニカル分析は国とは関係なく共通なので、上記のコードの修正を行い、単に銘柄の文字列の部分に"(銘柄コード).T"を入れればChatGPTが日本企業の株価のテクニカル分析をやってくれます(もちろん出力はすべて英文)。ただしChatGPTに入力しているのは直近の一日のオシレーター等の指標のみであることに注意が必要です。また、この記事ではテクニカル分析Pythonライブラリとして"pandas_ta"を利用していますが、当方が使ってみた限りでは"Williams %R"の計算が正しくない(たぶん符号が逆)ようです。これはChatGPTの回答からも確認できます。このコードの後半ではpandas_taライブラリを用いて計算された各種指標のチャートを描画しています。

mplfinanceライブラリを用いた株価チャートの描画(その6) - TA-Libによる株価テクニカル分析

前回はmplfinanceで別途コーディングして計算した指標をチャートに付け加える方法を説明しました。だいたいの指標はPythonで一からコーディングすれば実現できるようですが、手間がかかるのと、計算結果が正確かどうかを確かめるのが難しいという欠点があります。そこで、ここではテクニカル指標を計算するPythonライブラリである「TA-Lib」を使うこととします。このライブラリのインストールは通常のpipコマンドによるものではなく、環境上のコマンドプロンプトで「conda install -c conda-forge ta-lib」にて行います。

今回もまず、必要なライブラリをインポートし、株価データをダウンロードします。

# ライブラリのインポート
import pandas as pd
from pandas_datareader import data as pdr
import mplfinance as mpf
import datetime
import yfinance as yf
import talib as ta
# Yahoo! Financeからのデータ獲得
start = "2023-2-28"
end = datetime.date.today()
ticker = "4755.T" # 4755は楽天グループ
yf.pdr_override()
# yahooサイトからデータをダウンロード
df = pdr.get_data_yahoo(ticker, start, end)

ここでは、オシレーターの一つである「商品チャンネル指数(CCI)」をTA-Libで計算して、mplfinanceによりローソク足チャートの下にプロットしています。商品チャンネル指数は、ティピカル値〔(高値+安値+終値)÷3〕で計算した移動平均乖離幅を過去の移動平均乖離幅と比較したものです。

# subplotとして商品チャンネル指数(CCI)を描画する
# 各種指標の計算関数
def OSC_CCI(df, window):
  osc_cci = pd.DataFrame()
  # 商品チャンネル指数 (ta-lib)
  osc_cci['cci'] = ta.CCI(df['High'], df['Low'], df['Close'], timeperiod=window)
  return osc_cci
# 指標計算関数を実行して結果をデータフレームに格納する
osc_cci = OSC_CCI(df, 20)
# 追加プロットのリストを作成する
osc_plot = [
    mpf.make_addplot((osc_cci['cci']), panel=2, ylabel='CCI', secondary_y=False)
]
# 表題と凡例
ch_title = 'Stock Price Chart for ' + ticker
line_titles1 = ['cci']
# チャート描画
fig, axes = mpf.plot(df,type='candle',figsize =(15,7), style='yahoo',
         addplot=osc_plot,volume=True,mav=(15,25,75), returnfig=True)
# 表題と凡例の指定
axes[0].set_title(ch_title)
axes[4].legend(line_titles1)


前回投稿したコードでは付け加えたチャートの凡例が表示されませんでしたが、今回は凡例が表示されるよう改良しています。