株価データの取得と分析

株価データを取得してテクニカル分析を実装し、投資判断をサポートする可視化を行います。

この記事で学べること

  • Yahoo Financeからの株価取得
  • テクニカル指標の計算
  • ローソク足チャートの作成
  • 複数銘柄の比較分析

1. 準備:必要なライブラリ

# インストール
pip install yfinance
pip install pandas
pip install matplotlib
pip install mplfinance
pip install ta
pip install plotly
pip install requests

requirements.txt

yfinance==0.2.31
pandas==2.1.0
matplotlib==3.8.0
mplfinance==0.12.10b0
ta==0.11.0
plotly==5.17.0
requests==2.31.0

2. 基本的な株価取得

stock_scraper.py

#!/usr/bin/env python3
"""株価データスクレイパー"""

import yfinance as yf
import pandas as pd
from datetime import datetime, timedelta

class StockScraper:
    def __init__(self):
        pass
    
    def get_stock_data(self, ticker, period='1y', interval='1d'):
        """株価データを取得
        
        Parameters:
        -----------
        ticker : str
            銘柄コード (例: 'AAPL', '7203.T')
        period : str
            期間 ('1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', 'max')
        interval : str
            間隔 ('1m', '2m', '5m', '15m', '30m', '60m', '1d', '1wk', '1mo')
        """
        try:
            stock = yf.Ticker(ticker)
            df = stock.history(period=period, interval=interval)
            
            if df.empty:
                print(f"データが見つかりません: {ticker}")
                return None
            
            # インデックスをリセット
            df.reset_index(inplace=True)
            
            print(f"✅ {ticker}: {len(df)}件のデータを取得")
            return df
            
        except Exception as e:
            print(f"❌ エラー: {e}")
            return None
    
    def get_stock_info(self, ticker):
        """銘柄情報を取得"""
        try:
            stock = yf.Ticker(ticker)
            info = stock.info
            
            data = {
                '銘柄名': info.get('longName', 'N/A'),
                '業種': info.get('sector', 'N/A'),
                '現在価格': info.get('currentPrice', 'N/A'),
                '時価総額': info.get('marketCap', 'N/A'),
                'PER': info.get('trailingPE', 'N/A'),
                'PBR': info.get('priceToBook', 'N/A'),
                '配当利回り': info.get('dividendYield', 'N/A'),
                '52週高値': info.get('fiftyTwoWeekHigh', 'N/A'),
                '52週安値': info.get('fiftyTwoWeekLow', 'N/A')
            }
            
            return data
            
        except Exception as e:
            print(f"❌ エラー: {e}")
            return None
    
    def get_multiple_stocks(self, tickers, period='1y'):
        """複数銘柄のデータを取得"""
        all_data = {}
        
        for ticker in tickers:
            df = self.get_stock_data(ticker, period=period)
            if df is not None:
                all_data[ticker] = df
        
        return all_data

# 使用例
if __name__ == '__main__':
    scraper = StockScraper()
    
    # 米国株(Apple)
    df = scraper.get_stock_data('AAPL', period='1y')
    print(f"\nデータ期間: {df['Date'].min()} ~ {df['Date'].max()}")
    print(f"最新終値: ${df['Close'].iloc[-1]:.2f}")
    
    # 日本株(トヨタ)
    df_toyota = scraper.get_stock_data('7203.T', period='6mo')
    
    # 銘柄情報
    info = scraper.get_stock_info('AAPL')
    print("\n=== Apple株情報 ===")
    for key, value in info.items():
        print(f"{key}: {value}")

実行結果

✅ AAPL: 252件のデータを取得

データ期間: 2024-10-26 ~ 2025-10-25
最新終値: $178.45

=== Apple株情報 ===
銘柄名: Apple Inc.
業種: Technology
現在価格: 178.45
時価総額: 2750000000000
PER: 29.3
PBR: 45.2
配当利回り: 0.0048
52週高値: 199.62
52週安値: 164.08

3. テクニカル指標の計算

