こんにちは、ないとめあです。

今日もブログにお越しいただきありがとうございます。

 

最近の日本株、本当に絶好調ですね!

 

 私は「日経平均高配当株50指数」に連動する投資信託の積立をしていますが、ここで気になるのが「高市ラリー」や「衆議院解散ラリー」といった政治主導の急騰です。「今買わなきゃ乗り遅れる!」という焦りがある一方で、「今が天井だったらどうしよう……」という高値掴みの懸念も拭えません(出口時と比べて)。

 そこで、感情に流されて高値で飛びつくのを防ぎ、もし下落した場合には冷静にナンピン(買い増し)して基準価格を下げられるよう、株価の推移を一気に見える化するPythonプログラムを自作しました!w

 
もちろん、AIにプログラムをさくっと作成いただきました。てへぺろ
 
以下のコードを stock_check.py という名前で保存して実行してください。 実行には yfinancepython-dotenv ライブラリが必要です。
# ライブラリのインストール
# pip install yfinance python-dotenv
# -*- coding: utf-8 -*-

import yfinance as yf
from datetime import date, timedelta
import sys
import argparse
import os
import smtplib
import re
from email.mime.text import MIMEText
from email.utils import formatdate
from dotenv import load_dotenv

def get_colored_percentage(percentage_change):
    """
    騰落率を色付きの文字列としてフォーマットします。
    プラスは青、マイナスは赤になります。
    """
    if percentage_change >= 0:
        # 青色
        return f"\033[94m{percentage_change:+.2f}%\033[0m"
    else:
        # 赤色
        return f"\033[91m{percentage_change:+.2f}%\033[0m"

def get_stock_performance(ticker_symbol):
    """
    指定されたティッカーシンボルの現在価格と、
    過去1〜24週間の価格を比較し、それぞれの騰落率を計算します。

    Args:
        ticker_symbol (str): 株式のティッカーシンボル (例: "AAPL")

    Returns:
        str: 結果を示すフォーマットされた文字列。
    """
    try:
        # Tickerオブジェクトを作成
        ticker = yf.Ticker(ticker_symbol)
        info = ticker.info

        # 企業の正式名称を取得
        long_name = info.get('longName', ticker_symbol)

        # 現在価格を info から取得する試み
        current_price = info.get('regularMarketPrice', info.get('previousClose'))

        # 日付を設定
        end_date = date.today()
        # 24週間前のデータも必要なので、余裕をもって180日分のデータを取得
        start_date = end_date - timedelta(days=180)

        # 履歴データを yf.download で取得 (より信頼性が高い場合がある)
        hist = yf.download(ticker_symbol, start=start_date, end=end_date, auto_adjust=False, progress=False)

        if hist.empty:
            return f"ティッカーシンボル '{ticker_symbol}' の履歴データが見つかりませんでした。"

        # info から価格が取得できなかった場合のフォールバック
        if current_price is None:
            price_val = hist['Adj Close'].iloc[-1]
            # yfinanceがMultiIndexを返す場合、Seriesが返るので最初の値を取得
            if hasattr(price_val, 'iloc'):
                current_price = price_val.iloc[0]
            else:
                current_price = price_val
        current_date = hist.index[-1].date()

        performance_results = []
        for weeks_ago in range(1, 25):
            # 基準日を計算
            target_date = end_date - timedelta(days=weeks_ago * 7)
            
            # 基準日以前で最も近い過去データを取得
            past_data = hist[hist.index.date <= target_date]
            
            if past_data.empty:
                # 該当期間のデータがない場合は、取得期間内で最も古いデータを代用
                past_data = hist
            
            price_val = past_data['Adj Close'].iloc[-1]
            # yfinanceがMultiIndexを返す場合、Seriesが返るので最初の値を取得
            if hasattr(price_val, 'iloc'):
                past_price = price_val.iloc[0]
            else:
                past_price = price_val
            past_date = past_data.index[-1].date()

            # 騰落率を計算
            percentage_change = ((current_price - past_price) / past_price) * 100
            
            # 騰落率を色付きでフォーマット
            colored_percentage = get_colored_percentage(percentage_change)
            
            performance_results.append(
                f"{weeks_ago}週間前: {colored_percentage} ({past_price:,.0f})"
            )

        # 結果をフォーマット
        return (
            f"「{long_name} ({ticker_symbol})」\n"
            f"-----------------------\n"
            f"現在価格 ({current_date.strftime('%m-%d')}): {current_price:,.0f}\n"
            f"-----------------------\n"
            + "\n".join(performance_results)
        )

    except Exception as e:
        return f"エラーが発生しました: {e}\nティッカーシンボルが正しいか確認してください。"

