mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-28 04:52:07 +00:00
[Feature] Add Deribit Provider Extension For Crypto Options Data (#6985)
* add deribit provider and make some small changes to the standard model to allow for fractional volume and size * linter * typehint * more lint * more touchup * update test * remove line in test * maybe we need that after all..?
This commit is contained in:
@@ -150,10 +150,13 @@ class OptionsChainsData(OptionsChainsProperties):
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
option_type: List[str] = Field(description="Call or Put.")
|
||||
open_interest: List[Union[int, None]] = Field(
|
||||
contract_size: List[Union[int, float, None]] = Field(
|
||||
default_factory=list, description="Number of underlying units per contract."
|
||||
)
|
||||
open_interest: List[Union[int, float, None]] = Field(
|
||||
default_factory=list, description="Open interest on the contract."
|
||||
)
|
||||
volume: List[Union[int, None]] = Field(
|
||||
volume: List[Union[int, float, None]] = Field(
|
||||
default_factory=list, description=DATA_DESCRIPTIONS.get("volume", "")
|
||||
)
|
||||
theoretical_price: List[Union[float, None]] = Field(
|
||||
@@ -166,7 +169,7 @@ class OptionsChainsData(OptionsChainsProperties):
|
||||
description="Last trade price of the option.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
last_trade_size: List[Union[int, None]] = Field(
|
||||
last_trade_size: List[Union[int, float, None]] = Field(
|
||||
default_factory=list, description="Last trade size of the option."
|
||||
)
|
||||
last_trade_time: List[Union[datetime, None]] = Field(
|
||||
@@ -182,7 +185,7 @@ class OptionsChainsData(OptionsChainsProperties):
|
||||
description="Current bid price for the option.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
bid_size: List[Union[int, None]] = Field(
|
||||
bid_size: List[Union[int, float, None]] = Field(
|
||||
default_factory=list, description="Bid size for the option."
|
||||
)
|
||||
bid_time: List[Union[datetime, None]] = Field(
|
||||
@@ -197,7 +200,7 @@ class OptionsChainsData(OptionsChainsProperties):
|
||||
description="Current ask price for the option.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
ask_size: List[Union[int, None]] = Field(
|
||||
ask_size: List[Union[int, float, None]] = Field(
|
||||
default_factory=list, description="Ask size for the option."
|
||||
)
|
||||
ask_time: List[Union[datetime, None]] = Field(
|
||||
@@ -262,7 +265,7 @@ class OptionsChainsData(OptionsChainsProperties):
|
||||
description=DATA_DESCRIPTIONS.get("close", ""),
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
close_size: List[Union[int, None]] = Field(
|
||||
close_size: List[Union[int, float, None]] = Field(
|
||||
default_factory=list,
|
||||
description="The closing trade size for the option that day.",
|
||||
)
|
||||
@@ -275,7 +278,7 @@ class OptionsChainsData(OptionsChainsProperties):
|
||||
description="The closing bid price for the option that day.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
close_bid_size: List[Union[int, None]] = Field(
|
||||
close_bid_size: List[Union[int, float, None]] = Field(
|
||||
default_factory=list,
|
||||
description="The closing bid size for the option that day.",
|
||||
)
|
||||
@@ -287,7 +290,7 @@ class OptionsChainsData(OptionsChainsProperties):
|
||||
default_factory=list,
|
||||
description="The closing ask price for the option that day.",
|
||||
)
|
||||
close_ask_size: List[Union[int, None]] = Field(
|
||||
close_ask_size: List[Union[int, float, None]] = Field(
|
||||
default_factory=list,
|
||||
description="The closing ask size for the option that day.",
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# pylint: disable=too-many-lines, too-many-arguments, too-many-locals, too-many-statements, too-many-positional-arguments
|
||||
|
||||
from datetime import datetime
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Union
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
@@ -41,7 +42,7 @@ class OptionsChainsProperties(Data):
|
||||
if hasattr(self, "_last_price"):
|
||||
del self._last_price
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def dataframe(self) -> "DataFrame":
|
||||
"""Return all data as a Pandas DataFrame,
|
||||
with additional computed columns (Breakeven, GEX, DEX) if available.
|
||||
@@ -72,13 +73,13 @@ class OptionsChainsProperties(Data):
|
||||
raise OpenBBError("Error: No validated data was found.")
|
||||
|
||||
if "dte" not in chains_data.columns and "eod_date" in chains_data.columns:
|
||||
_date = to_datetime(chains_data["eod_date"])
|
||||
_date = to_datetime(chains_data.eod_date)
|
||||
temp = DatetimeIndex(chains_data.expiration)
|
||||
temp_ = temp - _date # type: ignore
|
||||
chains_data.loc[:, "dte"] = [Timedelta(_temp_).days for _temp_ in temp_]
|
||||
|
||||
if "dte" in chains_data.columns:
|
||||
chains_data = DataFrame(chains_data[chains_data["dte"] >= 0])
|
||||
chains_data = DataFrame(chains_data[chains_data.dte >= 0])
|
||||
|
||||
if "dte" not in chains_data.columns and "eod_date" not in chains_data.columns:
|
||||
today = datetime.today().date()
|
||||
@@ -86,51 +87,75 @@ class OptionsChainsProperties(Data):
|
||||
|
||||
# Add the breakeven price for each option, and the DEX and GEX for each option, if available.
|
||||
try:
|
||||
last_price = chains_data.underlying_price.iloc[0]
|
||||
_calls = DataFrame(chains_data[chains_data["option_type"] == "call"])
|
||||
_puts = DataFrame(chains_data[chains_data["option_type"] == "put"])
|
||||
_ask = self._identify_price_col(
|
||||
_calls = DataFrame(chains_data[chains_data.option_type == "call"])
|
||||
_puts = DataFrame(chains_data[chains_data.option_type == "put"])
|
||||
_ask = self._identify_price_col( # pylint: disable=W0212
|
||||
chains_data, "call", "ask"
|
||||
) # pylint: disable=W0212
|
||||
_calls.loc[:, ("Breakeven")] = (
|
||||
_calls.loc[:, ("strike")] + _calls.loc[:, (_ask)]
|
||||
)
|
||||
_puts.loc[:, ("Breakeven")] = (
|
||||
_puts.loc[:, ("strike")] - _puts.loc[:, (_ask)]
|
||||
)
|
||||
|
||||
_calls.loc[:, ("Breakeven")] = _calls.strike + _calls.loc[:, (_ask)]
|
||||
_puts.loc[:, ("Breakeven")] = _puts.strike - _puts.loc[:, (_ask)]
|
||||
if "delta" in _calls.columns:
|
||||
_calls.loc[:, ("DEX")] = (
|
||||
(_calls.loc[:, ("delta")] * 100)
|
||||
* (_calls.loc[:, ("open_interest")])
|
||||
* last_price
|
||||
(
|
||||
_calls.delta
|
||||
* (
|
||||
_calls.contract_size
|
||||
if hasattr(_calls, "contract_size")
|
||||
else 100
|
||||
)
|
||||
* _calls.open_interest
|
||||
* _calls.underlying_price
|
||||
)
|
||||
.replace({nan: 0})
|
||||
.astype("int64")
|
||||
)
|
||||
_calls["DEX"] = _calls["DEX"].replace({nan: 0}).astype("int64")
|
||||
_puts.loc[:, ("DEX")] = (
|
||||
(_puts.loc[:, ("delta")] * 100)
|
||||
* (_puts.loc[:, ("open_interest")])
|
||||
* last_price
|
||||
(
|
||||
_puts.delta
|
||||
* (
|
||||
_puts.contract_size
|
||||
if hasattr(_puts, "contract_size")
|
||||
else 100
|
||||
)
|
||||
* _puts.open_interest
|
||||
* _puts.underlying_price
|
||||
)
|
||||
.replace({nan: 0})
|
||||
.astype("int64")
|
||||
)
|
||||
_puts["DEX"] = _puts["DEX"].replace({nan: 0}).astype("int64")
|
||||
|
||||
if "gamma" in _calls.columns:
|
||||
_calls.loc[:, ("GEX")] = (
|
||||
_calls.loc[:, ("gamma")]
|
||||
* 100
|
||||
* _calls.loc[:, ("open_interest")]
|
||||
* (last_price * last_price)
|
||||
* 0.01
|
||||
(
|
||||
_calls.gamma
|
||||
* (
|
||||
_calls.contract_size
|
||||
if hasattr(_calls, "contract_size")
|
||||
else 100
|
||||
)
|
||||
* _calls.open_interest
|
||||
* (_calls.underlying_price * _calls.underlying_price)
|
||||
* 0.01
|
||||
)
|
||||
.replace({nan: 0})
|
||||
.astype("int64")
|
||||
)
|
||||
_calls["GEX"] = _calls["GEX"].replace({nan: 0}).astype("int64")
|
||||
_puts.loc[:, ("GEX")] = (
|
||||
_puts.loc[:, ("gamma")]
|
||||
* 100
|
||||
* _puts.loc[:, ("open_interest")]
|
||||
* (last_price * last_price)
|
||||
* 0.01
|
||||
* (-1)
|
||||
(
|
||||
_puts.gamma
|
||||
* (
|
||||
_puts.contract_size
|
||||
if hasattr(_puts, "contract_size")
|
||||
else 100
|
||||
)
|
||||
* _puts.open_interest
|
||||
* (_puts.underlying_price * _puts.underlying_price)
|
||||
* 0.01
|
||||
* (-1)
|
||||
)
|
||||
.replace({nan: 0})
|
||||
.astype("int64")
|
||||
)
|
||||
_puts["GEX"] = _puts["GEX"].replace({nan: 0}).astype("int64")
|
||||
|
||||
_calls.set_index(keys=["expiration", "strike", "option_type"], inplace=True)
|
||||
_puts.set_index(keys=["expiration", "strike", "option_type"], inplace=True)
|
||||
@@ -371,17 +396,7 @@ class OptionsChainsProperties(Data):
|
||||
from numpy import inf, nan
|
||||
from pandas import DataFrame, concat
|
||||
|
||||
df = DataFrame()
|
||||
|
||||
if metric in ["volume", "open_interest"]:
|
||||
df = DataFrame(
|
||||
self.model_dump(
|
||||
exclude_unset=True,
|
||||
exclude_none=True,
|
||||
)
|
||||
)
|
||||
else:
|
||||
df = self.dataframe
|
||||
df = self.dataframe
|
||||
|
||||
if metric in ["DEX", "GEX"]:
|
||||
if not self.has_greeks:
|
||||
@@ -395,7 +410,7 @@ class OptionsChainsProperties(Data):
|
||||
"Calls": total_calls,
|
||||
"Puts": total_puts,
|
||||
"Total": total_metric,
|
||||
"PCR": round(total_puts / total_calls, 4),
|
||||
"PCR": round(total_puts / total_calls, 4) if total_calls != 0 else 0,
|
||||
}
|
||||
|
||||
df = DataFrame(df[df[metric].notnull()]) # type: ignore
|
||||
@@ -542,6 +557,11 @@ class OptionsChainsProperties(Data):
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
|
||||
if date is not None:
|
||||
date = self._get_nearest_expiration(date)
|
||||
df = df[df.expiration.astype(str) == date]
|
||||
strikes = Series(df.strike.unique().tolist())
|
||||
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
@@ -549,11 +569,6 @@ class OptionsChainsProperties(Data):
|
||||
)
|
||||
strikes = Series(self.strikes)
|
||||
|
||||
if date is not None:
|
||||
date = self._get_nearest_expiration(date)
|
||||
df = df[df.expiration.astype(str) == date]
|
||||
strikes = Series(df.strike.unique().tolist())
|
||||
|
||||
upper = last_price * (1 + moneyness) # type: ignore
|
||||
lower = last_price * (1 - moneyness) # type: ignore
|
||||
nearest_call = (upper - strikes).abs().idxmin()
|
||||
@@ -606,15 +621,14 @@ class OptionsChainsProperties(Data):
|
||||
if days is None:
|
||||
days = 30
|
||||
|
||||
if strike is None:
|
||||
strike = chains.underlying_price.iloc[0]
|
||||
|
||||
dte_estimate = self._get_nearest_expiration(days)
|
||||
df = (
|
||||
chains[chains.expiration.astype(str) == dte_estimate]
|
||||
.query("`option_type` == @option_type")
|
||||
.copy()
|
||||
)
|
||||
if strike is None:
|
||||
strike = df.underlying_price.iloc[0]
|
||||
|
||||
if price_col is not None:
|
||||
df = df[df[price_col].notnull()] # type: ignore
|
||||
@@ -676,16 +690,6 @@ class OptionsChainsProperties(Data):
|
||||
|
||||
chains = self.dataframe
|
||||
|
||||
if not hasattr(chains, "underlying_price") and underlying_price is None:
|
||||
raise OpenBBError(
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
underlying_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
if days is None:
|
||||
days = 30
|
||||
|
||||
@@ -696,6 +700,16 @@ class OptionsChainsProperties(Data):
|
||||
|
||||
chains = chains[chains.expiration.astype(str) == dte_estimate]
|
||||
|
||||
if not hasattr(chains, "underlying_price") and underlying_price is None:
|
||||
raise OpenBBError(
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
underlying_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
force_otm = True
|
||||
|
||||
if strike is None and not hasattr(chains, "underlying_price"):
|
||||
@@ -843,6 +857,12 @@ class OptionsChainsProperties(Data):
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
|
||||
underlying_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
strikes = self._get_nearest_otm_strikes(
|
||||
dte_estimate, underlying_price, moneyness
|
||||
)
|
||||
@@ -968,11 +988,6 @@ class OptionsChainsProperties(Data):
|
||||
raise OpenBBError(
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
if days is None:
|
||||
days = 30
|
||||
@@ -986,6 +1001,12 @@ class OptionsChainsProperties(Data):
|
||||
"`option_type` == 'call'"
|
||||
)
|
||||
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
if bought is None:
|
||||
bought = last_price * 1.0250
|
||||
|
||||
@@ -1103,11 +1124,6 @@ class OptionsChainsProperties(Data):
|
||||
raise OpenBBError(
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
if days is None:
|
||||
days = 30
|
||||
@@ -1121,6 +1137,12 @@ class OptionsChainsProperties(Data):
|
||||
"`option_type` == 'put'"
|
||||
)
|
||||
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
if bought is None:
|
||||
bought = last_price * 0.9750
|
||||
|
||||
@@ -1226,11 +1248,6 @@ class OptionsChainsProperties(Data):
|
||||
raise OpenBBError(
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
if days is None:
|
||||
days = 30
|
||||
@@ -1240,6 +1257,11 @@ class OptionsChainsProperties(Data):
|
||||
|
||||
dte_estimate = self._get_nearest_expiration(days)
|
||||
chains = DataFrame(chains[chains["expiration"].astype(str) == dte_estimate])
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
bid = self._identify_price_col(chains, "put", "bid")
|
||||
ask = self._identify_price_col(chains, "call", "ask")
|
||||
strike_price = last_price if strike == 0 else strike
|
||||
@@ -1329,11 +1351,6 @@ class OptionsChainsProperties(Data):
|
||||
raise OpenBBError(
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
if days is None:
|
||||
days = 30
|
||||
@@ -1343,6 +1360,11 @@ class OptionsChainsProperties(Data):
|
||||
|
||||
dte_estimate = self._get_nearest_expiration(days)
|
||||
chains = DataFrame(chains[chains["expiration"].astype(str) == dte_estimate])
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else chains.underlying_price.iloc[0]
|
||||
)
|
||||
bid = self._identify_price_col(chains, "call", "bid")
|
||||
ask = self._identify_price_col(chains, "put", "ask")
|
||||
strike_price = last_price if strike == 0 else strike
|
||||
@@ -1667,11 +1689,6 @@ class OptionsChainsProperties(Data):
|
||||
raise OpenBBError(
|
||||
"Error: underlying_price must be provided if underlying_price is not available"
|
||||
)
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else data.underlying_price.iloc[0]
|
||||
)
|
||||
|
||||
if moneyness is not None and date is None:
|
||||
date = -1
|
||||
@@ -1703,13 +1720,12 @@ class OptionsChainsProperties(Data):
|
||||
if moneyness is not None:
|
||||
atm_call_iv = DataFrame()
|
||||
atm_put_iv = DataFrame()
|
||||
|
||||
for day in days:
|
||||
strikes = self._get_nearest_otm_strikes(
|
||||
date=day, moneyness=moneyness, underlying_price=underlying_price
|
||||
)
|
||||
atm_call_strike = self._get_nearest_strike( # noqa:F841
|
||||
"call", day, last_price, call_price_col, False
|
||||
"call", day, underlying_price, call_price_col, False
|
||||
)
|
||||
call_strike = self._get_nearest_strike( # noqa:F841
|
||||
"call", day, strikes["call"], call_price_col, False
|
||||
@@ -1719,6 +1735,11 @@ class OptionsChainsProperties(Data):
|
||||
.query("`option_type` == 'call'") # type: ignore
|
||||
.copy()
|
||||
)
|
||||
last_price = (
|
||||
underlying_price
|
||||
if underlying_price is not None
|
||||
else _calls.underlying_price.iloc[0]
|
||||
)
|
||||
if len(_calls) > 0:
|
||||
call_iv = _calls[_calls.strike == call_strike][
|
||||
["expiration", "strike", "implied_volatility"]
|
||||
|
||||
Reference in New Issue
Block a user