日本株×ドル円を1枚のグラフにしたら時刻がズレて分析が崩壊した話(pandasのタイムゾーン地獄)

Python実装・コード

先週末、保有している製造メーカー株の株価とドル円レートを1枚のグラフに重ねて「円安が進んだ日は株価も一緒に上がってるのか」を確認しようとしたんです。ところが出来上がったグラフが明らかにおかしい。。。株価が動く前にドル円が反応してるように見えて、半日「タイムラグの謎」を追いかけるハメになりました。結論から言うと、原因は分析ロジックのミスじゃなくて、単純にタイムゾーンの扱いを間違えていただけでした。恥ずかしい話です。

日本株(東証、JST基準で1日が区切られる)とドル円(FXは実質24時間、しかもデータソースによってUTCやNY時間区切りだったりする)は、そもそも「1日」の区切り方が違います。これを知らずに日付だけでマージすると、実は数時間ズレた別のタイミングのデータ同士を比較していた、なんてことが普通に起こる。同じ悩みを持つ人がいるかもと思い、原因と直し方をちゃんと整理してみることにしました。

tz-naiveとtz-aware、そもそも何が違うのか

pandasのDatetimeIndexには「tz-naive(タイムゾーン情報を持たない、ただの時刻)」と「tz-aware(世界のどこの時刻か明示されている)」の2種類があります。yfinanceで日本株を取得すると、返ってくるインデックスはtz-naiveのことが多く、実際には東証の現地時間(JST)のデータなのに、その情報がどこにも書かれていません。一方でFXのティッカー(例えば USDJPY=X)は、UTCとしてtz-awareで返ってくることがあります。この「片方は名札なし、片方は名札あり」という食い違いに気づかず、そのまま日付だけで結合したのが今回の事故の原因でした。

何が起きていたか:日付は合ってるのに中身がズレる

たとえば「7月10日」の日本株の終値は、JSTの15時(東証の大引け)時点の値です。一方で同じ「7月10日」のドル円データが実はUTC基準のタイムスタンプを日本時間だと勘違いして扱っていたとすると、実際には9時間ズレた「7月10日9時(UTC)=7月10日18時(JST)」の値を比較してしまうことになります。日付ラベルは同じ「7/10」なのに、中身は全然違うタイミングの数字。これでは相関を見ても分析が崩壊するのは当然でした。

直し方:tz_localizeとtz_convertで揃える

やるべきことはシンプルで、それぞれのデータに「このタイムスタンプはどこの時刻か」を明示的に付与(tz_localize)してから、共通のタイムゾーンに変換(tz_convert)するだけです。日本株はJSTとしてlocalizeし、FXデータはUTCとしてlocalizeしたうえでJSTにconvertしてやれば、同じ土俵で日付を揃えられます。

import pandas as pd
import yfinance as yf

jp = yf.download("6857.T", period="3mo", interval="1d")   # 例:アドバンテスト
fx = yf.download("JPY=X", period="3mo", interval="1d")     # ドル円

print(jp.index.tz)  # None(tz-naive)のことが多い
print(fx.index.tz)  # UTC または None(データソース依存)

# それぞれのタイムゾーンを明示してからJSTに揃える
jp.index = jp.index.tz_localize("Asia/Tokyo")

if fx.index.tz is None:
    fx.index = fx.index.tz_localize("UTC")
fx.index = fx.index.tz_convert("Asia/Tokyo")

# 日付だけで丸めてから結合すればズレを防げる
jp["date"] = jp.index.date
fx["date"] = fx.index.date

merged = pd.merge(jp, fx, on="date", suffixes=("_jp", "_fx"))
print(merged.tail())

ポイントは「そもそも今扱っているデータがtz-naiveなのかtz-awareなのか」を、コードを書く前に必ず.index.tzで確認する癖をつけることです。tz-naiveのデータをtz-awareのデータと突き合わせようとすると、pandasはエラーで教えてくれることもありますが、今回の僕のケースのように「日付単位に丸めてから結合」していると、ズレたまま何のエラーも出ずに突き進んでしまいます。これが一番怖いところでした。

まとめ

複数の市場・複数のデータソースを1つのデータフレームで扱うときは、「同じ日付ラベル=同じ瞬間」とは限らないという前提を持っておく必要があります。今回は幸いグラフの見た目がおかしいことで気づけましたが、これがそのままバックテストのシグナル生成に使われていたら、気づかないままおかしいエントリーを量産していたと思うとゾッとします。次はこのタイムゾーン処理を関数化して、日本株とドル円、それにゆくゆくは米国株も含めて安心して結合できる共通の下ごしらえ関数を作っておこうと思います。

タイトルとURLをコピーしました