def strip_color_codes(text):
    """
    文字列からANSIカラーコードを削除します。
    """
    return re.sub(r'\033\[[0-9;]*m', '', text)

def send_email(recipient_email, body):
    """
    指定された宛先に結果をメールで送信します。
    環境変数からSMTP設定を読み取ります。
    """
    # 環境変数からSMTP設定を取得
    smtp_host = os.environ.get('SMTP_HOST')
    smtp_port = int(os.environ.get('SMTP_PORT', 587))
    smtp_user = os.environ.get('SMTP_USER')
    smtp_password = os.environ.get('SMTP_PASSWORD')

    if not all([smtp_host, smtp_user, smtp_password]):
        print("エラー: SMTP設定用の環境変数(.envファイル内)が設定されていません。")
        sys.exit(1)

    # メールの作成
    subject = f"株式パフォーマンスレポート - {date.today().strftime('%Y-%m-%d')}"
    msg = MIMEText(strip_color_codes(body), 'plain', 'utf-8')
    msg['Subject'] = subject
    msg['From'] = smtp_user
    msg['To'] = recipient_email
    msg['Date'] = formatdate(localtime=True)

    try:
        # SMTPサーバーに接続してメールを送信
        with smtplib.SMTP(smtp_host, smtp_port) as server:
            server.starttls()
            server.login(smtp_user, smtp_password)
            server.sendmail(smtp_user, [recipient_email], msg.as_string())
        print(f"メールを {recipient_email} に正常に送信しました。")
    except Exception as e:
        print(f"メールの送信中にエラーが発生しました: {e}")
        sys.exit(1)

def main():
    """
    メインの実行関数。引数を処理し、結果を表示またはメールで送信します。
    """
    # .envファイルから環境変数を読み込む
    load_dotenv()
    
    # デフォルトのティッカーシンボルリスト
    default_tickers = [
        "XLC", "XLY", "XLP", "XLE", "XLF", "XLV", 
        "XLI", "XLB", "XLRE", "XLK", "XLU"
    ]

    parser = argparse.ArgumentParser(
        description="指定された株式ティッカーのパフォーマンスをチェックします。",
        epilog="ティッカーが指定されていない場合、デフォルトの主要セクターETFリストが使用されます。"
    )
    parser.add_argument(
        'tickers', 
        nargs='*', 
        default=default_tickers,
        help='チェックしたいティッカーシンボル(複数指定可)。例: AAPL GOOGL'
    )
    parser.add_argument(
        '--email', 
        type=str,
        help='結果を送信するメールアドレス。指定した場合、コンソールには出力されません。'
    )
    args = parser.parse_args()

    # レポート全体の文字列を格納する変数
    full_report = []

    if args.tickers == default_tickers:
        report_intro = "ティッカーシンボルが指定されていないため、デフォルトの主要セクターETFリストを処理します。\n"
        full_report.append(report_intro)

    for ticker in args.tickers:
        try:
            result = get_stock_performance(ticker.upper())
            full_report.append(result)
        except Exception as e:
            error_message = f"ティッカー {ticker} の処理中にエラーが発生しました: {e}"
            full_report.append(error_message)

    # レポートを結合
    final_report_str = "\n\n".join(full_report)

    if args.email:
        # メール送信が指定されている場合
        print("レポートをメールで送信します...")
        send_email(args.email, final_report_str)
    else:
        # デフォルト(コンソール出力)
        print(final_report_str)

if __name__ == "__main__":
    main()
🛠️ 私の運用フロー
  1. Linuxサーバー上でこのプログラムを週に1回自動実行
  2. 結果をメールで自分宛に送信
  3. メール内の「過去24週間の騰落率」を見て、調整局面(押し目)が来ていれば追加買い付けを実施!

ツールの実行方法

 コンソールからターゲットとなるティッカーシンボルを指定して実行します。 日経平均高配当株50指数の動きを見るなら、ETF版の「1489.T」を指定するのがおすすめです。

# コンソールで直接確認する場合
python stock_price_checker.py 1489.T

# メールで結果を送信する場合(.envでGMAILの設定が必要)
python stock_price_checker.py 1489.T --email your-address@example.com

自動化のメリット

「もっと前に買っておけば……」という後悔は、逆を言えば「安くなった時に勇気を持って買えるか」という課題でもあります。

このツールをサーバーで定期実行することで、 「プラスの時は静観、マイナスが目立ち始めたらナンピン検討」 というルールを自動化できました。 政治的な「お祭り騒ぎ」の時こそ、こうしたツールを使って客観的な数字を見るのが大切だと感じています。

 

では、また!