Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

변동성 곡면

Python을 사용하여 옵션 계약군에 대한 내재 변동성 곡면을 생성해 보겠습니다. 이는 옵션 분석에 매우 흔히 쓰이는 도구이며, 많은 퀀트 트레이딩 전략의 핵심 구성 요소입니다.

블랙-숄즈 모델은 옵션 가격을 매기는 레시피와 같습니다. 현재 주가, 행사가격, 만기까지의 시간, 이자율, 변동성 같은 재료를 넣으면 옵션의 이론적인 가격을 내놓습니다. 그러나 현실에서 트레이더들은 흔히 거꾸로 작업합니다. 즉, 옵션의 실제 시장 가격을 보고 블랙-숄즈 공식이 그 가격을 내도록 만드는 변동성 수치가 무엇인지 알아내려 합니다. 이렇게 역산한 수치를 내재 변동성(implied volatility), 줄여서 IV라고 부릅니다. 내재 변동성은 주가가 앞으로 얼마나 크게 출렁일지에 대한 시장의 예측이라고 생각하십시오. IV가 높다는 것은 트레이더들이 더 큰 가격 변동을 예상하고 있으며(그래서 보험 삼아 옵션에 더 많은 돈을 지불할 의향이 있음을 뜻합니다), IV가 낮다는 것은 앞으로 더 순탄한 흐름을 예상하고 있음을 시사합니다. 이는 일종의 공포 지수와 같습니다. 투자자들이 미래를 불안해하면 옵션을 통한 보호에 더 많은 돈을 내게 되고, 그 결과 IV가 올라갑니다.

이것이 정말 흥미로운 이유는 같은 기초 주식이라도 행사가격과 만기일이 다른 옵션은 IV가 서로 다른 경우가 많다는 점입니다. 이러한 IV를 그래프로 그리면 이른바 변동성 곡면(volatility surface)을 얻게 됩니다. 이는 미래 변동성에 대한 시장의 기대가 행사가격과 시간에 따라 어떻게 달라지는지를 보여 주는 3D 시각화입니다. 이 곡면에는 흔히 변동성 스마일(volatility smile)이나 변동성 스큐(volatility skew) 같은 패턴이 나타나는데, 외가격 풋옵션의 IV가 콜옵션보다 높아지는 현상으로, 시장이 상승장보다 폭락을 더 걱정하는 경향을 반영합니다. 그리고 이 곡면을 사용하여 비효율성과 차익거래 기회를 찾아낼 수 있습니다.

변동성 곡면을 생성하려면 몇 가지 라이브러리를 설치해야 합니다. 대부분 표준 라이브러리이지만, py_vollib 라이브러리는 예외입니다. 이는 블랙-숄즈 계산을 효율적으로 수행하는 VolLib 라이브러리의 Python 래퍼입니다.

pip install numpy pandas seaborn matplotlib py_vollib
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import scipy.interpolate as interpolate
import py_vollib.black_scholes.implied_volatility as iv
from py_vollib.black_scholes import black_scholes as bs

이제 기초 자산의 가격 데이터와 옵션 체인의 행사가격 데이터를 불러와야 합니다. 실무에서는 이 데이터를 Bloomberg나 다른 데이터 공급업체에서 불러올 것입니다. 하지만 지금은 그냥 CSV 파일에서 불러오겠습니다. 이 데이터는 기초 자산 SPY에 대한 옵션 계약 체인으로 구성됩니다. 각 계약에 대해 행사가격, 가격, 만기일이 있습니다. 이를 바탕으로 블랙-숄즈 공식을 사용하여 각 계약의 내재 변동성을 계산할 수 있습니다.

