mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-31 22:41:19 +00:00
[V5] openbb-deribit: Refactor provider for V5 (#7674)
* refactor openbb-deribit for v5 * restructure the optimizer around the published combos data and add integration test suites
This commit is contained in:
165
.github/workflows/publish-openbb-deribit.yml
vendored
Normal file
165
.github/workflows/publish-openbb-deribit.yml
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
name: 📦 publish openbb-deribit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [develop, v5]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
name: Verify version bump
|
||||
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/openbb-deribit-v')) || github.event_name == 'workflow_dispatch' }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.parse.outputs.version }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: openbb_platform/providers/deribit
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
|
||||
|
||||
- name: Parse [project].version from pyproject.toml
|
||||
id: parse
|
||||
run: |
|
||||
VERSION=$(grep -m1 -E '^version[[:space:]]*=' pyproject.toml | sed -E 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/')
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "::error::Could not parse [project].version from pyproject.toml."
|
||||
exit 1
|
||||
fi
|
||||
echo "Local version: $VERSION"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fail if version already exists on PyPI
|
||||
env:
|
||||
VERSION: ${{ steps.parse.outputs.version }}
|
||||
run: |
|
||||
STATUS=$(curl -sS -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/openbb-deribit/${VERSION}/json")
|
||||
case "$STATUS" in
|
||||
200)
|
||||
echo "::error::openbb-deribit ${VERSION} is already published on PyPI."
|
||||
echo "Bump openbb_platform/providers/deribit/pyproject.toml [project].version and re-merge a fresh release branch."
|
||||
exit 1
|
||||
;;
|
||||
404)
|
||||
echo "openbb-deribit ${VERSION} is not yet on PyPI — proceeding."
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unexpected status $STATUS from PyPI JSON API. Aborting to avoid a silent skip."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Verify release branch name matches pyproject version
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
VERSION: ${{ steps.parse.outputs.version }}
|
||||
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
run: |
|
||||
EXPECTED="release/openbb-deribit-v${VERSION}"
|
||||
if [ "$BRANCH" != "$EXPECTED" ]; then
|
||||
echo "::error::Release branch '${BRANCH}' does not match pyproject version '${VERSION}'."
|
||||
echo "Expected branch name: '${EXPECTED}'."
|
||||
echo "Either rename the release branch or update [project].version in pyproject.toml so they agree."
|
||||
exit 1
|
||||
fi
|
||||
echo "Branch '${BRANCH}' matches pyproject version '${VERSION}'."
|
||||
|
||||
build:
|
||||
name: Build sdist + wheel
|
||||
needs: version-check
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: openbb_platform/providers/deribit
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- run: python -m pip install --upgrade pip uv
|
||||
|
||||
- name: Build sdist + wheel
|
||||
run: uv build --no-sources
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openbb-deribit-dist
|
||||
path: openbb_platform/providers/deribit/dist/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
test:
|
||||
name: Test (${{ matrix.os }} / Python ${{ matrix.python_version }})
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu-latest, python_version: "3.10" }
|
||||
- { os: ubuntu-latest, python_version: "3.11" }
|
||||
- { os: ubuntu-latest, python_version: "3.12" }
|
||||
- { os: ubuntu-latest, python_version: "3.13" }
|
||||
- { os: ubuntu-latest, python_version: "3.14" }
|
||||
- { os: macos-latest, python_version: "3.10" }
|
||||
- { os: macos-latest, python_version: "3.14" }
|
||||
- { os: windows-latest, python_version: "3.10" }
|
||||
- { os: windows-latest, python_version: "3.14" }
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: openbb_platform/providers/deribit
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
allow-prereleases: true
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: openbb-deribit-dist
|
||||
path: openbb_platform/providers/deribit/dist
|
||||
|
||||
- run: python -m pip install --upgrade pip uv
|
||||
|
||||
- name: Install the dev group then the openbb-deribit wheel
|
||||
run: |
|
||||
uv pip install --system --group dev
|
||||
uv pip install --system dist/openbb_deribit-*.whl
|
||||
|
||||
- name: Test the installed wheel
|
||||
run: |
|
||||
rm -rf openbb_deribit
|
||||
pytest tests --cov=openbb_deribit --cov-report=term-missing --cov-fail-under=100
|
||||
|
||||
publish:
|
||||
name: Publish to PyPI
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: openbb-deribit-dist
|
||||
path: dist
|
||||
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
packages-dir: dist
|
||||
118
.github/workflows/test-unit-openbb-deribit.yml
vendored
Normal file
118
.github/workflows/test-unit-openbb-deribit.yml
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
name: 🧪 test openbb-deribit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop, main, v5]
|
||||
paths:
|
||||
- "openbb_platform/providers/deribit/**"
|
||||
- ".github/workflows/test-unit-openbb-deribit.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: test-openbb-deribit-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build sdist + wheel
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: openbb_platform/providers/deribit
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- run: python -m pip install --upgrade pip uv
|
||||
|
||||
- name: Build sdist + wheel
|
||||
run: uv build --no-sources
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openbb-deribit-dist
|
||||
path: openbb_platform/providers/deribit/dist/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
lint:
|
||||
name: Lint (ruff + ty)
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: openbb_platform/providers/deribit
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- run: python -m pip install --upgrade pip uv
|
||||
|
||||
- name: Install openbb-deribit in editable mode with the dev group
|
||||
run: uv pip install --system -e . --group dev
|
||||
|
||||
- name: ruff check
|
||||
run: ruff check --no-fix openbb_deribit tests integration
|
||||
|
||||
- name: ruff format --check
|
||||
run: ruff format --check openbb_deribit tests integration
|
||||
|
||||
- name: ty check
|
||||
run: ty check openbb_deribit
|
||||
|
||||
test:
|
||||
name: Test (${{ matrix.os }} / Python ${{ matrix.python_version }})
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu-latest, python_version: "3.10" }
|
||||
- { os: ubuntu-latest, python_version: "3.11" }
|
||||
- { os: ubuntu-latest, python_version: "3.12" }
|
||||
- { os: ubuntu-latest, python_version: "3.13" }
|
||||
- { os: ubuntu-latest, python_version: "3.14" }
|
||||
- { os: macos-latest, python_version: "3.10" }
|
||||
- { os: macos-latest, python_version: "3.14" }
|
||||
- { os: windows-latest, python_version: "3.10" }
|
||||
- { os: windows-latest, python_version: "3.14" }
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: openbb_platform/providers/deribit
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
allow-prereleases: true
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: openbb-deribit-dist
|
||||
path: openbb_platform/providers/deribit/dist
|
||||
|
||||
- run: python -m pip install --upgrade pip uv
|
||||
|
||||
- name: Install the dev group then the openbb-deribit wheel
|
||||
run: |
|
||||
uv pip install --system --group dev
|
||||
uv pip install --system dist/openbb_deribit-*.whl
|
||||
|
||||
- name: Test the installed wheel
|
||||
run: |
|
||||
rm -rf openbb_deribit
|
||||
pytest tests --cov=openbb_deribit --cov-report=term-missing --cov-fail-under=100
|
||||
342
openbb_platform/providers/deribit/README.md
vendored
342
openbb_platform/providers/deribit/README.md
vendored
@@ -1,45 +1,305 @@
|
||||
# OpenBB Deribit Provider Extension
|
||||
|
||||
This package adds the `openbb-deribit` provider and router extension to the Open Data Platform by
|
||||
OpenBB.
|
||||
|
||||
It implements the complete public market data surface of [Deribit](https://docs.deribit.com/) — the
|
||||
crypto derivatives exchange listing options, futures, perpetuals, spot pairs, and multi-leg combos —
|
||||
as OpenBB Platform endpoints. No API key is required, and nothing here trades.
|
||||
|
||||
## Installation
|
||||
|
||||
Install from PyPI with:
|
||||
|
||||
```sh
|
||||
pip install openbb-deribit
|
||||
```
|
||||
|
||||
Then build the Python static assets by running:
|
||||
|
||||
```sh
|
||||
openbb-build
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
The fastest way to get started is by connecting to the OpenBB Workspace as a custom backend.
|
||||
|
||||
### Start Server
|
||||
|
||||
```sh
|
||||
openbb-api
|
||||
```
|
||||
|
||||
This starts the FastAPI server over localhost on port 6900.
|
||||
|
||||
### Add to Workspace
|
||||
|
||||
See the documentation [here](https://docs.openbb.co/python/quickstart/workspace) for more details.
|
||||
The extension ships an `apps.json` describing a ready-made dashboard with Markets, Futures, Options,
|
||||
Analysis, Volatility, and Reference tabs.
|
||||
|
||||
## Conditional Registration
|
||||
|
||||
Five of the Deribit models implement OpenBB standard models — `FuturesCurve`, `FuturesHistorical`,
|
||||
`FuturesInfo`, `FuturesInstruments`, and `OptionsChains` — which are normally served by the
|
||||
`openbb-derivatives` extension. When it is installed, Deribit registers as a provider under its
|
||||
existing commands and no Deribit-specific route is added for them.
|
||||
|
||||
When it is absent, the same data is served from this extension's own router under
|
||||
`/deribit/futures/...` and `/deribit/options/...`, so installing `openbb-deribit` on its own still
|
||||
exposes every dataset. The bundled `apps.json` resolves its widget IDs the same way.
|
||||
|
||||
Everything else is Deribit-specific and always lives under `/deribit/...`.
|
||||
|
||||
## Coverage
|
||||
|
||||
- obb.derivatives.options.chains
|
||||
- Support for symbols:
|
||||
- BTC
|
||||
- ETH
|
||||
- XRP
|
||||
- SOL
|
||||
- BNB
|
||||
- PAXG
|
||||
- obb.derivatives.futures.curve
|
||||
- Support for symbols:
|
||||
- BTC
|
||||
- ETH
|
||||
- PAXG
|
||||
- obb.derivatives.futures.historical
|
||||
- Support for symbols (Perpetual Futures):
|
||||
- ADAUSDC
|
||||
- ALGOUSDC
|
||||
- AVAXUSDC
|
||||
- BCHUSDC
|
||||
- BNBUSDC
|
||||
- BTC
|
||||
- BTCUSDC
|
||||
- BTCUSDT
|
||||
- DOGEUSDC
|
||||
- DOTUSDC
|
||||
- ETH
|
||||
- ETHUSDC
|
||||
- ETHUSDT
|
||||
- LINKUSDC
|
||||
- LTCUSDC
|
||||
- NEARUSDC
|
||||
- PAXGUSDC
|
||||
- SOLUSDC
|
||||
- TRXUSDC
|
||||
- UNIUSDC
|
||||
- XRPUSDC
|
||||
- Additional symbols include all Deribit instrument names of current open contracts for BTC, ETH, and PAXG futures.
|
||||
- obb.derivatives.futures.instruments
|
||||
- Reference data for all current futures instruments.
|
||||
- obb.derivatives.futures.info
|
||||
- Key stats by symbol (multiple items allowed).
|
||||
### Endpoints
|
||||
|
||||
```python
|
||||
from openbb import obb
|
||||
|
||||
obb.deribit
|
||||
# /deribit
|
||||
# index
|
||||
# delivery_prices
|
||||
# historical
|
||||
# price
|
||||
# market
|
||||
# block_rfq_trades
|
||||
# book_summary
|
||||
# order_book
|
||||
# settlements
|
||||
# ticker
|
||||
# trade_volumes
|
||||
# trades
|
||||
# rates
|
||||
# apr_history
|
||||
# funding_chart
|
||||
# funding_history
|
||||
# funding_value <- utility endpoint, returns a single rate
|
||||
# reference
|
||||
# announcements
|
||||
# combo_choices <- utility endpoint serving choices to Workspace widgets
|
||||
# combos
|
||||
# contract_size <- utility endpoint
|
||||
# currencies
|
||||
# currency_choices <- utility endpoint
|
||||
# expirations
|
||||
# index_choices <- utility endpoint
|
||||
# instrument_choices <- utility endpoint
|
||||
# instruments
|
||||
# server_time <- utility endpoint
|
||||
# status <- utility endpoint
|
||||
# volatility
|
||||
# index
|
||||
# realized
|
||||
# futures
|
||||
# curve
|
||||
# curve_choices <- utility endpoint
|
||||
# historical
|
||||
# info
|
||||
# instruments
|
||||
# perpetual_choices <- utility endpoint
|
||||
# options
|
||||
# chains
|
||||
# optimizer
|
||||
# payoff
|
||||
# smile
|
||||
# spreads
|
||||
# stats
|
||||
# straddle
|
||||
# strangle
|
||||
# surface
|
||||
# term_structure
|
||||
# underlying_choices <- utility endpoint
|
||||
```
|
||||
|
||||
Every public data method the exchange documents is reachable. The methods that exist only to open a
|
||||
websocket session — `auth`, `hello`, `subscribe`, `set_heartbeat`, `test` — are not exposed, because
|
||||
they carry no data.
|
||||
|
||||
### Datasets
|
||||
|
||||
- **Options chains** — the full chain of an underlying, with all five greeks, mark/bid/ask implied
|
||||
volatility, and open interest. Read over a websocket subscription, which is the only way the
|
||||
exchange publishes greeks. BTC and ETH contracts are quoted in the underlying, so their premiums
|
||||
are carried to USD at the index level each quote was published against.
|
||||
- **Order books** — the resting depth of any instrument, one row per level, by name or by numeric
|
||||
instrument identifier.
|
||||
- **Quotes** — the live ticker of any instrument, and the whole-currency book summary carrying the
|
||||
top of book and trailing session statistics of every instrument at once.
|
||||
- **Trades** — the prints that crossed, by instrument or across a currency, optionally over a span.
|
||||
Option prints carry the volatility they implied.
|
||||
- **Block RFQ** — the block requests that traded, one row per leg, with the index levels each
|
||||
structure was marked against.
|
||||
- **Futures** — the term structure of any underlying with a dated curve, priced off each contract's
|
||||
mark, optionally against how the curve stood some hours ago. Candles are available for any listed
|
||||
instrument, not only futures.
|
||||
- **Funding and yield** — the hourly funding a perpetual has paid, the funding accrued over the
|
||||
current window, the single rate for a span, and the daily yield of each yield-bearing currency.
|
||||
- **Volatility** — the DVOL volatility index as candles, and the volatility an asset has actually
|
||||
realized. Both accept several at once, and both offer only what the reading means something for:
|
||||
DVOL is computed for BTC and ETH alone, and a dollar-pegged token has no volatility against the
|
||||
dollar to realize, so USDC and USDT are not offered on either. EURR is, because a euro token
|
||||
measured against the dollar reads the euro rate — around 5 percent against their 0.5.
|
||||
|
||||
This is separate from a currency contracts are *settled* in. USDC margins a real book on Deribit,
|
||||
so it stays on the instrument, book summary, settlement and trade views, and the dashboard binds
|
||||
the two meanings to two different controls rather than one.
|
||||
|
||||
### Options Are Listed By Underlying, Not By Settlement Currency
|
||||
|
||||
Only BTC and ETH are both a settlement currency and an underlying, which is the only reason the
|
||||
exchange's by-currency listings read sensibly for them. USDC settles options written on seven
|
||||
different underlyings, so asking for `kind=option` by USDC returns 3,656 contracts across AVAX,
|
||||
BTC, ETH, HYPE, SOL, TRON and XRP — a settlement rail, not a chain. USDT and EURR settle no options
|
||||
at all.
|
||||
|
||||
So a listing of option contracts scoped to a currency that only settles them is refused, and says
|
||||
to ask by the underlying instead. Every one of those underlyings is offered by name, and naming an
|
||||
instrument directly still works. The same views keep USDC for what it genuinely carries — 174
|
||||
futures, 47 spot pairs — and settlements, which are a settlement-currency fact, keep every
|
||||
currency the exchange collateralises in.
|
||||
- **Indices** — the current level of any of the 344 published indices, its history over a span, and
|
||||
the level each index has delivered at.
|
||||
- **Settlements** — the settlements, deliveries, and bankruptcies the exchange has processed.
|
||||
- **Reference** — the full contract specification of every listed instrument, every currency the
|
||||
exchange lists, the expirations listed on each currency, every multi-leg combo, and the exchange's
|
||||
own announcements.
|
||||
|
||||
## Options Analysis
|
||||
|
||||
The chain is also the input to an analysis suite built on it. Every figure below is computed from
|
||||
the quotes the exchange publishes — there is no second data source, and no assumption about a rate
|
||||
or a dividend the exchange does not quote.
|
||||
|
||||
- **Strategy optimizer** — builds every Deribit combo structure the chosen expiration can form,
|
||||
bought and sold: call and put spreads (CS, PS), straddles and strangles (STRD, STRG), risk
|
||||
reversals and synthetics (RR, REV), 1x2 ratio spreads (CSR12, PSR12), butterflies and iron
|
||||
butterflies (CBUT, PBUT, IBUT), condors and iron condors (CCOND, PCOND, ICOND), calendars and
|
||||
diagonals against the expiration about a month later (CCAL, PCAL, CDIAG, PDIAG), and single
|
||||
calls and puts. A position Deribit lists as a combo book is priced from that book's bid or offer,
|
||||
and every listed combo on the expiration is ranked even outside the strike window. Each position
|
||||
is sized so its worst loss across a threefold move either way is the budget, which is what lets
|
||||
credit structures rank alongside debit ones. The best position of each structure is returned,
|
||||
ranked by what it returns if the underlying reaches a price by a date.
|
||||
- **Payoff** — the profit and loss of one ranked strategy, drawn only over the prices where the
|
||||
position actually responds. A payoff is a straight line everywhere outside its strikes, so the
|
||||
range is taken from the position rather than from a fixed span either side of spot, and the
|
||||
target and breakevens are added when they sit within reach of it.
|
||||
- **Straddle, strangle, vertical spreads** — the standard structures priced at every listed
|
||||
expiration, each with its cost, its cost as a share of spot, its breakevens, and its bounds.
|
||||
- **Volatility smile** — implied volatility or skew against strike for calls and puts, up to five
|
||||
expirations, optionally only out of the money. Drawn the same way as the Cboe smile.
|
||||
- **Volatility term structure** — implied volatility or price at the nearest out-of-the-money
|
||||
strike, a chosen strike, or a moneyness, across every expiration. Drawn the same way as Cboe's.
|
||||
- **Volatility surface** — implied volatility, a greek, or delta and gamma exposure, raised over
|
||||
days to expiration and strike, for either side of the chain. Drawn the same way as Cboe's.
|
||||
- **Statistics** — call and put open interest or volume by expiration, or by strike for one
|
||||
expiration, as values, shares of the total, or put/call ratios. Drawn the same way as Cboe's.
|
||||
|
||||
### Priced and Sized in the Quote Currency
|
||||
|
||||
Budgets and profits are expressed in what the underlying is quoted in — USD for the coin-margined
|
||||
roots, USDC for the linear ones — so a budget means the same thing whichever contract it buys, and
|
||||
the numbers are readable without converting anything.
|
||||
|
||||
BTC and ETH options are still settled in the coin, and that is not hidden. The premium is coin the
|
||||
account no longer holds, so what it cost depends on what the coin is worth at the price being
|
||||
valued: at the price the position was opened at the loss is exactly the premium, above it more,
|
||||
below it less. This is why an inverse position's loss is not the flat line a linear payoff has, and
|
||||
the chart says which contract settles in what.
|
||||
|
||||
The settlement style, contract size and tick size are read from the exchange's own instrument
|
||||
specification rather than assumed. The tick matters for sizing: far out of the money the published
|
||||
mark can run below the smallest price the instrument trades in — a TRON contract marked at 3.1e-07
|
||||
against a tick of 5e-05 — and sizing a budget against a price nobody can pay invents a position
|
||||
hundreds of times larger than the money would really buy. A contract that is priced at all is
|
||||
entered at no less than one tick; a contract with no price is left out.
|
||||
|
||||
### Greeks Are Modelled, Not Subscribed
|
||||
|
||||
The `options.chains` table reads the greeks the exchange itself publishes, which means subscribing
|
||||
to every contract over a websocket — about twelve seconds for BTC. The analysis views do not need
|
||||
the exchange's greeks, only its quotes and volatilities, and those come from three REST calls in
|
||||
well under a second. They read that path instead and model the greeks from the quoted volatility.
|
||||
|
||||
Measured against the greeks Deribit publishes, across all 972 BTC contracts: delta agrees to a
|
||||
median of 0.0001, gamma to 0.000002, vega to 0.03. Theta follows the exchange's own two
|
||||
conventions rather than the bare derivative — scaled down inside the last day, where the
|
||||
derivative diverges but a contract cannot decay faster than it expires, and floored at the
|
||||
contract's own value, because nothing decays by more than it is worth. Both rules bind on real
|
||||
contracts; together they bring the worst disagreement across the chain from 193 per day to 0.11.
|
||||
|
||||
The whole nine-widget Analysis tab loads in under three seconds cold and about one warm, against
|
||||
fourteen seconds before. The last read of a chain is held briefly and shared, so nine views of one
|
||||
underlying read it once.
|
||||
|
||||
### Choices Are Read Live
|
||||
|
||||
The exchange lists 52 currencies, 344 indices, and roughly 5,800 instruments, and the set changes
|
||||
week to week. Nothing in this extension freezes those into a literal: the underlyings with listed
|
||||
options, the underlyings with a futures curve, the perpetuals, the indices, and the combo
|
||||
identifiers are all read from the exchange when asked for. The utility endpoints marked above serve
|
||||
them to Workspace widgets as `[{label, value}]`.
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from openbb import obb
|
||||
|
||||
# The full BTC option chain, with greeks.
|
||||
chains = obb.deribit.options.chains(symbol="BTC", provider="deribit")
|
||||
|
||||
# The BTC futures term structure, against where it stood a day ago.
|
||||
curve = obb.deribit.futures.curve(symbol="BTC", hours_ago=24, provider="deribit")
|
||||
|
||||
# The resting depth of the BTC perpetual.
|
||||
book = obb.deribit.market.order_book(
|
||||
symbol="BTC-PERPETUAL", depth=20, provider="deribit"
|
||||
)
|
||||
|
||||
# The top of book of every BTC instrument at once.
|
||||
summary = obb.deribit.market.book_summary(
|
||||
currency="BTC", kind="option", provider="deribit"
|
||||
)
|
||||
|
||||
# The hourly funding the BTC perpetual has paid.
|
||||
funding = obb.deribit.rates.funding_history(symbol="BTC-PERPETUAL", provider="deribit")
|
||||
|
||||
# The DVOL volatility index, as daily candles.
|
||||
dvol = obb.deribit.volatility.index(currency="BTC", interval="1d", provider="deribit")
|
||||
|
||||
# Every instrument the exchange lists, with its full specification.
|
||||
instruments = obb.deribit.reference.instruments(currency="any", provider="deribit")
|
||||
|
||||
# The best position of every combo structure for a view of 100,000 BTC by year end,
|
||||
# each risking 5,000 USD.
|
||||
ranked = obb.deribit.options.optimizer(
|
||||
symbol="BTC", target_price=100000, target_date="2026-12-25", budget=5000
|
||||
)
|
||||
|
||||
# The payoff of the best of them.
|
||||
diagram = obb.deribit.options.payoff(
|
||||
symbol="BTC", target_price=100000, target_date="2026-12-25"
|
||||
)
|
||||
|
||||
# At-the-money volatility against time to expiration.
|
||||
term = obb.deribit.options.term_structure(symbol="BTC")
|
||||
```
|
||||
|
||||
When `openbb-derivatives` is installed, the five shared models are reached through the standard
|
||||
namespace instead — `obb.derivatives.options.chains(provider="deribit")`,
|
||||
`obb.derivatives.futures.curve(provider="deribit")`, and so on.
|
||||
|
||||
## Notes
|
||||
|
||||
Responses are cached on disk with a lifetime chosen per endpoint: a day for the currency and index
|
||||
directories, an hour for the instrument listings, minutes for the historical series, and not at all
|
||||
for quotes, books, and the server clock. The cache file is swept once per process and capped at
|
||||
128 MiB.
|
||||
|
||||
Perpetuals can be named either in full — `SOL_USDC-PERPETUAL` — or by their shortened root,
|
||||
`SOLUSDC`.
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Live Deribit integration tests."""
|
||||
107
openbb_platform/providers/deribit/integration/cases.py
Normal file
107
openbb_platform/providers/deribit/integration/cases.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""The Deribit commands and parameters both consumption surfaces are exercised with."""
|
||||
|
||||
SHARED = {
|
||||
("futures", "curve"),
|
||||
("futures", "historical"),
|
||||
("futures", "info"),
|
||||
("futures", "instruments"),
|
||||
("options", "chains"),
|
||||
}
|
||||
|
||||
MODELS = [
|
||||
("futures", "curve", {"symbol": "BTC"}),
|
||||
(
|
||||
"futures",
|
||||
"historical",
|
||||
{
|
||||
"symbol": "BTC-PERPETUAL",
|
||||
"start_date": "2026-01-01",
|
||||
"end_date": "2026-02-01",
|
||||
},
|
||||
),
|
||||
("futures", "info", {"symbol": "BTC-PERPETUAL"}),
|
||||
("futures", "instruments", {}),
|
||||
("index", "delivery_prices", {"index_name": "btc_usd", "limit": 5}),
|
||||
("index", "historical", {"index_name": "btc_usd", "span": "1h"}),
|
||||
("index", "price", {"index_name": "btc_usd,eth_usd"}),
|
||||
("market", "block_rfq_trades", {"limit": 10}),
|
||||
("market", "book_summary", {"currency": "BTC", "kind": "future"}),
|
||||
("market", "order_book", {"symbol": "BTC-PERPETUAL", "depth": 5}),
|
||||
("market", "settlements", {"currency": "BTC", "limit": 5}),
|
||||
("market", "ticker", {"symbol": "BTC-PERPETUAL"}),
|
||||
("market", "trade_volumes", {}),
|
||||
("market", "trades", {"symbol": "BTC-PERPETUAL", "limit": 5}),
|
||||
("options", "chains", {"symbol": "BTC"}),
|
||||
("rates", "apr_history", {"currency": "usde", "limit": 5}),
|
||||
("rates", "funding_chart", {"symbol": "BTC-PERPETUAL"}),
|
||||
("rates", "funding_history", {"symbol": "BTC-PERPETUAL"}),
|
||||
("reference", "announcements", {"limit": 3}),
|
||||
("reference", "combos", {"currency": "BTC"}),
|
||||
("reference", "currencies", {}),
|
||||
("reference", "expirations", {"currency": "BTC", "kind": "any"}),
|
||||
("reference", "instruments", {"currency": "BTC", "kind": "future"}),
|
||||
("volatility", "index", {"currency": "BTC", "interval": "1d"}),
|
||||
("volatility", "realized", {"currency": "BTC"}),
|
||||
]
|
||||
|
||||
FEEDS = [
|
||||
("futures", "curve_choices", {}),
|
||||
("futures", "perpetual_choices", {}),
|
||||
("options", "underlying_choices", {}),
|
||||
("options", "expiration_choices", {"symbol": "BTC"}),
|
||||
("options", "strike_choices", {"symbol": "BTC"}),
|
||||
("options", "strategy_choices", {"symbol": "BTC"}),
|
||||
("rates", "funding_value", {"symbol": "BTC-PERPETUAL"}),
|
||||
("reference", "combo_choices", {"currency": "BTC"}),
|
||||
("reference", "contract_size", {"symbol": "BTC-PERPETUAL"}),
|
||||
("reference", "currency_choices", {}),
|
||||
("reference", "index_choices", {}),
|
||||
("reference", "index_choices", {"supported": True}),
|
||||
("reference", "instrument_choices", {"kind": "future"}),
|
||||
("reference", "server_time", {}),
|
||||
("reference", "status", {}),
|
||||
]
|
||||
|
||||
TABLES = [
|
||||
("optimizer", {"symbol": "BTC", "target_price": 100000, "limit": 5}),
|
||||
("straddle", {"symbol": "BTC"}),
|
||||
("strangle", {"symbol": "BTC", "moneyness": 5}),
|
||||
("spreads", {"symbol": "BTC", "moneyness": 5}),
|
||||
("straddle", {"symbol": "ETH"}),
|
||||
]
|
||||
|
||||
CHARTS = [
|
||||
("smile", {"symbol": "BTC"}),
|
||||
("smile", {"symbol": "BTC", "otm": True, "skew": True}),
|
||||
("smile", {"symbol": "ETH"}),
|
||||
("stats", {"symbol": "BTC"}),
|
||||
("stats", {"symbol": "BTC", "metric": "volume", "unit": "pcr"}),
|
||||
("stats", {"symbol": "BTC", "by": "strike", "unit": "percent"}),
|
||||
("stats", {"symbol": "SOL_USDC"}),
|
||||
("surface", {"symbol": "BTC"}),
|
||||
("surface", {"symbol": "BTC", "metric": "gex", "option_type": "calls"}),
|
||||
("surface", {"symbol": "BTC", "dte_min": 7, "dte_max": 90, "oi": True}),
|
||||
("term_structure", {"symbol": "BTC"}),
|
||||
("term_structure", {"symbol": "BTC", "metric": "price", "moneyness": 5}),
|
||||
("payoff", {"symbol": "BTC"}),
|
||||
("payoff", {"symbol": "BTC", "target_price": 100000, "budget": 1000}),
|
||||
]
|
||||
|
||||
|
||||
def namespace(router: str, name: str) -> str:
|
||||
"""Return where a command is served: its own namespace or the shared one."""
|
||||
from openbb_deribit import DERIVATIVES_INSTALLED
|
||||
|
||||
return (
|
||||
"derivatives"
|
||||
if DERIVATIVES_INSTALLED and (router, name) in SHARED
|
||||
else "deribit"
|
||||
)
|
||||
|
||||
|
||||
def case_id(case: tuple) -> str:
|
||||
"""Name a parametrized case by its command and parameters."""
|
||||
*path, params = case
|
||||
written = ",".join(f"{key}={value}" for key, value in params.items())
|
||||
|
||||
return ".".join(path) + (f"[{written}]" if written else "")
|
||||
72
openbb_platform/providers/deribit/integration/conftest.py
Normal file
72
openbb_platform/providers/deribit/integration/conftest.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Serve the OpenBB REST API for the Deribit integration tests."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _free_port() -> int:
|
||||
"""Return a local port nothing is listening on."""
|
||||
import socket
|
||||
|
||||
with socket.socket() as probe:
|
||||
probe.bind(("127.0.0.1", 0))
|
||||
|
||||
return probe.getsockname()[1]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api():
|
||||
"""Run the REST API on a free local port.
|
||||
|
||||
Yields
|
||||
------
|
||||
dict
|
||||
The base URL of the API and the authorization headers to call it with.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
If the server does not start within a minute.
|
||||
"""
|
||||
import base64
|
||||
import threading
|
||||
import time
|
||||
|
||||
import uvicorn
|
||||
from openbb_core.api.rest_api import app
|
||||
from openbb_core.env import Env
|
||||
|
||||
from openbb_deribit.utils.constants import API_PREFIX
|
||||
|
||||
port = _free_port()
|
||||
server = uvicorn.Server(
|
||||
uvicorn.Config(app, host="127.0.0.1", port=port, log_level="warning")
|
||||
)
|
||||
thread = threading.Thread(target=server.run, daemon=True)
|
||||
thread.start()
|
||||
deadline = time.monotonic() + 60
|
||||
|
||||
while not server.started:
|
||||
if time.monotonic() > deadline:
|
||||
raise RuntimeError("The OpenBB REST API did not start within 60 seconds.")
|
||||
|
||||
time.sleep(0.25)
|
||||
|
||||
credentials = base64.b64encode(
|
||||
f"{Env().API_USERNAME}:{Env().API_PASSWORD}".encode("ascii")
|
||||
).decode("ascii")
|
||||
|
||||
yield {
|
||||
"base": f"http://127.0.0.1:{port}{API_PREFIX}",
|
||||
"headers": {"Authorization": f"Basic {credentials}"},
|
||||
}
|
||||
|
||||
server.should_exit = True
|
||||
thread.join(timeout=15)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def obb():
|
||||
"""Return the Python interface, built with this extension installed."""
|
||||
from openbb import obb as interface
|
||||
|
||||
return interface
|
||||
@@ -0,0 +1,143 @@
|
||||
"""Deribit through the REST API, against the live exchange."""
|
||||
|
||||
import pytest
|
||||
|
||||
from .cases import CHARTS, FEEDS, MODELS, TABLES, case_id, namespace
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
def _get(api: dict, path: str, params: dict):
|
||||
"""Call one route and return its decoded body.
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the route does not answer 200.
|
||||
"""
|
||||
import requests
|
||||
|
||||
written = {
|
||||
key: str(value).lower() if isinstance(value, bool) else value
|
||||
for key, value in params.items()
|
||||
}
|
||||
response = requests.get(
|
||||
f"{api['base']}{path}", params=written, headers=api["headers"], timeout=120
|
||||
)
|
||||
|
||||
assert response.status_code == 200, f"{path} {params}: {response.text}"
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
class TestModels:
|
||||
"""Every model-backed route returns rows."""
|
||||
|
||||
@pytest.mark.parametrize("case", MODELS, ids=case_id)
|
||||
def test_returns_rows(self, api, case):
|
||||
"""The route answers with an OBBject body holding at least one row."""
|
||||
router, name, params = case
|
||||
body = _get(
|
||||
api,
|
||||
f"/{namespace(router, name)}/{router}/{name}",
|
||||
{"provider": "deribit", **params},
|
||||
)
|
||||
|
||||
assert body["provider"] == "deribit"
|
||||
assert body["results"]
|
||||
|
||||
def test_an_unoffered_depth_is_refused(self, api):
|
||||
"""A book depth Deribit does not return is refused before the exchange."""
|
||||
import requests
|
||||
|
||||
response = requests.get(
|
||||
f"{api['base']}/deribit/market/order_book",
|
||||
params={"provider": "deribit", "symbol": "BTC-PERPETUAL", "depth": "7"},
|
||||
headers=api["headers"],
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
assert response.status_code in (400, 422)
|
||||
assert "not 7" in response.text
|
||||
|
||||
|
||||
class TestFeeds:
|
||||
"""Every choice feed and scalar route answers."""
|
||||
|
||||
@pytest.mark.parametrize("case", FEEDS, ids=case_id)
|
||||
def test_answers(self, api, case):
|
||||
"""The route returns something to offer."""
|
||||
router, name, params = case
|
||||
body = _get(api, f"/deribit/{router}/{name}", params)
|
||||
|
||||
assert body is not None
|
||||
assert body not in ([], {})
|
||||
|
||||
|
||||
class TestAnalysisTables:
|
||||
"""The options analysis routes return ranked or priced rows."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"case", TABLES, ids=lambda case: case_id(("options", *case))
|
||||
)
|
||||
def test_returns_rows(self, api, case):
|
||||
"""The route answers with rows."""
|
||||
name, params = case
|
||||
body = _get(api, f"/deribit/options/{name}", params)
|
||||
|
||||
assert body["results"]
|
||||
|
||||
def test_the_optimizer_codes_every_row(self, api):
|
||||
"""Each ranked position names the Deribit combo it is."""
|
||||
body = _get(
|
||||
api, "/deribit/options/optimizer", {"symbol": "BTC", "target_price": 100000}
|
||||
)
|
||||
|
||||
assert all(row["code"] for row in body["results"])
|
||||
|
||||
|
||||
class TestCharts:
|
||||
"""Every chart route draws, and hands back its rows on request."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"case", CHARTS, ids=lambda case: case_id(("options", *case))
|
||||
)
|
||||
def test_draws_a_figure(self, api, case):
|
||||
"""The route answers with a Plotly figure and its display config."""
|
||||
name, params = case
|
||||
figure = _get(api, f"/deribit/options/{name}", params)
|
||||
|
||||
assert figure["data"]
|
||||
assert figure["layout"]["title"]["text"]
|
||||
assert figure["config"]["responsive"] is True
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name", sorted({name for name, _params in CHARTS}), ids=str
|
||||
)
|
||||
def test_raw_returns_rows(self, api, name):
|
||||
"""With raw set, the route answers with the rows behind the chart."""
|
||||
rows = _get(api, f"/deribit/options/{name}", {"symbol": "BTC", "raw": True})
|
||||
|
||||
assert isinstance(rows, list)
|
||||
assert rows
|
||||
|
||||
def test_the_payoff_draws_a_ranked_position(self, api):
|
||||
"""The legs the optimizer prints draw back as that position."""
|
||||
view = {"symbol": "BTC", "target_price": 100000}
|
||||
row = _get(api, "/deribit/options/optimizer", view)["results"][-1]
|
||||
figure = _get(api, "/deribit/options/payoff", {**view, "legs": row["legs"]})
|
||||
|
||||
assert row["strategy"] in figure["layout"]["title"]["text"]
|
||||
|
||||
def test_an_unlisted_underlying_is_a_404(self, api):
|
||||
"""An underlying Deribit lists no options on answers 404."""
|
||||
import requests
|
||||
|
||||
response = requests.get(
|
||||
f"{api['base']}/deribit/options/smile",
|
||||
params={"symbol": "NOPE"},
|
||||
headers=api["headers"],
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -0,0 +1,148 @@
|
||||
"""Deribit through the Python interface, against the live exchange."""
|
||||
|
||||
import pytest
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
|
||||
from .cases import CHARTS, FEEDS, MODELS, TABLES, case_id, namespace
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
def _command(obb, router: str, name: str):
|
||||
"""Return the Python command for a route, wherever it is served."""
|
||||
return getattr(getattr(getattr(obb, namespace(router, name)), router), name)
|
||||
|
||||
|
||||
def _field(row, name: str):
|
||||
"""Read one field of a result row, whether a model or a mapping."""
|
||||
return row[name] if isinstance(row, dict) else getattr(row, name)
|
||||
|
||||
|
||||
class TestModels:
|
||||
"""Every model-backed command returns rows."""
|
||||
|
||||
@pytest.mark.parametrize("case", MODELS, ids=case_id)
|
||||
def test_returns_rows(self, obb, case):
|
||||
"""The command answers with an OBBject holding at least one row."""
|
||||
router, name, params = case
|
||||
result = _command(obb, router, name)(provider="deribit", **params)
|
||||
|
||||
assert isinstance(result, OBBject)
|
||||
assert result.provider == "deribit"
|
||||
assert result.results
|
||||
|
||||
def test_a_result_converts_to_a_frame(self, obb):
|
||||
"""The rows convert to a DataFrame for Python users."""
|
||||
frame = _command(obb, "market", "ticker")(
|
||||
symbol="BTC-PERPETUAL", provider="deribit"
|
||||
).to_df()
|
||||
|
||||
assert not frame.empty
|
||||
|
||||
def test_an_unoffered_depth_is_refused(self, obb):
|
||||
"""A book depth Deribit does not return is refused before the exchange."""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
with pytest.raises(OpenBBError, match="not 7"):
|
||||
obb.deribit.market.order_book(
|
||||
symbol="BTC-PERPETUAL", depth=7, provider="deribit"
|
||||
)
|
||||
|
||||
|
||||
class TestFeeds:
|
||||
"""Every choice feed and scalar answers."""
|
||||
|
||||
@pytest.mark.parametrize("case", FEEDS, ids=case_id)
|
||||
def test_answers(self, obb, case):
|
||||
"""The feed returns something to offer."""
|
||||
router, name, params = case
|
||||
answer = _command(obb, router, name)(**params)
|
||||
|
||||
assert answer is not None
|
||||
assert answer not in ([], {})
|
||||
|
||||
def test_status_reports_locked_currencies(self, obb):
|
||||
"""The platform status names what is locked."""
|
||||
assert "locked" in obb.deribit.reference.status()
|
||||
|
||||
|
||||
class TestAnalysisTables:
|
||||
"""The options analysis tables return ranked or priced rows."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"case", TABLES, ids=lambda case: case_id(("options", *case))
|
||||
)
|
||||
def test_returns_rows(self, obb, case):
|
||||
"""The table answers with rows."""
|
||||
name, params = case
|
||||
result = getattr(obb.deribit.options, name)(**params)
|
||||
|
||||
assert isinstance(result, OBBject)
|
||||
assert result.results
|
||||
|
||||
def test_the_optimizer_codes_every_row(self, obb):
|
||||
"""Each ranked position names the Deribit combo it is."""
|
||||
result = obb.deribit.options.optimizer(symbol="BTC", target_price=100000)
|
||||
codes = [_field(row, "code") for row in result.results]
|
||||
|
||||
assert all(codes)
|
||||
assert len(set(codes)) > 1
|
||||
|
||||
|
||||
class TestCharts:
|
||||
"""Every chart draws, and every chart hands back its rows on request."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"case", CHARTS, ids=lambda case: case_id(("options", *case))
|
||||
)
|
||||
def test_draws_a_figure(self, obb, case):
|
||||
"""The chart answers with a Plotly figure."""
|
||||
name, params = case
|
||||
figure = getattr(obb.deribit.options, name)(**params)
|
||||
|
||||
assert isinstance(figure, dict)
|
||||
assert figure["data"]
|
||||
assert figure["layout"]["title"]["text"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name", sorted({name for name, _params in CHARTS}), ids=str
|
||||
)
|
||||
def test_raw_returns_rows(self, obb, name):
|
||||
"""With raw set, the chart answers with the rows behind it."""
|
||||
rows = getattr(obb.deribit.options, name)(symbol="BTC", raw=True)
|
||||
|
||||
assert isinstance(rows, list)
|
||||
assert rows
|
||||
|
||||
def test_the_smile_draws_chosen_expirations(self, obb):
|
||||
"""Expirations picked from the feed are drawn as calls and puts each."""
|
||||
offered = obb.deribit.options.expiration_choices(symbol="BTC")
|
||||
chosen = [item["value"] for item in offered[2:4]]
|
||||
figure = obb.deribit.options.smile(symbol="BTC", expirations=",".join(chosen))
|
||||
|
||||
assert len(figure["data"]) == 2 * len(chosen)
|
||||
|
||||
def test_stats_for_one_expiration(self, obb):
|
||||
"""An expiration picked from the feed is drawn by strike."""
|
||||
offered = obb.deribit.options.expiration_choices(symbol="BTC")
|
||||
figure = obb.deribit.options.stats(symbol="BTC", date=offered[1]["value"])
|
||||
|
||||
assert "By Strike" in figure["layout"]["title"]["text"]
|
||||
|
||||
def test_term_structure_at_a_chosen_strike(self, obb):
|
||||
"""A strike picked from the feed titles the term structure."""
|
||||
offered = obb.deribit.options.strike_choices(symbol="BTC")
|
||||
strike = offered[len(offered) // 2]["value"]
|
||||
figure = obb.deribit.options.term_structure(symbol="BTC", strike=strike)
|
||||
|
||||
assert f"${strike}" in figure["layout"]["title"]["text"]
|
||||
|
||||
def test_the_payoff_draws_a_ranked_position(self, obb):
|
||||
"""The legs the optimizer prints draw back as that position."""
|
||||
ranked = obb.deribit.options.optimizer(symbol="BTC", target_price=100000)
|
||||
row = ranked.results[-1]
|
||||
figure = obb.deribit.options.payoff(
|
||||
symbol="BTC", target_price=100000, legs=_field(row, "legs")
|
||||
)
|
||||
|
||||
assert _field(row, "strategy") in figure["layout"]["title"]["text"]
|
||||
@@ -1,24 +1,94 @@
|
||||
"""OpenBB Deribit Provider Module."""
|
||||
|
||||
from importlib.util import find_spec
|
||||
|
||||
from openbb_core.provider.abstract.provider import Provider
|
||||
|
||||
from openbb_deribit.models.announcements import DeribitAnnouncementsFetcher
|
||||
from openbb_deribit.models.apr_history import DeribitAprHistoryFetcher
|
||||
from openbb_deribit.models.block_rfq_trades import DeribitBlockRfqTradesFetcher
|
||||
from openbb_deribit.models.book_summary import DeribitBookSummaryFetcher
|
||||
from openbb_deribit.models.combos import DeribitCombosFetcher
|
||||
from openbb_deribit.models.currencies import DeribitCurrenciesFetcher
|
||||
from openbb_deribit.models.delivery_prices import DeribitDeliveryPricesFetcher
|
||||
from openbb_deribit.models.expirations import DeribitExpirationsFetcher
|
||||
from openbb_deribit.models.funding_chart import DeribitFundingChartFetcher
|
||||
from openbb_deribit.models.funding_rate_history import (
|
||||
DeribitFundingRateHistoryFetcher,
|
||||
)
|
||||
from openbb_deribit.models.futures_curve import DeribitFuturesCurveFetcher
|
||||
from openbb_deribit.models.futures_historical import DeribitFuturesHistoricalFetcher
|
||||
from openbb_deribit.models.futures_info import DeribitFuturesInfoFetcher
|
||||
from openbb_deribit.models.futures_instruments import DeribitFuturesInstrumentsFetcher
|
||||
from openbb_deribit.models.historical_volatility import (
|
||||
DeribitHistoricalVolatilityFetcher,
|
||||
)
|
||||
from openbb_deribit.models.index_historical import DeribitIndexHistoricalFetcher
|
||||
from openbb_deribit.models.index_price import DeribitIndexPriceFetcher
|
||||
from openbb_deribit.models.instruments import DeribitInstrumentsFetcher
|
||||
from openbb_deribit.models.options_chains import DeribitOptionsChainsFetcher
|
||||
from openbb_deribit.models.order_book import DeribitOrderBookFetcher
|
||||
from openbb_deribit.models.settlements import DeribitSettlementsFetcher
|
||||
from openbb_deribit.models.ticker import DeribitTickerFetcher
|
||||
from openbb_deribit.models.trade_volumes import DeribitTradeVolumesFetcher
|
||||
from openbb_deribit.models.trades import DeribitTradesFetcher
|
||||
from openbb_deribit.models.volatility_index import DeribitVolatilityIndexFetcher
|
||||
|
||||
DERIVATIVES_INSTALLED = find_spec("openbb_derivatives") is not None
|
||||
CHARTING_INSTALLED = find_spec("openbb_charting") is not None
|
||||
|
||||
|
||||
def _key(standard: str, alias: str, installed: bool) -> str:
|
||||
"""Return the standard key when the extension owning it is installed, else the alias."""
|
||||
return standard if installed else alias
|
||||
|
||||
|
||||
_fetcher_dict: dict = {
|
||||
"DeribitAnnouncements": DeribitAnnouncementsFetcher,
|
||||
"DeribitAprHistory": DeribitAprHistoryFetcher,
|
||||
"DeribitBlockRfqTrades": DeribitBlockRfqTradesFetcher,
|
||||
"DeribitBookSummary": DeribitBookSummaryFetcher,
|
||||
"DeribitCombos": DeribitCombosFetcher,
|
||||
"DeribitCurrencies": DeribitCurrenciesFetcher,
|
||||
"DeribitDeliveryPrices": DeribitDeliveryPricesFetcher,
|
||||
"DeribitExpirations": DeribitExpirationsFetcher,
|
||||
"DeribitFundingChart": DeribitFundingChartFetcher,
|
||||
"DeribitFundingRateHistory": DeribitFundingRateHistoryFetcher,
|
||||
"DeribitHistoricalVolatility": DeribitHistoricalVolatilityFetcher,
|
||||
"DeribitIndexHistorical": DeribitIndexHistoricalFetcher,
|
||||
"DeribitIndexPrice": DeribitIndexPriceFetcher,
|
||||
"DeribitInstruments": DeribitInstrumentsFetcher,
|
||||
"DeribitOrderBook": DeribitOrderBookFetcher,
|
||||
"DeribitSettlements": DeribitSettlementsFetcher,
|
||||
"DeribitTicker": DeribitTickerFetcher,
|
||||
"DeribitTradeVolumes": DeribitTradeVolumesFetcher,
|
||||
"DeribitTrades": DeribitTradesFetcher,
|
||||
"DeribitVolatilityIndex": DeribitVolatilityIndexFetcher,
|
||||
_key("FuturesCurve", "DeribitFuturesCurve", DERIVATIVES_INSTALLED): (
|
||||
DeribitFuturesCurveFetcher
|
||||
),
|
||||
_key("FuturesHistorical", "DeribitFuturesHistorical", DERIVATIVES_INSTALLED): (
|
||||
DeribitFuturesHistoricalFetcher
|
||||
),
|
||||
_key("FuturesInfo", "DeribitFuturesInfo", DERIVATIVES_INSTALLED): (
|
||||
DeribitFuturesInfoFetcher
|
||||
),
|
||||
_key("FuturesInstruments", "DeribitFuturesInstruments", DERIVATIVES_INSTALLED): (
|
||||
DeribitFuturesInstrumentsFetcher
|
||||
),
|
||||
_key("OptionsChains", "DeribitOptionsChains", DERIVATIVES_INSTALLED): (
|
||||
DeribitOptionsChainsFetcher
|
||||
),
|
||||
}
|
||||
|
||||
deribit_provider = Provider(
|
||||
name="deribit",
|
||||
website="https://deribit.com/",
|
||||
description="""Unofficial Python client for public data published by Deribit.""",
|
||||
description="""Deribit is a crypto derivatives exchange listing futures, perpetuals,
|
||||
options, and spot pairs. This extension covers its public market data API, which
|
||||
needs no credentials and carries no trading.""",
|
||||
credentials=None,
|
||||
fetcher_dict={
|
||||
"FuturesCurve": DeribitFuturesCurveFetcher,
|
||||
"FuturesHistorical": DeribitFuturesHistoricalFetcher,
|
||||
"FuturesInfo": DeribitFuturesInfoFetcher,
|
||||
"FuturesInstruments": DeribitFuturesInstrumentsFetcher,
|
||||
"OptionsChains": DeribitOptionsChainsFetcher,
|
||||
},
|
||||
fetcher_dict=_fetcher_dict,
|
||||
repr_name="Deribit Public Data",
|
||||
instructions="This provider does not require any credentials and is not meant for trading.",
|
||||
instructions="This provider needs no credentials and is not meant for trading.",
|
||||
)
|
||||
|
||||
586
openbb_platform/providers/deribit/openbb_deribit/assets/apps.json
vendored
Normal file
586
openbb_platform/providers/deribit/openbb_deribit/assets/apps.json
vendored
Normal file
@@ -0,0 +1,586 @@
|
||||
[
|
||||
{
|
||||
"name": "Deribit",
|
||||
"img": "https://www.deribit.com//share-image.png",
|
||||
"img_dark": "",
|
||||
"img_light": "",
|
||||
"description": "Order books, options chains, funding, and volatility indexes from Deribit's public market data API.",
|
||||
"allowCustomization": true,
|
||||
"groups": [
|
||||
{
|
||||
"name": "Instrument",
|
||||
"type": "endpointParam",
|
||||
"paramName": "symbol",
|
||||
"defaultValue": "BTC-PERPETUAL",
|
||||
"widgetIds": [
|
||||
"deribit_market_ticker_deribit_obb",
|
||||
"deribit_market_order_book_deribit_obb",
|
||||
"deribit_market_trades_deribit_obb",
|
||||
"deribit_market_book_summary_deribit_obb",
|
||||
"deribit_market_settlements_deribit_obb",
|
||||
"derivatives_futures_historical_deribit_obb",
|
||||
"derivatives_futures_info_deribit_obb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Index",
|
||||
"type": "endpointParam",
|
||||
"paramName": "index_name",
|
||||
"defaultValue": "btc_usd",
|
||||
"widgetIds": [
|
||||
"deribit_index_price_deribit_obb",
|
||||
"deribit_index_historical_deribit_obb",
|
||||
"deribit_index_delivery_prices_deribit_obb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Settlement Currency",
|
||||
"type": "param",
|
||||
"paramName": "currency",
|
||||
"defaultValue": "BTC",
|
||||
"widgetIds": [
|
||||
"deribit_reference_expirations_deribit_obb",
|
||||
"deribit_reference_combos_deribit_obb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Volatility Asset",
|
||||
"type": "param",
|
||||
"paramName": "currency",
|
||||
"defaultValue": "BTC",
|
||||
"widgetIds": [
|
||||
"deribit_volatility_realized_deribit_obb",
|
||||
"deribit_volatility_index_deribit_obb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Underlying",
|
||||
"type": "endpointParam",
|
||||
"paramName": "symbol",
|
||||
"defaultValue": "BTC",
|
||||
"widgetIds": [
|
||||
"derivatives_options_chains_deribit_obb",
|
||||
"deribit_options_optimizer_custom_obb",
|
||||
"deribit_options_payoff_custom_obb",
|
||||
"deribit_options_smile_custom_obb",
|
||||
"deribit_options_term_structure_custom_obb",
|
||||
"deribit_options_surface_custom_obb",
|
||||
"deribit_options_stats_custom_obb",
|
||||
"deribit_options_straddle_custom_obb",
|
||||
"deribit_options_strangle_custom_obb",
|
||||
"deribit_options_spreads_custom_obb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Strategy",
|
||||
"type": "endpointParam",
|
||||
"paramName": "legs",
|
||||
"widgetIds": [
|
||||
"deribit_options_optimizer_custom_obb",
|
||||
"deribit_options_payoff_custom_obb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tabs": {
|
||||
"markets": {
|
||||
"id": "markets",
|
||||
"name": "Markets",
|
||||
"layout": [
|
||||
{
|
||||
"i": "deribit_market_ticker_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 8,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC-PERPETUAL"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Instrument"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_market_order_book_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"w": 20,
|
||||
"h": 16,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC-PERPETUAL",
|
||||
"depth": "20"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Instrument"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_market_trades_deribit_obb",
|
||||
"x": 20,
|
||||
"y": 8,
|
||||
"w": 20,
|
||||
"h": 16,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC-PERPETUAL",
|
||||
"limit": 100
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Instrument"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_market_book_summary_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 24,
|
||||
"w": 40,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"currency": "BTC",
|
||||
"kind": "future"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Instrument"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_market_trade_volumes_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 39,
|
||||
"w": 40,
|
||||
"h": 12,
|
||||
"state": {
|
||||
"params": {
|
||||
"extended": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"futures": {
|
||||
"id": "futures",
|
||||
"name": "Futures",
|
||||
"layout": [
|
||||
{
|
||||
"i": "derivatives_futures_curve_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "derivatives_futures_historical_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 15,
|
||||
"w": 40,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC-PERPETUAL",
|
||||
"interval": "1d"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Instrument"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "derivatives_futures_info_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 30,
|
||||
"w": 40,
|
||||
"h": 8,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC-PERPETUAL"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Instrument"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_rates_funding_history_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 38,
|
||||
"w": 40,
|
||||
"h": 14,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC-PERPETUAL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "derivatives_futures_instruments_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 52,
|
||||
"w": 40,
|
||||
"h": 15
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"id": "options",
|
||||
"name": "Options",
|
||||
"layout": [
|
||||
{
|
||||
"i": "derivatives_options_chains_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 20,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_reference_expirations_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 20,
|
||||
"w": 20,
|
||||
"h": 14,
|
||||
"state": {
|
||||
"params": {
|
||||
"currency": "BTC",
|
||||
"kind": "any"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Settlement Currency"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_reference_combos_deribit_obb",
|
||||
"x": 20,
|
||||
"y": 20,
|
||||
"w": 20,
|
||||
"h": 14,
|
||||
"state": {
|
||||
"params": {
|
||||
"currency": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Settlement Currency"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_market_block_rfq_trades_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 34,
|
||||
"w": 40,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"limit": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"analysis": {
|
||||
"id": "analysis",
|
||||
"name": "Analysis",
|
||||
"layout": [
|
||||
{
|
||||
"i": "deribit_options_optimizer_custom_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 40,
|
||||
"h": 18,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying",
|
||||
"Strategy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_options_payoff_custom_obb",
|
||||
"x": 0,
|
||||
"y": 18,
|
||||
"w": 40,
|
||||
"h": 18,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying",
|
||||
"Strategy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_options_smile_custom_obb",
|
||||
"x": 0,
|
||||
"y": 36,
|
||||
"w": 40,
|
||||
"h": 14,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_options_term_structure_custom_obb",
|
||||
"x": 0,
|
||||
"y": 50,
|
||||
"w": 40,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_options_surface_custom_obb",
|
||||
"x": 0,
|
||||
"y": 65,
|
||||
"w": 40,
|
||||
"h": 20,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_options_stats_custom_obb",
|
||||
"x": 0,
|
||||
"y": 72,
|
||||
"w": 40,
|
||||
"h": 16,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_options_straddle_custom_obb",
|
||||
"x": 0,
|
||||
"y": 88,
|
||||
"w": 20,
|
||||
"h": 16,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_options_strangle_custom_obb",
|
||||
"x": 20,
|
||||
"y": 88,
|
||||
"w": 20,
|
||||
"h": 16,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_options_spreads_custom_obb",
|
||||
"x": 0,
|
||||
"y": 104,
|
||||
"w": 40,
|
||||
"h": 16,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Underlying"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"volatility": {
|
||||
"id": "volatility",
|
||||
"name": "Volatility",
|
||||
"layout": [
|
||||
{
|
||||
"i": "deribit_volatility_index_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 20,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"currency": "BTC",
|
||||
"interval": "1d"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Volatility Asset"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_volatility_realized_deribit_obb",
|
||||
"x": 20,
|
||||
"y": 0,
|
||||
"w": 20,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"currency": "BTC"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Volatility Asset"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"reference": {
|
||||
"id": "reference",
|
||||
"name": "Reference",
|
||||
"layout": [
|
||||
{
|
||||
"i": "deribit_index_price_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 20,
|
||||
"h": 8,
|
||||
"state": {
|
||||
"params": {
|
||||
"index_name": "btc_usd"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_index_historical_deribit_obb",
|
||||
"x": 20,
|
||||
"y": 0,
|
||||
"w": 20,
|
||||
"h": 8,
|
||||
"state": {
|
||||
"params": {
|
||||
"index_name": "btc_usd",
|
||||
"span": "1d"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_index_delivery_prices_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"w": 40,
|
||||
"h": 14,
|
||||
"state": {
|
||||
"params": {
|
||||
"index_name": "btc_usd"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_market_settlements_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 22,
|
||||
"w": 40,
|
||||
"h": 14,
|
||||
"state": {
|
||||
"params": {
|
||||
"symbol": "BTC-PERPETUAL"
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"Instrument"
|
||||
]
|
||||
},
|
||||
{
|
||||
"i": "deribit_rates_apr_history_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 36,
|
||||
"w": 40,
|
||||
"h": 14,
|
||||
"state": {
|
||||
"params": {
|
||||
"currency": "usde"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "deribit_reference_currencies_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 50,
|
||||
"w": 40,
|
||||
"h": 12
|
||||
},
|
||||
{
|
||||
"i": "deribit_reference_instruments_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 62,
|
||||
"w": 40,
|
||||
"h": 15,
|
||||
"state": {
|
||||
"params": {
|
||||
"currency": "any",
|
||||
"kind": "future"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"i": "deribit_reference_announcements_deribit_obb",
|
||||
"x": 0,
|
||||
"y": 77,
|
||||
"w": 40,
|
||||
"h": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Deribit Router."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from openbb_core.app.router import Router
|
||||
|
||||
from openbb_deribit import DERIVATIVES_INSTALLED
|
||||
from openbb_deribit.routers.futures import router as futures_router
|
||||
from openbb_deribit.routers.index import router as index_router
|
||||
from openbb_deribit.routers.market import router as market_router
|
||||
from openbb_deribit.routers.options import router as options_router
|
||||
from openbb_deribit.routers.rates import router as rates_router
|
||||
from openbb_deribit.routers.reference import router as reference_router
|
||||
from openbb_deribit.routers.volatility import router as volatility_router
|
||||
|
||||
router = Router(prefix="", description="Deribit provider router.")
|
||||
|
||||
for _subrouter in (
|
||||
reference_router,
|
||||
market_router,
|
||||
index_router,
|
||||
rates_router,
|
||||
volatility_router,
|
||||
futures_router,
|
||||
options_router,
|
||||
):
|
||||
router.include_router(_subrouter)
|
||||
|
||||
_APPS_JSON = Path(__file__).parent / "assets" / "apps.json"
|
||||
|
||||
_STANDALONE_WIDGET_IDS: dict[str, tuple[str, bool]] = {
|
||||
"derivatives_futures_curve_deribit_obb": (
|
||||
"deribit_futures_curve_deribit_obb",
|
||||
DERIVATIVES_INSTALLED,
|
||||
),
|
||||
"derivatives_futures_historical_deribit_obb": (
|
||||
"deribit_futures_historical_deribit_obb",
|
||||
DERIVATIVES_INSTALLED,
|
||||
),
|
||||
"derivatives_futures_info_deribit_obb": (
|
||||
"deribit_futures_info_deribit_obb",
|
||||
DERIVATIVES_INSTALLED,
|
||||
),
|
||||
"derivatives_futures_instruments_deribit_obb": (
|
||||
"deribit_futures_instruments_deribit_obb",
|
||||
DERIVATIVES_INSTALLED,
|
||||
),
|
||||
"derivatives_options_chains_deribit_obb": (
|
||||
"deribit_options_chains_deribit_obb",
|
||||
DERIVATIVES_INSTALLED,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _resolve_widget_id(widget_id: str) -> str:
|
||||
"""Return the widget ID that resolves against the active install."""
|
||||
standalone, installed = _STANDALONE_WIDGET_IDS.get(widget_id, (widget_id, True))
|
||||
|
||||
return widget_id if installed else standalone
|
||||
|
||||
|
||||
@router.api_router.get("/apps.json", include_in_schema=False)
|
||||
async def deribit_apps() -> list:
|
||||
"""Serve the bundled Deribit dashboard template (``assets/apps.json``).
|
||||
|
||||
A parameter group names the widgets it binds, so its identifiers are
|
||||
resolved against the active install the same way the layout's are.
|
||||
"""
|
||||
apps = json.loads(_APPS_JSON.read_text(encoding="utf-8"))
|
||||
|
||||
for app in apps:
|
||||
for tab in app.get("tabs", {}).values():
|
||||
for widget in tab.get("layout", []):
|
||||
widget["i"] = _resolve_widget_id(widget["i"])
|
||||
|
||||
for group in app.get("groups", []):
|
||||
group["widgetIds"] = [
|
||||
_resolve_widget_id(widget_id)
|
||||
for widget_id in group.get("widgetIds", [])
|
||||
]
|
||||
|
||||
return apps
|
||||
@@ -0,0 +1,145 @@
|
||||
"""Deribit Announcements Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class DeribitAnnouncementsQueryParams(QueryParams):
|
||||
"""Deribit Announcements Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/account-management/public-get_announcements
|
||||
"""
|
||||
|
||||
start_date: Any | None = Field(
|
||||
default=None,
|
||||
description="Return announcements published on or after this date.",
|
||||
)
|
||||
limit: int = Field(
|
||||
default=50, description="The number of announcements to return.", ge=1
|
||||
)
|
||||
|
||||
|
||||
class DeribitAnnouncementsData(Data):
|
||||
"""Deribit Announcements Data."""
|
||||
|
||||
__alias_dict__ = {"date": "publication_timestamp"}
|
||||
|
||||
date: datetime = Field(
|
||||
description="When the announcement was published.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
title: str = Field(
|
||||
description="The title of the announcement.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
body: str = Field(
|
||||
description="The body of the announcement, as HTML.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "cellDataType": "text"}
|
||||
},
|
||||
)
|
||||
important: bool | None = Field(
|
||||
default=None,
|
||||
description="Whether the exchange flagged the announcement.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"chartDataType": "excluded",
|
||||
"cellDataType": "boolean",
|
||||
}
|
||||
},
|
||||
)
|
||||
confirmation: bool | None = Field(
|
||||
default=None,
|
||||
description="Whether the announcement needs acknowledging.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"chartDataType": "excluded",
|
||||
"cellDataType": "boolean",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
id: int | None = Field(
|
||||
default=None,
|
||||
description="The identifier of the announcement.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_date(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
|
||||
class DeribitAnnouncementsFetcher(
|
||||
Fetcher[DeribitAnnouncementsQueryParams, list[DeribitAnnouncementsData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitAnnouncementsQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitAnnouncementsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitAnnouncementsQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange published no announcements over the span.
|
||||
"""
|
||||
from openbb_deribit.utils.client import request
|
||||
from openbb_deribit.utils.helpers import to_timestamp
|
||||
|
||||
data = await request(
|
||||
"get_announcements",
|
||||
{
|
||||
"start_timestamp": (
|
||||
to_timestamp(query.start_date) if query.start_date else None
|
||||
),
|
||||
"count": query.limit,
|
||||
},
|
||||
)
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit published no announcements over the span.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitAnnouncementsQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitAnnouncementsData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitAnnouncementsData.model_validate(record)
|
||||
for record in sorted(
|
||||
data, key=lambda d: d.get("publication_timestamp") or 0, reverse=True
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Deribit APR History Model."""
|
||||
|
||||
from datetime import date as dateType
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import AprCurrencies
|
||||
|
||||
|
||||
class DeribitAprHistoryQueryParams(QueryParams):
|
||||
"""Deribit APR History Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_apr_history
|
||||
"""
|
||||
|
||||
currency: AprCurrencies = Field(
|
||||
default="usde", description="The yield-bearing currency."
|
||||
)
|
||||
limit: int = Field(default=365, description="The number of days to return.", ge=1)
|
||||
|
||||
|
||||
class DeribitAprHistoryData(Data):
|
||||
"""Deribit APR History Data."""
|
||||
|
||||
__alias_dict__ = {"date": "day"}
|
||||
|
||||
date: dateType = Field(
|
||||
description="The day the rate applied to.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
currency: str = Field(
|
||||
description="The currency earning the rate.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
apr: float = Field(
|
||||
description="The annual percentage rate the currency earned.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {
|
||||
"headerName": "APR",
|
||||
"chartDataType": "series",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_date(cls, v):
|
||||
"""Read the day number as a date."""
|
||||
from openbb_deribit.utils.helpers import from_day_number
|
||||
|
||||
return from_day_number(v)
|
||||
|
||||
|
||||
class DeribitAprHistoryFetcher(
|
||||
Fetcher[DeribitAprHistoryQueryParams, list[DeribitAprHistoryData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitAprHistoryQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitAprHistoryQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitAprHistoryQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the currency earned nothing over the span.
|
||||
"""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
result = await request(
|
||||
"get_apr_history",
|
||||
{"currency": query.currency.lower(), "limit": query.limit},
|
||||
)
|
||||
data = [
|
||||
{"currency": query.currency.upper(), **record}
|
||||
for record in (result or {}).get("data") or []
|
||||
]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no yield history for {query.currency.upper()}."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitAprHistoryQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitAprHistoryData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitAprHistoryData.model_validate(record)
|
||||
for record in sorted(data, key=lambda d: d.get("day") or 0)
|
||||
]
|
||||
@@ -0,0 +1,212 @@
|
||||
"""Deribit Block RFQ Trades Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
BLOCK_RFQ_MAX_COUNT,
|
||||
BLOCK_RFQ_MIN_COUNT,
|
||||
AnyListingCurrencies,
|
||||
)
|
||||
|
||||
|
||||
class DeribitBlockRfqTradesQueryParams(QueryParams):
|
||||
"""Deribit Block RFQ Trades Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/block-rfq/public-get_block_rfq_trades
|
||||
"""
|
||||
|
||||
currency: AnyListingCurrencies = Field(
|
||||
default="any", description="The settlement currency of the quoted legs."
|
||||
)
|
||||
limit: int = Field(
|
||||
default=BLOCK_RFQ_MAX_COUNT,
|
||||
description=f"The number of requests to return, between"
|
||||
f" {BLOCK_RFQ_MIN_COUNT} and {BLOCK_RFQ_MAX_COUNT}.",
|
||||
ge=BLOCK_RFQ_MIN_COUNT,
|
||||
le=BLOCK_RFQ_MAX_COUNT,
|
||||
)
|
||||
|
||||
|
||||
class DeribitBlockRfqTradesData(Data):
|
||||
"""Deribit Block RFQ Trades Data."""
|
||||
|
||||
timestamp: datetime = Field(
|
||||
description="When the request traded.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "category", "pinned": "left"}
|
||||
},
|
||||
)
|
||||
rfq_id: int | None = Field(
|
||||
default=None,
|
||||
description="The identifier of the request.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "RFQ ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
combo_id: str | None = Field(
|
||||
default=None,
|
||||
description="The combo the request was quoted as.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Combo ID",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
direction: str | None = Field(
|
||||
default=None,
|
||||
description="Which side the taker was on.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"headerName": "Side", "chartDataType": "excluded"}
|
||||
},
|
||||
)
|
||||
amount: float | None = Field(
|
||||
default=None,
|
||||
description="The size of the request.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
mark_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the exchange marked the structure at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
leg: str | None = Field(
|
||||
default=None,
|
||||
description="The instrument the leg trades.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
leg_direction: str | None = Field(
|
||||
default=None,
|
||||
description="Which side the leg was traded on.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
leg_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the leg traded at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
leg_ratio: float | None = Field(
|
||||
default=None,
|
||||
description="How many of the leg one structure holds.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
index_prices: dict | None = Field(
|
||||
default=None,
|
||||
description="The index prices the structure was marked against.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("timestamp", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
|
||||
class DeribitBlockRfqTradesFetcher(
|
||||
Fetcher[DeribitBlockRfqTradesQueryParams, list[DeribitBlockRfqTradesData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitBlockRfqTradesQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitBlockRfqTradesQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitBlockRfqTradesQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If no block request traded.
|
||||
"""
|
||||
from openbb_deribit.utils.client import request
|
||||
from openbb_deribit.utils.helpers import normalize_currency
|
||||
|
||||
result = await request(
|
||||
"get_block_rfq_trades",
|
||||
{
|
||||
"currency": normalize_currency(query.currency),
|
||||
"count": query.limit,
|
||||
},
|
||||
use_cache=False,
|
||||
)
|
||||
data = (result or {}).get("block_rfqs") or []
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit reports no block requests traded.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitBlockRfqTradesQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitBlockRfqTradesData]:
|
||||
"""Transform the data to the model."""
|
||||
records: list[dict] = []
|
||||
|
||||
for rfq in data:
|
||||
context = {
|
||||
"timestamp": rfq.get("timestamp"),
|
||||
"rfq_id": rfq.get("id"),
|
||||
"combo_id": rfq.get("combo_id"),
|
||||
"direction": rfq.get("direction"),
|
||||
"amount": rfq.get("amount"),
|
||||
"mark_price": rfq.get("mark_price"),
|
||||
"index_prices": rfq.get("index_prices"),
|
||||
}
|
||||
|
||||
for leg in rfq.get("legs") or [{}]:
|
||||
records.append(
|
||||
{
|
||||
**context,
|
||||
"leg": leg.get("instrument_name"),
|
||||
"leg_direction": leg.get("direction"),
|
||||
"leg_price": leg.get("price"),
|
||||
"leg_ratio": leg.get("ratio"),
|
||||
}
|
||||
)
|
||||
|
||||
return [
|
||||
DeribitBlockRfqTradesData.model_validate(record)
|
||||
for record in sorted(
|
||||
records,
|
||||
key=lambda d: (d["timestamp"] or 0, d["rfq_id"] or 0),
|
||||
reverse=True,
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,334 @@
|
||||
"""Deribit Book Summary Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INSTRUMENT_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
InstrumentKinds,
|
||||
ListingCurrencies,
|
||||
)
|
||||
|
||||
|
||||
class DeribitBookSummaryQueryParams(QueryParams):
|
||||
"""Deribit Book Summary Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_book_summary_by_currency
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INSTRUMENT_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
symbol: str | None = Field(
|
||||
default=None,
|
||||
description="One or more instrument names. When given, the currency and"
|
||||
+ " kind are ignored.",
|
||||
)
|
||||
currency: ListingCurrencies = Field(
|
||||
default="BTC", description="The settlement currency of the instruments."
|
||||
)
|
||||
kind: InstrumentKinds | None = Field(
|
||||
default=None, description="The kind of instrument. Default is all of them."
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _options_are_listed_by_underlying(self):
|
||||
"""Refuse a listing of options scoped to a currency that only settles them."""
|
||||
from openbb_deribit.utils.helpers import reject_options_by_settlement
|
||||
|
||||
if not self.symbol:
|
||||
reject_options_by_settlement(self.currency, self.kind)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class DeribitBookSummaryData(Data):
|
||||
"""Deribit Book Summary Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"symbol": "instrument_name",
|
||||
"last_price": "last",
|
||||
"bid": "bid_price",
|
||||
"ask": "ask_price",
|
||||
"mid": "mid_price",
|
||||
"change_percent": "price_change",
|
||||
"implied_volatility": "mark_iv",
|
||||
"timestamp": "creation_timestamp",
|
||||
}
|
||||
|
||||
symbol: str = Field(
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="When the summary was published.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
base_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The currency the instrument is based on.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
quote_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The currency the instrument is quoted in.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
last_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the instrument last traded at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
bid: float | None = Field(
|
||||
default=None,
|
||||
description="The highest price bid.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
ask: float | None = Field(
|
||||
default=None,
|
||||
description="The lowest price offered.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
mid: float | None = Field(
|
||||
default=None,
|
||||
description="The midpoint of the best bid and offer.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
mark_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the exchange marks positions at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
high: float | None = Field(
|
||||
default=None,
|
||||
description="The highest price of the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
low: float | None = Field(
|
||||
default=None,
|
||||
description="The lowest price of the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
change_percent: float | None = Field(
|
||||
default=None,
|
||||
description="The price change over the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
volume: float | None = Field(
|
||||
default=None,
|
||||
description="The volume of the last 24 hours, in base currency.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
volume_notional: float | None = Field(
|
||||
default=None,
|
||||
description="The volume of the last 24 hours, in quote currency.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
volume_usd: float | None = Field(
|
||||
default=None,
|
||||
description="The volume of the last 24 hours, in USD.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {
|
||||
"headerName": "Volume USD",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
open_interest: float | None = Field(
|
||||
default=None,
|
||||
description="The contracts left outstanding.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
current_funding: float | None = Field(
|
||||
default=None,
|
||||
description="The funding rate a perpetual is paying right now.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
funding_8h: float | None = Field(
|
||||
default=None,
|
||||
description="The funding rate a perpetual paid over eight hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {
|
||||
"headerName": "Funding 8H",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
estimated_delivery_price: float | None = Field(
|
||||
default=None,
|
||||
description="What the instrument would deliver at right now.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
interest_rate: float | None = Field(
|
||||
default=None,
|
||||
description="The rate the exchange prices the option's greeks at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
implied_volatility: float | None = Field(
|
||||
default=None,
|
||||
description="The volatility implied by the mark price.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {
|
||||
"headerName": "Mark IV",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
underlying_index: str | None = Field(
|
||||
default=None,
|
||||
description="The future or index the option is priced against.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
underlying_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price of the underlying the option is priced against.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("timestamp", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v) if v else None
|
||||
|
||||
@field_validator(
|
||||
"current_funding",
|
||||
"funding_8h",
|
||||
"interest_rate",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def as_percent(cls, v):
|
||||
"""Return a rate the exchange sends as a fraction in percent units."""
|
||||
return v * 100 if v is not None else None
|
||||
|
||||
|
||||
class DeribitBookSummaryFetcher(
|
||||
Fetcher[DeribitBookSummaryQueryParams, list[DeribitBookSummaryData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitBookSummaryQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitBookSummaryQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitBookSummaryQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange published no summary for the query.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather, request
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols
|
||||
|
||||
if query.symbol:
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
"get_book_summary_by_instrument",
|
||||
{"instrument_name": perpetuals.get(symbol, symbol)},
|
||||
)
|
||||
for symbol in symbols
|
||||
]
|
||||
)
|
||||
data = [
|
||||
record
|
||||
for result in results
|
||||
if isinstance(result, list)
|
||||
for record in result
|
||||
]
|
||||
else:
|
||||
data = await request(
|
||||
"get_book_summary_by_currency",
|
||||
{"currency": query.currency.upper(), "kind": query.kind},
|
||||
)
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit published no summary for the query.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitBookSummaryQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitBookSummaryData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitBookSummaryData.model_validate(record)
|
||||
for record in sorted(data, key=lambda d: str(d.get("instrument_name")))
|
||||
]
|
||||
@@ -0,0 +1,175 @@
|
||||
"""Deribit Combos Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
COMBO_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
AnyListingCurrencies,
|
||||
)
|
||||
|
||||
|
||||
class DeribitCombosQueryParams(QueryParams):
|
||||
"""Deribit Combos Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/combo-books/public-get_combos
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"combo_id": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": COMBO_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
currency: AnyListingCurrencies = Field(
|
||||
default="BTC", description="The currency the combos settle in."
|
||||
)
|
||||
combo_id: str | None = Field(
|
||||
default=None,
|
||||
description="One or more combo identifiers. When given, the currency is"
|
||||
+ " ignored.",
|
||||
)
|
||||
|
||||
|
||||
class DeribitCombosData(Data):
|
||||
"""Deribit Combos Data."""
|
||||
|
||||
combo_id: str = Field(
|
||||
description="The identifier of the combo.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Combo ID",
|
||||
"chartDataType": "category",
|
||||
"pinned": "left",
|
||||
}
|
||||
},
|
||||
)
|
||||
instrument_id: int | None = Field(
|
||||
default=None,
|
||||
description="The numeric identifier of the combo.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Instrument ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
state: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the combo is still tradeable.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
leg: str = Field(
|
||||
description="The instrument the leg trades.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
amount: float | None = Field(
|
||||
default=None,
|
||||
description="The size of the leg. A negative amount is a sold leg.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "series"}},
|
||||
)
|
||||
creation_timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="When the combo was listed.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
state_timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="When the combo last changed state.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"creation_timestamp", "state_timestamp", mode="before", check_fields=False
|
||||
)
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v) if v else None
|
||||
|
||||
|
||||
class DeribitCombosFetcher(Fetcher[DeribitCombosQueryParams, list[DeribitCombosData]]):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitCombosQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitCombosQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitCombosQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange lists no matching combo.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather, request
|
||||
from openbb_deribit.utils.helpers import normalize_currency
|
||||
|
||||
if query.combo_id:
|
||||
ids = [c.strip() for c in query.combo_id.split(",") if c.strip()]
|
||||
results = await gather(
|
||||
[("get_combo_details", {"combo_id": c}) for c in ids]
|
||||
)
|
||||
data = [result for result in results if isinstance(result, dict) and result]
|
||||
else:
|
||||
data = await request(
|
||||
"get_combos", {"currency": normalize_currency(query.currency)}
|
||||
)
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit lists no combo matching the query.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitCombosQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitCombosData]:
|
||||
"""Transform the data to the model."""
|
||||
records: list[dict] = []
|
||||
|
||||
for combo in data:
|
||||
for leg in combo.get("legs") or []:
|
||||
records.append(
|
||||
{
|
||||
"combo_id": combo.get("id"),
|
||||
"instrument_id": combo.get("instrument_id"),
|
||||
"state": combo.get("state"),
|
||||
"leg": leg.get("instrument_name"),
|
||||
"amount": leg.get("amount"),
|
||||
"creation_timestamp": combo.get("creation_timestamp"),
|
||||
"state_timestamp": combo.get("state_timestamp"),
|
||||
}
|
||||
)
|
||||
|
||||
return [
|
||||
DeribitCombosData.model_validate(record)
|
||||
for record in sorted(records, key=lambda d: (d["combo_id"], d["leg"]))
|
||||
]
|
||||
@@ -0,0 +1,155 @@
|
||||
"""Deribit Currencies Model."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class DeribitCurrenciesQueryParams(QueryParams):
|
||||
"""Deribit Currencies Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_currencies
|
||||
"""
|
||||
|
||||
|
||||
class DeribitCurrenciesData(Data):
|
||||
"""Deribit Currencies Data."""
|
||||
|
||||
currency: str = Field(
|
||||
description="The abbreviation of the currency.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "category", "pinned": "left"}
|
||||
},
|
||||
)
|
||||
currency_long: str | None = Field(
|
||||
default=None,
|
||||
description="The full name of the currency.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "headerName": "Name"}
|
||||
},
|
||||
)
|
||||
coin_type: str | None = Field(
|
||||
default=None,
|
||||
description="The type of the currency.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
currency_uuid: str | None = Field(
|
||||
default=None,
|
||||
description="The unique identifier of the currency.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"chartDataType": "excluded",
|
||||
"headerName": "Currency UUID",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
network_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The currency the network fee is charged in.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
apr: float | None = Field(
|
||||
default=None,
|
||||
description="The annual percentage rate earned by holding the currency.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "series", "headerName": "APR"},
|
||||
},
|
||||
)
|
||||
decimals: int | None = Field(
|
||||
default=None,
|
||||
description="The number of decimal places the currency is held to.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
onchain_operations_precision: int | None = Field(
|
||||
default=None,
|
||||
description="The number of decimal places an on-chain operation is held to.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"chartDataType": "excluded",
|
||||
"headerName": "On-chain Precision",
|
||||
}
|
||||
},
|
||||
)
|
||||
min_confirmations: int | None = Field(
|
||||
default=None,
|
||||
description="The confirmations a deposit needs before it is credited.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
min_withdrawal_fee: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest fee a withdrawal is charged.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
withdrawal_fee: float | None = Field(
|
||||
default=None,
|
||||
description="The fee a withdrawal is charged.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
network_fee: float | None = Field(
|
||||
default=None,
|
||||
description="The fee the network itself charges.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
in_cross_collateral_pool: bool | None = Field(
|
||||
default=None,
|
||||
description="Whether the currency counts towards cross collateral.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"chartDataType": "excluded",
|
||||
"cellDataType": "boolean",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DeribitCurrenciesFetcher(
|
||||
Fetcher[DeribitCurrenciesQueryParams, list[DeribitCurrenciesData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitCurrenciesQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitCurrenciesQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitCurrenciesQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange listed no currencies.
|
||||
"""
|
||||
from openbb_deribit.utils.helpers import get_currencies
|
||||
|
||||
data = await get_currencies()
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit listed no currencies.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitCurrenciesQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitCurrenciesData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitCurrenciesData.model_validate(record)
|
||||
for record in sorted(data, key=lambda d: str(d.get("currency")))
|
||||
]
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Deribit Delivery Prices Model."""
|
||||
|
||||
from datetime import date as dateType
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INDEX_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
)
|
||||
|
||||
|
||||
class DeribitDeliveryPricesQueryParams(QueryParams):
|
||||
"""Deribit Delivery Prices Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_delivery_prices
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"index_name": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INDEX_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
index_name: str = Field(
|
||||
default="btc_usd",
|
||||
description="One or more index names, such as 'btc_usd'.",
|
||||
)
|
||||
limit: int = Field(
|
||||
default=100, description="The number of settlements to return.", ge=1
|
||||
)
|
||||
offset: int = Field(
|
||||
default=0,
|
||||
description="How many of the most recent settlements to skip.",
|
||||
ge=0,
|
||||
)
|
||||
|
||||
|
||||
class DeribitDeliveryPricesData(Data):
|
||||
"""Deribit Delivery Prices Data."""
|
||||
|
||||
date: dateType = Field(
|
||||
description="The session the index delivered on.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
index_name: str = Field(
|
||||
description="The name of the index.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
delivery_price: float = Field(
|
||||
description="The level the index delivered at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DeribitDeliveryPricesFetcher(
|
||||
Fetcher[DeribitDeliveryPricesQueryParams, list[DeribitDeliveryPricesData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitDeliveryPricesQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitDeliveryPricesQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitDeliveryPricesQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the indexes has delivered.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather
|
||||
|
||||
names = [n.strip().lower() for n in query.index_name.split(",") if n.strip()]
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
"get_delivery_prices",
|
||||
{
|
||||
"index_name": name,
|
||||
"count": query.limit,
|
||||
"offset": query.offset,
|
||||
},
|
||||
)
|
||||
for name in names
|
||||
]
|
||||
)
|
||||
data = [
|
||||
{"index_name": name, **record}
|
||||
for name, result in zip(names, results)
|
||||
if isinstance(result, dict)
|
||||
for record in result.get("data") or []
|
||||
]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no delivery prices for {', '.join(names)}."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitDeliveryPricesQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitDeliveryPricesData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitDeliveryPricesData.model_validate(record)
|
||||
for record in sorted(data, key=lambda d: (d["date"], d["index_name"]))
|
||||
]
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Deribit Expirations Model."""
|
||||
|
||||
from datetime import date as dateType
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field
|
||||
|
||||
from openbb_deribit.utils.constants import ExpirationCurrencies, ExpirationKinds
|
||||
|
||||
|
||||
class DeribitExpirationsQueryParams(QueryParams):
|
||||
"""Deribit Expirations Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_expirations
|
||||
"""
|
||||
|
||||
currency: ExpirationCurrencies = Field(
|
||||
default="grouped",
|
||||
description="The currency to list expirations for. 'any' pools every"
|
||||
+ " currency together, and 'grouped' keeps them apart.",
|
||||
)
|
||||
kind: ExpirationKinds = Field(default="any", description="The kind of instrument.")
|
||||
currency_pair: str | None = Field(
|
||||
default=None,
|
||||
description="Narrow the listing to one currency pair, such as 'btc_usd'.",
|
||||
)
|
||||
|
||||
|
||||
class DeribitExpirationsData(Data):
|
||||
"""Deribit Expirations Data."""
|
||||
|
||||
currency: str = Field(
|
||||
description="The currency the contracts settle in.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "pinned": "left"}
|
||||
},
|
||||
)
|
||||
kind: str = Field(
|
||||
description="The kind of instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
code: str = Field(
|
||||
description="The expiration as it appears in instrument names.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
expiration: dateType | None = Field(
|
||||
default=None,
|
||||
description="The expiration date. Perpetual contracts carry none.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
|
||||
|
||||
class DeribitExpirationsFetcher(
|
||||
Fetcher[DeribitExpirationsQueryParams, list[DeribitExpirationsData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitExpirationsQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitExpirationsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitExpirationsQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> dict:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange lists no expirations for the query.
|
||||
"""
|
||||
from openbb_deribit.utils.helpers import get_expirations
|
||||
|
||||
data = await get_expirations(query.currency, query.kind, query.currency_pair)
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit lists no expirations matching the query.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitExpirationsQueryParams,
|
||||
data: dict,
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitExpirationsData]:
|
||||
"""Transform the data to the model.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If every currency in the response listed nothing.
|
||||
"""
|
||||
from pandas import to_datetime
|
||||
|
||||
by_currency = (
|
||||
{query.currency: data}
|
||||
if all(isinstance(value, list) for value in data.values())
|
||||
else data
|
||||
)
|
||||
records: list[dict] = []
|
||||
|
||||
for currency, kinds in by_currency.items():
|
||||
for kind, codes in kinds.items():
|
||||
for code in codes:
|
||||
records.append(
|
||||
{
|
||||
"currency": currency.upper(),
|
||||
"kind": kind,
|
||||
"code": code,
|
||||
"expiration": (
|
||||
None
|
||||
if code == "PERPETUAL"
|
||||
else to_datetime(code).date()
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
if not records:
|
||||
raise EmptyDataError("Deribit lists no expirations matching the query.")
|
||||
|
||||
return [
|
||||
DeribitExpirationsData.model_validate(record)
|
||||
for record in sorted(
|
||||
records,
|
||||
key=lambda d: (
|
||||
d["currency"],
|
||||
d["kind"],
|
||||
d["expiration"] or dateType.max,
|
||||
),
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,177 @@
|
||||
"""Deribit Funding Chart Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
PERPETUAL_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
FundingLengths,
|
||||
)
|
||||
|
||||
|
||||
class DeribitFundingChartQueryParams(QueryParams):
|
||||
"""Deribit Funding Chart Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_funding_chart_data
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": PERPETUAL_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
symbol: str = Field(
|
||||
default="BTC-PERPETUAL",
|
||||
description="One or more perpetual instrument names. A perpetual can also"
|
||||
+ " be given by its shortened root, such as 'SOLUSDC'.",
|
||||
)
|
||||
length: FundingLengths = Field(
|
||||
default="8h", description="How far back the series runs."
|
||||
)
|
||||
|
||||
|
||||
class DeribitFundingChartData(Data):
|
||||
"""Deribit Funding Chart Data."""
|
||||
|
||||
__alias_dict__ = {"date": "timestamp"}
|
||||
|
||||
date: datetime = Field(
|
||||
description="When the rate was published.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
symbol: str = Field(
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
interest_8h: float | None = Field(
|
||||
default=None,
|
||||
description="The funding rate over the trailing eight hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {
|
||||
"headerName": "Interest 8H",
|
||||
"chartDataType": "series",
|
||||
},
|
||||
},
|
||||
)
|
||||
index_price: float | None = Field(
|
||||
default=None,
|
||||
description="The level of the index when the rate was published.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
current_interest: float | None = Field(
|
||||
default=None,
|
||||
description="The funding rate accrued so far in the current interval.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_date(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
@field_validator(
|
||||
"interest_8h",
|
||||
"current_interest",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def as_percent(cls, v):
|
||||
"""Return a rate the exchange sends as a fraction in percent units."""
|
||||
return v * 100 if v is not None else None
|
||||
|
||||
|
||||
class DeribitFundingChartFetcher(
|
||||
Fetcher[DeribitFundingChartQueryParams, list[DeribitFundingChartData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitFundingChartQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitFundingChartQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitFundingChartQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the instruments published funding over the span.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols
|
||||
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
resolved = [perpetuals.get(symbol, symbol) for symbol in symbols]
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
"get_funding_chart_data",
|
||||
{"instrument_name": symbol, "length": query.length},
|
||||
)
|
||||
for symbol in resolved
|
||||
]
|
||||
)
|
||||
data = [
|
||||
{
|
||||
"symbol": symbol,
|
||||
"current_interest": result.get("current_interest"),
|
||||
**record,
|
||||
}
|
||||
for symbol, result in zip(resolved, results)
|
||||
if isinstance(result, dict)
|
||||
for record in result.get("data") or []
|
||||
]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no funding over the span for {', '.join(resolved)}."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitFundingChartQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitFundingChartData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitFundingChartData.model_validate(record)
|
||||
for record in sorted(
|
||||
data, key=lambda d: (d.get("timestamp") or 0, d["symbol"])
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,208 @@
|
||||
"""Deribit Funding Rate History Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
PERPETUAL_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
)
|
||||
|
||||
|
||||
class DeribitFundingRateHistoryQueryParams(QueryParams):
|
||||
"""Deribit Funding Rate History Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_funding_rate_history
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": PERPETUAL_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
symbol: str = Field(
|
||||
default="BTC-PERPETUAL",
|
||||
description="One or more perpetual instrument names. A perpetual can also"
|
||||
+ " be given by its shortened root, such as 'SOLUSDC'.",
|
||||
)
|
||||
start_date: Any | None = Field(
|
||||
default=None, description="Return rates from this date onwards."
|
||||
)
|
||||
end_date: Any | None = Field(
|
||||
default=None, description="Return rates up to this date."
|
||||
)
|
||||
|
||||
|
||||
class DeribitFundingRateHistoryData(Data):
|
||||
"""Deribit Funding Rate History Data."""
|
||||
|
||||
__alias_dict__ = {"date": "timestamp"}
|
||||
|
||||
date: datetime = Field(
|
||||
description="The hour the rate applied to.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
symbol: str = Field(
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
interest_1h: float | None = Field(
|
||||
default=None,
|
||||
description="The funding rate over the hour.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {
|
||||
"headerName": "Interest 1H",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
interest_8h: float | None = Field(
|
||||
default=None,
|
||||
description="The funding rate over the trailing eight hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {
|
||||
"headerName": "Interest 8H",
|
||||
"chartDataType": "series",
|
||||
},
|
||||
},
|
||||
)
|
||||
index_price: float | None = Field(
|
||||
default=None,
|
||||
description="The level of the index at the end of the hour.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
prev_index_price: float | None = Field(
|
||||
default=None,
|
||||
description="The level of the index at the start of the hour.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {
|
||||
"headerName": "Previous Index Price",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_date(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
@field_validator(
|
||||
"interest_1h",
|
||||
"interest_8h",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def as_percent(cls, v):
|
||||
"""Return a rate the exchange sends as a fraction in percent units."""
|
||||
return v * 100 if v is not None else None
|
||||
|
||||
|
||||
class DeribitFundingRateHistoryFetcher(
|
||||
Fetcher[DeribitFundingRateHistoryQueryParams, list[DeribitFundingRateHistoryData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(
|
||||
params: dict[str, Any],
|
||||
) -> DeribitFundingRateHistoryQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitFundingRateHistoryQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitFundingRateHistoryQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the instruments paid funding over the span.
|
||||
"""
|
||||
from datetime import (
|
||||
datetime as dt,
|
||||
timedelta,
|
||||
timezone,
|
||||
)
|
||||
|
||||
from openbb_deribit.utils.client import gather
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols, to_timestamp
|
||||
|
||||
now = dt.now(timezone.utc)
|
||||
start = (
|
||||
to_timestamp(query.start_date)
|
||||
if query.start_date
|
||||
else to_timestamp(now - timedelta(days=7))
|
||||
)
|
||||
end = to_timestamp(query.end_date) if query.end_date else to_timestamp(now)
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
resolved = [perpetuals.get(symbol, symbol) for symbol in symbols]
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
"get_funding_rate_history",
|
||||
{
|
||||
"instrument_name": symbol,
|
||||
"start_timestamp": start,
|
||||
"end_timestamp": end,
|
||||
},
|
||||
)
|
||||
for symbol in resolved
|
||||
]
|
||||
)
|
||||
data = [
|
||||
{"symbol": symbol, **record}
|
||||
for symbol, result in zip(resolved, results)
|
||||
if isinstance(result, list)
|
||||
for record in result
|
||||
]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no funding over the span for {', '.join(resolved)}."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitFundingRateHistoryQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitFundingRateHistoryData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitFundingRateHistoryData.model_validate(record)
|
||||
for record in sorted(
|
||||
data, key=lambda d: (d.get("timestamp") or 0, d["symbol"])
|
||||
)
|
||||
]
|
||||
@@ -1,105 +1,142 @@
|
||||
"""Deribit Futures Curve Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import date as dateType
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.futures_curve import (
|
||||
FuturesCurveData,
|
||||
FuturesCurveQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import (
|
||||
DATA_DESCRIPTIONS,
|
||||
QUERY_DESCRIPTIONS,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_deribit.utils.helpers import (
|
||||
DERIBIT_FUTURES_CURVE_SYMBOLS,
|
||||
FuturesCurveSymbols,
|
||||
)
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
CURVE_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
)
|
||||
|
||||
|
||||
class DeribitFuturesCurveQueryParams(FuturesCurveQueryParams):
|
||||
"""
|
||||
Deribit Futures Curve Query.
|
||||
"""Deribit Futures Curve Query.
|
||||
|
||||
Source: https://docs.deribit.com/?shell#public-ticker
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-ticker
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": False,
|
||||
"choices": DERIBIT_FUTURES_CURVE_SYMBOLS,
|
||||
},
|
||||
"hours_ago": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": CURVE_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
},
|
||||
"hours_ago": {"multiple_items_allowed": True},
|
||||
}
|
||||
|
||||
symbol: FuturesCurveSymbols = Field(
|
||||
symbol: str = Field(
|
||||
default="BTC",
|
||||
description=QUERY_DESCRIPTIONS.get("symbol", "")
|
||||
+ " Default is 'btc' Supported symbols are: ['btc', 'eth', 'paxg']",
|
||||
+ " The underlying root, as it appears in the instrument name.",
|
||||
)
|
||||
hours_ago: int | list[int] | str | None = Field(
|
||||
hours_ago: str | None = Field(
|
||||
default=None,
|
||||
description="Compare the current curve with the specified number of hours ago. Default is None.",
|
||||
description="Compare the current curve against how it stood this many"
|
||||
+ " hours ago.",
|
||||
)
|
||||
|
||||
@field_validator("symbol", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_symbol(cls, v):
|
||||
"""Validate the symbol."""
|
||||
symbol = v.upper()
|
||||
if symbol not in DERIBIT_FUTURES_CURVE_SYMBOLS:
|
||||
raise ValueError(
|
||||
f"Invalid Deribit symbol, {symbol}. Supported symbols are: {', '.join(DERIBIT_FUTURES_CURVE_SYMBOLS)}"
|
||||
)
|
||||
return symbol
|
||||
|
||||
@field_validator("hours_ago", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def _validate_hours_ago(cls, v):
|
||||
"""Validate hours ago."""
|
||||
if isinstance(v, str):
|
||||
return v
|
||||
if isinstance(v, int):
|
||||
return v
|
||||
if isinstance(v, list):
|
||||
return ",".join([str(i) for i in v])
|
||||
return None
|
||||
def validate_hours_ago(cls, v):
|
||||
"""Read one or more hour counts as a comma-separated string."""
|
||||
if v is None:
|
||||
return None
|
||||
|
||||
return ",".join(str(hour) for hour in v) if isinstance(v, list) else str(v)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def _validate_model(cls, values):
|
||||
"""Validate the model."""
|
||||
if values.get("date"):
|
||||
def validate_model(cls, values):
|
||||
"""Reject a date, which the exchange does not serve a curve for.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If a date was given.
|
||||
"""
|
||||
if isinstance(values, dict) and values.get("date"):
|
||||
raise ValueError(
|
||||
"Date field is not supported for Deribit provider. Use 'hours_ago' instead."
|
||||
"Deribit serves no curve as of a date. Use 'hours_ago' instead."
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class DeribitFuturesCurveData(FuturesCurveData):
|
||||
"""Deribit Futures Curve Data."""
|
||||
|
||||
date: dateType | None = Field(
|
||||
default=None,
|
||||
description=DATA_DESCRIPTIONS.get("date", ""),
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
expiration: str = Field(
|
||||
description="Futures expiration month.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "category", "pinned": "left"}
|
||||
},
|
||||
)
|
||||
price: float | None = Field(
|
||||
default=None,
|
||||
description="The price of the futures contract, taken from the mark.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
symbol: str | None = Field(
|
||||
default=None,
|
||||
description="The name of the contract.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
last_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the contract last traded at, which on a thin"
|
||||
+ " contract can be a long way from the mark the curve is built on.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
hours_ago: int | None = Field(
|
||||
default=None,
|
||||
description="The number of hours ago represented by the price."
|
||||
+ " Only available when hours_ago is set in the query.",
|
||||
description="How many hours back the price was read, when the query"
|
||||
+ " asked for one.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
|
||||
|
||||
class DeribitFuturesCurveFetcher(
|
||||
Fetcher[DeribitFuturesCurveQueryParams, list[DeribitFuturesCurveData]]
|
||||
):
|
||||
"""Deribit Futures Curve Fetcher."""
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint.
|
||||
|
||||
The current curve is priced off each contract's mark, because a thin contract's
|
||||
last trade can be a long way from where the exchange marks it. A curve read
|
||||
some hours back is priced off traded closes instead, the only history the
|
||||
exchange publishes, so it carries only the contracts that had traded by then.
|
||||
"""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitFuturesCurveQueryParams:
|
||||
"""Transform query params."""
|
||||
"""Transform the query."""
|
||||
return DeribitFuturesCurveQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
@@ -107,79 +144,83 @@ class DeribitFuturesCurveFetcher(
|
||||
query: DeribitFuturesCurveQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list:
|
||||
"""Extract the raw data."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the contracts on the curve published a quote.
|
||||
"""
|
||||
from openbb_deribit.utils.helpers import (
|
||||
get_futures_curve_symbols,
|
||||
get_ticker_data,
|
||||
get_futures_curve_by_hours_ago,
|
||||
get_futures_curve_symbols,
|
||||
get_tickers,
|
||||
)
|
||||
|
||||
try:
|
||||
symbols = await get_futures_curve_symbols(query.symbol)
|
||||
tasks = [get_ticker_data(s) for s in symbols]
|
||||
data = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
symbols = await get_futures_curve_symbols(query.symbol)
|
||||
data = await get_tickers(symbols)
|
||||
|
||||
if query.hours_ago is not None:
|
||||
num_hours = query.hours_ago
|
||||
if query.hours_ago:
|
||||
for hour in [int(h) for h in query.hours_ago.split(",") if h.strip()]:
|
||||
data.extend(await get_futures_curve_by_hours_ago(query.symbol, hour))
|
||||
|
||||
hours_ago = (
|
||||
[int(d) for d in num_hours.split(",")]
|
||||
if isinstance(num_hours, str)
|
||||
else [int(num_hours)] if isinstance(num_hours, int) else num_hours
|
||||
)
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no quote for the {query.symbol} curve."
|
||||
)
|
||||
|
||||
for hours in hours_ago:
|
||||
hours_data = await get_futures_curve_by_hours_ago(
|
||||
query.symbol, hours
|
||||
)
|
||||
if hours_data:
|
||||
data.extend(hours_data)
|
||||
return data
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
raise OpenBBError(
|
||||
f"Failed to get futures curve -> {e.__class__.__name__ if hasattr(e, '__class__') else e}: {e.args}"
|
||||
) from e
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitFuturesCurveQueryParams, data: list, **kwargs: Any
|
||||
query: DeribitFuturesCurveQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitFuturesCurveData]:
|
||||
"""Transform the data."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from datetime import datetime # noqa
|
||||
"""Transform the data to the model.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If every contract on the curve came back unpriced.
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from pandas import to_datetime
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("No data found")
|
||||
records: list[dict] = []
|
||||
|
||||
futures_curve: list[DeribitFuturesCurveData] = []
|
||||
for record in data:
|
||||
symbol = str(record.get("instrument_name") or "")
|
||||
code = symbol.split("-")[1] if "-" in symbol else ""
|
||||
price = record.get("mark_price") or record.get("last_price")
|
||||
|
||||
for d in data:
|
||||
if not d:
|
||||
if not symbol or price is None:
|
||||
continue
|
||||
|
||||
ins_name = d.get("instrument_name", "")
|
||||
exp = ins_name.split("-")[1]
|
||||
hours_ago = d.get("hours_ago", 0)
|
||||
exp = (
|
||||
datetime.today().strftime("%Y-%m-%d")
|
||||
if exp == "PERPETUAL"
|
||||
else to_datetime(exp).strftime("%Y-%m-%d")
|
||||
records.append(
|
||||
{
|
||||
"symbol": symbol,
|
||||
"expiration": (
|
||||
datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||
if code == "PERPETUAL"
|
||||
else to_datetime(code).strftime("%Y-%m-%d")
|
||||
),
|
||||
"price": price,
|
||||
"last_price": record.get("last_price"),
|
||||
"hours_ago": record.get("hours_ago") if query.hours_ago else None,
|
||||
}
|
||||
)
|
||||
|
||||
price = d.get("last_price", d.get("mark_price"))
|
||||
if not records:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no priced contract on the {query.symbol} curve."
|
||||
)
|
||||
|
||||
result = {"expiration": exp, "price": price}
|
||||
if query.hours_ago:
|
||||
result["hours_ago"] = hours_ago
|
||||
|
||||
if price:
|
||||
futures_curve.append(DeribitFuturesCurveData.model_validate(result))
|
||||
|
||||
if not futures_curve:
|
||||
raise EmptyDataError("No data found.")
|
||||
|
||||
return sorted(futures_curve, key=lambda x: x.expiration)
|
||||
return [
|
||||
DeribitFuturesCurveData.model_validate(record)
|
||||
for record in sorted(
|
||||
records, key=lambda d: (d["hours_ago"] or 0, d["expiration"])
|
||||
)
|
||||
]
|
||||
|
||||
@@ -1,91 +1,87 @@
|
||||
"""Deribit Futures Historical Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
timedelta,
|
||||
)
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.futures_historical import (
|
||||
FuturesHistoricalData,
|
||||
FuturesHistoricalQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.descriptions import (
|
||||
DATA_DESCRIPTIONS,
|
||||
QUERY_DESCRIPTIONS,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_deribit.utils.helpers import DERIBIT_INTERVALS, DeribitIntervals
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
from pydantic import Field, model_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INSTRUMENT_CHOICES_ENDPOINT,
|
||||
INTERVALS,
|
||||
SYMBOL_STYLE,
|
||||
Intervals,
|
||||
)
|
||||
|
||||
INTERVAL_LOOKBACK = {
|
||||
"1m": 30,
|
||||
"3m": 60,
|
||||
"5m": 60,
|
||||
"10m": 90,
|
||||
"15m": 90,
|
||||
"30m": 90,
|
||||
}
|
||||
DEFAULT_LOOKBACK = 364
|
||||
|
||||
|
||||
class DeribitFuturesHistoricalQueryParams(FuturesHistoricalQueryParams):
|
||||
"""
|
||||
Deribit Futures historical Price Query.
|
||||
"""Deribit Futures Historical Query.
|
||||
|
||||
Source: https://docs.deribit.com/?shell#public-get_tradingview_chart_data
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_tradingview_chart_data
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {"multiple_items_allowed": True},
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INSTRUMENT_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
},
|
||||
"interval": {
|
||||
"multiple_items_allowed": False,
|
||||
"choices": DERIBIT_INTERVALS,
|
||||
"choices": list(INTERVALS),
|
||||
},
|
||||
"expiration": {"x-widget_config": {"exclude": True}},
|
||||
}
|
||||
|
||||
interval: DeribitIntervals = Field(
|
||||
symbol: str = Field(
|
||||
description=QUERY_DESCRIPTIONS.get("symbol", "")
|
||||
+ " Any listed instrument works, not only futures. A perpetual can also"
|
||||
+ " be given by its shortened root, such as 'SOLUSDC'."
|
||||
)
|
||||
interval: Intervals = Field(
|
||||
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
|
||||
)
|
||||
|
||||
@field_validator("symbol", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def _validate_symbol(cls, v):
|
||||
"""Validate the symbol."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import run_async
|
||||
from openbb_deribit.utils.helpers import (
|
||||
get_futures_symbols,
|
||||
get_perpetual_symbols,
|
||||
)
|
||||
|
||||
if not v:
|
||||
raise ValueError("Symbol is required.")
|
||||
|
||||
futures_symbols = run_async(get_futures_symbols)
|
||||
perpetual_symbols = run_async(get_perpetual_symbols)
|
||||
all_symbols = list(perpetual_symbols) + futures_symbols
|
||||
symbols = v.upper().split(",")
|
||||
new_symbols: list = []
|
||||
|
||||
for symbol in symbols:
|
||||
if symbol not in all_symbols:
|
||||
raise ValueError(
|
||||
f"Invalid Deribit symbol: {symbol}. Supported symbols are: {', '.join(all_symbols)}"
|
||||
)
|
||||
if symbol in perpetual_symbols:
|
||||
new_symbols.append(perpetual_symbols[symbol])
|
||||
else:
|
||||
new_symbols.append(symbol)
|
||||
|
||||
return ",".join(new_symbols)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def _validate_model(cls, values):
|
||||
"""Validate the model."""
|
||||
interval = values.get("interval")
|
||||
def validate_model(cls, values):
|
||||
"""Fill in a span the interval can be served over."""
|
||||
if not isinstance(values, dict):
|
||||
return values
|
||||
|
||||
now = datetime.today()
|
||||
|
||||
if not values.get("start_date"):
|
||||
if interval == "1m":
|
||||
start = now - timedelta(days=30)
|
||||
elif interval in ["3m", "5m"]:
|
||||
start = now - timedelta(days=60)
|
||||
elif interval in ["15m", "30m"]:
|
||||
start = now - timedelta(days=90)
|
||||
else:
|
||||
start = now - timedelta(days=364)
|
||||
|
||||
values["start_date"] = start.strftime("%Y-%m-%d")
|
||||
lookback = INTERVAL_LOOKBACK.get(
|
||||
str(values.get("interval") or "1d"), DEFAULT_LOOKBACK
|
||||
)
|
||||
values["start_date"] = (now - timedelta(days=lookback)).strftime("%Y-%m-%d")
|
||||
|
||||
if not values.get("end_date"):
|
||||
values["end_date"] = (now + timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
@@ -96,16 +92,72 @@ class DeribitFuturesHistoricalQueryParams(FuturesHistoricalQueryParams):
|
||||
class DeribitFuturesHistoricalData(FuturesHistoricalData):
|
||||
"""Deribit Futures Historical Data."""
|
||||
|
||||
volume_notional: float = Field(description="Trading volume in quote currency.")
|
||||
date: datetime | dateType = Field(
|
||||
description=DATA_DESCRIPTIONS.get("date", ""),
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
open: float | None = Field(
|
||||
default=None,
|
||||
description=DATA_DESCRIPTIONS.get("open", ""),
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
high: float | None = Field(
|
||||
default=None,
|
||||
description=DATA_DESCRIPTIONS.get("high", ""),
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
low: float | None = Field(
|
||||
default=None,
|
||||
description=DATA_DESCRIPTIONS.get("low", ""),
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
close: float = Field(
|
||||
description=DATA_DESCRIPTIONS.get("close", ""),
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
volume: float | None = Field(
|
||||
default=None,
|
||||
description=DATA_DESCRIPTIONS.get("volume", ""),
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
symbol: str | None = Field(
|
||||
default=None,
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
volume_notional: float | None = Field(
|
||||
default=None,
|
||||
description="The volume of the candle, in quote currency.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DeribitFuturesHistoricalFetcher(
|
||||
Fetcher[DeribitFuturesHistoricalQueryParams, list[DeribitFuturesHistoricalData]]
|
||||
):
|
||||
"""Deribit Futures Historical Fetcher."""
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitFuturesHistoricalQueryParams:
|
||||
def transform_query(
|
||||
params: dict[str, Any],
|
||||
) -> DeribitFuturesHistoricalQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitFuturesHistoricalQueryParams(**params)
|
||||
|
||||
@@ -114,42 +166,51 @@ class DeribitFuturesHistoricalFetcher(
|
||||
query: DeribitFuturesHistoricalQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list:
|
||||
"""Extract the raw data."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_deribit.utils.helpers import get_ohlc_data
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
symbols = query.symbol.split(",")
|
||||
results: list = []
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the instruments published candles over the span.
|
||||
"""
|
||||
from openbb_deribit.utils.helpers import get_ohlc_data, get_perpetual_symbols
|
||||
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
results: list[dict] = []
|
||||
|
||||
for symbol in symbols:
|
||||
try:
|
||||
data = await get_ohlc_data(
|
||||
symbol=symbol,
|
||||
interval=query.interval,
|
||||
start_date=query.start_date,
|
||||
end_date=query.end_date,
|
||||
results.extend(
|
||||
await get_ohlc_data(
|
||||
perpetuals.get(symbol, symbol),
|
||||
query.start_date,
|
||||
query.end_date,
|
||||
query.interval,
|
||||
)
|
||||
if data:
|
||||
results.extend(data)
|
||||
except OpenBBError as e:
|
||||
raise e from e
|
||||
)
|
||||
|
||||
if not results:
|
||||
raise EmptyDataError("No data found.")
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no candles over the span for {', '.join(symbols)}."
|
||||
)
|
||||
|
||||
return sorted(results, key=lambda x: x["date"])
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitFuturesHistoricalQueryParams, data: list, **kwargs: Any
|
||||
query: DeribitFuturesHistoricalQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitFuturesHistoricalData]:
|
||||
"""Transform the data."""
|
||||
symbols = query.symbol.split(",")
|
||||
if len(symbols) == 1:
|
||||
results: list[DeribitFuturesHistoricalData] = []
|
||||
for d in data:
|
||||
_ = d.pop("symbol", None)
|
||||
results.append(DeribitFuturesHistoricalData.model_validate(d))
|
||||
return [DeribitFuturesHistoricalData.model_validate(d) for d in data]
|
||||
return [DeribitFuturesHistoricalData.model_validate(d) for d in data]
|
||||
"""Transform the data to the model."""
|
||||
single = len([s for s in query.symbol.split(",") if s.strip()]) == 1
|
||||
|
||||
return [
|
||||
DeribitFuturesHistoricalData.model_validate(
|
||||
{key: value for key, value in record.items() if key != "symbol"}
|
||||
if single
|
||||
else record
|
||||
)
|
||||
for record in sorted(data, key=lambda d: (d["date"], d["symbol"]))
|
||||
]
|
||||
|
||||
@@ -1,187 +1,59 @@
|
||||
"""Deribit Futures Info Models."""
|
||||
"""Deribit Futures Info Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.futures_info import (
|
||||
FuturesInfoData,
|
||||
FuturesInfoQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import (
|
||||
DATA_DESCRIPTIONS,
|
||||
QUERY_DESCRIPTIONS,
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from openbb_deribit.models.ticker import DeribitTickerData
|
||||
from openbb_deribit.utils.constants import (
|
||||
INSTRUMENT_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
)
|
||||
from pydantic import ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
class DeribitFuturesInfoQueryParams(FuturesInfoQueryParams):
|
||||
"""Deribit Futures Instruments Query."""
|
||||
"""Deribit Futures Info Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-ticker
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {"multiple_items_allowed": True},
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INSTRUMENT_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
symbol: str = Field(
|
||||
description=QUERY_DESCRIPTIONS.get("symbol", "")
|
||||
+ " Perpetual contracts can be referenced by their currency pair - i.e, SOLUSDC"
|
||||
+ " - or by their official Deribit symbol - i.e, SOL_USDC-PERPETUAL"
|
||||
+ " For a list of currently available instruments, use `derivatives.futures.instruments()`"
|
||||
+ " A perpetual can be given by its currency pair - SOLUSDC - or by its"
|
||||
+ " full instrument name - SOL_USDC-PERPETUAL."
|
||||
)
|
||||
|
||||
@field_validator("symbol", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def _validate_symbol(cls, v):
|
||||
"""Validate the symbol."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import run_async
|
||||
from openbb_deribit.utils.helpers import (
|
||||
get_futures_symbols,
|
||||
get_perpetual_symbols,
|
||||
)
|
||||
|
||||
symbols = v.split(",")
|
||||
new_symbols: list = []
|
||||
perpetual_symbols = run_async(get_perpetual_symbols)
|
||||
futures_symbols = run_async(get_futures_symbols)
|
||||
all_symbols = futures_symbols + list(perpetual_symbols)
|
||||
|
||||
for s in symbols:
|
||||
if s not in all_symbols:
|
||||
raise ValueError(
|
||||
f"Invalid symbol: {s}. Valid symbols are: {all_symbols}"
|
||||
)
|
||||
if s in perpetual_symbols:
|
||||
new_symbols.append(perpetual_symbols[s])
|
||||
else:
|
||||
new_symbols.append(s)
|
||||
|
||||
return ",".join(new_symbols)
|
||||
|
||||
|
||||
class DeribitFuturesInfoData(FuturesInfoData):
|
||||
class DeribitFuturesInfoData(DeribitTickerData, FuturesInfoData):
|
||||
"""Deribit Futures Info Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"symbol": "instrument_name",
|
||||
"change_percent": "price_change",
|
||||
}
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
|
||||
state: Literal["open", "closed"] = Field(
|
||||
description="The state of the order book. Possible values are open and closed."
|
||||
)
|
||||
open_interest: float = Field(
|
||||
description="The total amount of outstanding contracts in the corresponding amount units."
|
||||
)
|
||||
index_price: float = Field(
|
||||
description="Current index (reference) price",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
best_ask_amount: float | None = Field(
|
||||
default=None,
|
||||
description="It represents the requested order size of all best asks",
|
||||
)
|
||||
best_ask_price: float | None = Field(
|
||||
default=None,
|
||||
description="The current best ask price, null if there aren't any asks",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
best_bid_price: float | None = Field(
|
||||
default=None,
|
||||
description="The current best bid price, null if there aren't any bids",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
best_bid_amount: float | None = Field(
|
||||
default=None,
|
||||
description="It represents the requested order size of all best bids",
|
||||
)
|
||||
last_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price for the last trade",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
high: float | None = Field(
|
||||
default=None,
|
||||
description="Highest price during 24h",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
low: float | None = Field(
|
||||
default=None,
|
||||
description="Lowest price during 24h",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
change_percent: float | None = Field(
|
||||
default=None,
|
||||
description="24-hour price change expressed as a percentage, null if there weren't any trades",
|
||||
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
|
||||
)
|
||||
volume: float | None = Field(
|
||||
default=None,
|
||||
description="Volume during last 24h in base currency",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
volume_usd: float | None = Field(
|
||||
default=None,
|
||||
description="Volume in USD",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
mark_price: float = Field(
|
||||
description="The mark price for the instrument",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
settlement_price: float | None = Field(
|
||||
default=None,
|
||||
description="The settlement price for the instrument. Only when state = open",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
delivery_price: float | None = Field(
|
||||
default=None,
|
||||
description="The settlement price for the instrument. Only when state = closed.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
estimated_delivery_price: float = Field(
|
||||
description="Estimated delivery price for the market.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
current_funding: float | None = Field(
|
||||
default=None,
|
||||
description="Current funding (perpetual only)",
|
||||
)
|
||||
funding_8h: float | None = Field(
|
||||
default=None,
|
||||
description="Funding 8h (perpetual only)",
|
||||
)
|
||||
interest_value: float | None = Field(
|
||||
default=None,
|
||||
description="Value used to calculate realized_funding in positions (perpetual only)",
|
||||
)
|
||||
max_price: float = Field(
|
||||
description="The maximum price for the future."
|
||||
+ " Any buy orders submitted higher than this price, will be clamped to this maximum.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
min_price: float = Field(
|
||||
description="The minimum price for the future."
|
||||
+ " Any sell orders submitted lower than this price will be clamped to this minimum.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
)
|
||||
timestamp: datetime = Field(description="The timestamp of the data.")
|
||||
|
||||
@field_validator("change_percent", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def _validate_change_percent(cls, v):
|
||||
"""Validate the change percent."""
|
||||
return v / 100 if v else v
|
||||
|
||||
|
||||
class DeribitFuturesInfoFetcher(
|
||||
Fetcher[DeribitFuturesInfoQueryParams, list[DeribitFuturesInfoData]]
|
||||
):
|
||||
"""Deribit Futures Info Fetcher."""
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitFuturesInfoQueryParams:
|
||||
@@ -193,32 +65,40 @@ class DeribitFuturesInfoFetcher(
|
||||
query: DeribitFuturesInfoQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list:
|
||||
"""Extract data from the response."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
from openbb_core.provider.utils.errors import EmptyDataError, OpenBBError
|
||||
from openbb_deribit.utils.helpers import get_ticker_data
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
result: list = []
|
||||
symbols = query.symbol.split(",")
|
||||
try:
|
||||
tasks = [get_ticker_data(symbol) for symbol in symbols]
|
||||
for task in asyncio.as_completed(tasks, timeout=10):
|
||||
result.append(await task)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
raise OpenBBError(f"Error fetching data: {e}") from e
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the instruments published a quote.
|
||||
"""
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols, get_tickers
|
||||
|
||||
if not result:
|
||||
raise EmptyDataError("No data found for the given symbol(s).")
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
resolved = [perpetuals.get(symbol, symbol) for symbol in symbols]
|
||||
data = await get_tickers(resolved)
|
||||
|
||||
return sorted(result, key=lambda x: symbols.index(x["instrument_name"]))
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no quote for {', '.join(resolved)}."
|
||||
)
|
||||
|
||||
return sorted(
|
||||
data,
|
||||
key=lambda d: (
|
||||
resolved.index(d["instrument_name"])
|
||||
if d["instrument_name"] in resolved
|
||||
else len(resolved)
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitFuturesInfoQueryParams,
|
||||
data: list,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitFuturesInfoData]:
|
||||
"""Transform the data."""
|
||||
return [DeribitFuturesInfoData(**d) for d in data]
|
||||
"""Transform the data to the model."""
|
||||
return [DeribitFuturesInfoData.model_validate(record) for record in data]
|
||||
|
||||
@@ -1,104 +1,301 @@
|
||||
"""Deribit Futures Instruments Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.futures_instruments import (
|
||||
FuturesInstrumentsData,
|
||||
FuturesInstrumentsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import ConfigDict, Field, field_validator
|
||||
|
||||
from openbb_deribit.models.instruments import PERPETUAL_EXPIRATION
|
||||
|
||||
|
||||
class DeribitFuturesInstrumentsQueryParams(FuturesInstrumentsQueryParams):
|
||||
"""Deribit Futures Instruments Query."""
|
||||
"""Deribit Futures Instruments Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_instruments
|
||||
"""
|
||||
|
||||
|
||||
class DeribitFuturesInstrumentData(FuturesInstrumentsData):
|
||||
"""Deribit Futures Instrument Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"symbol": "instrument_name",
|
||||
}
|
||||
__alias_dict__ = {"symbol": "instrument_name"}
|
||||
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
instrument_id: int = Field(description="Deribit Instrument ID")
|
||||
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
|
||||
base_currency: str = Field(description="The underlying currency being traded.")
|
||||
counter_currency: str = Field(description="Counter currency for the instrument.")
|
||||
quote_currency: str = Field(
|
||||
description="The currency in which the instrument prices are quoted."
|
||||
symbol: str = Field(
|
||||
description=DATA_DESCRIPTIONS.get("symbol", ""),
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
instrument_id: int = Field(
|
||||
description="The numeric identifier of the instrument.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Instrument ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
kind: str | None = Field(
|
||||
default=None,
|
||||
description="The kind of instrument.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
instrument_type: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument is linear or reversed.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
future_type: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the future is linear or reversed.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
state: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument's book is open.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
base_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The currency the instrument is based on.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
quote_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The currency the instrument is quoted in.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
counter_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The counter currency of the instrument.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
settlement_currency: str | None = Field(
|
||||
default=None, description="Settlement currency for the instrument."
|
||||
default=None,
|
||||
description="The currency the instrument settles in.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
future_type: str = Field(description="Type of the instrument. linear or reversed")
|
||||
settlement_period: str | None = Field(
|
||||
default=None, description="The settlement period."
|
||||
default=None,
|
||||
description="How often the instrument settles.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
price_index: str = Field(
|
||||
description="Name of price index that is used for this instrument"
|
||||
underlying_type: str | None = Field(
|
||||
default=None,
|
||||
description="The asset class of the underlying.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
contract_size: float = Field(description="Contract size for instrument.")
|
||||
is_active: bool = Field(
|
||||
description="Indicates if the instrument can currently be traded."
|
||||
product_group: str | None = Field(
|
||||
default=None,
|
||||
description="The margin group the instrument belongs to.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
creation_timestamp: datetime = Field(
|
||||
description="The time when the instrument was first created (milliseconds since the UNIX epoch)."
|
||||
price_index: str | None = Field(
|
||||
default=None,
|
||||
description="The index the instrument is priced against.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
lot_size: int | None = Field(
|
||||
default=None,
|
||||
description="The number of contracts in one lot.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
min_trade_amount: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest amount that can be traded.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
tick_size: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest price increment.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
max_leverage: int | None = Field(
|
||||
default=None,
|
||||
description="The highest leverage the instrument allows.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
max_non_default_leverage: float | None = Field(
|
||||
default=None,
|
||||
description="The highest leverage available outside the default tier.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
max_liquidation_commission: float | None = Field(
|
||||
default=None,
|
||||
description="The largest commission a liquidation is charged.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
maker_commission: float | None = Field(
|
||||
default=None,
|
||||
description="The commission charged to a resting order.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
taker_commission: float | None = Field(
|
||||
default=None,
|
||||
description="The commission charged to an aggressing order.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
block_trade_commission: float | None = Field(
|
||||
default=None,
|
||||
description="The commission charged to a block trade.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
block_trade_min_trade_amount: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest amount a block trade can be.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
block_trade_tick_size: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest price increment of a block trade.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
is_active: bool | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument can be traded right now.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"cellDataType": "boolean", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
contract_size: float | None = Field(
|
||||
default=None,
|
||||
description="The size of one contract.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
index_id: int | None = Field(
|
||||
default=None,
|
||||
description="The numeric identifier of the price index.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Index ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
tick_size_steps: list[dict] | None = Field(
|
||||
default=None,
|
||||
description="The price levels above which a different tick size applies.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
base_currency_uuid: str | None = Field(
|
||||
default=None,
|
||||
description="The unique identifier of the base currency.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Base Currency UUID",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
quote_currency_uuid: str | None = Field(
|
||||
default=None,
|
||||
description="The unique identifier of the quote currency.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Quote Currency UUID",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
creation_timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="When the instrument was listed.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
expiration_timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="The time when the instrument will expire (milliseconds since the UNIX epoch).",
|
||||
)
|
||||
tick_size: float = Field(
|
||||
description="Specifies minimal price change and, as follows, the number of decimal places for instrument prices."
|
||||
)
|
||||
min_trade_amount: float = Field(
|
||||
description="Minimum amount for trading, in USD units."
|
||||
)
|
||||
max_leverage: int = Field(description="Maximal leverage for instrument.")
|
||||
max_liquidation_commission: float = Field(
|
||||
description="Maximal liquidation trade commission for instrument."
|
||||
)
|
||||
block_trade_commission: float = Field(
|
||||
description="Block Trade commission for instrument."
|
||||
)
|
||||
block_trade_min_trade_amount: float = Field(
|
||||
description="Minimum amount for block trading."
|
||||
)
|
||||
block_trade_tick_size: float = Field(
|
||||
description="Specifies minimal price change for block trading."
|
||||
)
|
||||
|
||||
maker_commission: float | None = Field(
|
||||
default=None, description="Maker commission for instrument."
|
||||
)
|
||||
taker_commission: float | None = Field(
|
||||
default=None, description="Taker commission for instrument."
|
||||
description="When the instrument expires. Perpetual contracts carry none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("expiration_timestamp", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def _validate_expiration(cls, v):
|
||||
"""Validate the expiration timestamp."""
|
||||
if int(v) == 32503708800000:
|
||||
return None
|
||||
return v
|
||||
def validate_expiration(cls, v):
|
||||
"""Read the expiration, dropping the sentinel a perpetual carries."""
|
||||
return None if not v or int(v) == PERPETUAL_EXPIRATION else v
|
||||
|
||||
|
||||
class DeribitFuturesInstrumentsFetcher(
|
||||
Fetcher[DeribitFuturesInstrumentsQueryParams, list[DeribitFuturesInstrumentData]]
|
||||
):
|
||||
"""Deribit Futures Instruments Fetcher."""
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitFuturesInstrumentsQueryParams:
|
||||
def transform_query(
|
||||
params: dict[str, Any],
|
||||
) -> DeribitFuturesInstrumentsQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitFuturesInstrumentsQueryParams(**params)
|
||||
|
||||
@@ -107,27 +304,37 @@ class DeribitFuturesInstrumentsFetcher(
|
||||
query: DeribitFuturesInstrumentsQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list:
|
||||
"""Extract data from Deribit API."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange lists no futures.
|
||||
"""
|
||||
from openbb_deribit.utils.helpers import get_instruments
|
||||
|
||||
try:
|
||||
data = await get_instruments("all", "future")
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
raise OpenBBError(f"Error fetching data: {e}") from e
|
||||
data = await get_instruments("any", "future")
|
||||
|
||||
if not data:
|
||||
raise OpenBBError(
|
||||
"There was an error with the request and it was returned empty."
|
||||
)
|
||||
raise EmptyDataError("Deribit lists no futures.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitFuturesInstrumentsQueryParams,
|
||||
data: list,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitFuturesInstrumentData]:
|
||||
"""Transform the data."""
|
||||
return [DeribitFuturesInstrumentData.model_validate(d) for d in data]
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitFuturesInstrumentData.model_validate(record)
|
||||
for record in sorted(
|
||||
data,
|
||||
key=lambda d: (
|
||||
d.get("expiration_timestamp") or 0,
|
||||
str(d.get("instrument_name")),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
"""Deribit Historical Volatility Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import REALIZED_VOLATILITY_CURRENCIES
|
||||
|
||||
|
||||
class DeribitHistoricalVolatilityQueryParams(QueryParams):
|
||||
"""Deribit Historical Volatility Query.
|
||||
|
||||
A currency pegged to the unit it is measured against has no volatility to
|
||||
realize: USDC and USDT read a few tenths of a percent, which is the peg
|
||||
moving rather than the asset, so neither is offered. EURR is a euro token
|
||||
measured against the dollar, so what it reads is the euro rate, and it is.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_historical_volatility
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"currency": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"options": [
|
||||
{"label": name, "value": name}
|
||||
for name in REALIZED_VOLATILITY_CURRENCIES
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
currency: str = Field(
|
||||
default="BTC", description="The currency whose realized volatility to read."
|
||||
)
|
||||
|
||||
@field_validator("currency", mode="before")
|
||||
@classmethod
|
||||
def _listed_currencies(cls, value):
|
||||
"""Accept one currency or several, and refuse any the exchange omits.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If a currency the exchange does not publish this for is asked for.
|
||||
"""
|
||||
chosen = [
|
||||
item.strip().upper() for item in str(value).split(",") if item.strip()
|
||||
]
|
||||
unlisted = [
|
||||
item for item in chosen if item not in REALIZED_VOLATILITY_CURRENCIES
|
||||
]
|
||||
|
||||
if unlisted:
|
||||
raise ValueError(
|
||||
f"Deribit publishes no realized volatility for {unlisted}."
|
||||
f" It publishes {list(REALIZED_VOLATILITY_CURRENCIES)}."
|
||||
)
|
||||
|
||||
return ",".join(chosen)
|
||||
|
||||
|
||||
class DeribitHistoricalVolatilityData(Data):
|
||||
"""Deribit Historical Volatility Data."""
|
||||
|
||||
date: datetime = Field(
|
||||
description="When the reading was published.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
currency: str = Field(
|
||||
description="The currency the reading belongs to.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
volatility: float = Field(
|
||||
description="The realized volatility over the trailing window.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_date(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
|
||||
class DeribitHistoricalVolatilityFetcher(
|
||||
Fetcher[
|
||||
DeribitHistoricalVolatilityQueryParams,
|
||||
list[DeribitHistoricalVolatilityData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(
|
||||
params: dict[str, Any],
|
||||
) -> DeribitHistoricalVolatilityQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitHistoricalVolatilityQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitHistoricalVolatilityQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the currency published no readings.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather
|
||||
|
||||
currencies = [c.strip().upper() for c in query.currency.split(",") if c.strip()]
|
||||
results = await gather(
|
||||
[
|
||||
("get_historical_volatility", {"currency": currency})
|
||||
for currency in currencies
|
||||
]
|
||||
)
|
||||
data = [
|
||||
{"date": point[0], "currency": currency, "volatility": point[1]}
|
||||
for currency, result in zip(currencies, results)
|
||||
if isinstance(result, list)
|
||||
for point in result
|
||||
]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no realized volatility for {', '.join(currencies)}."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitHistoricalVolatilityQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitHistoricalVolatilityData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitHistoricalVolatilityData.model_validate(record)
|
||||
for record in sorted(data, key=lambda d: (d["date"], d["currency"]))
|
||||
]
|
||||
@@ -0,0 +1,129 @@
|
||||
"""Deribit Index Historical Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INDEX_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
IndexChartRanges,
|
||||
)
|
||||
|
||||
|
||||
class DeribitIndexHistoricalQueryParams(QueryParams):
|
||||
"""Deribit Index Historical Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_index_chart_data
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"index_name": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INDEX_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
index_name: str = Field(
|
||||
default="btc_usd",
|
||||
description="One or more index names, such as 'btc_usd'.",
|
||||
)
|
||||
span: IndexChartRanges = Field(
|
||||
default="1d", description="How far back the series runs."
|
||||
)
|
||||
|
||||
|
||||
class DeribitIndexHistoricalData(Data):
|
||||
"""Deribit Index Historical Data."""
|
||||
|
||||
date: datetime = Field(
|
||||
description="When the level was published.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
index_name: str = Field(
|
||||
description="The name of the index.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
price: float = Field(
|
||||
description="The level of the index.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_date(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
|
||||
class DeribitIndexHistoricalFetcher(
|
||||
Fetcher[DeribitIndexHistoricalQueryParams, list[DeribitIndexHistoricalData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitIndexHistoricalQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitIndexHistoricalQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitIndexHistoricalQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the indexes published a series.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather
|
||||
|
||||
names = [n.strip().lower() for n in query.index_name.split(",") if n.strip()]
|
||||
results = await gather(
|
||||
[
|
||||
("get_index_chart_data", {"index_name": name, "range": query.span})
|
||||
for name in names
|
||||
]
|
||||
)
|
||||
data = [
|
||||
{"date": point[0], "index_name": name, "price": point[1]}
|
||||
for name, result in zip(names, results)
|
||||
if isinstance(result, list)
|
||||
for point in result
|
||||
]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(f"Deribit published no series for {', '.join(names)}.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitIndexHistoricalQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitIndexHistoricalData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitIndexHistoricalData.model_validate(record)
|
||||
for record in sorted(data, key=lambda d: (d["date"], d["index_name"]))
|
||||
]
|
||||
@@ -0,0 +1,118 @@
|
||||
"""Deribit Index Price Model."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INDEX_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
)
|
||||
|
||||
|
||||
class DeribitIndexPriceQueryParams(QueryParams):
|
||||
"""Deribit Index Price Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_index_price
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"index_name": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INDEX_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
index_name: str = Field(
|
||||
default="btc_usd",
|
||||
description="One or more index names, such as 'btc_usd'.",
|
||||
)
|
||||
|
||||
|
||||
class DeribitIndexPriceData(Data):
|
||||
"""Deribit Index Price Data."""
|
||||
|
||||
index_name: str = Field(
|
||||
description="The name of the index.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "category", "pinned": "left"}
|
||||
},
|
||||
)
|
||||
index_price: float | None = Field(
|
||||
default=None,
|
||||
description="The current level of the index.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
estimated_delivery_price: float | None = Field(
|
||||
default=None,
|
||||
description="What instruments on the index would deliver at right now.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DeribitIndexPriceFetcher(
|
||||
Fetcher[DeribitIndexPriceQueryParams, list[DeribitIndexPriceData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitIndexPriceQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitIndexPriceQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitIndexPriceQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the indexes published a level.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather
|
||||
|
||||
names = [n.strip().lower() for n in query.index_name.split(",") if n.strip()]
|
||||
results = await gather(
|
||||
[("get_index_price", {"index_name": name}) for name in names],
|
||||
use_cache=False,
|
||||
)
|
||||
|
||||
data = [
|
||||
{"index_name": name, **result}
|
||||
for name, result in zip(names, results)
|
||||
if isinstance(result, dict) and result
|
||||
]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(f"Deribit published no level for {', '.join(names)}.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitIndexPriceQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitIndexPriceData]:
|
||||
"""Transform the data to the model."""
|
||||
return [DeribitIndexPriceData.model_validate(record) for record in data]
|
||||
@@ -0,0 +1,430 @@
|
||||
"""Deribit Instruments Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INSTRUMENT_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
AnyListingCurrencies,
|
||||
InstrumentKinds,
|
||||
)
|
||||
|
||||
PERPETUAL_EXPIRATION = 32503708800000
|
||||
|
||||
|
||||
class DeribitInstrumentsQueryParams(QueryParams):
|
||||
"""Deribit Instruments Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_instruments
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INSTRUMENT_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
symbol: str | None = Field(
|
||||
default=None,
|
||||
description="One or more instrument names. When given, the currency and"
|
||||
+ " kind are ignored.",
|
||||
)
|
||||
currency: AnyListingCurrencies = Field(
|
||||
default="any", description="The settlement currency of the instruments."
|
||||
)
|
||||
kind: InstrumentKinds | None = Field(
|
||||
default=None,
|
||||
description="The kind of instrument. Default is all of them.",
|
||||
)
|
||||
expired: bool = Field(
|
||||
default=False,
|
||||
description="When True, returns instruments that have already expired.",
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _options_are_listed_by_underlying(self):
|
||||
"""Refuse a listing of options scoped to a currency that only settles them."""
|
||||
from openbb_deribit.utils.helpers import reject_options_by_settlement
|
||||
|
||||
if not self.symbol:
|
||||
reject_options_by_settlement(self.currency, self.kind)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class DeribitInstrumentsData(Data):
|
||||
"""Deribit Instruments Data."""
|
||||
|
||||
__alias_dict__ = {"symbol": "instrument_name"}
|
||||
|
||||
symbol: str = Field(
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
instrument_id: int | None = Field(
|
||||
default=None,
|
||||
description="The numeric identifier of the instrument.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Instrument ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
kind: str | None = Field(
|
||||
default=None,
|
||||
description="The kind of instrument.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
instrument_type: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument is linear or reversed.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
future_type: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the future is linear or reversed.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
state: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument's book is open.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
base_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The currency the instrument is based on.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
quote_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The currency the instrument is quoted in.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
counter_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The counter currency of the instrument.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
settlement_currency: str | None = Field(
|
||||
default=None,
|
||||
description="The currency the instrument settles in.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
settlement_period: str | None = Field(
|
||||
default=None,
|
||||
description="How often the instrument settles.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
underlying_type: str | None = Field(
|
||||
default=None,
|
||||
description="The asset class of the underlying.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
product_group: str | None = Field(
|
||||
default=None,
|
||||
description="The margin group the instrument belongs to.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
price_index: str | None = Field(
|
||||
default=None,
|
||||
description="The index the instrument is priced against.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
lot_size: int | None = Field(
|
||||
default=None,
|
||||
description="The number of contracts in one lot.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
min_trade_amount: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest amount that can be traded.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
tick_size: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest price increment.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
max_leverage: int | None = Field(
|
||||
default=None,
|
||||
description="The highest leverage the instrument allows.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
max_non_default_leverage: float | None = Field(
|
||||
default=None,
|
||||
description="The highest leverage available outside the default tier.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
max_liquidation_commission: float | None = Field(
|
||||
default=None,
|
||||
description="The largest commission a liquidation is charged.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
maker_commission: float | None = Field(
|
||||
default=None,
|
||||
description="The commission charged to a resting order.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
taker_commission: float | None = Field(
|
||||
default=None,
|
||||
description="The commission charged to an aggressing order.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
block_trade_commission: float | None = Field(
|
||||
default=None,
|
||||
description="The commission charged to a block trade.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
block_trade_min_trade_amount: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest amount a block trade can be.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
block_trade_tick_size: float | None = Field(
|
||||
default=None,
|
||||
description="The smallest price increment of a block trade.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
option_type: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the option is a call or a put.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
strike: float | None = Field(
|
||||
default=None,
|
||||
description="The strike price of the option.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
is_active: bool | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument can be traded right now.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"cellDataType": "boolean", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
contract_size: float | None = Field(
|
||||
default=None,
|
||||
description="The size of one contract.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
is_csr: bool | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument is a cross settled routed pair.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Is CSR",
|
||||
"cellDataType": "boolean",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
is_cbe_routed: bool | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument routes to Coinbase Exchange liquidity.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Is CBE Routed",
|
||||
"cellDataType": "boolean",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
index_id: int | None = Field(
|
||||
default=None,
|
||||
description="The numeric identifier of the price index.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Index ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
tick_size_steps: list[dict] | None = Field(
|
||||
default=None,
|
||||
description="The price levels above which a different tick size applies.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
base_currency_uuid: str | None = Field(
|
||||
default=None,
|
||||
description="The unique identifier of the base currency.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Base Currency UUID",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
quote_currency_uuid: str | None = Field(
|
||||
default=None,
|
||||
description="The unique identifier of the quote currency.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Quote Currency UUID",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
creation_timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="When the instrument was listed.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
expiration_timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="When the instrument expires. Perpetual and spot instruments carry none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"creation_timestamp", "expiration_timestamp", mode="before", check_fields=False
|
||||
)
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
"""Read the timestamp as a datetime, dropping the perpetual sentinel."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return None if not v or int(v) == PERPETUAL_EXPIRATION else from_timestamp(v)
|
||||
|
||||
|
||||
class DeribitInstrumentsFetcher(
|
||||
Fetcher[DeribitInstrumentsQueryParams, list[DeribitInstrumentsData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitInstrumentsQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitInstrumentsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitInstrumentsQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange lists no matching instrument.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather
|
||||
from openbb_deribit.utils.helpers import get_instruments
|
||||
|
||||
if query.symbol:
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
results = await gather(
|
||||
[("get_instrument", {"instrument_name": s}) for s in symbols]
|
||||
)
|
||||
data = [result for result in results if isinstance(result, dict) and result]
|
||||
else:
|
||||
data = await get_instruments(query.currency, query.kind, query.expired)
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit lists no instrument matching the query.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitInstrumentsQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitInstrumentsData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitInstrumentsData.model_validate(record)
|
||||
for record in sorted(
|
||||
data,
|
||||
key=lambda d: (
|
||||
str(d.get("kind")),
|
||||
d.get("expiration_timestamp") or 0,
|
||||
str(d.get("instrument_name")),
|
||||
),
|
||||
)
|
||||
]
|
||||
@@ -1,44 +1,58 @@
|
||||
"""Deribit Options Chains Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
)
|
||||
from typing import Any
|
||||
|
||||
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
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
SYMBOL_STYLE,
|
||||
UNDERLYING_CHOICES_ENDPOINT,
|
||||
)
|
||||
|
||||
INVERSE_QUOTED = ("BTC", "ETH")
|
||||
PRICE_FIELDS = (
|
||||
"last_price",
|
||||
"settlement_price",
|
||||
"mark_price",
|
||||
"min_price",
|
||||
"max_price",
|
||||
"best_ask_price",
|
||||
"best_bid_price",
|
||||
"high",
|
||||
"low",
|
||||
)
|
||||
|
||||
|
||||
class DeribitOptionsChainsQueryParams(OptionsChainsQueryParams):
|
||||
"""Deribit Options Chains Query Parameters Model."""
|
||||
"""Deribit Options Chains Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-ticker
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": False,
|
||||
"choices": DERIBIT_OPTIONS_SYMBOLS,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": UNDERLYING_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@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."""
|
||||
"""Deribit Options Chains Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"contract_symbol": "instrument_name",
|
||||
@@ -57,60 +71,459 @@ class DeribitOptionsChainsData(OptionsChainsData):
|
||||
|
||||
__doc__ = OptionsChainsData.__doc__
|
||||
|
||||
bid_iv: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The implied volatility of the bid price.",
|
||||
json_schema_extra={"x-unit_measurement": "decimal"},
|
||||
contract_symbol: list[str] = Field(
|
||||
description="The symbol of the contract.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Contract",
|
||||
"chartDataType": "excluded",
|
||||
"pinned": "left",
|
||||
},
|
||||
},
|
||||
)
|
||||
ask_iv: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The implied volatility of the ask price.",
|
||||
json_schema_extra={"x-unit_measurement": "decimal"},
|
||||
expiration: list[dateType] = Field(
|
||||
description="The expiration date of the contract.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
interest_rate: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The interest rate used by Deribit to calculate greeks.",
|
||||
dte: list[int | None] = Field(
|
||||
description="The number of days until expiration.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"headerName": "DTE", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
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"},
|
||||
strike: list[float] = Field(
|
||||
description="The strike price of the contract.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "category"},
|
||||
},
|
||||
)
|
||||
settlement_price: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The settlement price of the contract.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
option_type: list[str] = Field(
|
||||
description="Whether the contract is a call or a put.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"headerName": "Type", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
min_price: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The minimum price allowed.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
mark: list[float | None] = Field(
|
||||
description="The mark price of the contract.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
max_price: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The maximum price allowed.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
bid: list[float | None] = Field(
|
||||
description="The highest price bid.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
bid_size: list[int | float | None] = Field(
|
||||
description="The size resting at the best bid.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
ask: list[float | None] = Field(
|
||||
description="The lowest price offered.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
ask_size: list[int | float | None] = Field(
|
||||
description="The size resting at the best offer.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
last_trade_price: list[float | None] = Field(
|
||||
description="The price the contract last traded at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
open_interest: list[int | float | None] = Field(
|
||||
description="The contracts left outstanding.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
volume: list[int | float | None] = Field(
|
||||
description="The volume of the session.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
volume_notional: list[float | None] = Field(
|
||||
description="The notional volume of the contract, in USD or USDC.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
high: list[float | None] = Field(
|
||||
description="The highest price of the session.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
low: list[float | None] = Field(
|
||||
description="The lowest price of the session.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
change_percent: list[float | None] = Field(
|
||||
description="The price change over the session.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
implied_volatility: list[float | None] = Field(
|
||||
description="The volatility implied by the mark price.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"headerName": "Mark IV", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
bid_iv: list[float | None] = Field(
|
||||
description="The volatility implied by the bid price.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"headerName": "Bid IV", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
ask_iv: list[float | None] = Field(
|
||||
description="The volatility implied by the ask price.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"headerName": "Ask IV", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
delta: list[float | None] = Field(
|
||||
description="The sensitivity to the underlying's price.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
gamma: list[float | None] = Field(
|
||||
description="The sensitivity of delta to the underlying's price.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
theta: list[float | None] = Field(
|
||||
description="The sensitivity to the passage of time.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
vega: list[float | None] = Field(
|
||||
description="The sensitivity to implied volatility.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
rho: list[float | None] = Field(
|
||||
description="The sensitivity to the interest rate.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
interest_rate: list[float | None] = Field(
|
||||
description="The rate the exchange prices the greeks at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
settlement_price: list[float | None] = Field(
|
||||
description="The price the contract last settled at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
min_price: list[float | None] = Field(
|
||||
description="The lowest price an order will be accepted at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
max_price: list[float | None] = Field(
|
||||
description="The highest price an order will be accepted at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
underlying_symbol: list[str | None] = Field(
|
||||
description="The future or index the contract is priced against.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
underlying_price: list[float | None] = Field(
|
||||
description="The price of the underlying the contract is priced against.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
underlying_spot_price: list[float] = Field(
|
||||
description="The spot price of the underlying the option is based on.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
contract_size: list[int | float | None] = Field(
|
||||
description="The size of one contract, in units of the underlying.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
is_inverse: list[bool | None] = Field(
|
||||
default_factory=list,
|
||||
description="The notional trading volume of the contract, as USD or USDC.",
|
||||
json_schema_extra={"x-unit_measurement": "currency"},
|
||||
description="Whether the contract settles in the underlying rather than"
|
||||
+ " in the quote currency, which changes how its payoff is valued.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Is Inverse",
|
||||
"cellDataType": "boolean",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
timestamp: list[datetime] = Field(
|
||||
description="The datetime of the data, as America/New_York time.",
|
||||
description="When the quote was published, as America/New_York time.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
eod_date: list[dateType | None] = Field(
|
||||
default_factory=list,
|
||||
description="The end-of-day date. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "EOD Date",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
theoretical_price: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The theoretical price of the contract. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
last_trade_size: list[int | float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The size of the last trade. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
last_trade_time: list[datetime | None] = Field(
|
||||
default_factory=list,
|
||||
description="When the last trade printed. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
tick: list[str | None] = Field(
|
||||
default_factory=list,
|
||||
description="The direction of the last price move. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
bid_time: list[datetime | None] = Field(
|
||||
default_factory=list,
|
||||
description="When the bid was published. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
bid_exchange: list[str | None] = Field(
|
||||
default_factory=list,
|
||||
description="The exchange the bid came from. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
ask_time: list[datetime | None] = Field(
|
||||
default_factory=list,
|
||||
description="When the offer was published. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
ask_exchange: list[str | None] = Field(
|
||||
default_factory=list,
|
||||
description="The exchange the offer came from. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
open: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The opening price of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
open_bid: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The opening bid of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
open_ask: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The opening offer of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
bid_high: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The highest bid of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
ask_high: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The highest offer of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
bid_low: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The lowest bid of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
ask_low: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The lowest offer of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The closing price of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close_size: list[int | float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The size of the closing trade. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close_time: list[datetime | None] = Field(
|
||||
default_factory=list,
|
||||
description="When the closing trade printed. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close_bid: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The closing bid of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close_bid_size: list[int | float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The size at the closing bid. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close_bid_time: list[datetime | None] = Field(
|
||||
default_factory=list,
|
||||
description="When the closing bid was published. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close_ask: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The closing offer of the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close_ask_size: list[int | float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The size at the closing offer. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
close_ask_time: list[datetime | None] = Field(
|
||||
default_factory=list,
|
||||
description="When the closing offer was published. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
prev_close: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The closing price of the previous session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
change: list[float | None] = Field(
|
||||
default_factory=list,
|
||||
description="The price change over the session. Deribit publishes none.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"interest_rate",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def as_percent(cls, v):
|
||||
"""Return a rate the exchange sends as a fraction in percent units."""
|
||||
return v * 100 if v is not None else None
|
||||
|
||||
|
||||
class DeribitOptionsChainsFetcher(
|
||||
Fetcher[DeribitOptionsChainsQueryParams, DeribitOptionsChainsData]
|
||||
):
|
||||
"""Deribit Options Chains Fetcher."""
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitOptionsChainsQueryParams:
|
||||
"""Transform the query parameters."""
|
||||
"""Transform the query."""
|
||||
return DeribitOptionsChainsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
@@ -119,126 +532,87 @@ class DeribitOptionsChainsFetcher(
|
||||
credentials: dict[str, str] | None,
|
||||
**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 websockets.asyncio.client import connect
|
||||
"""Return the raw data from the Deribit websocket.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If every expiration failed.
|
||||
EmptyDataError
|
||||
If the subscriptions returned nothing at all.
|
||||
"""
|
||||
import asyncio
|
||||
from warnings import warn
|
||||
|
||||
# We need to identify each option contract in order to fetch the chains data.
|
||||
symbols_dict: dict[str, str] = {}
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from pandas import to_datetime
|
||||
|
||||
try:
|
||||
symbols_dict = await get_options_symbols(query.symbol) # type: ignore
|
||||
except OpenBBError as e:
|
||||
raise OpenBBError(e) from e
|
||||
from openbb_deribit.utils.helpers import (
|
||||
flatten_ticker,
|
||||
get_instruments,
|
||||
get_options_symbols,
|
||||
)
|
||||
from openbb_deribit.utils.websocket import subscribe_tickers
|
||||
|
||||
# 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 = []
|
||||
by_expiration = await get_options_symbols(query.symbol)
|
||||
specs = {
|
||||
str(d["instrument_name"]): d for d in await get_instruments("any", "option")
|
||||
}
|
||||
messages: set = set()
|
||||
subscriptions = await asyncio.gather(
|
||||
*(
|
||||
subscribe_tickers(symbols, messages)
|
||||
for symbols in by_expiration.values()
|
||||
),
|
||||
return_exceptions=True,
|
||||
)
|
||||
today = to_datetime("today").date()
|
||||
results: list[dict] = []
|
||||
|
||||
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 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.")
|
||||
for subscription in subscriptions:
|
||||
if not isinstance(subscription, dict):
|
||||
continue
|
||||
|
||||
async def receive_data(websocket, symbols, received_symbols):
|
||||
"""Receive the data from the websocket with a timeout."""
|
||||
while True:
|
||||
try:
|
||||
response = await websocket.recv()
|
||||
except websockets.ConnectionClosed:
|
||||
break
|
||||
data = json.loads(response)
|
||||
for symbol, ticker in subscription.items():
|
||||
parts = symbol.split("-")
|
||||
strike = parts[2].replace("d", ".")
|
||||
flat = flatten_ticker(ticker)
|
||||
flat.pop("state", None)
|
||||
published = flat.pop("timestamp", None)
|
||||
|
||||
if "params" not in data:
|
||||
continue
|
||||
if flat.get("underlying_index") == "index_price":
|
||||
flat["underlying_index"] = parts[0].replace("_", "-")
|
||||
|
||||
if "error" in data and data.get("error"):
|
||||
messages.add(f"Error while receiving data -> {data['error']}")
|
||||
break
|
||||
if flat.get("estimated_delivery_price") == flat.get("index_price"):
|
||||
flat.pop("estimated_delivery_price", None)
|
||||
|
||||
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,
|
||||
expiration = to_datetime(parts[1]).date()
|
||||
spec = specs.get(symbol, {})
|
||||
results.append(
|
||||
{
|
||||
"expiration": expiration,
|
||||
"strike": float(strike),
|
||||
"option_type": "call" if symbol.endswith("-C") else "put",
|
||||
"dte": (expiration - today).days,
|
||||
"contract_size": spec.get("contract_size"),
|
||||
"is_inverse": spec.get("instrument_type") == "reversed",
|
||||
"timestamp": to_datetime(
|
||||
published, unit="ms", utc=True
|
||||
).tz_convert("America/New_York"),
|
||||
**flat,
|
||||
}
|
||||
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, return_exceptions=True)
|
||||
)
|
||||
|
||||
if messages and not results:
|
||||
raise OpenBBError(", ".join(messages))
|
||||
raise OpenBBError(", ".join(sorted(messages)))
|
||||
|
||||
if results and messages:
|
||||
for message in messages:
|
||||
warn(message)
|
||||
for message in sorted(messages):
|
||||
warn(message)
|
||||
|
||||
if not results and not messages:
|
||||
raise EmptyDataError("All requests returned empty with no error messages.")
|
||||
if not results:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no option quotes for {query.symbol}."
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@@ -248,34 +622,22 @@ class DeribitOptionsChainsFetcher(
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> DeribitOptionsChainsData:
|
||||
"""Transform the data."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
"""Transform the data to the model.
|
||||
|
||||
Contracts on BTC and ETH are quoted in the underlying, so their prices are
|
||||
carried to USD at the index level the quote was published against.
|
||||
"""
|
||||
from numpy import nan
|
||||
from pandas import DataFrame
|
||||
|
||||
df = DataFrame(data)
|
||||
frame = DataFrame(data)
|
||||
inverse = bool(frame["is_inverse"].any())
|
||||
|
||||
# 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[col] = (df[col].astype(float) * df["index_price"]).round(2)
|
||||
elif col in ["price_change", "mark_iv", "bid_iv", "ask_iv"]:
|
||||
df[col] = df[col].astype(float) / 100
|
||||
for column in frame.columns:
|
||||
if column in PRICE_FIELDS and inverse:
|
||||
frame[column] = frame[column].astype(float) * frame["index_price"]
|
||||
|
||||
df = df.replace({nan: None})
|
||||
df = df.sort_values(["expiration", "strike", "option_type"])
|
||||
df = df.reset_index(drop=True)
|
||||
df["contract_size"] = 1
|
||||
results = df.to_dict(orient="list")
|
||||
|
||||
return DeribitOptionsChainsData.model_validate(results)
|
||||
frame = frame.replace({nan: None})
|
||||
frame = frame.sort_values(["expiration", "strike", "option_type"])
|
||||
frame = frame.reset_index(drop=True)
|
||||
return DeribitOptionsChainsData.model_validate(frame.to_dict(orient="list"))
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
"""Deribit Order Book Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INSTRUMENT_CHOICES_ENDPOINT,
|
||||
ORDER_BOOK_DEPTHS,
|
||||
SYMBOL_STYLE,
|
||||
)
|
||||
|
||||
|
||||
class DeribitOrderBookQueryParams(QueryParams):
|
||||
"""Deribit Order Book Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_order_book
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INSTRUMENT_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
},
|
||||
"depth": {
|
||||
"x-widget_config": {
|
||||
"options": [
|
||||
{"label": str(depth), "value": depth} for depth in ORDER_BOOK_DEPTHS
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
symbol: str | None = Field(
|
||||
default=None,
|
||||
description="One or more instrument names. A perpetual can also be given"
|
||||
+ " by its shortened root, such as 'SOLUSDC'.",
|
||||
)
|
||||
instrument_id: int | None = Field(
|
||||
default=None,
|
||||
description="The numeric identifier of one instrument, used in place of"
|
||||
+ " its name.",
|
||||
)
|
||||
depth: int = Field(
|
||||
default=10,
|
||||
description="The number of levels to return on each side: 1, 5, 10, 20, 50,"
|
||||
+ " 100, 1000, or 10000.",
|
||||
)
|
||||
|
||||
@field_validator("depth", check_fields=False)
|
||||
@classmethod
|
||||
def validate_depth(cls, v):
|
||||
"""Refuse a depth the exchange does not offer.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the depth is not one Deribit accepts.
|
||||
"""
|
||||
if v not in ORDER_BOOK_DEPTHS:
|
||||
raise ValueError(
|
||||
f"Deribit returns a book {', '.join(map(str, ORDER_BOOK_DEPTHS))}"
|
||||
f" levels deep, not {v}."
|
||||
)
|
||||
|
||||
return v
|
||||
|
||||
|
||||
class DeribitOrderBookData(Data):
|
||||
"""Deribit Order Book Data."""
|
||||
|
||||
symbol: str = Field(
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="When the book was published.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
side: str = Field(
|
||||
description="Whether the level is a bid or an ask.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
level: int = Field(
|
||||
description="How far the level sits from the top of the book.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
price: float = Field(
|
||||
description="The price of the level.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "category", "pinned": "left"},
|
||||
},
|
||||
)
|
||||
size: float = Field(
|
||||
description="The size resting at the level.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "series"}},
|
||||
)
|
||||
mark_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the exchange marks positions at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
index_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price of the index the instrument is priced against.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
change_id: int | None = Field(
|
||||
default=None,
|
||||
description="The revision of the book the level came from.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Change ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("timestamp", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v) if v else None
|
||||
|
||||
|
||||
class DeribitOrderBookFetcher(
|
||||
Fetcher[DeribitOrderBookQueryParams, list[DeribitOrderBookData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitOrderBookQueryParams:
|
||||
"""Transform the query.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If neither a symbol nor an instrument ID was given.
|
||||
"""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
query = DeribitOrderBookQueryParams(**params)
|
||||
|
||||
if not query.symbol and query.instrument_id is None:
|
||||
raise OpenBBError("Give either a symbol or an instrument ID.")
|
||||
|
||||
return query
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitOrderBookQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the instruments published a book.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols
|
||||
|
||||
calls: list[tuple[str, dict[str, Any] | None]]
|
||||
|
||||
if query.symbol:
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
calls = [
|
||||
(
|
||||
"get_order_book",
|
||||
{
|
||||
"instrument_name": perpetuals.get(symbol, symbol),
|
||||
"depth": query.depth,
|
||||
},
|
||||
)
|
||||
for symbol in symbols
|
||||
]
|
||||
else:
|
||||
calls = [
|
||||
(
|
||||
"get_order_book_by_instrument_id",
|
||||
{
|
||||
"instrument_id": query.instrument_id,
|
||||
"depth": query.depth,
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
results = await gather(calls, use_cache=False)
|
||||
data = [result for result in results if isinstance(result, dict) and result]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit published no book for the query.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitOrderBookQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitOrderBookData]:
|
||||
"""Transform the data to the model.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If every book came back with no resting orders.
|
||||
"""
|
||||
records: list[dict] = []
|
||||
|
||||
for book in data:
|
||||
context = {
|
||||
"symbol": book.get("instrument_name"),
|
||||
"timestamp": book.get("timestamp"),
|
||||
"mark_price": book.get("mark_price"),
|
||||
"index_price": book.get("index_price"),
|
||||
"change_id": book.get("change_id"),
|
||||
}
|
||||
|
||||
for side in ("bids", "asks"):
|
||||
for level, entry in enumerate(book.get(side) or [], start=1):
|
||||
records.append(
|
||||
{
|
||||
**context,
|
||||
"side": side[:-1],
|
||||
"level": level,
|
||||
"price": entry[0],
|
||||
"size": entry[1],
|
||||
}
|
||||
)
|
||||
|
||||
if not records:
|
||||
raise EmptyDataError("Deribit published no resting orders for the query.")
|
||||
|
||||
return [DeribitOrderBookData.model_validate(record) for record in records]
|
||||
@@ -0,0 +1,244 @@
|
||||
"""Deribit Settlements Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INSTRUMENT_CHOICES_ENDPOINT,
|
||||
MAX_SETTLEMENT_COUNT,
|
||||
SYMBOL_STYLE,
|
||||
Currencies,
|
||||
SettlementTypes,
|
||||
)
|
||||
|
||||
|
||||
class DeribitSettlementsQueryParams(QueryParams):
|
||||
"""Deribit Settlements Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_last_settlements_by_currency
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INSTRUMENT_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
symbol: str | None = Field(
|
||||
default=None,
|
||||
description="One or more instrument names. When given, the currency is"
|
||||
+ " ignored.",
|
||||
)
|
||||
currency: Currencies = Field(
|
||||
default="BTC", description="The settlement currency of the instruments."
|
||||
)
|
||||
settlement_type: SettlementTypes | None = Field(
|
||||
default=None,
|
||||
description="The kind of settlement. Default is all of them.",
|
||||
)
|
||||
start_date: Any | None = Field(
|
||||
default=None, description="Search back no further than this date."
|
||||
)
|
||||
limit: int = Field(
|
||||
default=100,
|
||||
description="The number of settlements to return, at most"
|
||||
f" {MAX_SETTLEMENT_COUNT}.",
|
||||
ge=1,
|
||||
le=MAX_SETTLEMENT_COUNT,
|
||||
)
|
||||
|
||||
|
||||
class DeribitSettlementsData(Data):
|
||||
"""Deribit Settlements Data."""
|
||||
|
||||
__alias_dict__ = {"symbol": "instrument_name", "settlement_type": "type"}
|
||||
|
||||
timestamp: datetime = Field(
|
||||
description="When the instrument settled.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "category", "pinned": "left"}
|
||||
},
|
||||
)
|
||||
symbol: str | None = Field(
|
||||
default=None,
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
settlement_type: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the event was a settlement or a delivery.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"headerName": "Type", "chartDataType": "excluded"}
|
||||
},
|
||||
)
|
||||
position: float | None = Field(
|
||||
default=None,
|
||||
description="The size that settled.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
index_price: float | None = Field(
|
||||
default=None,
|
||||
description="The level of the index at settlement.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
mark_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the instrument settled at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
profit_loss: float | None = Field(
|
||||
default=None,
|
||||
description="The profit and loss the settlement realized.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Profit & Loss",
|
||||
"chartDataType": "excluded",
|
||||
"renderFn": "greenRed",
|
||||
}
|
||||
},
|
||||
)
|
||||
session_profit_loss: float | None = Field(
|
||||
default=None,
|
||||
description="The profit and loss of the whole session.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Session Profit & Loss",
|
||||
"chartDataType": "excluded",
|
||||
"renderFn": "greenRed",
|
||||
}
|
||||
},
|
||||
)
|
||||
funding: float | None = Field(
|
||||
default=None,
|
||||
description="The funding the session paid.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
session_bankruptcy: float | None = Field(
|
||||
default=None,
|
||||
description="The loss the session could not cover.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
session_tax: float | None = Field(
|
||||
default=None,
|
||||
description="The socialised loss the session was charged.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
session_tax_rate: float | None = Field(
|
||||
default=None,
|
||||
description="The rate the socialised loss was charged at.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
socialized: float | None = Field(
|
||||
default=None,
|
||||
description="The loss spread across other accounts.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
|
||||
@field_validator("timestamp", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
|
||||
class DeribitSettlementsFetcher(
|
||||
Fetcher[DeribitSettlementsQueryParams, list[DeribitSettlementsData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitSettlementsQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitSettlementsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitSettlementsQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If nothing settled over the span.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather, request
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols, to_timestamp
|
||||
|
||||
shared = {
|
||||
"type": query.settlement_type,
|
||||
"count": query.limit,
|
||||
"search_start_timestamp": (
|
||||
to_timestamp(query.start_date) if query.start_date else None
|
||||
),
|
||||
}
|
||||
|
||||
if query.symbol:
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
"get_last_settlements_by_instrument",
|
||||
{
|
||||
"instrument_name": perpetuals.get(symbol, symbol),
|
||||
**shared,
|
||||
},
|
||||
)
|
||||
for symbol in symbols
|
||||
]
|
||||
)
|
||||
data = [
|
||||
record
|
||||
for result in results
|
||||
if isinstance(result, dict)
|
||||
for record in result.get("settlements") or []
|
||||
]
|
||||
else:
|
||||
result = await request(
|
||||
"get_last_settlements_by_currency",
|
||||
{"currency": query.currency.upper(), **shared},
|
||||
)
|
||||
data = (result or {}).get("settlements") or []
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit reports nothing settled over the span.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitSettlementsQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitSettlementsData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitSettlementsData.model_validate(record)
|
||||
for record in sorted(
|
||||
data, key=lambda d: d.get("timestamp") or 0, reverse=True
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,401 @@
|
||||
"""Deribit Ticker Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INSTRUMENT_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
)
|
||||
|
||||
|
||||
class DeribitTickerQueryParams(QueryParams):
|
||||
"""Deribit Ticker Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-ticker
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INSTRUMENT_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
symbol: str = Field(
|
||||
description="One or more instrument names. A perpetual can also be given"
|
||||
+ " by its shortened root, such as 'SOLUSDC'."
|
||||
)
|
||||
|
||||
|
||||
class DeribitTickerData(Data):
|
||||
"""Deribit Ticker Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"symbol": "instrument_name",
|
||||
"change_percent": "price_change",
|
||||
"implied_volatility": "mark_iv",
|
||||
"bid": "best_bid_price",
|
||||
"ask": "best_ask_price",
|
||||
"bid_size": "best_bid_amount",
|
||||
"ask_size": "best_ask_amount",
|
||||
"last_price": "last_price",
|
||||
}
|
||||
|
||||
symbol: str = Field(
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
timestamp: datetime | None = Field(
|
||||
default=None,
|
||||
description="When the quote was published.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
state: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the instrument's book is open.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
combo_state: str | None = Field(
|
||||
default=None,
|
||||
description="Whether the combo is still tradeable.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
last_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the instrument last traded at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
bid: float | None = Field(
|
||||
default=None,
|
||||
description="The highest price bid.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
ask: float | None = Field(
|
||||
default=None,
|
||||
description="The lowest price offered.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
bid_size: float | None = Field(
|
||||
default=None,
|
||||
description="The size resting at the best bid.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
ask_size: float | None = Field(
|
||||
default=None,
|
||||
description="The size resting at the best offer.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
implied_bid: float | None = Field(
|
||||
default=None,
|
||||
description="The bid implied by the legs of a combo.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
implied_ask: float | None = Field(
|
||||
default=None,
|
||||
description="The offer implied by the legs of a combo.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
mark_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the exchange marks positions at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
index_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price of the index the instrument is priced against.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
settlement_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the instrument last settled at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
delivery_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price an expired instrument was delivered at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
estimated_delivery_price: float | None = Field(
|
||||
default=None,
|
||||
description="What the instrument would deliver at right now.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
min_price: float | None = Field(
|
||||
default=None,
|
||||
description="The lowest price an order will be accepted at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
max_price: float | None = Field(
|
||||
default=None,
|
||||
description="The highest price an order will be accepted at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
high: float | None = Field(
|
||||
default=None,
|
||||
description="The highest price of the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
low: float | None = Field(
|
||||
default=None,
|
||||
description="The lowest price of the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
change_percent: float | None = Field(
|
||||
default=None,
|
||||
description="The price change over the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
volume: float | None = Field(
|
||||
default=None,
|
||||
description="The volume of the last 24 hours, in base currency.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
volume_notional: float | None = Field(
|
||||
default=None,
|
||||
description="The volume of the last 24 hours, in quote currency.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
volume_usd: float | None = Field(
|
||||
default=None,
|
||||
description="The volume of the last 24 hours, in USD.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {
|
||||
"headerName": "Volume USD",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
open_interest: float | None = Field(
|
||||
default=None,
|
||||
description="The contracts left outstanding.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
current_funding: float | None = Field(
|
||||
default=None,
|
||||
description="The funding rate a perpetual is paying right now.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
funding_8h: float | None = Field(
|
||||
default=None,
|
||||
description="The funding rate a perpetual paid over eight hours.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {
|
||||
"headerName": "Funding 8H",
|
||||
"chartDataType": "excluded",
|
||||
},
|
||||
},
|
||||
)
|
||||
interest_value: float | None = Field(
|
||||
default=None,
|
||||
description="The value realized funding on a perpetual is derived from.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
interest_rate: float | None = Field(
|
||||
default=None,
|
||||
description="The rate the exchange prices the option's greeks at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
underlying_index: str | None = Field(
|
||||
default=None,
|
||||
description="The future or index the option is priced against.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
underlying_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price of the underlying the option is priced against.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
implied_volatility: float | None = Field(
|
||||
default=None,
|
||||
description="The volatility implied by the mark price.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"headerName": "Mark IV", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
bid_iv: float | None = Field(
|
||||
default=None,
|
||||
description="The volatility implied by the best bid.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"headerName": "Bid IV", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
ask_iv: float | None = Field(
|
||||
default=None,
|
||||
description="The volatility implied by the best offer.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"headerName": "Ask IV", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
delta: float | None = Field(
|
||||
default=None,
|
||||
description="The sensitivity to the underlying's price.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
gamma: float | None = Field(
|
||||
default=None,
|
||||
description="The sensitivity of delta to the underlying's price.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
vega: float | None = Field(
|
||||
default=None,
|
||||
description="The sensitivity to implied volatility.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
theta: float | None = Field(
|
||||
default=None,
|
||||
description="The sensitivity to the passage of time.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
rho: float | None = Field(
|
||||
default=None,
|
||||
description="The sensitivity to the interest rate.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
|
||||
@field_validator("timestamp", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v) if v else None
|
||||
|
||||
@field_validator(
|
||||
"current_funding",
|
||||
"funding_8h",
|
||||
"interest_rate",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def as_percent(cls, v):
|
||||
"""Return a rate the exchange sends as a fraction in percent units."""
|
||||
return v * 100 if v is not None else None
|
||||
|
||||
|
||||
class DeribitTickerFetcher(Fetcher[DeribitTickerQueryParams, list[DeribitTickerData]]):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitTickerQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitTickerQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitTickerQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If none of the instruments published a quote.
|
||||
"""
|
||||
from openbb_deribit.utils.client import gather
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols
|
||||
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
resolved = [perpetuals.get(symbol, symbol) for symbol in symbols]
|
||||
results = await gather(
|
||||
[("ticker", {"instrument_name": s}) for s in resolved], use_cache=False
|
||||
)
|
||||
data = [result for result in results if isinstance(result, dict) and result]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no quote for {', '.join(resolved)}."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitTickerQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitTickerData]:
|
||||
"""Transform the data to the model."""
|
||||
from openbb_deribit.utils.helpers import flatten_ticker
|
||||
|
||||
return [
|
||||
DeribitTickerData.model_validate(flatten_ticker(record)) for record in data
|
||||
]
|
||||
@@ -0,0 +1,222 @@
|
||||
"""Deribit Trade Volumes Model."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field
|
||||
|
||||
DAY_VOLUMES = ("futures_volume", "calls_volume", "puts_volume", "spot_volume")
|
||||
|
||||
|
||||
def _traded(record: dict) -> float:
|
||||
"""Return everything that traded in one currency over the last day."""
|
||||
return sum(float(record.get(field) or 0) for field in DAY_VOLUMES)
|
||||
|
||||
|
||||
class DeribitTradeVolumesQueryParams(QueryParams):
|
||||
"""Deribit Trade Volumes Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_trade_volumes
|
||||
"""
|
||||
|
||||
extended: bool = Field(
|
||||
default=True,
|
||||
description="When True, adds the seven and thirty day volumes to the"
|
||||
+ " trailing twenty-four hours.",
|
||||
)
|
||||
|
||||
|
||||
class DeribitTradeVolumesData(Data):
|
||||
"""Deribit Trade Volumes Data."""
|
||||
|
||||
currency: str = Field(
|
||||
description="The currency the volume was traded in.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "category", "pinned": "left"}
|
||||
},
|
||||
)
|
||||
currency_pair: str | None = Field(
|
||||
default=None,
|
||||
description="The index the currency is priced against.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
futures_volume: float | None = Field(
|
||||
default=None,
|
||||
description="The futures volume of the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Futures Volume",
|
||||
"chartDataType": "series",
|
||||
}
|
||||
},
|
||||
)
|
||||
calls_volume: float | None = Field(
|
||||
default=None,
|
||||
description="The call volume of the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Calls Volume",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
puts_volume: float | None = Field(
|
||||
default=None,
|
||||
description="The put volume of the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Puts Volume",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
spot_volume: float | None = Field(
|
||||
default=None,
|
||||
description="The spot volume of the last 24 hours.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Spot Volume",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
futures_volume_7d: float | None = Field(
|
||||
default=None,
|
||||
description="The futures volume of the last seven days.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Futures Volume 7D",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
calls_volume_7d: float | None = Field(
|
||||
default=None,
|
||||
description="The call volume of the last seven days.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Calls Volume 7D",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
puts_volume_7d: float | None = Field(
|
||||
default=None,
|
||||
description="The put volume of the last seven days.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Puts Volume 7D",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
spot_volume_7d: float | None = Field(
|
||||
default=None,
|
||||
description="The spot volume of the last seven days.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Spot Volume 7D",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
futures_volume_30d: float | None = Field(
|
||||
default=None,
|
||||
description="The futures volume of the last thirty days.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Futures Volume 30D",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
calls_volume_30d: float | None = Field(
|
||||
default=None,
|
||||
description="The call volume of the last thirty days.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Calls Volume 30D",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
puts_volume_30d: float | None = Field(
|
||||
default=None,
|
||||
description="The put volume of the last thirty days.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Puts Volume 30D",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
spot_volume_30d: float | None = Field(
|
||||
default=None,
|
||||
description="The spot volume of the last thirty days.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Spot Volume 30D",
|
||||
"chartDataType": "excluded",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class DeribitTradeVolumesFetcher(
|
||||
Fetcher[DeribitTradeVolumesQueryParams, list[DeribitTradeVolumesData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitTradeVolumesQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitTradeVolumesQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitTradeVolumesQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange published no volumes.
|
||||
"""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
data = await request("get_trade_volumes", {"extended": query.extended})
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit published no trade volumes.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitTradeVolumesQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitTradeVolumesData]:
|
||||
"""Transform the data to the model.
|
||||
|
||||
The busiest currencies come first. Most of the currencies the exchange
|
||||
lists trade nothing on a given day, and ordering them by name buries the
|
||||
handful that did under the ones that did not. Volumes are counted in
|
||||
each currency's own units, so the ranking is by activity rather than by
|
||||
value.
|
||||
"""
|
||||
return [
|
||||
DeribitTradeVolumesData.model_validate(record)
|
||||
for record in sorted(
|
||||
data,
|
||||
key=lambda d: (-_traded(d), str(d.get("currency"))),
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,354 @@
|
||||
"""Deribit Trades Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
INSTRUMENT_CHOICES_ENDPOINT,
|
||||
MAX_TRADE_COUNT,
|
||||
SYMBOL_STYLE,
|
||||
ListingCurrencies,
|
||||
Sorting,
|
||||
TradeKinds,
|
||||
)
|
||||
|
||||
|
||||
class DeribitTradesQueryParams(QueryParams):
|
||||
"""Deribit Trades Query.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_last_trades_by_instrument
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"symbol": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": INSTRUMENT_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
symbol: str | None = Field(
|
||||
default=None,
|
||||
description="One or more instrument names. When given, the currency is"
|
||||
+ " ignored.",
|
||||
)
|
||||
currency: ListingCurrencies = Field(
|
||||
default="BTC", description="The settlement currency of the instruments."
|
||||
)
|
||||
kind: TradeKinds = Field(
|
||||
default="future",
|
||||
description="The kind of instrument. Only used when reading by currency,"
|
||||
+ " where the exchange rejects a request that leaves it out, and where"
|
||||
+ " spot is not offered at all. Read spot trades by symbol instead.",
|
||||
)
|
||||
start_date: Any | None = Field(
|
||||
default=None, description="Return trades from this date onwards."
|
||||
)
|
||||
end_date: Any | None = Field(
|
||||
default=None, description="Return trades up to this date."
|
||||
)
|
||||
limit: int = Field(
|
||||
default=100,
|
||||
description=f"The number of trades to return, at most {MAX_TRADE_COUNT}.",
|
||||
ge=1,
|
||||
le=MAX_TRADE_COUNT,
|
||||
)
|
||||
sorting: Sorting = Field(
|
||||
default="desc", description="The order the trades are returned in."
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _options_are_listed_by_underlying(self):
|
||||
"""Refuse a listing of options scoped to a currency that only settles them."""
|
||||
from openbb_deribit.utils.helpers import reject_options_by_settlement
|
||||
|
||||
if not self.symbol:
|
||||
reject_options_by_settlement(self.currency, self.kind)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class DeribitTradesData(Data):
|
||||
"""Deribit Trades Data."""
|
||||
|
||||
__alias_dict__ = {"symbol": "instrument_name"}
|
||||
|
||||
timestamp: datetime = Field(
|
||||
description="When the trade printed.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "category", "pinned": "left"}
|
||||
},
|
||||
)
|
||||
symbol: str = Field(
|
||||
description="The name of the instrument.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
direction: str | None = Field(
|
||||
default=None,
|
||||
description="Which side the aggressor was on.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"headerName": "Side", "chartDataType": "excluded"}
|
||||
},
|
||||
)
|
||||
price: float = Field(
|
||||
description="The price the trade printed at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
amount: float | None = Field(
|
||||
default=None,
|
||||
description="The size of the trade, in base currency.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
contracts: float | None = Field(
|
||||
default=None,
|
||||
description="The size of the trade, in contracts.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
iv: float | None = Field(
|
||||
default=None,
|
||||
description="The volatility the option trade implies.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"headerName": "IV", "chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
mark_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price the exchange marked the instrument at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
index_price: float | None = Field(
|
||||
default=None,
|
||||
description="The price of the index at the time of the trade.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "currency",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
tick_direction: int | None = Field(
|
||||
default=None,
|
||||
description="How the price moved against the previous trade. 0 and 1 are"
|
||||
+ " upticks, 2 and 3 are downticks.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {"chartDataType": "excluded", "hide": True}
|
||||
},
|
||||
)
|
||||
trade_id: str | None = Field(
|
||||
default=None,
|
||||
description="The identifier of the trade.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Trade ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
trade_seq: int | None = Field(
|
||||
default=None,
|
||||
description="The sequence number of the trade.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Trade Seq",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
block_trade_id: str | None = Field(
|
||||
default=None,
|
||||
description="The block trade the print belongs to.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Block Trade ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
liquidation: str | None = Field(
|
||||
default=None,
|
||||
description="Which side of the trade was liquidated.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
starbase_match_id: int | None = Field(
|
||||
default=None,
|
||||
description="The matching engine's identifier for the fill.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Match ID",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
starbase_timestamp: int | None = Field(
|
||||
default=None,
|
||||
description="When the matching engine recorded the fill, in nanoseconds"
|
||||
+ " since the epoch.",
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"headerName": "Match Timestamp",
|
||||
"cellDataType": "text",
|
||||
"chartDataType": "excluded",
|
||||
"hide": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("timestamp", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_timestamp(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
|
||||
class DeribitTradesFetcher(Fetcher[DeribitTradesQueryParams, list[DeribitTradesData]]):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitTradesQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitTradesQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitTradesQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the symbol is one of the spot pairs the exchange routes to Coinbase,
|
||||
which publish no trade history here.
|
||||
EmptyDataError
|
||||
If the exchange printed no trades over the span.
|
||||
"""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
from openbb_deribit.utils.client import gather, request
|
||||
from openbb_deribit.utils.constants import ROUTED_SPOT_ERROR
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols, to_timestamp
|
||||
|
||||
window = {
|
||||
"start_timestamp": (
|
||||
to_timestamp(query.start_date) if query.start_date else None
|
||||
),
|
||||
"end_timestamp": to_timestamp(query.end_date) if query.end_date else None,
|
||||
}
|
||||
timed = window["start_timestamp"] and window["end_timestamp"]
|
||||
shared = {"count": query.limit, "sorting": query.sorting}
|
||||
|
||||
if query.symbol:
|
||||
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
method = (
|
||||
"get_last_trades_by_instrument_and_time"
|
||||
if timed
|
||||
else "get_last_trades_by_instrument"
|
||||
)
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
method,
|
||||
{
|
||||
"instrument_name": perpetuals.get(symbol, symbol),
|
||||
**window,
|
||||
**shared,
|
||||
},
|
||||
)
|
||||
for symbol in symbols
|
||||
],
|
||||
use_cache=False,
|
||||
)
|
||||
data = [
|
||||
trade
|
||||
for result in results
|
||||
if isinstance(result, dict)
|
||||
for trade in result.get("trades") or []
|
||||
]
|
||||
|
||||
if not data and any(
|
||||
ROUTED_SPOT_ERROR in str(result)
|
||||
for result in results
|
||||
if isinstance(result, Exception)
|
||||
):
|
||||
raise OpenBBError(
|
||||
f"Deribit publishes no trade history for {', '.join(symbols)}."
|
||||
" Spot pairs it routes to Coinbase carry none."
|
||||
)
|
||||
else:
|
||||
method = (
|
||||
"get_last_trades_by_currency_and_time"
|
||||
if timed
|
||||
else "get_last_trades_by_currency"
|
||||
)
|
||||
try:
|
||||
result = await request(
|
||||
method,
|
||||
{
|
||||
"currency": query.currency.upper(),
|
||||
"kind": query.kind,
|
||||
**window,
|
||||
**shared,
|
||||
},
|
||||
use_cache=False,
|
||||
)
|
||||
except OpenBBError as error:
|
||||
if ROUTED_SPOT_ERROR not in str(error):
|
||||
raise
|
||||
|
||||
raise OpenBBError(
|
||||
"Deribit publishes no trade history for the spot pairs it"
|
||||
" routes to Coinbase. Read the other kinds by currency, or"
|
||||
" a specific spot pair by symbol."
|
||||
) from error
|
||||
|
||||
data = (result or {}).get("trades") or []
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError("Deribit printed no trades matching the query.")
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitTradesQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitTradesData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitTradesData.model_validate(record)
|
||||
for record in sorted(
|
||||
data,
|
||||
key=lambda d: d.get("timestamp") or 0,
|
||||
reverse=query.sorting != "asc",
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,216 @@
|
||||
"""Deribit Volatility Index Model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from openbb_deribit.utils.constants import (
|
||||
VOLATILITY_INDEX_CURRENCIES,
|
||||
VolatilityResolutions,
|
||||
)
|
||||
|
||||
|
||||
class DeribitVolatilityIndexQueryParams(QueryParams):
|
||||
"""Deribit Volatility Index Query.
|
||||
|
||||
Deribit computes DVOL for BTC and ETH only. Asking for it on a pegged
|
||||
currency returns an empty series, so none are offered.
|
||||
|
||||
Source: https://docs.deribit.com/api-reference/market-data/public-get_volatility_index_data
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {
|
||||
"currency": {
|
||||
"multiple_items_allowed": True,
|
||||
"x-widget_config": {
|
||||
"options": [
|
||||
{"label": name, "value": name}
|
||||
for name in VOLATILITY_INDEX_CURRENCIES
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
currency: str = Field(
|
||||
default="BTC", description="The currency whose volatility index to read."
|
||||
)
|
||||
|
||||
@field_validator("currency", mode="before")
|
||||
@classmethod
|
||||
def _listed_currencies(cls, value):
|
||||
"""Accept one currency or several, and refuse any the exchange omits.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If a currency the exchange does not publish this for is asked for.
|
||||
"""
|
||||
chosen = [
|
||||
item.strip().upper() for item in str(value).split(",") if item.strip()
|
||||
]
|
||||
unlisted = [item for item in chosen if item not in VOLATILITY_INDEX_CURRENCIES]
|
||||
|
||||
if unlisted:
|
||||
raise ValueError(
|
||||
f"Deribit publishes no volatility index for {unlisted}."
|
||||
f" It publishes {list(VOLATILITY_INDEX_CURRENCIES)}."
|
||||
)
|
||||
|
||||
return ",".join(chosen)
|
||||
|
||||
start_date: Any | None = Field(
|
||||
default=None, description="Return candles from this date onwards."
|
||||
)
|
||||
end_date: Any | None = Field(
|
||||
default=None, description="Return candles up to this date."
|
||||
)
|
||||
interval: VolatilityResolutions = Field(
|
||||
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
|
||||
)
|
||||
|
||||
|
||||
class DeribitVolatilityIndexData(Data):
|
||||
"""Deribit Volatility Index Data."""
|
||||
|
||||
date: datetime = Field(
|
||||
description="When the candle closed.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "category"}},
|
||||
)
|
||||
currency: str = Field(
|
||||
description="The currency the index belongs to.",
|
||||
json_schema_extra={"x-widget_config": {"chartDataType": "excluded"}},
|
||||
)
|
||||
open: float = Field(
|
||||
description="The level the candle opened at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
high: float = Field(
|
||||
description="The highest level of the candle.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
low: float = Field(
|
||||
description="The lowest level of the candle.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "excluded"},
|
||||
},
|
||||
)
|
||||
close: float = Field(
|
||||
description="The level the candle closed at.",
|
||||
json_schema_extra={
|
||||
"x-unit_measurement": "percent",
|
||||
"x-widget_config": {"chartDataType": "series"},
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_date(cls, v):
|
||||
"""Read the timestamp as a datetime."""
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
|
||||
return from_timestamp(v)
|
||||
|
||||
|
||||
class DeribitVolatilityIndexFetcher(
|
||||
Fetcher[DeribitVolatilityIndexQueryParams, list[DeribitVolatilityIndexData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the Deribit endpoint."""
|
||||
|
||||
require_credentials = False
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: dict[str, Any]) -> DeribitVolatilityIndexQueryParams:
|
||||
"""Transform the query."""
|
||||
return DeribitVolatilityIndexQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: DeribitVolatilityIndexQueryParams,
|
||||
credentials: dict[str, str] | None,
|
||||
**kwargs: Any,
|
||||
) -> list[dict]:
|
||||
"""Return the raw data from the Deribit endpoint.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the index published no candles over the span.
|
||||
"""
|
||||
from datetime import (
|
||||
datetime as dt,
|
||||
timedelta,
|
||||
timezone,
|
||||
)
|
||||
|
||||
from openbb_deribit.utils.client import gather
|
||||
from openbb_deribit.utils.constants import VOLATILITY_RESOLUTION_MAP
|
||||
from openbb_deribit.utils.helpers import to_timestamp
|
||||
|
||||
now = dt.now(timezone.utc)
|
||||
start = (
|
||||
to_timestamp(query.start_date)
|
||||
if query.start_date
|
||||
else to_timestamp(now - timedelta(days=30))
|
||||
)
|
||||
end = to_timestamp(query.end_date) if query.end_date else to_timestamp(now)
|
||||
currencies = [c.strip().upper() for c in query.currency.split(",") if c.strip()]
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
"get_volatility_index_data",
|
||||
{
|
||||
"currency": currency,
|
||||
"start_timestamp": start,
|
||||
"end_timestamp": end,
|
||||
"resolution": VOLATILITY_RESOLUTION_MAP[query.interval],
|
||||
},
|
||||
)
|
||||
for currency in currencies
|
||||
]
|
||||
)
|
||||
data = [
|
||||
{
|
||||
"date": candle[0],
|
||||
"currency": currency,
|
||||
"open": candle[1],
|
||||
"high": candle[2],
|
||||
"low": candle[3],
|
||||
"close": candle[4],
|
||||
}
|
||||
for currency, result in zip(currencies, results)
|
||||
if isinstance(result, dict)
|
||||
for candle in result.get("data") or []
|
||||
]
|
||||
|
||||
if not data:
|
||||
raise EmptyDataError(
|
||||
f"Deribit published no volatility index candles for"
|
||||
f" {', '.join(currencies)} over the span."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: DeribitVolatilityIndexQueryParams,
|
||||
data: list[dict],
|
||||
**kwargs: Any,
|
||||
) -> list[DeribitVolatilityIndexData]:
|
||||
"""Transform the data to the model."""
|
||||
return [
|
||||
DeribitVolatilityIndexData.model_validate(record)
|
||||
for record in sorted(data, key=lambda d: (d["date"], d["currency"]))
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
"""Deribit sub-routers."""
|
||||
@@ -0,0 +1,181 @@
|
||||
"""Deribit Futures sub-router."""
|
||||
|
||||
from openbb_core.app.model.command_context import CommandContext
|
||||
from openbb_core.app.model.example import APIEx
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.provider_interface import (
|
||||
ExtraParams,
|
||||
ProviderChoices,
|
||||
StandardParams,
|
||||
)
|
||||
from openbb_core.app.query import Query as OBBQuery
|
||||
from openbb_core.app.router import Router
|
||||
|
||||
from openbb_deribit import DERIVATIVES_INSTALLED
|
||||
|
||||
router = Router(prefix="/futures", description="Deribit futures data.")
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Get the underlyings with a listed futures curve.",
|
||||
parameters={},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def curve_choices() -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of every underlying with a listed futures curve."""
|
||||
from openbb_deribit.utils.choices import futures_root_choices
|
||||
|
||||
return await futures_root_choices()
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[APIEx(description="Get the listed perpetuals.", parameters={})],
|
||||
)
|
||||
async def perpetual_choices() -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of every perpetual the exchange lists."""
|
||||
from openbb_deribit.utils.choices import perpetual_choices as choices
|
||||
|
||||
return await choices()
|
||||
|
||||
|
||||
if not DERIVATIVES_INSTALLED:
|
||||
|
||||
@router.command(
|
||||
model="DeribitFuturesInstruments",
|
||||
widget_config={
|
||||
"name": "Deribit Futures Instruments",
|
||||
"description": "The contract specification of every listed future.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Futures",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Every future Deribit lists.",
|
||||
parameters={"provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def instruments(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the contract specification of every listed Deribit future."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
@router.command(
|
||||
model="DeribitFuturesCurve",
|
||||
widget_config={
|
||||
"name": "Deribit Futures Curve",
|
||||
"description": "The term structure of an underlying's dated futures,"
|
||||
+ " optionally against how it stood some hours ago.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Futures",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
"params": [{"paramName": "symbol", "value": "BTC"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": True, "chartType": "line"},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The current BTC futures term structure.",
|
||||
parameters={"symbol": "BTC", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="The curve against where it stood a day ago.",
|
||||
parameters={"symbol": "BTC", "hours_ago": 24, "provider": "deribit"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def curve(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the term structure of a Deribit underlying's dated futures."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
@router.command(
|
||||
model="DeribitFuturesHistorical",
|
||||
widget_config={
|
||||
"name": "Deribit Historical Prices",
|
||||
"description": "Candles for any listed instrument, not only futures.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Futures",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
"params": [{"paramName": "symbol", "value": "BTC-PERPETUAL"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": True, "chartType": "line"},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Daily candles for the BTC perpetual.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="Hourly candles over a named span.",
|
||||
parameters={
|
||||
"symbol": "BTC-PERPETUAL",
|
||||
"interval": "1h",
|
||||
"start_date": "2026-09-01",
|
||||
"end_date": "2026-09-15",
|
||||
"provider": "deribit",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def historical(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get candles for any listed Deribit instrument."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
@router.command(
|
||||
model="DeribitFuturesInfo",
|
||||
widget_config={
|
||||
"name": "Deribit Futures Info",
|
||||
"description": "The live quote and session statistics of a perpetual or"
|
||||
+ " dated future.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Futures",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 8},
|
||||
"params": [{"paramName": "symbol", "value": "BTC-PERPETUAL"}],
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The live quote of the BTC perpetual.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def info(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the live quote and session statistics of a Deribit future."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Deribit Index sub-router."""
|
||||
|
||||
from openbb_core.app.model.command_context import CommandContext
|
||||
from openbb_core.app.model.example import APIEx
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.provider_interface import (
|
||||
ExtraParams,
|
||||
ProviderChoices,
|
||||
StandardParams,
|
||||
)
|
||||
from openbb_core.app.query import Query as OBBQuery
|
||||
from openbb_core.app.router import Router
|
||||
|
||||
router = Router(prefix="/index", description="Deribit index data.")
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitIndexPrice",
|
||||
widget_config={
|
||||
"name": "Deribit Index Price",
|
||||
"description": "The current level of an index, and what it would deliver at.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Index",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 20, "h": 6},
|
||||
"params": [{"paramName": "index_name", "value": "btc_usd"}],
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The current level of the BTC index.",
|
||||
parameters={"index_name": "btc_usd", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="Multiple indexes are comma-separated.",
|
||||
parameters={"index_name": "btc_usd,eth_usd", "provider": "deribit"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def price(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the current level of a Deribit index."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitIndexHistorical",
|
||||
widget_config={
|
||||
"name": "Deribit Index History",
|
||||
"description": "The published level of an index over a span.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Index",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 14},
|
||||
"params": [{"paramName": "index_name", "value": "btc_usd"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": True, "chartType": "line"},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The BTC index over the last day.",
|
||||
parameters={"index_name": "btc_usd", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="The whole published history.",
|
||||
parameters={"index_name": "btc_usd", "span": "all", "provider": "deribit"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def historical(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the published level of a Deribit index over a span."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitDeliveryPrices",
|
||||
widget_config={
|
||||
"name": "Deribit Delivery Prices",
|
||||
"description": "The level each index has delivered at.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Index",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 14},
|
||||
"params": [{"paramName": "index_name", "value": "btc_usd"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": True, "chartType": "line"},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The level the BTC index has delivered at.",
|
||||
parameters={"index_name": "btc_usd", "provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def delivery_prices(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the level a Deribit index has delivered at."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
@@ -0,0 +1,256 @@
|
||||
"""Deribit Market sub-router."""
|
||||
|
||||
from openbb_core.app.model.command_context import CommandContext
|
||||
from openbb_core.app.model.example import APIEx
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.provider_interface import (
|
||||
ExtraParams,
|
||||
ProviderChoices,
|
||||
StandardParams,
|
||||
)
|
||||
from openbb_core.app.query import Query as OBBQuery
|
||||
from openbb_core.app.router import Router
|
||||
|
||||
router = Router(prefix="/market", description="Deribit market data.")
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitBookSummary",
|
||||
widget_config={
|
||||
"name": "Deribit Book Summary",
|
||||
"description": "The top of book and trailing session statistics of every"
|
||||
+ " instrument in a currency.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Market Data",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
"params": [{"paramName": "currency", "value": "BTC"}],
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Every BTC instrument's top of book.",
|
||||
parameters={"currency": "BTC", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="Only the futures.",
|
||||
parameters={"currency": "BTC", "kind": "future", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="One named instrument.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def book_summary(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the top of book and session statistics of Deribit instruments."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitOrderBook",
|
||||
widget_config={
|
||||
"name": "Deribit Order Book",
|
||||
"description": "The resting depth of one instrument, one row per level.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Market Data",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 20, "h": 15},
|
||||
"params": [{"paramName": "symbol", "value": "BTC-PERPETUAL"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": False},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The resting depth of the BTC perpetual.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="A book read by its numeric instrument identifier.",
|
||||
parameters={"instrument_id": 210838, "provider": "deribit"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def order_book(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the resting depth of a Deribit instrument, one row per level."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitTicker",
|
||||
widget_config={
|
||||
"name": "Deribit Ticker",
|
||||
"description": "The live quote, session statistics, and greeks of one"
|
||||
+ " instrument.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Market Data",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 8},
|
||||
"params": [{"paramName": "symbol", "value": "BTC-PERPETUAL"}],
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The live quote of the BTC perpetual.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="Multiple instruments are comma-separated.",
|
||||
parameters={"symbol": "BTC-PERPETUAL,ETH-PERPETUAL", "provider": "deribit"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def ticker(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the live quote, session statistics, and greeks of a Deribit instrument."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitTrades",
|
||||
widget_config={
|
||||
"name": "Deribit Trades",
|
||||
"description": "The prints that crossed, by instrument or across a currency.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Market Data",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
"params": [{"paramName": "symbol", "value": "BTC-PERPETUAL"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": False},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The most recent prints on the BTC perpetual.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="Every BTC option print over a span.",
|
||||
parameters={
|
||||
"currency": "BTC",
|
||||
"kind": "option",
|
||||
"start_date": "2026-09-01",
|
||||
"end_date": "2026-09-02",
|
||||
"provider": "deribit",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def trades(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the Deribit prints that crossed, by instrument or across a currency."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitTradeVolumes",
|
||||
widget_config={
|
||||
"name": "Deribit Trade Volumes",
|
||||
"description": "Traded volume by currency and product, over one, seven,"
|
||||
+ " and thirty days.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Market Data",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 12},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Traded volume by currency and product.",
|
||||
parameters={"provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def trade_volumes(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get Deribit's traded volume by currency and product."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitBlockRfqTrades",
|
||||
widget_config={
|
||||
"name": "Deribit Block RFQ Trades",
|
||||
"description": "The block requests that traded, one row per leg.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Market Data",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The block requests that most recently traded.",
|
||||
parameters={"provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def block_rfq_trades(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the Deribit block requests that traded, one row per leg."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitSettlements",
|
||||
widget_config={
|
||||
"name": "Deribit Settlements",
|
||||
"description": "The settlements, deliveries, and bankruptcies the exchange"
|
||||
+ " has processed.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Market Data",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
"params": [{"paramName": "currency", "value": "BTC"}],
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The most recent BTC settlements.",
|
||||
parameters={"currency": "BTC", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="Only the deliveries of one instrument.",
|
||||
parameters={
|
||||
"symbol": "BTC-PERPETUAL",
|
||||
"settlement_type": "settlement",
|
||||
"provider": "deribit",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def settlements(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the settlements, deliveries, and bankruptcies Deribit has processed."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
@@ -0,0 +1,992 @@
|
||||
"""Deribit Options sub-router."""
|
||||
|
||||
import logging
|
||||
from typing import Annotated, Any, Literal
|
||||
|
||||
from fastapi import Query as FastAPIQuery
|
||||
from openbb_core.app.model.command_context import CommandContext
|
||||
from openbb_core.app.model.example import APIEx
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.provider_interface import (
|
||||
ExtraParams,
|
||||
ProviderChoices,
|
||||
StandardParams,
|
||||
)
|
||||
from openbb_core.app.query import Query as OBBQuery
|
||||
from openbb_core.app.router import Router
|
||||
|
||||
from openbb_deribit import DERIVATIVES_INSTALLED
|
||||
from openbb_deribit.utils.constants import (
|
||||
EXPIRATION_CHOICES_ENDPOINT,
|
||||
STRATEGY_CHOICES_ENDPOINT,
|
||||
STRIKE_CHOICES_ENDPOINT,
|
||||
SYMBOL_STYLE,
|
||||
UNDERLYING_CHOICES_ENDPOINT,
|
||||
)
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
router = Router(prefix="/options", description="Deribit options data and analysis.")
|
||||
|
||||
_UNDERLYING = {
|
||||
"x-widget_config": {
|
||||
"groupId": "deribit_underlying",
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": UNDERLYING_CHOICES_ENDPOINT,
|
||||
"style": SYMBOL_STYLE,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _underlying() -> Any:
|
||||
"""Build the shared, group-linked underlying parameter."""
|
||||
return FastAPIQuery(
|
||||
description="The underlying root, as it appears in the instrument name.",
|
||||
json_schema_extra=_UNDERLYING,
|
||||
)
|
||||
|
||||
|
||||
def _excluded_query(description: str = "") -> Any:
|
||||
"""Build a query parameter that is accepted but hidden from the widget UI."""
|
||||
return FastAPIQuery(
|
||||
description=description,
|
||||
json_schema_extra={"x-widget_config": {"exclude": True}},
|
||||
)
|
||||
|
||||
|
||||
def _expiry_query(description: str, multi: bool = False) -> Any:
|
||||
"""Build an expiration query parameter backed by the expiration choices."""
|
||||
config: dict = {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": EXPIRATION_CHOICES_ENDPOINT,
|
||||
"optionsParams": {"symbol": "$symbol"},
|
||||
"style": SYMBOL_STYLE,
|
||||
}
|
||||
|
||||
if multi:
|
||||
config["multiSelect"] = True
|
||||
|
||||
return FastAPIQuery(
|
||||
description=description, json_schema_extra={"x-widget_config": config}
|
||||
)
|
||||
|
||||
|
||||
def _strike_query(description: str) -> Any:
|
||||
"""Build a strike query parameter backed by the strike choices."""
|
||||
return FastAPIQuery(
|
||||
description=description,
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": STRIKE_CHOICES_ENDPOINT,
|
||||
"optionsParams": {"symbol": "$symbol"},
|
||||
"style": SYMBOL_STYLE,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _choice_query(description: str, options: list[tuple[str, str]]) -> Any:
|
||||
"""Build a query parameter rendered as a labelled dropdown."""
|
||||
return FastAPIQuery(
|
||||
description=description,
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"options": [
|
||||
{"label": label, "value": value} for label, value in options
|
||||
]
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _legs_query(description: str) -> Any:
|
||||
"""Build the strategy parameter the optimizer's Legs column groups by."""
|
||||
return FastAPIQuery(
|
||||
description=description,
|
||||
json_schema_extra={
|
||||
"x-widget_config": {
|
||||
"type": "endpoint",
|
||||
"optionsEndpoint": STRATEGY_CHOICES_ENDPOINT,
|
||||
"optionsParams": {
|
||||
"symbol": "$symbol",
|
||||
"target_price": "$target_price",
|
||||
"target_date": "$target_date",
|
||||
"budget": "$budget",
|
||||
},
|
||||
"style": SYMBOL_STYLE,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _column(field: str, header: str, **extra) -> dict:
|
||||
"""Describe one grid column."""
|
||||
return {"field": field, "headerName": header, **extra}
|
||||
|
||||
|
||||
_MONEY = {"cellDataType": "number"}
|
||||
_COUNT = {"cellDataType": "number"}
|
||||
_NORMALIZED = {"cellDataType": "number", "formatterFn": "normalizedPercent"}
|
||||
|
||||
_PERCENT = {"cellDataType": "number", "formatterFn": "percent"}
|
||||
|
||||
_STRATEGY_TAIL = [
|
||||
_column("cost", "Cost", **_MONEY),
|
||||
_column("cost_percent", "Cost %", **_NORMALIZED),
|
||||
_column("max_loss", "Max Loss", **_MONEY),
|
||||
_column("breakeven_lower", "Breakeven Lower", **_MONEY),
|
||||
_column("breakeven_upper", "Breakeven Upper", **_MONEY),
|
||||
]
|
||||
|
||||
CHAIN_COLUMNS = [
|
||||
_column("contract_symbol", "Contract", pinned="left", cellDataType="text"),
|
||||
_column("expiration", "Expiration", cellDataType="text"),
|
||||
_column("dte", "DTE", **_COUNT),
|
||||
_column("strike", "Strike", **_MONEY),
|
||||
_column("option_type", "Type", cellDataType="text"),
|
||||
_column("open_interest", "OI", **_COUNT),
|
||||
_column("volume", "Volume", **_COUNT),
|
||||
_column("volume_notional", "Volume Notional", **_MONEY),
|
||||
_column("bid", "Bid", **_MONEY),
|
||||
_column("bid_size", "Bid Size", **_COUNT),
|
||||
_column("ask", "Ask", **_MONEY),
|
||||
_column("ask_size", "Ask Size", **_COUNT),
|
||||
_column("mark", "Mark", **_MONEY),
|
||||
_column("last_trade_price", "Last", **_MONEY),
|
||||
_column("high", "High", **_MONEY),
|
||||
_column("low", "Low", **_MONEY),
|
||||
_column("change_percent", "Change %", **_PERCENT, renderFn="greenRed"),
|
||||
_column("implied_volatility", "Mark IV", **_PERCENT),
|
||||
_column("bid_iv", "Bid IV", **_PERCENT),
|
||||
_column("ask_iv", "Ask IV", **_PERCENT),
|
||||
_column("delta", "Delta", cellDataType="number"),
|
||||
_column("gamma", "Gamma", cellDataType="number"),
|
||||
_column("theta", "Theta", cellDataType="number"),
|
||||
_column("vega", "Vega", cellDataType="number"),
|
||||
_column("rho", "Rho", cellDataType="number"),
|
||||
_column("settlement_price", "Settlement Price", **_MONEY),
|
||||
_column("min_price", "Min Price", **_MONEY),
|
||||
_column("max_price", "Max Price", **_MONEY),
|
||||
_column("interest_rate", "Interest Rate", **_PERCENT),
|
||||
_column("contract_size", "Contract Size", **_COUNT),
|
||||
_column("is_inverse", "Is Inverse", cellDataType="boolean"),
|
||||
_column("underlying_symbol", "Underlying", cellDataType="text"),
|
||||
_column("underlying_price", "Underlying Forward", **_MONEY),
|
||||
_column("underlying_spot_price", "Underlying Spot", **_MONEY),
|
||||
_column("timestamp", "Timestamp", cellDataType="text"),
|
||||
]
|
||||
|
||||
OPTIMIZER_COLUMNS = [
|
||||
_column("strategy", "Strategy", pinned="left", cellDataType="text"),
|
||||
_column("code", "Combo Type", cellDataType="text"),
|
||||
_column(
|
||||
"legs",
|
||||
"Legs",
|
||||
cellDataType="text",
|
||||
formatterFn="none",
|
||||
renderFn="cellOnClick",
|
||||
renderFnParams={
|
||||
"actionType": "groupBy",
|
||||
"groupBy": {"paramName": "legs"},
|
||||
},
|
||||
),
|
||||
_column("combo", "Listed Combo", cellDataType="text"),
|
||||
_column("contracts", "Contracts", **_COUNT),
|
||||
_column("cost", "Net Premium", **_MONEY),
|
||||
_column("expected_profit", "Expected Profit", **_MONEY, renderFn="greenRed"),
|
||||
_column("expected_return", "Expected Return", **_NORMALIZED, renderFn="greenRed"),
|
||||
_column("max_profit", "Max Profit", **_MONEY),
|
||||
_column("max_loss", "Max Loss", **_MONEY),
|
||||
_column("breakeven_lower", "Breakeven Lower", **_MONEY),
|
||||
_column("breakeven_upper", "Breakeven Upper", **_MONEY),
|
||||
]
|
||||
|
||||
STRADDLE_COLUMNS = [
|
||||
_column("expiration", "Expiration", pinned="left", cellDataType="text"),
|
||||
_column("dte", "DTE", **_COUNT),
|
||||
_column("underlying_price", "Underlying Price", **_MONEY),
|
||||
_column("strike", "Strike", **_MONEY),
|
||||
_column("call_premium", "Call Premium", **_MONEY),
|
||||
_column("put_premium", "Put Premium", **_MONEY),
|
||||
] + _STRATEGY_TAIL
|
||||
|
||||
STRANGLE_COLUMNS = [
|
||||
_column("expiration", "Expiration", pinned="left", cellDataType="text"),
|
||||
_column("dte", "DTE", **_COUNT),
|
||||
_column("underlying_price", "Underlying Price", **_MONEY),
|
||||
_column("put_strike", "Put Strike", **_MONEY),
|
||||
_column("call_strike", "Call Strike", **_MONEY),
|
||||
_column("call_premium", "Call Premium", **_MONEY),
|
||||
_column("put_premium", "Put Premium", **_MONEY),
|
||||
] + _STRATEGY_TAIL
|
||||
|
||||
SPREAD_COLUMNS = [
|
||||
_column("strategy", "Strategy", pinned="left", cellDataType="text"),
|
||||
_column("expiration", "Expiration", cellDataType="text"),
|
||||
_column("dte", "DTE", **_COUNT),
|
||||
_column("underlying_price", "Underlying Price", **_MONEY),
|
||||
_column("bought_strike", "Bought Strike", **_MONEY),
|
||||
_column("sold_strike", "Sold Strike", **_MONEY),
|
||||
_column("bought_premium", "Bought Premium", **_MONEY),
|
||||
_column("sold_premium", "Sold Premium", **_MONEY),
|
||||
_column("cost", "Cost", **_MONEY),
|
||||
_column("cost_percent", "Cost %", **_NORMALIZED),
|
||||
_column("max_profit", "Max Profit", **_MONEY),
|
||||
_column("max_loss", "Max Loss", **_MONEY),
|
||||
_column("breakeven", "Breakeven", **_MONEY),
|
||||
]
|
||||
|
||||
|
||||
def _table_widget(name: str, description: str, columns: list, height: int = 16) -> dict:
|
||||
"""Build the Workspace table-widget config for an analysis view."""
|
||||
return {
|
||||
"name": f"Deribit {name}",
|
||||
"description": description,
|
||||
"category": "Crypto",
|
||||
"subCategory": "Options",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": height},
|
||||
"data": {"table": {"columnsDefs": columns}},
|
||||
}
|
||||
|
||||
|
||||
def _chart_widget(name: str, description: str, height: int = 18) -> dict:
|
||||
"""Build the Workspace chart-widget config for a Plotly route."""
|
||||
return {
|
||||
"widget_config": {
|
||||
"name": f"Deribit {name}",
|
||||
"description": description,
|
||||
"type": "chart",
|
||||
"raw": True,
|
||||
"category": "Crypto",
|
||||
"subCategory": "Options",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": height},
|
||||
"refetchInterval": False,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def _loaded(symbol: str) -> tuple:
|
||||
"""Return the chain, its spot price, and how it settles."""
|
||||
from openbb_deribit.utils.options.chain import (
|
||||
is_inverse,
|
||||
load_chain,
|
||||
settlement_currency,
|
||||
underlying_price,
|
||||
)
|
||||
|
||||
frame = await load_chain(symbol)
|
||||
|
||||
return (
|
||||
frame,
|
||||
underlying_price(frame),
|
||||
is_inverse(frame),
|
||||
settlement_currency(frame, symbol),
|
||||
)
|
||||
|
||||
|
||||
def _figure_json(output: Any, raw: bool) -> Any:
|
||||
"""Return the drawn figure, or the rows behind it when raw is asked for.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
output : Any
|
||||
The OBBject a view returned.
|
||||
raw : bool
|
||||
Whether the widget's raw toggle is on.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
The Plotly figure as plain JSON, or the list of rows.
|
||||
"""
|
||||
import json
|
||||
|
||||
from openbb_deribit.utils.options.theme import CHART_CONFIG
|
||||
|
||||
if raw or output.chart is None:
|
||||
return [row.model_dump() for row in (output.results or [])]
|
||||
|
||||
figure_json = json.loads(output.chart.fig.to_json())
|
||||
figure_json["config"] = CHART_CONFIG
|
||||
|
||||
return figure_json
|
||||
|
||||
|
||||
async def _chart_json(builder, symbol: str, theme: str, raw: bool, **kwargs) -> Any:
|
||||
"""Load the chain, build the view, and return rows or figure JSON.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
If the exchange lists no options on the underlying.
|
||||
"""
|
||||
from fastapi import HTTPException
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
from openbb_deribit.utils.options.data_handler import load_symbol
|
||||
|
||||
try:
|
||||
data = await load_symbol(symbol)
|
||||
except OpenBBError as exc:
|
||||
_logger.exception("Loading the Deribit option chain for %s failed", symbol)
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Deribit lists no options on {symbol}.",
|
||||
) from exc
|
||||
|
||||
return _figure_json(builder(data, theme=theme, **kwargs), raw)
|
||||
|
||||
|
||||
async def _chain_or_404(symbol: str) -> tuple:
|
||||
"""Load a chain, answering 404 when the exchange lists none.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
If the exchange publishes no option quotes for the underlying.
|
||||
"""
|
||||
from fastapi import HTTPException
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
try:
|
||||
return await _loaded(symbol)
|
||||
except OpenBBError as exc:
|
||||
_logger.exception("Loading the Deribit option chain for %s failed", symbol)
|
||||
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Deribit lists no options on {symbol}.",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(description="Get the underlyings with listed options.", parameters={})
|
||||
],
|
||||
)
|
||||
async def underlying_choices() -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of every underlying with listed options."""
|
||||
from openbb_deribit.utils.choices import options_root_choices
|
||||
|
||||
return await options_root_choices()
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Get the expirations listed on an underlying.",
|
||||
parameters={"symbol": "BTC"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def expiration_choices(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
) -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of every expiration the underlying lists."""
|
||||
from openbb_deribit.utils.options.chain import expirations, load_chain
|
||||
|
||||
frame = await load_chain(symbol)
|
||||
|
||||
return [
|
||||
{
|
||||
"label": f"{day} ({int(frame[frame['expiration'] == day]['dte'].iloc[0])}d)",
|
||||
"value": str(day),
|
||||
}
|
||||
for day in expirations(frame)
|
||||
]
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Get the strikes listed on an underlying.",
|
||||
parameters={"symbol": "BTC"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def strike_choices(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
) -> list[dict]:
|
||||
"""``[{label, value}]`` of every strike the underlying lists."""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
from openbb_deribit.utils.options.data_handler import get_strikes, load_symbol
|
||||
|
||||
try:
|
||||
data = await load_symbol(symbol)
|
||||
except OpenBBError:
|
||||
return []
|
||||
|
||||
return get_strikes(data)
|
||||
|
||||
|
||||
if not DERIVATIVES_INSTALLED:
|
||||
|
||||
@router.command(
|
||||
model="DeribitOptionsChains",
|
||||
widget_config={
|
||||
"name": "Deribit Options Chains",
|
||||
"description": "The full option chain of an underlying, with greeks and"
|
||||
+ " implied volatility.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Options",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 20},
|
||||
"params": [{"paramName": "symbol", "value": "BTC"}],
|
||||
"data": {"table": {"columnsDefs": CHAIN_COLUMNS}},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The full BTC option chain.",
|
||||
parameters={"symbol": "BTC", "provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def chains(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the full Deribit option chain of an underlying, with greeks."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
def _quote(symbol: str) -> str:
|
||||
"""Return the currency a position in this underlying is priced in."""
|
||||
return "USDC" if symbol.upper().endswith("_USDC") else "USD"
|
||||
|
||||
|
||||
async def _combos(symbol: str) -> list:
|
||||
"""Return the combo books listed on an underlying."""
|
||||
from openbb_deribit.utils.options.chain import load_combos
|
||||
|
||||
return await load_combos(symbol)
|
||||
|
||||
|
||||
def _ranked_row(item: dict) -> dict:
|
||||
"""Describe one ranked strategy as a grid row."""
|
||||
crossings = item.get("breakevens") or []
|
||||
|
||||
return {
|
||||
"strategy": item["strategy"],
|
||||
"code": item["code"],
|
||||
"legs": item["legs"],
|
||||
"combo": item["combo"],
|
||||
"contracts": item["contracts"],
|
||||
"cost": item["cost"],
|
||||
"expected_profit": item["expected_profit"],
|
||||
"expected_return": item["expected_return"],
|
||||
"max_profit": item["max_profit"],
|
||||
"max_loss": item["max_loss"],
|
||||
"breakeven_lower": crossings[0] if crossings else None,
|
||||
"breakeven_upper": crossings[-1] if len(crossings) > 1 else None,
|
||||
}
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config=_table_widget(
|
||||
"Strategy Optimizer",
|
||||
"Every Deribit combo structure the expiration can form, bought and sold,"
|
||||
" priced from its listed combo book where one trades, sized to the"
|
||||
" capital it puts at risk, and ranked by what it returns if the"
|
||||
" underlying reaches a price.",
|
||||
OPTIMIZER_COLUMNS,
|
||||
18,
|
||||
),
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Rank strategies for a move to 100,000 by year end.",
|
||||
parameters={
|
||||
"symbol": "BTC",
|
||||
"target_price": 100000,
|
||||
"target_date": "2026-12-25",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def optimizer(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
target_price: Annotated[
|
||||
float | None,
|
||||
FastAPIQuery(description="The price the underlying is expected to reach."),
|
||||
] = None,
|
||||
target_date: Annotated[
|
||||
str | None,
|
||||
FastAPIQuery(description="When it is expected to reach it."),
|
||||
] = None,
|
||||
budget: Annotated[
|
||||
float,
|
||||
FastAPIQuery(
|
||||
description="The capital to put at risk, in the quote currency. Each"
|
||||
" position is sized so its worst loss across a threefold move either"
|
||||
" way is this much."
|
||||
),
|
||||
] = 5000.0,
|
||||
limit: Annotated[
|
||||
int,
|
||||
FastAPIQuery(
|
||||
description="How many structures to return, each at its best strikes."
|
||||
),
|
||||
] = 40,
|
||||
legs: Annotated[
|
||||
str | None,
|
||||
_legs_query(
|
||||
"The strategy picked from the Legs column, shared with the payoff"
|
||||
" chart. It does not change the ranking."
|
||||
),
|
||||
] = None,
|
||||
) -> OBBject:
|
||||
"""Rank every combo structure against a view on the underlying."""
|
||||
from openbb_deribit.utils.options.chain import nearest_expiration
|
||||
from openbb_deribit.utils.options.optimizer import rank
|
||||
|
||||
frame, spot, inverse, settles = await _chain_or_404(symbol)
|
||||
target = float(target_price) if target_price else spot
|
||||
expiration = nearest_expiration(frame, target_date or frame["expiration"].min())
|
||||
ranked = rank(
|
||||
frame, expiration, spot, target, budget, inverse, limit, await _combos(symbol)
|
||||
)
|
||||
|
||||
return OBBject(
|
||||
results=[_ranked_row(item) for item in ranked],
|
||||
provider="deribit",
|
||||
extra={
|
||||
"results_metadata": {
|
||||
"symbol": symbol,
|
||||
"underlying_price": spot,
|
||||
"target_price": target,
|
||||
"expiration": str(expiration),
|
||||
"quote_currency": _quote(symbol),
|
||||
"settlement_currency": settles,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config=_table_widget(
|
||||
"Straddle",
|
||||
"A long straddle at the money, priced at every expiration.",
|
||||
STRADDLE_COLUMNS,
|
||||
),
|
||||
examples=[APIEx(description="Price the BTC straddles.", parameters={})],
|
||||
)
|
||||
async def straddle(symbol: Annotated[str, _underlying()] = "BTC") -> OBBject:
|
||||
"""Price a long straddle at each expiration."""
|
||||
from openbb_deribit.utils.options.strategies import straddles
|
||||
|
||||
frame, spot, _inverse, _settles = await _chain_or_404(symbol)
|
||||
|
||||
return OBBject(results=straddles(frame, spot), provider="deribit")
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config=_table_widget(
|
||||
"Strangle",
|
||||
"A long strangle out of the money, priced at every expiration.",
|
||||
STRANGLE_COLUMNS,
|
||||
),
|
||||
examples=[APIEx(description="Price the BTC strangles.", parameters={})],
|
||||
)
|
||||
async def strangle(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
moneyness: Annotated[
|
||||
float,
|
||||
FastAPIQuery(description="How far out of the money each leg sits, in percent."),
|
||||
] = 5.0,
|
||||
) -> OBBject:
|
||||
"""Price a long strangle at each expiration."""
|
||||
from openbb_deribit.utils.options.strategies import strangles
|
||||
|
||||
frame, spot, _inverse, _settles = await _chain_or_404(symbol)
|
||||
|
||||
return OBBject(
|
||||
results=strangles(frame, spot, abs(moneyness) / 100), provider="deribit"
|
||||
)
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config=_table_widget(
|
||||
"Vertical Spreads",
|
||||
"A bull call and a bear put spread at every expiration.",
|
||||
SPREAD_COLUMNS,
|
||||
),
|
||||
examples=[APIEx(description="Price the BTC vertical spreads.", parameters={})],
|
||||
)
|
||||
async def spreads(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
moneyness: Annotated[
|
||||
float,
|
||||
FastAPIQuery(description="How far out the sold leg sits, in percent."),
|
||||
] = 5.0,
|
||||
) -> OBBject:
|
||||
"""Price a bull call and a bear put spread at each expiration."""
|
||||
from openbb_deribit.utils.options.strategies import verticals
|
||||
|
||||
frame, spot, _inverse, _settles = await _chain_or_404(symbol)
|
||||
|
||||
return OBBject(
|
||||
results=verticals(frame, spot, abs(moneyness) / 100), provider="deribit"
|
||||
)
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Get the strategies the optimizer ranks for a view.",
|
||||
parameters={"symbol": "BTC"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def strategy_choices(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
target_price: Annotated[float | None, FastAPIQuery()] = None,
|
||||
target_date: Annotated[str | None, FastAPIQuery()] = None,
|
||||
budget: Annotated[float, FastAPIQuery()] = 5000.0,
|
||||
) -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of the strategies the optimizer ranks for a view."""
|
||||
from openbb_deribit.utils.options.chain import nearest_expiration
|
||||
from openbb_deribit.utils.options.optimizer import SHORTLIST, rank
|
||||
|
||||
frame, spot, inverse, _settles = await _loaded(symbol)
|
||||
target = float(target_price) if target_price else spot
|
||||
expiration = nearest_expiration(frame, target_date or frame["expiration"].min())
|
||||
ranked = rank(
|
||||
frame,
|
||||
expiration,
|
||||
spot,
|
||||
target,
|
||||
budget,
|
||||
inverse,
|
||||
SHORTLIST,
|
||||
await _combos(symbol),
|
||||
)
|
||||
|
||||
return [
|
||||
{"label": f"{item['strategy']} — {item['legs']}", "value": item["legs"]}
|
||||
for item in ranked
|
||||
]
|
||||
|
||||
|
||||
async def payoff_chart(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
target_price: Annotated[
|
||||
float | None,
|
||||
FastAPIQuery(description="The price the underlying is expected to reach."),
|
||||
] = None,
|
||||
target_date: Annotated[
|
||||
str | None, FastAPIQuery(description="When it is expected to reach it.")
|
||||
] = None,
|
||||
budget: Annotated[
|
||||
float,
|
||||
FastAPIQuery(
|
||||
description="The capital to put at risk, in the quote currency. Each"
|
||||
" position is sized so its worst loss across a threefold move either"
|
||||
" way is this much."
|
||||
),
|
||||
] = 5000.0,
|
||||
legs: Annotated[
|
||||
str | None,
|
||||
_legs_query(
|
||||
"The contracts to draw, as 'Buy SYMBOL / Sell 2 SYMBOL'. Click the"
|
||||
" Legs cell of a strategy in the optimizer to set it, or pick one"
|
||||
" here. Left empty, the best strategy for the view is drawn."
|
||||
),
|
||||
] = None,
|
||||
raw: Annotated[bool, _excluded_query()] = False,
|
||||
theme: Annotated[str, _excluded_query()] = "dark",
|
||||
):
|
||||
"""Draw what one position returns across a range of underlying prices.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
If the expiration lists no strategy, or does not list the position.
|
||||
"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
from openbb_deribit.utils.options.chain import nearest_expiration, quotes_at
|
||||
from openbb_deribit.utils.options.create_payoff import create_payoff
|
||||
from openbb_deribit.utils.options.optimizer import draw, rank, rebuild, score
|
||||
|
||||
frame, spot, inverse, settles = await _chain_or_404(symbol)
|
||||
target = float(target_price) if target_price else spot
|
||||
expiration = nearest_expiration(frame, target_date or frame["expiration"].min())
|
||||
books = await _combos(symbol)
|
||||
|
||||
if legs:
|
||||
try:
|
||||
built = rebuild(legs, quotes_at(frame), books, spot, inverse)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
scored = score(built, spot, target, inverse)
|
||||
|
||||
if scored is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="That position puts nothing at risk, so a budget does not"
|
||||
" size it. Pick one the optimizer ranked.",
|
||||
)
|
||||
|
||||
chosen = draw(scored, spot, target, budget, inverse)
|
||||
else:
|
||||
ranked = rank(frame, expiration, spot, target, budget, inverse, 1, books)
|
||||
|
||||
if not ranked:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"No strategy on the {expiration} expiration can be sized"
|
||||
f" to {budget:,.0f} {_quote(symbol)}.",
|
||||
)
|
||||
|
||||
chosen = ranked[0]
|
||||
|
||||
output = create_payoff(
|
||||
{
|
||||
**chosen,
|
||||
"spot": spot,
|
||||
"target": target,
|
||||
"quote": _quote(symbol),
|
||||
"settles": settles,
|
||||
},
|
||||
theme,
|
||||
)
|
||||
|
||||
return _figure_json(output, raw)
|
||||
|
||||
|
||||
async def smile_chart(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
expirations: Annotated[
|
||||
str | None,
|
||||
_expiry_query("Up to five expiration dates (comma-separated).", multi=True),
|
||||
] = None,
|
||||
otm: Annotated[
|
||||
bool, FastAPIQuery(description="Show only out-of-the-money contracts.")
|
||||
] = False,
|
||||
skew: Annotated[
|
||||
bool, FastAPIQuery(description="Plot skew relative to ATM instead of IV.")
|
||||
] = False,
|
||||
raw: Annotated[bool, _excluded_query()] = False,
|
||||
theme: Annotated[str, _excluded_query()] = "dark",
|
||||
):
|
||||
"""Implied-volatility smile / skew across strikes."""
|
||||
from openbb_deribit.utils.options.create_smile import create_smile
|
||||
|
||||
return await _chart_json(
|
||||
create_smile, symbol, theme, raw, expirations=expirations, otm=otm, skew=skew
|
||||
)
|
||||
|
||||
|
||||
async def surface_chart(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
metric: Annotated[
|
||||
Literal[
|
||||
"implied_volatility", "delta", "gamma", "theta", "vega", "rho", "dex", "gex"
|
||||
],
|
||||
_choice_query(
|
||||
"The surface metric.",
|
||||
[
|
||||
("Implied Volatility", "implied_volatility"),
|
||||
("Delta", "delta"),
|
||||
("Gamma", "gamma"),
|
||||
("Theta", "theta"),
|
||||
("Vega", "vega"),
|
||||
("Rho", "rho"),
|
||||
("Delta Exposure", "dex"),
|
||||
("Gamma Exposure", "gex"),
|
||||
],
|
||||
),
|
||||
] = "implied_volatility",
|
||||
option_type: Annotated[
|
||||
Literal["otm", "itm", "puts", "calls"],
|
||||
_choice_query(
|
||||
"Which side of the chain to plot.",
|
||||
[
|
||||
("Out-Of-The-Money", "otm"),
|
||||
("In-The-Money", "itm"),
|
||||
("Puts", "puts"),
|
||||
("Calls", "calls"),
|
||||
],
|
||||
),
|
||||
] = "otm",
|
||||
dte_min: Annotated[
|
||||
int | None, FastAPIQuery(description="Minimum days to expiry.")
|
||||
] = None,
|
||||
dte_max: Annotated[
|
||||
int | None, FastAPIQuery(description="Maximum days to expiry.")
|
||||
] = None,
|
||||
moneyness: Annotated[
|
||||
float | None,
|
||||
FastAPIQuery(description="Percent from the underlying price to include."),
|
||||
] = None,
|
||||
oi: Annotated[
|
||||
bool, FastAPIQuery(description="Drop contracts with no open interest.")
|
||||
] = False,
|
||||
volume: Annotated[
|
||||
bool, FastAPIQuery(description="Drop contracts that have not traded.")
|
||||
] = False,
|
||||
raw: Annotated[bool, _excluded_query()] = False,
|
||||
theme: Annotated[str, _excluded_query()] = "dark",
|
||||
):
|
||||
"""Implied-volatility or greeks 3-D surface over DTE and strike."""
|
||||
from openbb_deribit.utils.options.create_surface import create_surface
|
||||
|
||||
dte_range = [dte_min or 0, dte_max or 5000] if (dte_min or dte_max) else None
|
||||
|
||||
return await _chart_json(
|
||||
create_surface,
|
||||
symbol,
|
||||
theme,
|
||||
raw,
|
||||
option_type=option_type,
|
||||
metric=metric,
|
||||
dte_range=dte_range,
|
||||
moneyness=moneyness,
|
||||
oi=oi,
|
||||
volume=volume,
|
||||
)
|
||||
|
||||
|
||||
async def stats_chart(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
by: Annotated[
|
||||
Literal["strike", "expiration"],
|
||||
_choice_query(
|
||||
"Aggregate by strike or expiration.",
|
||||
[("Expiration", "expiration"), ("Strike", "strike")],
|
||||
),
|
||||
] = "expiration",
|
||||
metric: Annotated[
|
||||
Literal["oi", "volume"],
|
||||
_choice_query(
|
||||
"Open interest or volume.",
|
||||
[("Open Interest", "oi"), ("Volume", "volume")],
|
||||
),
|
||||
] = "oi",
|
||||
date: Annotated[
|
||||
str | None,
|
||||
_expiry_query("Expiry to view by strike (switches 'by' to strike)."),
|
||||
] = None,
|
||||
unit: Annotated[
|
||||
Literal["value", "percent", "pcr"],
|
||||
_choice_query(
|
||||
"Raw values, share of total, or put/call ratio.",
|
||||
[("Value", "value"), ("Percent", "percent"), ("Put/Call Ratio", "pcr")],
|
||||
),
|
||||
] = "value",
|
||||
raw: Annotated[bool, _excluded_query()] = False,
|
||||
theme: Annotated[str, _excluded_query()] = "dark",
|
||||
):
|
||||
"""Open-interest or volume statistics by strike or expiration."""
|
||||
from openbb_deribit.utils.options.create_stats import create_stats
|
||||
|
||||
return await _chart_json(
|
||||
create_stats, symbol, theme, raw, by=by, metric=metric, date=date, unit=unit
|
||||
)
|
||||
|
||||
|
||||
async def term_structure_chart(
|
||||
symbol: Annotated[str, _underlying()] = "BTC",
|
||||
strike: Annotated[
|
||||
float | None, _strike_query("Target strike. Default is nearest OTM per expiry.")
|
||||
] = None,
|
||||
moneyness: Annotated[
|
||||
float | None,
|
||||
FastAPIQuery(description="Percent out-of-the-money instead of a fixed strike."),
|
||||
] = None,
|
||||
metric: Annotated[
|
||||
Literal["iv", "price"],
|
||||
_choice_query(
|
||||
"Implied volatility or price.",
|
||||
[("Implied Volatility", "iv"), ("Price", "price")],
|
||||
),
|
||||
] = "iv",
|
||||
option_type: Annotated[
|
||||
Literal["both", "calls", "puts"],
|
||||
_choice_query(
|
||||
"Which side of the chain to plot.",
|
||||
[("Both", "both"), ("Calls", "calls"), ("Puts", "puts")],
|
||||
),
|
||||
] = "both",
|
||||
raw: Annotated[bool, _excluded_query()] = False,
|
||||
theme: Annotated[str, _excluded_query()] = "dark",
|
||||
):
|
||||
"""Price or IV term structure across expirations."""
|
||||
from openbb_deribit.utils.options.create_term_structure import create_term_structure
|
||||
|
||||
return await _chart_json(
|
||||
create_term_structure,
|
||||
symbol,
|
||||
theme,
|
||||
raw,
|
||||
strike=strike,
|
||||
moneyness=moneyness,
|
||||
metric=metric,
|
||||
option_type=option_type,
|
||||
)
|
||||
|
||||
|
||||
CHART_ROUTES = (
|
||||
(
|
||||
"/payoff",
|
||||
payoff_chart,
|
||||
"Options Payoff",
|
||||
"What one ranked strategy returns across a range of underlying prices.",
|
||||
18,
|
||||
),
|
||||
(
|
||||
"/smile",
|
||||
smile_chart,
|
||||
"Volatility Smile",
|
||||
"Implied volatility smile or skew across strikes, per expiration.",
|
||||
16,
|
||||
),
|
||||
(
|
||||
"/term_structure",
|
||||
term_structure_chart,
|
||||
"Volatility Term Structure",
|
||||
"Price or implied volatility at one strike across expirations.",
|
||||
16,
|
||||
),
|
||||
(
|
||||
"/surface",
|
||||
surface_chart,
|
||||
"Volatility Surface",
|
||||
"Implied volatility or a greek as a surface over DTE and strike.",
|
||||
20,
|
||||
),
|
||||
(
|
||||
"/stats",
|
||||
stats_chart,
|
||||
"Options Statistics",
|
||||
"Open interest and volume, split between calls and puts.",
|
||||
16,
|
||||
),
|
||||
)
|
||||
|
||||
for _path, _endpoint, _name, _description, _height in CHART_ROUTES:
|
||||
router.api_router.add_api_route(
|
||||
path=_path,
|
||||
endpoint=_endpoint,
|
||||
methods=["GET"],
|
||||
openapi_extra=_chart_widget(_name, _description, _height),
|
||||
)
|
||||
@@ -0,0 +1,146 @@
|
||||
"""Deribit Rates sub-router."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Query as FastAPIQuery
|
||||
from openbb_core.app.model.command_context import CommandContext
|
||||
from openbb_core.app.model.example import APIEx
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.provider_interface import (
|
||||
ExtraParams,
|
||||
ProviderChoices,
|
||||
StandardParams,
|
||||
)
|
||||
from openbb_core.app.query import Query as OBBQuery
|
||||
from openbb_core.app.router import Router
|
||||
|
||||
router = Router(prefix="/rates", description="Deribit funding and yield data.")
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitFundingRateHistory",
|
||||
widget_config={
|
||||
"name": "Deribit Funding Rate History",
|
||||
"description": "The hourly funding a perpetual has paid.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Rates",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 14},
|
||||
"params": [{"paramName": "symbol", "value": "BTC-PERPETUAL"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": True, "chartType": "line"},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The funding the BTC perpetual has paid over the last week.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def funding_history(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the hourly funding a Deribit perpetual has paid."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitFundingChart",
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The funding the BTC perpetual has accrued over eight hours.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def funding_chart(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the funding a Deribit perpetual has accrued over the current window."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitAprHistory",
|
||||
widget_config={
|
||||
"name": "Deribit Yield History",
|
||||
"description": "The daily yield a yield-bearing currency has earned.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Rates",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 14},
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": True, "chartType": "line"},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The yield USDe has earned.",
|
||||
parameters={"currency": "usde", "provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def apr_history(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the daily yield a Deribit yield-bearing currency has earned."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Read the funding one perpetual paid over a span.",
|
||||
parameters={
|
||||
"symbol": "BTC-PERPETUAL",
|
||||
"start_date": "2026-09-01",
|
||||
"end_date": "2026-09-08",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def funding_value(
|
||||
symbol: Annotated[
|
||||
str, FastAPIQuery(description="The perpetual instrument name.")
|
||||
] = "BTC-PERPETUAL",
|
||||
start_date: Annotated[
|
||||
str | None, FastAPIQuery(description="The first date of the span.")
|
||||
] = None,
|
||||
end_date: Annotated[
|
||||
str | None, FastAPIQuery(description="The last date of the span.")
|
||||
] = None,
|
||||
) -> float:
|
||||
"""Get the funding one perpetual paid over a span, as a single rate."""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from openbb_deribit.utils.client import request
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols, to_timestamp
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
|
||||
return await request(
|
||||
"get_funding_rate_value",
|
||||
{
|
||||
"instrument_name": perpetuals.get(symbol.upper(), symbol),
|
||||
"start_timestamp": to_timestamp(start_date or now - timedelta(days=7)),
|
||||
"end_timestamp": to_timestamp(end_date or now),
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,321 @@
|
||||
"""Deribit Reference sub-router."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Query as FastAPIQuery
|
||||
from openbb_core.app.model.command_context import CommandContext
|
||||
from openbb_core.app.model.example import APIEx
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.provider_interface import (
|
||||
ExtraParams,
|
||||
ProviderChoices,
|
||||
StandardParams,
|
||||
)
|
||||
from openbb_core.app.query import Query as OBBQuery
|
||||
from openbb_core.app.router import Router
|
||||
|
||||
router = Router(prefix="/reference", description="Deribit exchange reference data.")
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitCurrencies",
|
||||
widget_config={
|
||||
"name": "Deribit Currencies",
|
||||
"description": "Every currency the exchange lists, with its yield and"
|
||||
+ " withdrawal terms.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Reference",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 12},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="List every currency Deribit lists.",
|
||||
parameters={"provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def currencies(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get every currency Deribit lists, with its yield and withdrawal terms."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitInstruments",
|
||||
widget_config={
|
||||
"name": "Deribit Instruments",
|
||||
"description": "The full contract specification of every listed instrument.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Reference",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 15},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Every instrument the exchange lists.",
|
||||
parameters={"provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="Only the options settling in USDC.",
|
||||
parameters={"currency": "USDC", "kind": "option", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="The specification of one named instrument.",
|
||||
parameters={"symbol": "BTC-PERPETUAL", "provider": "deribit"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def instruments(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the full contract specification of every listed Deribit instrument."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitExpirations",
|
||||
widget_config={
|
||||
"name": "Deribit Expirations",
|
||||
"description": "The expirations listed on each currency.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Reference",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 20, "h": 12},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Every expiration listed, kept apart by currency.",
|
||||
parameters={"provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="The option expirations listed on BTC.",
|
||||
parameters={"currency": "BTC", "kind": "option", "provider": "deribit"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def expirations(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the expirations Deribit lists on each currency."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitCombos",
|
||||
widget_config={
|
||||
"name": "Deribit Combos",
|
||||
"description": "The listed multi-leg structures, one row per leg.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Reference",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 30, "h": 12},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Every combo listed on BTC.",
|
||||
parameters={"currency": "BTC", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="The legs of one named combo.",
|
||||
parameters={
|
||||
"combo_id": "BTC-STRG-30OCT26-66000_90000",
|
||||
"provider": "deribit",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def combos(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the multi-leg structures Deribit lists, one row per leg."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitAnnouncements",
|
||||
widget_config={
|
||||
"name": "Deribit Announcements",
|
||||
"description": "What the exchange has announced.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Reference",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 12},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The most recent exchange announcements.",
|
||||
parameters={"provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def announcements(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get what Deribit has announced."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Read the contract size of one instrument.",
|
||||
parameters={"symbol": "BTC-PERPETUAL"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def contract_size(
|
||||
symbol: Annotated[
|
||||
str, FastAPIQuery(description="The instrument name.")
|
||||
] = "BTC-PERPETUAL",
|
||||
) -> float | None:
|
||||
"""Get the size of one contract of an instrument."""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
result = await request("get_contract_size", {"instrument_name": symbol})
|
||||
|
||||
return (result or {}).get("contract_size")
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[APIEx(description="Read the exchange's platform status.", parameters={})],
|
||||
)
|
||||
async def status() -> dict:
|
||||
"""Get whether the exchange has locked the platform."""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
return await request("status", use_cache=False)
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[APIEx(description="Read the exchange's server time.", parameters={})],
|
||||
)
|
||||
async def server_time() -> int:
|
||||
"""Get the exchange's server time, in milliseconds since the epoch."""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
return await request("get_time", use_cache=False)
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[APIEx(description="Get currency choices.", parameters={})],
|
||||
)
|
||||
async def currency_choices() -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of every currency the exchange lists."""
|
||||
from openbb_deribit.utils.choices import currency_choices as choices
|
||||
|
||||
return await choices()
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(description="Get the names of every published index.", parameters={})
|
||||
],
|
||||
)
|
||||
async def index_choices(
|
||||
supported: Annotated[
|
||||
bool,
|
||||
FastAPIQuery(
|
||||
description="When True, returns only the indexes instruments settle"
|
||||
+ " against, rather than every published index."
|
||||
),
|
||||
] = False,
|
||||
kind: Annotated[
|
||||
str,
|
||||
FastAPIQuery(
|
||||
description="Narrow the supported names to spot or derivative indexes."
|
||||
),
|
||||
] = "all",
|
||||
) -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of every index the exchange publishes."""
|
||||
from openbb_deribit.utils.choices import (
|
||||
_choices,
|
||||
index_choices as choices,
|
||||
)
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
if not supported:
|
||||
return await choices()
|
||||
|
||||
names = await request("get_supported_index_names", {"type": kind})
|
||||
|
||||
return _choices(sorted(str(name) for name in names))
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Get instrument choices for one kind.",
|
||||
parameters={"kind": "future"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def instrument_choices(
|
||||
kind: Annotated[
|
||||
str | None,
|
||||
FastAPIQuery(description="The kind of instrument. Default is all of them."),
|
||||
] = None,
|
||||
currency: Annotated[
|
||||
str, FastAPIQuery(description="The settlement currency of the instruments.")
|
||||
] = "any",
|
||||
expired: Annotated[
|
||||
bool, FastAPIQuery(description="When True, returns expired instruments.")
|
||||
] = False,
|
||||
) -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of every instrument the exchange lists."""
|
||||
from openbb_deribit.utils.choices import instrument_choices as choices
|
||||
|
||||
return await choices(kind, currency, expired)
|
||||
|
||||
|
||||
@router.command(
|
||||
methods=["GET"],
|
||||
widget_config={"exclude": True},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="Get the combo identifiers listed on one currency.",
|
||||
parameters={"currency": "BTC"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def combo_choices(
|
||||
currency: Annotated[
|
||||
str, FastAPIQuery(description="The currency the combos settle in.")
|
||||
] = "BTC",
|
||||
state: Annotated[
|
||||
str | None,
|
||||
FastAPIQuery(description="Narrow to active or inactive combos."),
|
||||
] = None,
|
||||
) -> list[dict[str, str]]:
|
||||
"""``[{label, value}]`` of every combo identifier the exchange lists."""
|
||||
from openbb_deribit.utils.choices import combo_choices as choices
|
||||
|
||||
return await choices(currency, state)
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Deribit Volatility sub-router."""
|
||||
|
||||
from openbb_core.app.model.command_context import CommandContext
|
||||
from openbb_core.app.model.example import APIEx
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.provider_interface import (
|
||||
ExtraParams,
|
||||
ProviderChoices,
|
||||
StandardParams,
|
||||
)
|
||||
from openbb_core.app.query import Query as OBBQuery
|
||||
from openbb_core.app.router import Router
|
||||
|
||||
router = Router(prefix="/volatility", description="Deribit volatility data.")
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitHistoricalVolatility",
|
||||
widget_config={
|
||||
"name": "Deribit Realized Volatility",
|
||||
"description": "The volatility a currency has actually realized.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Volatility",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 14},
|
||||
"params": [{"paramName": "currency", "value": "BTC"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": True, "chartType": "line"},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The volatility BTC has realized.",
|
||||
parameters={"currency": "BTC", "provider": "deribit"},
|
||||
)
|
||||
],
|
||||
)
|
||||
async def realized(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the volatility a Deribit currency has actually realized."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="DeribitVolatilityIndex",
|
||||
widget_config={
|
||||
"name": "Deribit Volatility Index",
|
||||
"description": "The DVOL index, as candles over a span.",
|
||||
"category": "Crypto",
|
||||
"subCategory": "Volatility",
|
||||
"source": ["Deribit"],
|
||||
"gridData": {"w": 40, "h": 14},
|
||||
"params": [{"paramName": "currency", "value": "BTC"}],
|
||||
"data": {
|
||||
"table": {
|
||||
"chartView": {"enabled": True, "chartType": "line"},
|
||||
}
|
||||
},
|
||||
},
|
||||
examples=[
|
||||
APIEx(
|
||||
description="The BTC volatility index over the last month.",
|
||||
parameters={"currency": "BTC", "provider": "deribit"},
|
||||
),
|
||||
APIEx(
|
||||
description="Daily candles over a named span.",
|
||||
parameters={
|
||||
"currency": "ETH",
|
||||
"interval": "1d",
|
||||
"start_date": "2026-01-01",
|
||||
"end_date": "2026-06-30",
|
||||
"provider": "deribit",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def index(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Get the Deribit DVOL volatility index, as candles over a span."""
|
||||
return await OBBject.from_query(OBBQuery(**locals()))
|
||||
146
openbb_platform/providers/deribit/openbb_deribit/utils/cache.py
Normal file
146
openbb_platform/providers/deribit/openbb_deribit/utils/cache.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Deribit shared response cache."""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiohttp_client_cache import SQLiteBackend
|
||||
|
||||
MINUTE = 60
|
||||
HOUR = 60 * 60
|
||||
DAY = 60 * 60 * 24
|
||||
|
||||
CACHE_NAME = "deribit"
|
||||
CACHE_TTL_DEFAULT = 5 * MINUTE
|
||||
CACHE_MAX_BYTES = 128 * 1024 * 1024
|
||||
CACHE_SWEEP_PASSES = 3
|
||||
|
||||
URL_CACHE_TTL = {
|
||||
"*/public/get_currencies*": DAY,
|
||||
"*/public/get_index_price_names*": DAY,
|
||||
"*/public/get_supported_index_names*": DAY,
|
||||
"*/public/get_instruments*": HOUR,
|
||||
"*/public/get_instrument*": HOUR,
|
||||
"*/public/get_contract_size*": DAY,
|
||||
"*/public/get_expirations*": HOUR,
|
||||
"*/public/get_combo_ids*": HOUR,
|
||||
"*/public/get_combos*": HOUR,
|
||||
"*/public/get_combo_details*": HOUR,
|
||||
"*/public/get_delivery_prices*": HOUR,
|
||||
"*/public/get_apr_history*": HOUR,
|
||||
"*/public/get_announcements*": HOUR,
|
||||
"*/public/get_trade_volumes*": 15 * MINUTE,
|
||||
"*/public/get_historical_volatility*": 15 * MINUTE,
|
||||
"*/public/get_volatility_index_data*": 15 * MINUTE,
|
||||
"*/public/get_funding_rate_history*": 15 * MINUTE,
|
||||
"*/public/get_funding_chart_data*": 5 * MINUTE,
|
||||
"*/public/get_funding_rate_value*": 5 * MINUTE,
|
||||
"*/public/get_tradingview_chart_data*": 5 * MINUTE,
|
||||
"*/public/get_index_chart_data*": 5 * MINUTE,
|
||||
"*/public/get_last_settlements_by_currency*": 5 * MINUTE,
|
||||
"*/public/get_last_settlements_by_instrument*": 5 * MINUTE,
|
||||
"*/public/get_block_rfq_trades*": MINUTE,
|
||||
"*/public/get_last_trades_by_currency*": MINUTE,
|
||||
"*/public/get_last_trades_by_instrument*": MINUTE,
|
||||
"*/public/get_book_summary_by_currency*": MINUTE,
|
||||
"*/public/get_book_summary_by_instrument*": MINUTE,
|
||||
"*/public/get_index_price*": MINUTE,
|
||||
"*/public/get_order_book*": 0,
|
||||
"*/public/ticker*": 0,
|
||||
"*/public/get_time*": 0,
|
||||
"*/public/status*": 0,
|
||||
}
|
||||
|
||||
_swept = False
|
||||
|
||||
|
||||
def cache_path() -> str:
|
||||
"""Return the file the cached responses are kept in."""
|
||||
from openbb_core.app.utils import get_user_cache_directory
|
||||
|
||||
return f"{get_user_cache_directory()}/http/{CACHE_NAME}"
|
||||
|
||||
|
||||
def get_cache_backend() -> "SQLiteBackend":
|
||||
"""Build the on-disk response cache, with a TTL per endpoint.
|
||||
|
||||
Returns
|
||||
-------
|
||||
SQLiteBackend
|
||||
The shared response cache.
|
||||
"""
|
||||
from aiohttp_client_cache import SQLiteBackend
|
||||
|
||||
return SQLiteBackend(
|
||||
cache_name=cache_path(),
|
||||
expire_after=CACHE_TTL_DEFAULT,
|
||||
urls_expire_after=URL_CACHE_TTL,
|
||||
allowed_codes=(200,),
|
||||
allowed_methods=("GET",),
|
||||
include_headers=False,
|
||||
)
|
||||
|
||||
|
||||
async def _oldest(backend: "SQLiteBackend", count: int) -> set:
|
||||
"""Return the keys of the entries written longest ago."""
|
||||
keys: set = set()
|
||||
|
||||
async for key in backend.responses.keys():
|
||||
keys.add(key)
|
||||
|
||||
if len(keys) >= count:
|
||||
break
|
||||
|
||||
return keys
|
||||
|
||||
|
||||
async def _vacuum(backend: "SQLiteBackend") -> None:
|
||||
"""Give back the pages the deleted responses were held in."""
|
||||
from aiohttp_client_cache.backends.sqlite import SQLiteCache
|
||||
|
||||
stored = backend.responses
|
||||
|
||||
if not isinstance(stored, SQLiteCache):
|
||||
return
|
||||
|
||||
async with stored.get_connection(commit=True) as connection:
|
||||
await connection.execute("VACUUM")
|
||||
|
||||
|
||||
async def sweep_cache(backend: "SQLiteBackend") -> None:
|
||||
"""Drop what has expired, then the oldest of what is left if still too big.
|
||||
|
||||
An expiry marks a response stale rather than deleting it, so the file grows
|
||||
by every request ever made until something clears it out. It is swept once
|
||||
per process, and what the sweep leaves over the limit is trimmed by the
|
||||
share of the file that overshoots it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
backend : SQLiteBackend
|
||||
The cache to sweep.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
global _swept # noqa: PLW0603
|
||||
|
||||
if _swept:
|
||||
return
|
||||
|
||||
_swept = True
|
||||
await backend.delete_expired_responses()
|
||||
await _vacuum(backend)
|
||||
stored = Path(f"{cache_path()}.sqlite")
|
||||
|
||||
for _ in range(CACHE_SWEEP_PASSES):
|
||||
if not stored.exists() or stored.stat().st_size <= CACHE_MAX_BYTES:
|
||||
return
|
||||
|
||||
held = await backend.responses.size()
|
||||
overshoot = 1 - CACHE_MAX_BYTES / stored.stat().st_size
|
||||
dropped = await _oldest(backend, max(1, round(held * overshoot)))
|
||||
|
||||
if not dropped:
|
||||
return
|
||||
|
||||
await backend.responses.bulk_delete(dropped)
|
||||
await _vacuum(backend)
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Choice feeds built from what the exchange lists right now."""
|
||||
|
||||
|
||||
def _choices(values, labels: dict | None = None) -> list[dict[str, str]]:
|
||||
"""Pair each value with a label for a widget parameter."""
|
||||
named = labels or {}
|
||||
|
||||
return [{"label": named.get(value, value), "value": value} for value in values]
|
||||
|
||||
|
||||
async def currency_choices() -> list[dict[str, str]]:
|
||||
"""Return every currency the exchange lists."""
|
||||
from openbb_deribit.utils.helpers import get_currencies
|
||||
|
||||
currencies = await get_currencies()
|
||||
|
||||
return _choices(
|
||||
sorted(str(d["currency"]) for d in currencies),
|
||||
{
|
||||
str(d["currency"]): str(d.get("currency_long") or d["currency"])
|
||||
for d in currencies
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def index_choices(extended: bool = False) -> list[dict[str, str]]:
|
||||
"""Return the name of every published index."""
|
||||
from openbb_deribit.utils.helpers import get_index_names
|
||||
|
||||
names = await get_index_names(extended)
|
||||
|
||||
return _choices(
|
||||
sorted(
|
||||
str(name["name"]) if isinstance(name, dict) else str(name) for name in names
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def instrument_choices(
|
||||
kind: str | None = None,
|
||||
currency: str = "any",
|
||||
expired: bool = False,
|
||||
) -> list[dict[str, str]]:
|
||||
"""Return every instrument of one kind, labelled by its name."""
|
||||
from openbb_deribit.utils.helpers import get_instruments
|
||||
|
||||
instruments = await get_instruments(currency, kind, expired)
|
||||
|
||||
return _choices(sorted(str(d["instrument_name"]) for d in instruments))
|
||||
|
||||
|
||||
async def perpetual_choices() -> list[dict[str, str]]:
|
||||
"""Return every perpetual, labelled by its instrument name."""
|
||||
from openbb_deribit.utils.helpers import get_perpetual_symbols
|
||||
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
|
||||
return [{"label": name, "value": name} for name in sorted(perpetuals.values())]
|
||||
|
||||
|
||||
async def options_root_choices() -> list[dict[str, str]]:
|
||||
"""Return every underlying with listed options."""
|
||||
from openbb_deribit.utils.helpers import get_options_roots
|
||||
|
||||
return _choices(await get_options_roots())
|
||||
|
||||
|
||||
async def futures_root_choices() -> list[dict[str, str]]:
|
||||
"""Return every underlying with a listed futures curve."""
|
||||
from openbb_deribit.utils.helpers import get_futures_roots
|
||||
|
||||
return _choices(await get_futures_roots())
|
||||
|
||||
|
||||
async def combo_choices(
|
||||
currency: str = "BTC", state: str | None = None
|
||||
) -> list[dict[str, str]]:
|
||||
"""Return the identifier of every combo listed on one currency."""
|
||||
from openbb_deribit.utils.client import request
|
||||
from openbb_deribit.utils.helpers import normalize_currency
|
||||
|
||||
combos = await request(
|
||||
"get_combo_ids", {"currency": normalize_currency(currency), "state": state}
|
||||
)
|
||||
|
||||
return _choices(sorted(str(combo) for combo in combos))
|
||||
132
openbb_platform/providers/deribit/openbb_deribit/utils/client.py
Normal file
132
openbb_platform/providers/deribit/openbb_deribit/utils/client.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""Transport for the Deribit public JSON-RPC API."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
BASE_URL = "https://www.deribit.com/api/v2"
|
||||
REQUEST_TIMEOUT = 60
|
||||
TOO_MANY_REQUESTS = 429
|
||||
|
||||
|
||||
def build_url(method: str, params: dict[str, Any] | None = None) -> str:
|
||||
"""Return the request URL for one public method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
method : str
|
||||
The method name, without the 'public/' prefix.
|
||||
params : dict or None
|
||||
The query parameters. Entries with a value of None are dropped, and
|
||||
booleans are sent as the lower-case literals the API expects.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The fully-qualified request URL.
|
||||
"""
|
||||
from urllib.parse import urlencode
|
||||
|
||||
query = {
|
||||
key: str(value).lower() if isinstance(value, bool) else value
|
||||
for key, value in (params or {}).items()
|
||||
if value is not None
|
||||
}
|
||||
url = f"{BASE_URL}/public/{method}"
|
||||
|
||||
return f"{url}?{urlencode(query)}" if query else url
|
||||
|
||||
|
||||
async def request(
|
||||
method: str,
|
||||
params: dict[str, Any] | None = None,
|
||||
use_cache: bool = True,
|
||||
) -> Any:
|
||||
"""Call one public method and return its result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
method : str
|
||||
The method name, without the 'public/' prefix.
|
||||
params : dict or None
|
||||
The query parameters.
|
||||
use_cache : bool
|
||||
Whether to read and write the on-disk response cache.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
The 'result' member of the JSON-RPC response.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the API reports an error, or the response cannot be read.
|
||||
"""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
from openbb_deribit.utils.session import get_cached_session, get_session
|
||||
|
||||
url = build_url(method, params)
|
||||
session = await get_cached_session() if use_cache else await get_session()
|
||||
|
||||
try:
|
||||
response = await session.get(url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
if response.status != 200:
|
||||
raise OpenBBError(
|
||||
f"Deribit answered public/{method} with HTTP {response.status}."
|
||||
+ (
|
||||
" The request rate was exceeded; narrow the query or retry."
|
||||
if response.status == TOO_MANY_REQUESTS
|
||||
else ""
|
||||
)
|
||||
)
|
||||
|
||||
payload = await response.json(content_type=None)
|
||||
except OpenBBError:
|
||||
raise
|
||||
except Exception as error: # noqa: BLE001
|
||||
raise OpenBBError(
|
||||
f"Deribit request failed for public/{method} ->"
|
||||
f" {error.__class__.__name__}: {error}"
|
||||
) from error
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise OpenBBError(f"Unexpected Deribit response for public/{method}.")
|
||||
|
||||
if payload.get("error"):
|
||||
detail = payload["error"]
|
||||
message = detail.get("message") if isinstance(detail, dict) else detail
|
||||
reason = detail.get("data") if isinstance(detail, dict) else None
|
||||
raise OpenBBError(
|
||||
f"Deribit returned an error for public/{method} -> {message}"
|
||||
+ (f" {reason}" if reason else "")
|
||||
)
|
||||
|
||||
return payload.get("result")
|
||||
|
||||
|
||||
async def gather(
|
||||
calls: "list[tuple[str, dict[str, Any] | None]]",
|
||||
use_cache: bool = True,
|
||||
) -> list:
|
||||
"""Call several public methods at once.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
calls : list[tuple[str, dict or None]]
|
||||
Each method name with its query parameters.
|
||||
use_cache : bool
|
||||
Whether to read and write the on-disk response cache.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
The result of each call, in the order it was given, with an exception
|
||||
in place of any result that could not be read.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
return await asyncio.gather(
|
||||
*(request(method, params, use_cache) for method, params in calls),
|
||||
return_exceptions=True,
|
||||
)
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Fixed enumerations and choice endpoints of the Deribit public API."""
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from openbb_core.app.service.system_service import SystemService
|
||||
|
||||
API_PREFIX = (
|
||||
SystemService()
|
||||
.system_settings.python_settings.model_dump()
|
||||
.get("api_settings", {})
|
||||
.get("prefix", "")
|
||||
or "/api/v1"
|
||||
)
|
||||
|
||||
CURRENCY_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/reference/currency_choices"
|
||||
INDEX_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/reference/index_choices"
|
||||
INSTRUMENT_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/reference/instrument_choices"
|
||||
COMBO_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/reference/combo_choices"
|
||||
CURVE_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/futures/curve_choices"
|
||||
PERPETUAL_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/futures/perpetual_choices"
|
||||
UNDERLYING_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/options/underlying_choices"
|
||||
EXPIRATION_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/options/expiration_choices"
|
||||
STRATEGY_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/options/strategy_choices"
|
||||
STRIKE_CHOICES_ENDPOINT = f"{API_PREFIX}/deribit/options/strike_choices"
|
||||
|
||||
SYMBOL_STYLE = {"popupWidth": 600}
|
||||
|
||||
CURRENCIES = ("BTC", "ETH", "USDC", "USDT", "EURR")
|
||||
Currencies = Literal["BTC", "ETH", "USDC", "USDT", "EURR"]
|
||||
AnyCurrencies = Literal["BTC", "ETH", "USDC", "USDT", "EURR", "any"]
|
||||
|
||||
VOLATILITY_INDEX_CURRENCIES = ("BTC", "ETH")
|
||||
REALIZED_VOLATILITY_CURRENCIES = ("BTC", "ETH", "EURR")
|
||||
|
||||
UNDERLYING_CURRENCIES = ("BTC", "ETH")
|
||||
LISTING_CURRENCIES = ("BTC", "ETH", "USDC")
|
||||
ListingCurrencies = Literal["BTC", "ETH", "USDC"]
|
||||
AnyListingCurrencies = Literal["BTC", "ETH", "USDC", "any"]
|
||||
OPTION_KINDS = ("option", "option_combo")
|
||||
|
||||
INSTRUMENT_KINDS = ("future", "option", "spot", "future_combo", "option_combo")
|
||||
InstrumentKinds = Literal["future", "option", "spot", "future_combo", "option_combo"]
|
||||
TradeKinds = Literal["future", "option", "future_combo", "option_combo", "combo"]
|
||||
ROUTED_SPOT_ERROR = "not_supported_for_coinbase_routed_spot"
|
||||
ExpirationKinds = Literal["future", "option", "any"]
|
||||
ExpirationCurrencies = Literal["BTC", "ETH", "any", "grouped"]
|
||||
|
||||
SettlementTypes = Literal["settlement", "delivery", "bankruptcy"]
|
||||
ComboStates = Literal["active", "inactive"]
|
||||
IndexNameTypes = Literal["all", "spot", "derivative"]
|
||||
Sorting = Literal["asc", "desc", "default"]
|
||||
FundingLengths = Literal["8h", "24h", "1m"]
|
||||
IndexChartRanges = Literal["1h", "1d", "2d", "1m", "1y", "all"]
|
||||
AprCurrencies = Literal["usde", "steth", "usdc", "build"]
|
||||
|
||||
ORDER_BOOK_DEPTHS = (1, 5, 10, 20, 50, 100, 1000, 10000)
|
||||
|
||||
INTERVALS = (
|
||||
"1m",
|
||||
"3m",
|
||||
"5m",
|
||||
"10m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"6h",
|
||||
"12h",
|
||||
"1d",
|
||||
)
|
||||
Intervals = Literal[
|
||||
"1m", "3m", "5m", "10m", "15m", "30m", "1h", "2h", "3h", "6h", "12h", "1d"
|
||||
]
|
||||
INTERVAL_MAP = {
|
||||
"1m": "1",
|
||||
"3m": "3",
|
||||
"5m": "5",
|
||||
"10m": "10",
|
||||
"15m": "15",
|
||||
"30m": "30",
|
||||
"1h": "60",
|
||||
"2h": "120",
|
||||
"3h": "180",
|
||||
"6h": "360",
|
||||
"12h": "720",
|
||||
"1d": "1D",
|
||||
}
|
||||
|
||||
VOLATILITY_RESOLUTIONS = ("1m", "1h", "12h", "1d")
|
||||
VolatilityResolutions = Literal["1m", "1h", "12h", "1d"]
|
||||
VOLATILITY_RESOLUTION_MAP = {
|
||||
"1m": "60",
|
||||
"1h": "3600",
|
||||
"12h": "43200",
|
||||
"1d": "1D",
|
||||
}
|
||||
|
||||
BLOCK_RFQ_MIN_COUNT = 10
|
||||
BLOCK_RFQ_MAX_COUNT = 50
|
||||
MAX_TRADE_COUNT = 1000
|
||||
MAX_SETTLEMENT_COUNT = 1000
|
||||
@@ -1,402 +1,535 @@
|
||||
"""Deribit Helpers Module."""
|
||||
"""Shared accessors for the Deribit public API."""
|
||||
|
||||
from typing import Literal
|
||||
from typing import Any
|
||||
|
||||
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"]
|
||||
DERIBIT_FUTURES_CURVE_SYMBOLS = ["BTC", "ETH", "PAXG"]
|
||||
FuturesCurveSymbols = Literal["BTC", "ETH", "PAXG"]
|
||||
BASE_URL = "https://www.deribit.com"
|
||||
|
||||
DERIBIT_INTERVALS = [
|
||||
"1m",
|
||||
"3m",
|
||||
"5m",
|
||||
"10m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"6h",
|
||||
"12h",
|
||||
"1d",
|
||||
]
|
||||
DeribitIntervals = Literal[
|
||||
"1m", "3m", "5m", "10m", "15m", "30m", "1h", "2h", "3h", "6h", "12h", "1d"
|
||||
]
|
||||
INTERVAL_MAP = {
|
||||
"1m": "1",
|
||||
"3m": "3",
|
||||
"5m": "5",
|
||||
"10m": "10",
|
||||
"15m": "15",
|
||||
"30m": "30",
|
||||
"1h": "60",
|
||||
"2h": "120",
|
||||
"3h": "180",
|
||||
"6h": "360",
|
||||
"12h": "720",
|
||||
"1d": "1D",
|
||||
}
|
||||
MAX_CHART_WINDOWS = 15
|
||||
CHART_WINDOW_SIZE = 5000
|
||||
SECONDS_PER_DAY = 86400
|
||||
CURVE_LOOKBACK_HOURS = 24
|
||||
|
||||
|
||||
@alru_cache(maxsize=64)
|
||||
async def get_instruments(
|
||||
currency: Currencies = "BTC",
|
||||
derivative_type: DerivativeTypes | None = None,
|
||||
expired: bool = False,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Get Deribit instruments.
|
||||
def to_timestamp(value: Any) -> int:
|
||||
"""Return a date, datetime, or string as milliseconds since the epoch."""
|
||||
from pandas import Timestamp, to_datetime
|
||||
|
||||
stamp = value if isinstance(value, Timestamp) else to_datetime(value, utc=True)
|
||||
|
||||
if stamp.tzinfo is None:
|
||||
stamp = stamp.tz_localize("UTC")
|
||||
|
||||
return int(stamp.timestamp() * 1000)
|
||||
|
||||
|
||||
def from_timestamp(value: Any) -> Any:
|
||||
"""Return milliseconds since the epoch as a UTC datetime."""
|
||||
from pandas import to_datetime
|
||||
|
||||
return (
|
||||
to_datetime(value, unit="ms", origin="unix", utc=True)
|
||||
if value is not None
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
def from_day_number(value: Any) -> Any:
|
||||
"""Return a count of whole days since the epoch as a date."""
|
||||
from pandas import to_datetime
|
||||
|
||||
return (
|
||||
to_datetime(
|
||||
int(value) * SECONDS_PER_DAY, unit="s", origin="unix", utc=True
|
||||
).date()
|
||||
if value is not None
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
def reject_options_by_settlement(currency: str, kind: "str | None") -> None:
|
||||
"""Refuse a listing of option contracts scoped to a settlement currency.
|
||||
|
||||
Only BTC and ETH are both a settlement currency and an underlying. USDC
|
||||
settles options written on seven different underlyings, so a listing scoped
|
||||
to it returns a mixture of unrelated assets rather than a chain, and USDT
|
||||
settles no options at all. Options are listed by the underlying they are
|
||||
written on, or by instrument name.
|
||||
|
||||
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.
|
||||
currency : str
|
||||
The settlement currency asked for.
|
||||
kind : str or None
|
||||
The kind of instrument asked for.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If option contracts are asked for by a currency that only settles them.
|
||||
"""
|
||||
from openbb_deribit.utils.constants import OPTION_KINDS, UNDERLYING_CURRENCIES
|
||||
|
||||
if currency.upper() in UNDERLYING_CURRENCIES or kind not in OPTION_KINDS:
|
||||
return
|
||||
|
||||
raise ValueError(
|
||||
f"{currency.upper()} settles options, it is not an underlying, so there"
|
||||
" are no options on it to list. Ask for options by the underlying they"
|
||||
" are written on, such as BTC_USDC or SOL_USDC, using the symbol"
|
||||
" parameter."
|
||||
)
|
||||
|
||||
|
||||
def normalize_currency(currency: str) -> str:
|
||||
"""Return a currency as the exchange spells it, leaving the wildcard alone."""
|
||||
return "any" if currency.lower() in ("any", "all") else currency.upper()
|
||||
|
||||
|
||||
async def get_currencies() -> list[dict]:
|
||||
"""Return every currency the exchange lists."""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
return await request("get_currencies")
|
||||
|
||||
|
||||
async def get_instruments(
|
||||
currency: str = "any",
|
||||
kind: str | None = None,
|
||||
expired: bool = False,
|
||||
) -> list[dict]:
|
||||
"""Return the instruments of one currency, optionally of one kind.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
currency : str
|
||||
The settlement currency, or 'any' for all of them.
|
||||
kind : str or None
|
||||
One of the instrument kinds, or None for all of them.
|
||||
expired : bool
|
||||
When True, returns instruments that have already expired.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[dict]
|
||||
A list of instrument dictionaries.
|
||||
Every matching instrument.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import amake_request
|
||||
from openbb_deribit.utils.client import 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}"
|
||||
|
||||
if expired:
|
||||
url += f"&expired={str(expired).lower()}"
|
||||
|
||||
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()
|
||||
return await request(
|
||||
"get_instruments",
|
||||
{
|
||||
"currency": normalize_currency(currency),
|
||||
"kind": kind,
|
||||
"expired": expired,
|
||||
},
|
||||
)
|
||||
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"))
|
||||
)
|
||||
|
||||
|
||||
async def get_instrument(symbol: str) -> dict:
|
||||
"""Return the definition of one instrument."""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
return await request("get_instrument", {"instrument_name": symbol})
|
||||
|
||||
|
||||
def flatten_ticker(data: dict) -> dict:
|
||||
"""Return a ticker with its nested statistics and greeks at the top level."""
|
||||
flat = dict(data)
|
||||
stats = flat.pop("stats", None) or {}
|
||||
greeks = flat.pop("greeks", None) or {}
|
||||
|
||||
return {**flat, **stats, **greeks}
|
||||
|
||||
|
||||
async def get_ticker(symbol: str) -> dict:
|
||||
"""Return the quote and statistics of one instrument."""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
return flatten_ticker(await request("ticker", {"instrument_name": symbol}))
|
||||
|
||||
|
||||
async def get_tickers(symbols: list[str]) -> list[dict]:
|
||||
"""Return the quotes of many instruments, skipping the ones that fail."""
|
||||
from openbb_deribit.utils.client import gather
|
||||
|
||||
results = await gather([("ticker", {"instrument_name": s}) for s in symbols])
|
||||
|
||||
return [
|
||||
flatten_ticker(result)
|
||||
for result in results
|
||||
if isinstance(result, dict) and result
|
||||
]
|
||||
|
||||
|
||||
async def get_index_names(extended: bool = False) -> list:
|
||||
"""Return the names of every published index."""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
return await request("get_index_price_names", {"extended": extended})
|
||||
|
||||
|
||||
async def get_expirations(
|
||||
currency: str = "any",
|
||||
kind: str = "any",
|
||||
currency_pair: str | None = None,
|
||||
) -> dict:
|
||||
"""Return the expirations listed for one currency and kind."""
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
return await request(
|
||||
"get_expirations",
|
||||
{"currency": currency, "kind": kind, "currency_pair": currency_pair},
|
||||
)
|
||||
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()}
|
||||
|
||||
|
||||
async def get_futures_curve_symbols(symbol: FuturesCurveSymbols = "BTC") -> list[str]:
|
||||
"""
|
||||
Get a list of futures symbols for a given symbol.
|
||||
def _roots(instruments: list[dict]) -> dict[str, list[dict]]:
|
||||
"""Group instruments by the root of their name."""
|
||||
grouped: dict[str, list[dict]] = {}
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : FuturesCurveSymbols
|
||||
The symbol to get futures symbols for.
|
||||
for instrument in instruments:
|
||||
root = str(instrument.get("instrument_name") or "").split("-")[0]
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[str]
|
||||
A list of futures symbols.
|
||||
"""
|
||||
symbol = symbol.upper() # type: ignore
|
||||
if symbol not in DERIBIT_FUTURES_CURVE_SYMBOLS:
|
||||
raise ValueError(
|
||||
f"Invalid Deribit symbol. Supported symbols are: {', '.join(DERIBIT_FUTURES_CURVE_SYMBOLS)}",
|
||||
)
|
||||
if root:
|
||||
grouped.setdefault(root, []).append(instrument)
|
||||
|
||||
currency = "USDC" if symbol == "PAXG" else symbol
|
||||
instruments = await get_instruments(currency, "future")
|
||||
|
||||
symbols: list = []
|
||||
for d in instruments:
|
||||
ins_name = d.get("instrument_name", "")
|
||||
if ins_name.startswith(symbol):
|
||||
symbols.append(ins_name)
|
||||
|
||||
return symbols
|
||||
return grouped
|
||||
|
||||
|
||||
async def get_ticker_data(symbol: str) -> dict:
|
||||
"""
|
||||
Get ticker data.
|
||||
async def get_options_roots() -> list[str]:
|
||||
"""Return every underlying the exchange currently lists options on."""
|
||||
return sorted(_roots(await get_instruments("any", "option")))
|
||||
|
||||
|
||||
async def get_futures_roots() -> list[str]:
|
||||
"""Return every underlying the exchange currently lists dated futures on."""
|
||||
grouped = _roots(await get_instruments("any", "future"))
|
||||
|
||||
return sorted(
|
||||
root
|
||||
for root, listed in grouped.items()
|
||||
if sum(1 for d in listed if d.get("settlement_period") != "perpetual") > 1
|
||||
)
|
||||
|
||||
|
||||
async def get_options_symbols(symbol: str = "BTC") -> dict[str, list[str]]:
|
||||
"""Return the option symbols of one underlying, keyed by expiration.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : str
|
||||
The symbol to get ticker data for.
|
||||
The underlying root, as it appears in the instrument name.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The ticker data.
|
||||
dict[str, list[str]]
|
||||
Every contract symbol, keyed by its expiration date.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the exchange lists no options on the underlying.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from openbb_core.provider.utils.helpers import amake_request
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from pandas import to_datetime
|
||||
|
||||
url = f"{BASE_URL}/api/v2/public/ticker?instrument_name={symbol}"
|
||||
root = symbol.upper()
|
||||
grouped = _roots(await get_instruments("any", "option"))
|
||||
listed = grouped.get(root)
|
||||
|
||||
try:
|
||||
response = await amake_request(url)
|
||||
if response.get("error"): # type: ignore[union-attr]
|
||||
raise OpenBBError(response.get("error")) # type: ignore[union-attr]
|
||||
data = response.get("result", {}) # type: ignore[union-attr]
|
||||
stats = data.pop("stats", {})
|
||||
return {**data, **stats}
|
||||
if not listed:
|
||||
raise OpenBBError(
|
||||
f"Deribit lists no options on {root}."
|
||||
f" Underlyings with listed options are: {', '.join(sorted(grouped))}"
|
||||
)
|
||||
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
raise OpenBBError(f"Failed to get ticker data -> {e}: {e.args[0]}") from e
|
||||
symbols = sorted(
|
||||
str(d["instrument_name"])
|
||||
for d in listed
|
||||
if str(d.get("instrument_name", "")).endswith(("-C", "-P"))
|
||||
)
|
||||
expirations: dict[str, list[str]] = {}
|
||||
|
||||
for contract in symbols:
|
||||
code = contract.split("-")[1]
|
||||
expiration = to_datetime(code).date().strftime("%Y-%m-%d")
|
||||
expirations.setdefault(expiration, []).append(contract)
|
||||
|
||||
return dict(sorted(expirations.items()))
|
||||
|
||||
|
||||
async def get_perpetual_symbols() -> dict:
|
||||
"""
|
||||
Get perpetual symbols.
|
||||
async def get_futures_curve_symbols(symbol: str = "BTC") -> list[str]:
|
||||
"""Return the dated future symbols of one underlying.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : str
|
||||
The underlying root, as it appears in the instrument name.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
A dictionary of short symbols to full perpetual symbols.
|
||||
list[str]
|
||||
Every contract symbol on the curve, nearest expiration first.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the exchange lists no curve for the underlying.
|
||||
"""
|
||||
instruments = await get_instruments("all", "future")
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
root = symbol.upper()
|
||||
grouped = _roots(await get_instruments("any", "future"))
|
||||
listed = [
|
||||
d
|
||||
for d in grouped.get(root, [])
|
||||
if d.get("settlement_period") != "perpetual"
|
||||
and str(d.get("instrument_name", "")).split("-")[0] == root
|
||||
]
|
||||
|
||||
if not listed:
|
||||
roots = await get_futures_roots()
|
||||
|
||||
raise OpenBBError(
|
||||
f"Deribit lists no futures curve for {root}."
|
||||
f" Underlyings with a curve are: {', '.join(roots)}"
|
||||
)
|
||||
|
||||
return [
|
||||
str(d["instrument_name"])
|
||||
for d in sorted(listed, key=lambda d: d.get("expiration_timestamp") or 0)
|
||||
]
|
||||
|
||||
|
||||
async def get_perpetual_symbols() -> dict[str, str]:
|
||||
"""Return every perpetual symbol, keyed by its shortened root."""
|
||||
instruments = await get_instruments("any", "future")
|
||||
|
||||
return {
|
||||
d["instrument_name"].split("-")[0].replace("_", ""): d["instrument_name"]
|
||||
str(d["instrument_name"]).split("-")[0].replace("_", ""): str(
|
||||
d["instrument_name"]
|
||||
)
|
||||
for d in instruments
|
||||
if d.get("settlement_period") == "perpetual"
|
||||
}
|
||||
|
||||
|
||||
async def get_ohlc_data(
|
||||
symbol: str, start_date: str, end_date: str, interval: DeribitIntervals = "1d"
|
||||
) -> list[dict]:
|
||||
"""Get OHLC data for a given symbol. Enter dates in the format 'YYYY-MM-DD'."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.helpers import amake_request
|
||||
from pandas import DataFrame, date_range, to_datetime
|
||||
async def get_futures_symbols() -> list[str]:
|
||||
"""Return every listed future symbol."""
|
||||
instruments = await get_instruments("any", "future")
|
||||
|
||||
new_interval = INTERVAL_MAP.get(interval, interval)
|
||||
|
||||
all_instruments = await get_instruments("all", None)
|
||||
all_symbols = {
|
||||
d.get("instrument_name"): d.get("creation_timestamp") for d in all_instruments
|
||||
}
|
||||
creation_date = all_symbols.get(symbol, 0)
|
||||
use_start = to_datetime(start_date).timestamp() * 1000 > creation_date
|
||||
|
||||
if creation_date == 0:
|
||||
raise ValueError(f"Symbol {symbol} not found")
|
||||
|
||||
def generate_urls(symbol, start_date, end_date, interval, window_size=5000):
|
||||
"""Generate urls for historical data breaking it down into requests of length window_size."""
|
||||
interval_period = f"{interval}min" if interval.lower() != "1d" else "1d"
|
||||
interval = "1D" if interval.lower() == "1d" else interval
|
||||
dates = date_range(
|
||||
start=start_date if use_start else creation_date,
|
||||
end=end_date,
|
||||
freq=interval_period,
|
||||
)
|
||||
windows = [
|
||||
(dates[i], dates[min(i + window_size, len(dates) - 1)])
|
||||
for i in range(0, len(dates), window_size)
|
||||
]
|
||||
urls: list = []
|
||||
for start, end in windows:
|
||||
start_timestamp = int(start.timestamp() * 1000)
|
||||
end_timestamp = int(end.timestamp() * 1000)
|
||||
url = (
|
||||
"https://www.deribit.com/api/v2/public/get_tradingview_chart_data?"
|
||||
f"instrument_name={symbol}&start_timestamp={start_timestamp}&"
|
||||
f"end_timestamp={end_timestamp}&resolution={interval}"
|
||||
)
|
||||
urls.append(url)
|
||||
|
||||
return urls
|
||||
|
||||
results: list = []
|
||||
|
||||
async def get_one(url):
|
||||
"""Get data from one url."""
|
||||
json_response = await amake_request(url)
|
||||
if json_response.get("error"): # type: ignore[union-attr]
|
||||
raise ValueError(json_response["error"]) # type: ignore[call-overload]
|
||||
if json_response.get("result"): # type: ignore[union-attr]
|
||||
result = json_response["result"] # type: ignore[call-overload]
|
||||
df = DataFrame(result)
|
||||
df = (
|
||||
df.drop(columns=["status"])
|
||||
.rename(columns={"ticks": "date", "cost": "volume_notional"})
|
||||
.convert_dtypes()
|
||||
)
|
||||
df["date"] = to_datetime(df.date, unit="ms", origin="unix", utc=True)
|
||||
if interval == "1D":
|
||||
df.date = df.date.dt.date
|
||||
df["symbol"] = symbol
|
||||
results.extend(
|
||||
df[
|
||||
[
|
||||
"date",
|
||||
"symbol",
|
||||
"open",
|
||||
"high",
|
||||
"low",
|
||||
"close",
|
||||
"volume",
|
||||
"volume_notional",
|
||||
]
|
||||
].to_dict(orient="records")
|
||||
)
|
||||
|
||||
urls = generate_urls(symbol, start_date, end_date, new_interval)
|
||||
|
||||
if len(urls) > 15:
|
||||
raise OpenBBError(
|
||||
"The request is too large. Break up the request into smaller chunks."
|
||||
)
|
||||
|
||||
tasks = [get_one(url) for url in urls]
|
||||
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
if results:
|
||||
return sorted(results, key=lambda x: x["date"])
|
||||
raise EmptyDataError("No data found for the given symbol and dates.")
|
||||
return sorted(str(d["instrument_name"]) for d in instruments)
|
||||
|
||||
|
||||
async def check_ohlc_symbol(symbol: str) -> bool | str:
|
||||
"""
|
||||
Check if the symbol has OHLC data.
|
||||
async def resolve_symbol(symbol: str) -> str:
|
||||
"""Return the instrument name a symbol refers to.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : str
|
||||
The symbol to check.
|
||||
An instrument name, or the shortened root of a perpetual.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the symbol has OHLC data, False otherwise.
|
||||
str
|
||||
The instrument name the exchange knows the symbol by.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the exchange lists no such instrument.
|
||||
"""
|
||||
all_instruments = await get_instruments("all", None)
|
||||
all_symbols = {
|
||||
d.get("instrument_name", ""): d.get("creation_timestamp")
|
||||
for d in all_instruments
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
|
||||
candidate = symbol.upper()
|
||||
perpetuals = await get_perpetual_symbols()
|
||||
|
||||
if candidate in perpetuals:
|
||||
return perpetuals[candidate]
|
||||
|
||||
instruments = await get_instruments("any", None)
|
||||
listed = {str(d.get("instrument_name")) for d in instruments}
|
||||
|
||||
if candidate in listed:
|
||||
return candidate
|
||||
|
||||
raise OpenBBError(f"Deribit lists no instrument named {symbol}.")
|
||||
|
||||
|
||||
def _chart_windows(
|
||||
start: int, end: int, resolution: str, window: int = CHART_WINDOW_SIZE
|
||||
) -> list[tuple[int, int]]:
|
||||
"""Split a span into windows of at most one page of candles each."""
|
||||
from pandas import date_range
|
||||
|
||||
freq = "1D" if resolution == "1D" else f"{resolution}min"
|
||||
stamps = date_range(start=from_timestamp(start), end=from_timestamp(end), freq=freq)
|
||||
|
||||
if len(stamps) <= 1:
|
||||
return [(start, end)]
|
||||
|
||||
return [
|
||||
(
|
||||
int(stamps[index].timestamp() * 1000),
|
||||
int(stamps[min(index + window, len(stamps) - 1)].timestamp() * 1000),
|
||||
)
|
||||
for index in range(0, len(stamps), window)
|
||||
]
|
||||
|
||||
|
||||
async def get_ohlc_data(
|
||||
symbol: str,
|
||||
start_date: Any,
|
||||
end_date: Any,
|
||||
interval: str = "1d",
|
||||
) -> list[dict]:
|
||||
"""Return the candles of one instrument over a span.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : str
|
||||
The instrument name.
|
||||
start_date : Any
|
||||
The first date of the span.
|
||||
end_date : Any
|
||||
The last date of the span.
|
||||
interval : str
|
||||
One of the supported candle intervals.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[dict]
|
||||
One record per candle, oldest first.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the span needs more pages than the exchange will serve at once.
|
||||
EmptyDataError
|
||||
If the instrument published no candles over the span.
|
||||
"""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
|
||||
from openbb_deribit.utils.client import gather
|
||||
from openbb_deribit.utils.constants import INTERVAL_MAP
|
||||
|
||||
resolution = INTERVAL_MAP.get(interval, interval)
|
||||
instruments = await get_instruments("any", None)
|
||||
created = {
|
||||
str(d.get("instrument_name")): d.get("creation_timestamp") for d in instruments
|
||||
}
|
||||
all_perpetuals = await get_perpetual_symbols()
|
||||
|
||||
if symbol in all_perpetuals:
|
||||
return all_perpetuals.get(symbol, False)
|
||||
if symbol in all_symbols:
|
||||
return symbol
|
||||
return False
|
||||
if symbol not in created:
|
||||
raise OpenBBError(f"Deribit lists no instrument named {symbol}.")
|
||||
|
||||
start = max(to_timestamp(start_date), int(created[symbol] or 0))
|
||||
end = to_timestamp(end_date)
|
||||
windows = _chart_windows(start, end, resolution)
|
||||
|
||||
if len(windows) > MAX_CHART_WINDOWS:
|
||||
raise OpenBBError(
|
||||
f"The span covers {len(windows)} pages of {interval} candles, and"
|
||||
f" Deribit serves at most {MAX_CHART_WINDOWS} at a time."
|
||||
" Narrow the dates, or widen the interval."
|
||||
)
|
||||
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
"get_tradingview_chart_data",
|
||||
{
|
||||
"instrument_name": symbol,
|
||||
"start_timestamp": window_start,
|
||||
"end_timestamp": window_end,
|
||||
"resolution": resolution,
|
||||
},
|
||||
)
|
||||
for window_start, window_end in windows
|
||||
]
|
||||
)
|
||||
records: dict[int, dict] = {}
|
||||
|
||||
for result in results:
|
||||
if not isinstance(result, dict) or not result.get("ticks"):
|
||||
continue
|
||||
|
||||
for index, tick in enumerate(result["ticks"]):
|
||||
records[tick] = {
|
||||
"date": from_timestamp(tick),
|
||||
"symbol": symbol,
|
||||
"open": result["open"][index],
|
||||
"high": result["high"][index],
|
||||
"low": result["low"][index],
|
||||
"close": result["close"][index],
|
||||
"volume": result["volume"][index],
|
||||
"volume_notional": result["cost"][index],
|
||||
}
|
||||
|
||||
if not records:
|
||||
raise EmptyDataError(f"Deribit published no {interval} candles for {symbol}.")
|
||||
|
||||
ordered = [records[tick] for tick in sorted(records)]
|
||||
|
||||
if resolution == "1D":
|
||||
for record in ordered:
|
||||
record["date"] = record["date"].date()
|
||||
|
||||
return ordered
|
||||
|
||||
|
||||
async def get_futures_symbols() -> list:
|
||||
"""
|
||||
Get futures symbols.
|
||||
async def get_futures_curve_by_hours_ago(symbol: str, hours: int) -> list[dict]:
|
||||
"""Return the futures curve of one underlying as it stood some hours ago.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : str
|
||||
The underlying root.
|
||||
hours : int
|
||||
How many hours back to read the curve.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
A list of futures symbols.
|
||||
list[dict]
|
||||
The last price of each contract that had traded by that hour. A contract
|
||||
the exchange published no candle for is left out, because a curve that
|
||||
carries the current price under a past label is not a past curve.
|
||||
"""
|
||||
instruments = await get_instruments("all", "future")
|
||||
return [d["instrument_name"] for d in instruments]
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
||||
async def get_futures_curve_by_hours_ago(symbol, hours):
|
||||
"""Get futures curve N hours ago."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import asyncio # noqa
|
||||
from datetime import datetime, timedelta
|
||||
from openbb_deribit.utils.client import gather
|
||||
|
||||
symbols = await get_futures_curve_symbols(symbol)
|
||||
now = datetime.now().replace(microsecond=0, second=0)
|
||||
now = datetime.now(timezone.utc).replace(microsecond=0, second=0, minute=0)
|
||||
target = to_timestamp(now - timedelta(hours=hours))
|
||||
start = to_timestamp(now - timedelta(hours=hours + CURVE_LOOKBACK_HOURS))
|
||||
results = await gather(
|
||||
[
|
||||
(
|
||||
"get_tradingview_chart_data",
|
||||
{
|
||||
"instrument_name": contract,
|
||||
"start_timestamp": start,
|
||||
"end_timestamp": target,
|
||||
"resolution": "60",
|
||||
},
|
||||
)
|
||||
for contract in symbols
|
||||
]
|
||||
)
|
||||
curve: list[dict] = []
|
||||
|
||||
if hours < 24:
|
||||
start = (now - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
else:
|
||||
start = (now - timedelta(hours=hours)).strftime("%Y-%m-%d")
|
||||
end = (now + timedelta(days=2)).strftime("%Y-%m-%d")
|
||||
for contract, result in zip(symbols, results):
|
||||
if not isinstance(result, dict) or not result.get("ticks"):
|
||||
continue
|
||||
|
||||
results: list = []
|
||||
ticks = result["ticks"]
|
||||
index = min(
|
||||
range(len(ticks)), key=lambda position: abs(ticks[position] - target)
|
||||
)
|
||||
curve.append(
|
||||
{
|
||||
"instrument_name": contract,
|
||||
"hours_ago": hours,
|
||||
"last_price": result["close"][index],
|
||||
}
|
||||
)
|
||||
|
||||
async def get_one(s):
|
||||
"""Get data for one symbol."""
|
||||
data = await get_ohlc_data(s, start, end, "1h")
|
||||
result = {
|
||||
"instrument_name": data[-1 - hours]["symbol"],
|
||||
"hours_ago": hours,
|
||||
"last_price": data[-1 - hours]["close"],
|
||||
}
|
||||
results.append(result)
|
||||
|
||||
tasks = [get_one(s) for s in symbols]
|
||||
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
return results
|
||||
return curve
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Deribit options analysis."""
|
||||
@@ -0,0 +1,337 @@
|
||||
"""Loading a Deribit option chain for analysis.
|
||||
|
||||
The chain endpoint subscribes to every contract over a websocket to collect the
|
||||
greeks the exchange publishes, which takes about twelve seconds for BTC. The
|
||||
analysis views need quotes, a volatility and the contract specification rather
|
||||
than the exchange's own greeks, and all three are reachable over REST in under
|
||||
a second, so this is where they are read from. The greeks are modelled from the
|
||||
quoted volatility instead.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pandas import DataFrame
|
||||
|
||||
CHAIN_TTL = 120.0
|
||||
|
||||
_LOADED: dict = {}
|
||||
_LOCKS: dict = {}
|
||||
|
||||
_SETTLEMENT = {"BTC": "BTC", "ETH": "ETH"}
|
||||
|
||||
|
||||
def settlement_of(root: str) -> str:
|
||||
"""Return the currency a root's options are collateralised in."""
|
||||
return _SETTLEMENT.get(root, "USDC")
|
||||
|
||||
|
||||
async def _source(root: str) -> tuple:
|
||||
"""Read the quotes, specifications, and index level of one underlying.
|
||||
|
||||
The specification is what names a contract's strike, side, size, and
|
||||
settlement style, so it is read rather than parsed back out of the
|
||||
instrument name, which spells a fractional strike with a letter.
|
||||
"""
|
||||
from openbb_deribit.utils.client import request
|
||||
from openbb_deribit.utils.helpers import get_instruments
|
||||
|
||||
currency = settlement_of(root)
|
||||
summary, instruments = await asyncio.gather(
|
||||
request(
|
||||
"get_book_summary_by_currency", {"currency": currency, "kind": "option"}
|
||||
),
|
||||
get_instruments(currency, "option"),
|
||||
)
|
||||
specs = {
|
||||
str(spec["instrument_name"]): spec
|
||||
for spec in instruments
|
||||
if str(spec["instrument_name"]).split("-")[0] == root
|
||||
}
|
||||
quotes = [row for row in summary if str(row["instrument_name"]) in specs]
|
||||
index = next(iter(specs.values()), {}).get("price_index")
|
||||
level = await request("get_index_price", {"index_name": index}) if index else {}
|
||||
|
||||
return quotes, specs, float((level or {}).get("index_price") or 0)
|
||||
|
||||
|
||||
def _rows(quotes: list, specs: dict, spot: float) -> list:
|
||||
"""Build one analysis row per quoted contract.
|
||||
|
||||
An inverse contract is quoted in the coin, so its premium is carried to the
|
||||
quote currency at the index level, which is what the chain endpoint does and
|
||||
what the valuation layer expects.
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from openbb_deribit.utils.helpers import from_timestamp
|
||||
from openbb_deribit.utils.options.greeks import SECONDS_PER_YEAR, greeks
|
||||
|
||||
now = datetime.now(timezone.utc).timestamp()
|
||||
built: list[dict] = []
|
||||
|
||||
for quote in quotes:
|
||||
name = str(quote["instrument_name"])
|
||||
spec = specs[name]
|
||||
option_type = str(spec["option_type"])
|
||||
strike = float(spec["strike"])
|
||||
inverse = str(spec.get("instrument_type")) == "reversed"
|
||||
carry = spot if inverse else 1.0
|
||||
forward = float(quote.get("underlying_price") or 0)
|
||||
volatility = quote.get("mark_iv")
|
||||
years = (float(spec["expiration_timestamp"]) / 1000 - now) / SECONDS_PER_YEAR
|
||||
modelled = greeks(
|
||||
forward, strike, years, float(volatility or 0) / 100, option_type
|
||||
)
|
||||
built.append(
|
||||
{
|
||||
"contract_symbol": name,
|
||||
"expiration": from_timestamp(spec["expiration_timestamp"]).date(),
|
||||
"dte": max(int(years * 365), 0),
|
||||
"strike": strike,
|
||||
"option_type": option_type,
|
||||
"contract_size": float(spec.get("contract_size") or 1),
|
||||
"tick_size": _carried(spec.get("tick_size"), carry),
|
||||
"is_inverse": inverse,
|
||||
"mark": _carried(quote.get("mark_price"), carry),
|
||||
"bid": _carried(quote.get("bid_price"), carry),
|
||||
"ask": _carried(quote.get("ask_price"), carry),
|
||||
"last": _carried(quote.get("last"), carry),
|
||||
"implied_volatility": volatility,
|
||||
"open_interest": quote.get("open_interest"),
|
||||
"volume": quote.get("volume"),
|
||||
"volume_notional": quote.get("volume_usd"),
|
||||
"underlying_price": forward,
|
||||
"underlying_spot_price": spot,
|
||||
**modelled,
|
||||
}
|
||||
)
|
||||
|
||||
return built
|
||||
|
||||
|
||||
def _carried(value: Any, carry: float) -> "float | None":
|
||||
"""Carry one quoted price to the currency the position is measured in."""
|
||||
return None if value is None else float(value) * carry
|
||||
|
||||
|
||||
async def load_chain(symbol: str, use_cache: bool = True) -> "DataFrame":
|
||||
"""Return the full chain of one underlying as a frame.
|
||||
|
||||
Reading a chain is three REST calls, so a dashboard showing several views of
|
||||
one underlying would make them once per view, and every parameter a reader
|
||||
changes would make them again. The last read is held and shared instead: a
|
||||
nine-widget tab costs three calls, and changing what is drawn costs none.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : str
|
||||
The underlying root, as it appears in the instrument name.
|
||||
use_cache : bool
|
||||
Whether a read taken moments ago may be reused.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DataFrame
|
||||
One row per contract, with its quote, greeks, and settlement style.
|
||||
|
||||
Raises
|
||||
------
|
||||
EmptyDataError
|
||||
If the exchange published no option quotes for the underlying.
|
||||
"""
|
||||
from time import monotonic
|
||||
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pandas import DataFrame
|
||||
|
||||
root = symbol.upper()
|
||||
lock = _LOCKS.setdefault(root, asyncio.Lock())
|
||||
|
||||
async with lock:
|
||||
held = _LOADED.get(root)
|
||||
|
||||
if use_cache and held and monotonic() - held[0] < CHAIN_TTL:
|
||||
return held[1].copy()
|
||||
|
||||
quotes, specs, spot = await _source(root)
|
||||
frame = DataFrame(_rows(quotes, specs, spot))
|
||||
|
||||
if frame.empty:
|
||||
raise EmptyDataError(f"Deribit published no option quotes for {root}.")
|
||||
|
||||
_LOADED[root] = (monotonic(), frame)
|
||||
|
||||
return frame.copy()
|
||||
|
||||
|
||||
async def load_combos(symbol: str, use_cache: bool = True) -> list:
|
||||
"""Return the combo books listed on an underlying's options, with their quotes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : str
|
||||
The underlying root, as it appears in the instrument name.
|
||||
use_cache : bool
|
||||
Whether a read taken moments ago may be reused.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
One record per combo: its name, its legs as (instrument, amount) pairs,
|
||||
and its bid and ask in the currency the combo is quoted in.
|
||||
"""
|
||||
from time import monotonic
|
||||
|
||||
from openbb_deribit.utils.client import request
|
||||
|
||||
root = symbol.upper()
|
||||
key = f"{root}#combos"
|
||||
lock = _LOCKS.setdefault(key, asyncio.Lock())
|
||||
|
||||
async with lock:
|
||||
held = _LOADED.get(key)
|
||||
|
||||
if use_cache and held and monotonic() - held[0] < CHAIN_TTL:
|
||||
return list(held[1])
|
||||
|
||||
currency = settlement_of(root)
|
||||
listed, quoted = await asyncio.gather(
|
||||
request("get_combos", {"currency": currency}),
|
||||
request(
|
||||
"get_book_summary_by_currency",
|
||||
{"currency": currency, "kind": "option_combo"},
|
||||
),
|
||||
)
|
||||
quotes = {str(row["instrument_name"]): row for row in quoted or []}
|
||||
books = []
|
||||
|
||||
for combo in listed or []:
|
||||
legs = [
|
||||
(str(leg["instrument_name"]), int(leg["amount"]))
|
||||
for leg in combo["legs"]
|
||||
]
|
||||
|
||||
if not all(
|
||||
name.split("-")[0] == root and len(name.split("-")) == 4
|
||||
for name, _ in legs
|
||||
):
|
||||
continue
|
||||
|
||||
quote = quotes.get(str(combo["id"]), {})
|
||||
books.append(
|
||||
{
|
||||
"name": str(combo["id"]),
|
||||
"legs": legs,
|
||||
"bid": quote.get("bid_price"),
|
||||
"ask": quote.get("ask_price"),
|
||||
}
|
||||
)
|
||||
|
||||
_LOADED[key] = (monotonic(), books)
|
||||
|
||||
return list(books)
|
||||
|
||||
|
||||
def underlying_price(frame: "DataFrame") -> float:
|
||||
"""Return the spot price of the underlying the chain was quoted against."""
|
||||
spot = frame["underlying_spot_price"].dropna()
|
||||
|
||||
return float(spot.iloc[0]) if not spot.empty else 0.0
|
||||
|
||||
|
||||
def is_inverse(frame: "DataFrame") -> bool:
|
||||
"""Return whether the chain's contracts settle in the underlying."""
|
||||
return bool(frame["is_inverse"].any())
|
||||
|
||||
|
||||
def settlement_currency(frame: "DataFrame", symbol: str) -> str:
|
||||
"""Return what a position in this chain settles in."""
|
||||
root = symbol.split("_", maxsplit=1)[0].upper()
|
||||
|
||||
return root if is_inverse(frame) else "USDC"
|
||||
|
||||
|
||||
def expirations(frame: "DataFrame") -> list:
|
||||
"""Return every expiration on the chain, nearest first."""
|
||||
return sorted(frame["expiration"].dropna().unique())
|
||||
|
||||
|
||||
def nearest_expiration(frame: "DataFrame", target: Any) -> Any:
|
||||
"""Return the expiration closest to a date.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame : DataFrame
|
||||
The chain.
|
||||
target : Any
|
||||
The date the position is held to.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
The listed expiration nearest the target.
|
||||
"""
|
||||
from pandas import to_datetime
|
||||
|
||||
wanted = to_datetime(target).date()
|
||||
listed = expirations(frame)
|
||||
|
||||
return min(listed, key=lambda day: abs((day - wanted).days))
|
||||
|
||||
|
||||
def far_expiration(frame: "DataFrame", near: Any, gap: int = 30) -> Any:
|
||||
"""Return the expiration a calendar sells the near one against.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame : DataFrame
|
||||
The chain.
|
||||
near : Any
|
||||
The expiration being traded.
|
||||
gap : int
|
||||
How many days past it the far expiration is sought.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
The later expiration nearest the gap, or None when nothing is later.
|
||||
"""
|
||||
later = [day for day in expirations(frame) if day > near]
|
||||
|
||||
if not later:
|
||||
return None
|
||||
|
||||
return min(later, key=lambda day: abs((day - near).days - gap))
|
||||
|
||||
|
||||
def quotes_at(frame: "DataFrame", expiration: Any = None) -> "DataFrame":
|
||||
"""Return the tradeable contracts, entered at no less than one tick.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame : DataFrame
|
||||
The chain.
|
||||
expiration : Any
|
||||
The expiration to keep. None keeps every expiration.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DataFrame
|
||||
The contracts with a price, each with the price it is entered and left at.
|
||||
"""
|
||||
at = (
|
||||
frame.copy()
|
||||
if expiration is None
|
||||
else frame[frame["expiration"] == expiration].copy()
|
||||
)
|
||||
|
||||
for column in ("bid", "ask", "mark", "tick_size"):
|
||||
at[column] = at[column].astype(float)
|
||||
|
||||
entry = at["ask"].where(at["ask"] > 0, at["mark"])
|
||||
at["entry"] = entry.combine(at["tick_size"].fillna(0), max).where(entry > 0, 0.0)
|
||||
at["exit"] = at["bid"].where(at["bid"] > 0, at["mark"])
|
||||
|
||||
return at[at["entry"] > 0].sort_values(["option_type", "strike"])
|
||||
@@ -0,0 +1,145 @@
|
||||
"""The payoff diagram of one Deribit option position."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
|
||||
|
||||
def _mark(figure, price: float, text: str, color: str, height: float) -> None:
|
||||
"""Draw one labelled vertical marker at a price."""
|
||||
figure.add_vline(x=price, line_width=1, line_dash="dash", line_color=color)
|
||||
figure.add_annotation(
|
||||
x=price,
|
||||
y=height,
|
||||
yref="paper",
|
||||
text=text,
|
||||
showarrow=False,
|
||||
font=dict(size=10, color=color),
|
||||
bordercolor=color,
|
||||
borderwidth=1,
|
||||
borderpad=3,
|
||||
bgcolor="rgba(255, 255, 255, 0.85)",
|
||||
yanchor="middle",
|
||||
)
|
||||
|
||||
|
||||
def create_payoff(data: dict, theme: str = "dark", **kwargs) -> "OBBject":
|
||||
"""Draw what one position returns across a range of underlying prices.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : dict
|
||||
The sized position, the prices it was valued at, the profit at each,
|
||||
the spot and target prices, and what it settles in.
|
||||
theme : str
|
||||
Either 'dark' or 'light'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
OBBject
|
||||
The figure, with the evaluated curve as its results.
|
||||
"""
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.utils import df_to_basemodel
|
||||
from pandas import DataFrame
|
||||
|
||||
from openbb_deribit.utils.options.theme import finalize, new_figure
|
||||
|
||||
prices = data["prices"]
|
||||
values = data["payoff"]
|
||||
spot = float(data.get("spot") or 0)
|
||||
target = float(data.get("target") or spot)
|
||||
settles = str(data.get("settles") or "")
|
||||
quote = str(data.get("quote") or "USD")
|
||||
figure, text_color, background = new_figure(theme)
|
||||
|
||||
figure.add_scatter(
|
||||
x=prices,
|
||||
y=[max(value, 0) for value in values],
|
||||
mode="lines",
|
||||
name="Profit",
|
||||
line=dict(width=0),
|
||||
fill="tozeroy",
|
||||
fillcolor="rgba(63, 185, 80, 0.22)",
|
||||
hoverinfo="skip",
|
||||
)
|
||||
figure.add_scatter(
|
||||
x=prices,
|
||||
y=[min(value, 0) for value in values],
|
||||
mode="lines",
|
||||
name="Loss",
|
||||
line=dict(width=0),
|
||||
fill="tozeroy",
|
||||
fillcolor="rgba(227, 93, 106, 0.22)",
|
||||
hoverinfo="skip",
|
||||
)
|
||||
figure.add_scatter(
|
||||
x=prices,
|
||||
y=values,
|
||||
mode="lines",
|
||||
name="P/L",
|
||||
line=dict(color="#2f6fed", width=2),
|
||||
hovertemplate="<b>%{y:,.6f}</b><extra></extra>",
|
||||
)
|
||||
|
||||
low, high = min(prices), max(prices)
|
||||
markers = [(spot, f"Spot {spot:,.6g}", "#8b949e")]
|
||||
|
||||
if abs(target - spot) > (high - low) / 50:
|
||||
markers.append((target, f"Target {target:,.6g}", "#8b949e"))
|
||||
|
||||
markers += [
|
||||
(level, f"B/E {level:,.6g}", "#e35d6a")
|
||||
for level in data.get("breakevens") or []
|
||||
]
|
||||
markers = sorted(marker for marker in markers if low <= marker[0] <= high)
|
||||
|
||||
for index, (price, text, color) in enumerate(markers):
|
||||
_mark(figure, price, text, color, 0.95 if index % 2 == 0 else 0.83)
|
||||
|
||||
profit = float(data.get("expected_profit") or 0)
|
||||
cost = float(data.get("cost") or 0)
|
||||
coin = f" | settled in {settles}" if settles != quote else ""
|
||||
priced = (
|
||||
f"for {cost:,.0f} {quote}"
|
||||
if cost >= 0
|
||||
else f"for a {abs(cost):,.0f} {quote} credit"
|
||||
)
|
||||
listed = f" | {data['combo']}" if data.get("combo") else ""
|
||||
figure.set_title(
|
||||
f"{data.get('strategy', '')} {priced}"
|
||||
f"<br><span style='font-size:12px'>{data.get('legs', '')}{listed}"
|
||||
f" | expected {profit:+,.0f} {quote}"
|
||||
f" | spot {spot:,.6g}{coin}</span>",
|
||||
x=0.5,
|
||||
font=dict(size=15),
|
||||
)
|
||||
figure.update_layout(
|
||||
paper_bgcolor=background,
|
||||
plot_bgcolor=background,
|
||||
font=dict(color=text_color),
|
||||
margin=dict(l=10, r=10, t=76, b=10),
|
||||
hovermode="x",
|
||||
showlegend=False,
|
||||
xaxis=dict(
|
||||
title="Underlying price",
|
||||
showgrid=False,
|
||||
linecolor=text_color,
|
||||
hoverformat=",.6g",
|
||||
autorange=False,
|
||||
range=[low, high],
|
||||
),
|
||||
yaxis=dict(
|
||||
title=f"Profit and loss ({quote})",
|
||||
side="left",
|
||||
showgrid=True,
|
||||
zeroline=True,
|
||||
zerolinecolor=text_color,
|
||||
linecolor=text_color,
|
||||
),
|
||||
)
|
||||
frame = DataFrame({"underlying_price": prices, "profit_or_loss": values})
|
||||
output: Any = OBBject(results=df_to_basemodel(frame))
|
||||
|
||||
return finalize(output, figure, theme)
|
||||
@@ -0,0 +1,209 @@
|
||||
"""Deribit implied-volatility smile and skew chart."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.provider.standard_models.options_chains import OptionsChainsData
|
||||
|
||||
MAX_EXPIRATIONS = 5
|
||||
|
||||
COLORS = [
|
||||
"royalblue",
|
||||
"red",
|
||||
"orange",
|
||||
"green",
|
||||
"grey",
|
||||
"burlywood",
|
||||
"magenta",
|
||||
"cyan",
|
||||
"indigo",
|
||||
"yellowgreen",
|
||||
]
|
||||
|
||||
|
||||
def _first_priced_expiration(data: OptionsChainsData) -> str:
|
||||
"""Return the front expiration that still carries non-zero implied volatility.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : OptionsChainsData
|
||||
The loaded Deribit options chain.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The earliest expiration with usable implied volatility.
|
||||
"""
|
||||
df = data.dataframe
|
||||
priced = df[df["implied_volatility"] > 0]
|
||||
|
||||
if priced.empty:
|
||||
from openbb_deribit.utils.options.data_handler import chain_expirations
|
||||
|
||||
expirations = chain_expirations(data)
|
||||
|
||||
return expirations[0] if expirations else data.expirations[0]
|
||||
|
||||
return sorted(priced["expiration"].astype(str).unique())[0]
|
||||
|
||||
|
||||
def create_smile(
|
||||
data: OptionsChainsData,
|
||||
expirations: str | None = None,
|
||||
otm: bool = False,
|
||||
skew: bool = False,
|
||||
**kwargs,
|
||||
) -> OBBject:
|
||||
"""Build the IV smile/skew across strikes for up to five expirations.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : OptionsChainsData
|
||||
The loaded Deribit options chain.
|
||||
expirations : str | None
|
||||
Up to five comma-separated expiration dates. Defaults to the front expiry.
|
||||
otm : bool
|
||||
When True, restrict each curve to out-of-the-money contracts.
|
||||
skew : bool
|
||||
When True, plot the skew relative to ATM instead of raw implied volatility.
|
||||
|
||||
Returns
|
||||
-------
|
||||
OBBject
|
||||
The plotted rows, with the Plotly figure attached to ``chart``.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the chain carries no implied volatility, or more than five expirations
|
||||
were requested.
|
||||
"""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.utils import df_to_basemodel
|
||||
from pandas import DataFrame, concat, to_datetime
|
||||
|
||||
from openbb_deribit.utils.options.theme import finalize, new_figure
|
||||
|
||||
if data.has_iv is False:
|
||||
raise OpenBBError(
|
||||
"Implied Volatility was not found in the data and is required here."
|
||||
)
|
||||
|
||||
exp_list = (
|
||||
expirations.split(",")
|
||||
if isinstance(expirations, str)
|
||||
else expirations
|
||||
if isinstance(expirations, list)
|
||||
else [_first_priced_expiration(data)]
|
||||
)
|
||||
exp_list = [data._get_nearest_expiration(e) for e in exp_list]
|
||||
|
||||
if len(exp_list) > MAX_EXPIRATIONS:
|
||||
raise OpenBBError("Too many dates! Up to five can be selected.")
|
||||
|
||||
df = concat(
|
||||
[data.skew(date=cast(str, to_datetime(exp).date())) for exp in exp_list]
|
||||
)
|
||||
output_df = DataFrame()
|
||||
symbol = data.underlying_symbol[0]
|
||||
index_name = exp_list[0] if len(exp_list) <= 1 else None
|
||||
target_col = "Skew" if skew is True else "IV"
|
||||
title = (
|
||||
f"{symbol} {'OTM ' if otm is True else ''}"
|
||||
f"{'IV Skew' if skew is True else 'Implied Volatility'}"
|
||||
)
|
||||
theme = kwargs.get("theme") or "dark"
|
||||
fig, text_color, background = new_figure(theme)
|
||||
color = -1
|
||||
|
||||
for expiration in exp_list:
|
||||
calls = (
|
||||
df.query("`Expiration` == @expiration & `Option Type` == 'call'")
|
||||
.copy()
|
||||
.reset_index(drop=True)
|
||||
)
|
||||
puts = (
|
||||
df.query("`Expiration` == @expiration & `Option Type` == 'put'")
|
||||
.copy()
|
||||
.reset_index(drop=True)
|
||||
)
|
||||
|
||||
if otm is True:
|
||||
put_idx = puts[puts["Skew"] == 0].index.values[0]
|
||||
call_idx = calls[calls["Skew"] == 0].index.values[0]
|
||||
puts = puts.iloc[0 : put_idx + 1].reset_index(drop=True)
|
||||
calls = calls.iloc[call_idx:-1].reset_index(drop=True)
|
||||
|
||||
output_df = (
|
||||
concat([output_df, calls, puts], axis=0)
|
||||
if not output_df.empty
|
||||
else concat([calls, puts], axis=0)
|
||||
)
|
||||
color = color + 1
|
||||
fig.add_scatter(
|
||||
x=calls["Strike"].unique().tolist(),
|
||||
y=calls[target_col],
|
||||
mode="lines+markers",
|
||||
name="Calls" if len(exp_list) <= 1 else f"Calls at {expiration}",
|
||||
marker_color=COLORS[color],
|
||||
hoverinfo="x+y+name",
|
||||
)
|
||||
color = color + 1
|
||||
fig.add_scatter(
|
||||
x=puts["Strike"].unique().tolist(),
|
||||
y=puts[target_col],
|
||||
mode="lines+markers",
|
||||
name="Puts" if len(exp_list) <= 1 else f"Puts at {expiration}",
|
||||
marker_color=COLORS[color],
|
||||
hoverinfo="x+y+name",
|
||||
)
|
||||
|
||||
fig.update_layout(
|
||||
title=dict(text=title, x=0.5, font=dict(size=18)),
|
||||
yaxis=dict(
|
||||
ticklen=0,
|
||||
showline=False,
|
||||
linecolor=text_color,
|
||||
tickfont=dict(size=14),
|
||||
nticks=7,
|
||||
),
|
||||
xaxis=dict(
|
||||
showgrid=False,
|
||||
autorange=True,
|
||||
ticklen=5,
|
||||
showline=False,
|
||||
linecolor=text_color,
|
||||
title=dict(text=index_name if index_name else "", font=dict(size=16)),
|
||||
tickfont=dict(size=16),
|
||||
nticks=7,
|
||||
showspikes=False,
|
||||
tickprefix="$",
|
||||
),
|
||||
legend=dict(
|
||||
font=dict(size=14),
|
||||
orientation="v",
|
||||
yanchor="top",
|
||||
y=1,
|
||||
xanchor="center",
|
||||
x=0.5,
|
||||
itemdoubleclick="toggleothers",
|
||||
),
|
||||
font=dict(color=text_color),
|
||||
paper_bgcolor=background,
|
||||
plot_bgcolor=background,
|
||||
hoverdistance=1,
|
||||
hovermode="x unified",
|
||||
dragmode="pan",
|
||||
)
|
||||
output_df = (
|
||||
output_df.set_index(["Expiration", "Strike", "Option Type"])
|
||||
.sort_index()
|
||||
.reset_index()
|
||||
)
|
||||
output: Any = OBBject(results=df_to_basemodel(output_df))
|
||||
|
||||
return finalize(output, fig, theme)
|
||||
@@ -0,0 +1,143 @@
|
||||
"""Deribit open-interest and volume statistics chart."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.provider.standard_models.options_chains import OptionsChainsData
|
||||
|
||||
|
||||
def create_stats(
|
||||
data: OptionsChainsData,
|
||||
by: Literal["strike", "expiration"] = "expiration",
|
||||
metric: Literal["oi", "volume"] = "oi",
|
||||
date: str | None = None,
|
||||
unit: Literal["value", "percent", "pcr"] = "value",
|
||||
**kwargs,
|
||||
) -> OBBject:
|
||||
"""Chart open interest or volume by strike or expiration.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : OptionsChainsData
|
||||
The loaded Deribit options chain.
|
||||
by : Literal["strike", "expiration"]
|
||||
The axis to aggregate over.
|
||||
metric : Literal["oi", "volume"]
|
||||
Open interest or contract volume.
|
||||
date : str | None
|
||||
Restrict to a single expiration, which forces the strike axis.
|
||||
unit : Literal["value", "percent", "pcr"]
|
||||
Raw values, share of total, or the put/call ratio.
|
||||
|
||||
Returns
|
||||
-------
|
||||
OBBject
|
||||
The aggregated rows, with the Plotly figure attached to ``chart``.
|
||||
"""
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.utils import df_to_basemodel
|
||||
|
||||
from openbb_deribit.utils.options.theme import finalize, new_figure
|
||||
|
||||
stat = "open_interest" if metric == "oi" else metric
|
||||
|
||||
if date is not None:
|
||||
date = data._get_nearest_expiration(date)
|
||||
stats = data.filter_data(by=by, date=date, stat=stat)
|
||||
else:
|
||||
stats = data.filter_data(by=by, stat=stat)
|
||||
|
||||
symbol = data.underlying_symbol[-1]
|
||||
stat_type = stat.replace("_", " ").title()
|
||||
index_name = "Expiration" if by == "expiration" and not date else "Strike"
|
||||
stats = stats.set_index(index_name)
|
||||
stats.Puts = stats.Puts * (-1)
|
||||
title = (
|
||||
f"{symbol} {stat_type} (%)"
|
||||
if unit == "percent"
|
||||
else f"{symbol} {stat_type} By {index_name}"
|
||||
)
|
||||
theme = kwargs.get("theme") or "dark"
|
||||
fig, text_color, background = new_figure(theme)
|
||||
xtick_vals = (
|
||||
[
|
||||
round(d) if "." in str(d) and str(d).endswith(".0") else round(d, 2)
|
||||
for d in stats.index.tolist()
|
||||
]
|
||||
if by == "strike"
|
||||
else stats.index
|
||||
)
|
||||
|
||||
if unit == "percent":
|
||||
stats = stats.dropna(subset=["Net Percent"])
|
||||
fig.add_bar(
|
||||
x=stats.index,
|
||||
y=stats["Net Percent"],
|
||||
name="% of Total",
|
||||
orientation="v",
|
||||
hovertemplate="%{y:.4f}%",
|
||||
)
|
||||
elif unit == "pcr":
|
||||
title = f"{symbol} {stat_type} Put/Call Ratio"
|
||||
fig.add_bar(
|
||||
x=xtick_vals,
|
||||
y=stats.PCR,
|
||||
orientation="v",
|
||||
name="Put/Call Ratio",
|
||||
hovertemplate="%{y:.4f}",
|
||||
)
|
||||
else:
|
||||
fig.add_bar(
|
||||
x=xtick_vals,
|
||||
y=stats.Calls,
|
||||
name="Calls",
|
||||
orientation="v",
|
||||
marker=dict(color="royalblue"),
|
||||
hovertemplate="%{y:.0f}",
|
||||
)
|
||||
fig.add_bar(
|
||||
x=xtick_vals,
|
||||
y=stats.Puts,
|
||||
name="Puts",
|
||||
orientation="v",
|
||||
marker=dict(color="red"),
|
||||
hovertemplate="%{y:.0f}",
|
||||
)
|
||||
|
||||
fig.update_traces(width=0.95, selector=dict(type="bar"))
|
||||
fig.set_title(title, x=0.5, font=dict(size=16))
|
||||
fig.update_layout(
|
||||
paper_bgcolor=background,
|
||||
plot_bgcolor=background,
|
||||
barmode="relative",
|
||||
yaxis=dict(
|
||||
ticklen=0,
|
||||
showgrid=True,
|
||||
tickfont=dict(size=12),
|
||||
automargin=True,
|
||||
linecolor=text_color,
|
||||
showline=True,
|
||||
),
|
||||
xaxis=dict(
|
||||
showgrid=False,
|
||||
tickfont=dict(size=11),
|
||||
ticklen=0,
|
||||
type="category",
|
||||
linecolor=text_color,
|
||||
showline=True,
|
||||
nticks=10,
|
||||
tickprefix="$" if by == "strike" else "",
|
||||
showspikes=False,
|
||||
),
|
||||
legend=dict(orientation="v", yanchor="top", y=0.90, xanchor="right", x=-0.01),
|
||||
font=dict(color=text_color),
|
||||
margin=dict(l=10, r=10, t=10, b=10),
|
||||
hovermode="x unified",
|
||||
)
|
||||
stats.Puts = stats.Puts * (-1)
|
||||
output: Any = OBBject(results=df_to_basemodel(stats))
|
||||
|
||||
return finalize(output, fig, theme)
|
||||
@@ -0,0 +1,199 @@
|
||||
"""Deribit options 3-D surface chart over DTE and strike."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.provider.standard_models.options_chains import OptionsChainsData
|
||||
|
||||
SurfaceMetric = Literal[
|
||||
"implied_volatility", "delta", "gamma", "theta", "vega", "rho", "dex", "gex"
|
||||
]
|
||||
|
||||
METRIC_TITLES = {
|
||||
"implied_volatility": "IV",
|
||||
"delta": "Delta",
|
||||
"gamma": "Gamma",
|
||||
"theta": "Theta",
|
||||
"vega": "Vega",
|
||||
"rho": "Rho",
|
||||
"dex": "DEX",
|
||||
"gex": "GEX",
|
||||
}
|
||||
|
||||
OPTION_TYPE_LABELS = {"calls": "Call", "puts": "Put", "otm": "OTM", "itm": "ITM"}
|
||||
|
||||
|
||||
def create_surface(
|
||||
data: OptionsChainsData,
|
||||
option_type: Literal["otm", "itm", "puts", "calls"] = "otm",
|
||||
metric: SurfaceMetric = "implied_volatility",
|
||||
dte_range: list[int] | None = None,
|
||||
moneyness: float | None = None,
|
||||
oi: bool = False,
|
||||
volume: bool = False,
|
||||
**kwargs,
|
||||
) -> OBBject:
|
||||
"""Chart an option metric as a 3-D surface over DTE and strike.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : OptionsChainsData
|
||||
The loaded Deribit options chain.
|
||||
option_type : Literal["otm", "itm", "puts", "calls"]
|
||||
Which side of the chain to plot.
|
||||
metric : SurfaceMetric
|
||||
The Z-axis metric.
|
||||
dte_range : list[int] | None
|
||||
Inclusive lower and upper days-to-expiry bounds.
|
||||
moneyness : float | None
|
||||
Restrict strikes to within this percent of the underlying price.
|
||||
oi : bool
|
||||
When True, drop contracts with no open interest.
|
||||
volume : bool
|
||||
When True, drop contracts that have not traded.
|
||||
|
||||
Returns
|
||||
-------
|
||||
OBBject
|
||||
The plotted rows, with the Plotly figure attached to ``chart``.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the requested metric is not present in the chain.
|
||||
"""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.app.model.charts.chart import Chart
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.utils import df_to_basemodel
|
||||
from pandas import concat
|
||||
|
||||
from openbb_deribit import CHARTING_INSTALLED
|
||||
from openbb_deribit.utils.options.theme import CHART_CONFIG
|
||||
|
||||
options = data.dataframe
|
||||
column_map = {c.lower(): c for c in options.columns}
|
||||
target = column_map.get(metric.lower(), metric)
|
||||
|
||||
if target not in options.columns:
|
||||
raise OpenBBError(f"No {metric} data available in this chain.")
|
||||
|
||||
last_price = float(data.underlying_price[0] or 0.0)
|
||||
symbol = data.underlying_symbol[0]
|
||||
calls = options.query(f"`option_type` == 'call' & `dte` >= 0 & `{target}` != 0")
|
||||
puts = options.query(f"`option_type` == 'put' & `dte` >= 0 & `{target}` != 0")
|
||||
|
||||
if oi:
|
||||
calls = calls[calls["open_interest"] > 0]
|
||||
puts = puts[puts["open_interest"] > 0]
|
||||
|
||||
if volume:
|
||||
calls = calls[calls["volume"] > 0]
|
||||
puts = puts[puts["volume"] > 0]
|
||||
|
||||
def _between(frame, col, low, high):
|
||||
"""Filter a frame to an inclusive range on one column."""
|
||||
return frame[(frame[col] >= low) & (frame[col] <= high)]
|
||||
|
||||
if dte_range is not None and len(dte_range) > 1:
|
||||
calls = _between(calls, "dte", min(dte_range), max(dte_range))
|
||||
puts = _between(puts, "dte", min(dte_range), max(dte_range))
|
||||
|
||||
if moneyness is not None and moneyness > 0:
|
||||
low = (1 - (moneyness / 100)) * last_price
|
||||
high = (1 + (moneyness / 100)) * last_price
|
||||
calls = _between(calls, "strike", low, high)
|
||||
puts = _between(puts, "strike", low, high)
|
||||
|
||||
keys = ["expiration", "strike", "option_type"]
|
||||
|
||||
if option_type == "otm":
|
||||
df = (
|
||||
concat(
|
||||
[
|
||||
calls.query("`strike` > @last_price").set_index(keys),
|
||||
puts.query("`strike` < @last_price").set_index(keys),
|
||||
]
|
||||
)
|
||||
.sort_index()
|
||||
.reset_index()
|
||||
)
|
||||
elif option_type == "itm":
|
||||
df = (
|
||||
concat(
|
||||
[
|
||||
calls.query("`strike` < @last_price").set_index(keys),
|
||||
puts.query("`strike` > @last_price").set_index(keys),
|
||||
]
|
||||
)
|
||||
.sort_index()
|
||||
.reset_index()
|
||||
)
|
||||
elif option_type == "calls":
|
||||
df = calls
|
||||
else:
|
||||
df = puts
|
||||
|
||||
columns = [
|
||||
"expiration",
|
||||
"strike",
|
||||
"option_type",
|
||||
"dte",
|
||||
target,
|
||||
"open_interest",
|
||||
"volume",
|
||||
]
|
||||
df = df[[c for c in dict.fromkeys(columns) if c in df.columns]]
|
||||
metric_title = METRIC_TITLES.get(metric, metric.replace("_", " ").title())
|
||||
label = f"{symbol} {OPTION_TYPE_LABELS[option_type]} {metric_title} Surface"
|
||||
|
||||
if oi:
|
||||
label += " With Open Interest"
|
||||
|
||||
if volume:
|
||||
label += " Excluding Untraded Contracts"
|
||||
|
||||
theme = kwargs.get("theme") or "dark"
|
||||
df.expiration = df.expiration.astype(str)
|
||||
output: Any = OBBject(results=df_to_basemodel(df))
|
||||
|
||||
if not CHARTING_INSTALLED:
|
||||
return output
|
||||
|
||||
from openbb_charting.charts.generic_charts import surface3d
|
||||
|
||||
fig = surface3d(
|
||||
X=df["dte"],
|
||||
Y=df["strike"],
|
||||
Z=df[target],
|
||||
xtitle="DTE",
|
||||
ytitle="Strike",
|
||||
ztitle=metric_title,
|
||||
title=label,
|
||||
theme=theme,
|
||||
)
|
||||
text_color = "white" if theme == "dark" else "black"
|
||||
grid_color = "rgba(255,255,255,0.15)" if theme == "dark" else "rgba(0,0,0,0.15)"
|
||||
wall_color = "rgba(255,255,255,0.04)" if theme == "dark" else "rgba(0,0,0,0.04)"
|
||||
scene_axis = dict(
|
||||
color=text_color, gridcolor=grid_color, backgroundcolor=wall_color
|
||||
)
|
||||
fig.update_layout(
|
||||
paper_bgcolor="rgba(0,0,0,0)",
|
||||
plot_bgcolor="rgba(0,0,0,0)",
|
||||
font=dict(color=text_color),
|
||||
title=dict(font=dict(color=text_color)),
|
||||
scene=dict(xaxis=scene_axis, yaxis=scene_axis, zaxis=scene_axis),
|
||||
hoverlabel=dict(
|
||||
bgcolor="#0E0E0E" if theme == "dark" else "#FFFFFF",
|
||||
bordercolor=text_color,
|
||||
font=dict(color=text_color, size=12),
|
||||
),
|
||||
)
|
||||
content = fig.show(config=CHART_CONFIG, external=True).to_plotly_json()
|
||||
output.chart = Chart(fig=fig, content=content, format="plotly")
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,191 @@
|
||||
"""Deribit options term-structure chart across expirations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.provider.standard_models.options_chains import OptionsChainsData
|
||||
|
||||
|
||||
def create_term_structure(
|
||||
data: OptionsChainsData,
|
||||
strike: float | None = None,
|
||||
moneyness: float | None = None,
|
||||
metric: Literal["price", "iv"] = "iv",
|
||||
option_type: Literal["both", "calls", "puts"] = "both",
|
||||
**kwargs,
|
||||
) -> OBBject:
|
||||
"""Chart price or implied volatility at the nearest strike across expirations.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : OptionsChainsData
|
||||
The loaded Deribit options chain.
|
||||
strike : float | None
|
||||
Target strike. Defaults to the nearest out-of-the-money strike per expiry.
|
||||
moneyness : float | None
|
||||
Select strikes this percent out-of-the-money instead of a fixed strike.
|
||||
metric : Literal["price", "iv"]
|
||||
Contract price or implied volatility.
|
||||
option_type : Literal["both", "calls", "puts"]
|
||||
Which side of the chain to plot.
|
||||
|
||||
Returns
|
||||
-------
|
||||
OBBject
|
||||
The plotted rows, with the Plotly figure attached to ``chart``.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If implied volatility was requested but is absent from the chain.
|
||||
"""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.app.model.obbject import OBBject
|
||||
from openbb_core.app.utils import df_to_basemodel
|
||||
from pandas import DataFrame, concat
|
||||
|
||||
from openbb_deribit.utils.options.theme import finalize, new_figure
|
||||
|
||||
if metric == "iv" and not data.has_iv:
|
||||
raise OpenBBError("No implied volatility data available.")
|
||||
|
||||
from openbb_deribit.utils.options.data_handler import chain_expirations
|
||||
|
||||
df = data.dataframe.copy()
|
||||
expirations = chain_expirations(data)
|
||||
symbol = data.underlying_symbol[0]
|
||||
price_col = (
|
||||
"last_trade_price"
|
||||
if "last_trade_price" in df.columns
|
||||
else data._identify_price_col(df, "call", "ask")
|
||||
)
|
||||
target_col_map = {"price": price_col, "iv": "implied_volatility"}
|
||||
df.expiration = df.expiration.astype(str)
|
||||
base = f"{symbol} {'Implied Volatility' if metric == 'iv' else 'Price'}"
|
||||
|
||||
if strike:
|
||||
title = f"{base} @ Strike Nearest To ${strike}"
|
||||
elif moneyness:
|
||||
title = f"{base} @ {moneyness}% Moneyness"
|
||||
else:
|
||||
title = f"{base} Nearest OTM Strikes"
|
||||
|
||||
calls = DataFrame()
|
||||
puts = DataFrame()
|
||||
|
||||
for expiration in expirations:
|
||||
nearest_otm = (
|
||||
data._get_nearest_otm_strikes(expiration, moneyness=moneyness)
|
||||
if moneyness
|
||||
else {}
|
||||
)
|
||||
call_strike = (
|
||||
nearest_otm.get("call")
|
||||
if nearest_otm
|
||||
else data._get_nearest_strike(
|
||||
option_type="call", days=expiration, strike=strike
|
||||
)
|
||||
)
|
||||
put_strike = (
|
||||
nearest_otm.get("put")
|
||||
if nearest_otm
|
||||
else data._get_nearest_strike(
|
||||
option_type="put", days=expiration, strike=strike
|
||||
)
|
||||
)
|
||||
calls_filtered = df[
|
||||
(df.expiration == expiration)
|
||||
& (df.option_type == "call")
|
||||
& (df[price_col] > 0)
|
||||
]
|
||||
|
||||
if not calls_filtered.empty:
|
||||
calls = concat(
|
||||
[
|
||||
calls,
|
||||
calls_filtered.iloc[
|
||||
(calls_filtered.strike - call_strike).abs().argsort()[:1]
|
||||
],
|
||||
]
|
||||
)
|
||||
|
||||
puts_filtered = df[
|
||||
(df.expiration == expiration)
|
||||
& (df.option_type == "put")
|
||||
& (df[price_col] > 0)
|
||||
]
|
||||
|
||||
if not puts_filtered.empty:
|
||||
puts = concat(
|
||||
[
|
||||
puts,
|
||||
puts_filtered.iloc[
|
||||
(puts_filtered.strike - put_strike).abs().argsort()[:1]
|
||||
],
|
||||
]
|
||||
)
|
||||
|
||||
output_df = concat([calls, puts], axis=0).reset_index(drop=True)
|
||||
theme = kwargs.get("theme") or "dark"
|
||||
fig, text_color, background = new_figure(theme)
|
||||
hovertemplate = (
|
||||
"$<b>%{y} @ $</b>%{customdata} Strike<extra></extra>"
|
||||
if metric == "price"
|
||||
else "<b>%{y} @ $</b>%{customdata} Strike<extra></extra>"
|
||||
)
|
||||
|
||||
if option_type in ["calls", "both"] and not calls.empty:
|
||||
fig.add_scatter(
|
||||
x=calls["expiration"],
|
||||
y=calls[target_col_map[metric]],
|
||||
mode="lines+markers",
|
||||
name="Calls",
|
||||
marker_color="royalblue",
|
||||
hovertemplate=hovertemplate,
|
||||
customdata=calls["strike"],
|
||||
)
|
||||
|
||||
if option_type in ["puts", "both"] and not puts.empty:
|
||||
fig.add_scatter(
|
||||
x=puts["expiration"],
|
||||
y=puts[target_col_map[metric]],
|
||||
mode="lines+markers",
|
||||
name="Puts",
|
||||
marker_color="red",
|
||||
hovertemplate=hovertemplate,
|
||||
customdata=puts["strike"],
|
||||
)
|
||||
|
||||
fig.set_title(title, x=0.5, font=dict(size=16))
|
||||
fig.update_layout(
|
||||
paper_bgcolor=background,
|
||||
plot_bgcolor=background,
|
||||
yaxis=dict(
|
||||
ticklen=0,
|
||||
showgrid=True,
|
||||
tickfont=dict(size=12),
|
||||
automargin=True,
|
||||
linecolor=text_color,
|
||||
showline=True,
|
||||
),
|
||||
xaxis=dict(
|
||||
showgrid=False,
|
||||
tickfont=dict(size=11),
|
||||
ticklen=0,
|
||||
type="category",
|
||||
linecolor=text_color,
|
||||
showline=True,
|
||||
nticks=10,
|
||||
showspikes=False,
|
||||
),
|
||||
legend=dict(orientation="v", yanchor="top", y=0.90, xanchor="right", x=-0.01),
|
||||
hovermode="x unified",
|
||||
font=dict(color=text_color),
|
||||
margin=dict(l=10, r=10, t=10, b=10),
|
||||
)
|
||||
output: Any = OBBject(results=df_to_basemodel(output_df))
|
||||
|
||||
return finalize(output, fig, theme)
|
||||
@@ -0,0 +1,144 @@
|
||||
"""Shared Deribit options chain, as the core model the options charts draw from."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
CHAIN_COLUMNS = {
|
||||
"contract_symbol": "contract_symbol",
|
||||
"expiration": "expiration",
|
||||
"dte": "dte",
|
||||
"strike": "strike",
|
||||
"option_type": "option_type",
|
||||
"contract_size": "contract_size",
|
||||
"open_interest": "open_interest",
|
||||
"volume": "volume",
|
||||
"last": "last_trade_price",
|
||||
"bid": "bid",
|
||||
"ask": "ask",
|
||||
"mark": "mark",
|
||||
"implied_volatility": "implied_volatility",
|
||||
"delta": "delta",
|
||||
"gamma": "gamma",
|
||||
"theta": "theta",
|
||||
"vega": "vega",
|
||||
"rho": "rho",
|
||||
"underlying_price": "underlying_price",
|
||||
}
|
||||
|
||||
|
||||
def chain_expirations(data: Any) -> list[str]:
|
||||
"""Return the expirations that still have contracts in the chain.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : Any
|
||||
The loaded ``OptionsChainsData``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[str]
|
||||
Sorted expirations that have rows, falling back to the model's own list
|
||||
if the frame cannot be built.
|
||||
"""
|
||||
try:
|
||||
frame = data.dataframe
|
||||
except Exception: # noqa: BLE001
|
||||
return list(data.expirations)
|
||||
|
||||
if "expiration" not in frame.columns:
|
||||
return list(data.expirations)
|
||||
|
||||
return sorted({str(value) for value in frame["expiration"]})
|
||||
|
||||
|
||||
def to_chain(frame: Any, symbol: str) -> Any:
|
||||
"""Return a loaded chain frame as the core ``OptionsChainsData``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame : DataFrame
|
||||
The chain, as ``load_chain`` returns it.
|
||||
symbol : str
|
||||
The underlying root, as it appears in the instrument name.
|
||||
|
||||
Returns
|
||||
-------
|
||||
OptionsChainsData
|
||||
One entry per contract, with implied volatility as a decimal.
|
||||
"""
|
||||
from numpy import nan
|
||||
from openbb_core.provider.standard_models.options_chains import OptionsChainsData
|
||||
|
||||
columns = frame.reindex(columns=list(CHAIN_COLUMNS)).rename(columns=CHAIN_COLUMNS)
|
||||
spot = frame["underlying_spot_price"].astype(float)
|
||||
forward = columns["underlying_price"].astype(float)
|
||||
columns["underlying_price"] = forward.where(forward > 0, spot)
|
||||
columns["implied_volatility"] = columns["implied_volatility"].astype(float) / 100
|
||||
columns["underlying_symbol"] = symbol.upper()
|
||||
columns = columns.sort_values(["expiration", "strike", "option_type"])
|
||||
columns = columns.astype(object).replace({nan: None}).reset_index(drop=True)
|
||||
|
||||
return OptionsChainsData.model_validate(columns.to_dict(orient="list"))
|
||||
|
||||
|
||||
async def load_symbol(symbol: str) -> Any:
|
||||
"""Return the chain of one underlying as the core ``OptionsChainsData``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol : str
|
||||
The underlying root, as it appears in the instrument name.
|
||||
|
||||
Returns
|
||||
-------
|
||||
OptionsChainsData
|
||||
The chain the options charts are drawn from.
|
||||
|
||||
Raises
|
||||
------
|
||||
OpenBBError
|
||||
If the exchange lists no options on the underlying.
|
||||
"""
|
||||
from openbb_core.app.model.abstract.error import OpenBBError
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
|
||||
from openbb_deribit.utils.options import chain
|
||||
|
||||
root = symbol.upper()
|
||||
|
||||
try:
|
||||
frame = await chain.load_chain(root)
|
||||
except (OpenBBError, EmptyDataError) as exc:
|
||||
raise OpenBBError(f"No options available for {root}.") from exc
|
||||
|
||||
return to_chain(frame, root)
|
||||
|
||||
|
||||
def get_strikes(data: Any) -> list[dict]:
|
||||
"""Return the strike choices of a chain for its dropdown.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : OptionsChainsData
|
||||
The loaded chain.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[dict]
|
||||
``[{label, value}]`` strike choices, led by the nearest out of the money.
|
||||
"""
|
||||
underlying = data.underlying_price[0] if data.underlying_price else None
|
||||
choices: list[dict] = [{"label": "Nearest OTM", "value": None}]
|
||||
|
||||
for strike in data.strikes:
|
||||
extra = (
|
||||
{"rightOfDescription": f"Underlying: ${underlying}"} if underlying else {}
|
||||
)
|
||||
choices.append(
|
||||
{
|
||||
"label": f"${int(strike) if strike.is_integer() else strike}",
|
||||
"value": strike,
|
||||
"extraInfo": extra,
|
||||
}
|
||||
)
|
||||
|
||||
return choices
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Greeks for a Deribit option, off its forward price and quoted volatility.
|
||||
|
||||
The book summary the analysis suite reads publishes a volatility but no greeks,
|
||||
so they are modelled here. Measured against the greeks Deribit publishes over
|
||||
all 972 BTC contracts, delta agrees to a median of 0.0001, gamma to 0.000002
|
||||
and vega to 0.03.
|
||||
|
||||
Theta follows the exchange's own two rules rather than the bare derivative: it
|
||||
is scaled down inside the last day, where the derivative diverges but the
|
||||
contract cannot decay faster than it expires, and it is floored at the
|
||||
contract's own value, because nothing decays by more than it is worth. Both
|
||||
rules bind on real contracts and together bring the worst disagreement across
|
||||
the chain from 193 to 0.11 per day.
|
||||
"""
|
||||
|
||||
from math import exp, log, pi, sqrt
|
||||
|
||||
SECONDS_PER_YEAR = 365 * 86400
|
||||
|
||||
|
||||
def _pdf(x: float) -> float:
|
||||
"""Return the standard normal density at a point."""
|
||||
return exp(-0.5 * x * x) / sqrt(2 * pi)
|
||||
|
||||
|
||||
def greeks(
|
||||
forward: float, strike: float, years: float, sigma: float, option_type: str
|
||||
) -> dict:
|
||||
"""Return the greeks of one unit of the underlying.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
forward : float
|
||||
The forward price of the underlying at expiration.
|
||||
strike : float
|
||||
The strike of the contract.
|
||||
years : float
|
||||
The time to expiration, in years.
|
||||
sigma : float
|
||||
The implied volatility, as a decimal.
|
||||
option_type : str
|
||||
Either 'call' or 'put'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Delta, gamma, theta, vega, and rho. Every value is None when the
|
||||
contract has expired or carries no volatility to model it with.
|
||||
"""
|
||||
from statistics import NormalDist
|
||||
|
||||
if years <= 0 or sigma <= 0 or forward <= 0 or strike <= 0:
|
||||
return {name: None for name in ("delta", "gamma", "theta", "vega", "rho")}
|
||||
|
||||
normal = NormalDist()
|
||||
deviation = sigma * sqrt(years)
|
||||
d1 = (log(forward / strike) + 0.5 * sigma**2 * years) / deviation
|
||||
d2 = d1 - deviation
|
||||
density = _pdf(d1)
|
||||
call = option_type.startswith("c")
|
||||
|
||||
value = (
|
||||
forward * normal.cdf(d1) - strike * normal.cdf(d2)
|
||||
if call
|
||||
else strike * normal.cdf(-d2) - forward * normal.cdf(-d1)
|
||||
)
|
||||
decay = -forward * density * sigma / (2 * sqrt(years)) / 365
|
||||
|
||||
return {
|
||||
"delta": normal.cdf(d1) if call else -normal.cdf(-d1),
|
||||
"gamma": density / (forward * deviation),
|
||||
"theta": max(decay * min(1.0, years * 365), -value),
|
||||
"vega": forward * density * sqrt(years) / 100,
|
||||
"rho": (
|
||||
strike * years * normal.cdf(d2) / 100
|
||||
if call
|
||||
else -strike * years * normal.cdf(-d2) / 100
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
"""Valuing Deribit option positions.
|
||||
|
||||
The chain quotes every contract in its quote currency per unit of the
|
||||
underlying, so a position is valued the same way whichever settlement style a
|
||||
contract has. What differs is where the profit lands: an inverse contract pays
|
||||
in the coin, so its result is also reported there, which is the number the
|
||||
account actually moves by.
|
||||
"""
|
||||
|
||||
MIN_VOL = 0.0001
|
||||
MAX_VOL = 5.0
|
||||
VOL_STEPS = 100
|
||||
|
||||
|
||||
def intrinsic(price: float, strike: float, option_type: str) -> float:
|
||||
"""Return the exercise value of one unit of the underlying."""
|
||||
return (
|
||||
max(0.0, price - strike)
|
||||
if option_type.startswith("c")
|
||||
else max(0.0, strike - price)
|
||||
)
|
||||
|
||||
|
||||
def option_value(
|
||||
forward: float,
|
||||
strike: float,
|
||||
years: float,
|
||||
sigma: float,
|
||||
discount: float,
|
||||
option_type: str,
|
||||
) -> float:
|
||||
"""Value one unit of the underlying off its forward price.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
forward : float
|
||||
The forward price of the underlying at expiration.
|
||||
strike : float
|
||||
The strike of the contract.
|
||||
years : float
|
||||
The time to expiration, in years.
|
||||
sigma : float
|
||||
The implied volatility, as a decimal.
|
||||
discount : float
|
||||
The discount factor to expiration.
|
||||
option_type : str
|
||||
Either 'call' or 'put'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The value of one unit, in the quote currency.
|
||||
"""
|
||||
from math import log, sqrt
|
||||
from statistics import NormalDist
|
||||
|
||||
if years <= 0 or sigma <= 0 or forward <= 0 or strike <= 0:
|
||||
return discount * intrinsic(forward, strike, option_type)
|
||||
|
||||
normal = NormalDist()
|
||||
deviation = sigma * sqrt(years)
|
||||
d1 = (log(forward / strike) + 0.5 * sigma**2 * years) / deviation
|
||||
d2 = d1 - deviation
|
||||
|
||||
if option_type.startswith("c"):
|
||||
return discount * (forward * normal.cdf(d1) - strike * normal.cdf(d2))
|
||||
|
||||
return discount * (strike * normal.cdf(-d2) - forward * normal.cdf(-d1))
|
||||
|
||||
|
||||
def implied_vol(
|
||||
price: float,
|
||||
forward: float,
|
||||
strike: float,
|
||||
years: float,
|
||||
discount: float,
|
||||
option_type: str,
|
||||
) -> "float | None":
|
||||
"""Return the volatility one quoted price implies.
|
||||
|
||||
A chain publishes a volatility alongside a price, and the two do not always
|
||||
agree. Valuing a position at a volatility its own quote does not imply
|
||||
prices the exit off a different market than the entry, which shows a
|
||||
position as losing before it has moved.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
price : float
|
||||
The quoted price of one unit.
|
||||
forward : float
|
||||
The forward price of the underlying at expiration.
|
||||
strike : float
|
||||
The strike of the contract.
|
||||
years : float
|
||||
The time to expiration, in years.
|
||||
discount : float
|
||||
The discount factor to expiration.
|
||||
option_type : str
|
||||
Either 'call' or 'put'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float or None
|
||||
The volatility the price implies, or None when no volatility does.
|
||||
"""
|
||||
if price <= 0 or years <= 0 or forward <= 0 or strike <= 0:
|
||||
return None
|
||||
|
||||
if price <= discount * intrinsic(forward, strike, option_type):
|
||||
return None
|
||||
|
||||
low, high = MIN_VOL, MAX_VOL
|
||||
|
||||
if option_value(forward, strike, years, high, discount, option_type) < price:
|
||||
return None
|
||||
|
||||
for _ in range(VOL_STEPS):
|
||||
middle = (low + high) / 2
|
||||
|
||||
if option_value(forward, strike, years, middle, discount, option_type) < price:
|
||||
low = middle
|
||||
else:
|
||||
high = middle
|
||||
|
||||
return (low + high) / 2
|
||||
|
||||
|
||||
def leg_value(leg: dict, price: float, elapsed: int) -> float:
|
||||
"""Return what one unit of a leg is worth at a price.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
leg : dict
|
||||
The contract, its volatility, and its days to expiration.
|
||||
price : float
|
||||
The price of the underlying at the valuation date.
|
||||
elapsed : int
|
||||
Days from now to the valuation date.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The exercise value of a leg that has expired by the valuation date,
|
||||
otherwise its modelled value, in the quote currency per unit.
|
||||
"""
|
||||
remaining = int(leg.get("dte") or 0) - elapsed
|
||||
|
||||
if remaining <= 0 or not leg.get("implied_volatility"):
|
||||
return intrinsic(price, float(leg["strike"]), str(leg["option_type"]))
|
||||
|
||||
return option_value(
|
||||
price,
|
||||
float(leg["strike"]),
|
||||
remaining / 365,
|
||||
float(leg["implied_volatility"]),
|
||||
1.0,
|
||||
str(leg["option_type"]),
|
||||
)
|
||||
|
||||
|
||||
def net_cost(legs: "list[dict]") -> float:
|
||||
"""Return what a position costs to open, in the quote currency.
|
||||
|
||||
A negative result is a credit.
|
||||
"""
|
||||
return sum(
|
||||
float(leg["quantity"]) * float(leg["size"]) * float(leg["price"])
|
||||
for leg in legs
|
||||
)
|
||||
|
||||
|
||||
def position_value(legs: "list[dict]", price: float, elapsed: int) -> float:
|
||||
"""Return what a position is worth at a price, in the quote currency."""
|
||||
return sum(
|
||||
float(leg["quantity"]) * float(leg["size"]) * leg_value(leg, price, elapsed)
|
||||
for leg in legs
|
||||
)
|
||||
|
||||
|
||||
def payoff(
|
||||
legs: "list[dict]",
|
||||
prices: "list[float]",
|
||||
elapsed: int = 0,
|
||||
inverse: bool = False,
|
||||
entry_price: "float | None" = None,
|
||||
cost: "float | None" = None,
|
||||
) -> "list[float]":
|
||||
"""Return the profit and loss of a position across a price range, in the quote currency.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
legs : list[dict]
|
||||
Each leg's strike, type, quantity, contract size, price, days to
|
||||
expiration, and implied volatility.
|
||||
prices : list[float]
|
||||
The underlying prices to evaluate.
|
||||
elapsed : int
|
||||
Days from now to the valuation date.
|
||||
inverse : bool
|
||||
Whether the contracts settle in the underlying.
|
||||
entry_price : float or None
|
||||
The price of the underlying when the position was opened, which an
|
||||
inverse premium was paid at. Required when inverse.
|
||||
cost : float or None
|
||||
What the position was opened for, in the quote currency, when it was
|
||||
traded as one combo rather than leg by leg. None prices it off its legs.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[float]
|
||||
The profit and loss at each price, in the quote currency.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If an inverse position is valued without the price it was opened at.
|
||||
"""
|
||||
entry = net_cost(legs) if cost is None else cost
|
||||
|
||||
if not inverse:
|
||||
return [position_value(legs, price, elapsed) - entry for price in prices]
|
||||
|
||||
if not entry_price:
|
||||
raise ValueError(
|
||||
"An inverse position needs the price it was opened at, because its"
|
||||
" premium was paid in the coin at that price."
|
||||
)
|
||||
|
||||
paid = entry / entry_price
|
||||
|
||||
return [position_value(legs, price, elapsed) - paid * price for price in prices]
|
||||
|
||||
|
||||
def breakevens(prices: "list[float]", values: "list[float]") -> "list[float]":
|
||||
"""Return every price where a payoff crosses zero."""
|
||||
crossings: list[float] = []
|
||||
|
||||
for index in range(1, len(values)):
|
||||
if (values[index - 1] < 0) == (values[index] < 0):
|
||||
continue
|
||||
|
||||
low, high = values[index - 1], values[index]
|
||||
weight = abs(low) / (abs(low) + abs(high)) if (low or high) else 0
|
||||
crossings.append(
|
||||
prices[index - 1] + weight * (prices[index] - prices[index - 1])
|
||||
)
|
||||
|
||||
return crossings
|
||||
@@ -0,0 +1,764 @@
|
||||
"""Ranking every Deribit combo structure against a view."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pandas import DataFrame
|
||||
|
||||
PROBE_STEPS = 400
|
||||
PROBE_SPREAD = 0.6
|
||||
SCOUT_STEPS = 300
|
||||
WINDOW_PAD = 0.06
|
||||
MIN_PAD = 0.0025
|
||||
FLAT_SLOPE = 0.02
|
||||
ANCHOR_REACH = 0.35
|
||||
SHORTLIST = 40
|
||||
WING_REACH = 4
|
||||
STRIKE_WINDOW = 0.35
|
||||
STRESS_MOVE = 3.0
|
||||
STRESS_STEPS = 60
|
||||
DIAGONAL_REACH = 3
|
||||
|
||||
NAMES = {
|
||||
("C", 1): "Long Call",
|
||||
("C", -1): "Short Call",
|
||||
("P", 1): "Long Put",
|
||||
("P", -1): "Short Put",
|
||||
("CS", 1): "Bull Call Spread",
|
||||
("CS", -1): "Bear Call Spread",
|
||||
("PS", 1): "Bear Put Spread",
|
||||
("PS", -1): "Bull Put Spread",
|
||||
("STRD", 1): "Long Straddle",
|
||||
("STRD", -1): "Short Straddle",
|
||||
("STRG", 1): "Long Strangle",
|
||||
("STRG", -1): "Short Strangle",
|
||||
("RR", 1): "Bearish Risk Reversal",
|
||||
("RR", -1): "Bullish Risk Reversal",
|
||||
("REV", 1): "Synthetic Long",
|
||||
("REV", -1): "Synthetic Short",
|
||||
("CSR12", 1): "Call Ratio Spread",
|
||||
("CSR12", -1): "Call Backspread",
|
||||
("PSR12", 1): "Put Ratio Spread",
|
||||
("PSR12", -1): "Put Backspread",
|
||||
("CBUT", 1): "Long Call Butterfly",
|
||||
("CBUT", -1): "Short Call Butterfly",
|
||||
("PBUT", 1): "Long Put Butterfly",
|
||||
("PBUT", -1): "Short Put Butterfly",
|
||||
("IBUT", 1): "Iron Butterfly",
|
||||
("IBUT", -1): "Reverse Iron Butterfly",
|
||||
("CCOND", 1): "Long Call Condor",
|
||||
("CCOND", -1): "Short Call Condor",
|
||||
("PCOND", 1): "Long Put Condor",
|
||||
("PCOND", -1): "Short Put Condor",
|
||||
("ICOND", 1): "Iron Condor",
|
||||
("ICOND", -1): "Reverse Iron Condor",
|
||||
("CCAL", 1): "Long Call Calendar",
|
||||
("CCAL", -1): "Short Call Calendar",
|
||||
("PCAL", 1): "Long Put Calendar",
|
||||
("PCAL", -1): "Short Put Calendar",
|
||||
("CDIAG", 1): "Long Call Diagonal",
|
||||
("CDIAG", -1): "Short Call Diagonal",
|
||||
("PDIAG", 1): "Long Put Diagonal",
|
||||
("PDIAG", -1): "Short Put Diagonal",
|
||||
}
|
||||
|
||||
|
||||
def _leg(row: Any, quantity: int) -> dict:
|
||||
"""Describe one contract as a leg of a position, volatility as a decimal."""
|
||||
quoted = row["implied_volatility"]
|
||||
|
||||
return {
|
||||
"symbol": str(row["contract_symbol"]),
|
||||
"expiration": str(row["expiration"]),
|
||||
"strike": float(row["strike"]),
|
||||
"option_type": str(row["option_type"]),
|
||||
"quantity": quantity,
|
||||
"size": float(row["contract_size"] or 1),
|
||||
"price": float(row["entry"] if quantity > 0 else row["exit"]),
|
||||
"dte": int(row["dte"] or 0),
|
||||
"implied_volatility": None if quoted is None else float(quoted) / 100,
|
||||
}
|
||||
|
||||
|
||||
def _describe(legs: "list[dict]") -> str:
|
||||
"""Name the contracts a position trades, and how many of each."""
|
||||
parts: list[str] = []
|
||||
|
||||
for leg in legs:
|
||||
side = "Buy" if leg["quantity"] > 0 else "Sell"
|
||||
count = abs(int(leg["quantity"]))
|
||||
parts.append(
|
||||
f"{side} {leg['symbol']}"
|
||||
if count == 1
|
||||
else f"{side} {count} {leg['symbol']}"
|
||||
)
|
||||
|
||||
return " / ".join(parts)
|
||||
|
||||
|
||||
def _key(legs: "list[dict]") -> frozenset:
|
||||
"""Return what identifies a position: each contract and how much of it."""
|
||||
return frozenset((leg["symbol"], int(leg["quantity"])) for leg in legs)
|
||||
|
||||
|
||||
def _sign(value: float) -> int:
|
||||
"""Return the direction of a quantity."""
|
||||
return 1 if value > 0 else -1
|
||||
|
||||
|
||||
def _two_legs(first: dict, second: dict) -> tuple:
|
||||
"""Classify a two-legged position on one expiration."""
|
||||
kinds = {first["option_type"][0], second["option_type"][0]}
|
||||
|
||||
if len(kinds) == 2:
|
||||
call, put = (
|
||||
(first, second) if first["option_type"][0] == "c" else (second, first)
|
||||
)
|
||||
|
||||
if abs(call["quantity"]) != 1 or abs(put["quantity"]) != 1:
|
||||
return "", 1
|
||||
|
||||
together = call["quantity"] == put["quantity"]
|
||||
|
||||
if put["strike"] == call["strike"]:
|
||||
return (
|
||||
("STRD", _sign(call["quantity"]))
|
||||
if together
|
||||
else (
|
||||
"REV",
|
||||
_sign(call["quantity"]),
|
||||
)
|
||||
)
|
||||
|
||||
if put["strike"] < call["strike"]:
|
||||
return (
|
||||
("STRG", _sign(call["quantity"]))
|
||||
if together
|
||||
else (
|
||||
"RR",
|
||||
_sign(put["quantity"]),
|
||||
)
|
||||
)
|
||||
|
||||
return "", 1
|
||||
|
||||
low, high = sorted((first, second), key=lambda leg: leg["strike"])
|
||||
kind = "C" if low["option_type"][0] == "c" else "P"
|
||||
units = (int(low["quantity"]), int(high["quantity"]))
|
||||
|
||||
if low["strike"] == high["strike"]:
|
||||
return "", 1
|
||||
|
||||
if units in ((1, -1), (-1, 1)):
|
||||
return f"{kind}S", _sign(units[0] if kind == "C" else units[1])
|
||||
|
||||
ratios = {"C": ((1, -2), (-1, 2)), "P": ((-2, 1), (2, -1))}
|
||||
|
||||
if units in ratios[kind]:
|
||||
return f"{kind}SR12", 1 if units == ratios[kind][0] else -1
|
||||
|
||||
return "", 1
|
||||
|
||||
|
||||
def classify(legs: "list[dict]") -> tuple:
|
||||
"""Return the Deribit combo code of a position and the side it is taken on.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
legs : list[dict]
|
||||
The unit legs of the position.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
The code, such as 'CS' or 'ICOND', and 1 when the combo is bought or -1
|
||||
when it is sold. An unrecognised position has an empty code.
|
||||
"""
|
||||
terms = sorted({int(leg["dte"]) for leg in legs})
|
||||
ordered = sorted(legs, key=lambda leg: (leg["strike"], leg["option_type"]))
|
||||
kinds = {leg["option_type"][0] for leg in legs}
|
||||
|
||||
if len(legs) == 1:
|
||||
leg = legs[0]
|
||||
|
||||
return ("C" if leg["option_type"][0] == "c" else "P"), _sign(leg["quantity"])
|
||||
|
||||
if len(terms) == 2 and len(legs) == 2 and len(kinds) == 1:
|
||||
near, far = sorted(legs, key=lambda leg: leg["dte"])
|
||||
kind = "C" if near["option_type"][0] == "c" else "P"
|
||||
|
||||
if near["quantity"] != -far["quantity"] or abs(far["quantity"]) != 1:
|
||||
return "", 1
|
||||
|
||||
shape = "CAL" if near["strike"] == far["strike"] else "DIAG"
|
||||
|
||||
return f"{kind}{shape}", _sign(far["quantity"])
|
||||
|
||||
if len(terms) != 1:
|
||||
return "", 1
|
||||
|
||||
if len(legs) == 2:
|
||||
return _two_legs(*legs)
|
||||
|
||||
units = tuple(int(leg["quantity"]) for leg in ordered)
|
||||
strikes = [leg["strike"] for leg in ordered]
|
||||
|
||||
if len(legs) == 3 and len(kinds) == 1 and len(set(strikes)) == 3:
|
||||
kind = "C" if ordered[0]["option_type"][0] == "c" else "P"
|
||||
even = strikes[1] - strikes[0] == strikes[2] - strikes[1]
|
||||
|
||||
if even and units in ((1, -2, 1), (-1, 2, -1)):
|
||||
return f"{kind}BUT", units[0]
|
||||
|
||||
if len(legs) == 4 and len(kinds) == 1 and len(set(strikes)) == 4:
|
||||
kind = "C" if ordered[0]["option_type"][0] == "c" else "P"
|
||||
|
||||
if units in ((1, -1, -1, 1), (-1, 1, 1, -1)):
|
||||
return f"{kind}COND", units[0]
|
||||
|
||||
if len(legs) == 4 and len(kinds) == 2:
|
||||
puts = sorted(
|
||||
(leg for leg in legs if leg["option_type"][0] == "p"),
|
||||
key=lambda leg: leg["strike"],
|
||||
)
|
||||
calls = sorted(
|
||||
(leg for leg in legs if leg["option_type"][0] == "c"),
|
||||
key=lambda leg: leg["strike"],
|
||||
)
|
||||
shape = (
|
||||
[int(leg["quantity"]) for leg in puts],
|
||||
[int(leg["quantity"]) for leg in calls],
|
||||
)
|
||||
|
||||
if len(puts) == 2 and puts[1]["strike"] <= calls[0]["strike"]:
|
||||
body = "IBUT" if puts[1]["strike"] == calls[0]["strike"] else "ICOND"
|
||||
|
||||
if shape == ([1, -1], [-1, 1]):
|
||||
return body, 1
|
||||
|
||||
if shape == ([-1, 1], [1, -1]):
|
||||
return body, -1
|
||||
|
||||
return "", 1
|
||||
|
||||
|
||||
def _position(pairs: "list[tuple]", book: dict) -> dict:
|
||||
"""Build one position from contracts and quantities, priced off a combo book."""
|
||||
legs = [_leg(row, quantity) for row, quantity in pairs]
|
||||
code, side = classify(legs)
|
||||
listed = book.get(_key(legs))
|
||||
|
||||
return {
|
||||
"strategy": NAMES.get((code, side), "Custom"),
|
||||
"code": code or None,
|
||||
"combo": listed[0] if listed else None,
|
||||
"premium": listed[1] * legs[0]["size"] if listed else None,
|
||||
"legs": legs,
|
||||
}
|
||||
|
||||
|
||||
def _both(pairs: "list[tuple]", book: dict) -> "list[dict]":
|
||||
"""Return a position bought and the same position sold."""
|
||||
flipped = [(row, -quantity) for row, quantity in pairs]
|
||||
|
||||
return [_position(pairs, book), _position(flipped, book)]
|
||||
|
||||
|
||||
def combo_book(books: "list | tuple", spot: float, inverse: bool) -> dict:
|
||||
"""Return each listed combo's price, keyed by the legs that make it up.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
books : list or tuple
|
||||
The listed combos, as ``load_combos`` returns them.
|
||||
spot : float
|
||||
The current price of the underlying, which a coin quote is carried at.
|
||||
inverse : bool
|
||||
Whether the combos are quoted in the coin.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The combo name and what one unit costs in the quote currency, for the
|
||||
combo bought at its offer and for it sold at its bid.
|
||||
"""
|
||||
carry = spot if inverse else 1.0
|
||||
lookup: dict = {}
|
||||
|
||||
for book in books:
|
||||
bought = frozenset(book["legs"])
|
||||
sold = frozenset((name, -amount) for name, amount in book["legs"])
|
||||
|
||||
if book["ask"] is not None:
|
||||
lookup[bought] = (book["name"], float(book["ask"]) * carry)
|
||||
|
||||
if book["bid"] is not None:
|
||||
lookup[sold] = (book["name"], -float(book["bid"]) * carry)
|
||||
|
||||
return lookup
|
||||
|
||||
|
||||
def _spaced(rows: list) -> "list[tuple]":
|
||||
"""Return every pair of strikes, lower first."""
|
||||
return [(low, high) for index, low in enumerate(rows) for high in rows[index + 1 :]]
|
||||
|
||||
|
||||
def _single_expiry(calls: list, puts: list, book: dict) -> "list[dict]":
|
||||
"""Build every structure that trades one expiration."""
|
||||
built: list[dict] = []
|
||||
call_at = {float(row["strike"]): row for row in calls}
|
||||
put_at = {float(row["strike"]): row for row in puts}
|
||||
|
||||
for row in [*calls, *puts]:
|
||||
built += _both([(row, 1)], book)
|
||||
|
||||
for low, high in _spaced(calls):
|
||||
built += _both([(low, 1), (high, -1)], book)
|
||||
built += _both([(low, 1), (high, -2)], book)
|
||||
|
||||
for low, high in _spaced(puts):
|
||||
built += _both([(high, 1), (low, -1)], book)
|
||||
built += _both([(high, 1), (low, -2)], book)
|
||||
|
||||
for strike, call in call_at.items():
|
||||
if strike in put_at:
|
||||
built += _both([(call, 1), (put_at[strike], 1)], book)
|
||||
built += _both([(call, 1), (put_at[strike], -1)], book)
|
||||
|
||||
for put in puts:
|
||||
for call in calls:
|
||||
if float(put["strike"]) < float(call["strike"]):
|
||||
built += _both([(put, 1), (call, 1)], book)
|
||||
built += _both([(put, 1), (call, -1)], book)
|
||||
|
||||
for rows, at in ((calls, call_at), (puts, put_at)):
|
||||
for low, middle in _spaced(rows):
|
||||
width = float(middle["strike"]) - float(low["strike"])
|
||||
high = at.get(float(middle["strike"]) + width)
|
||||
|
||||
if high is not None:
|
||||
built += _both([(low, 1), (middle, -2), (high, 1)], book)
|
||||
|
||||
for index, first in enumerate(rows):
|
||||
for second in rows[index + 1 : index + 1 + WING_REACH]:
|
||||
wing = float(second["strike"]) - float(first["strike"])
|
||||
|
||||
for third in rows:
|
||||
fourth = at.get(float(third["strike"]) + wing)
|
||||
|
||||
if float(third["strike"]) > float(second["strike"]) and fourth:
|
||||
built += _both(
|
||||
[(first, 1), (second, -1), (third, -1), (fourth, 1)],
|
||||
book,
|
||||
)
|
||||
|
||||
for index, low in enumerate(puts):
|
||||
for high in puts[index + 1 : index + 1 + WING_REACH]:
|
||||
wing = float(high["strike"]) - float(low["strike"])
|
||||
|
||||
for call in calls:
|
||||
upper = call_at.get(float(call["strike"]) + wing)
|
||||
|
||||
if float(call["strike"]) >= float(high["strike"]) and upper:
|
||||
built += _both([(low, 1), (high, -1), (call, -1), (upper, 1)], book)
|
||||
|
||||
return built
|
||||
|
||||
|
||||
def _two_expiries(near: list, far: list, book: dict) -> "list[dict]":
|
||||
"""Build every calendar and diagonal between two expirations."""
|
||||
built: list[dict] = []
|
||||
|
||||
for kind in ("call", "put"):
|
||||
later = [row for row in far if row["option_type"] == kind]
|
||||
strikes = [float(row["strike"]) for row in later]
|
||||
|
||||
for row in (row for row in near if row["option_type"] == kind):
|
||||
position = min(
|
||||
range(len(strikes)),
|
||||
key=lambda index: abs(strikes[index] - float(row["strike"])),
|
||||
default=None,
|
||||
)
|
||||
|
||||
if position is None:
|
||||
continue
|
||||
|
||||
reach = later[
|
||||
max(position - DIAGONAL_REACH, 0) : position + DIAGONAL_REACH + 1
|
||||
]
|
||||
|
||||
for other in reach:
|
||||
built += _both([(row, -1), (other, 1)], book)
|
||||
|
||||
return built
|
||||
|
||||
|
||||
def _window_rows(quotes: "DataFrame", spot: float) -> list:
|
||||
"""Return the tradeable contracts near the money, as records."""
|
||||
near = quotes[
|
||||
(quotes["strike"] >= spot * (1 - STRIKE_WINDOW))
|
||||
& (quotes["strike"] <= spot * (1 + STRIKE_WINDOW))
|
||||
]
|
||||
|
||||
return near.sort_values("strike").to_dict("records")
|
||||
|
||||
|
||||
def candidates(
|
||||
frame: "DataFrame",
|
||||
expiration: Any,
|
||||
spot: float,
|
||||
books: "list | tuple" = (),
|
||||
inverse: bool = False,
|
||||
) -> "list[dict]":
|
||||
"""Build every combo structure an expiration can form, both bought and sold.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame : DataFrame
|
||||
The chain.
|
||||
expiration : Any
|
||||
The expiration the position is held to.
|
||||
spot : float
|
||||
The current price of the underlying.
|
||||
books : list or tuple
|
||||
The listed combos, as ``load_combos`` returns them.
|
||||
inverse : bool
|
||||
Whether the contracts settle in the underlying.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[dict]
|
||||
Each position, named and coded, with its legs and any listed combo that
|
||||
trades it as one instrument.
|
||||
"""
|
||||
from openbb_deribit.utils.options.chain import far_expiration, quotes_at
|
||||
|
||||
book = combo_book(books, spot, inverse)
|
||||
near = _window_rows(quotes_at(frame, expiration), spot)
|
||||
calls = [row for row in near if row["option_type"] == "call"]
|
||||
puts = [row for row in near if row["option_type"] == "put"]
|
||||
built = _single_expiry(calls, puts, book)
|
||||
later = far_expiration(frame, expiration)
|
||||
|
||||
if later is not None:
|
||||
built += _two_expiries(near, _window_rows(quotes_at(frame, later), spot), book)
|
||||
|
||||
tradeable = {
|
||||
str(row["contract_symbol"]): row for row in quotes_at(frame).to_dict("records")
|
||||
}
|
||||
|
||||
for listed in books:
|
||||
rows = [
|
||||
tradeable[name] for name, _amount in listed["legs"] if name in tradeable
|
||||
]
|
||||
|
||||
if len(rows) != len(listed["legs"]) or (
|
||||
min(row["expiration"] for row in rows) != expiration
|
||||
):
|
||||
continue
|
||||
|
||||
built += _both(
|
||||
[
|
||||
(row, amount)
|
||||
for row, (_name, amount) in zip(rows, listed["legs"], strict=True)
|
||||
],
|
||||
book,
|
||||
)
|
||||
|
||||
unique: dict = {}
|
||||
|
||||
for item in built:
|
||||
unique.setdefault(_key(item["legs"]), item)
|
||||
|
||||
return list(unique.values())
|
||||
|
||||
|
||||
def rebuild(
|
||||
described: str,
|
||||
quotes: "DataFrame",
|
||||
books: "list | tuple" = (),
|
||||
spot: float = 0.0,
|
||||
inverse: bool = False,
|
||||
) -> dict:
|
||||
"""Return the position a description names, priced at the current quotes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
described : str
|
||||
The legs, as ``_describe`` writes them: 'Buy SYMBOL / Sell 2 SYMBOL'.
|
||||
quotes : DataFrame
|
||||
The tradeable contracts of every expiration.
|
||||
books : list or tuple
|
||||
The listed combos, as ``load_combos`` returns them.
|
||||
spot : float
|
||||
The current price of the underlying.
|
||||
inverse : bool
|
||||
Whether the contracts settle in the underlying.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The position, named and coded, with its legs.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the description names no contract, or one not listed.
|
||||
"""
|
||||
pairs: list[tuple] = []
|
||||
|
||||
for part in described.split("/"):
|
||||
words = part.split()
|
||||
count = words[1] if len(words) == 3 else "1"
|
||||
|
||||
if (
|
||||
len(words) not in (2, 3)
|
||||
or words[0].lower() not in ("buy", "sell")
|
||||
or not count.isdigit()
|
||||
or int(count) < 1
|
||||
):
|
||||
raise ValueError(
|
||||
f"{part.strip()!r} does not name a leg. A position reads"
|
||||
" 'Buy SYMBOL / Sell SYMBOL', with a count before the symbol"
|
||||
" when there is more than one, which is what the optimizer"
|
||||
" prints in its Legs column."
|
||||
)
|
||||
|
||||
symbol = words[-1]
|
||||
listed = quotes[quotes["contract_symbol"] == symbol]
|
||||
|
||||
if listed.empty:
|
||||
raise ValueError(
|
||||
f"{symbol} is not a listed contract, so the position cannot be"
|
||||
" priced. Pick a strategy from the optimizer."
|
||||
)
|
||||
|
||||
quantity = int(count) if words[0].lower() == "buy" else -int(count)
|
||||
pairs.append((listed.to_dict("records")[0], quantity))
|
||||
|
||||
return _position(pairs, combo_book(books, spot, inverse))
|
||||
|
||||
|
||||
def _stress(legs: "list[dict]", spot: float, elapsed: int) -> "list[float]":
|
||||
"""Return the prices a position's worst loss is sought over."""
|
||||
low, high = spot / STRESS_MOVE, spot * STRESS_MOVE
|
||||
points = {low, spot, high, *(leg["strike"] for leg in legs)}
|
||||
|
||||
if any(int(leg["dte"] or 0) > elapsed for leg in legs):
|
||||
points.update(_grid(low, high, STRESS_STEPS))
|
||||
|
||||
return sorted(point for point in points if low <= point <= high)
|
||||
|
||||
|
||||
def score(strategy: dict, spot: float, target: float, inverse: bool) -> "dict | None":
|
||||
"""Size one position to one unit of capital at risk and value it at the target.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
strategy : dict
|
||||
The position, with its legs and any combo premium.
|
||||
spot : float
|
||||
The current price of the underlying.
|
||||
target : float
|
||||
The price the view expects.
|
||||
inverse : bool
|
||||
Whether the contracts settle in the underlying.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict or None
|
||||
The position sized so its worst loss across a threefold move either way
|
||||
is one unit, or None when it has nothing at risk.
|
||||
"""
|
||||
from openbb_deribit.utils.options.legs import net_cost, payoff
|
||||
|
||||
legs = strategy["legs"]
|
||||
premium = strategy.get("premium")
|
||||
unit_cost = net_cost(legs) if premium is None else premium
|
||||
elapsed = max(min(int(leg["dte"] or 0) for leg in legs), 0)
|
||||
points = _stress(legs, spot, elapsed)
|
||||
losses = payoff(legs, points, elapsed, inverse, spot, unit_cost)
|
||||
|
||||
if inverse:
|
||||
losses = [
|
||||
loss / price * spot for loss, price in zip(losses, points, strict=True)
|
||||
]
|
||||
|
||||
risk = -min(losses)
|
||||
|
||||
if risk <= 0:
|
||||
return None
|
||||
|
||||
lots = 1.0 / risk
|
||||
sized = [{**leg, "quantity": leg["quantity"] * lots} for leg in legs]
|
||||
expected = payoff(sized, [target], elapsed, inverse, spot, unit_cost * lots)[0]
|
||||
|
||||
return {
|
||||
**strategy,
|
||||
"sized": sized,
|
||||
"lots": lots,
|
||||
"elapsed": elapsed,
|
||||
"cost": unit_cost * lots,
|
||||
"expected": expected,
|
||||
}
|
||||
|
||||
|
||||
def _grid(low: float, high: float, steps: int) -> "list[float]":
|
||||
"""Return an evenly spaced range of prices."""
|
||||
return [low + (high - low) * step / steps for step in range(steps + 1)]
|
||||
|
||||
|
||||
def _moving(prices: "list[float]", curve: "list[float]") -> tuple:
|
||||
"""Return the price range over which a payoff is still changing."""
|
||||
slopes = [
|
||||
abs((curve[step] - curve[step - 1]) / (prices[step] - prices[step - 1]))
|
||||
for step in range(1, len(prices))
|
||||
]
|
||||
steepest = max(slopes)
|
||||
moving = [
|
||||
step for step, slope in enumerate(slopes) if slope > steepest * FLAT_SLOPE
|
||||
]
|
||||
|
||||
if not moving:
|
||||
return prices[0], prices[-1]
|
||||
|
||||
return prices[moving[0]], prices[moving[-1] + 1]
|
||||
|
||||
|
||||
def _window(
|
||||
legs: "list[dict]",
|
||||
spot: float,
|
||||
target: float,
|
||||
elapsed: int,
|
||||
inverse: bool,
|
||||
cost: "float | None" = None,
|
||||
) -> tuple:
|
||||
"""Return the price range worth drawing for one position.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
legs : list[dict]
|
||||
The sized position.
|
||||
spot : float
|
||||
The current price of the underlying.
|
||||
target : float
|
||||
The price the view expects.
|
||||
elapsed : int
|
||||
Days from now to the valuation date.
|
||||
inverse : bool
|
||||
Whether the contracts settle in the underlying.
|
||||
cost : float or None
|
||||
What the position was opened for, when traded as one combo.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
The lowest and highest price to draw.
|
||||
"""
|
||||
from openbb_deribit.utils.options.legs import breakevens, payoff, position_value
|
||||
|
||||
scout = _grid(spot * (1 - PROBE_SPREAD), spot * (1 + PROBE_SPREAD), SCOUT_STEPS)
|
||||
curve = payoff(legs, scout, elapsed, inverse, spot, cost)
|
||||
low, high = _moving(
|
||||
scout, [position_value(legs, price, elapsed) for price in scout]
|
||||
)
|
||||
anchors = [target, *breakevens(scout, curve)]
|
||||
|
||||
if low <= scout[0]:
|
||||
low = min([anchor for anchor in anchors if anchor < high] or [spot])
|
||||
|
||||
if high >= scout[-1]:
|
||||
high = max([anchor for anchor in anchors if anchor > low] or [spot])
|
||||
|
||||
reach = max(high - low, spot * MIN_PAD) * ANCHOR_REACH
|
||||
nearby = [anchor for anchor in anchors if low - reach <= anchor <= high + reach]
|
||||
low, high = min([low, *nearby]), max([high, *nearby])
|
||||
pad = max((high - low) * WINDOW_PAD, spot * MIN_PAD)
|
||||
|
||||
return max(low - pad, 0.0), high + pad
|
||||
|
||||
|
||||
def draw(
|
||||
scored: dict, spot: float, target: float, budget: float, inverse: bool
|
||||
) -> dict:
|
||||
"""Value one scored position across the price range worth drawing."""
|
||||
from openbb_deribit.utils.options.legs import breakevens, payoff
|
||||
|
||||
legs = [{**leg, "quantity": leg["quantity"] * budget} for leg in scored["sized"]]
|
||||
cost = scored["cost"] * budget
|
||||
elapsed = scored["elapsed"]
|
||||
low, high = _window(legs, spot, target, elapsed, inverse, cost)
|
||||
probe = _grid(low, high, PROBE_STEPS)
|
||||
curve = payoff(legs, probe, elapsed, inverse, spot, cost)
|
||||
|
||||
return {
|
||||
"strategy": scored["strategy"],
|
||||
"code": scored["code"],
|
||||
"combo": scored["combo"],
|
||||
"legs": _describe(scored["legs"]),
|
||||
"contracts": scored["lots"] * budget,
|
||||
"cost": cost,
|
||||
"expected_profit": scored["expected"] * budget,
|
||||
"expected_return": scored["expected"],
|
||||
"max_profit": max(curve),
|
||||
"max_loss": min(curve),
|
||||
"breakevens": breakevens(probe, curve),
|
||||
"position": legs,
|
||||
"elapsed": elapsed,
|
||||
"prices": probe,
|
||||
"payoff": curve,
|
||||
}
|
||||
|
||||
|
||||
def rank(
|
||||
frame: "DataFrame",
|
||||
expiration: Any,
|
||||
spot: float,
|
||||
target: float,
|
||||
budget: float,
|
||||
inverse: bool,
|
||||
limit: int = SHORTLIST,
|
||||
books: "list | tuple" = (),
|
||||
) -> "list[dict]":
|
||||
"""Return the best position of each structure, most profitable first.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame : DataFrame
|
||||
The chain.
|
||||
expiration : Any
|
||||
The expiration the position is held to.
|
||||
spot : float
|
||||
The current price of the underlying.
|
||||
target : float
|
||||
The price the view expects.
|
||||
budget : float
|
||||
The capital to put at risk, in the quote currency.
|
||||
inverse : bool
|
||||
Whether the contracts settle in the underlying.
|
||||
limit : int
|
||||
How many structures to return.
|
||||
books : list or tuple
|
||||
The listed combos, as ``load_combos`` returns them.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[dict]
|
||||
The best-returning position of each structure, sized to the budget,
|
||||
with its payoff.
|
||||
"""
|
||||
scored = [
|
||||
result
|
||||
for strategy in candidates(frame, expiration, spot, books, inverse)
|
||||
if (result := score(strategy, spot, target, inverse))
|
||||
]
|
||||
scored.sort(key=lambda item: item["expected"], reverse=True)
|
||||
best: dict = {}
|
||||
|
||||
for item in scored:
|
||||
best.setdefault(item["strategy"], item)
|
||||
|
||||
return [
|
||||
draw(item, spot, target, budget, inverse)
|
||||
for item in list(best.values())[:limit]
|
||||
]
|
||||
@@ -0,0 +1,162 @@
|
||||
"""Pricing the standard option structures at every expiration.
|
||||
|
||||
Prices are quoted in the chain's quote currency per unit of the underlying, so
|
||||
a cost is comparable across expirations of the same underlying but not across
|
||||
underlyings. The cost as a share of spot is, which is why it is reported too.
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pandas import DataFrame
|
||||
|
||||
|
||||
def _closest(side: "DataFrame", target: float) -> "Any | None":
|
||||
"""Return the contract whose strike is nearest a price."""
|
||||
if side.empty:
|
||||
return None
|
||||
|
||||
return side.iloc[(side["strike"] - target).abs().argmin()]
|
||||
|
||||
|
||||
def _sides(quotes: "DataFrame") -> tuple:
|
||||
"""Split one expiration into its calls and its puts."""
|
||||
return (
|
||||
quotes[quotes["option_type"] == "call"],
|
||||
quotes[quotes["option_type"] == "put"],
|
||||
)
|
||||
|
||||
|
||||
def straddles(frame: "DataFrame", spot: float) -> "list[dict]":
|
||||
"""Price a long straddle at the money, at every expiration."""
|
||||
from openbb_deribit.utils.options.chain import expirations, quotes_at
|
||||
|
||||
priced: list[dict] = []
|
||||
|
||||
for expiration in expirations(frame):
|
||||
quotes = quotes_at(frame, expiration)
|
||||
calls, puts = _sides(quotes)
|
||||
call = _closest(calls, spot)
|
||||
put = _closest(puts, spot)
|
||||
|
||||
if call is None or put is None:
|
||||
continue
|
||||
|
||||
if float(call["strike"]) != float(put["strike"]):
|
||||
continue
|
||||
|
||||
strike = float(call["strike"])
|
||||
cost = float(call["entry"]) + float(put["entry"])
|
||||
|
||||
priced.append(
|
||||
{
|
||||
"expiration": expiration,
|
||||
"dte": int(call["dte"] or 0),
|
||||
"underlying_price": spot,
|
||||
"strike": strike,
|
||||
"call_premium": float(call["entry"]),
|
||||
"put_premium": float(put["entry"]),
|
||||
"cost": cost,
|
||||
"cost_percent": cost / spot if spot else None,
|
||||
"breakeven_lower": strike - cost,
|
||||
"breakeven_upper": strike + cost,
|
||||
"max_loss": -cost,
|
||||
}
|
||||
)
|
||||
|
||||
return priced
|
||||
|
||||
|
||||
def strangles(frame: "DataFrame", spot: float, moneyness: float) -> "list[dict]":
|
||||
"""Price a long strangle the given distance out of the money."""
|
||||
from openbb_deribit.utils.options.chain import expirations, quotes_at
|
||||
|
||||
priced: list[dict] = []
|
||||
|
||||
for expiration in expirations(frame):
|
||||
quotes = quotes_at(frame, expiration)
|
||||
calls, puts = _sides(quotes)
|
||||
call = _closest(calls, spot * (1 + moneyness))
|
||||
put = _closest(puts, spot * (1 - moneyness))
|
||||
|
||||
if call is None or put is None:
|
||||
continue
|
||||
|
||||
upper = float(call["strike"])
|
||||
lower = float(put["strike"])
|
||||
|
||||
if upper <= lower:
|
||||
continue
|
||||
|
||||
cost = float(call["entry"]) + float(put["entry"])
|
||||
|
||||
priced.append(
|
||||
{
|
||||
"expiration": expiration,
|
||||
"dte": int(call["dte"] or 0),
|
||||
"underlying_price": spot,
|
||||
"call_strike": upper,
|
||||
"put_strike": lower,
|
||||
"call_premium": float(call["entry"]),
|
||||
"put_premium": float(put["entry"]),
|
||||
"cost": cost,
|
||||
"cost_percent": cost / spot if spot else None,
|
||||
"breakeven_lower": lower - cost,
|
||||
"breakeven_upper": upper + cost,
|
||||
"max_loss": -cost,
|
||||
}
|
||||
)
|
||||
|
||||
return priced
|
||||
|
||||
|
||||
def verticals(frame: "DataFrame", spot: float, moneyness: float) -> "list[dict]":
|
||||
"""Price a bull call and a bear put spread of the given width."""
|
||||
from openbb_deribit.utils.options.chain import expirations, quotes_at
|
||||
|
||||
priced: list[dict] = []
|
||||
|
||||
for expiration in expirations(frame):
|
||||
quotes = quotes_at(frame, expiration)
|
||||
calls, puts = _sides(quotes)
|
||||
|
||||
for name, side, long_target, short_target in (
|
||||
("Bull Call Spread", calls, spot, spot * (1 + moneyness)),
|
||||
("Bear Put Spread", puts, spot, spot * (1 - moneyness)),
|
||||
):
|
||||
bought = _closest(side, long_target)
|
||||
sold = _closest(side, short_target)
|
||||
|
||||
if bought is None or sold is None:
|
||||
continue
|
||||
|
||||
width = abs(float(sold["strike"]) - float(bought["strike"]))
|
||||
cost = float(bought["entry"]) - float(sold["exit"])
|
||||
|
||||
if width <= 0 or cost <= 0:
|
||||
continue
|
||||
|
||||
breakeven = (
|
||||
float(bought["strike"]) + cost
|
||||
if name.startswith("Bull")
|
||||
else float(bought["strike"]) - cost
|
||||
)
|
||||
priced.append(
|
||||
{
|
||||
"strategy": name,
|
||||
"expiration": expiration,
|
||||
"dte": int(bought["dte"] or 0),
|
||||
"underlying_price": spot,
|
||||
"bought_strike": float(bought["strike"]),
|
||||
"sold_strike": float(sold["strike"]),
|
||||
"bought_premium": float(bought["entry"]),
|
||||
"sold_premium": float(sold["exit"]),
|
||||
"cost": cost,
|
||||
"cost_percent": cost / spot if spot else None,
|
||||
"breakeven": breakeven,
|
||||
"max_profit": width - cost,
|
||||
"max_loss": -cost,
|
||||
}
|
||||
)
|
||||
|
||||
return priced
|
||||
@@ -0,0 +1,119 @@
|
||||
"""Shared Plotly theming for the Deribit options charts."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
CHART_CONFIG = {"scrollZoom": True, "displayModeBar": False, "responsive": True}
|
||||
|
||||
|
||||
class Unplotted:
|
||||
"""Accept every drawing call and draw nothing."""
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""Return a call that does nothing and keeps the chain going."""
|
||||
|
||||
def drawn(*args, **kwargs):
|
||||
return self
|
||||
|
||||
return drawn
|
||||
|
||||
|
||||
def supported_layout(layout: dict) -> dict:
|
||||
"""Drop the layout keys the installed Plotly no longer accepts.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
layout : dict
|
||||
The template layout to filter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Only the keys the installed Plotly's ``Layout`` accepts.
|
||||
"""
|
||||
from plotly.graph_objects import Layout
|
||||
|
||||
valid = getattr(Layout, "_valid_props", None)
|
||||
|
||||
if not valid:
|
||||
return layout
|
||||
|
||||
return {key: value for key, value in layout.items() if key in valid}
|
||||
|
||||
|
||||
def new_figure(theme: str) -> "tuple[Any, str, str]":
|
||||
"""Create a themed figure with its text and background colors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
theme : str
|
||||
Either 'dark' or 'light'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[Any, str, str]
|
||||
The figure, the text color, and the transparent background color. The
|
||||
figure draws nothing when the charting extension is absent.
|
||||
"""
|
||||
from openbb_deribit import CHARTING_INSTALLED
|
||||
|
||||
text_color = "white" if theme == "dark" else "black"
|
||||
background = "rgba(0,0,0,0)" if theme == "dark" else "rgba(255,255,255,0)"
|
||||
|
||||
if not CHARTING_INSTALLED:
|
||||
return Unplotted(), text_color, background
|
||||
|
||||
from openbb_charting.core.chart_style import ChartStyle
|
||||
from openbb_charting.core.openbb_figure import OpenBBFigure
|
||||
|
||||
figure = OpenBBFigure(create_backend=True)
|
||||
figure.update_layout(
|
||||
supported_layout(ChartStyle().plotly_template.get("layout", {}))
|
||||
)
|
||||
|
||||
return figure, text_color, background
|
||||
|
||||
|
||||
def finalize(output: Any, figure: Any, theme: str) -> Any:
|
||||
"""Apply the chart style and attach the chart to the result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
output : Any
|
||||
The OBBject carrying the tabular results.
|
||||
figure : Any
|
||||
The figure to attach.
|
||||
theme : str
|
||||
Either 'dark' or 'light'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
The same result, with a chart attached, or unchanged when there is no
|
||||
charting extension to draw with.
|
||||
"""
|
||||
from openbb_core.app.model.charts.chart import Chart
|
||||
|
||||
from openbb_deribit import CHARTING_INSTALLED
|
||||
|
||||
if not CHARTING_INSTALLED:
|
||||
return output
|
||||
|
||||
text_color = "white" if theme == "dark" else "black"
|
||||
output.charting._charting_settings.chart_style = theme
|
||||
figure = output.charting._set_chart_style(figure)
|
||||
figure.update_layout(
|
||||
hoverlabel=dict(
|
||||
bgcolor="#0E0E0E" if theme == "dark" else "#FFFFFF",
|
||||
bordercolor=text_color,
|
||||
font=dict(color=text_color, size=12),
|
||||
)
|
||||
)
|
||||
figure = figure.show(config=CHART_CONFIG, external=True)
|
||||
|
||||
for trace in figure.data:
|
||||
if getattr(trace, "hoverinfo", None) == "skip":
|
||||
trace.hovertemplate = None
|
||||
|
||||
output.chart = Chart(fig=figure, content=figure.to_plotly_json(), format="plotly")
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Pooled HTTP sessions for the Deribit API.
|
||||
|
||||
One session is held per event loop so connections are kept alive across the
|
||||
many parallel calls a chain or curve needs. The session is closed when its loop
|
||||
shuts down, so nothing is left open once the work that opened it is done.
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp_client_cache.session import CachedSession
|
||||
|
||||
_SESSIONS: dict = {}
|
||||
_CACHED_SESSIONS: dict = {}
|
||||
|
||||
|
||||
def _hook_loop_close(loop) -> None:
|
||||
"""Close this loop's sessions when the loop itself is closed.
|
||||
|
||||
The Python interface runs every command in its own short-lived loop, so the
|
||||
pool is emptied there without waiting for an API shutdown that never comes.
|
||||
Loops that do not accept the hook, such as uvloop's, are left to the
|
||||
shutdown handler.
|
||||
"""
|
||||
if getattr(loop, "_deribit_close_hooked", False):
|
||||
return
|
||||
|
||||
original = loop.close
|
||||
|
||||
def close() -> None:
|
||||
try:
|
||||
if not loop.is_closed() and not loop.is_running():
|
||||
loop.run_until_complete(close_sessions(loop))
|
||||
except Exception: # noqa: BLE001, S110
|
||||
pass
|
||||
|
||||
original()
|
||||
|
||||
try:
|
||||
loop.close = close
|
||||
loop._deribit_close_hooked = True
|
||||
except (AttributeError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
async def get_session() -> "ClientSession":
|
||||
"""Return the uncached session for the running loop.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ClientSession
|
||||
A session shared by every caller on this loop.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from aiohttp import ClientSession
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
session = _SESSIONS.get(loop)
|
||||
|
||||
if session is None or session.closed:
|
||||
session = ClientSession()
|
||||
_SESSIONS[loop] = session
|
||||
_hook_loop_close(loop)
|
||||
|
||||
return session
|
||||
|
||||
|
||||
async def get_cached_session() -> "CachedSession":
|
||||
"""Return the cache-backed session for the running loop.
|
||||
|
||||
Returns
|
||||
-------
|
||||
CachedSession
|
||||
A cache-backed session shared by every caller on this loop.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from aiohttp_client_cache.session import CachedSession
|
||||
|
||||
from openbb_deribit.utils.cache import get_cache_backend, sweep_cache
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
session = _CACHED_SESSIONS.get(loop)
|
||||
|
||||
if session is None or session.closed:
|
||||
backend = get_cache_backend()
|
||||
session = CachedSession(cache=backend)
|
||||
_CACHED_SESSIONS[loop] = session
|
||||
_hook_loop_close(loop)
|
||||
|
||||
with suppress(Exception):
|
||||
await sweep_cache(backend)
|
||||
|
||||
return session
|
||||
|
||||
|
||||
async def close_sessions(loop=None) -> None:
|
||||
"""Close pooled sessions and forget the loops that owned them.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
loop : AbstractEventLoop or None
|
||||
Close only this loop's sessions, or every pooled session when None.
|
||||
"""
|
||||
for pool in (_SESSIONS, _CACHED_SESSIONS):
|
||||
keys = [loop] if loop is not None else list(pool)
|
||||
|
||||
for key in keys:
|
||||
session = pool.pop(key, None)
|
||||
|
||||
if session is not None and not getattr(session, "closed", True):
|
||||
with suppress(Exception):
|
||||
await session.close()
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Ticker subscriptions over the Deribit websocket."""
|
||||
|
||||
WS_URL = "wss://www.deribit.com/ws/api/v2"
|
||||
SUBSCRIBE_TIMEOUT = 15.0
|
||||
|
||||
|
||||
async def _collect(websocket, wanted: set, received: dict, messages: set) -> None:
|
||||
"""Read tickers off one subscription until every symbol has arrived."""
|
||||
import json
|
||||
|
||||
from websockets import ConnectionClosed
|
||||
|
||||
while True:
|
||||
try:
|
||||
frame = await websocket.recv()
|
||||
except ConnectionClosed:
|
||||
return
|
||||
|
||||
payload = json.loads(frame)
|
||||
|
||||
if payload.get("error"):
|
||||
messages.add(f"Deribit returned an error -> {payload['error']}")
|
||||
return
|
||||
|
||||
ticker = (payload.get("params") or {}).get("data") or {}
|
||||
symbol = ticker.get("instrument_name")
|
||||
|
||||
if not symbol or symbol in received:
|
||||
continue
|
||||
|
||||
received[symbol] = ticker
|
||||
|
||||
if wanted <= set(received):
|
||||
return
|
||||
|
||||
|
||||
async def subscribe_tickers(symbols: list[str], messages: set) -> dict[str, dict]:
|
||||
"""Return the ticker of every symbol on one subscription.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbols : list[str]
|
||||
The instrument names to subscribe to.
|
||||
messages : set
|
||||
Collects what went wrong, for the caller to warn about.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict[str, dict]
|
||||
The ticker of each symbol that published one.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from websockets.asyncio.client import connect
|
||||
|
||||
received: dict[str, dict] = {}
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "public/subscribe",
|
||||
"params": {"channels": [f"ticker.{symbol}.100ms" for symbol in symbols]},
|
||||
}
|
||||
|
||||
try:
|
||||
async with connect(WS_URL) as websocket:
|
||||
await websocket.send(json.dumps(request))
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
_collect(websocket, set(symbols), received, messages),
|
||||
timeout=SUBSCRIBE_TIMEOUT,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
missing = len(symbols) - len(received)
|
||||
messages.add(
|
||||
f"Deribit did not publish {missing} of {len(symbols)} contracts"
|
||||
" before the subscription timed out."
|
||||
)
|
||||
except Exception as error: # noqa: BLE001
|
||||
messages.add(
|
||||
f"The Deribit subscription failed -> {error.__class__.__name__}: {error}"
|
||||
)
|
||||
|
||||
return received
|
||||
1974
openbb_platform/providers/deribit/poetry.lock
generated
vendored
1974
openbb_platform/providers/deribit/poetry.lock
generated
vendored
File diff suppressed because it is too large
Load Diff
104
openbb_platform/providers/deribit/pyproject.toml
vendored
104
openbb_platform/providers/deribit/pyproject.toml
vendored
@@ -1,20 +1,96 @@
|
||||
[tool.poetry]
|
||||
[project]
|
||||
name = "openbb-deribit"
|
||||
version = "1.2.1"
|
||||
description = "Deribit is a crypto-native derivatives exchange."
|
||||
authors = ["OpenBB Team <hello@openbb.co>"]
|
||||
license = "AGPL-3.0-only"
|
||||
version = "2.0.0"
|
||||
description = "Deribit provider extension for OpenBB Platform."
|
||||
authors = [{ name = "OpenBB Team", email = "hello@openbb.co" }]
|
||||
license = { text = "AGPL-3.0-only" }
|
||||
readme = "README.md"
|
||||
packages = [{ include = "openbb_deribit" }]
|
||||
requires-python = ">=3.10,<4"
|
||||
dependencies = [
|
||||
"openbb-core[pandas]>=2.0.0",
|
||||
"aiohttp-client-cache>=0.11",
|
||||
"aiosqlite>=0.20",
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10,<4"
|
||||
openbb-core = "^1.6.10"
|
||||
async-lru = "^2.0.4"
|
||||
[project.entry-points."openbb_provider_extension"]
|
||||
deribit = "openbb_deribit:deribit_provider"
|
||||
|
||||
[project.entry-points."openbb_core_extension"]
|
||||
deribit = "openbb_deribit.deribit_router:router"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://openbb.co"
|
||||
Repository = "https://github.com/OpenBB-finance/OpenBB"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"openbb-devtools>=2.0.0",
|
||||
"openbb-charting>=3.0.0",
|
||||
"openbb-platform-api>=2.0.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = ["hatchling>=1.25"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.poetry.plugins."openbb_provider_extension"]
|
||||
deribit = "openbb_deribit:deribit_provider"
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["openbb_deribit"]
|
||||
|
||||
[tool.uv.sources]
|
||||
openbb-core = { path = "../../core", editable = true }
|
||||
openbb-devtools = { path = "../../extensions/devtools", editable = true }
|
||||
openbb-charting = { path = "../../obbject_extensions/charting", editable = true }
|
||||
openbb-platform-api = { path = "../../extensions/platform_api", editable = true }
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py310"
|
||||
fix = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["D", "E", "W", "F", "S", "UP", "I", "PLC", "PLE", "PLR", "PLW", "SIM", "T20"]
|
||||
ignore = [
|
||||
"PLR2004", "PLR0913", "PLR0915", "PLC0415",
|
||||
"E402", "E501",
|
||||
"D202", "D203", "D205", "D206", "D210", "D212", "D213", "D214",
|
||||
"D215", "D300", "D400", "D404", "D405", "D406", "D407", "D408",
|
||||
"D409", "D410", "D411", "D413", "D415", "D416", "D417",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"openbb_deribit/routers/*.py" = ["ARG001", "PLC0415", "PLR0913", "PLR0917"]
|
||||
"openbb_deribit/models/*.py" = ["ARG002", "PLC0415", "PLR0912"]
|
||||
"openbb_deribit/utils/options/*.py" = ["PLC0415", "PLR0911", "PLR0912", "PLR0913", "PLR0914", "PLR0915", "PLR0917", "SLF001"]
|
||||
"tests/**" = ["S101", "S108", "D", "SIM105", "SIM117"]
|
||||
"integration/**" = ["S101", "S108", "D", "SIM105", "SIM117", "PLC0415", "ARG001"]
|
||||
"**/__init__.py" = ["F401"]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
combine-as-imports = true
|
||||
force-wrap-aliases = true
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "numpy"
|
||||
|
||||
[tool.ty]
|
||||
[tool.ty.environment]
|
||||
python-version = "3.10"
|
||||
|
||||
[tool.ty.src]
|
||||
include = ["openbb_deribit"]
|
||||
exclude = ["tests", "integration"]
|
||||
|
||||
[tool.ty.rules]
|
||||
unresolved-import = "warn"
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:", "raise NotImplementedError", "@overload"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
addopts = "-q --strict-markers"
|
||||
markers = [
|
||||
"record_http: VCR-backed HTTP cassette tests",
|
||||
"integration: end-to-end tests that hit live services (excluded from the coverage gate)",
|
||||
"uses_session: builds real pooled sessions instead of patching them",
|
||||
]
|
||||
|
||||
160
openbb_platform/providers/deribit/tests/conftest.py
Normal file
160
openbb_platform/providers/deribit/tests/conftest.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""Shared fixtures for the openbb_deribit test suite."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
DATA_DIR = Path(__file__).parent / "data"
|
||||
|
||||
|
||||
def payload(name: str):
|
||||
"""Return one captured Deribit payload."""
|
||||
return json.loads((DATA_DIR / f"{name}.json").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def load():
|
||||
"""Return the loader for a captured Deribit payload."""
|
||||
return payload
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_network(request, monkeypatch):
|
||||
"""Fail loudly rather than reaching the exchange from a unit test.
|
||||
|
||||
A test marked ``uses_session`` builds its own sessions and is left alone;
|
||||
a session is only a connection pool until something asks it for a URL.
|
||||
"""
|
||||
if request.node.get_closest_marker("uses_session"):
|
||||
return
|
||||
|
||||
async def _forbidden(*args, **kwargs):
|
||||
raise AssertionError(
|
||||
"A unit test opened a session. Patch the client or the session."
|
||||
)
|
||||
|
||||
monkeypatch.setattr("openbb_deribit.utils.session.get_session", _forbidden)
|
||||
monkeypatch.setattr("openbb_deribit.utils.session.get_cached_session", _forbidden)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def responder(monkeypatch):
|
||||
"""Answer client calls from a mapping of method name to payload."""
|
||||
|
||||
def install(mapping: dict):
|
||||
async def _request(method, params=None, use_cache=True):
|
||||
if method not in mapping:
|
||||
raise AssertionError(f"No fixture registered for public/{method}.")
|
||||
|
||||
answer = mapping[method]
|
||||
|
||||
return answer(params) if callable(answer) else answer
|
||||
|
||||
async def _gather(calls, use_cache=True):
|
||||
return [await _request(method, params) for method, params in calls]
|
||||
|
||||
monkeypatch.setattr("openbb_deribit.utils.client.request", _request)
|
||||
monkeypatch.setattr("openbb_deribit.utils.client.gather", _gather)
|
||||
|
||||
return mapping
|
||||
|
||||
return install
|
||||
|
||||
|
||||
def _chain_rows(inverse: bool, spot: float, size: float):
|
||||
"""Build a small, well-formed chain around a spot price."""
|
||||
from datetime import date, timedelta
|
||||
|
||||
today = date.today()
|
||||
rows = []
|
||||
|
||||
for days in (1, 30):
|
||||
expiration = today + timedelta(days=days)
|
||||
|
||||
for step in range(-4, 5):
|
||||
strike = round(spot * (1 + step * 0.02), 4)
|
||||
|
||||
for option_type in ("call", "put"):
|
||||
moneyness = (
|
||||
max(0.0, spot - strike)
|
||||
if option_type == "call"
|
||||
else max(0.0, strike - spot)
|
||||
)
|
||||
extrinsic = spot * 0.01 * (days / 30) ** 0.5
|
||||
mark = moneyness + extrinsic
|
||||
rows.append(
|
||||
{
|
||||
"contract_symbol": (
|
||||
f"TEST-{days}-{strike:g}-{option_type[0].upper()}"
|
||||
),
|
||||
"expiration": expiration,
|
||||
"dte": days,
|
||||
"strike": float(strike),
|
||||
"option_type": option_type,
|
||||
"contract_size": size,
|
||||
"tick_size": 0.0001,
|
||||
"is_inverse": inverse,
|
||||
"mark": mark,
|
||||
"bid": mark * 0.98,
|
||||
"ask": mark * 1.02,
|
||||
"last": mark,
|
||||
"implied_volatility": 50.0 + abs(step) * 2,
|
||||
"open_interest": 10.0 + step,
|
||||
"volume": 5.0,
|
||||
"underlying_price": spot,
|
||||
"underlying_spot_price": spot,
|
||||
"delta": 0.5,
|
||||
"gamma": 0.001,
|
||||
"theta": -1.0,
|
||||
"vega": 10.0,
|
||||
"rho": 1.0,
|
||||
}
|
||||
)
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def chain_frame():
|
||||
"""Return a builder for a synthetic option chain."""
|
||||
from pandas import DataFrame
|
||||
|
||||
def build(inverse: bool = True, spot: float = 100.0, size: float = 1.0):
|
||||
return DataFrame(_chain_rows(inverse, spot, size))
|
||||
|
||||
return build
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def options_chain(chain_frame):
|
||||
"""Return a synthetic chain as the model the options charts draw from."""
|
||||
from openbb_deribit.utils.options.data_handler import to_chain
|
||||
|
||||
return to_chain(chain_frame(), "BTC")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def loaded_chain(monkeypatch, chain_frame):
|
||||
"""Serve a synthetic chain wherever a chain would be loaded."""
|
||||
|
||||
def install(
|
||||
inverse: bool = True,
|
||||
spot: float = 100.0,
|
||||
size: float = 1.0,
|
||||
books: list | None = None,
|
||||
):
|
||||
frame = chain_frame(inverse, spot, size)
|
||||
|
||||
async def _load(symbol, use_cache=True):
|
||||
return frame.copy()
|
||||
|
||||
async def _combos(symbol, use_cache=True):
|
||||
return list(books or [])
|
||||
|
||||
monkeypatch.setattr("openbb_deribit.utils.options.chain.load_chain", _load)
|
||||
monkeypatch.setattr("openbb_deribit.utils.options.chain.load_combos", _combos)
|
||||
|
||||
return frame
|
||||
|
||||
return install
|
||||
26
openbb_platform/providers/deribit/tests/data/announcements.json
vendored
Normal file
26
openbb_platform/providers/deribit/tests/data/announcements.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"id": 1789741775268,
|
||||
"title": "\ud83d\udea8 Coin50 Index, Pre-IPO OpenAI, & EURC Perpetuals. Coming to Deribit",
|
||||
"body": "<p style=\"font-family: Arial; font-size: 16px;\">On Monday September 21, 2026 at 10:00 AM UTC, Deribit will launch the following USDC-settled perpetual markets:<br><br>\u2022<span style=\"font-family: Arial; font-size: 16px;\"> <span><b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">COIN50_USDC</b></span>, offering exposure to the <a href=\"https://www.coinbase.com/en-nl/coin50\" style=\"font-family: Arial; font-size: 16px;\"></a><a href=\"https://www.coinbase.com/coin50\" style=\"font-family: Arial; font-size: 16px;\">Coin50 Index</a> through a perpetual contract<br>\u2022 <span style=\"font-family: Arial; font-size: 16px;\"><b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">OPENAI_USDC</b>,</span> a pre-IPO perpetual linked to OpenAI<br>\u2022 <span><b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">EURC_USDC</b></span></span>, The euro-backed stablecoin issued by Circle</p><p style=\"font-family: Arial; font-size: 16px;\"><b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">Contract details:</b><br>\ud83d\udd39 <span style=\"font-family: Arial; font-size: 16px;\">Settlement</span>: USDC<br>\ud83d\udd39 Full specifications:<span> </span><a href=\"https://support.deribit.com/hc/en-us/articles/38325634622493-RWA-Perpetual\" style=\"font-family: Arial; font-size: 16px;\">Deribit RWA Perpetual contract details</a><br><br>Read more<span> </span><a href=\"https://insights.deribit.com/exchange-updates/deribit-launches-its-biggest-ever-product-expansion/\"><b><u style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">here</u></b></a>.<br><br><b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">Deribit. Always Open.</b><br><br><i style=\"font-family: Arial; font-size: 16px;\">Disclaimer: Deribit FZE is authorised and regulated by the Virtual Assets Regulatory Authority (VARA) in Dubai. Availability of products and services is subject to applicable regulatory requirements and investor classification. Virtual Assets are subject to extreme market volatility, involve a high degree of risk, and can lose value</i></p><div></div>",
|
||||
"important": true,
|
||||
"confirmation": false,
|
||||
"publication_timestamp": 1789739944023
|
||||
},
|
||||
{
|
||||
"id": 1789656759479,
|
||||
"title": "\ud83d\udd14 Update: Spot Routing Completed",
|
||||
"body": "All announced spot routing markets are now live and routed through Coinbase Exchange liquidity on Deribit:<br><br>\u2022 BTC-USDC<br>\u2022 ETH-USDC<br>\u2022 XRP-USDC<br>\u2022 BNB-USDC<br>\u2022 SOL-USDC<br>\u2022 PAXG-USDC<br><br>Benefit from deeper liquidity and improved execution quality. Routed pairs will follow Deribit\u2019s applicable<a href=\"https://support.deribit.com/hc/en-us/articles/25944746248989-Fees\" style=\"font-family: Arial; font-size: 16px;\"> Fee Schedule</a> and CBE market specifications. For pair-level specifications, see the <a href=\"https://support.deribit.com/hc/en-us/articles/31424969480093-Spot-Instruments\" style=\"font-family: Arial; font-size: 16px;\">Spot Instruments</a> page.<br><br><b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">Deribit. Always Open.</b><div></div>",
|
||||
"important": true,
|
||||
"confirmation": false,
|
||||
"publication_timestamp": 1789651389316
|
||||
},
|
||||
{
|
||||
"id": 1789659935624,
|
||||
"title": "\ud83d\udea8 More new perpetual futures are coming to Deribit",
|
||||
"body": "We are pleased to announce that on September 18, 2026, at 10:00 AM UTC, Deribit is launching a new lineup of USDC-settled perpetuals.<br><br>MORPHO \u00b7 AERO \u00b7 BASED1 \u00b7 S \u00b7 EIGEN \u00b7 JTO \u00b7 NIGHT \u00b7 AVNT \u00b7 STRK \u00b7 COMP \u00b7 ALT \u00b7 DYDX \u00b7 OPN \u00b7 TOWNS \u00b7 PYTH \u00b7 RUNE \u00b7 RESOLV \u00b7 ATOM<br><br><b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">Contract details:</b><br>\ud83d\udd39<b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">Settlement</b>: USDC<br>\ud83d\udd39<a href=\"https://support.deribit.com/hc/en-us/articles/31424969384605-Linear-Perpetual\"><b><u style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">Click here</u></b></a> for all contract details and specifications.<br><br>See <a href=\"https://insights.deribit.com/exchange-updates/deribit-launches-its-biggest-ever-product-expansion/\"><b><u style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">here</u></b></a> for more information.<br><br>Please note that these newly listed perpetual contracts are subject <a href=\"https://insights.deribit.com/exchange-updates/deribit-launches-its-biggest-ever-product-expansion/\" style=\"font-family: Arial; font-size: 16px;\">t</a>o our Auto-Deleveraging Mechanism (ADL) and are covered by the Liquidity Support Program (LSP). By trading any of the products listed above, you acknowledge and agree that the terms of ADL and LSP will apply.<br><br>For more information on how ADL works, when it may be triggered, and which Instruments are in scope, please refer to the<a href=\"https://support.deribit.com/hc/en-us/articles/38374258465437-Auto-Deleveraging-ADL\"><u style=\"font-family: Arial; font-size: 16px;\"> ADL article</u></a> available in our Knowledge Base. For more information about the LSP, please refer to the<a href=\"https://support.deribit.com/hc/en-us/articles/38376103377949-Liquidity-Support-Program-LSP\"><u style=\"font-family: Arial; font-size: 16px;\"> LSP article</u></a> available in our Knowledge Base.<br><br><b style=\"font-family: Arial; font-size: 16px; font-weight: bold;\">Deribit. Always Open.</b><br><div></div>",
|
||||
"important": true,
|
||||
"confirmation": false,
|
||||
"publication_timestamp": 1789651341429
|
||||
}
|
||||
]
|
||||
25
openbb_platform/providers/deribit/tests/data/apr_history.json
vendored
Normal file
25
openbb_platform/providers/deribit/tests/data/apr_history.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"day": 20715,
|
||||
"apr": 4.75
|
||||
},
|
||||
{
|
||||
"day": 20714,
|
||||
"apr": 4.75
|
||||
},
|
||||
{
|
||||
"day": 20713,
|
||||
"apr": 4.75
|
||||
},
|
||||
{
|
||||
"day": 20712,
|
||||
"apr": 4.75
|
||||
},
|
||||
{
|
||||
"day": 20711,
|
||||
"apr": 4.75
|
||||
}
|
||||
],
|
||||
"continuation": 20711
|
||||
}
|
||||
343
openbb_platform/providers/deribit/tests/data/block_rfq_trades.json
vendored
Normal file
343
openbb_platform/providers/deribit/tests/data/block_rfq_trades.json
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
{
|
||||
"continuation": "1789886834420:55537",
|
||||
"block_rfqs": [
|
||||
{
|
||||
"id": 55558,
|
||||
"timestamp": 1789918919275,
|
||||
"amount": 84.0,
|
||||
"direction": "buy",
|
||||
"mark_price": -0.001760294,
|
||||
"trades": [
|
||||
{
|
||||
"price": -0.0016,
|
||||
"amount": 84.0,
|
||||
"direction": "buy"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.0026,
|
||||
"direction": "buy",
|
||||
"instrument_name": "ETH-21SEP26-2580-P",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0052,
|
||||
"direction": "buy",
|
||||
"instrument_name": "ETH-21SEP26-2600-P",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0094,
|
||||
"direction": "sell",
|
||||
"instrument_name": "ETH-21SEP26-2620-P",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": null,
|
||||
"index_prices": {
|
||||
"eth_usd": 2602.81
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55557,
|
||||
"timestamp": 1789918897736,
|
||||
"amount": 84.0,
|
||||
"direction": "buy",
|
||||
"mark_price": -0.001321293,
|
||||
"trades": [
|
||||
{
|
||||
"price": -0.001,
|
||||
"amount": 84.0,
|
||||
"direction": "buy"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.0059,
|
||||
"direction": "sell",
|
||||
"instrument_name": "ETH-21SEP26-2600-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0032,
|
||||
"direction": "buy",
|
||||
"instrument_name": "ETH-21SEP26-2620-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0017,
|
||||
"direction": "buy",
|
||||
"instrument_name": "ETH-21SEP26-2640-C",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": null,
|
||||
"index_prices": {
|
||||
"eth_usd": 2602.51
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55555,
|
||||
"timestamp": 1789908858693,
|
||||
"amount": 60.0,
|
||||
"direction": "buy",
|
||||
"mark_price": 0.005304094,
|
||||
"trades": [
|
||||
{
|
||||
"price": 0.0057,
|
||||
"amount": 60.0,
|
||||
"direction": "buy"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.0091,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-9OCT26-86000-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0034,
|
||||
"direction": "sell",
|
||||
"instrument_name": "BTC-9OCT26-90000-C",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": "BTC-CS-9OCT26-86000_90000",
|
||||
"index_prices": {
|
||||
"btc_usd": 80415.41
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55549,
|
||||
"timestamp": 1789896450568,
|
||||
"amount": 50.0,
|
||||
"direction": "buy",
|
||||
"mark_price": 0.005195826,
|
||||
"trades": [
|
||||
{
|
||||
"price": 0.0057,
|
||||
"amount": 50.0,
|
||||
"direction": "buy"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.0093,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-9OCT26-86000-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0036,
|
||||
"direction": "sell",
|
||||
"instrument_name": "BTC-9OCT26-90000-C",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": "BTC-CS-9OCT26-86000_90000",
|
||||
"index_prices": {
|
||||
"btc_usd": 80371.04
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55548,
|
||||
"timestamp": 1789896384838,
|
||||
"amount": 100.0,
|
||||
"direction": "buy",
|
||||
"mark_price": 0.005186311,
|
||||
"trades": [
|
||||
{
|
||||
"price": 0.0056,
|
||||
"amount": 100.0,
|
||||
"direction": "buy"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.009,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-9OCT26-86000-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0034,
|
||||
"direction": "sell",
|
||||
"instrument_name": "BTC-9OCT26-90000-C",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": "BTC-CS-9OCT26-86000_90000",
|
||||
"index_prices": {
|
||||
"btc_usd": 80356.16
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55547,
|
||||
"timestamp": 1789896337667,
|
||||
"amount": 100.0,
|
||||
"direction": "buy",
|
||||
"mark_price": 0.005164996,
|
||||
"trades": [
|
||||
{
|
||||
"price": 0.0056,
|
||||
"amount": 100.0,
|
||||
"direction": "buy"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.0091,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-9OCT26-86000-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0035,
|
||||
"direction": "sell",
|
||||
"instrument_name": "BTC-9OCT26-90000-C",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": "BTC-CS-9OCT26-86000_90000",
|
||||
"index_prices": {
|
||||
"btc_usd": 80340.78
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55540,
|
||||
"timestamp": 1789891560285,
|
||||
"amount": 25.0,
|
||||
"direction": "sell",
|
||||
"mark_price": 0.030700065,
|
||||
"trades": [
|
||||
{
|
||||
"price": 0.0299,
|
||||
"amount": 25.0,
|
||||
"direction": "sell"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.0175,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-25SEP26-80000-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.0124,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-25SEP26-80000-P",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": "BTC-STRD-25SEP26-80000",
|
||||
"index_prices": {
|
||||
"btc_usd": 80399.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55539,
|
||||
"timestamp": 1789890623307,
|
||||
"amount": 12.5,
|
||||
"direction": "buy",
|
||||
"mark_price": -0.056176534,
|
||||
"trades": [
|
||||
{
|
||||
"price": -0.0549,
|
||||
"amount": 12.5,
|
||||
"direction": "buy"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.0016,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-21SEP26-79500-P",
|
||||
"ratio": 2
|
||||
},
|
||||
{
|
||||
"price": 0.0053,
|
||||
"direction": "sell",
|
||||
"instrument_name": "BTC-25SEP26-83000-C",
|
||||
"ratio": 2
|
||||
},
|
||||
{
|
||||
"price": 0.0475,
|
||||
"direction": "sell",
|
||||
"instrument_name": "BTC-25SEP26-84000-P",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": null,
|
||||
"index_prices": {
|
||||
"btc_usd": 80312.58
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55536,
|
||||
"timestamp": 1789886838687,
|
||||
"amount": 20.0,
|
||||
"direction": "sell",
|
||||
"mark_price": 0.01431241,
|
||||
"trades": [
|
||||
{
|
||||
"price": 0.0136,
|
||||
"amount": 20.0,
|
||||
"direction": "sell"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.3167,
|
||||
"direction": "sell",
|
||||
"instrument_name": "BTC-25SEP26-55000-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.3303,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-25DEC26-55000-C",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": "BTC-CCAL-25DEC26_25SEP26-55000",
|
||||
"index_prices": {
|
||||
"btc_usd": 80393.37
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 55537,
|
||||
"timestamp": 1789886834420,
|
||||
"amount": 30.0,
|
||||
"direction": "sell",
|
||||
"mark_price": 0.014293477,
|
||||
"trades": [
|
||||
{
|
||||
"price": 0.0136,
|
||||
"amount": 30.0,
|
||||
"direction": "sell"
|
||||
}
|
||||
],
|
||||
"legs": [
|
||||
{
|
||||
"price": 0.3168,
|
||||
"direction": "sell",
|
||||
"instrument_name": "BTC-25SEP26-55000-C",
|
||||
"ratio": 1
|
||||
},
|
||||
{
|
||||
"price": 0.3304,
|
||||
"direction": "buy",
|
||||
"instrument_name": "BTC-25DEC26-55000-C",
|
||||
"ratio": 1
|
||||
}
|
||||
],
|
||||
"combo_id": "BTC-CCAL-25DEC26_25SEP26-55000",
|
||||
"index_prices": {
|
||||
"btc_usd": 80395.44
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
251
openbb_platform/providers/deribit/tests/data/book_summary_future.json
vendored
Normal file
251
openbb_platform/providers/deribit/tests/data/book_summary_future.json
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
[
|
||||
{
|
||||
"high": 82220.0,
|
||||
"low": 81012.5,
|
||||
"last": 81407.5,
|
||||
"instrument_name": "BTC-27NOV26",
|
||||
"bid_price": 81587.5,
|
||||
"ask_price": 81607.5,
|
||||
"open_interest": 15846850,
|
||||
"mark_price": 81599.51,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.4311,
|
||||
"volume": 17.28326384,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 1407140.0,
|
||||
"volume_notional": 1407140.0,
|
||||
"mid_price": 81597.5
|
||||
},
|
||||
{
|
||||
"high": 81625.0,
|
||||
"low": 80435.0,
|
||||
"last": 80835.0,
|
||||
"instrument_name": "BTC-9OCT26",
|
||||
"bid_price": 81017.5,
|
||||
"ask_price": 81040.0,
|
||||
"open_interest": 1456590,
|
||||
"mark_price": 81025.68,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.3575,
|
||||
"volume": 3.98398075,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 321910.0,
|
||||
"volume_notional": 321910.0,
|
||||
"mid_price": 81028.75
|
||||
},
|
||||
{
|
||||
"high": 81895.0,
|
||||
"low": 80630.0,
|
||||
"last": 81132.5,
|
||||
"instrument_name": "BTC-30OCT26",
|
||||
"bid_price": 81275.0,
|
||||
"ask_price": 81287.5,
|
||||
"open_interest": 48225710,
|
||||
"mark_price": 81280.79,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.4753,
|
||||
"volume": 17.64207412,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 1428710.0,
|
||||
"volume_notional": 1428710.0,
|
||||
"mid_price": 81281.25
|
||||
},
|
||||
{
|
||||
"high": 81445.0,
|
||||
"low": 80145.0,
|
||||
"last": 80930.0,
|
||||
"instrument_name": "BTC-21SEP26",
|
||||
"bid_price": 80810.0,
|
||||
"ask_price": 80825.0,
|
||||
"open_interest": 1047880,
|
||||
"mark_price": 80815.99,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.1357,
|
||||
"volume": 1.60535557,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 129670.0,
|
||||
"volume_notional": 129670.0,
|
||||
"mid_price": 80817.5
|
||||
},
|
||||
{
|
||||
"high": 81542.5,
|
||||
"low": 80362.5,
|
||||
"last": 80775.0,
|
||||
"instrument_name": "BTC-2OCT26",
|
||||
"bid_price": 80950.0,
|
||||
"ask_price": 80965.0,
|
||||
"open_interest": 2466040,
|
||||
"mark_price": 80957.16,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.502,
|
||||
"volume": 6.13587707,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 495940.0,
|
||||
"volume_notional": 495940.0,
|
||||
"mid_price": 80957.5
|
||||
},
|
||||
{
|
||||
"high": 84572.5,
|
||||
"low": 83330.0,
|
||||
"last": 83755.0,
|
||||
"instrument_name": "BTC-25JUN27",
|
||||
"bid_price": 83925.0,
|
||||
"ask_price": 83970.0,
|
||||
"open_interest": 20255740,
|
||||
"mark_price": 83945.35,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.4457,
|
||||
"volume": 12.61012996,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 1055470.0,
|
||||
"volume_notional": 1055470.0,
|
||||
"mid_price": 83947.5
|
||||
},
|
||||
{
|
||||
"high": 81462.5,
|
||||
"low": 80192.5,
|
||||
"last": 80702.5,
|
||||
"instrument_name": "BTC-22SEP26",
|
||||
"bid_price": 80822.5,
|
||||
"ask_price": 80825.0,
|
||||
"open_interest": 950130,
|
||||
"mark_price": 80831.9,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.4165,
|
||||
"volume": 2.61038568,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 211330.0,
|
||||
"volume_notional": 211330.0,
|
||||
"mid_price": 80823.75
|
||||
},
|
||||
{
|
||||
"high": 81497.5,
|
||||
"low": 80149.5,
|
||||
"last": 80832.5,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"bid_price": 80836.5,
|
||||
"ask_price": 80837.0,
|
||||
"open_interest": 787799210,
|
||||
"mark_price": 80831.57,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"current_funding": 0.0,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"funding_8h": 1.583e-05,
|
||||
"price_change": -0.2493,
|
||||
"volume": 3099.91787897,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 250336360.0,
|
||||
"volume_notional": 250336360.0,
|
||||
"mid_price": 80836.75
|
||||
},
|
||||
{
|
||||
"high": 83547.5,
|
||||
"low": 82220.0,
|
||||
"last": 82757.5,
|
||||
"instrument_name": "BTC-26MAR27",
|
||||
"bid_price": 82907.5,
|
||||
"ask_price": 82960.0,
|
||||
"open_interest": 72755930,
|
||||
"mark_price": 82934.37,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.3432,
|
||||
"volume": 8.74194039,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 722750.0,
|
||||
"volume_notional": 722750.0,
|
||||
"mid_price": 82933.75
|
||||
},
|
||||
{
|
||||
"high": 81460.0,
|
||||
"low": 80197.5,
|
||||
"last": 80752.5,
|
||||
"instrument_name": "BTC-23SEP26",
|
||||
"bid_price": 80835.0,
|
||||
"ask_price": 80855.0,
|
||||
"open_interest": 680650,
|
||||
"mark_price": 80845.52,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.3117,
|
||||
"volume": 5.33023474,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 431280.0,
|
||||
"volume_notional": 431280.0,
|
||||
"mid_price": 80845.0
|
||||
},
|
||||
{
|
||||
"high": 82587.5,
|
||||
"low": 81300.0,
|
||||
"last": 81780.0,
|
||||
"instrument_name": "BTC-25DEC26",
|
||||
"bid_price": 81932.5,
|
||||
"ask_price": 81955.0,
|
||||
"open_interest": 275741350,
|
||||
"mark_price": 81945.21,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.4504,
|
||||
"volume": 60.58913468,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 4975480.0,
|
||||
"volume_notional": 4975480.0,
|
||||
"mid_price": 81943.75
|
||||
},
|
||||
{
|
||||
"high": 81525.0,
|
||||
"low": 80197.5,
|
||||
"last": 80775.0,
|
||||
"instrument_name": "BTC-25SEP26",
|
||||
"bid_price": 80872.5,
|
||||
"ask_price": 80882.5,
|
||||
"open_interest": 470803600,
|
||||
"mark_price": 80875.33,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": -0.3208,
|
||||
"volume": 88.95297416,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 7192670.0,
|
||||
"volume_notional": 7192670.0,
|
||||
"mid_price": 80877.5
|
||||
},
|
||||
{
|
||||
"high": 81470.0,
|
||||
"low": 80272.5,
|
||||
"last": 80757.5,
|
||||
"instrument_name": "BTC-24SEP26",
|
||||
"bid_price": 80840.0,
|
||||
"ask_price": 80875.0,
|
||||
"open_interest": 6340,
|
||||
"mark_price": 80858.26,
|
||||
"creation_timestamp": 1789939036004,
|
||||
"estimated_delivery_price": 80815.88,
|
||||
"price_change": 0.4665,
|
||||
"volume": 0.08452116,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 6840.0,
|
||||
"volume_notional": 6840.0,
|
||||
"mid_price": 80857.5
|
||||
}
|
||||
]
|
||||
23
openbb_platform/providers/deribit/tests/data/book_summary_instrument.json
vendored
Normal file
23
openbb_platform/providers/deribit/tests/data/book_summary_instrument.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"high": 81497.5,
|
||||
"low": 80149.5,
|
||||
"last": 80832.5,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"bid_price": 80836.5,
|
||||
"ask_price": 80837.0,
|
||||
"open_interest": 787799210,
|
||||
"mark_price": 80831.54,
|
||||
"creation_timestamp": 1789939037058,
|
||||
"current_funding": 0.0,
|
||||
"estimated_delivery_price": 80815.66,
|
||||
"funding_8h": 1.583e-05,
|
||||
"price_change": -0.2487,
|
||||
"volume": 3100.02328212,
|
||||
"base_currency": "BTC",
|
||||
"quote_currency": "USD",
|
||||
"volume_usd": 250344880.0,
|
||||
"volume_notional": 250344880.0,
|
||||
"mid_price": 80836.75
|
||||
}
|
||||
]
|
||||
17
openbb_platform/providers/deribit/tests/data/combo_details.json
vendored
Normal file
17
openbb_platform/providers/deribit/tests/data/combo_details.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "BTC-STRG-30OCT26-66000_90000",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-30OCT26-66000-P"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-30OCT26-90000-C"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698294,
|
||||
"creation_timestamp": 1789845798000,
|
||||
"state_timestamp": 1789845798201
|
||||
}
|
||||
202
openbb_platform/providers/deribit/tests/data/combo_ids.json
vendored
Normal file
202
openbb_platform/providers/deribit/tests/data/combo_ids.json
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
[
|
||||
"BTC-BOX-25DEC26-30000_130000",
|
||||
"BTC-BOX-25DEC26-35000_100000",
|
||||
"BTC-BOX-25DEC26-35000_70000",
|
||||
"BTC-BOX-25DEC26-40000_80000",
|
||||
"BTC-BOX-25DEC26-45000_110000",
|
||||
"BTC-BOX-25DEC26-50000_100000",
|
||||
"BTC-BOX-25DEC26-55000_105000",
|
||||
"BTC-BOX-25DEC26-56000_78000",
|
||||
"BTC-BOX-25DEC26-56000_80000",
|
||||
"BTC-BOX-25DEC26-60000_100000",
|
||||
"BTC-BOX-25DEC26-60000_80000",
|
||||
"BTC-BOX-25DEC26-60000_85000",
|
||||
"BTC-BOX-25DEC26-60000_86000",
|
||||
"BTC-BOX-25DEC26-60000_90000",
|
||||
"BTC-BOX-25DEC26-65000_85000",
|
||||
"BTC-BOX-25DEC26-70000_72000",
|
||||
"BTC-BOX-25DEC26-80000_100000",
|
||||
"BTC-BOX-25SEP26-160000_170000",
|
||||
"BTC-BOX-25SEP26-30000_120000",
|
||||
"BTC-BOX-25SEP26-40000_110000",
|
||||
"BTC-BOX-25SEP26-40000_90000",
|
||||
"BTC-BOX-25SEP26-50000_100000",
|
||||
"BTC-BOX-25SEP26-50000_130000",
|
||||
"BTC-BOX-25SEP26-50000_90000",
|
||||
"BTC-BOX-25SEP26-56000_75000",
|
||||
"BTC-BOX-25SEP26-60000_105000",
|
||||
"BTC-BOX-25SEP26-60000_110000",
|
||||
"BTC-BOX-25SEP26-60000_115000",
|
||||
"BTC-BOX-25SEP26-60000_120000",
|
||||
"BTC-BOX-25SEP26-60000_130000",
|
||||
"BTC-BOX-25SEP26-60000_140000",
|
||||
"BTC-BOX-25SEP26-60000_68000",
|
||||
"BTC-BOX-25SEP26-60000_95000",
|
||||
"BTC-BOX-25SEP26-70000_105000",
|
||||
"BTC-BOX-25SEP26-70000_80000",
|
||||
"BTC-BOX-25SEP26-80000_100000",
|
||||
"BTC-BOX-25SEP26-80000_120000",
|
||||
"BTC-BOX-25SEP26-80000_130000",
|
||||
"BTC-BOX-25SEP26-85000_135000",
|
||||
"BTC-BOX-25SEP26-85000_90000",
|
||||
"BTC-BOX-25SEP26-90000_100000",
|
||||
"BTC-BOX-26MAR27-50000_150000",
|
||||
"BTC-CBUT-25DEC26-62000_72000_82000",
|
||||
"BTC-CBUT-25DEC26-64000_74000_84000",
|
||||
"BTC-CBUT-25DEC26-64000_75000_84000",
|
||||
"BTC-CBUT-25DEC26-64000_76000_88000",
|
||||
"BTC-CBUT-25DEC26-65000_75000_85000",
|
||||
"BTC-CBUT-25DEC26-68000_70000_74000",
|
||||
"BTC-CBUT-25DEC26-75000_125000_180000",
|
||||
"BTC-CBUT-25JUN27-100000_125000_150000",
|
||||
"BTC-CBUT-25JUN27-72000_74000_78000",
|
||||
"BTC-CBUT-25JUN27-76000_80000_85000",
|
||||
"BTC-CBUT-25SEP26-60000_62000_65000",
|
||||
"BTC-CBUT-25SEP26-62000_75000_86000",
|
||||
"BTC-CBUT-25SEP26-64000_75000_84000",
|
||||
"BTC-CBUT-25SEP26-65000_70000_75000",
|
||||
"BTC-CBUT-25SEP26-65000_75000_85000",
|
||||
"BTC-CBUT-25SEP26-70000_75000_80000",
|
||||
"BTC-CBUT-25SEP26-75000_130000_180000",
|
||||
"BTC-CBUT-26MAR27-60000_80000_100000",
|
||||
"BTC-CBUT-26MAR27-62000_72000_82000",
|
||||
"BTC-CBUT-26MAR27-64000_74000_84000",
|
||||
"BTC-CBUT-26MAR27-80000_90000_100000",
|
||||
"BTC-CBUT-30OCT26-79000_81000_82000",
|
||||
"BTC-CBUT111-25SEP26-125000_135000_145000",
|
||||
"BTC-CBUT111-26MAR27-100000_115000_130000",
|
||||
"BTC-CBUT111-26MAR27-60000_80000_100000",
|
||||
"BTC-CCAL-22SEP26_21SEP26-82000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-140000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-45000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-55000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-66000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-72000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-75000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-80000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-90000",
|
||||
"BTC-CCAL-25DEC26_25SEP26-92000",
|
||||
"BTC-CCAL-25JUN27_25SEP26-115000",
|
||||
"BTC-CCAL-25JUN27_25SEP26-60000",
|
||||
"BTC-CCAL-25JUN27_26MAR27-100000",
|
||||
"BTC-CCAL-25JUN27_26MAR27-70000",
|
||||
"BTC-CCAL-26MAR27_25DEC26-100000",
|
||||
"BTC-CCAL-26MAR27_25DEC26-140000",
|
||||
"BTC-CCAL-26MAR27_25DEC26-70000",
|
||||
"BTC-CCAL-26MAR27_25DEC26-90000",
|
||||
"BTC-CCAL-26MAR27_25DEC26-94000",
|
||||
"BTC-CCAL-26MAR27_25DEC26-95000",
|
||||
"BTC-CCAL-26MAR27_25SEP26-100000",
|
||||
"BTC-CCAL-26MAR27_25SEP26-130000",
|
||||
"BTC-CCAL-26MAR27_25SEP26-70000",
|
||||
"BTC-CCAL-2OCT26_25SEP26-85000",
|
||||
"BTC-CCAL-30OCT26_25SEP26-70000",
|
||||
"BTC-CCAL-30OCT26_25SEP26-78000",
|
||||
"BTC-CCAL-30OCT26_25SEP26-85000",
|
||||
"BTC-CCAL-30OCT26_25SEP26-86000",
|
||||
"BTC-CCAL-30OCT26_25SEP26-90000",
|
||||
"BTC-CCAL-30OCT26_9OCT26-85000",
|
||||
"BTC-CCAL-9OCT26_2OCT26-85000",
|
||||
"BTC-CCOND-25DEC26-68000_72000_75000_78000",
|
||||
"BTC-CCOND-25SEP26-105000_110000_115000_120000",
|
||||
"BTC-CCOND-25SEP26-75000_78000_83000_86000",
|
||||
"BTC-CCOND-25SEP26-77000_79000_84000_110000",
|
||||
"BTC-CCOND-25SEP26-85000_90000_94000_96000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-100000_125000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-100000_86000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-100000_88000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-120000_105000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-125000_95000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-130000_125000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-170000_150000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-45000_66000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-50000_58000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-50000_66000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-70000_85000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-80000_75000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-82000_72000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-82000_76000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-82000_78000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-90000_76000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-90000_78000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-90000_79000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-90000_80000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-90000_81000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-95000_86000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-98000_86000",
|
||||
"BTC-CDIAG-25DEC26_25SEP26-98000_88000",
|
||||
"BTC-CDIAG-25DEC26_27NOV26-84000_81000",
|
||||
"BTC-CDIAG-25DEC26_27NOV26-90000_85000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-105000_96000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-106000_86000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-108000_86000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-74000_78000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-76000_72000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-76000_73000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-78000_73000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-80000_74000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-80000_75000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-82000_75000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-82000_76000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-82000_78000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-84000_76000",
|
||||
"BTC-CDIAG-25DEC26_30OCT26-86000_75000",
|
||||
"BTC-CDIAG-25JUN27_25DEC26-96000_80000",
|
||||
"BTC-CDIAG-25JUN27_25DEC26-98000_82000",
|
||||
"BTC-CDIAG-25JUN27_25SEP26-100000_68000",
|
||||
"BTC-CDIAG-25JUN27_26MAR27-88000_82000",
|
||||
"BTC-CDIAG-25JUN27_30OCT26-110000_81000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-110000_84000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-110000_85000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-110000_88000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-130000_140000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-130000_85000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-130000_86000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-130000_88000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-130000_90000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-130000_92000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-50000_68000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-80000_72000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-80000_75000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-82000_75000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-84000_78000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-85000_75000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-85000_80000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-86000_80000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-88000_80000",
|
||||
"BTC-CDIAG-26MAR27_25DEC26-90000_82000",
|
||||
"BTC-CDIAG-26MAR27_25SEP26-125000_78000",
|
||||
"BTC-CDIAG-26MAR27_25SEP26-130000_76000",
|
||||
"BTC-CDIAG-26MAR27_25SEP26-35000_56000",
|
||||
"BTC-CDIAG-26MAR27_25SEP26-58000_72000",
|
||||
"BTC-CDIAG-26MAR27_30OCT26-58000_75000",
|
||||
"BTC-CDIAG-26MAR27_30OCT26-82000_71000",
|
||||
"BTC-CDIAG-26MAR27_30OCT26-82000_75000",
|
||||
"BTC-CDIAG-26MAR27_30OCT26-88000_75000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-74000_70000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-75000_70000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-76000_72000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-77000_72000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-78000_74000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-80000_74000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-81000_75000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-82000_76000",
|
||||
"BTC-CDIAG-27NOV26_25SEP26-83000_76000",
|
||||
"BTC-CDIAG-27NOV26_30OCT26-100000_88000",
|
||||
"BTC-CDIAG-27NOV26_30OCT26-72000_71000",
|
||||
"BTC-CDIAG-27NOV26_30OCT26-73000_72000",
|
||||
"BTC-CDIAG-27NOV26_30OCT26-74000_72000",
|
||||
"BTC-CDIAG-27NOV26_30OCT26-82000_80000",
|
||||
"BTC-CDIAG-27NOV26_30OCT26-89000_84000",
|
||||
"BTC-CDIAG-27NOV26_30OCT26-93000_96000",
|
||||
"BTC-CDIAG-27NOV26_30OCT26-94000_96000",
|
||||
"BTC-CDIAG-2OCT26_21SEP26-79000_78000",
|
||||
"BTC-CDIAG-2OCT26_25SEP26-74000_75000",
|
||||
"BTC-CDIAG-30OCT26_25SEP26-100000_95000",
|
||||
"BTC-CDIAG-30OCT26_25SEP26-71000_68000",
|
||||
"BTC-CDIAG-30OCT26_25SEP26-73000_70000",
|
||||
"BTC-CDIAG-30OCT26_25SEP26-74000_72000",
|
||||
"BTC-CDIAG-30OCT26_25SEP26-75000_70000",
|
||||
"BTC-CDIAG-30OCT26_25SEP26-75000_72000",
|
||||
"BTC-CDIAG-30OCT26_25SEP26-76000_72000"
|
||||
]
|
||||
342
openbb_platform/providers/deribit/tests/data/combos.json
vendored
Normal file
342
openbb_platform/providers/deribit/tests/data/combos.json
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
[
|
||||
{
|
||||
"id": "BTC-CCAL-22SEP26_21SEP26-82000",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-21SEP26-82000-C"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-22SEP26-82000-C"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698874,
|
||||
"creation_timestamp": 1789903657000,
|
||||
"state_timestamp": 1789903657878
|
||||
},
|
||||
{
|
||||
"id": "BTC-CCAL-25DEC26_25SEP26-55000",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-25SEP26-55000-C"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-25DEC26-55000-C"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698013,
|
||||
"creation_timestamp": 1789804851000,
|
||||
"state_timestamp": 1789886736196
|
||||
},
|
||||
{
|
||||
"id": "BTC-CCAL-30OCT26_9OCT26-85000",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-9OCT26-85000-C"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-30OCT26-85000-C"
|
||||
}
|
||||
],
|
||||
"instrument_id": 699021,
|
||||
"creation_timestamp": 1789935402000,
|
||||
"state_timestamp": 1789935402766
|
||||
},
|
||||
{
|
||||
"id": "BTC-CS-25SEP26-70000_81000",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-25SEP26-70000-C"
|
||||
},
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-25SEP26-81000-C"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698867,
|
||||
"creation_timestamp": 1789899898000,
|
||||
"state_timestamp": 1789899898697
|
||||
},
|
||||
{
|
||||
"id": "BTC-CS-25SEP26-80000_85000",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-25SEP26-80000-C"
|
||||
},
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-25SEP26-85000-C"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698859,
|
||||
"creation_timestamp": 1789892310000,
|
||||
"state_timestamp": 1789892310502
|
||||
},
|
||||
{
|
||||
"id": "BTC-CS-30OCT26-85000_95000",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-30OCT26-85000-C"
|
||||
},
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-30OCT26-95000-C"
|
||||
}
|
||||
],
|
||||
"instrument_id": 686334,
|
||||
"creation_timestamp": 1788236302000,
|
||||
"state_timestamp": 1789918883563
|
||||
},
|
||||
{
|
||||
"id": "BTC-CS-30OCT26-90000_96000",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-30OCT26-90000-C"
|
||||
},
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-30OCT26-96000-C"
|
||||
}
|
||||
],
|
||||
"instrument_id": 681583,
|
||||
"creation_timestamp": 1787577101000,
|
||||
"state_timestamp": 1789930557812
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-21SEP26_PERP",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-PERPETUAL"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-21SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 696453,
|
||||
"creation_timestamp": 1789632397000,
|
||||
"state_timestamp": 1789632397153
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-22SEP26_21SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-21SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-22SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 697148,
|
||||
"creation_timestamp": 1789718801000,
|
||||
"state_timestamp": 1789718801472
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-22SEP26_PERP",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-PERPETUAL"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-22SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 697145,
|
||||
"creation_timestamp": 1789718797000,
|
||||
"state_timestamp": 1789718797046
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-23SEP26_21SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-21SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-23SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698119,
|
||||
"creation_timestamp": 1789805208000,
|
||||
"state_timestamp": 1789805208337
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-23SEP26_22SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-22SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-23SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698120,
|
||||
"creation_timestamp": 1789805208000,
|
||||
"state_timestamp": 1789805208708
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-23SEP26_PERP",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-PERPETUAL"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-23SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698117,
|
||||
"creation_timestamp": 1789805200000,
|
||||
"state_timestamp": 1789805200468
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-24SEP26_21SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-21SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-24SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698832,
|
||||
"creation_timestamp": 1789891599000,
|
||||
"state_timestamp": 1789891599976
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-24SEP26_22SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-22SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-24SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698833,
|
||||
"creation_timestamp": 1789891600000,
|
||||
"state_timestamp": 1789891600277
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-24SEP26_23SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-23SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-24SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698834,
|
||||
"creation_timestamp": 1789891600000,
|
||||
"state_timestamp": 1789891600576
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-24SEP26_PERP",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-PERPETUAL"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-24SEP26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698831,
|
||||
"creation_timestamp": 1789891597000,
|
||||
"state_timestamp": 1789891597281
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-25DEC26_21SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-21SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-25DEC26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 696466,
|
||||
"creation_timestamp": 1789632402000,
|
||||
"state_timestamp": 1789632402806
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-25DEC26_22SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-22SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-25DEC26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 697154,
|
||||
"creation_timestamp": 1789718803000,
|
||||
"state_timestamp": 1789718803121
|
||||
},
|
||||
{
|
||||
"id": "BTC-FS-25DEC26_23SEP26",
|
||||
"state": "active",
|
||||
"legs": [
|
||||
{
|
||||
"amount": -1,
|
||||
"instrument_name": "BTC-23SEP26"
|
||||
},
|
||||
{
|
||||
"amount": 1,
|
||||
"instrument_name": "BTC-25DEC26"
|
||||
}
|
||||
],
|
||||
"instrument_id": 698126,
|
||||
"creation_timestamp": 1789805212000,
|
||||
"state_timestamp": 1789805212770
|
||||
}
|
||||
]
|
||||
3
openbb_platform/providers/deribit/tests/data/contract_size.json
vendored
Normal file
3
openbb_platform/providers/deribit/tests/data/contract_size.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"contract_size": 10.0
|
||||
}
|
||||
162
openbb_platform/providers/deribit/tests/data/currencies.json
vendored
Normal file
162
openbb_platform/providers/deribit/tests/data/currencies.json
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
[
|
||||
{
|
||||
"decimals": 6,
|
||||
"currency": "USDT",
|
||||
"apr": 0.0,
|
||||
"min_withdrawal_fee": 2.0,
|
||||
"withdrawal_fee": 2.0,
|
||||
"coin_type": "USDT",
|
||||
"withdrawal_priorities": [],
|
||||
"coinbase_networks": [
|
||||
{
|
||||
"display_name": "Ethereum",
|
||||
"resource_name": "networks/ethereum-mainnet"
|
||||
}
|
||||
],
|
||||
"network_fee": 3.69791145e-06,
|
||||
"network_currency": "ETH",
|
||||
"min_confirmations": 1,
|
||||
"currency_long": "Tether",
|
||||
"in_cross_collateral_pool": true,
|
||||
"onchain_operations_precision": 6,
|
||||
"currency_uuid": "b26327c1-9a34-51d9-b982-9b29e6012648"
|
||||
},
|
||||
{
|
||||
"decimals": 6,
|
||||
"currency": "USDE",
|
||||
"apr": 4.75,
|
||||
"min_withdrawal_fee": 0.0001,
|
||||
"withdrawal_fee": 0.007617,
|
||||
"coin_type": "USDE",
|
||||
"withdrawal_priorities": [],
|
||||
"network_fee": 2.9071511e-06,
|
||||
"network_currency": "ETH",
|
||||
"min_confirmations": 1,
|
||||
"currency_long": "Ethena USDe",
|
||||
"in_cross_collateral_pool": true,
|
||||
"onchain_operations_precision": 6,
|
||||
"currency_uuid": "e98ecced-8122-45a8-8bce-b842323af19d"
|
||||
},
|
||||
{
|
||||
"decimals": 8,
|
||||
"currency": "USDC",
|
||||
"apr": 3.3999999999999995,
|
||||
"min_withdrawal_fee": 5.0,
|
||||
"withdrawal_fee": 5.0,
|
||||
"coin_type": "USDC",
|
||||
"withdrawal_priorities": [],
|
||||
"coinbase_networks": [
|
||||
{
|
||||
"display_name": "Arbitrum",
|
||||
"resource_name": "networks/arbitrum-mainnet"
|
||||
},
|
||||
{
|
||||
"display_name": "Avalanche C-Chain",
|
||||
"resource_name": "networks/avacchain-mainnet"
|
||||
},
|
||||
{
|
||||
"display_name": "Base",
|
||||
"resource_name": "networks/base-mainnet"
|
||||
},
|
||||
{
|
||||
"display_name": "Ethereum",
|
||||
"resource_name": "networks/ethereum-mainnet"
|
||||
},
|
||||
{
|
||||
"display_name": "Optimism",
|
||||
"resource_name": "networks/optimism-mainnet"
|
||||
},
|
||||
{
|
||||
"display_name": "Polygon",
|
||||
"resource_name": "networks/polygon-mainnet"
|
||||
},
|
||||
{
|
||||
"display_name": "Solana",
|
||||
"resource_name": "networks/solana-mainnet"
|
||||
}
|
||||
],
|
||||
"network_fee": 3.0608777e-06,
|
||||
"network_currency": "ETH",
|
||||
"min_confirmations": 50,
|
||||
"currency_long": "USD Coin",
|
||||
"in_cross_collateral_pool": true,
|
||||
"onchain_operations_precision": 6,
|
||||
"currency_uuid": "2b92315d-eab7-5bef-84fa-089a131333f5"
|
||||
},
|
||||
{
|
||||
"decimals": 6,
|
||||
"currency": "STETH",
|
||||
"apr": 2.251857142857143,
|
||||
"min_withdrawal_fee": 0.0001,
|
||||
"withdrawal_fee": 0.0001,
|
||||
"coin_type": "STETH",
|
||||
"withdrawal_priorities": [],
|
||||
"network_fee": 2.9071511e-06,
|
||||
"network_currency": "ETH",
|
||||
"min_confirmations": 1,
|
||||
"currency_long": "Staked ETH",
|
||||
"in_cross_collateral_pool": true,
|
||||
"onchain_operations_precision": 6,
|
||||
"currency_uuid": "997e0978-a0a0-502d-a51b-9ae33bb54a49"
|
||||
},
|
||||
{
|
||||
"decimals": 6,
|
||||
"currency": "EURR",
|
||||
"apr": 0.0,
|
||||
"min_withdrawal_fee": 0.0001,
|
||||
"withdrawal_fee": 0.006177,
|
||||
"coin_type": "EURR",
|
||||
"withdrawal_priorities": [],
|
||||
"network_fee": 2.71280495e-06,
|
||||
"network_currency": "ETH",
|
||||
"min_confirmations": 1,
|
||||
"currency_long": "StablR Euro",
|
||||
"in_cross_collateral_pool": false,
|
||||
"onchain_operations_precision": 6,
|
||||
"currency_uuid": "20d8642e-36ad-4f92-becf-a68b2b0d05ef"
|
||||
},
|
||||
{
|
||||
"decimals": 6,
|
||||
"currency": "ETH",
|
||||
"apr": 0.0,
|
||||
"min_withdrawal_fee": 0.001,
|
||||
"withdrawal_fee": 0.001,
|
||||
"coin_type": "ETH",
|
||||
"withdrawal_priorities": [],
|
||||
"coinbase_networks": [
|
||||
{
|
||||
"display_name": "Ethereum",
|
||||
"resource_name": "networks/ethereum-mainnet"
|
||||
}
|
||||
],
|
||||
"network_fee": 1.7949723900000002e-06,
|
||||
"network_currency": "ETH",
|
||||
"min_confirmations": 50,
|
||||
"currency_long": "Ethereum",
|
||||
"in_cross_collateral_pool": true,
|
||||
"onchain_operations_precision": 6,
|
||||
"currency_uuid": "d85dce9b-5b73-5c3c-8978-522ce1d1c1b4"
|
||||
},
|
||||
{
|
||||
"decimals": 8,
|
||||
"currency": "BTC",
|
||||
"apr": 0.0,
|
||||
"min_withdrawal_fee": 1e-05,
|
||||
"withdrawal_fee": 1e-05,
|
||||
"coin_type": "BTC",
|
||||
"withdrawal_priorities": [],
|
||||
"coinbase_networks": [
|
||||
{
|
||||
"display_name": "Bitcoin",
|
||||
"resource_name": "networks/bitcoin-mainnet"
|
||||
}
|
||||
],
|
||||
"network_fee": 3.072e-06,
|
||||
"network_currency": "BTC",
|
||||
"min_confirmations": 1,
|
||||
"currency_long": "Bitcoin",
|
||||
"in_cross_collateral_pool": true,
|
||||
"onchain_operations_precision": 8,
|
||||
"currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5"
|
||||
}
|
||||
]
|
||||
25
openbb_platform/providers/deribit/tests/data/delivery_prices.json
vendored
Normal file
25
openbb_platform/providers/deribit/tests/data/delivery_prices.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"date": "2026-09-20",
|
||||
"delivery_price": 80321.21
|
||||
},
|
||||
{
|
||||
"date": "2026-09-19",
|
||||
"delivery_price": 81053.19
|
||||
},
|
||||
{
|
||||
"date": "2026-09-18",
|
||||
"delivery_price": 77720.28
|
||||
},
|
||||
{
|
||||
"date": "2026-09-17",
|
||||
"delivery_price": 76422.2
|
||||
},
|
||||
{
|
||||
"date": "2026-09-16",
|
||||
"delivery_price": 75764.23
|
||||
}
|
||||
],
|
||||
"records_total": 2614
|
||||
}
|
||||
32
openbb_platform/providers/deribit/tests/data/expirations_flat.json
vendored
Normal file
32
openbb_platform/providers/deribit/tests/data/expirations_flat.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"option": [
|
||||
"21SEP26",
|
||||
"22SEP26",
|
||||
"23SEP26",
|
||||
"24SEP26",
|
||||
"25SEP26",
|
||||
"2OCT26",
|
||||
"9OCT26",
|
||||
"30OCT26",
|
||||
"27NOV26",
|
||||
"25DEC26",
|
||||
"26MAR27",
|
||||
"25JUN27"
|
||||
],
|
||||
"future": [
|
||||
"21SEP26",
|
||||
"22SEP26",
|
||||
"23SEP26",
|
||||
"24SEP26",
|
||||
"25SEP26",
|
||||
"30SEP26",
|
||||
"2OCT26",
|
||||
"9OCT26",
|
||||
"30OCT26",
|
||||
"27NOV26",
|
||||
"25DEC26",
|
||||
"26MAR27",
|
||||
"25JUN27",
|
||||
"PERPETUAL"
|
||||
]
|
||||
}
|
||||
280
openbb_platform/providers/deribit/tests/data/expirations_grouped.json
vendored
Normal file
280
openbb_platform/providers/deribit/tests/data/expirations_grouped.json
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
{
|
||||
"steth": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"pepe": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"ltc": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"usyc": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"doge": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"dot": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"usdc": {
|
||||
"option": [
|
||||
"21SEP26",
|
||||
"22SEP26",
|
||||
"23SEP26",
|
||||
"24SEP26",
|
||||
"25SEP26",
|
||||
"2OCT26",
|
||||
"30OCT26",
|
||||
"25DEC26"
|
||||
],
|
||||
"future": [
|
||||
"21SEP26",
|
||||
"22SEP26",
|
||||
"23SEP26",
|
||||
"24SEP26",
|
||||
"25SEP26",
|
||||
"30SEP26",
|
||||
"2OCT26",
|
||||
"30OCT26",
|
||||
"25DEC26",
|
||||
"PERPETUAL"
|
||||
]
|
||||
},
|
||||
"zora": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"xlm": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"ondo": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"bnb": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"jasmy": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"fil": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"eurr": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"uni": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"atom": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"arb": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"bonk": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"link": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"xrp": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"vet": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"avax": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"bch": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"pump": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"usde": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"paxg": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"op": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"sui": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"sol": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"eth": {
|
||||
"option": [
|
||||
"21SEP26",
|
||||
"22SEP26",
|
||||
"23SEP26",
|
||||
"24SEP26",
|
||||
"25SEP26",
|
||||
"2OCT26",
|
||||
"9OCT26",
|
||||
"30OCT26",
|
||||
"27NOV26",
|
||||
"25DEC26",
|
||||
"26MAR27",
|
||||
"25JUN27"
|
||||
],
|
||||
"future": [
|
||||
"21SEP26",
|
||||
"22SEP26",
|
||||
"23SEP26",
|
||||
"24SEP26",
|
||||
"25SEP26",
|
||||
"2OCT26",
|
||||
"9OCT26",
|
||||
"30OCT26",
|
||||
"27NOV26",
|
||||
"25DEC26",
|
||||
"26MAR27",
|
||||
"25JUN27",
|
||||
"PERPETUAL"
|
||||
]
|
||||
},
|
||||
"aero": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"hype": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"pengu": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"tao": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"buidl": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"hbar": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"usdt": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"matic": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"fartcoin": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"cbeth": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"aave": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"algo": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"shib": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"inj": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"ada": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"near": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"btc": {
|
||||
"option": [
|
||||
"21SEP26",
|
||||
"22SEP26",
|
||||
"23SEP26",
|
||||
"24SEP26",
|
||||
"25SEP26",
|
||||
"2OCT26",
|
||||
"9OCT26",
|
||||
"30OCT26",
|
||||
"27NOV26",
|
||||
"25DEC26",
|
||||
"26MAR27",
|
||||
"25JUN27"
|
||||
],
|
||||
"future": [
|
||||
"21SEP26",
|
||||
"22SEP26",
|
||||
"23SEP26",
|
||||
"24SEP26",
|
||||
"25SEP26",
|
||||
"2OCT26",
|
||||
"9OCT26",
|
||||
"30OCT26",
|
||||
"27NOV26",
|
||||
"25DEC26",
|
||||
"26MAR27",
|
||||
"25JUN27",
|
||||
"PERPETUAL"
|
||||
]
|
||||
},
|
||||
"eurc": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"apt": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"icp": {
|
||||
"option": [],
|
||||
"future": []
|
||||
},
|
||||
"crv": {
|
||||
"option": [],
|
||||
"future": []
|
||||
}
|
||||
}
|
||||
306
openbb_platform/providers/deribit/tests/data/funding_chart.json
vendored
Normal file
306
openbb_platform/providers/deribit/tests/data/funding_chart.json
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"timestamp": 1789910220000,
|
||||
"index_price": 80478.71,
|
||||
"interest_8h": 3.41745351036981e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910280000,
|
||||
"index_price": 80461.03,
|
||||
"interest_8h": 5.499236711247856e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910340000,
|
||||
"index_price": 80497.56,
|
||||
"interest_8h": 4.976063870761619e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910400000,
|
||||
"index_price": 80470.1,
|
||||
"interest_8h": 3.656606615373497e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910460000,
|
||||
"index_price": 80491.32,
|
||||
"interest_8h": 6.158639217231364e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910520000,
|
||||
"index_price": 80490.9,
|
||||
"interest_8h": 9.115657794864267e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910580000,
|
||||
"index_price": 80502.97,
|
||||
"interest_8h": 4.7504551695385744e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910640000,
|
||||
"index_price": 80526.04,
|
||||
"interest_8h": 4.257616542440465e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910700000,
|
||||
"index_price": 80516.17,
|
||||
"interest_8h": 1.988367678189268e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910760000,
|
||||
"index_price": 80535.28,
|
||||
"interest_8h": 6.017462160677812e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910820000,
|
||||
"index_price": 80503.64,
|
||||
"interest_8h": 2.5888146175830073e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910880000,
|
||||
"index_price": 80499.48,
|
||||
"interest_8h": 6.896069390852815e-06
|
||||
},
|
||||
{
|
||||
"timestamp": 1789910940000,
|
||||
"index_price": 80480.81,
|
||||
"interest_8h": 1.1157628757265574e-06
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911000000,
|
||||
"index_price": 80480.89,
|
||||
"interest_8h": 1.3640194584817102e-06
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911060000,
|
||||
"index_price": 80477.35,
|
||||
"interest_8h": 5.3513504108716075e-06
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911120000,
|
||||
"index_price": 80475.24,
|
||||
"interest_8h": 6.103616466388657e-06
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911180000,
|
||||
"index_price": 80445.49,
|
||||
"interest_8h": 0.0
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911240000,
|
||||
"index_price": 80402.98,
|
||||
"interest_8h": 0.0
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911300000,
|
||||
"index_price": 80440.61,
|
||||
"interest_8h": 0.0
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911360000,
|
||||
"index_price": 80433.97,
|
||||
"interest_8h": 2.712171859717478e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911420000,
|
||||
"index_price": 80424.68,
|
||||
"interest_8h": 4.150840264485127e-06
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911480000,
|
||||
"index_price": 80428.84,
|
||||
"interest_8h": 4.9270759095953434e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911540000,
|
||||
"index_price": 80453.16,
|
||||
"interest_8h": 2.9665832889596872e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911600000,
|
||||
"index_price": 80444.94,
|
||||
"interest_8h": 1.4404448558281916e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911660000,
|
||||
"index_price": 80442.65,
|
||||
"interest_8h": 0.0
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911720000,
|
||||
"index_price": 80472.27,
|
||||
"interest_8h": 3.568847380583005e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911780000,
|
||||
"index_price": 80505.23,
|
||||
"interest_8h": 6.202941721935202e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911840000,
|
||||
"index_price": 80535.21,
|
||||
"interest_8h": 7.097761960254734e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911900000,
|
||||
"index_price": 80542.03,
|
||||
"interest_8h": 7.2569874387467355e-06
|
||||
},
|
||||
{
|
||||
"timestamp": 1789911960000,
|
||||
"index_price": 80519.08,
|
||||
"interest_8h": 0.0
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912020000,
|
||||
"index_price": 80511.51,
|
||||
"interest_8h": 4.0009496780229505e-06
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912080000,
|
||||
"index_price": 80511.82,
|
||||
"interest_8h": 0.0
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912140000,
|
||||
"index_price": 80521.37,
|
||||
"interest_8h": 2.085977300184626e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912200000,
|
||||
"index_price": 80511.36,
|
||||
"interest_8h": 4.026959673759505e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912260000,
|
||||
"index_price": 80512.72,
|
||||
"interest_8h": 2.001944537465927e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912320000,
|
||||
"index_price": 80554.59,
|
||||
"interest_8h": 8.629368605806955e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912380000,
|
||||
"index_price": 80535.97,
|
||||
"interest_8h": 4.6513470937329694e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912440000,
|
||||
"index_price": 80531.07,
|
||||
"interest_8h": 9.073805302708352e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912500000,
|
||||
"index_price": 80539.43,
|
||||
"interest_8h": 4.823901162454312e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912560000,
|
||||
"index_price": 80526.07,
|
||||
"interest_8h": 4.816430877596233e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912620000,
|
||||
"index_price": 80525.6,
|
||||
"interest_8h": 5.139483592782319e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912680000,
|
||||
"index_price": 80528.09,
|
||||
"interest_8h": 3.2261754873491686e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912740000,
|
||||
"index_price": 80538.33,
|
||||
"interest_8h": 4.600812433032208e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912800000,
|
||||
"index_price": 80553.72,
|
||||
"interest_8h": 7.773160569113602e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912860000,
|
||||
"index_price": 80608.02,
|
||||
"interest_8h": 5.840603701716753e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912920000,
|
||||
"index_price": 80671.08,
|
||||
"interest_8h": 8.444451220928148e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912980000,
|
||||
"index_price": 80750.82,
|
||||
"interest_8h": 0.00010405213222598241
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913040000,
|
||||
"index_price": 80686.09,
|
||||
"interest_8h": 6.28172402455645e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913100000,
|
||||
"index_price": 80633.77,
|
||||
"interest_8h": 5.7067373880630395e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913160000,
|
||||
"index_price": 80650.99,
|
||||
"interest_8h": 9.457109577940204e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913220000,
|
||||
"index_price": 80649.47,
|
||||
"interest_8h": 9.122976877596435e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913280000,
|
||||
"index_price": 80640.07,
|
||||
"interest_8h": 8.730129450528856e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913340000,
|
||||
"index_price": 80627.14,
|
||||
"interest_8h": 0.00012493578465013052
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913400000,
|
||||
"index_price": 80606.03,
|
||||
"interest_8h": 0.0001334700704153708
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913460000,
|
||||
"index_price": 80585.89,
|
||||
"interest_8h": 8.752807098112448e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913520000,
|
||||
"index_price": 80576.14,
|
||||
"interest_8h": 7.093867986230891e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913580000,
|
||||
"index_price": 80555.73,
|
||||
"interest_8h": 8.641306459522893e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913640000,
|
||||
"index_price": 80563.01,
|
||||
"interest_8h": 7.322526181690768e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913700000,
|
||||
"index_price": 80515.67,
|
||||
"interest_8h": 6.770213177138991e-05
|
||||
},
|
||||
{
|
||||
"timestamp": 1789913760000,
|
||||
"index_price": 80510.3,
|
||||
"interest_8h": 8.5236609477181e-05
|
||||
}
|
||||
],
|
||||
"interest_8h": 1.58883550430331e-05,
|
||||
"current_interest": 0.0
|
||||
}
|
||||
170
openbb_platform/providers/deribit/tests/data/funding_rate_history.json
vendored
Normal file
170
openbb_platform/providers/deribit/tests/data/funding_rate_history.json
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1789855200000,
|
||||
"index_price": 81079.79,
|
||||
"interest_8h": 0.00020037536421028146,
|
||||
"interest_1h": 2.517506579644087e-05,
|
||||
"prev_index_price": 81134.69
|
||||
},
|
||||
{
|
||||
"timestamp": 1789858800000,
|
||||
"index_price": 81237.22,
|
||||
"interest_8h": 0.0002110867228833884,
|
||||
"interest_1h": 2.348022400972763e-05,
|
||||
"prev_index_price": 81079.79
|
||||
},
|
||||
{
|
||||
"timestamp": 1789862400000,
|
||||
"index_price": 81241.25,
|
||||
"interest_8h": 0.00020390510396112773,
|
||||
"interest_1h": 1.5820572974398556e-05,
|
||||
"prev_index_price": 81237.22
|
||||
},
|
||||
{
|
||||
"timestamp": 1789866000000,
|
||||
"index_price": 81280.55,
|
||||
"interest_8h": 0.0001866800598579058,
|
||||
"interest_1h": 1.168079644171095e-05,
|
||||
"prev_index_price": 81241.25
|
||||
},
|
||||
{
|
||||
"timestamp": 1789869600000,
|
||||
"index_price": 81078.04,
|
||||
"interest_8h": 0.00017209580603703834,
|
||||
"interest_1h": 1.6878746070219188e-05,
|
||||
"prev_index_price": 81280.55
|
||||
},
|
||||
{
|
||||
"timestamp": 1789873200000,
|
||||
"index_price": 80301.73,
|
||||
"interest_8h": 0.00016377878201952157,
|
||||
"interest_1h": 1.618723881084165e-05,
|
||||
"prev_index_price": 81078.04
|
||||
},
|
||||
{
|
||||
"timestamp": 1789876800000,
|
||||
"index_price": 80466.11,
|
||||
"interest_8h": 0.00015410304928169024,
|
||||
"interest_1h": 1.6038603796340042e-05,
|
||||
"prev_index_price": 80301.73
|
||||
},
|
||||
{
|
||||
"timestamp": 1789880400000,
|
||||
"index_price": 80406.86,
|
||||
"interest_8h": 0.00013640125860698012,
|
||||
"interest_1h": 1.0921269949361024e-05,
|
||||
"prev_index_price": 80466.11
|
||||
},
|
||||
{
|
||||
"timestamp": 1789884000000,
|
||||
"index_price": 80467.53,
|
||||
"interest_8h": 0.00011438115897154783,
|
||||
"interest_1h": 3.2034180506026057e-06,
|
||||
"prev_index_price": 80406.86
|
||||
},
|
||||
{
|
||||
"timestamp": 1789887600000,
|
||||
"index_price": 80284.73,
|
||||
"interest_8h": 9.32111909045898e-05,
|
||||
"interest_1h": 2.116139038499294e-06,
|
||||
"prev_index_price": 80467.53
|
||||
},
|
||||
{
|
||||
"timestamp": 1789891200000,
|
||||
"index_price": 80292.92,
|
||||
"interest_8h": 8.225487389838831e-05,
|
||||
"interest_1h": 4.800598032729875e-06,
|
||||
"prev_index_price": 80284.73
|
||||
},
|
||||
{
|
||||
"timestamp": 1789894800000,
|
||||
"index_price": 80236.52,
|
||||
"interest_8h": 7.930890611540408e-05,
|
||||
"interest_1h": 8.592850151835521e-06,
|
||||
"prev_index_price": 80292.92
|
||||
},
|
||||
{
|
||||
"timestamp": 1789898400000,
|
||||
"index_price": 80439.57,
|
||||
"interest_8h": 7.711337096560992e-05,
|
||||
"interest_1h": 1.4684741829057852e-05,
|
||||
"prev_index_price": 80236.52
|
||||
},
|
||||
{
|
||||
"timestamp": 1789902000000,
|
||||
"index_price": 80290.85,
|
||||
"interest_8h": 7.764449418518562e-05,
|
||||
"interest_1h": 1.731978357173989e-05,
|
||||
"prev_index_price": 80439.57
|
||||
},
|
||||
{
|
||||
"timestamp": 1789905600000,
|
||||
"index_price": 80455.12,
|
||||
"interest_8h": 7.064469331448168e-05,
|
||||
"interest_1h": 8.904895798545535e-06,
|
||||
"prev_index_price": 80290.85
|
||||
},
|
||||
{
|
||||
"timestamp": 1789909200000,
|
||||
"index_price": 80462.51,
|
||||
"interest_8h": 6.356288774768103e-05,
|
||||
"interest_1h": 3.8857716514232716e-06,
|
||||
"prev_index_price": 80455.12
|
||||
},
|
||||
{
|
||||
"timestamp": 1789912800000,
|
||||
"index_price": 80538.42,
|
||||
"interest_8h": 6.371065529221617e-05,
|
||||
"interest_1h": 3.3054358812664526e-06,
|
||||
"prev_index_price": 80462.51
|
||||
},
|
||||
{
|
||||
"timestamp": 1789916400000,
|
||||
"index_price": 80652.54,
|
||||
"interest_8h": 6.88962968161614e-05,
|
||||
"interest_1h": 7.465221177076952e-06,
|
||||
"prev_index_price": 80538.42
|
||||
},
|
||||
{
|
||||
"timestamp": 1789920000000,
|
||||
"index_price": 80886.2,
|
||||
"interest_8h": 6.66477755274478e-05,
|
||||
"interest_1h": 2.559066476957397e-06,
|
||||
"prev_index_price": 80652.54
|
||||
},
|
||||
{
|
||||
"timestamp": 1789923600000,
|
||||
"index_price": 81314.35,
|
||||
"interest_8h": 6.029910188939407e-05,
|
||||
"interest_1h": 2.303457349941978e-06,
|
||||
"prev_index_price": 80886.2
|
||||
},
|
||||
{
|
||||
"timestamp": 1789927200000,
|
||||
"index_price": 81133.09,
|
||||
"interest_8h": 4.613047154922585e-05,
|
||||
"interest_1h": 4.0705398072082257e-07,
|
||||
"prev_index_price": 81314.35
|
||||
},
|
||||
{
|
||||
"timestamp": 1789930800000,
|
||||
"index_price": 81022.44,
|
||||
"interest_8h": 2.895317790831924e-05,
|
||||
"interest_1h": 1.9579548790115389e-07,
|
||||
"prev_index_price": 81133.09
|
||||
},
|
||||
{
|
||||
"timestamp": 1789934400000,
|
||||
"index_price": 81163.58,
|
||||
"interest_8h": 2.0166039693856925e-05,
|
||||
"interest_1h": 7.728119084922554e-08,
|
||||
"prev_index_price": 81022.44
|
||||
},
|
||||
{
|
||||
"timestamp": 1789938000000,
|
||||
"index_price": 81080.82,
|
||||
"interest_8h": 1.6317565552435866e-05,
|
||||
"interest_1h": 3.6114071476582626e-08,
|
||||
"prev_index_price": 81163.58
|
||||
}
|
||||
]
|
||||
242
openbb_platform/providers/deribit/tests/data/historical_volatility.json
vendored
Normal file
242
openbb_platform/providers/deribit/tests/data/historical_volatility.json
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
[
|
||||
[
|
||||
1788566400000,
|
||||
45.882391305049836
|
||||
],
|
||||
[
|
||||
1788566400000,
|
||||
45.882391305049836
|
||||
],
|
||||
[
|
||||
1788570000000,
|
||||
45.82361062125136
|
||||
],
|
||||
[
|
||||
1788573600000,
|
||||
45.59941784506282
|
||||
],
|
||||
[
|
||||
1788577200000,
|
||||
44.612462181646315
|
||||
],
|
||||
[
|
||||
1788580800000,
|
||||
44.374337703551326
|
||||
],
|
||||
[
|
||||
1788584400000,
|
||||
44.37190609736692
|
||||
],
|
||||
[
|
||||
1788588000000,
|
||||
44.24576674351413
|
||||
],
|
||||
[
|
||||
1788591600000,
|
||||
44.15370915488827
|
||||
],
|
||||
[
|
||||
1788595200000,
|
||||
44.14077093901336
|
||||
],
|
||||
[
|
||||
1788598800000,
|
||||
43.922274146490885
|
||||
],
|
||||
[
|
||||
1788602400000,
|
||||
39.712802749423844
|
||||
],
|
||||
[
|
||||
1788606000000,
|
||||
38.79826012590313
|
||||
],
|
||||
[
|
||||
1788609600000,
|
||||
38.79452396048651
|
||||
],
|
||||
[
|
||||
1788613200000,
|
||||
38.16053217245645
|
||||
],
|
||||
[
|
||||
1788616800000,
|
||||
38.0423846788947
|
||||
],
|
||||
[
|
||||
1788620400000,
|
||||
38.04257037517399
|
||||
],
|
||||
[
|
||||
1788624000000,
|
||||
38.04116200468372
|
||||
],
|
||||
[
|
||||
1788627600000,
|
||||
38.02487922966197
|
||||
],
|
||||
[
|
||||
1788631200000,
|
||||
38.01249826894891
|
||||
],
|
||||
[
|
||||
1788634800000,
|
||||
38.008479376183935
|
||||
],
|
||||
[
|
||||
1788638400000,
|
||||
37.97117984102993
|
||||
],
|
||||
[
|
||||
1788642000000,
|
||||
37.996183996065994
|
||||
],
|
||||
[
|
||||
1788645600000,
|
||||
37.862274629559266
|
||||
],
|
||||
[
|
||||
1788649200000,
|
||||
37.34360231641064
|
||||
],
|
||||
[
|
||||
1788652800000,
|
||||
37.34923604440181
|
||||
],
|
||||
[
|
||||
1788656400000,
|
||||
37.34361921064971
|
||||
],
|
||||
[
|
||||
1788660000000,
|
||||
37.26147973855525
|
||||
],
|
||||
[
|
||||
1788663600000,
|
||||
37.260474276405915
|
||||
],
|
||||
[
|
||||
1788667200000,
|
||||
37.05677556381301
|
||||
],
|
||||
[
|
||||
1788670800000,
|
||||
37.06264148278253
|
||||
],
|
||||
[
|
||||
1788674400000,
|
||||
37.07432061271084
|
||||
],
|
||||
[
|
||||
1788678000000,
|
||||
36.42356181385526
|
||||
],
|
||||
[
|
||||
1788681600000,
|
||||
36.440761159345996
|
||||
],
|
||||
[
|
||||
1788685200000,
|
||||
36.42054104287318
|
||||
],
|
||||
[
|
||||
1788688800000,
|
||||
36.41643755891511
|
||||
],
|
||||
[
|
||||
1788692400000,
|
||||
36.40554285175305
|
||||
],
|
||||
[
|
||||
1788696000000,
|
||||
36.40858448251462
|
||||
],
|
||||
[
|
||||
1788699600000,
|
||||
36.39957652159652
|
||||
],
|
||||
[
|
||||
1788703200000,
|
||||
36.38204816584679
|
||||
],
|
||||
[
|
||||
1788706800000,
|
||||
36.38599922715135
|
||||
],
|
||||
[
|
||||
1788710400000,
|
||||
36.38484019515988
|
||||
],
|
||||
[
|
||||
1788714000000,
|
||||
36.399284438365605
|
||||
],
|
||||
[
|
||||
1788717600000,
|
||||
36.34531043724365
|
||||
],
|
||||
[
|
||||
1788721200000,
|
||||
36.34701202847672
|
||||
],
|
||||
[
|
||||
1788724800000,
|
||||
36.368927169042905
|
||||
],
|
||||
[
|
||||
1788728400000,
|
||||
36.361783875986184
|
||||
],
|
||||
[
|
||||
1788732000000,
|
||||
36.358728205612444
|
||||
],
|
||||
[
|
||||
1788735600000,
|
||||
36.23666065332862
|
||||
],
|
||||
[
|
||||
1788739200000,
|
||||
36.23120744020156
|
||||
],
|
||||
[
|
||||
1788742800000,
|
||||
36.273090013075176
|
||||
],
|
||||
[
|
||||
1788746400000,
|
||||
36.264687293335214
|
||||
],
|
||||
[
|
||||
1788750000000,
|
||||
36.28173268858742
|
||||
],
|
||||
[
|
||||
1788753600000,
|
||||
36.27665195999784
|
||||
],
|
||||
[
|
||||
1788757200000,
|
||||
36.30368643385408
|
||||
],
|
||||
[
|
||||
1788760800000,
|
||||
36.25447357121739
|
||||
],
|
||||
[
|
||||
1788764400000,
|
||||
36.08275664288014
|
||||
],
|
||||
[
|
||||
1788768000000,
|
||||
36.087195163195176
|
||||
],
|
||||
[
|
||||
1788771600000,
|
||||
36.116470746575025
|
||||
],
|
||||
[
|
||||
1788775200000,
|
||||
35.97731607070047
|
||||
]
|
||||
]
|
||||
242
openbb_platform/providers/deribit/tests/data/index_chart_data.json
vendored
Normal file
242
openbb_platform/providers/deribit/tests/data/index_chart_data.json
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
[
|
||||
[
|
||||
1789935450000,
|
||||
81110.95
|
||||
],
|
||||
[
|
||||
1789935456000,
|
||||
81110.428333
|
||||
],
|
||||
[
|
||||
1789935462000,
|
||||
81113.761667
|
||||
],
|
||||
[
|
||||
1789935468000,
|
||||
81112.3
|
||||
],
|
||||
[
|
||||
1789935474000,
|
||||
81110.728333
|
||||
],
|
||||
[
|
||||
1789935480000,
|
||||
81110.993333
|
||||
],
|
||||
[
|
||||
1789935486000,
|
||||
81111.161667
|
||||
],
|
||||
[
|
||||
1789935492000,
|
||||
81110.811667
|
||||
],
|
||||
[
|
||||
1789935498000,
|
||||
81110.6
|
||||
],
|
||||
[
|
||||
1789935504000,
|
||||
81117.588333
|
||||
],
|
||||
[
|
||||
1789935510000,
|
||||
81118.403333
|
||||
],
|
||||
[
|
||||
1789935516000,
|
||||
81118.701667
|
||||
],
|
||||
[
|
||||
1789935522000,
|
||||
81117.903333
|
||||
],
|
||||
[
|
||||
1789935528000,
|
||||
81117.62
|
||||
],
|
||||
[
|
||||
1789935534000,
|
||||
81117.58
|
||||
],
|
||||
[
|
||||
1789935540000,
|
||||
81111.988333
|
||||
],
|
||||
[
|
||||
1789935546000,
|
||||
81112.675
|
||||
],
|
||||
[
|
||||
1789935552000,
|
||||
81131.768333
|
||||
],
|
||||
[
|
||||
1789935558000,
|
||||
81131.501667
|
||||
],
|
||||
[
|
||||
1789935564000,
|
||||
81129.551667
|
||||
],
|
||||
[
|
||||
1789935570000,
|
||||
81118.418333
|
||||
],
|
||||
[
|
||||
1789935576000,
|
||||
81120.615
|
||||
],
|
||||
[
|
||||
1789935582000,
|
||||
81120.415
|
||||
],
|
||||
[
|
||||
1789935588000,
|
||||
81117.451667
|
||||
],
|
||||
[
|
||||
1789935594000,
|
||||
81115.761667
|
||||
],
|
||||
[
|
||||
1789935600000,
|
||||
81115.99
|
||||
],
|
||||
[
|
||||
1789935606000,
|
||||
81118.363333
|
||||
],
|
||||
[
|
||||
1789935612000,
|
||||
81122.728333
|
||||
],
|
||||
[
|
||||
1789935618000,
|
||||
81123.318333
|
||||
],
|
||||
[
|
||||
1789935624000,
|
||||
81121.158333
|
||||
],
|
||||
[
|
||||
1789935630000,
|
||||
81119.46
|
||||
],
|
||||
[
|
||||
1789935636000,
|
||||
81119.791667
|
||||
],
|
||||
[
|
||||
1789935642000,
|
||||
81118.191667
|
||||
],
|
||||
[
|
||||
1789935648000,
|
||||
81114.616667
|
||||
],
|
||||
[
|
||||
1789935654000,
|
||||
81111.5
|
||||
],
|
||||
[
|
||||
1789935660000,
|
||||
81110.628333
|
||||
],
|
||||
[
|
||||
1789935666000,
|
||||
81109.99
|
||||
],
|
||||
[
|
||||
1789935672000,
|
||||
81109.06
|
||||
],
|
||||
[
|
||||
1789935678000,
|
||||
81113.483333
|
||||
],
|
||||
[
|
||||
1789935684000,
|
||||
81116.048333
|
||||
],
|
||||
[
|
||||
1789935690000,
|
||||
81117.225
|
||||
],
|
||||
[
|
||||
1789935696000,
|
||||
81115.436667
|
||||
],
|
||||
[
|
||||
1789935702000,
|
||||
81108.545
|
||||
],
|
||||
[
|
||||
1789935708000,
|
||||
81105.001667
|
||||
],
|
||||
[
|
||||
1789935714000,
|
||||
81104.198333
|
||||
],
|
||||
[
|
||||
1789935720000,
|
||||
81103.061667
|
||||
],
|
||||
[
|
||||
1789935726000,
|
||||
81122.996667
|
||||
],
|
||||
[
|
||||
1789935732000,
|
||||
81124.31
|
||||
],
|
||||
[
|
||||
1789935738000,
|
||||
81124.393333
|
||||
],
|
||||
[
|
||||
1789935744000,
|
||||
81130.29
|
||||
],
|
||||
[
|
||||
1789935750000,
|
||||
81132.178333
|
||||
],
|
||||
[
|
||||
1789935756000,
|
||||
81132.845
|
||||
],
|
||||
[
|
||||
1789935762000,
|
||||
81131.555
|
||||
],
|
||||
[
|
||||
1789935768000,
|
||||
81131.316667
|
||||
],
|
||||
[
|
||||
1789935774000,
|
||||
81117.736667
|
||||
],
|
||||
[
|
||||
1789935780000,
|
||||
81111.978333
|
||||
],
|
||||
[
|
||||
1789935786000,
|
||||
81111.286667
|
||||
],
|
||||
[
|
||||
1789935792000,
|
||||
81123.215
|
||||
],
|
||||
[
|
||||
1789935798000,
|
||||
81127.655
|
||||
],
|
||||
[
|
||||
1789935804000,
|
||||
81130.285
|
||||
]
|
||||
]
|
||||
4
openbb_platform/providers/deribit/tests/data/index_price.json
vendored
Normal file
4
openbb_platform/providers/deribit/tests/data/index_price.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"estimated_delivery_price": 80814.47,
|
||||
"index_price": 80814.47
|
||||
}
|
||||
202
openbb_platform/providers/deribit/tests/data/index_price_names.json
vendored
Normal file
202
openbb_platform/providers/deribit/tests/data/index_price_names.json
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
[
|
||||
"ada_usd",
|
||||
"ada_usdc",
|
||||
"ada_usdt",
|
||||
"algo_usd",
|
||||
"algo_usdc",
|
||||
"algo_usdt",
|
||||
"avax_usd",
|
||||
"avax_usdc",
|
||||
"avax_usdt",
|
||||
"bch_usd",
|
||||
"bch_usdc",
|
||||
"bch_usdt",
|
||||
"bnb_usd",
|
||||
"bnb_usdc",
|
||||
"bnb_usdt",
|
||||
"bonk_usd",
|
||||
"btc_usd",
|
||||
"btc_usdc",
|
||||
"btc_usdt",
|
||||
"buidl_usd",
|
||||
"buidl_usdc",
|
||||
"doge_usd",
|
||||
"doge_usdc",
|
||||
"doge_usdt",
|
||||
"dot_usd",
|
||||
"dot_usdc",
|
||||
"dot_usdt",
|
||||
"eth_usd",
|
||||
"eth_usdc",
|
||||
"eth_usdt",
|
||||
"ethw_usd",
|
||||
"link_usd",
|
||||
"link_usdc",
|
||||
"link_usdt",
|
||||
"ltc_usd",
|
||||
"ltc_usdc",
|
||||
"ltc_usdt",
|
||||
"matic_usd",
|
||||
"matic_usdc",
|
||||
"matic_usdt",
|
||||
"mog_usd",
|
||||
"near_usd",
|
||||
"near_usdc",
|
||||
"near_usdt",
|
||||
"paxg_usd",
|
||||
"paxg_usdc",
|
||||
"paxg_usdt",
|
||||
"pepe_usd",
|
||||
"shib_usd",
|
||||
"shib_usdc",
|
||||
"shib_usdt",
|
||||
"sol_usd",
|
||||
"sol_usdc",
|
||||
"sol_usdt",
|
||||
"sol_eth",
|
||||
"ton_usd",
|
||||
"ton_usdc",
|
||||
"ton_usdt",
|
||||
"trump_usd",
|
||||
"trump_usdc",
|
||||
"trump_usdt",
|
||||
"trx_usd",
|
||||
"trx_usdc",
|
||||
"trx_usdt",
|
||||
"uni_usd",
|
||||
"uni_usdc",
|
||||
"uni_usdt",
|
||||
"xrp_usd",
|
||||
"xrp_usdc",
|
||||
"xrp_usdt",
|
||||
"usd_usdc",
|
||||
"usd_usdt",
|
||||
"usdc_usd",
|
||||
"usdc_usdt",
|
||||
"usdt_usdc",
|
||||
"usde_usd",
|
||||
"usde_usdc",
|
||||
"usde_usdt",
|
||||
"usdt_usd",
|
||||
"drbfix-btc_usdc",
|
||||
"drbfix-eth_usdc",
|
||||
"eth_btc",
|
||||
"eth_steth",
|
||||
"steth_eth",
|
||||
"steth_usd",
|
||||
"steth_usdc",
|
||||
"steth_usdt",
|
||||
"btc_eurr",
|
||||
"eth_eurr",
|
||||
"usdc_eur",
|
||||
"usdt_eur",
|
||||
"eur_usdc",
|
||||
"eur_usdt",
|
||||
"eurr_usd",
|
||||
"eurr_usdc",
|
||||
"eurr_usdt",
|
||||
"eth_matic",
|
||||
"btc_usyc",
|
||||
"eth_usyc",
|
||||
"usyc_usd",
|
||||
"usyc_usdc",
|
||||
"paxg_btc",
|
||||
"eth_paxg",
|
||||
"btc_usde",
|
||||
"eth_usde",
|
||||
"eth_buidl",
|
||||
"hype_usd",
|
||||
"hype_usdc",
|
||||
"hype_usdt",
|
||||
"aave_usd",
|
||||
"aave_usdc",
|
||||
"apt_usd",
|
||||
"apt_usdc",
|
||||
"arb_usd",
|
||||
"arb_usdc",
|
||||
"ena_usd",
|
||||
"ena_usdc",
|
||||
"etc_usd",
|
||||
"etc_usdc",
|
||||
"fil_usd",
|
||||
"fil_usdc",
|
||||
"hbar_usd",
|
||||
"hbar_usdc",
|
||||
"icp_usd",
|
||||
"icp_usdc",
|
||||
"inj_usd",
|
||||
"inj_usdc",
|
||||
"jup_usd",
|
||||
"jup_usdc",
|
||||
"ondo_usd",
|
||||
"ondo_usdc",
|
||||
"op_usd",
|
||||
"op_usdc",
|
||||
"render_usd",
|
||||
"render_usdc",
|
||||
"sei_usd",
|
||||
"sei_usdc",
|
||||
"sui_usd",
|
||||
"sui_usdc",
|
||||
"tao_usd",
|
||||
"tao_usdc",
|
||||
"tia_usd",
|
||||
"tia_usdc",
|
||||
"wld_usd",
|
||||
"wld_usdc",
|
||||
"xlm_usd",
|
||||
"xlm_usdc",
|
||||
"1000bonk_usd",
|
||||
"1000bonk_usdc",
|
||||
"1000mog_usd",
|
||||
"1000mog_usdc",
|
||||
"1000pepe_usd",
|
||||
"1000pepe_usdc",
|
||||
"1000shib_usd",
|
||||
"1000shib_usdc",
|
||||
"aero_usd",
|
||||
"aero_usdc",
|
||||
"atom_usd",
|
||||
"atom_usdc",
|
||||
"axs_usd",
|
||||
"axs_usdc",
|
||||
"bera_usd",
|
||||
"bera_usdc",
|
||||
"bome_usd",
|
||||
"bome_usdc",
|
||||
"brett_usd",
|
||||
"brett_usdc",
|
||||
"cfx_usd",
|
||||
"cfx_usdc",
|
||||
"crv_usd",
|
||||
"crv_usdc",
|
||||
"dydx_usd",
|
||||
"dydx_usdc",
|
||||
"eigen_usd",
|
||||
"eigen_usdc",
|
||||
"ethfi_usd",
|
||||
"ethfi_usdc",
|
||||
"fet_usd",
|
||||
"fet_usdc",
|
||||
"jto_usd",
|
||||
"jto_usdc",
|
||||
"ldo_usd",
|
||||
"ldo_usdc",
|
||||
"morpho_usd",
|
||||
"morpho_usdc",
|
||||
"pendle_usd",
|
||||
"pendle_usdc",
|
||||
"pengu_usd",
|
||||
"pengu_usdc",
|
||||
"pnut_usd",
|
||||
"pnut_usdc",
|
||||
"pump_usd",
|
||||
"pump_usdc",
|
||||
"pyth_usd",
|
||||
"pyth_usdc",
|
||||
"rune_usd",
|
||||
"rune_usdc",
|
||||
"s_usd",
|
||||
"s_usdc",
|
||||
"strk_usd"
|
||||
]
|
||||
35
openbb_platform/providers/deribit/tests/data/instrument.json
vendored
Normal file
35
openbb_platform/providers/deribit/tests/data/instrument.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"max_liquidation_commission": 0.01,
|
||||
"taker_commission": 0.00035,
|
||||
"counter_currency": "USD",
|
||||
"index_id": 1000033,
|
||||
"price_index": "btc_usd",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"contract_size": 10.0,
|
||||
"state": "open",
|
||||
"tick_size": 0.5,
|
||||
"kind": "future",
|
||||
"creation_timestamp": 1534242287000,
|
||||
"expiration_timestamp": 32503708800000,
|
||||
"is_active": true,
|
||||
"instrument_type": "reversed",
|
||||
"underlying_type": "crypto",
|
||||
"tick_size_steps": [],
|
||||
"min_trade_amount": 10.0,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"instrument_id": 210838,
|
||||
"product_group": "BTC",
|
||||
"lot_size": 1,
|
||||
"quote_currency": "USD",
|
||||
"future_type": "reversed",
|
||||
"base_currency": "BTC",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1",
|
||||
"block_trade_tick_size": 0.01,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"max_leverage": 50,
|
||||
"settlement_currency": "BTC",
|
||||
"max_non_default_leverage": 5.0,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_period": "perpetual",
|
||||
"maker_commission": 0.00015
|
||||
}
|
||||
457
openbb_platform/providers/deribit/tests/data/instruments_future.json
vendored
Normal file
457
openbb_platform/providers/deribit/tests/data/instruments_future.json
vendored
Normal file
@@ -0,0 +1,457 @@
|
||||
[
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 50,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 32503708800000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 210838,
|
||||
"creation_timestamp": 1534242287000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 0.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "perpetual",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1789977600000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 695928,
|
||||
"creation_timestamp": 1789632015000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-21SEP26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "day",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1790064000000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 696991,
|
||||
"creation_timestamp": 1789718417000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-22SEP26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "day",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1790150400000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 697962,
|
||||
"creation_timestamp": 1789804818000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-23SEP26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "day",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1790236800000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 698438,
|
||||
"creation_timestamp": 1789891217000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-24SEP26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "day",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1790323200000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 522657,
|
||||
"creation_timestamp": 1758873621000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-25SEP26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "month",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1790928000000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 691809,
|
||||
"creation_timestamp": 1789027214000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-2OCT26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "week",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1791532800000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 695930,
|
||||
"creation_timestamp": 1789632015000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-9OCT26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "week",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1793347200000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 667489,
|
||||
"creation_timestamp": 1785398413000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-30OCT26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "month",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1795766400000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 682891,
|
||||
"creation_timestamp": 1787817624000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-27NOV26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "month",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1798185600000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 567609,
|
||||
"creation_timestamp": 1766736024000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-25DEC26",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "month",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1806048000000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 607780,
|
||||
"creation_timestamp": 1774598426000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-26MAR27",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "month",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
},
|
||||
{
|
||||
"tick_size_steps": [],
|
||||
"block_trade_tick_size": 0.01,
|
||||
"quote_currency": "USD",
|
||||
"price_index": "btc_usd",
|
||||
"instrument_type": "reversed",
|
||||
"future_type": "reversed",
|
||||
"kind": "future",
|
||||
"max_leverage": 25,
|
||||
"underlying_type": "crypto",
|
||||
"product_group": "BTC",
|
||||
"state": "open",
|
||||
"expiration_timestamp": 1813910400000,
|
||||
"contract_size": 10.0,
|
||||
"min_trade_amount": 10.0,
|
||||
"instrument_id": 652925,
|
||||
"creation_timestamp": 1782460823000,
|
||||
"base_currency_uuid": "5b71fc48-3dd3-540c-809b-f8c94d0e68b5",
|
||||
"is_active": true,
|
||||
"lot_size": 1,
|
||||
"taker_commission": 0.00035,
|
||||
"tick_size": 2.5,
|
||||
"block_trade_commission": 0.00025,
|
||||
"settlement_currency": "BTC",
|
||||
"instrument_name": "BTC-25JUN27",
|
||||
"block_trade_min_trade_amount": 200000,
|
||||
"max_liquidation_commission": 0.01,
|
||||
"max_non_default_leverage": 5.0,
|
||||
"index_id": 1000033,
|
||||
"counter_currency": "USD",
|
||||
"base_currency": "BTC",
|
||||
"maker_commission": 0.00015,
|
||||
"settlement_period": "month",
|
||||
"quote_currency_uuid": "dc311642-e9fb-4ba9-a312-c75c1aef4ae1"
|
||||
}
|
||||
]
|
||||
2870
openbb_platform/providers/deribit/tests/data/instruments_option.json
vendored
Normal file
2870
openbb_platform/providers/deribit/tests/data/instruments_option.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
452
openbb_platform/providers/deribit/tests/data/option_tickers.json
vendored
Normal file
452
openbb_platform/providers/deribit/tests/data/option_tickers.json
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
{
|
||||
"BTC-21SEP26-68000-C": {
|
||||
"timestamp": 1789939517455,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 1.0,
|
||||
"gamma": 0.0,
|
||||
"vega": 0.0,
|
||||
"theta": 0.0,
|
||||
"rho": 0.82154
|
||||
},
|
||||
"index_price": 80808.14,
|
||||
"instrument_name": "BTC-21SEP26-68000-C",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.15328061,
|
||||
"min_price": 0.1285,
|
||||
"max_price": 0.1865,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.1585,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 80808.14,
|
||||
"best_ask_price": 0.161,
|
||||
"best_bid_price": 0.1565,
|
||||
"mark_iv": 61.59,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 315.62,
|
||||
"underlying_price": 80809.22,
|
||||
"underlying_index": "BTC-21SEP26",
|
||||
"best_ask_amount": 56.0,
|
||||
"best_bid_amount": 56.0
|
||||
},
|
||||
"BTC-21SEP26-68000-P": {
|
||||
"timestamp": 1789939517455,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 0.0,
|
||||
"gamma": 0.0,
|
||||
"vega": 0.0,
|
||||
"theta": 0.0,
|
||||
"rho": 0.0
|
||||
},
|
||||
"index_price": 80808.14,
|
||||
"instrument_name": "BTC-21SEP26-68000-P",
|
||||
"last_price": 0.0001,
|
||||
"settlement_price": 0.0,
|
||||
"min_price": 0.0001,
|
||||
"max_price": 0.015,
|
||||
"open_interest": 0.5,
|
||||
"mark_price": 0.0,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 80808.14,
|
||||
"best_ask_price": 0.0001,
|
||||
"best_bid_price": 0.0,
|
||||
"mark_iv": 61.59,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 193.39,
|
||||
"underlying_price": 80809.22,
|
||||
"underlying_index": "BTC-21SEP26",
|
||||
"best_ask_amount": 20.0,
|
||||
"best_bid_amount": 0.0
|
||||
},
|
||||
"BTC-21SEP26-69000-C": {
|
||||
"timestamp": 1789939517455,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 1.0,
|
||||
"gamma": 0.0,
|
||||
"vega": 0.0,
|
||||
"theta": 0.0,
|
||||
"rho": 0.83362
|
||||
},
|
||||
"index_price": 80808.14,
|
||||
"instrument_name": "BTC-21SEP26-69000-C",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.14082886,
|
||||
"min_price": 0.1155,
|
||||
"max_price": 0.175,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.1461,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 80808.14,
|
||||
"best_ask_price": 0.148,
|
||||
"best_bid_price": 0.144,
|
||||
"mark_iv": 61.57,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 276.84,
|
||||
"underlying_price": 80808.56,
|
||||
"underlying_index": "BTC-21SEP26",
|
||||
"best_ask_amount": 56.0,
|
||||
"best_bid_amount": 56.0
|
||||
},
|
||||
"BTC-21SEP26-69000-P": {
|
||||
"timestamp": 1789939517455,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 0.0,
|
||||
"gamma": 0.0,
|
||||
"vega": 0.0,
|
||||
"theta": 0.0,
|
||||
"rho": 0.0
|
||||
},
|
||||
"index_price": 80808.14,
|
||||
"instrument_name": "BTC-21SEP26-69000-P",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.0,
|
||||
"min_price": 0.0001,
|
||||
"max_price": 0.015,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.0,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 80808.14,
|
||||
"best_ask_price": 0.0001,
|
||||
"best_bid_price": 0.0,
|
||||
"mark_iv": 61.57,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 178.62,
|
||||
"underlying_price": 80809.22,
|
||||
"underlying_index": "BTC-21SEP26",
|
||||
"best_ask_amount": 18.5,
|
||||
"best_bid_amount": 0.0
|
||||
},
|
||||
"BTC-21SEP26-70000-C": {
|
||||
"timestamp": 1789939517455,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 1.0,
|
||||
"gamma": 0.0,
|
||||
"vega": 0.0,
|
||||
"theta": 0.0,
|
||||
"rho": 0.8457
|
||||
},
|
||||
"index_price": 80808.14,
|
||||
"instrument_name": "BTC-21SEP26-70000-C",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.12837712,
|
||||
"min_price": 0.1025,
|
||||
"max_price": 0.163,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.1338,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 80808.14,
|
||||
"best_ask_price": 0.1355,
|
||||
"best_bid_price": 0.1315,
|
||||
"mark_iv": 61.57,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 252.4,
|
||||
"underlying_price": 80809.22,
|
||||
"underlying_index": "BTC-21SEP26",
|
||||
"best_ask_amount": 56.0,
|
||||
"best_bid_amount": 56.0
|
||||
},
|
||||
"BTC-21SEP26-70000-P": {
|
||||
"timestamp": 1789939518462,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 0.0,
|
||||
"gamma": 0.0,
|
||||
"vega": 0.0,
|
||||
"theta": 0.0,
|
||||
"rho": 0.0
|
||||
},
|
||||
"index_price": 80807.52,
|
||||
"instrument_name": "BTC-21SEP26-70000-P",
|
||||
"last_price": 0.0001,
|
||||
"settlement_price": 2e-08,
|
||||
"min_price": 0.0001,
|
||||
"max_price": 0.015,
|
||||
"open_interest": 22.1,
|
||||
"mark_price": 0.0,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 80807.52,
|
||||
"best_ask_price": 0.0001,
|
||||
"best_bid_price": 0.0,
|
||||
"mark_iv": 61.57,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 163.96,
|
||||
"underlying_price": 80808.75,
|
||||
"underlying_index": "BTC-21SEP26",
|
||||
"best_ask_amount": 27.9,
|
||||
"best_bid_amount": 0.0
|
||||
},
|
||||
"XRP_USDC-21SEP26-1d25-C": {
|
||||
"timestamp": 1789939517605,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0,
|
||||
"volume_notional": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 0.99603,
|
||||
"gamma": 0.19661,
|
||||
"vega": 1e-05,
|
||||
"theta": -0.00035,
|
||||
"rho": 2e-05
|
||||
},
|
||||
"index_price": 1.3987,
|
||||
"instrument_name": "XRP_USDC-21SEP26-1d25-C",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.13044156,
|
||||
"min_price": 0.0655,
|
||||
"max_price": 0.238,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.1491,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 1.3987,
|
||||
"best_ask_price": 0.0,
|
||||
"best_bid_price": 0.0,
|
||||
"mark_iv": 123.13,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 0.0,
|
||||
"underlying_price": 1.3991,
|
||||
"underlying_index": "XRP_USDC-21SEP26",
|
||||
"best_ask_amount": 0.0,
|
||||
"best_bid_amount": 0.0
|
||||
},
|
||||
"XRP_USDC-21SEP26-1d25-P": {
|
||||
"timestamp": 1789939517605,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0,
|
||||
"volume_notional": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": -0.00395,
|
||||
"gamma": 0.19572,
|
||||
"vega": 1e-05,
|
||||
"theta": -7e-05,
|
||||
"rho": 0.0
|
||||
},
|
||||
"index_price": 1.3987,
|
||||
"instrument_name": "XRP_USDC-21SEP26-1d25-P",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.00024156,
|
||||
"min_price": 0.0005,
|
||||
"max_price": 0.0335,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.0001,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 1.3987,
|
||||
"best_ask_price": 0.0025,
|
||||
"best_bid_price": 0.0,
|
||||
"mark_iv": 123.13,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 209.07,
|
||||
"underlying_price": 1.3992,
|
||||
"underlying_index": "XRP_USDC-21SEP26",
|
||||
"best_ask_amount": 25000.0,
|
||||
"best_bid_amount": 0.0
|
||||
},
|
||||
"XRP_USDC-21SEP26-1d28-C": {
|
||||
"timestamp": 1789939517605,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0,
|
||||
"volume_notional": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 0.98222,
|
||||
"gamma": 0.7315,
|
||||
"vega": 2e-05,
|
||||
"theta": -0.00131,
|
||||
"rho": 2e-05
|
||||
},
|
||||
"index_price": 1.3987,
|
||||
"instrument_name": "XRP_USDC-21SEP26-1d28-C",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.10089075,
|
||||
"min_price": 0.0355,
|
||||
"max_price": 0.211,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.1194,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 1.3987,
|
||||
"best_ask_price": 0.0,
|
||||
"best_bid_price": 0.0,
|
||||
"mark_iv": 123.13,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 0.0,
|
||||
"underlying_price": 1.3992,
|
||||
"underlying_index": "XRP_USDC-21SEP26",
|
||||
"best_ask_amount": 0.0,
|
||||
"best_bid_amount": 0.0
|
||||
},
|
||||
"XRP_USDC-21SEP26-1d28-P": {
|
||||
"timestamp": 1789939517605,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0,
|
||||
"volume_notional": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": -0.01778,
|
||||
"gamma": 0.7315,
|
||||
"vega": 2e-05,
|
||||
"theta": -0.00039,
|
||||
"rho": 0.0
|
||||
},
|
||||
"index_price": 1.3987,
|
||||
"instrument_name": "XRP_USDC-21SEP26-1d28-P",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.00069075,
|
||||
"min_price": 0.0005,
|
||||
"max_price": 0.045,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.0004,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 1.3987,
|
||||
"best_ask_price": 0.0025,
|
||||
"best_bid_price": 0.0,
|
||||
"mark_iv": 123.13,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 173.59,
|
||||
"underlying_price": 1.3992,
|
||||
"underlying_index": "XRP_USDC-21SEP26",
|
||||
"best_ask_amount": 25000.0,
|
||||
"best_bid_amount": 0.0
|
||||
},
|
||||
"XRP_USDC-21SEP26-1d3-C": {
|
||||
"timestamp": 1789939517605,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0,
|
||||
"volume_notional": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": 0.96884,
|
||||
"gamma": 1.25842,
|
||||
"vega": 3e-05,
|
||||
"theta": -0.00196,
|
||||
"rho": 2e-05
|
||||
},
|
||||
"index_price": 1.3987,
|
||||
"instrument_name": "XRP_USDC-21SEP26-1d3-C",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.08169251,
|
||||
"min_price": 0.0155,
|
||||
"max_price": 0.1915,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.0997,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 1.3987,
|
||||
"best_ask_price": 0.0,
|
||||
"best_bid_price": 0.0,
|
||||
"mark_iv": 114.73,
|
||||
"bid_iv": 0.0,
|
||||
"ask_iv": 0.0,
|
||||
"underlying_price": 1.3992,
|
||||
"underlying_index": "XRP_USDC-21SEP26",
|
||||
"best_ask_amount": 0.0,
|
||||
"best_bid_amount": 0.0
|
||||
},
|
||||
"XRP_USDC-21SEP26-1d3-P": {
|
||||
"timestamp": 1789939517605,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": null,
|
||||
"low": null,
|
||||
"price_change": null,
|
||||
"volume": 0.0,
|
||||
"volume_usd": 0.0,
|
||||
"volume_notional": 0.0
|
||||
},
|
||||
"greeks": {
|
||||
"delta": -0.03116,
|
||||
"gamma": 1.25842,
|
||||
"vega": 3e-05,
|
||||
"theta": -0.00069,
|
||||
"rho": 0.0
|
||||
},
|
||||
"index_price": 1.3987,
|
||||
"instrument_name": "XRP_USDC-21SEP26-1d3-P",
|
||||
"last_price": null,
|
||||
"settlement_price": 0.00149251,
|
||||
"min_price": 0.0005,
|
||||
"max_price": 0.05,
|
||||
"open_interest": 0.0,
|
||||
"mark_price": 0.0007,
|
||||
"interest_rate": 0.0,
|
||||
"estimated_delivery_price": 1.3987,
|
||||
"best_ask_price": 0.001,
|
||||
"best_bid_price": 0.0005,
|
||||
"mark_iv": 114.73,
|
||||
"bid_iv": 108.74,
|
||||
"ask_iv": 122.91,
|
||||
"underlying_price": 1.3992,
|
||||
"underlying_index": "XRP_USDC-21SEP26",
|
||||
"best_ask_amount": 25000.0,
|
||||
"best_bid_amount": 25000.0
|
||||
}
|
||||
}
|
||||
73
openbb_platform/providers/deribit/tests/data/order_book.json
vendored
Normal file
73
openbb_platform/providers/deribit/tests/data/order_book.json
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"timestamp": 1789939036574,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": 81497.5,
|
||||
"low": 80149.5,
|
||||
"price_change": -0.2493,
|
||||
"volume": 3099.91787897,
|
||||
"volume_usd": 250336360.0,
|
||||
"volume_notional": 250336360.0
|
||||
},
|
||||
"change_id": 174959286421,
|
||||
"index_price": 80815.66,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"bids": [
|
||||
[
|
||||
80836.5,
|
||||
65010.0
|
||||
],
|
||||
[
|
||||
80832.0,
|
||||
6510.0
|
||||
],
|
||||
[
|
||||
80831.0,
|
||||
15520.0
|
||||
],
|
||||
[
|
||||
80827.0,
|
||||
1470.0
|
||||
],
|
||||
[
|
||||
80826.0,
|
||||
4460.0
|
||||
]
|
||||
],
|
||||
"asks": [
|
||||
[
|
||||
80837.0,
|
||||
10810.0
|
||||
],
|
||||
[
|
||||
80837.5,
|
||||
10000.0
|
||||
],
|
||||
[
|
||||
80838.0,
|
||||
3350.0
|
||||
],
|
||||
[
|
||||
80838.5,
|
||||
6000.0
|
||||
],
|
||||
[
|
||||
80840.0,
|
||||
6000.0
|
||||
]
|
||||
],
|
||||
"last_price": 80832.5,
|
||||
"settlement_price": 80317.84,
|
||||
"min_price": 79618.5,
|
||||
"max_price": 82044.0,
|
||||
"open_interest": 787799210,
|
||||
"mark_price": 80831.35,
|
||||
"interest_value": 0.024991800794000794,
|
||||
"current_funding": 0.0,
|
||||
"estimated_delivery_price": 80815.66,
|
||||
"funding_8h": 1.583e-05,
|
||||
"best_ask_price": 80837.0,
|
||||
"best_bid_price": 80836.5,
|
||||
"best_ask_amount": 10810.0,
|
||||
"best_bid_amount": 65010.0
|
||||
}
|
||||
1
openbb_platform/providers/deribit/tests/data/server_time.json
vendored
Normal file
1
openbb_platform/providers/deribit/tests/data/server_time.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
1789939041264
|
||||
55
openbb_platform/providers/deribit/tests/data/settlements_currency.json
vendored
Normal file
55
openbb_platform/providers/deribit/tests/data/settlements_currency.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"continuation": "3JbvTsVzTGQ6HW7KSsh78zQ4sZ8tMD9vrhkwQNR7TJTVHbBswFK6j9yJdsz8vwZTgaYJihMaRyXn9",
|
||||
"settlements": [
|
||||
{
|
||||
"position": 31999610,
|
||||
"timestamp": 1789891200065,
|
||||
"type": "delivery",
|
||||
"index_price": 80321.21,
|
||||
"instrument_name": "BTC-20SEP26",
|
||||
"mark_price": 80321.21,
|
||||
"profit_loss": -3.345250995000001,
|
||||
"session_profit_loss": 23493.117937861054
|
||||
},
|
||||
{
|
||||
"position": 17.8,
|
||||
"timestamp": 1789891200065,
|
||||
"type": "exercise",
|
||||
"index_price": 80321.21,
|
||||
"instrument_name": "BTC-20SEP26-68000-P",
|
||||
"mark_price": 0.0,
|
||||
"profit_loss": -8.673617379884035e-19,
|
||||
"session_profit_loss": 492.86261408100006
|
||||
},
|
||||
{
|
||||
"position": 88.8,
|
||||
"timestamp": 1789891200065,
|
||||
"type": "exercise",
|
||||
"index_price": 80321.21,
|
||||
"instrument_name": "BTC-20SEP26-69000-P",
|
||||
"mark_price": 0.0,
|
||||
"profit_loss": 0.009744748999999999,
|
||||
"session_profit_loss": 1642.87538027
|
||||
},
|
||||
{
|
||||
"position": 65.2,
|
||||
"timestamp": 1789891200065,
|
||||
"type": "exercise",
|
||||
"index_price": 80321.21,
|
||||
"instrument_name": "BTC-20SEP26-70000-P",
|
||||
"mark_price": 0.0,
|
||||
"profit_loss": 0.005033333,
|
||||
"session_profit_loss": 1642.87538027
|
||||
},
|
||||
{
|
||||
"position": 8.599999999999998,
|
||||
"timestamp": 1789891200065,
|
||||
"type": "exercise",
|
||||
"index_price": 80321.21,
|
||||
"instrument_name": "BTC-20SEP26-71000-P",
|
||||
"mark_price": 0.0,
|
||||
"profit_loss": -0.004930000000000005,
|
||||
"session_profit_loss": 1807.162918297
|
||||
}
|
||||
]
|
||||
}
|
||||
55
openbb_platform/providers/deribit/tests/data/settlements_instrument.json
vendored
Normal file
55
openbb_platform/providers/deribit/tests/data/settlements_instrument.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"continuation": "23nYDkVbSynLKHS9FLtdNzkdZehx1XPceCZ2K54nzc3PhhBDfRmKbxsg59v47UKDhQL7c",
|
||||
"settlements": [
|
||||
{
|
||||
"position": 778628500,
|
||||
"timestamp": 1789891200065,
|
||||
"type": "settlement",
|
||||
"index_price": 80292.87,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"mark_price": 80317.84,
|
||||
"profit_loss": -2.882714252999952,
|
||||
"session_profit_loss": 366525.4973382353
|
||||
},
|
||||
{
|
||||
"position": 778159550,
|
||||
"timestamp": 1789804800083,
|
||||
"type": "settlement",
|
||||
"index_price": 81078.23,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"mark_price": 81109.92,
|
||||
"profit_loss": -15.834744451000045,
|
||||
"session_profit_loss": 1564451.0500825022
|
||||
},
|
||||
{
|
||||
"position": 778769380,
|
||||
"timestamp": 1789718400096,
|
||||
"type": "settlement",
|
||||
"index_price": 77738.19,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"mark_price": 77762.07,
|
||||
"profit_loss": 0.33698070499993793,
|
||||
"session_profit_loss": 660354.2430167407
|
||||
},
|
||||
{
|
||||
"position": 796354170,
|
||||
"timestamp": 1789632000061,
|
||||
"type": "settlement",
|
||||
"index_price": 76391.01,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"mark_price": 76418.56,
|
||||
"profit_loss": 1.8330351599999015,
|
||||
"session_profit_loss": 357003.47944851726
|
||||
},
|
||||
{
|
||||
"position": 794478700,
|
||||
"timestamp": 1789545600759,
|
||||
"type": "settlement",
|
||||
"index_price": 75684.39,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"mark_price": 75709.26,
|
||||
"profit_loss": -0.049004756999928276,
|
||||
"session_profit_loss": 676031.4583834042
|
||||
}
|
||||
]
|
||||
}
|
||||
3
openbb_platform/providers/deribit/tests/data/status.json
vendored
Normal file
3
openbb_platform/providers/deribit/tests/data/status.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"locked": "false"
|
||||
}
|
||||
192
openbb_platform/providers/deribit/tests/data/supported_index_names.json
vendored
Normal file
192
openbb_platform/providers/deribit/tests/data/supported_index_names.json
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
[
|
||||
"aave_usdc",
|
||||
"ada_usdc",
|
||||
"aero_usdc",
|
||||
"algo_usdc",
|
||||
"apt_usdc",
|
||||
"arb_usdc",
|
||||
"atom_usdc",
|
||||
"avax_usdc",
|
||||
"bch_usdc",
|
||||
"bnb_usdc",
|
||||
"bonk_usdc",
|
||||
"btc_eurr",
|
||||
"btc_paxg",
|
||||
"btc_usdc",
|
||||
"btc_usde",
|
||||
"btc_usdt",
|
||||
"btc_usyc",
|
||||
"buidl_usdc",
|
||||
"cbeth_usdc",
|
||||
"crv_usdc",
|
||||
"doge_usdc",
|
||||
"dot_usdc",
|
||||
"eth_btc",
|
||||
"eth_eurr",
|
||||
"eth_paxg",
|
||||
"eth_usdc",
|
||||
"eth_usde",
|
||||
"eth_usdt",
|
||||
"eth_usyc",
|
||||
"eurc_usdc",
|
||||
"eurr_usdc",
|
||||
"eurr_usdt",
|
||||
"fartcoin_usdc",
|
||||
"fil_usdc",
|
||||
"hbar_usdc",
|
||||
"icp_usdc",
|
||||
"inj_usdc",
|
||||
"jasmy_usdc",
|
||||
"link_usdc",
|
||||
"ltc_usdc",
|
||||
"matic_usdc",
|
||||
"near_usdc",
|
||||
"ondo_usdc",
|
||||
"op_usdc",
|
||||
"paxg_btc",
|
||||
"paxg_eth",
|
||||
"paxg_usdc",
|
||||
"pengu_usdc",
|
||||
"pepe_usdc",
|
||||
"pump_usdc",
|
||||
"shib_usdc",
|
||||
"sol_eth",
|
||||
"sol_usdc",
|
||||
"sol_usdt",
|
||||
"steth_eth",
|
||||
"steth_usdc",
|
||||
"steth_usdt",
|
||||
"sui_usdc",
|
||||
"tao_usdc",
|
||||
"uni_usdc",
|
||||
"usdc_usdt",
|
||||
"usde_usdc",
|
||||
"usde_usdt",
|
||||
"usdt_usdc",
|
||||
"usyc_usdc",
|
||||
"vet_usdc",
|
||||
"xlm_usdc",
|
||||
"xrp_usdc",
|
||||
"zora_usdc",
|
||||
"btc_usd",
|
||||
"eth_usd",
|
||||
"1000bonk_usdc",
|
||||
"1000mog_usdc",
|
||||
"1000pepe_usdc",
|
||||
"1000shib_usdc",
|
||||
"aapl_usdc",
|
||||
"alt_usdc",
|
||||
"amd_usdc",
|
||||
"amzn_usdc",
|
||||
"anthropic_usdc",
|
||||
"ape_usdc",
|
||||
"arm_usdc",
|
||||
"aster_usdc",
|
||||
"avnt_usdc",
|
||||
"axs_usdc",
|
||||
"based1_usdc",
|
||||
"be_usdc",
|
||||
"bera_usdc",
|
||||
"bill_usdc",
|
||||
"bome_usdc",
|
||||
"brentoil_usdc",
|
||||
"brett_usdc",
|
||||
"btcdvol_usdc",
|
||||
"cbrs_usdc",
|
||||
"cfx_usdc",
|
||||
"chip_usdc",
|
||||
"coin50_usdc",
|
||||
"comp_usdc",
|
||||
"crcl_usdc",
|
||||
"dash_usdc",
|
||||
"dram_usdc",
|
||||
"dydx_usdc",
|
||||
"edgex_usdc",
|
||||
"eigen_usdc",
|
||||
"ena_usdc",
|
||||
"etc_usdc",
|
||||
"ethdvol_usdc",
|
||||
"ethfi_usdc",
|
||||
"ewy_usdc",
|
||||
"fet_usdc",
|
||||
"gold_usdc",
|
||||
"googl_usdc",
|
||||
"hood_usdc",
|
||||
"hype_usdc",
|
||||
"intc_usdc",
|
||||
"inx_usdc",
|
||||
"jto_usdc",
|
||||
"jup_usdc",
|
||||
"ldo_usdc",
|
||||
"lit_usdc",
|
||||
"lite_usdc",
|
||||
"mega_usdc",
|
||||
"merl_usdc",
|
||||
"meta_usdc",
|
||||
"mog_usdc",
|
||||
"mon_usdc",
|
||||
"morpho_usdc",
|
||||
"mrvl_usdc",
|
||||
"msft_usdc",
|
||||
"mstr_usdc",
|
||||
"mu_usdc",
|
||||
"nbis_usdc",
|
||||
"night_usdc",
|
||||
"nvda_usdc",
|
||||
"openai_usdc",
|
||||
"opn_usdc",
|
||||
"pendle_usdc",
|
||||
"pnut_usdc",
|
||||
"prl_usdc",
|
||||
"pyth_usdc",
|
||||
"qqq_usdc",
|
||||
"render_usdc",
|
||||
"resolv_usdc",
|
||||
"robo_usdc",
|
||||
"rune_usdc",
|
||||
"s_usdc",
|
||||
"saga_usdc",
|
||||
"sei_usdc",
|
||||
"silver_usdc",
|
||||
"skhy_usdc",
|
||||
"sndk_usdc",
|
||||
"soxl_usdc",
|
||||
"spcx_usdc",
|
||||
"spy_usdc",
|
||||
"strk_usdc",
|
||||
"tia_usdc",
|
||||
"ton_usdc",
|
||||
"towns_usdc",
|
||||
"trump_usdc",
|
||||
"trx_usdc",
|
||||
"tsla_usdc",
|
||||
"tsm_usdc",
|
||||
"virtual_usdc",
|
||||
"vvv_usdc",
|
||||
"w_usdc",
|
||||
"wif_usdc",
|
||||
"wld_usdc",
|
||||
"wlfi_usdc",
|
||||
"wtioil_usdc",
|
||||
"xpl_usdc",
|
||||
"zec_usdc",
|
||||
"ada_usdt",
|
||||
"algo_usdt",
|
||||
"avax_usdt",
|
||||
"bch_usdt",
|
||||
"bnb_usdt",
|
||||
"doge_usdt",
|
||||
"dot_usdt",
|
||||
"hype_usdt",
|
||||
"link_usdt",
|
||||
"ltc_usdt",
|
||||
"matic_usdt",
|
||||
"near_usdt",
|
||||
"paxg_usdt",
|
||||
"shib_usdt",
|
||||
"ton_usdt",
|
||||
"trump_usdt",
|
||||
"trx_usdt",
|
||||
"uni_usdt",
|
||||
"xrp_usdt"
|
||||
]
|
||||
28
openbb_platform/providers/deribit/tests/data/ticker_future.json
vendored
Normal file
28
openbb_platform/providers/deribit/tests/data/ticker_future.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"timestamp": 1789939037426,
|
||||
"state": "open",
|
||||
"stats": {
|
||||
"high": 81497.5,
|
||||
"low": 80149.5,
|
||||
"price_change": -0.2487,
|
||||
"volume": 3100.02328212,
|
||||
"volume_usd": 250344880.0,
|
||||
"volume_notional": 250344880.0
|
||||
},
|
||||
"index_price": 80814.49,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"last_price": 80832.5,
|
||||
"settlement_price": 80317.84,
|
||||
"min_price": 79617.5,
|
||||
"max_price": 82043.0,
|
||||
"open_interest": 787799210,
|
||||
"mark_price": 80830.37,
|
||||
"interest_value": 0.024991800794000794,
|
||||
"current_funding": 0.0,
|
||||
"estimated_delivery_price": 80814.49,
|
||||
"funding_8h": 1.583e-05,
|
||||
"best_ask_price": 80837.0,
|
||||
"best_bid_price": 80836.5,
|
||||
"best_ask_amount": 2520.0,
|
||||
"best_bid_amount": 65010.0
|
||||
}
|
||||
313
openbb_platform/providers/deribit/tests/data/trade_volumes.json
vendored
Normal file
313
openbb_platform/providers/deribit/tests/data/trade_volumes.json
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
[
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "AAVE",
|
||||
"currency_pair": "aave_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "ADA",
|
||||
"currency_pair": "ada_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "AERO",
|
||||
"currency_pair": "aero_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "ALGO",
|
||||
"currency_pair": "algo_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "APT",
|
||||
"currency_pair": "apt_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "ARB",
|
||||
"currency_pair": "arb_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "ATOM",
|
||||
"currency_pair": "atom_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "AVAX",
|
||||
"currency_pair": "avax_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "BCH",
|
||||
"currency_pair": "bch_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "BNB",
|
||||
"currency_pair": "bnb_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "BONK",
|
||||
"currency_pair": "bonk_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 3598.5,
|
||||
"calls_volume_30d": 469702.8,
|
||||
"calls_volume_7d": 126410.1,
|
||||
"currency": "BTC",
|
||||
"currency_pair": "btc_usd",
|
||||
"futures_volume": 3324.25470988,
|
||||
"futures_volume_30d": 176985.7839283,
|
||||
"futures_volume_7d": 42302.8948902,
|
||||
"puts_volume": 2512.5,
|
||||
"puts_volume_30d": 276401.6,
|
||||
"puts_volume_7d": 65783.1
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "BUIDL",
|
||||
"currency_pair": "buidl_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 58799.0,
|
||||
"spot_volume_7d": 16200.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "CBETH",
|
||||
"currency_pair": "cbeth_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "CRV",
|
||||
"currency_pair": "crv_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "DOGE",
|
||||
"currency_pair": "doge_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "DOT",
|
||||
"currency_pair": "dot_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 37094.0,
|
||||
"calls_volume_30d": 2042905.0,
|
||||
"calls_volume_7d": 697174.0,
|
||||
"currency": "ETH",
|
||||
"currency_pair": "eth_usd",
|
||||
"futures_volume": 46811.902868,
|
||||
"futures_volume_30d": 1743490.936324,
|
||||
"futures_volume_7d": 524179.809436,
|
||||
"puts_volume": 31091.0,
|
||||
"puts_volume_30d": 1450096.0,
|
||||
"puts_volume_7d": 399976.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "EURC",
|
||||
"currency_pair": "eurc_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
},
|
||||
{
|
||||
"calls_volume": 0.0,
|
||||
"calls_volume_30d": 0.0,
|
||||
"calls_volume_7d": 0.0,
|
||||
"currency": "EURR",
|
||||
"currency_pair": "eurr_usd",
|
||||
"futures_volume": 0.0,
|
||||
"futures_volume_30d": 0.0,
|
||||
"futures_volume_7d": 0.0,
|
||||
"puts_volume": 0.0,
|
||||
"puts_volume_30d": 0.0,
|
||||
"puts_volume_7d": 0.0,
|
||||
"spot_volume": 0.0,
|
||||
"spot_volume_30d": 0.0,
|
||||
"spot_volume_7d": 0.0
|
||||
}
|
||||
]
|
||||
85
openbb_platform/providers/deribit/tests/data/trades_currency.json
vendored
Normal file
85
openbb_platform/providers/deribit/tests/data/trades_currency.json
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"trades": [
|
||||
{
|
||||
"timestamp": 1789939017544,
|
||||
"iv": 31.75,
|
||||
"price": 0.0011,
|
||||
"amount": 0.1,
|
||||
"direction": "sell",
|
||||
"index_price": 80805.64,
|
||||
"instrument_name": "BTC-21SEP26-80000-P",
|
||||
"trade_seq": 134,
|
||||
"mark_price": 0.00102437,
|
||||
"tick_direction": 2,
|
||||
"starbase_match_id": 227538549002481664,
|
||||
"trade_id": "445682190",
|
||||
"contracts": 0.1,
|
||||
"starbase_timestamp": 1789939017544100459
|
||||
},
|
||||
{
|
||||
"timestamp": 1789939015485,
|
||||
"iv": 34.23,
|
||||
"price": 0.0265,
|
||||
"amount": 1.6,
|
||||
"direction": "buy",
|
||||
"index_price": 80796.05,
|
||||
"instrument_name": "BTC-30OCT26-85000-C",
|
||||
"trade_seq": 660,
|
||||
"mark_price": 0.02644517,
|
||||
"tick_direction": 2,
|
||||
"starbase_match_id": 227538540366409728,
|
||||
"trade_id": "445682189",
|
||||
"contracts": 1.6,
|
||||
"starbase_timestamp": 1789939015485340364
|
||||
},
|
||||
{
|
||||
"timestamp": 1789939009041,
|
||||
"iv": 56.3,
|
||||
"price": 0.0001,
|
||||
"amount": 0.1,
|
||||
"direction": "buy",
|
||||
"index_price": 80791.09,
|
||||
"instrument_name": "BTC-25SEP26-95000-C",
|
||||
"trade_seq": 2213,
|
||||
"mark_price": 0.00012879,
|
||||
"tick_direction": 3,
|
||||
"starbase_match_id": 227538513338314753,
|
||||
"trade_id": "445682181",
|
||||
"contracts": 0.1,
|
||||
"starbase_timestamp": 1789939009041727430
|
||||
},
|
||||
{
|
||||
"timestamp": 1789938977723,
|
||||
"iv": 34.72,
|
||||
"price": 0.006,
|
||||
"amount": 0.1,
|
||||
"direction": "buy",
|
||||
"index_price": 80784.57,
|
||||
"instrument_name": "BTC-9OCT26-88000-C",
|
||||
"trade_seq": 103,
|
||||
"mark_price": 0.0060825,
|
||||
"tick_direction": 3,
|
||||
"starbase_match_id": 227538381981102081,
|
||||
"trade_id": "445682157",
|
||||
"contracts": 0.1,
|
||||
"starbase_timestamp": 1789938977723298197
|
||||
},
|
||||
{
|
||||
"timestamp": 1789938977723,
|
||||
"iv": 34.72,
|
||||
"price": 0.006,
|
||||
"amount": 0.1,
|
||||
"direction": "buy",
|
||||
"index_price": 80784.57,
|
||||
"instrument_name": "BTC-9OCT26-88000-C",
|
||||
"trade_seq": 102,
|
||||
"mark_price": 0.0060825,
|
||||
"tick_direction": 3,
|
||||
"starbase_match_id": 227538381981102080,
|
||||
"trade_id": "445682156",
|
||||
"contracts": 0.1,
|
||||
"starbase_timestamp": 1789938977723298197
|
||||
}
|
||||
],
|
||||
"has_more": true
|
||||
}
|
||||
80
openbb_platform/providers/deribit/tests/data/trades_instrument.json
vendored
Normal file
80
openbb_platform/providers/deribit/tests/data/trades_instrument.json
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"trades": [
|
||||
{
|
||||
"timestamp": 1789939035856,
|
||||
"price": 80832.5,
|
||||
"amount": 6000.0,
|
||||
"direction": "buy",
|
||||
"index_price": 80815.88,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"trade_seq": 299983429,
|
||||
"mark_price": 80831.54,
|
||||
"tick_direction": 1,
|
||||
"starbase_match_id": 227538625808576513,
|
||||
"trade_id": "445682210",
|
||||
"contracts": 600.0,
|
||||
"starbase_timestamp": 1789939035856973859
|
||||
},
|
||||
{
|
||||
"timestamp": 1789939035856,
|
||||
"price": 80832.5,
|
||||
"amount": 2520.0,
|
||||
"direction": "buy",
|
||||
"index_price": 80815.88,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"trade_seq": 299983428,
|
||||
"mark_price": 80831.54,
|
||||
"tick_direction": 0,
|
||||
"starbase_match_id": 227538625808576512,
|
||||
"trade_id": "445682209",
|
||||
"contracts": 252.0,
|
||||
"starbase_timestamp": 1789939035856973859
|
||||
},
|
||||
{
|
||||
"timestamp": 1789939032782,
|
||||
"price": 80832.0,
|
||||
"amount": 2660.0,
|
||||
"direction": "sell",
|
||||
"index_price": 80815.6,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"trade_seq": 299983427,
|
||||
"mark_price": 80831.17,
|
||||
"tick_direction": 0,
|
||||
"starbase_match_id": 227538612915286016,
|
||||
"trade_id": "445682208",
|
||||
"contracts": 266.0,
|
||||
"starbase_timestamp": 1789939032782921844
|
||||
},
|
||||
{
|
||||
"timestamp": 1789939029560,
|
||||
"price": 80829.0,
|
||||
"amount": 2520.0,
|
||||
"direction": "buy",
|
||||
"index_price": 80813.03,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"trade_seq": 299983426,
|
||||
"mark_price": 80828.35,
|
||||
"tick_direction": 0,
|
||||
"starbase_match_id": 227538599401238528,
|
||||
"trade_id": "445682207",
|
||||
"contracts": 252.0,
|
||||
"starbase_timestamp": 1789939029560823168
|
||||
},
|
||||
{
|
||||
"timestamp": 1789939027344,
|
||||
"price": 80824.5,
|
||||
"amount": 2520.0,
|
||||
"direction": "buy",
|
||||
"index_price": 80815.27,
|
||||
"instrument_name": "BTC-PERPETUAL",
|
||||
"trade_seq": 299983425,
|
||||
"mark_price": 80830.58,
|
||||
"tick_direction": 1,
|
||||
"starbase_match_id": 227538590106660864,
|
||||
"trade_id": "445682206",
|
||||
"contracts": 252.0,
|
||||
"starbase_timestamp": 1789939027344529585
|
||||
}
|
||||
],
|
||||
"has_more": true
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user