technical_analysis.py

#!/usr/bin/env python3
"""テクニカル分析指標"""

import pandas as pd
import numpy as np
from ta.trend import SMAIndicator, EMAIndicator, MACD
from ta.momentum import RSIIndicator, StochasticOscillator
from ta.volatility import BollingerBands

class TechnicalAnalyzer:
    def __init__(self, df):
        """データフレームを初期化"""
        self.df = df.copy()
    
    def add_moving_averages(self, periods=[5, 25, 75]):
        """移動平均線を追加"""
        for period in periods:
            # 単純移動平均(SMA)
            sma = SMAIndicator(close=self.df['Close'], window=period)
            self.df[f'SMA_{period}'] = sma.sma_indicator()
            
            # 指数移動平均(EMA)
            ema = EMAIndicator(close=self.df['Close'], window=period)
            self.df[f'EMA_{period}'] = ema.ema_indicator()
        
        return self.df
    
    def add_rsi(self, period=14):
        """RSI(相対力指数)を追加"""
        rsi = RSIIndicator(close=self.df['Close'], window=period)
        self.df['RSI'] = rsi.rsi()
        return self.df
    
    def add_macd(self):
        """MACD(移動平均収束拡散)を追加"""
        macd = MACD(
            close=self.df['Close'],
            window_slow=26,
            window_fast=12,
            window_sign=9
        )
        self.df['MACD'] = macd.macd()
        self.df['MACD_Signal'] = macd.macd_signal()
        self.df['MACD_Histogram'] = macd.macd_diff()
        return self.df
    
    def add_bollinger_bands(self, period=20):
        """ボリンジャーバンドを追加"""
        bb = BollingerBands(
            close=self.df['Close'],
            window=period,
            window_dev=2
        )
        self.df['BB_Upper'] = bb.bollinger_hband()
        self.df['BB_Middle'] = bb.bollinger_mavg()
        self.df['BB_Lower'] = bb.bollinger_lband()
        return self.df
    
    def add_stochastic(self, period=14):
        """ストキャスティクスを追加"""
        stoch = StochasticOscillator(
            high=self.df['High'],
            low=self.df['Low'],
            close=self.df['Close'],
            window=period
        )
        self.df['Stoch_K'] = stoch.stoch()
        self.df['Stoch_D'] = stoch.stoch_signal()
        return self.df
    
    def add_all_indicators(self):
        """すべての指標を追加"""
        self.add_moving_averages()
        self.add_rsi()
        self.add_macd()
        self.add_bollinger_bands()
        self.add_stochastic()
        return self.df
    
    def get_signals(self):
        """売買シグナルを生成"""
        signals = []
        
        latest = self.df.iloc[-1]
        
        # RSIシグナル
        if latest['RSI'] < 30:
            signals.append('🟢 RSI: 買われすぎ(買いシグナル)')
        elif latest['RSI'] > 70:
            signals.append('🔴 RSI: 売られすぎ(売りシグナル)')
        
        # MACDシグナル
        if latest['MACD'] > latest['MACD_Signal']:
            signals.append('🟢 MACD: ゴールデンクロス(買い)')
        elif latest['MACD'] < latest['MACD_Signal']:
            signals.append('🔴 MACD: デッドクロス(売り)')
        
        # ボリンジャーバンド
        if latest['Close'] < latest['BB_Lower']:
            signals.append('🟢 BB: 下限接触(反発期待)')
        elif latest['Close'] > latest['BB_Upper']:
            signals.append('🔴 BB: 上限接触(調整期待)')
        
        # 移動平均線
        if latest['Close'] > latest['SMA_25']:
            signals.append('🟢 SMA25: 上抜け(上昇トレンド)')
        else:
            signals.append('🔴 SMA25: 下抜け(下降トレンド)')
        
        return signals

# 使用例
from stock_scraper import StockScraper

scraper = StockScraper()
df = scraper.get_stock_data('AAPL', period='6mo')

analyzer = TechnicalAnalyzer(df)
df = analyzer.add_all_indicators()