contractSymbollastTradeDatestrikelastPriceinTheMoneycurrencyoptionTypeexpirationdaysToExpiration
SPY241227C005980002024-11-26 20:57:08+00:00598.010.0TrueUSDcall2024-12-27 23:59:5931
SPY241227C005990002024-11-26 21:08:56+00:00599.09.4TrueUSDcall2024-12-27 23:59:5931
SPY241227C006000002024-11-26 21:11:54+00:00600.08.8TrueUSDcall2024-12-27 23:59:5931
SPY241227C006010002024-11-26 20:50:47+00:00601.08.06FalseUSDcall2024-12-27 23:59:5931
SPY241227C006020002024-11-26 20:57:59+00:00602.07.39FalseUSDcall2024-12-27 23:59:5931
SPY241227C006025002024-11-26 21:00:20+00:00602.57.05FalseUSDcall2024-12-27 23:59:5931

콜옵션에 대한 블랙-숄즈 공식은 다음과 같습니다.

여기서:

  • 는 콜옵션의 가격입니다
  • 는 기초자산의 현재 가격입니다
  • 는 옵션의 행사가격입니다
  • 은 무위험 이자율(즉, 미국 3개월 만기 재무부 단기채권의 수익률)입니다
  • 는 기초자산의 변동성입니다
  • 는 만기까지 남은 시간입니다
  • 는 표준정규분포의 누적분포함수입니다

블랙-숄즈 모형에서 *머니니스(moneyness)*라는 개념이 나오며, 이는 기초 자산의 현재 가격과 옵션 행사가격 사이의 관계를 나타냅니다. 옵션이 내재가치를 가질 때, 즉 옵션을 행사하면 양의 현금흐름이 발생할 때 그 옵션을 *내가격(in the money)*이라고 합니다(흔히 ITM으로 줄여 쓰며, 그 반대는 OTM, 즉 외가격(out of the money)입니다). 콜 옵션의 경우, 행사가격이 기초자산의 현재 시장 가격보다 낮을 때 이러한 상황이 발생하며, 보유자는 기초자산을 시장 가치보다 낮은 가격에 매수할 수 있습니다. 반대로 풋 옵션의 경우, 행사가격이 현재 시장 가격보다 높을 때 옵션이 ITM 상태가 되며, 보유자는 기초자산을 시장 가치보다 높은 가격에 매도할 수 있습니다.

  • 내가격(In-the-Money, ITM): 내재가치를 가진 옵션입니다. 콜의 경우 행사가격이 현재 시장 가격보다 낮고, 풋의 경우 높습니다.
  • 등가격(ATM): 행사가격이 기초자산의 현재 시장 가격과 거의 같은 옵션입니다.
  • 외가격(Out-of-the-Money, OTM): 내재가치가 없는 옵션입니다. 콜의 경우 행사가격이 기초자산의 현재 시장 가격보다 높다는 뜻이며, 풋의 경우 낮다는 뜻입니다.

ITM 옵션은 내재가치 때문에 일반적으로 프리미엄이 더 높으며, 기초 자산 가격이 앞으로도 유리하게 움직일 것으로 예상하는 트레이더에게 더 매력적입니다. 대체로 ITM 옵션은 시장 상황 변화에 더 민감할 수 있으므로 관련 위험도 더 높습니다. 그리고 일반적으로 OTM 옵션은 더 저렴하지만 가치 없이 만기될 확률이 더 높습니다.

시장 데이터를 블랙-숄즈 공식에 대입하여 체인에 있는 각 옵션의 내재 변동성을 계산합니다. 무위험 이자율로는 3개월 만기 미국 재무부 단기채권의 수익률을 사용하며, 분석하는 옵션에 맞게 현재 실제 무위험 이자율로 바꾸어 사용하십시오.

def get_current_market_data():
    spy_price = pd.read_csv(PRICE_CSV)
    current_price = spy_price["Close"].iloc[-1]
    risk_free_rate = 4.3 / 100  # 3-month Treasury yield (4.3% as of 2024-11-27)
    return risk_free_rate, spy_price, current_price


