Skip to content

Commit

Permalink
feat(deribit_fetcher): Updated SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
akhercha committed Jul 22, 2024
1 parent 378b5e7 commit 3d34bb3
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pragma_sdk.common.fetchers.generic_fetchers.deribit import DeribitGenericFetcher
from pragma_sdk.common.fetchers.generic_fetchers.deribit import DeribitOptionsFetcher

__all__ = [
"DeribitGenericFetcher",
"DeribitOptionsFetcher",
]
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from pragma_sdk.onchain.constants import DERIBIT_MERKLE_FEED_KEY
from pragma_sdk.onchain.client import PragmaOnChainClient
from pragma_sdk.onchain.types import Network

logger = get_pragma_sdk_logger()

Expand Down Expand Up @@ -131,7 +132,7 @@ def __hash__(self) -> int:
return int(hashlib.sha256(hash_input.encode()).hexdigest(), 16)


class DeribitGenericFetcher(FetcherInterfaceT):
class DeribitOptionsFetcher(FetcherInterfaceT):
"""
Deribit fetcher.
Retrieves all the options data for all available instruments for a set of pairs.
Expand All @@ -144,11 +145,29 @@ class DeribitGenericFetcher(FetcherInterfaceT):
headers: Dict[Any, Any]
_client: PragmaOnChainClient

REQUIRED_PAIRS = {
Pair.from_tickers("BTC", "USD"),
Pair.from_tickers("ETH", "USD"),
}

SOURCE: str = "DERIBIT"
BASE_URL: str = "https://www.deribit.com/api/v2/public"
ENDPOINT_OPTIONS: str = "get_book_summary_by_currency?currency="
ENDPOINT_OPTIONS_SUFFIX: str = "&kind=option&expired=false"

def __init__(
self,
pairs: List[Pair],
publisher: str,
api_key: Optional[str] = None,
network: Network = "mainnet",
):
super().__init__(pairs, publisher, api_key, network)
if set(self.pairs) != self.REQUIRED_PAIRS:
raise ValueError(
"Currently, DeribitOptionsFetcher must be used for BTC/USD and ETH/USD only."
)

def format_url(self, currency: Currency) -> str: # type: ignore[override]
return f"{self.BASE_URL}/{self.ENDPOINT_OPTIONS}{currency.id}{self.ENDPOINT_OPTIONS_SUFFIX}"

Expand Down
1 change: 1 addition & 0 deletions pragma-sdk/pragma_sdk/common/fetchers/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pragma_sdk.common.exceptions import PublisherFetchError


# TODO(akhercha): FetcherInterfaceT should take as parameter the client instead of creating it
# Abstract base class for all fetchers
@add_sync_methods
class FetcherInterfaceT(abc.ABC):
Expand Down
27 changes: 16 additions & 11 deletions pragma-sdk/pragma_sdk/common/types/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@

class Asset:
data_type: DataTypes
pair_id: int
asset_id: int
expiration_timestamp: Optional[UnixTimestamp]

def __init__(
self,
data_type: DataTypes,
pair_id: str | int,
asset_id: str | int,
expiration_timestamp: Optional[UnixTimestamp] = None,
):
if isinstance(pair_id, str):
pair_id = str_to_felt(pair_id)
elif not isinstance(pair_id, int):
if isinstance(asset_id, str):
asset_id = str_to_felt(asset_id)
elif not isinstance(asset_id, int):
raise TypeError(
"Pair ID must be string (will be converted to felt) or integer"
"Asset ID must be string (will be converted to felt) or integer"
)

self.pair_id = pair_id
self.asset_id = asset_id
self.data_type = data_type
self.expiration_timestamp = expiration_timestamp

Expand All @@ -33,13 +33,18 @@ def serialize(self) -> Dict[str, Union[int | Tuple[int, Optional[int]]]]:
"""
match self.data_type:
case DataTypes.SPOT:
return {"SpotEntry": self.pair_id}
return {"SpotEntry": self.asset_id}
case DataTypes.FUTURE:
return {"FutureEntry": (self.pair_id, self.expiration_timestamp)}
return {"FutureEntry": (self.asset_id, self.expiration_timestamp)}
case DataTypes.GENERIC:
return {"GenericEntry": self.asset_id}

def to_dict(self) -> Dict[str, Union[int, str, None]]:
key_name = (
"pair_id" if self.data_type in [DataTypes.SPOT, DataTypes.FUTURE] else "key"
)
return {
"pair_id": self.pair_id,
"expiration_timestamp": self.expiration_timestamp,
key_name: self.asset_id,
"data_type": self.data_type.name,
"expiration_timestamp": self.expiration_timestamp,
}
14 changes: 8 additions & 6 deletions pragma-sdk/pragma_sdk/common/types/currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ def __repr__(self) -> str:
def __eq__(self, value: object) -> bool:
if not isinstance(value, Currency):
return False
return (
self.id == value.id
and self.decimals == value.decimals
and self.is_abstract_currency == value.is_abstract_currency
and self.starknet_address == value.starknet_address
and self.ethereum_address == value.ethereum_address
return all(
[
self.id == value.id,
self.decimals == value.decimals,
self.is_abstract_currency == value.is_abstract_currency,
self.starknet_address == value.starknet_address,
self.ethereum_address == value.ethereum_address,
]
)

def __hash__(self) -> int:
Expand Down
2 changes: 1 addition & 1 deletion pragma-sdk/pragma_sdk/common/types/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ def get_source(self) -> str:
return felt_to_str(self.base.source)

def get_asset_type(self) -> DataTypes:
return DataTypes.FUTURE
return DataTypes.GENERIC

@staticmethod
def from_dict(entry_dict: Any) -> "GenericEntry":
Expand Down
5 changes: 5 additions & 0 deletions pragma-sdk/pragma_sdk/common/types/pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def __repr__(self) -> str:
def __hash__(self) -> int:
return hash((self.id, self.base_currency, self.quote_currency))

def __eq__(self, other: object) -> bool:
if not isinstance(other, Pair):
return False
return self.id == other.id

def to_tuple(self) -> Tuple[str, str]:
return (self.base_currency.id, self.quote_currency.id)

Expand Down
1 change: 1 addition & 0 deletions pragma-sdk/pragma_sdk/common/types/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def serialize(self) -> Dict[str, None]:
class DataTypes(StrEnum):
SPOT = "Spot"
FUTURE = "Future"
GENERIC = "Generic"

def __repr__(self):
return f"'{self.value}'"
Expand Down
2 changes: 1 addition & 1 deletion pragma-sdk/pragma_sdk/onchain/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@
"0xe3e1c077138abb6d570b1a7ba425f5479b12f50a78a72be680167d4cf79c48"
)