print("\n=== 最新の指標値 ===")
latest = df.iloc[-1]
print(f"終値: ${latest['Close']:.2f}")
print(f"RSI: {latest['RSI']:.2f}")
print(f"MACD: {latest['MACD']:.2f}")
print(f"SMA25: ${latest['SMA_25']:.2f}")

print("\n=== 売買シグナル ===")
for signal in analyzer.get_signals():
    print(signal)

4. ローソク足チャート作成

visualizer.py

#!/usr/bin/env python3
"""株価データ可視化"""

import mplfinance as mpf
import matplotlib.pyplot as plt
import pandas as pd

class StockVisualizer:
    def __init__(self, df):
        self.df = df.copy()
        # DateをDatetimeIndexに変換
        if 'Date' in self.df.columns:
            self.df.set_index('Date', inplace=True)
    
    def plot_candlestick(self, ticker='Stock', save_path=None):
        """ローソク足チャート作成"""
        # 移動平均線を追加プロット
        add_plots = []
        
        if 'SMA_25' in self.df.columns:
            add_plots.append(
                mpf.make_addplot(self.df['SMA_25'], color='blue', width=1.5)
            )
        if 'SMA_75' in self.df.columns:
            add_plots.append(
                mpf.make_addplot(self.df['SMA_75'], color='red', width=1.5)
            )
        
        # スタイル設定
        mc = mpf.make_marketcolors(
            up='#17BF63',
            down='#F91880',
            edge='inherit',
            wick='inherit',
            volume='in'
        )
        s = mpf.make_mpf_style(
            marketcolors=mc,
            gridstyle='-',
            gridcolor='#E0E0E0',
            facecolor='white',
            figcolor='white'
        )
        
        # プロット
        kwargs = dict(
            type='candle',
            style=s,
            title=f'{ticker} 株価チャート',
            ylabel='価格 ($)',
            volume=True,
            ylabel_lower='出来高',
            figsize=(14, 8)
        )
        
        if add_plots:
            kwargs['addplot'] = add_plots
        
        if save_path:
            kwargs['savefig'] = save_path
        
        mpf.plot(self.df, **kwargs)
    
    def plot_technical_indicators(self, ticker='Stock'):
        """テクニカル指標のグラフ"""
        fig, axes = plt.subplots(4, 1, figsize=(14, 12), sharex=True)
        
        # 1. 価格とボリンジャーバンド
        axes[0].plot(self.df.index, self.df['Close'], label='終値', linewidth=2, color='#000')
        if 'BB_Upper' in self.df.columns:
            axes[0].plot(self.df.index, self.df['BB_Upper'], 'r--', alpha=0.5, label='上限')
            axes[0].plot(self.df.index, self.df['BB_Middle'], 'b-', alpha=0.5, label='中央')
            axes[0].plot(self.df.index, self.df['BB_Lower'], 'r--', alpha=0.5, label='下限')
            axes[0].fill_between(
                self.df.index,
                self.df['BB_Lower'],
                self.df['BB_Upper'],
                alpha=0.1,
                color='gray'
            )
        axes[0].set_ylabel('価格 ($)', fontsize=11)
        axes[0].set_title(f'{ticker} テクニカル分析', fontsize=14, fontweight='bold')
        axes[0].legend(loc='upper left')
        axes[0].grid(True, alpha=0.3)
        
        # 2. RSI
        if 'RSI' in self.df.columns:
            axes[1].plot(self.df.index, self.df['RSI'], color='#9C27B0', linewidth=2)
            axes[1].axhline(y=70, color='r', linestyle='--', alpha=0.5, label='売られすぎ')
            axes[1].axhline(y=30, color='g', linestyle='--', alpha=0.5, label='買われすぎ')
            axes[1].fill_between(self.df.index, 30, 70, alpha=0.1, color='gray')
            axes[1].set_ylabel('RSI', fontsize=11)
            axes[1].set_ylim(0, 100)
            axes[1].legend(loc='upper left')
            axes[1].grid(True, alpha=0.3)
        
        # 3. MACD
        if 'MACD' in self.df.columns:
            axes[2].plot(self.df.index, self.df['MACD'], label='MACD', linewidth=2, color='#2196F3')
            axes[2].plot(self.df.index, self.df['MACD_Signal'], label='シグナル', linewidth=2, color='#FF9800')
            axes[2].bar(
                self.df.index,
                self.df['MACD_Histogram'],
                label='ヒストグラム',
                color=['g' if val >= 0 else 'r' for val in self.df['MACD_Histogram']],
                alpha=0.3
            )
            axes[2].axhline(y=0, color='black', linestyle='-', linewidth=0.5)
            axes[2].set_ylabel('MACD', fontsize=11)
            axes[2].legend(loc='upper left')
            axes[2].grid(True, alpha=0.3)
        
        # 4. 出来高
        colors = ['g' if close >= open_ else 'r' 
                  for close, open_ in zip(self.df['Close'], self.df['Open'])]
        axes[3].bar(self.df.index, self.df['Volume'], color=colors, alpha=0.5)
        axes[3].set_ylabel('出来高', fontsize=11)
        axes[3].set_xlabel('日付', fontsize=11)
        axes[3].grid(True, alpha=0.3)
        
        plt.xticks(rotation=45)
        plt.tight_layout()
        plt.savefig(f'{ticker}_technical.png', dpi=300, bbox_inches='tight')
        plt.show()
    
    def plot_comparison(self, other_dfs, labels):
        """複数銘柄の比較"""
        plt.figure(figsize=(14, 7))
        
        # 最初の日を基準に正規化
        normalized = (self.df['Close'] / self.df['Close'].iloc[0] - 1) * 100
        plt.plot(self.df.index, normalized, linewidth=2, label=labels[0])
        
        for df, label in zip(other_dfs, labels[1:]):
            if 'Date' in df.columns:
                df.set_index('Date', inplace=True)
            normalized = (df['Close'] / df['Close'].iloc[0] - 1) * 100
            plt.plot(df.index, normalized, linewidth=2, label=label)
        
        plt.xlabel('日付', fontsize=12)
        plt.ylabel('変化率 (%)', fontsize=12)
        plt.title('銘柄比較(騰落率)', fontsize=14, fontweight='bold')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.axhline(y=0, color='black', linestyle='-', linewidth=0.8)
        plt.xticks(rotation=45)
        plt.tight_layout()
        plt.savefig('stock_comparison.png', dpi=300, bbox_inches='tight')
        plt.show()