def calculate_iv(row, risk_free_rate, sigma):
    try:
        price = bs(
            S=row["lastPrice"],
            K=row["strike"],
            t=row["t"],
            r=risk_free_rate,
            sigma=sigma,
            flag="c",
        )
        return iv.implied_volatility(
            price=price,
            S=row["lastPrice"],
            K=row["strike"],
            t=row["t"],
            r=risk_free_rate,
            flag="c",
        )
    except:
        print(f"Error calculating IV for {row['strike']}")
        return np.nan

변동성 곡면은 여러 행사가격과 만기일에 걸친 내재 변동성을 3차원으로 나타낸 것입니다. 이 곡면은 시장이 다양한 행사가격과 만기에 걸쳐 옵션의 시장 가격을 어떻게 매기는지에 대한 포괄적인 관점을 제공하며, 시장의 기대와 위험 선호에 나타나는 중요한 패턴을 드러냅니다. 이론적으로 완벽한 블랙-숄즈 세계에서는 모든 행사가격과 만기에 걸쳐 변동성이 일정하므로 이 곡면은 완전히 평평할 것입니다. 그러나 현실에서는 곡면에서 다양한 패턴과 왜곡이 관찰됩니다.

이산적인 시장 데이터로부터 매끄럽고 연속적인 변동성 곡면을 구성하기 위해 보간 기법을 사용합니다. 이 구현에서는 SciPy의 SmoothBivariateSpline을 통해 이변량 스플라인 보간 방법을 사용합니다. 이 방법은 정확도와 매끄러움 사이의 균형을 유지하면서 관측된 내재 변동성 점들을 지나는 매끄러운 곡면을 적합합니다. 평활화 매개변수(s=0.1)는 곡면이 원래 데이터 점에 얼마나 가깝게 적합되는지를 조절합니다. 값이 작을수록 데이터에 더 밀착되게 적합되고, 값이 클수록 시장 잡음에 덜 민감할 수 있는 더 매끄러운 곡면이 만들어집니다.

def create_volatility_surface(calls_data):
    surface = (
        calls_data[["daysToExpiration", "strike", "impliedVolatility"]]
        .pivot_table(
            values="impliedVolatility", index="strike", columns="daysToExpiration"
        )
        .dropna()
    )

    # Prepare interpolation data
    x = surface.columns.values
    y = surface.index.values
    X, Y = np.meshgrid(x, y)
    Z = surface.values

    # Create interpolation points
    x_new = np.linspace(x.min(), x.max(), 100)
    y_new = np.linspace(y.min(), y.max(), 100)
    X_new, Y_new = np.meshgrid(x_new, y_new)

    # Perform interpolation
    spline = interpolate.SmoothBivariateSpline(
        X.flatten(), Y.flatten(), Z.flatten(), s=0.1
    )
    Z_smooth = spline(x_new, y_new)

    return X_new, Y_new, Z_smooth


def plot_volatility_surface(X, Y, Z):
    plt.style.use("default")
    sns.set_style("whitegrid", {"axes.grid": False})

    fig = plt.figure(figsize=(12, 8))
    ax = fig.add_subplot(111, projection="3d")

    ax.plot_surface(
        X, Y, Z, cmap="viridis", alpha=0.9, linewidth=0, antialiased=True
    )

    ax.set_xlabel("Days to Expiration")
    ax.set_ylabel("Strike Price")
    ax.set_zlabel("Implied Volatility")
    ax.set_title("SPY Volatility Surface")
    ax.view_init(elev=20, azim=45)

    plt.tight_layout()
    plt.show()

이제 main 함수를 실행하여 변동성 곡면을 생성할 수 있습니다.