DERIBIT_MERKLE_FEED_KEY = str_to_felt("DERIBIT_OPTIONS_DATA")
DERIBIT_MERKLE_FEED_KEY = str_to_felt("DERIBIT_OPTIONS_MERKLE_ROOT")
14 changes: 7 additions & 7 deletions pragma-sdk/pragma_sdk/onchain/mixins/merkle_feed.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from typing import Any

from starknet_py.net.client import Client
from starknet_py.net.account.account import Account

from pragma_sdk.onchain.types import Contract


class MerkleFeedMixin:
"""
Class used to retrieve values from the Deribit options Merkle Feed.
The Merkle Feed is in fact a merkle root stored in a GenericEntry.
"""

client: Client
account: Account
summary_stats: Contract

async def publish_merkle_feed(self) -> None:
raise NotImplementedError("🙅‍♀ publish_merkle_feed not implemented yet!")

async def get_merkle_feed(self) -> Any:
raise NotImplementedError("🙅‍♀ get_merkle_feed not implemented yet!")
async def i_do_not_know_yet(self) -> None:
raise NotImplementedError("🙅‍♀ i_do_not_know_yet not implemented yet!")
69 changes: 52 additions & 17 deletions pragma-sdk/pragma_sdk/onchain/mixins/oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from pragma_sdk.common.logging import get_pragma_sdk_logger
from pragma_sdk.common.utils import felt_to_str, str_to_felt
from pragma_sdk.common.types.entry import Entry, FutureEntry, SpotEntry
from pragma_sdk.common.types.entry import Entry, FutureEntry, SpotEntry, GenericEntry
from pragma_sdk.common.types.types import AggregationMode
from pragma_sdk.common.types.asset import Asset
from pragma_sdk.common.types.pair import Pair
Expand Down Expand Up @@ -73,43 +73,46 @@ async def publish_spot_entry(
)
return invocation

async def publish_many(
self,
entries: List[Entry],
) -> List[InvokeResult]:
async def publish_many(self, entries: List[Entry]) -> List[InvokeResult]:
if not entries:
logger.warning("Skipping publishing as entries array is empty")
logger.warning("publish_many received no entries to publish. Skipping")
return []

invocations: List[InvokeResult] = []

spot_entries: List[Entry] = [
entry for entry in entries if isinstance(entry, SpotEntry)
]
future_entries: List[Entry] = [
entry for entry in entries if isinstance(entry, FutureEntry)
]
generic_entries: List[Entry] = [
entry for entry in entries if isinstance(entry, GenericEntry)
]

invocations = []
invocations.extend(await self._publish_entries(spot_entries, DataTypes.SPOT))
invocations.extend(
await self._publish_entries(future_entries, DataTypes.FUTURE)
)

invocations.extend(
await self._publish_entries(generic_entries, DataTypes.GENERIC)
)
return invocations

async def _publish_entries(
self, entries: List[Entry], data_type: DataTypes
) -> List[InvokeResult]:
invocations = []
serialized_entries = (
SpotEntry.serialize_entries(entries)
if data_type == DataTypes.SPOT
else FutureEntry.serialize_entries(entries)
)

if len(serialized_entries) == 0:
if len(entries) == 0:
return []

invocations = []
match data_type:
case DataTypes.SPOT:
serialized_entries = SpotEntry.serialize_entries(entries)
case DataTypes.FUTURE:
serialized_entries = FutureEntry.serialize_entries(entries)
case DataTypes.GENERIC:
serialized_entries = GenericEntry.serialize_entries(entries)

pagination = self.execution_config.pagination
if pagination:
for i in range(0, len(serialized_entries), pagination):
Expand Down Expand Up @@ -300,6 +303,38 @@ async def get_future(
response["expiration_timestamp"],
)

async def get_generic(
self,
key: str | int,
block_id: Optional[BlockId] = "latest",
) -> GenericEntry:
"""
Query the Oracle contract for the data of a generic entry.
:param key: Key ID of the generic entry
:param block_id: Block number or Block Tag
:return: GenericEntry
"""
if isinstance(key, str):
key = str_to_felt(key.upper())
elif not isinstance(key, int):
raise TypeError(
"Generic entry key must be string (will be converted to felt) or integer"
)
# TODO: get_generic_entry does not exist, yet?
(response,) = await self.oracle.functions["get_generic"].call(
key,
block_number=block_id,
)
response = dict(response)
return GenericEntry(
key=response["key"],
value=response["value"],
timestamp=response["timestamp"],
source=response["source"],
publisher=response["source"],
)

async def get_decimals(
self,
asset: Asset,
Expand Down

0 comments on commit 3d34bb3

Please sign in to comment.