Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions notebooks/applications/volatility_surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ jupytext:
extension: .md
format_name: myst
format_version: 0.13
jupytext_version: 1.16.6
jupytext_version: 1.17.2
kernelspec:
display_name: .venv
language: python
name: python3
display_name: Python 3 (ipykernel)
language: python
---

# Volatility Surface
Expand All @@ -21,7 +21,7 @@ First thing, fetch the data
from quantflow.data.deribit import Deribit

async with Deribit() as cli:
loader = await cli.volatility_surface_loader("eth")
loader = await cli.volatility_surface_loader("eth", exclude_open_interest=0)
```

Once we have loaded the data, we create the surface and display the term-structure of forwards
Expand Down Expand Up @@ -125,16 +125,20 @@ Serialization
It is possible to save the vol surface into a json file so it can be recreated for testing or for serialization/deserialization.

```{code-cell} ipython3
with open("../tests/volsurface.json", "w") as fp:
fp.write(vs.inputs().model_dump_json())
with open("../../quantflow_tests/volsurface.json", "w") as fp:
fp.write(vs.inputs().model_dump_json(indent=2))
```

```{code-cell} ipython3
from quantflow.options.surface import VolSurfaceInputs, surface_from_inputs
import json

with open("../tests/volsurface.json", "r") as fp:
with open("../../quantflow_tests/volsurface.json", "r") as fp:
inputs = VolSurfaceInputs(**json.load(fp))

vs2 = surface_from_inputs(inputs)
```

```{code-cell} ipython3

