From 1dffba8ee4c6c97674893996081db5a7ae1b48d1 Mon Sep 17 00:00:00 2001 From: Colin Delahunty <72827203+colin99d@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:12:19 -0400 Subject: [PATCH] Adds Error Handling to the options class (#4516) * Step one: error handle Option class * Step one: error handle Option class * Handle unfindable --- openbb_terminal/stocks/options/op_helpers.py | 60 ++++++++++++------- .../stocks/options/options_controller.py | 6 +- .../stocks/options/options_sdk_helper.py | 33 +++++----- .../stocks/options/test_op_helpers.py | 37 ++++++++++++ 4 files changed, 97 insertions(+), 39 deletions(-) create mode 100644 tests/openbb_terminal/stocks/options/test_op_helpers.py diff --git a/openbb_terminal/stocks/options/op_helpers.py b/openbb_terminal/stocks/options/op_helpers.py index 784eae3affe8..58c4182eefc5 100644 --- a/openbb_terminal/stocks/options/op_helpers.py +++ b/openbb_terminal/stocks/options/op_helpers.py @@ -270,31 +270,37 @@ def get_greeks( strikes = [] for _, row in chain.iterrows(): vol = row["impliedVolatility"] - opt_type = 1 if row["optionType"] == "call" else -1 - opt = Option( - current_price, row["strike"], risk_free, div_cont, dif, vol, opt_type - ) - tmp = [ - opt.Delta(), - opt.Gamma(), - opt.Vega(), - opt.Theta(), - ] + is_call = row["optionType"] == "call" result = ( [row[col] for col in row.index.tolist()] if show_all else [row[col] for col in ["strike", "impliedVolatility"]] ) - result += tmp - - if show_extra_greeks: - result += [ - opt.Rho(), - opt.Phi(), - opt.Charm(), - opt.Vanna(0.01), - opt.Vomma(0.01), + try: + opt = Option( + current_price, row["strike"], risk_free, div_cont, dif, vol, is_call + ) + tmp = [ + opt.Delta(), + opt.Gamma(), + opt.Vega(), + opt.Theta(), ] + result += tmp + + if show_extra_greeks: + result += [ + opt.Rho(), + opt.Phi(), + opt.Charm(), + opt.Vanna(0.01), + opt.Vomma(0.01), + ] + except ValueError: + result += [np.nan] * 4 + + if show_extra_greeks: + result += [np.nan] * 5 strikes.append(result) greek_columns = [ @@ -393,7 +399,7 @@ def __init__( div_cont: float, expiry: float, vol: float, - opt_type: int = 1, + is_call: bool = True, ): """ Class for getting the greeks of options. Inspiration from: @@ -413,10 +419,18 @@ def __init__( The number of days until expiration vol : float The underlying volatility for an option - opt_type : int - put == -1; call == +1 + is_call : bool + True if call, False if put """ - self.Type = int(opt_type) + if expiry <= 0: + raise ValueError("Expiry must be greater than 0") + if vol <= 0: + raise ValueError("Volatility must be greater than 0") + if s <= 0: + raise ValueError("Price must be greater than 0") + if k <= 0: + raise ValueError("Strike must be greater than 0") + self.Type = 1 if is_call else -1 self.price = float(s) self.strike = float(k) self.risk_free = float(rf) diff --git a/openbb_terminal/stocks/options/options_controller.py b/openbb_terminal/stocks/options/options_controller.py index 345d86beb982..2815abb540bb 100644 --- a/openbb_terminal/stocks/options/options_controller.py +++ b/openbb_terminal/stocks/options/options_controller.py @@ -217,7 +217,11 @@ def set_current_price(self): self.current_price = yfinance_model.get_last_price(self.ticker) def set_expiry_dates(self): - self.expiry_dates = self.full_chain.expiration.unique().tolist() or [] + if self.full_chain.empty: + self.expiry_dates = [] + console.print("[red]The ticker provided could not be found[/red]\n") + else: + self.expiry_dates = self.full_chain.expiration.unique().tolist() or [] def update_runtime_choices(self): """Update runtime choices""" diff --git a/openbb_terminal/stocks/options/options_sdk_helper.py b/openbb_terminal/stocks/options/options_sdk_helper.py index 7744b31e2e5d..c0cedb0e932b 100644 --- a/openbb_terminal/stocks/options/options_sdk_helper.py +++ b/openbb_terminal/stocks/options/options_sdk_helper.py @@ -296,21 +296,24 @@ def get_greeks( strikes = [] for _, row in chain.iterrows(): vol = row["impliedVolatility"] - opt_type = 1 if row["optionType"] == "call" else -1 - opt = Option( - current_price, row["strike"], risk_free, div_cont, dif, vol, opt_type - ) - tmp = [ - opt.Delta(), - opt.Gamma(), - opt.Vega(), - opt.Theta(), - opt.Rho(), - opt.Phi(), - opt.Charm(), - opt.Vanna(0.01), - opt.Vomma(0.01), - ] + is_call = row["optionType"] == "call" + try: + opt = Option( + current_price, row["strike"], risk_free, div_cont, dif, vol, is_call + ) + tmp = [ + opt.Delta(), + opt.Gamma(), + opt.Vega(), + opt.Theta(), + opt.Rho(), + opt.Phi(), + opt.Charm(), + opt.Vanna(0.01), + opt.Vomma(0.01), + ] + except ValueError: + tmp = [np.nan] * 9 result = [row[col] for col in row.index.tolist()] result += tmp diff --git a/tests/openbb_terminal/stocks/options/test_op_helpers.py b/tests/openbb_terminal/stocks/options/test_op_helpers.py new file mode 100644 index 000000000000..5facb92b9dd6 --- /dev/null +++ b/tests/openbb_terminal/stocks/options/test_op_helpers.py @@ -0,0 +1,37 @@ +import pytest + +from openbb_terminal.stocks.options.op_helpers import Option + + +@pytest.mark.parametrize("s", [0, 1]) +@pytest.mark.parametrize("k", [0, 1]) +@pytest.mark.parametrize("rf", [-1, 0, 0.05]) +@pytest.mark.parametrize("div_cont", [0, 0.05]) +@pytest.mark.parametrize("expiry", [0, 0.05]) +@pytest.mark.parametrize("vol", [0, 0.05]) +@pytest.mark.parametrize("is_call", [True, False]) +def test_option_class(s, k, rf, div_cont, expiry, vol, is_call): + if expiry <= 0 or vol <= 0 or s <= 0 or k <= 0: + with pytest.raises(ValueError): + opt = Option( + s=s, + k=k, + rf=rf, + div_cont=div_cont, + expiry=expiry, + vol=vol, + is_call=is_call, + ) + else: + opt = Option( + s=s, k=k, rf=rf, div_cont=div_cont, expiry=expiry, vol=vol, is_call=is_call + ) + opt.Delta() + opt.Gamma() + opt.Vega() + opt.Theta() + opt.Rho() + opt.Phi() + opt.Charm() + opt.Vanna(0.01) + opt.Vomma(0.01)