※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。
保有銘柄の決算発表日を把握しないまま、ポジションを持ち続けているかと思います。決算発表の直後は、好決算でも「材料出尽くし」で急落するケースがあり、事前の把握なしに乗り切るのは困難です。
決算発表日(Earnings Date)の管理は、個別株投資におけるリスク管理の基本中の基本です。発表日を事前に知っていれば、ポジションの縮小や利確のタイミングを計画的に判断できます。
しかし、保有銘柄が5〜10銘柄を超えると、証券会社のサイトやIRページを個別に確認する作業は難しいです。確認漏れが1銘柄でもあれば、予期しない急変動に巻き込まれます。
原因は明確で、決算日の確認作業が手動に依存しているからです。人間が定期的に複数サイトを巡回する運用は、遅かれ早かれ破綻します。
ということで、この記事では、yfinanceを使って保有銘柄の決算発表日を自動取得し、直近の決算日が近い銘柄を一覧表示するPythonコードを提供します。さらに、日数ベースのアラート機能を追加した応用コードも解説します。
📘 外部参考:Python 公式(ダウンロード) / Python 公式ドキュメント(日本語)
📘 外部参考:yfinance 公式GitHub / PyPI
コードはすべてコピペで動作します。SBI証券で保有中の銘柄リストを入力するだけで、決算カレンダーの自動生成が完了します。
決算発表日とリスク管理の基本知識
決算発表前後に株価が急変動する理由
決算発表は、企業の業績が市場の期待と合致しているかを確認する最大のイベントです。市場予想を上回れば急騰、下回れば急落するのが基本パターンです。
しかし、好決算でも「織り込み済み」として売られるケースは珍しくありません。決算発表前後の1〜3営業日は、通常の2〜5倍のボラティリティ(Volatility:価格変動率)が発生すると想定してください。
決算日管理で防げるリスク
決算日を事前に把握していれば、以下のような判断が可能になります。発表の5営業日前にはポジションサイズの見直しを完了させるのが実践的な目安です。
* ポジションの一部利確で急落時の損失を限定する
* 逆指値注文の幅を通常より広げ、ノイズによる不要な損切りを防ぐ
* 決算またぎを意図的に行う場合は、最大損失額を事前に算出する
【コピペOK】決算発表日を自動取得するメインコード
まず、必要なライブラリをインストールしてください。
pip install yfinance pandas
以下がメインコードです。
import datetime
from typing import Optional
import pandas as pd
import yfinance as yf
# ==============================
# 設定エリア
# ==============================
# 保有銘柄のティッカーリスト(日本株は .T 付き)
WATCHLIST: list[str] = [
"7203.T", # トヨタ自動車
"9984.T", # ソフトバンクグループ
"6758.T", # ソニーグループ
"8306.T", # 三菱UFJフィナンシャル・グループ
"AAPL", # Apple
"MSFT", # Microsoft
]
# アラート閾値:決算まで何営業日以内で警告を出すか
ALERT_DAYS: int = 14
# 取得する決算日の上限件数
MAX_EARNINGS_DATES: int = 4
# CSV出力ファイル名
OUTPUT_CSV: str = "earnings_calendar.csv"
# ==============================
# 決算発表日の取得
# ==============================
def fetch_earnings_dates(
ticker: str,
limit: int,
) -> Optional[pd.DataFrame]:
'ティッカーの直近決算発表日をDataFrameで返す'
try:
tk: yf.Ticker = yf.Ticker(ticker)
earnings_df: pd.DataFrame = tk.get_earnings_dates(limit=limit)
if earnings_df is None or earnings_df.empty:
return None
earnings_df = earnings_df.reset_index()
earnings_df.rename(
columns={"Earnings Date": "earnings_date"},
inplace=True,
)
earnings_df["ticker"] = ticker
return earnings_df[["ticker", "earnings_date"]].copy()
except Exception:
return None
# ==============================
# 直近の未来の決算日を抽出
# ==============================
def extract_next_earnings(
earnings_df: Optional[pd.DataFrame],
ticker: str,
now: datetime.datetime,
) -> dict:
'直近の未来の決算日を辞書で返す'
if earnings_df is None or earnings_df.empty:
return {
"ticker": ticker,
"next_earnings": None,
"days_until": None,
}
# タイムゾーン処理:naive datetimeに統一
dates: pd.Series = pd.to_datetime(earnings_df["earnings_date"]).dt.tz_localize(None)
future_dates: pd.Series = dates[dates > now]
if future_dates.empty:
return {
"ticker": ticker,
"next_earnings": None,
"days_until": None,
}
next_date: datetime.datetime = future_dates.min()
days_until: int = (next_date - now).days
return {
"ticker": ticker,
"next_earnings": next_date.strftime("%Y-%m-%d"),
"days_until": days_until,
}
# ==============================
# アラート判定
# ==============================
def add_alert_flag(
record: dict,
threshold_days: int,
) -> dict:
'days_untilが閾値以内なら警告フラグを付与する'
record = record.copy()
if record["days_until"] is not None and record["days_until"] <= threshold_days:
record["alert"] = "⚠ 決算間近"
else:
record["alert"] = '
return record
# ==============================
# 銘柄名の取得
# ==============================
def fetch_short_name(ticker: str) -> str:
'ティッカーの短縮名称を返す'
try:
info: dict = yf.Ticker(ticker).info
return info.get("shortName", ticker)
except Exception:
return ticker
# ==============================
# 全銘柄の一括処理
# ==============================
def build_earnings_calendar(
watchlist: list[str],
alert_days: int,
limit: int,
) -> pd.DataFrame:
'ウォッチリスト全銘柄の決算カレンダーを生成する'
now: datetime.datetime = datetime.datetime.now()
records: list[dict] = []
for ticker in watchlist:
name: str = fetch_short_name(ticker)
earnings_df: Optional[pd.DataFrame] = fetch_earnings_dates(ticker, limit)
record: dict = extract_next_earnings(earnings_df, ticker, now)
record["name"] = name
record = add_alert_flag(record, alert_days)
records.append(record)
print(f" 取得完了: {ticker} ({name})")
result: pd.DataFrame = pd.DataFrame(records)
result = result[["ticker", "name", "next_earnings", "days_until", "alert"]]
result = result.sort_values("days_until", ascending=True, na_position="last")
return result
# ==============================
# CSV出力
# ==============================
def export_csv(df: pd.DataFrame, filepath: str) -> None:
'DataFrameをCSVファイルに保存する'
df.to_csv(filepath, index=False, encoding="utf-8-sig")
print(f"CSVを保存しました: {filepath}")
# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
print("=" * 50)
print("決算発表日カレンダー生成ツール")
print("=" * 50)
calendar_df: pd.DataFrame = build_earnings_calendar(
WATCHLIST, ALERT_DAYS, MAX_EARNINGS_DATES,
)
print("n【決算カレンダー】")
print(calendar_df.to_string(index=False))
# 警告銘柄の抽出
alerts: pd.DataFrame = calendar_df[calendar_df["alert"] != ']
if not alerts.empty:
print(f"n⚠ {ALERT_DAYS}日以内に決算を控えた銘柄が {len(alerts)} 件あります。")
print(" ポジションサイズの見直しを検討してください。")
else:
print(f"n{ALERT_DAYS}日以内に決算を控えた銘柄はありません。")
# CSV出力
export_csv(calendar_df, OUTPUT_CSV)
コードの処理フロー解説
上記のコードは、以下の5ステップで構成されています。
* ステップ1 決算日取得:yf.Ticker().get_earnings_dates()で各銘柄の直近決算発表日を最大4件取得する。Yahoo Financeが提供するアナリスト予想ベースの日程が返される
* ステップ2 未来日抽出:取得した決算日から現在日時より未来の日付のみを抽出し、最も近い日付を「次回決算日」として特定する
* ステップ3 アラート判定:次回決算日までの残日数がALERT_DAYS(14日)以内であれば、警告フラグを付与する
* ステップ4 一覧生成:全銘柄の結果を決算日が近い順にソートしたDataFrameを生成する
* ステップ5 出力:コンソールに一覧表を表示し、CSVファイルとしても保存する
WATCHLISTに自分の保有銘柄を追加するだけで、カスタマイズは完了です。
決算日データの読み解き方と実践的な活用法
yfinanceの決算日データの精度について
yfinanceが返す決算発表日は、Yahoo Financeに掲載されたアナリスト予想ベースの日程です。企業が正式にIRで公表した日程とは異なる場合があります。
特に日本株は、米国株と比較してデータの精度が低い傾向があります。yfinanceの日程を「目安」として使い、正式な発表日は各企業のIRページまたはTDnet(適時開示情報閲覧サービス)で必ず二重確認してください。yfinanceの日程だけを信頼するのは危険です。
SBI証券での実践的な運用フロー
毎週月曜日の朝にこのコードを実行することを推奨します。出力されたCSVをもとに、以下の手順でリスク管理を行ってください。
* 決算まで14日以内の銘柄:逆指値注文の見直しとポジションサイズの確認
* 決算まで5日以内の銘柄:ポジションの半分を利確、または全決済の判断
* 決算を通過した銘柄:翌営業日の値動きを確認後、ポジション再構築を検討
【コピペOK】過去の決算日と株価変動を分析する応用コード
import datetime
from typing import Optional
import pandas as pd
import yfinance as yf
# ==============================
# 設定エリア
# ==============================
TICKER: str = "7203.T"
MAX_EARNINGS_DATES: int = 12 # 過去の決算日を最大12件取得
PRICE_CHECK_DAYS: int = 3 # 決算後の株価変動を確認する営業日数
OUTPUT_CSV: str = "earnings_impact.csv"
# ==============================
# 決算前後の株価変動を算出
# ==============================
def analyze_earnings_impact(
ticker: str,
limit: int,
check_days: int,
) -> pd.DataFrame:
'過去の決算日前後の株価変動率をDataFrameで返す'
tk: yf.Ticker = yf.Ticker(ticker)
earnings_df: Optional[pd.DataFrame] = tk.get_earnings_dates(limit=limit)
if earnings_df is None or earnings_df.empty:
raise ValueError(f"{ticker} の決算日データが取得できません。")
# 株価データを広めに取得
close: pd.DataFrame = yf.download(
ticker, period="5y", auto_adjust=True, progress=False,
)
if isinstance(close.columns, pd.MultiIndex):
close.columns = close.columns.get_level_values(0)
close_series: pd.Series = close["Close"].squeeze()
now: datetime.datetime = datetime.datetime.now()
records: list[dict] = []
for date_idx in earnings_df.index:
ed: datetime.datetime = pd.Timestamp(date_idx).tz_localize(None)
if ed >= now:
continue
# 決算日直前の終値
pre_dates: pd.Series = close_series[close_series.index <= ed]
if len(pre_dates) < 2:
continue
pre_close: float = pre_dates.iloc[-2]
# 決算後N営業日の終値
post_dates: pd.Series = close_series[close_series.index > ed]
if len(post_dates) < check_days:
continue
post_close: float = post_dates.iloc[check_days - 1]
change_pct: float = round((post_close - pre_close) / pre_close * 100, 2)
records.append({
"決算日": ed.strftime("%Y-%m-%d"),
"決算前終値": round(pre_close, 1),
f"決算後{check_days}日終値": round(post_close, 1),
"変動率(%)": change_pct,
})
return pd.DataFrame(records)
# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
print(f"銘柄: {TICKER}")
print(f"決算後 {PRICE_CHECK_DAYS} 営業日の株価変動を分析n")
impact_df: pd.DataFrame = analyze_earnings_impact(
TICKER, MAX_EARNINGS_DATES, PRICE_CHECK_DAYS,
)
print(impact_df.to_string(index=False))
# 統計サマリー
if not impact_df.empty:
avg_change: float = round(impact_df["変動率(%)"].mean(), 2)
max_drop: float = round(impact_df["変動率(%)"].min(), 2)
max_rise: float = round(impact_df["変動率(%)"].max(), 2)
print(f"n平均変動率: {avg_change}%")
print(f"最大下落: {max_drop}%")
print(f"最大上昇: {max_rise}%")
impact_df.to_csv(OUTPUT_CSV, index=False, encoding="utf-8-sig")
print(f"nCSVを保存しました: {OUTPUT_CSV}")
コードの処理フロー解説
上記のコードは、以下の3ステップで構成されています。
* ステップ1 過去の決算日取得:get_earnings_dates()で最大12件の決算日を取得し、現在より過去の日付のみを分析対象とする
* ステップ2 前後の株価比較:各決算日の直前終値と決算後N営業日の終値を取得し、変動率を算出する。PRICE_CHECK_DAYSを変えれば確認期間を調整できる
* ステップ3 統計サマリー:全決算の平均変動率・最大下落・最大上昇をコンソールに表示する
過去の決算インパクトを数値で把握しておけば、次回決算でのポジション管理の判断材料になります。
よくあるエラーと対処法
get_earnings_dates()がNoneを返す
yfinanceがYahoo Financeから決算日情報を取得できなかった場合に発生します。日本株は米国株と比べてデータが欠落しているケースが多いです。
以下を試してください。
* yfinanceを最新版に更新する(pip install --upgrade yfinance)
* limitパラメータの値を小さくする(4→2に変更)
* 米国ADRのティッカー(トヨタならTM)で試し、データの有無を確認する
タイムゾーンエラー TypeError: can’t compare offset-naive and offset-aware datetimes
yfinanceが返す日時がタイムゾーン付き(aware)で、Pythonのdatetime.now()がタイムゾーンなし(naive)の場合に発生します。両者の比較ができないことが原因です。
対処法として、本記事のコードでは.tz_localize(None)でタイムゾーン情報を除去しています。この処理がextract_next_earnings()関数内に含まれているか確認してください。
決算日データが過去の日付しか返ってこない
Yahoo Finance側で次回決算日の予想がまだ掲載されていない場合に起こります。特に日本企業の四半期決算では、次回日程の掲載が遅れることがあります。
以下を試してください。
* limitの値を8〜12に増やし、より多くの日程を取得して未来日が含まれるか確認する
* 決算短信の発表スケジュールをTDnetで直接確認する
* 次回日程が未掲載の場合は、前回決算日から3か月後を暫定的に設定する運用ルールを作る
まとめ
この記事では、Pythonを使って保有銘柄の決算発表日を自動取得し、リスク管理に活用する方法を解説しました。
実際に使ってみた要点をまとめます。
* 決算発表前後は通常の2〜5倍のボラティリティが発生するため、発表日の事前把握は必須である
* yf.Ticker().get_earnings_dates()で決算日を取得できるが、日本株はデータ精度が低いためIRページとの二重確認が必要である
* ALERT_DAYSを14日に設定し、決算間近の銘柄を自動で警告する仕組みを作ることで確認漏れを防げる
* 過去の決算前後の株価変動を分析すれば、銘柄ごとの「決算リスク」を定量的に評価できる
* 毎週月曜日に定期実行し、CSVで出力する運用フローを確立することを推奨する
また、このコードの出力結果をSlackやLINE Notifyに自動送信する仕組みを追加してください。requestsライブラリでWebhook URLにPOSTするだけで実装できます。さらに、cronやWindowsのタスクスケジューラで毎週自動実行すれば、完全自動の決算アラートシステムになります。