```
458 changes: 243 additions & 215 deletions poetry.lock

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "quantflow"
version = "0.3.3"
version = "0.4.0"
description = "quantitative analysis"
authors = [{ name = "Luca Sbardella", email = "luca@quantmind.com" }]
license = "BSD-3-Clause"
Expand All @@ -20,9 +20,7 @@ Repository = "https://github.com/quantmind/quantflow"
Documentation = "https://quantmind.github.io/quantflow/"

[project.optional-dependencies]
data = [
"aio-fluid[http]>=1.2.1",
]
data = ["aio-fluid[http]>=1.2.1"]
cli = [
"asciichartpy>=1.5.25",
"async-cache>=1.1.1",
Expand All @@ -41,7 +39,7 @@ black = "^25.1.0"
pytest-cov = "^6.0.0"
mypy = "^1.14.1"
ghp-import = "^2.0.2"
ruff = "^0.11.12"
ruff = "^0.12.2"
pytest-asyncio = "^1.0.0"
isort = "^6.0.1"

Expand Down
2 changes: 1 addition & 1 deletion quantflow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Quantitative analysis and pricing"""

__version__ = "0.3.3"
__version__ = "0.4.0"
50 changes: 36 additions & 14 deletions quantflow/data/deribit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
from fluid.utils.data import compact_dict
from fluid.utils.http_client import AioHttpClient, HttpResponse, HttpResponseError

from quantflow.options.inputs import OptionType
from quantflow.options.surface import VolSecurityType, VolSurfaceLoader
from quantflow.utils.numbers import round_to_step, to_decimal
from quantflow.utils.numbers import (
Number,
round_to_step,
to_decimal,
to_decimal_or_none,
)


def parse_maturity(v: str) -> datetime:
Expand Down Expand Up @@ -80,9 +86,19 @@ async def get_volatility(self, currency: str, **kw: Any) -> pd.DataFrame:
kw.update(params=dict(currency=currency), callback=self.to_df)
return await self.get_path("public/get_historical_volatility", **kw)

async def volatility_surface_loader(self, currency: str) -> VolSurfaceLoader:
async def volatility_surface_loader(
self,
currency: str,
*,
exclude_open_interest: Number | None = None,
exclude_volume: Number | None = None,
) -> VolSurfaceLoader:
"""Create a :class:`.VolSurfaceLoader` for a given crypto-currency"""
loader = VolSurfaceLoader()
loader = VolSurfaceLoader(
asset=currency,
exclude_open_interest=to_decimal_or_none(exclude_open_interest),
exclude_volume=to_decimal_or_none(exclude_volume),
)
futures = await self.get_book_summary_by_currency(
currency=currency, kind=InstrumentKind.future
)
Expand All @@ -92,9 +108,9 @@ async def volatility_surface_loader(self, currency: str) -> VolSurfaceLoader:
instruments = await self.get_instruments(currency=currency)
instrument_map = {i["instrument_name"]: i for i in instruments}
min_tick_size = Decimal("inf")
for future in futures:
if (bid_ := future["bid_price"]) and (ask_ := future["ask_price"]):
name = future["instrument_name"]
for entry in futures:
if (bid_ := entry["bid_price"]) and (ask_ := entry["ask_price"]):
name = entry["instrument_name"]
meta = instrument_map[name]
tick_size = to_decimal(meta["tick_size"])
min_tick_size = min(min_tick_size, tick_size)
Expand All @@ -105,8 +121,8 @@ async def volatility_surface_loader(self, currency: str) -> VolSurfaceLoader:
VolSecurityType.spot,
bid=bid,
ask=ask,
open_interest=int(future["open_interest"]),
volume=int(future["volume_usd"]),
open_interest=to_decimal(entry["open_interest"]),
volume=to_decimal(entry["volume_usd"]),
)
else:
maturity = pd.to_datetime(
Expand All @@ -119,15 +135,15 @@ async def volatility_surface_loader(self, currency: str) -> VolSurfaceLoader:
maturity=maturity,
bid=bid,
ask=ask,
open_interest=int(future["open_interest"]),
volume=int(future["volume_usd"]),
open_interest=to_decimal(entry["open_interest"]),
volume=to_decimal(entry["volume_usd"]),
)
loader.tick_size_forwards = min_tick_size

min_tick_size = Decimal("inf")
for option in options:
if (bid_ := option["bid_price"]) and (ask_ := option["ask_price"]):
name = option["instrument_name"]
for entry in options:
if (bid_ := entry["bid_price"]) and (ask_ := entry["ask_price"]):
name = entry["instrument_name"]
meta = instrument_map[name]
tick_size = to_decimal(meta["tick_size"])
min_tick_size = min(min_tick_size, tick_size)
Expand All @@ -139,9 +155,15 @@ async def volatility_surface_loader(self, currency: str) -> VolSurfaceLoader:
unit="ms",
utc=True,
).to_pydatetime(),
call=meta["option_type"] == "call",
option_type=(
OptionType.call
if meta["option_type"] == "call"
else OptionType.put
),
bid=round_to_step(bid_, tick_size),
ask=round_to_step(ask_, tick_size),
open_interest=to_decimal(entry["open_interest"]),
volume=to_decimal(entry["volume_usd"]),
)
loader.tick_size_options = min_tick_size
return loader
Expand Down
51 changes: 36 additions & 15 deletions quantflow/options/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,35 @@
import enum
from datetime import datetime
from decimal import Decimal
from typing import Generic, TypeVar
from typing import TypeVar

from pydantic import BaseModel

from quantflow.utils.numbers import ZERO

P = TypeVar("P")


class Side(enum.StrEnum):
"""Side of the market"""

bid = enum.auto()
ask = enum.auto()


class OptionType(enum.StrEnum):
"""Type of option"""

call = enum.auto()
put = enum.auto()

def is_call(self) -> bool:
return self is OptionType.call

def is_put(self) -> bool:
return self is OptionType.put


class VolSecurityType(enum.StrEnum):
"""Type of security for the volatility surface"""

Expand All @@ -21,31 +43,30 @@ def vol_surface_type(self) -> VolSecurityType:
return self


class VolSurfaceInput(BaseModel, Generic[P]):
bid: P
ask: P

class VolSurfaceInput(BaseModel):
bid: Decimal
ask: Decimal
open_interest: Decimal = ZERO
volume: Decimal = ZERO

class OptionInput(BaseModel):
price: Decimal
strike: Decimal
maturity: datetime
call: bool


class SpotInput(VolSurfaceInput[Decimal]):
class SpotInput(VolSurfaceInput):
security_type: VolSecurityType = VolSecurityType.spot


class ForwardInput(VolSurfaceInput[Decimal]):
class ForwardInput(VolSurfaceInput):
maturity: datetime
security_type: VolSecurityType = VolSecurityType.forward


class OptionSidesInput(VolSurfaceInput[OptionInput]):
class OptionInput(VolSurfaceInput):
strike: Decimal
maturity: datetime
option_type: OptionType
security_type: VolSecurityType = VolSecurityType.option


class VolSurfaceInputs(BaseModel):
asset: str
ref_date: datetime
inputs: list[ForwardInput | SpotInput | OptionSidesInput]
inputs: list[ForwardInput | SpotInput | OptionInput]
Loading