[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:
Danglewood
2024-12-30 15:57:57 -08:00
committed by GitHub
parent 8fd46ed450
commit efd09ce75b
20 changed files with 2831 additions and 106 deletions

View File

@@ -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.",
)

View File

@@ -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"]

View File

@@ -55,6 +55,7 @@ openbb-regulators = { path = "./extensions/regulators", develop = true }
openbb-alpha-vantage = { path = "./providers/alpha_vantage", optional = true, develop = true }
openbb-biztoc = { path = "./providers/biztoc", optional = true, develop = true }
openbb-cboe = { path = "./providers/cboe", optional = true, develop = true }
openbb-deribit = { path = "./providers/deribit", optional = true, develop = true }
openbb-ecb = { path = "./providers/ecb", optional = true, develop = true }
openbb-finra = { path = "./providers/finra", optional = true, develop = true }
openbb-finviz = { path = "./providers/finviz", optional = true, develop = true }

View File

@@ -46,6 +46,7 @@ def headers():
({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
({"provider": "tradier", "symbol": "AAPL"}),
({"provider": "yfinance", "symbol": "AAPL"}),
({"provider": "deribit", "symbol": "BTC"}),
(
{
"provider": "tmx",

View File

@@ -42,6 +42,7 @@ def obb(pytestconfig):
({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
({"provider": "tradier", "symbol": "AAPL"}),
({"provider": "yfinance", "symbol": "AAPL"}),
({"provider": "deribit", "symbol": "BTC"}),
(
{
"provider": "tmx",

29
openbb_platform/poetry.lock generated vendored
View File

@@ -2192,6 +2192,23 @@ files = [
[package.dependencies]
openbb-core = ">=1.3.6,<2.0.0"
[[package]]
name = "openbb-deribit"
version = "1.0.0b"
description = "Deribit is a cryptocurrency exchange that offers futures and options trading."
optional = true
python-versions = "^3.9"
files = []
develop = false
[package.dependencies]
async-lru = "^2.0.4"
openbb-core = "^1.3.7"
[package.source]
type = "directory"
url = "providers/deribit"
[[package]]
name = "openbb-derivatives"
version = "1.3.5"
@@ -2991,13 +3008,13 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "poetry"
version = "1.8.4"
version = "1.8.5"
description = "Python dependency management and packaging made easy."
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "poetry-1.8.4-py3-none-any.whl", hash = "sha256:1223bb6dfdbdfbebc6790796b9b7a88ea1f1f4679e709594f698499010ffb129"},
{file = "poetry-1.8.4.tar.gz", hash = "sha256:5490f8da66d17eecd660e091281f8aaa5554381644540291817c249872c99202"},
{file = "poetry-1.8.5-py3-none-any.whl", hash = "sha256:5505fba69bf2a792b5d7402d21839c853644337392b745109b86a23010cce5f3"},
{file = "poetry-1.8.5.tar.gz", hash = "sha256:eb2c88d224f58f36df8f7b36d6c380c07d1001bca28bde620f68fc086e881b70"},
]
[package.dependencies]
@@ -3012,7 +3029,7 @@ installer = ">=0.7.0,<0.8.0"
keyring = ">=24.0.0,<25.0.0"
packaging = ">=23.1"
pexpect = ">=4.7.0,<5.0.0"
pkginfo = ">=1.10,<2.0"
pkginfo = ">=1.12,<2.0"
platformdirs = ">=3.0.0,<5"
poetry-core = "1.9.1"
poetry-plugin-export = ">=1.6.0,<2.0.0"
@@ -4741,7 +4758,7 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools",
type = ["pytest-mypy"]
[extras]
all = ["openbb-alpha-vantage", "openbb-biztoc", "openbb-cboe", "openbb-charting", "openbb-ecb", "openbb-econometrics", "openbb-finra", "openbb-finviz", "openbb-government-us", "openbb-multpl", "openbb-nasdaq", "openbb-quantitative", "openbb-seeking-alpha", "openbb-stockgrid", "openbb-technical", "openbb-tmx", "openbb-tradier", "openbb-wsj"]
all = ["openbb-alpha-vantage", "openbb-biztoc", "openbb-cboe", "openbb-charting", "openbb-deribit", "openbb-ecb", "openbb-econometrics", "openbb-finra", "openbb-finviz", "openbb-government-us", "openbb-multpl", "openbb-nasdaq", "openbb-quantitative", "openbb-seeking-alpha", "openbb-stockgrid", "openbb-technical", "openbb-tmx", "openbb-tradier", "openbb-wsj"]
alpha-vantage = ["openbb-alpha-vantage"]
biztoc = ["openbb-biztoc"]
cboe = ["openbb-cboe"]
@@ -4764,4 +4781,4 @@ wsj = ["openbb-wsj"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.13"
content-hash = "a2901737d966bcf33f21094863132898f6ab2b3469e371bdd753f5651ff5a3cb"
content-hash = "814de57cabbeaba8d3614703e3ac73d124aded9ff34970c06957fad994c6397f"

View File

@@ -0,0 +1,12 @@
# OpenBB Deribit Provider Extension
## Coverage
- obb.derivatives.options.chains
- Support for symbols:
- BTC
- ETH
- XRP
- SOL
- BNB
- PAXG

View File

@@ -0,0 +1 @@
"""OpenBB Deribit Provider Module."""

View File

@@ -0,0 +1,15 @@
"""OpenBB Deribit Provider Module."""
from openbb_core.provider.abstract.provider import Provider
from openbb_deribit.models.options_chains import DeribitOptionsChainsFetcher
deribit_provider = Provider(
name="deribit",
website="https://deribit.com/",
description="""Unofficial Python client for public data published by Deribit.""",
credentials=None,
fetcher_dict={"OptionsChains": DeribitOptionsChainsFetcher},
repr_name="Deribit Public Data",
instructions="This provider does not require any credentials and is not meant for trading.",
)

View File

@@ -0,0 +1 @@
"""Deribit Data Models."""

View File

@@ -0,0 +1,279 @@
"""Deribit Options Chains Model."""
# pylint: disable=unused-argument
from datetime import datetime
from typing import Any, Optional, Union
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.options_chains import (
OptionsChainsData,
OptionsChainsQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_deribit.utils.helpers import DERIBIT_OPTIONS_SYMBOLS
from pydantic import Field, field_validator
class DeribitOptionsChainsQueryParams(OptionsChainsQueryParams):
"""Deribit Options Chains Query Parameters Model."""
__json_schema_extra__ = {
"symbol": {
"multiple_items_allowed": False,
"choices": DERIBIT_OPTIONS_SYMBOLS,
}
}
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def _validate_symbol(cls, v):
"""Validate the symbol."""
if v.upper() not in DERIBIT_OPTIONS_SYMBOLS:
raise ValueError(
f"Invalid Deribit symbol. Supported symbols are: {', '.join(DERIBIT_OPTIONS_SYMBOLS)}",
)
return v
class DeribitOptionsChainsData(OptionsChainsData):
"""Deribit Options Chains Data Model."""
__alias_dict__ = {
"contract_symbol": "instrument_name",
"change_percent": "price_change",
"underlying_symbol": "underlying_index",
"underlying_spot_price": "index_price",
"bid_size": "best_bid_amount",
"ask_size": "best_ask_amount",
"bid": "best_bid_price",
"ask": "best_ask_price",
"implied_volatility": "mark_iv",
"mark": "mark_price",
"last_trade_price": "last_price",
"volume_notional": "volume_usd",
}
__doc__ = OptionsChainsData.__doc__
bid_iv: list[Union[float, None]] = Field(
default_factory=list,
description="The implied volatility of the bid price.",
json_schema_extra={"x-unit_measurement": "decimal"},
)
ask_iv: list[Union[float, None]] = Field(
default_factory=list,
description="The implied volatility of the ask price.",
json_schema_extra={"x-unit_measurement": "decimal"},
)
interest_rate: list[Union[float, None]] = Field(
default_factory=list,
description="The interest rate used by Deribit to calculate greeks.",
)
underlying_spot_price: list[float] = Field(
description="The spot price of the underlying asset."
" The underlying asset is the specific future or index that the option is based on.",
json_schema_extra={"x-unit_measurement": "currency"},
)
settlement_price: list[Union[float, None]] = Field(
default_factory=list,
description="The settlement price of the contract.",
json_schema_extra={"x-unit_measurement": "currency"},
)
min_price: list[Union[float, None]] = Field(
default_factory=list,
description="The minimum price allowed.",
json_schema_extra={"x-unit_measurement": "currency"},
)
max_price: list[Union[float, None]] = Field(
default_factory=list,
description="The maximum price allowed.",
json_schema_extra={"x-unit_measurement": "currency"},
)
volume_notional: list[Union[float, None]] = Field(
default_factory=list,
description="The notional trading volume of the contract, as USD or USDC.",
json_schema_extra={"x-unit_measurement": "currency"},
)
timestamp: list[datetime] = Field(
description="The datetime of the data, as America/New_York time.",
)
class DeribitOptionsChainsFetcher(
Fetcher[DeribitOptionsChainsQueryParams, DeribitOptionsChainsData]
):
"""Deribit Options Chains Fetcher."""
require_credentials = False
@staticmethod
def transform_query(params: dict[str, Any]) -> DeribitOptionsChainsQueryParams:
"""Transform the query parameters."""
return DeribitOptionsChainsQueryParams(**params)
@staticmethod
async def aextract_data(
query: DeribitOptionsChainsQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> list[dict]:
"""Extract the data."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
import json
import websockets
from openbb_deribit.utils.helpers import get_options_symbols
from pandas import to_datetime
from warnings import warn
# We need to identify each option contract in order to fetch the chains data.
symbols_dict: dict[str, str] = {}
try:
symbols_dict = await get_options_symbols(query.symbol)
except OpenBBError as e:
raise OpenBBError(e) from e
# For each expiration, we need to create a websocket connection to fetch the data.
# We subscribe to each contract symbol and break the connection when we have all the data for an expiry.
# If it takes too long, we break the connection and return an error message.
results: list = []
messages: set = set()
async def call_api(expiration):
"""Call the Deribit API."""
symbols = symbols_dict[expiration]
received_symbols: set = set()
msg = {
"jsonrpc": "2.0",
"id": 3600,
"method": "public/subscribe",
"params": {"channels": ["ticker." + d + ".100ms" for d in symbols]},
}
async with websockets.connect(
"wss://www.deribit.com/ws/api/v2"
) as websocket:
await websocket.send(json.dumps(msg))
try:
await asyncio.wait_for(
receive_data(websocket, symbols, received_symbols), timeout=2.0
)
except asyncio.TimeoutError:
messages.add(f"Timeout reached for {expiration}, data incomplete.")
async def receive_data(websocket, symbols, received_symbols):
"""Receive the data from the websocket with a timeout."""
while websocket.open:
response = await websocket.recv()
data = json.loads(response)
if "params" not in data:
continue
if "error" in data and data.get("error"):
messages.add(f"Error while receiving data -> {data['error']}")
break
res = data.get("params", {}).get("data", {})
symbol = res.get("instrument_name")
# While we are handling the data, we will parse the message.
if symbol not in received_symbols:
received_symbols.add(symbol)
stats = res.pop("stats", {})
greeks = res.pop("greeks", {})
timestamp = res.pop("timestamp", None)
underlying_symbol = res.get("underlying_index")
if underlying_symbol == "index_price":
res["underlying_index"] = symbol.split("-")[0].replace("_", "-")
res["timestamp"] = to_datetime(
timestamp, unit="ms", utc=True
).tz_convert("America/New_York")
if res.get("estimated_delivery_price") == res.get("index_price"):
_ = res.pop("estimated_delivery_price", None)
_ = res.pop("state", None)
result = {
"expiration": to_datetime(symbol.split("-")[1]).date(),
"strike": (
float(symbol.split("-")[2].replace("d", "."))
if "d" in symbol.split("-")[2]
else int(symbol.split("-")[2])
),
"option_type": (
"call"
if symbol.endswith("-C")
else "put" if symbol.endswith("-P") else None
),
**res,
**stats,
**greeks,
}
result["dte"] = (
result["expiration"] - to_datetime("today").date()
).days
results.append(result)
if len(received_symbols) == len(symbols):
await websocket.close()
break
tasks = [
asyncio.create_task(call_api(expiration)) for expiration in symbols_dict
]
await asyncio.gather(*tasks)
if messages and not results:
raise OpenBBError(", ".join(messages))
if results and messages:
for message in messages:
warn(message)
if not results and not messages:
raise EmptyDataError("All requests returned empty with no error messages.")
return results
@staticmethod
def transform_data(
query: DeribitOptionsChainsQueryParams,
data: list[dict],
**kwargs: Any,
) -> DeribitOptionsChainsData:
"""Transform the data."""
# pylint: disable=import-outside-toplevel
from numpy import nan
from pandas import DataFrame
df = DataFrame(data)
# For BTC and ETH options, we need to convert price units to USD.
for col in df.columns:
if col in [
"last_price",
"settlement_price",
"mark_price",
"min_price",
"max_price",
"best_ask_price",
"best_bid_price",
"high",
"low",
] and query.symbol.upper() in ["BTC", "ETH"]:
df.loc[:, col] = df[col].astype(float).multiply(df.index_price).round(2)
elif col in ["price_change", "mark_iv", "bid_iv", "ask_iv"]:
df.loc[:, col] = df[col].astype(float).divide(100)
df = df.replace({nan: None})
df = df.sort_values(["expiration", "strike", "option_type"])
df = df.reset_index(drop=True)
df.loc[:, "contract_size"] = 1
results = df.to_dict(orient="list")
return DeribitOptionsChainsData.model_validate(results)

View File

@@ -0,0 +1 @@
"""Deribit Utility Functions."""

View File

@@ -0,0 +1,111 @@
"""Deribit Helpers Module."""
from typing import Literal, Optional
from async_lru import alru_cache
from openbb_core.app.model.abstract.error import OpenBBError
DERIBIT_OPTIONS_SYMBOLS = ["BTC", "ETH", "SOL", "XRP", "BNB", "PAXG"]
OptionsSymbols = Literal["BTC", "ETH", "SOL", "XRP", "BNB", "PAXG"]
CURRENCIES = ["BTC", "ETH", "USDC", "USDT", "EURR", "all"]
Currencies = Literal["BTC", "ETH", "USDC", "USDT", "EURR", "all"]
DERIVATIVE_TYPES = ["future", "option", "spot", "future_combo", "option_combo"]
DerivativeTypes = Literal["future", "option", "spot", "future_combo", "option_combo"]
BASE_URL = "https://www.deribit.com"
@alru_cache(maxsize=64)
async def get_instruments(
currency: Currencies = "BTC",
derivative_type: Optional[DerivativeTypes] = None,
) -> list[dict]:
"""
Get Deribit instruments.
Parameters
----------
currency : Currencies
The currency to get instruments for. Default is "BTC".
derivative_type : Optional[DerivativeTypes]
The type of derivative to get instruments for. Default is None, which gets all types.
Returns
-------
list[dict]
A list of instrument dictionaries.
"""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_request
if currency != "all" and currency.upper() not in CURRENCIES:
raise ValueError(
f"Currency {currency} not supported. Supported currencies are: {', '.join(CURRENCIES)}"
)
if derivative_type and derivative_type not in DERIVATIVE_TYPES:
raise ValueError(
f"Kind {derivative_type} not supported. Supported kinds are: {', '.join(DERIVATIVE_TYPES)}"
)
url = f"{BASE_URL}/api/v2/public/get_instruments?currency={currency.upper() if currency != 'all' else 'any'}"
if derivative_type is not None:
url += f"&kind={derivative_type}"
try:
response = await amake_request(url)
return response.get("result", []) # type: ignore
except Exception as e: # pylint: disable=broad-except
raise OpenBBError(
f"Failed to get instruments -> {e.__class__.__name__}: {e}"
) from e
async def get_options_symbols(symbol: OptionsSymbols = "BTC") -> dict:
"""
Get a dictionary of contract symbols by expiry.
Parameters
----------
symbol : OptionsSymbols
The underlying symbol to get options for. Default is "BTC".
Returns
-------
dict[str, str]
A dictionary of contract symbols by expiry date.
"""
# pylint: disable=import-outside-toplevel
from pandas import to_datetime
if symbol.upper() not in DERIBIT_OPTIONS_SYMBOLS:
raise ValueError(
f"Invalid Deribit symbol. Supported symbols are: {', '.join(DERIBIT_OPTIONS_SYMBOLS)}",
)
currency = (
"USDC" if symbol.upper() in ["BNB", "PAXG", "SOL", "XRP"] else symbol.upper()
)
instruments = await get_instruments(currency, "option")
expirations: dict = {}
all_options = list(
set(
d.get("instrument_name")
for d in instruments
if d.get("instrument_name").startswith(symbol)
and d.get("instrument_name").endswith(("-C", "-P"))
)
)
for item in sorted(
list(
set(
(
to_datetime(d.split("-")[1]).date().strftime("%Y-%m-%d"),
d.split("-")[1],
)
for d in all_options
)
)
):
expirations[item[0]] = item[1]
return {k: [d for d in all_options if v in d] for k, v in expirations.items()}

1609
openbb_platform/providers/deribit/poetry.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
[tool.poetry]
name = "openbb-deribit"
version = "1.0.0b"
description = "Deribit is a crypto-native derivatives exchange."
authors = ["OpenBB Team <hello@openbb.co>"]
license = "AGPL-3.0-only"
readme = "README.md"
packages = [{ include = "openbb_deribit" }]
[tool.poetry.dependencies]
python = "^3.9"
openbb-core = "^1.3.7"
async-lru = "^2.0.4"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."openbb_provider_extension"]
deribit = "openbb_deribit:deribit_provider"

View File

@@ -0,0 +1 @@
"""OpenBB Deribit Provider Tests."""

View File

@@ -0,0 +1,265 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://www.deribit.com/api/v2/public/get_instruments?currency=BTC&kind=option
response:
body:
string: !!binary |
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
eHz+ifEf/zcumxcqrf8IAA==
headers:
Access-Control-Allow-Headers:
- Authorization,User-Agent,Range,X-Requested-With,Content-Type,Partner
Access-Control-Allow-Methods:
- GET, POST, OPTIONS
Access-Control-Allow-Origin:
- '*'
Cache-Control:
- no-store
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Length:
- '12385'
Content-Type:
- application/json
Date:
- Sat, 07 Dec 2024 00:22:12 GMT
Server:
- nginx
Strict-Transport-Security:
- max-age=15768000
Vary:
- Origin,Authorization,Partner
- accept-encoding
X-Frame-Options:
- SAMEORIGIN
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,265 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://www.deribit.com/api/v2/public/get_instruments?currency=BTC&kind=option
response:
body:
string: !!binary |
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
wnWqHd4STq59p0cu7HFL2XWzM9wutM7ad17Akyc74Tz7r0VzPBWeJ3RSHQrvHgJ6ZdcwkT2dNNkJ
16lpIkM4qY6Fd9hbrRtOp654ezppohOuU23xhnBSHQzvsLdal1Bzv0NpcykndShdoJMmOuE6+w1K
czoVnSdwUp1w7LC3Wt/WdTXG29NJE51wnWqMN4ST6qhO95Ch63oNuf8h3vnkiupEF+ikiU64zn57
0pxORecJnFRnzrmHgl7X1RZvTydNdMJ1qi3eEE6qw5PcQ8WOu9X+Ba3BJZ0Bco1OmuiE6+z3zs3p
VHTO47xv8Wmi08OPWeg3dmrXeZlOkuhcoFNN8YZwUs2xe+ikcNzi4ISaEEJp6752net10kQnXGf3
c9KkTkXnCZxUo5geO+xW4tbvnAub23PZFZ3rddJEJ1znqHVuRqei8wROqpZ4j206LqX07xsMzrtQ
FZ0X6KSJTrjOwbfOKZ2KzhM4qfo6PbZzrtSGb3AqYnRO0XmBTprohOscXGQwpVPReQInVXOSx7Z/
1K3GwYGyaat5U3Su10kTnXCd/W+dczoVnfM47+cr0ERnwNYwa4qDlnjv3Z4UnRfoJInOBToHJ3ZO
6fz/JDr/+btXX57+44eXeloKWwypti36rYn7y9N/br+Dv/wDv6fbH/zq3e9+d/uP136m5/ab/vD4
/BPjP/5vfg/xlq3/CAA=
headers:
Access-Control-Allow-Headers:
- Authorization,User-Agent,Range,X-Requested-With,Content-Type,Partner
Access-Control-Allow-Methods:
- GET, POST, OPTIONS
Access-Control-Allow-Origin:
- '*'
Cache-Control:
- no-store
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Length:
- '12383'
Content-Type:
- application/json
Date:
- Sat, 07 Dec 2024 00:13:55 GMT
Server:
- nginx
Strict-Transport-Security:
- max-age=15768000
Vary:
- Origin,Authorization,Partner
- accept-encoding
X-Frame-Options:
- SAMEORIGIN
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,99 @@
"""Deribit Fetcher Tests."""
from unittest.mock import MagicMock, patch
import pytest
from openbb_core.app.service.user_service import UserService
from openbb_core.provider.utils.helpers import run_async
from openbb_deribit.models.options_chains import (
DeribitOptionsChainsData,
DeribitOptionsChainsFetcher,
)
test_credentials = UserService().default_user_settings.credentials.model_dump(
mode="json"
)
MOCK_OPTIONS_DATA = DeribitOptionsChainsData.model_validate(
{
"expiration": ["2024-12-07", "2024-12-07"],
"strike": [84000.0, 84000.0],
"option_type": ["call", "put"],
"underlying_symbol": ["SYN.BTC-7DEC24", "SYN.BTC-7DEC24"],
"underlying_price": [100671.2825, 100671.2825],
"contract_symbol": ["BTC-7DEC24-84000-C", "BTC-7DEC24-84000-P"],
"dte": [1, 1],
"contract_size": [1, 1],
"open_interest": [1.0, 124.8],
"volume": [0.1, 151.9],
"last_trade_price": [None, 20.13],
"bid": [10.06, 0.05],
"bid_size": [0.3, 0.0],
"ask": [0.06, 10.06],
"ask_size": [0.0, 0.2],
"mark": [16666.69, 0.0],
"high": [None, 442.83],
"low": [None, 10.06],
"change_percent": [0, -0.6],
"implied_volatility": [1.3618999999999999, 1.3618999999999999],
"delta": [0.99997, -3e-05],
"gamma": [0.0, 0.0],
"theta": [-0.24909, -0.0263],
"vega": [0.00366, 0.00366],
"rho": [0.91572, -3e-05],
"underlying_spot_price": [100644.24, 100644.24],
"settlement_price": [14590.44, 14.35],
"timestamp": [
"2024-12-06 17:26:36.234000-0500",
"2024-12-06 17:26:36.234000-0500",
],
"min_price": [13184.4, 10.06],
"max_price": [20078.53, 1509.66],
"interest_rate": [0.0, 0.0],
"bid_iv": [0.0, 0.0],
"ask_iv": [0.0, 2.1254],
"volume_notional": [0.0, 7083.24],
}
)
@pytest.fixture(scope="module")
def vcr_config():
"""VCR configuration."""
return {
"filter_headers": [("User-Agent", None)],
"filter_query_parameters": [
None,
],
}
@pytest.mark.record_http
def test_get_options_symbols():
"""Test getting the list of options symbols."""
# pylint: disable=import-outside-toplevel
from openbb_deribit.utils.helpers import get_options_symbols
params = {"symbol": "BTC"}
result = run_async(get_options_symbols, **params)
assert result is not None
assert isinstance(result, dict)
assert len(result) > 0
for key, value in result.items():
assert isinstance(value, list)
assert key.startswith("2")
@pytest.mark.asyncio
async def test_deribit_options_chains_fetcher(credentials=test_credentials):
"""Test Deribit Options Chains Fetcher."""
params = {"symbol": "BTC"}
fetcher = DeribitOptionsChainsFetcher()
with patch(
"openbb_deribit.models.options_chains.DeribitOptionsChainsFetcher.fetch_data",
return_value=MagicMock(MOCK_OPTIONS_DATA),
):
result = await fetcher.fetch_data(params, {})
assert isinstance(result, DeribitOptionsChainsData)

View File

@@ -45,6 +45,7 @@ openbb-regulators = "^1.3.5"
openbb-alpha-vantage = { version = "^1.3.5", optional = true }
openbb-biztoc = { version = "^1.3.5", optional = true }
openbb-cboe = { version = "^1.3.5", optional = true }
openbb-deribit = { path = "./providers/deribit", optional = true }
openbb-ecb = { version = "^1.3.5", optional = true }
openbb-finra = { version = "^1.3.5", optional = true }
openbb-finviz = { version = "^1.2.5", optional = true }
@@ -88,6 +89,7 @@ all = [
"openbb-biztoc",
"openbb-cboe",
"openbb-charting",
"openbb-deribit",
"openbb-ecb",
"openbb-econometrics",
"openbb-finra",