使用例

from stock_scraper import StockScraper
from technical_analysis import TechnicalAnalyzer
from visualizer import StockVisualizer

# データ取得
scraper = StockScraper()
df = scraper.get_stock_data('AAPL', period='1y')

# テクニカル指標追加
analyzer = TechnicalAnalyzer(df)
df = analyzer.add_all_indicators()

# グラフ作成
visualizer = StockVisualizer(df)
visualizer.plot_candlestick('AAPL', save_path='AAPL_candlestick.png')
visualizer.plot_technical_indicators('AAPL')

5. 複数銘柄の比較分析

def compare_stocks(tickers, period='1y'):
    """複数銘柄を比較分析"""
    scraper = StockScraper()
    all_data = scraper.get_multiple_stocks(tickers, period=period)
    
    if not all_data:
        print("データ取得失敗")
        return
    
    # 統計サマリー
    print("\n=== 銘柄比較サマリー ===")
    summary = []
    
    for ticker, df in all_data.items():
        start_price = df['Close'].iloc[0]
        end_price = df['Close'].iloc[-1]
        change = ((end_price - start_price) / start_price) * 100
        volatility = df['Close'].pct_change().std() * 100
        
        summary.append({
            '銘柄': ticker,
            '開始価格': f'${start_price:.2f}',
            '最新価格': f'${end_price:.2f}',
            '騰落率': f'{change:+.2f}%',
            'ボラティリティ': f'{volatility:.2f}%',
            '最高値': f'${df["High"].max():.2f}',
            '最安値': f'${df["Low"].min():.2f}'
        })
    
    summary_df = pd.DataFrame(summary)
    print(summary_df.to_string(index=False))
    
    # 比較グラフ
    first_ticker = list(all_data.keys())[0]
    first_df = all_data[first_ticker]
    other_dfs = [all_data[t] for t in list(all_data.keys())[1:]]
    
    visualizer = StockVisualizer(first_df)
    visualizer.plot_comparison(other_dfs, tickers)

# 使用例:GAFAM比較
tickers = ['GOOGL', 'AAPL', 'META', 'AMZN', 'MSFT']
compare_stocks(tickers, period='1y')

6. バックテスト機能

class SimpleBacktest:
    """シンプルな売買戦略バックテスト"""
    
    def __init__(self, df, initial_capital=10000):
        self.df = df.copy()
        self.initial_capital = initial_capital
        self.capital = initial_capital
        self.position = 0
        self.trades = []
    
    def moving_average_crossover(self, fast=25, slow=75):
        """移動平均クロス戦略"""
        self.df['signal'] = 0
        
        # ゴールデンクロス(買い)
        self.df.loc[
            (self.df[f'SMA_{fast}'] > self.df[f'SMA_{slow}']) &
            (self.df[f'SMA_{fast}'].shift(1) <= self.df[f'SMA_{slow}'].shift(1)),
            'signal'
        ] = 1
        
        # デッドクロス(売り)
        self.df.loc[
            (self.df[f'SMA_{fast}'] < self.df[f'SMA_{slow}']) &
            (self.df[f'SMA_{fast}'].shift(1) >= self.df[f'SMA_{slow}'].shift(1)),
            'signal'
        ] = -1
        
        return self.execute_trades()
    
    def execute_trades(self):
        """取引を実行"""
        for idx, row in self.df.iterrows():
            if row['signal'] == 1 and self.position == 0:
                # 買い
                shares = self.capital // row['Close']
                if shares > 0:
                    self.position = shares
                    cost = shares * row['Close']
                    self.capital -= cost
                    self.trades.append({
                        'date': row['Date'],
                        'action': '買い',
                        'price': row['Close'],
                        'shares': shares,
                        'value': cost
                    })
            
            elif row['signal'] == -1 and self.position > 0:
                # 売り
                proceeds = self.position * row['Close']
                self.capital += proceeds
                self.trades.append({
                    'date': row['Date'],
                    'action': '売り',
                    'price': row['Close'],
                    'shares': self.position,
                    'value': proceeds
                })
                self.position = 0
        
        # 最終ポジションを清算
        if self.position > 0:
            final_value = self.position * self.df['Close'].iloc[-1]
            self.capital += final_value
        
        return self.get_performance()
    
    def get_performance(self):
        """パフォーマンス計算"""
        total_return = ((self.capital - self.initial_capital) / 
                       self.initial_capital * 100)
        
        print("\n=== バックテスト結果 ===")
        print(f"初期資金: ${self.initial_capital:,.2f}")
        print(f"最終資金: ${self.capital:,.2f}")
        print(f"総リターン: {total_return:+.2f}%")
        print(f"取引回数: {len(self.trades)}")
        
        return pd.DataFrame(self.trades)

# 使用例
from stock_scraper import StockScraper
from technical_analysis import TechnicalAnalyzer

scraper = StockScraper()
df = scraper.get_stock_data('AAPL', period='2y')

analyzer = TechnicalAnalyzer(df)
df = analyzer.add_moving_averages([25, 75])

backtest = SimpleBacktest(df, initial_capital=10000)
trades_df = backtest.moving_average_crossover(fast=25, slow=75)

print("\n=== 取引履歴 ===")
print(trades_df)

7. リアルタイム監視

import time
from datetime import datetime

