Binomial Options Pricing (#1017)

* Added variables for binom function

* Finished binom function

* Updated .ipynb handling

* James updates

* Expanded definitions

* Expanded description
This commit is contained in:
Colin Delahunty
2021-12-08 16:23:03 -05:00
committed by GitHub
parent 1009ffccbe
commit 0be4c8a2d7
6 changed files with 389 additions and 2 deletions

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ exports/*
htmlcov
gamestonk_terminal/reports/stored/*
gamestonk_terminal/portfolio/portfolios/*
.ipynb_checkpoints/*
# Jupyter Lab Extension build files
jupyterlab/*/*.bundle.*

View File

@@ -75,6 +75,7 @@ class OptionsController:
"unu",
"plot",
"parity",
"binom",
]
CHOICES_MENUS = [
@@ -151,6 +152,7 @@ Current Expiry: {self.selected_date or None}
grhist plot option greek history [Syncretism.io]
plot plot variables provided by the user [Yfinance]
parity shows whether options are above or below expected price [Yfinance]
binom shows the value of an option using binomial options pricing [Yfinance]
> payoff shows payoff diagram for a selection of options [Yfinance]
> pricing shows options pricing and risk neutral valuation [Yfinance]
@@ -1260,6 +1262,79 @@ Current Expiry: {self.selected_date or None}
)
print("")
@try_except
def call_binom(self, other_args: List[str]):
"""Process binom command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="parity",
description="Gives the option value using binomial option valuation",
)
parser.add_argument(
"-s",
"--strike",
type=float,
default=0,
dest="strike",
help="Strike price for option shown",
)
parser.add_argument(
"-p",
"--put",
action="store_true",
default=False,
dest="put",
help="Value a put instead of a call",
)
parser.add_argument(
"-e",
"--european",
action="store_true",
default=False,
dest="europe",
help="Value a European option instead of an American one",
)
parser.add_argument(
"--export",
action="store_true",
default=False,
dest="export",
help="Export an excel spreadsheet with binomial pricing data",
)
parser.add_argument(
"--plot",
action="store_true",
default=False,
dest="plot",
help="Plot expected ending values",
)
parser.add_argument(
"-v",
"--volatility",
type=float,
default=None,
dest="volatility",
help="Underlying asset annualized volatility",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if not self.ticker or not self.selected_date:
print("Ticker and expiration required. \n")
return
yfinance_view.show_binom(
self.ticker,
self.selected_date,
ns_parser.strike,
ns_parser.put,
ns_parser.europe,
ns_parser.export,
ns_parser.plot,
ns_parser.volatility,
)
@try_except
def call_pricing(self, _):
"""Process pricing command"""

View File

@@ -117,9 +117,54 @@ def generate_data(
return x_vals, before, []
def get_price(ticker) -> float:
"""Get current price for a given ticker"""
def get_price(ticker: str) -> float:
"""Get current price for a given ticker
Parameters
----------
ticker : str
The ticker to get the price for
Returns
----------
price : float
The price of the ticker
"""
ticker_yahoo = yf.Ticker(ticker)
data = ticker_yahoo.history()
last_quote = data.tail(1)["Close"].iloc[0]
return last_quote
def get_info(ticker: str):
"""Get info for a given ticker
Parameters
----------
ticker : str
The ticker to get the price for
Returns
----------
price : float
The info for a given ticker
"""
tick = yf.Ticker(ticker)
return tick.info
def get_closing(ticker: str) -> pd.Series:
"""Get closing prices for a given ticker
Parameters
----------
ticker : str
The ticker to get the price for
Returns
----------
price : List[float]
A list of closing prices for a ticker
"""
tick = yf.Ticker(ticker)
return tick.history(period="1y")["Close"]

View File

@@ -2,17 +2,21 @@
__docformat__ = "numpy"
import os
import math
from bisect import bisect_left
from typing import List, Dict, Any
from datetime import datetime, date, timedelta
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as mtick
from scipy.stats import binom
import numpy as np
import pandas as pd
import seaborn as sns
import yfinance as yf
from tabulate import tabulate
from openpyxl import Workbook
import gamestonk_terminal.config_plot as cfp
import gamestonk_terminal.feature_flags as gtff
@@ -662,3 +666,238 @@ def risk_neutral_vals(
else:
print(new_df.to_string(index=False))
print("")
def plot_expected_prices(
und_vals: List[List[float]], p: float, ticker: str, expiration: str
) -> None:
"""Plot expected prices of the underlying asset at expiration
Parameters
----------
und_vals : List[List[float]]
The expected underlying values at the expiration date
p : float
The probability of the stock price moving upward each round
ticker : str
The ticker of the option's underlying asset
expiration : str
The expiration for the option
"""
up_moves = list(range(len(und_vals[-1])))
up_moves.reverse()
probs = [binom.pmf(r, len(up_moves), p) for r in up_moves]
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
ax.set_title(f"Probabilities for ending prices of {ticker} on {expiration}")
ax.xaxis.set_major_formatter("${x:1.2f}")
ax.yaxis.set_major_formatter(mtick.PercentFormatter())
plt.plot(und_vals[-1], probs)
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
plt.show()
def export_binomial_calcs(
up: float,
prob_up: float,
discount: float,
und_vals: List[List[float]],
opt_vals: List[List[float]],
days: int,
ticker: str,
) -> None:
"""Create an excel spreadsheet with binomial tables for underlying asset value and option value
Parameters
----------
up : float
The stock's increase on an upward move
prob_up : float
The probability of an upward move
discount : float
The daily discount rate
und_vals : List[List[float]]
The underlying asset values at each step
opt_vals : List[List[float]]
The values for the option at each step
days : int
The number of days until the option expires
ticker : str
The ticker for the company
"""
letters = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
]
letters += [
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
]
opts = (
[f"{x}" for x in letters]
+ [f"{x}{y}" for x in letters for y in letters]
+ [f"{x}{y}{z}" for x in letters for y in letters for z in letters]
)
wb = Workbook()
ws = wb.active
ws["A1"] = "Up Move"
ws["B1"] = up
ws["A2"] = "Down Move"
ws["B2"] = 1 / up
ws["D1"] = "Prob Up"
ws["E1"] = prob_up
ws["D2"] = "Prob Down"
ws["E2"] = 1 - prob_up
ws["D3"] = "Discount"
ws["E3"] = discount
ws["A4"] = "Binomial Tree for Underlying Values"
for i, _ in enumerate(und_vals):
for j, _ in enumerate(und_vals[i]):
ws[f"{opts[i]}{j+5}"] = und_vals[i][j]
ws[f"A{days+7}"] = "Binomial Tree for Option Values"
for i, _ in enumerate(opt_vals):
for j, _ in enumerate(opt_vals[i]):
ws[f"{opts[i]}{j+8+days}"] = opt_vals[i][j]
trypath = os.path.join(
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")),
"exports",
"stocks",
"options",
f"{ticker} {datetime.now()}.xlsx",
)
wb.save(trypath)
print(f"Analysis ran for {ticker}\nPlease look in {trypath} for the file.\n")
def show_binom(
ticker: str,
expiration: str,
strike: float,
put: bool,
europe: bool,
export: bool,
plot: bool,
vol: float,
) -> None:
"""Get binomial pricing for option
Parameters
----------
ticker : str
The ticker of the option's underlying asset
expiration : str
The expiration for the option
strike : float
The strike price for the option
put : bool
Value a put instead of a call
europe : bool
Value a European option instead of an American option
export : bool
Export the options data to an excel spreadsheet
plot : bool
Show a graph of expected ending prices
vol : float
The annualized volatility for the underlying asset
"""
# Base variables to calculate values
info = yfinance_model.get_info(ticker)
price = info["regularMarketPrice"]
if vol is None:
closings = yfinance_model.get_closing(ticker)
vol = (closings / closings.shift()).std() * (252 ** 0.5)
div_yield = (
info["trailingAnnualDividendYield"]
if info["trailingAnnualDividendYield"] is not None
else 0
)
delta_t = 1 / 252
rf = get_rf()
exp_date = datetime.strptime(expiration, "%Y-%m-%d").date()
today = date.today()
days = (exp_date - today).days
# Binomial pricing specific variables
up = math.exp(vol * (delta_t ** 0.5))
down = 1 / up
prob_up = (math.exp((rf - div_yield) * delta_t) - down) / (up - down)
prob_down = 1 - prob_up
discount = math.exp(delta_t * rf)
und_vals: List[List[float]] = [[price]]
# Binomial tree for underlying values
for i in range(days):
cur_date = today + timedelta(days=i + 1)
if cur_date.weekday() < 5:
last = und_vals[-1]
new = [x * up for x in last]
new.append(last[-1] * down)
und_vals.append(new)
# Binomial tree for option values
if put:
opt_vals = [[max(strike - x, 0) for x in und_vals[-1]]]
else:
opt_vals = [[max(x - strike, 0) for x in und_vals[-1]]]
j = 2
while len(opt_vals[0]) > 1:
new_vals = []
for i in range(len(opt_vals[0]) - 1):
if europe:
value = (
opt_vals[0][i] * prob_up + opt_vals[0][i + 1] * prob_down
) / discount
else:
if put:
value = max(
(opt_vals[0][i] * prob_up + opt_vals[0][i + 1] * prob_down)
/ discount,
strike - und_vals[-j][i],
)
else:
value = max(
(opt_vals[0][i] * prob_up + opt_vals[0][i + 1] * prob_down)
/ discount,
und_vals[-j][i] - strike,
)
new_vals.append(value)
opt_vals.insert(0, new_vals)
j += 1
if export:
export_binomial_calcs(up, prob_up, discount, und_vals, opt_vals, days, ticker)
if plot:
plot_expected_prices(und_vals, prob_up, ticker, expiration)
print(
f"{ticker} {'put' if put else 'call'} at ${strike:.2f} expiring on {expiration} is worth ${opt_vals[0][0]:.2f}\n"
)

View File

@@ -0,0 +1,25 @@
```
usage: binom [-s STRIKE] [-p] [-e] [-E] [-P] [-v VOLATILITY] [-h]
```
Shows the value of an option using binomial option pricing. Can also show raw data and provide a graph with predicted underlying asset ending values. The binomial options model calculates how big an up step or down step in the next time period will likely be. Then it creates a tree doing this at each period. The end results of this is a tree with possible asset values at each "step". For our calculations we use a day as our "step" time period. We then take all of the expected values at the finishing date and use this to begin a tree of option values at each step. The ending results is the value of the option today.
The up step is calculated by taking e to the power of volatility times the square root of the change in time during the step. This is the percentage we expect the stock to increase if there is an upward movement. The down step is the inverse of the up step. The probability of the up step is calculated by taking e to the power of the risk free rate minus the dividend yield. This is multiplied by the change in time for the step and then subtracted by the expected downward movement. This number is then divided by the up step subtracted by then down step. The probability of a downward step is just one minus the probability of an upward step.
Formulas:
up_step = e ^ (volatility * (delta_t ^ (1 / 2)))
down_step = 1 / up_step
prob_up = (e ^ ((risk_free - div_yield) * delta_t) - down_step) / (up_step - down_step)
prob_down = 1 - prob_up
```
optional arguments:
-s STRIKE, --strike STRIKE
strike price for the option (default: 0)
-p, --put value the option as a put (default: False)
-e, --european value the option as a European option (default: False)
--export export the binomial trees (default: False)
-P, --plot plots the expected underlying asset ending prices (default: False)
-v, --volatility sets the volatility for the underlying asset(default: None)
-h, --help show this help message (default: False)
```

View File

@@ -422,6 +422,8 @@ main:
ref: "/portfolio/portfolio_optimization"
- name: parity
ref: "/stocks/options/parity"
- name: binom
ref: "/stocks/options/binom"
- name: payoff
ref: "/stocks/options/payoff"
sub: