diff --git a/gamestonk_terminal/options/chains_view.py b/gamestonk_terminal/options/chains_view.py index b87b57460b6..8a1afdefdd7 100644 --- a/gamestonk_terminal/options/chains_view.py +++ b/gamestonk_terminal/options/chains_view.py @@ -56,7 +56,7 @@ def process_chains(response: requests.models.Response) -> pd.DataFrame: Parameters ---------- response: requests.models.Response - This is the respnose from tradier api. + This is the response from tradier api. Returns ------- diff --git a/gamestonk_terminal/options/volume_helper.py b/gamestonk_terminal/options/volume_helper.py new file mode 100644 index 00000000000..4a0f40d03c5 --- /dev/null +++ b/gamestonk_terminal/options/volume_helper.py @@ -0,0 +1,63 @@ +"""Functions for analyzing options data""" +__docformat__ = "numpy" +from typing import Union +import pandas as pd +import numpy as np + + +def get_loss_at_strike( + strike: Union[int, float], chain: pd.DataFrame +) -> Union[int, float]: + """ + Function to get the loss at the given expiry + Parameters + ---------- + strike: Union[int,float] + Value to calculate total loss at + chain: Dataframe: + Dataframe containing at least strike and openInterest + + Returns + ------- + loss: Union[float,int] + Total loss + """ + + itm_calls = chain[chain.index < strike][["OI_call"]] + itm_calls["loss"] = (strike - itm_calls.index) * itm_calls["OI_call"] + call_loss = itm_calls["loss"].sum() + # The *-1 below is due to a sign change for plotting in the _view code + itm_puts = chain[chain.index > strike][["OI_put"]] + itm_puts["loss"] = (itm_puts.index - strike) * itm_puts["OI_put"] * -1 + put_loss = itm_puts.loss.sum() + loss = call_loss + put_loss + return loss + + +def get_max_pain(chain: pd.DataFrame) -> Union[int, float]: + """ + Returns the max pain for a given call/put dataframe + Parameters + ---------- + chain: DataFrame + Dataframe to calculate value from + + Returns + ------- + max_pain : + Max pain value + """ + + strikes = np.array(chain.index) + if ("OI_call" not in chain.columns) or ("OI_put" not in chain.columns): + print("Incorrect columns. Unable to parse max pain") + return np.nan + + loss = [] + for price_at_exp in strikes: + loss.append(get_loss_at_strike(price_at_exp, chain)) + + chain["loss"] = loss + max_pain = chain["loss"].idxmin() + + return max_pain diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py index 4d32126c682..9558690ff3c 100644 --- a/gamestonk_terminal/options/volume_view.py +++ b/gamestonk_terminal/options/volume_view.py @@ -16,6 +16,7 @@ from gamestonk_terminal.helper_funcs import ( ) from gamestonk_terminal import config_plot as cfgPlot from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.options.volume_helper import get_max_pain def plot_volume_open_interest( @@ -250,9 +251,6 @@ def get_calls_puts_maxpain( df_calls["volume"] = df_calls["volume"] df_calls["oi+v"] = df_calls["openInterest"] + df_calls["volume"] df_calls["spot"] = round(last_adj_close_price, 2) - df_calls["dv"] = last_adj_close_price - df_calls.index - df_calls["dv"] = df_calls["dv"].apply(lambda x: max(0, x)) - df_calls["dv"] = abs(df_calls["dv"] * df_calls["volume"]) if not op_puts.empty: # Process Puts Data @@ -266,15 +264,14 @@ def get_calls_puts_maxpain( df_puts["openInterest"] = -df_puts["openInterest"] df_puts["oi+v"] = df_puts["openInterest"] + df_puts["volume"] df_puts["spot"] = round(last_adj_close_price, 2) - df_puts["dv"] = df_puts.index - last_adj_close_price - df_puts["dv"] = df_puts["dv"].apply(lambda x: max(0, x)) - df_puts["dv"] = abs(df_puts["dv"] * df_puts["volume"]) if not op_calls.empty and not op_puts.empty: # Get max pain df_opt = pd.merge(df_calls, df_puts, left_index=True, right_index=True) - df_opt["dv"] = round(df_opt["dv_x"] + df_opt["dv_y"], 2) - max_pain = df_opt["dv"].idxmax() + df_opt = df_opt[["openInterest_x", "openInterest_y"]].rename( + columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"} + ) + max_pain = get_max_pain(df_opt) return df_calls, df_puts, max_pain