def monitor_stocks(tickers, interval=60, threshold=2.0):
    """株価をリアルタイム監視
    
    Parameters:
    -----------
    tickers : list
        監視する銘柄リスト
    interval : int
        更新間隔(秒)
    threshold : float
        アラート閾値(変化率%)
    """
    scraper = StockScraper()
    previous_prices = {}
    
    print(f"\n=== 株価監視開始 ===")
    print(f"銘柄: {', '.join(tickers)}")
    print(f"更新間隔: {interval}秒")
    print(f"アラート閾値: ±{threshold}%\n")
    
    try:
        while True:
            current_time = datetime.now().strftime('%H:%M:%S')
            print(f"\n[{current_time}] 更新中...")
            
            for ticker in tickers:
                df = scraper.get_stock_data(ticker, period='1d', interval='1m')
                
                if df is not None and len(df) > 0:
                    current_price = df['Close'].iloc[-1]
                    
                    if ticker in previous_prices:
                        change = ((current_price - previous_prices[ticker]) / 
                                previous_prices[ticker] * 100)
                        
                        status = '📈' if change > 0 else '📉'
                        print(f"{status} {ticker}: ${current_price:.2f} ({change:+.2f}%)")
                        
                        # アラート
                        if abs(change) >= threshold:
                            alert = '🚨 大きな変動!' if abs(change) >= threshold * 2 else '⚠️ 注意'
                            print(f"   {alert} {abs(change):.2f}%の変動を検出")
                    else:
                        print(f"📊 {ticker}: ${current_price:.2f}")
                    
                    previous_prices[ticker] = current_price
            
            time.sleep(interval)
            
    except KeyboardInterrupt:
        print("\n\n監視を終了します")

# 使用例(Ctrl+Cで停止)
# monitor_stocks(['AAPL', 'GOOGL', 'MSFT'], interval=300, threshold=1.5)

8. 日本株対応

# 日本株の銘柄コード
japanese_stocks = {
    '7203.T': 'トヨタ自動車',
    '9984.T': 'ソフトバンクグループ',
    '6758.T': 'ソニーグループ',
    '7974.T': '任天堂',
    '6501.T': '日立製作所',
    '8306.T': '三菱UFJ',
    '9432.T': 'NTT',
    '6861.T': 'キーエンス'
}

def analyze_japanese_market():
    """日本株市場を分析"""
    scraper = StockScraper()
    
    print("\n=== 日本株分析 ===")
    results = []
    
    for ticker, name in japanese_stocks.items():
        df = scraper.get_stock_data(ticker, period='3mo')
        
        if df is not None:
            analyzer = TechnicalAnalyzer(df)
            df = analyzer.add_all_indicators()
            
            latest = df.iloc[-1]
            change = ((latest['Close'] - df['Close'].iloc[0]) / 
                     df['Close'].iloc[0] * 100)
            
            results.append({
                '銘柄名': name,
                'コード': ticker,
                '終値': f'¥{latest["Close"]:,.0f}',
                '3ヶ月騰落': f'{change:+.2f}%',
                'RSI': f'{latest["RSI"]:.1f}',
                '売買判断': '買い' if latest['RSI'] < 40 else '売り' if latest['RSI'] > 60 else '中立'
            })
    
    results_df = pd.DataFrame(results)
    print(results_df.to_string(index=False))
    
    return results_df

# 実行
analyze_japanese_market()

まとめ

yfinanceで株価データを取得・分析できます!

重要ポイント

  • ✅ yfinanceは無料で使いやすい
  • ✅ テクニカル指標でトレンド把握
  • ✅ mplfinanceで美しいチャート作成
  • ✅ バックテストで戦略検証
  • ✅ 日本株にも対応

応用例

  • 自動売買システム構築
  • ポートフォリオ管理
  • アービトラージ検出
  • リスク管理ダッシュボード
  • 経済指標との相関分析

注意事項

  • ⚠️ 投資は自己責任
  • ⚠️ データの正確性を確認
  • ⚠️ リアルタイムデータには遅延あり
  • ⚠️ 過去の成績は将来を保証しない