PythonでVIX指数(恐怖指数)を分析する方法|市場リスク管理と自動監視システム

Python実装・コード

VIX指数は市場の恐怖指数とも呼ばれ、S&P500オプションの価格から算出されます。yfinanceで「^VIX」ティッカーから取得でき、リスク管理の指標として活用できます。ということで、この記事ではVIX分析の実装から自動監視システムの構築まで手順をまとめます。

📘 外部参考yfinance 公式GitHubリポジトリPyPIページ

📘 外部参考CBOE VIX Index(公式)VIX(Wikipedia)

VIX指数の取得

import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def get_vix_data(period='5y'):
    """VIX指数と主要指数を取得"""
    vix = yf.download('^VIX', period=period, progress=False)['Close']
    sp500 = yf.download('^GSPC', period=period, progress=False)['Close']
    nikkei = yf.download('^N225', period=period, progress=False)['Close']
    
    df = pd.DataFrame({
        'VIX': vix,
        'SP500': sp500,
        'Nikkei': nikkei
    }).dropna()
    
    return df

df = get_vix_data()
print(f"データ期間: {df.index[0].date()} ~ {df.index[-1].date()}")
print(f"VIX 現在値: {df['VIX'].iloc[-1]:.2f}")
print(f"VIX 平均値: {df['VIX'].mean():.2f}")
print(f"VIX 最大値(コロナ禍): {df['VIX'].max():.2f}")

VIXレベル別の市場分類

def classify_vix_regime(vix_value):
    """VIX水準から市場状態を分類"""
    if vix_value < 15:
        return 'LOW', '低ボラ・強気相場'
    elif vix_value < 20:
        return 'NORMAL', '通常相場'
    elif vix_value < 30:
        return 'ELEVATED', '警戒相場'
    elif vix_value < 40:
        return 'HIGH', '高ボラ・弱気相場'
    else:
        return 'EXTREME', '危機的状況'

def analyze_vix_regimes(df):
    """VIX水準別のリターン分析"""
    df = df.copy()
    df['SP500_return'] = df['SP500'].pct_change()
    df['VIX_regime'] = df['VIX'].apply(lambda x: classify_vix_regime(x)[0])
    
    regime_stats = df.groupby('VIX_regime')['SP500_return'].agg([
        ('平均日次リターン', 'mean'),
        ('リターン標準偏差', 'std'),
        ('発生日数', 'count')
    ])
    regime_stats['平均日次リターン'] = regime_stats['平均日次リターン'].map('{:.4%}'.format)
    regime_stats['リターン標準偏差'] = regime_stats['リターン標準偏差'].map('{:.4%}'.format)
    
    return regime_stats

regime_stats = analyze_vix_regimes(df)
print(regime_stats)

VIXを使ったリスク回避戦略

def vix_risk_strategy(df, vix_exit=25, vix_entry=20, initial_capital=1000000):
    """
    VIXベースのリスク回避戦略
    vix_exit: VIXがこの値を超えたら株式から撤退
    vix_entry: VIXがこの値を下回ったら株式に再参入
    """
    capital = initial_capital
    position = 0
    in_market = True  # 最初は市場に参加
    
    portfolio_values = []
    trades = []
    
    prices = df['SP500'].values
    vix_values = df['VIX'].values
    
    for i in range(1, len(prices)):
        prev_vix = vix_values[i-1]
        current_price = prices[i]
        
        # VIXが上昇しすぎたら撤退
        if prev_vix > vix_exit and in_market and position > 0:
            capital += position * current_price
            trades.append({'action': 'SELL(VIX-Exit)', 'vix': prev_vix, 'price': current_price})
            position = 0
            in_market = False
        
        # VIXが落ち着いたら再参入
        elif prev_vix < vix_entry and not in_market:
            shares = capital / current_price
            position = shares
            capital = 0
            trades.append({'action': 'BUY(VIX-Entry)', 'vix': prev_vix, 'price': current_price})
            in_market = True
        
        # 初回買い
        elif i == 1 and in_market:
            shares = capital / current_price
            position = shares
            capital = 0
        
        total_value = capital + position * current_price
        portfolio_values.append(total_value)
    
    final_value = portfolio_values[-1]
    total_return = (final_value - initial_capital) / initial_capital
    buy_hold_return = (prices[-1] - prices[1]) / prices[1]
    
    print(f"VIX戦略リターン: {total_return:.2%}")
    print(f"バイ&ホールドリターン: {buy_hold_return:.2%}")
    print(f"リスク回避トレード数: {len(trades)}回")
    
    return portfolio_values, trades

portfolio_vix, trades_vix = vix_risk_strategy(df)

VIXスパイク検出と早期警戒

def detect_vix_spike(df, spike_threshold=1.2, window=5):
    """
    VIXスパイク(急上昇)を検出する早期警戒システム
    spike_threshold: N日平均に対する倍率
    """
    df = df.copy()
    df['VIX_MA'] = df['VIX'].rolling(window).mean()
    df['VIX_spike'] = df['VIX'] / df['VIX_MA']
    df['spike_alert'] = df['VIX_spike'] > spike_threshold
    
    # 直近のアラート
    recent = df.tail(10)
    
    current_vix = df['VIX'].iloc[-1]
    current_spike = df['VIX_spike'].iloc[-1]
    regime, regime_label = classify_vix_regime(current_vix)
    
    print(f"現在のVIX: {current_vix:.2f} ({regime_label})")
    print(f"5日平均比: {current_spike:.2f}x")
    
    if df['spike_alert'].iloc[-1]:
        print("警告: VIXスパイク検出!ポジション縮小を検討してください")
    
    return df

df_analyzed = detect_vix_spike(df)

Discord通知との連携

import requests
from datetime import datetime

def send_discord_notification(webhook_url, message):
    payload = {"content": message}
    requests.post(webhook_url, json=payload)

def daily_vix_monitor(discord_webhook_url):
    """毎日VIXを監視してDiscordに通知"""
    df = get_vix_data(period='1mo')
    df = detect_vix_spike(df)
    
    current_vix = df['VIX'].iloc[-1]
    regime, regime_label = classify_vix_regime(current_vix)
    spike_ratio = df['VIX_spike'].iloc[-1]
    
    vix_change = (df['VIX'].iloc[-1] - df['VIX'].iloc[-2]) / df['VIX'].iloc[-2]
    
    emoji = {'LOW': 'GREEN', 'NORMAL': 'WHITE', 'ELEVATED': 'YELLOW', 'HIGH': 'RED', 'EXTREME': 'SKULL'}
    
    message = (
        f"VIX日次レポート {datetime.now().strftime('%Y-%m-%d')}
"
        f"VIX: {current_vix:.2f} ({regime_label})
"
        f"前日比: {vix_change:+.2%}
"
        f"5日平均比: {spike_ratio:.2f}x
"
    )
    
    if current_vix > 30:
        message += "警告: VIXが30超!リスク管理を見直してください"
    elif df['spike_alert'].iloc[-1]:
        message += "注意: VIXスパイク検出 - ポジション縮小を検討"
    
    send_discord_notification(discord_webhook_url, message)
    print("Discord通知送信完了")

まとめ

実際に使ってみて、VIXを毎日チェックするのは意外と手間なので、Discordへの自動通知が便利だと感じています。VIX 30超が続くときはポジションを減らすルールを決めておくと、感情に左右されにくくなるかと思います。

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