def volsurface():
    # Get market data
    risk_free_rate, historical_price, current_price = get_current_market_data()
    print(f"Using current price: {current_price}")

    # Load and process calls data
    calls = pd.read_csv(OPTIONS_CSV)
    calls["t"] = calls["daysToExpiration"] / 365.0

    # Calculate historical volatility
    sigma = historical_price['Close'].pct_change().std() * np.sqrt(252)

    # Calculate implied volatilities
    calls["calculated_iv"] = calls.apply(
        lambda row: calculate_iv(row, risk_free_rate, sigma), axis=1
    )
    calls = calls.dropna(subset=["calculated_iv"])

    # Create and plot surface
    X_new, Y_new, Z_smooth = create_volatility_surface(calls)
    plot_volatility_surface(X_new, Y_new, Z_smooth)
Volatility Surface

변동성 곡면은 다양한 행사가격과 만기일에 걸친 미래 변동성에 대한 시장의 기대를 보여 줍니다.

변동성 차익거래

이 곡면은 여러 행사가격과 만기에 걸쳐 미래 변동성에 대한 시장의 기대를 보여 줍니다. 곡면에 불규칙한 부분이나 “융기“가 있으면 옵션 가격이 잘못 매겨졌음을 나타낼 수 있습니다. 이는 *변동성 차익거래(volatility arbitrage)*라는 거래 전략의 한 유형입니다. 본질적으로 주가가 얼마나 움직일지에 대한 시장의 기대가 너무 높은지 아니면 너무 낮은지에 베팅하는 것입니다. 요컨대 시장이 예측하는 미래 변동성과 실제로 그렇게 될 것이라고 여러분이 믿는 변동성 사이의 차이에서 수익을 얻으려는 것입니다.

곡면의 기울기를 계산하고 기울기가 특정 임곗값보다 큰 영역을 찾음으로써, 주변 영역에 비해 변동성이 급격하게 변하는 영역을 찾아 시장이 옵션 가격을 잘못 매긴 영역을 식별할 수 있습니다.

# Find areas where volatility changes abruptly
 def find_vol_arbitrage(surface, threshold=0.02):
      vol_gradients = np.gradient(surface)
      anomalies = np.where(abs(vol_gradients) > threshold)
    return anomalies

변동성 곡면에서 비효율성을 찾아낸다면, 저평가된 옵션을 매수하고 고평가된 옵션을 매도한 뒤 기초 자산으로 델타 헤지하여 확정적인 수익을 얻을 수 있습니다. 물론 비효율성을 찾는 일은 쉽지 않으며 많은 노력이 필요합니다. SPY와 같은 자산에 대한 옵션의 경우 시장이 매우 효율적이고, 수천 명의 다른 사람들이 나노초에 가까운 속도로 비효율성을 찾으려 하고 있으므로 성공할 확률은 매우 낮습니다.

Volatility Surface Anomaly

변동성 곡면의 이상 현상입니다.

스큐 트레이딩

변동성 스큐는 외가격(out-of-the-money) 옵션, 등가격(at-the-money) 옵션, 내가격(in-the-money) 옵션 사이의 내재 변동성 차이를 의미합니다. 스큐 트레이딩은 서로 다른 옵션 행사가격에 걸친 내재 변동성의 불일치를 활용하는 것입니다. 트레이더들은 스큐를 분석하여 잘못 가격이 매겨진 옵션을 찾아내고, 이러한 불일치가 정규화될 것으로 예상하여 이익을 얻을 수 있는 전략을 실행합니다.

변동성 “스마일“은 변동성 곡면에서 나타나는 볼록한 패턴으로, ATM 옵션에 비해 내가격(ITM) 옵션과 OTM 옵션 모두의 내재 변동성이 더 높은 현상입니다. 그래프로 그리면 미소나 비웃음 모양을 띠는 이 볼록성은 시장 참여자들이 현재 시장 가격에서 멀리 떨어진 옵션에 더 많은 비용을 지불할 의향이 있음을 나타냅니다. 이러한 볼록한 모양이 나타나는 이유는 트레이더들이 상방 위험과 하방 위험 모두에 대한 보호를 추구하는 경우가 많기 때문입니다. 즉, 시장 폭락에 대비해 헤지하기 위해 OTM 풋을 매수하고, 잠재적인 랠리를 포착하기 위해 OTM 콜을 매수합니다. 이러한 행동은 앞서 논의한 변동성 차익거래와 직접적으로 관련되는데, 스마일 패턴은 일반적으로 안정적인 이 관계에서 일시적인 왜곡이 생길 때 트레이더가 이를 활용할 기회를 만들어 낼 수 있기 때문입니다.

