Untitled

 avatar
unknown
plain_text
a month ago
9.2 kB
3
Indexable
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Dec 19 12:44:22 2024

@author: giacomostella
"""
"""
The following code lines are divided as follows:
    -Import libraries
    -Classes of Models 
        -Black and Scholes
        -Local Volatility
        -Heston Model with Fourier inversion)
    -Portfolio and Option classes
    -Application of the Models
        -Portfolio of stocks data import
        -Assumptions input
        -Option price creation
        -Results print

In between these lines you will kindly find an explanation of the models behind
the calculations and you will understand how option pricing is set and utilized.
Thus will comprehend three option pricing models that will deliver a series of 
prices. Results could be suited for the inclusion of some level of protection
into a random portfolio of stocks. 
My ultimate objective is to calculate price for both call and put option related
to each stock; thus serving for taking more informed decisions in the area of
portfolio risk management.
"""

#Import libraries according to what I did in Python class
import yfinance as yf #for importing data from Yahoo Finance
import pandas as pd
import numpy as np
from scipy.stats import norm
from scipy.integrate import quad
from itertools import groupby


#Creation of Classes of Models
"""
As seen in class, I will procede setting the basis of my coding. I create a set
of classes that define the functioning of my calculus; thus including variables
and formulas that are drafted to calculate the price of call and put options.
"""

# Black-Scholes Model
class BlackScholes:
    def __init__(self, S, K, T, r, sigma):
        self.S = S
        self.K = K
        self.T = T
        self.r = r
        self.sigma = sigma

    def d1(self):
        return (np.log(self.S / self.K) + (self.r + 0.5 * self.sigma**2) * self.T) / (self.sigma * np.sqrt(self.T))

    def d2(self):
        return self.d1() - self.sigma * np.sqrt(self.T)

    def call_price(self):
        d1 = self.d1()
        d2 = self.d2()
        return self.S * norm.cdf(d1) - self.K * np.exp(-self.r * self.T) * norm.cdf(d2)

    def put_price(self):
        d1 = self.d1()
        d2 = self.d2()
        return self.K * np.exp(-self.r * self.T) * norm.cdf(-d2) - self.S * norm.cdf(-d1)

# Local Volatility Model
class LocalVolatility:
    def __init__(self, S, K, T, r, local_vol_surface):
        self.S = S
        self.K = K
        self.T = T
        self.r = r
        self.local_vol_surface = local_vol_surface

    def local_vol(self):
        return self.local_vol_surface(self.S, self.K, self.T)

    def call_price(self):
        sigma = self.local_vol()
        return BlackScholes(self.S, self.K, self.T, self.r, sigma).call_price()

    def put_price(self):
        sigma = self.local_vol()
        return BlackScholes(self.S, self.K, self.T, self.r, sigma).put_price()

# Heston Model with Fourier Inversion
class HestonModel:
    def __init__(self, S, K, T, r, v0, kappa, theta, sigma, rho):
        self.S = S
        self.K = K
        self.T = T
        self.r = r
        self.v0 = v0
        self.kappa = kappa
        self.theta = theta
        self.sigma = sigma
        self.rho = rho

    def characteristic_function(self, phi):
        u = -0.5
        b = self.kappa + self.rho * self.sigma * phi * 1j
        d = np.sqrt(b**2 - self.sigma**2 * (2 * u * 1j * phi - phi**2))
        g = (b - d) / (b + d)
        C = self.r * phi * 1j * self.T + (self.kappa * self.theta / self.sigma**2) * (
            (b - d) * self.T - 2 * np.log((1 - g * np.exp(-d * self.T)) / (1 - g))
        )
        D = ((b - d) / self.sigma**2) * ((1 - np.exp(-d * self.T)) / (1 - g * np.exp(-d * self.T)))
        return np.exp(C + D * self.v0 + 1j * phi * np.log(self.S))

    def call_price(self):
        def integrand(phi):
            return np.real(
                np.exp(-1j * phi * np.log(self.K)) * self.characteristic_function(phi) / (1j * phi)
            )

        integral = quad(integrand, 0, np.inf)[0]
        return 0.5 * (self.S - self.K * np.exp(-self.r * self.T)) + (1 / np.pi) * integral

    def put_price(self):
        call_price = self.call_price()
        return call_price + self.K * np.exp(-self.r * self.T) - self.S

# Portfolio and Option Classes
"""
In the same way I defined the classes for each Model, I set the classes for a
portfolio of stocks and for option pricing.
In the first class (Portfolio) I create a more complicated class, as I include 
that the portfolio is constructed basing on real-time data imported from Yahoo
Finance. (In this regard I know that some Windows users may not access the data
, as YF now restrticted his data export. I am working on a Mac-rule type of coding
, so I can ensure data import from Yahoo Finance only from Mac users).
"""

class Portfolio:
    def __init__(self, tickers):
        self.tickers = tickers
        self.prices = self.get_prices()
        self.options = []

    def get_prices(self):
        """Fetch real-time prices for the tickers."""
        data = yf.download(self.tickers, period="1d", interval="1m")['Adj Close']
        return data.iloc[-1]  # Get the latest prices

    def add_option(self, option):
        """Add an option to the portfolio."""
        self.options.append(option)

class Option:
    def __init__(self, underlying, option_type, strike_price, expiry, pricing_model):
        self.underlying = underlying
        self.option_type = option_type
        self.strike_price = strike_price
        self.expiry = expiry
        self.pricing_model = pricing_model

    def price(self):
        """Calculate the price of the option using the selected pricing model."""
        if self.option_type == 'call':
            return self.pricing_model.call_price()
        elif self.option_type == 'put':
            return self.pricing_model.put_price()

"""For now, I set the basis for the option pricing, using three different model
I am able to apply their different rules into a set of stocks, delivering a set
of call and put prices. The models are created in order to give useful information 
that could simulate the reality of the market. In the following lines of code I
import a portfolio of stocks, with the aim of applying those models to real world
data. The following lines work as follows: 1. I set and print the different stock
prices imported from Yahoo Finance. 2. For each of the models (Black and Scholes,
Local Volatility and Heston Model, I code and insert directly the variables needed
to work the model in a proper way. 3. For each stock in the portfolio I deliver
a call price and a put price relative to each Model applied. 4. I print results
in an understandable way.
"""
portfolio_tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA", "META", "NVDA", "NFLX", "ADBE", "INTC"]
portfolio = Portfolio(portfolio_tickers)

# Display the portfolio prices
print("Portfolio Prices:")
print(portfolio.prices)

# Defining horizon of the option and different variables of the model
T = 1  # 1 year to maturity
r = 0.05  # Risk-free rate
sigma = 0.2  # Assumed volatility

for ticker, price in portfolio.prices.items():
    # Add call and put options for each stock
    call_option = Option(
        underlying=ticker,
        option_type='call',
        strike_price=price,  # At-the-money strike
        expiry=T,
        pricing_model=BlackScholes(price, price, T, r, sigma)
    )
    put_option = Option(
        underlying=ticker,
        option_type='put',
        strike_price=price,  # At-the-money strike
        expiry=T,
        pricing_model=BlackScholes(price, price, T, r, sigma)
    )
    portfolio.add_option(call_option)
    portfolio.add_option(put_option)

## Define options for the portfolio
T = 1  # 1 year to maturity
r = 0.05  # Risk-free rate

# Local volatility surface example: linear volatility change with stock price
local_vol_surface = lambda S, K, T: 0.2 + 0.1 * (S - 100) / 100

# Heston model parameters
v0 = 0.04  # Initial variance
kappa = 2.0  # Mean reversion speed
theta = 0.04  # Long-term variance
sigma_vol = 0.3  # Volatility of volatility
rho = -0.7  # Correlation

print("\nOption Prices:")
for ticker, price in portfolio.prices.items():
    # Add all options for each stock
    print(f"\n{ticker}:")
    for model_name, model_class in [
        ("BlackScholes", BlackScholes(price, price, T, r, 0.2)),
        ("LocalVolatility", LocalVolatility(price, price, T, r, local_vol_surface)),
        ("HestonModel", HestonModel(price, price, T, r, v0, kappa, theta, sigma_vol, rho))
    ]:
        call_option = Option(
            underlying=ticker,
            option_type='call',
            strike_price=price,  # At-the-money strike
            expiry=T,
            pricing_model=model_class
        )
        put_option = Option(
            underlying=ticker,
            option_type='put',
            strike_price=price,  # At-the-money strike
            expiry=T,
            pricing_model=model_class
        )
        print(f"  {call_option.option_type.capitalize()} ({model_name}): {call_option.price():.2f}")
        print(f"  {put_option.option_type.capitalize()} ({model_name}): {put_option.price():.2f}")
Leave a Comment