변동성 스마일은 대칭적이며, ATM 옵션에 비해 OTM 풋과 OTM 콜 모두의 내재 변동성이 더 높습니다.

변동성 스머크는 비대칭적이며, OTM 콜에 비해 OTM 풋의 내재 변동성이 더 높습니다.

스큐 트레이딩은 시장의 공포 수준이 고르게 맞춰질 것에 베팅하는 것이라고 생각하십시오. 트레이더들이 시장 폭락을 크게 우려하면, “복권”(콜 옵션)에 비해 “보험”(풋 옵션)의 가격을 끌어올립니다. 이로 인해 옵션 가격에 한쪽으로 치우친, 즉 “스큐된” 패턴이 생깁니다. 스큐 트레이더는 이러한 공포가 커지거나 줄어들 것에 베팅하여 수익을 내려고 합니다. 시장이 지나치게 불안해한다고 생각하면 상대적으로 저렴한 콜을 매수하고 비싼 풋을 매도할 수 있으며, 사람들이 충분히 걱정하지 않는다고 생각하면 그 반대로 할 수 있습니다. 이는 마치 공포의 차익거래자가 되어, 시장 심리가 공황과 안일함이라는 극단 사이를 오갈 때 수익을 얻는 것과 같습니다.

행사가격에 대한 IV에 선형 회귀를 수행하고 추세를 살펴봄으로써 스큐를 분석할 수 있습니다.

def analyze_volatility_skew(options_data):
    # Filter ATM options
    atm_strike = options_data.iloc[(options_data['strike'] - options_data['lastPrice']).abs().argsort()[:1]]['strike'].values[0]

    # Calculate skew: IV vs Strike
    skew_data = options_data.copy()
    skew_data['strike_diff'] = skew_data['strike'] - atm_strike
    skew_data = skew_data[~skew_data['impliedVolatility'].isna()]

    # Perform linear regression to quantify skew
    slope, intercept, r_value, p_value, std_err = linregress(skew_data['strike_diff'], skew_data['impliedVolatility'])

    print(f"Skew Slope: {slope:.5f}, R-squared: {r_value**2:.5f}")

    # Plot the skew
    plt.figure(figsize=(10, 6))
    plt.scatter(skew_data['strike_diff'], skew_data['impliedVolatility'], label='Options')
    plt.plot(skew_data['strike_diff'], intercept + slope * skew_data['strike_diff'], 'r', label='Linear Fit')
    plt.xlabel('Strike Difference (Strike - ATM Strike)')
    plt.ylabel('Implied Volatility')
    plt.title('Volatility Skew Analysis')
    plt.legend()
    plt.show()

    return slope

원문 © Stephen Diehl, stephendiehl.com · 글 CC BY-SA 4.0, 코드 MIT · Stephen Diehl은 런던의 소프트웨어 엔지니어이자 작가입니다(저자가 정한 소개 문구). 이 글은 저자가 개인 자격으로 쓴 것이며 어떤 조직과의 연관도 밝히거나 암시해서는 안 됩니다 (라이선스와 고지).
이 페이지는 원문을 한국어로 옮기며 변경한 비공식 번역이며 같은 CC BY-SA 4.0으로 제공합니다 · yeokja 한국어 번역 · Anthropic 사의 claude-opus-5-5 모델을 활용하여 번역되었으며 학습을 모두 비허용한 상태로 작업하였습니다.