Deribit adapter¶
Trades one Deribit account: perpetuals, futures and options on USDC-linear contracts (sizes in coins). Coin-margined contracts are not supported. It passes the adapter contract suite on testnet.
Setup¶
- Create an API key on test.deribit.com (or deribit.com for real money) with read and trade permissions only. Never grant withdrawal.
- Fill in the
DERIBIT_*lines of.env:
DERIBIT_TRADER_TOKEN=… # any long random string; qkt sends it
DERIBIT_GUARDIAN_TOKEN=… # any long random string; guardrails sends it
DERIBIT_CLIENT_ID=… # the API key's client id
DERIBIT_CLIENT_SECRET=… # the API key's secret
DERIBIT_ENVIRONMENT=testnet # or mainnet: real money
- Start it:
docker compose --profile deribit up -d # serves on 127.0.0.1:8444
source .env
curl -H "Authorization: Bearer $DERIBIT_TRADER_TOKEN" http://127.0.0.1:8444/v1/health
Health should show "adapter":"deribit", your client id as account_login, and
"venue_connected":true.
Without Compose:
docker run -d -p 127.0.0.1:8444:8443 -v gw-deribit:/data \
-e GATEWAY_ADAPTER=deribit -e GATEWAY_SETTING_ENVIRONMENT=testnet \
-e GATEWAY_LOGIN=<client id> -e GATEWAY_SECRET=<client secret> \
-e GATEWAY_TRADER_TOKEN=… -e GATEWAY_GUARDIAN_TOKEN=… \
qkt-venue-gateway
Connect qkt¶
brokers:
deribit:
type: gateway
gateway_url: http://127.0.0.1:8444
api_key: env:DERIBIT_TRADER_TOKEN
expected_adapter: deribit
expected_account_login: "<client id>"
expected_trade_mode: demo # real on mainnet
qkt writes - in venue codes as _.
Settings¶
| Variable | Default | Meaning |
|---|---|---|
GATEWAY_SETTING_ENVIRONMENT |
required | testnet, or mainnet for real money. Never defaulted |
GATEWAY_SETTING_CURRENCY |
USDC |
The only supported currency |
GATEWAY_SETTING_STOP_TRIGGER |
last_price |
Price a stop fires on: last_price, mark_price or index_price |
GATEWAY_SETTING_DEPTH_RETENTION_DAYS |
0 |
UTC days of recorded depth kept besides today (see Depth); 0 keeps everything |
GATEWAY_SETTING_OPEN_INTEREST_RETENTION_DAYS |
0 |
The same for recorded open interest (see Open interest) |
GATEWAY_LOGIN, GATEWAY_SECRET |
required | The API key's client id and secret |
Venue behaviour¶
Measured on testnet; recorded responses are in src/test/resources/fixtures.
- Order labels. The client order id is sent as Deribit's
label. Deribit doesn't enforce unique labels, so only the gateway decides when to resend. - Closed orders. Deribit forgets a closed order's label after about an hour, but keeps its trades, so the gateway recovers lost orders from fills.
- Future sizes. A future position's
sizeis in dollars; the quantity issize_currency. - Stops. A triggered stop becomes a new order under the same label. A stop already past its trigger is refused. Market and stop orders accept only GTC or DAY.
- Market remainders. Deribit fills a market order (or a fired stop-market) only inside its price band
and turns the rest into a GTC
limitat the band edge (original_order_type: market), which can fill minutes later far from the market: on 2026-10-05 the 20 left of a 50 SOL buy rested at 133.712, about 10% above the mark, and filled seven minutes later. IOC is refused on these orders, so the adapter cancels such a remainder by its order id as soon as it sees it (pushed, answered to a place, or read), logs a warning, and reports the order with its sent type:cancelledwith what filled, orfilledif the remainder filled first (wire spec: a market order never rests). A pushed remainder is not reported; its cancel goes out without waiting and Deribit's next push carries the result, and a lost cancel is retried by the next read (the host reconciles every minute). - Klines. Responses are silently cut at 5001 rows, so bars are fetched in chunks.
- Settlements. Expiries are read from
private/get_settlement_history_by_currency: adeliveryrow when a future expires, anexerciserow when an option does (out of the money too), both at 08:00 UTC and carrying the delivery price asindex_price(the same figurepublic/get_delivery_pricespublishes for the day). A future settles at it; an option at its intrinsic value from it, because anexerciserow'smark_priceis 0 even deep in the money.settlementrows, the daily session settlement every position gets, are not expiries and are left out. The history takes no window, only the newest instant to start from (inclusive), and pages newest first by a continuation ("none"on the last page);type=exerciseis refused (bad_argument), so every type is read. The delivery fee is thecommissionof the transaction log'sexpiry(option) ordelivery(future) row at the same instant, reported asdelivery_feewhen non-zero (0 for the testnet TRX call that expired on 2026-10-02). Expired contracts are looked up one by one (public/get_instrumentkeeps them) for their strike. - Listing. An order on a code the account's USDC listing does not hold (a coin-margined contract such
as
BTC-PERPETUAL, a spot pair) is refused before it reaches Deribit: every read is scoped to the currency, so such an order would be traded outside them. The public ticker link is reported as the quote feed, so a dropped ticker socket makes quotes stale instead of re-sending the last price. - Inactive contracts. Deribit lists contracts whose
stateisinactive(is_active: false; 96 of 3148 USDC options on testnet on 2026-10-05, e.g.AVAX_USDC-6OCT26-12-C): their ticker never pushes and they take no orders. They are left out of the listing and of a root's options, but still found by name, so one held settles. - Funding rates.
public/get_funding_rate_historygives one row per hour:interest_1h, the rate accrued over the hour ending attimestamp, andindex_priceat its end (rates are JSON numbers such as4.18e-05, read from their text). A call answers at most about 740 hours, the newest, without saying it cut the rest, so rates are fetched in 720-hour spans. Testnet's history reaches back to at least 2024 for BTC_USDC-PERPETUAL. - Funding. Deribit accrues funding continuously and realizes it into the transaction log
(
private/get_transaction_log) asinterest_plon a perpetual's rows, positive when the account gained: at every fill that changes the position (the close included) and at the 08:00 UTCsettlement(recorded on testnet 2026-10-05,transaction-log-funding.json). The adapter reports each non-zero row as a funding record (tx-<id>, the charge being-interest_pl); asettlementrow'spositionis the one charged on. Deribit pushes no funding, so the gateway reconciles it every minute. - Mark prices. Deribit keeps no mark or index history of futures or perpetuals:
public/get_mark_price_historyanswers[]for them (it serves second-by-second marks of only the options in its volatility index, and never the index). Every public trade, though, carries themark_priceandindex_priceof its instant, so/v1/marksserves, for each window, the last trade's (a window without a trade has no sample).get_last_trades_by_instrument_and_timeincludes both ends, answers in time order (trades of one millisecond in no particular sequence order) and at most 1000 trades a call (countabove is refused,value is too high), withhas_morewhen it cut the rest.get_last_trades_by_instrument(bystart_seq/end_seq) is not used: onhistory.deribit.comit answers the trades between the times of its two ends, so a page holds numbers outside the range and, cut at its count, misses some inside it (recorded intrades-history-mainnet-slice.json). The adapter reads the range's first and last trade, then either each window's last trade or every trade a page at a time by time (each page starting at the millisecond the last ended in), whichever costs fewer calls: at most a page's windows plus two, about 0.2 s a call measured from Europe. A millisecond holding 1000 trades cannot be paged and fails the request. - Option marks. Every option ticker carries
mark_ivandunderlying_price(its expiry's forward), so each option quote carries both and the adapter declaresOPTION_MARKS. The ticker'sgreeksare Black-76 on that mark IV and forward at rate 0 and a 365-day year (vega per volatility point, theta per day) to within 0.05%, published to 5 decimals, which leaves a BTC option's gamma one digit (ticker-option-greeks.json: gamma1e-5where the inputs give 0.0000059); they are not carried, and qkt prices the Greeks from the mark IV. The book summary hasmark_ivandunderlying_pricebut no Greeks (book-summary-option.json). No host keeps a history of either:public/get_mark_price_historyanswers[]for a linear option (mark-price-history-linear-option.json), and a trade carries its owniv, not the mark IV. - Mark history depth.
test.deribit.comandwww.deribit.comanswer trades by time for only about the last 24 hours (none 26 hours back, measured 2026-10-04). Mainnet's whole history, current to the second and including expired futures and options, is onhistory.deribit.comin the same shape, so a mainnet adapter reads marks there. Testnet has no history host (history.test.deribit.comdoes not answer): a testnet gateway serves marks of the last day only; fetch longer histories through a mainnet or paper gateway. - Open interest. Deribit publishes no open-interest history:
public/get_open_interest_historyandpublic/get_open_interestareMethod not found(-32601) on testnet and mainnet, andpublic/get_tradingview_chart_datacarries prices, volume and cost only (probed 2026-10-04). The present figure is the ticker'sopen_interest(also inpublic/get_book_summary_by_*), at the ticker'stimestamp, in the contract's amount unit: the base coin on a USDC-linear contract (testnet BTC_USDC-PERPETUAL 23307.7328, SOL_USDC-PERPETUAL 376285.029), USD on an inverse one. So the adapter records it: a read of/v1/open-interestwhose window reaches the present (within a minute) first takes the ticker's figure, then serves what was recorded, kept one file a UTC day inopen-interest/<code>/<yyyy-MM-dd>.csvin the state volume across restarts. A series starts when the gateway first read it and has one figure per read (qkt's live poll reads every minute); time nothing read it is a gap, never filled in. A gateway before the day files kept oneopen-interest/<code>.csv; the first read of the code splits it into day files and deletes it, every figure kept. - Depth. Deribit publishes no order-book history (
public/get_order_book_historyisMethod not foundon testnet and mainnet, probed 2026-10-05).public/get_order_bookwithdepth=10answers the book as it stands:bidsbest first,asksbest first, each[price, amount]in the amount unit (the base coin on a USDC-linear contract), stamped withtimestamp; prices may come in exponent form (8.62e4, read exactly), and a side with no orders is[](order-book-option-no-bids.json). So the adapter records it, as it does open interest: a read of/v1/depthwhose window reaches the present (within a minute) first takes the book, then serves what was recorded, kept one file a UTC day indepth/<code>/<yyyy-MM-dd>.csvin the state volume across restarts (about 270 bytes a snapshot of BTC_USDC-PERPETUAL: at qkt's 10-second poll, some 2.3 MB a contract a day). A series has one snapshot per read and starts when the gateway first read it. The read is one REST call, about 0.17 s from Europe. Thebook.<code>.none.10.100mschannel is not used: it pushes up to ten full snapshots a second (2.8 a second, 534 bytes each, on testnet BTC_USDC-PERPETUAL, measured 2026-10-05) to serve one read every 10 seconds, and would need its own staleness guard for a dropped socket. -
Retention of the recordings. Depth and open interest are recorded only by this gateway, so what it deletes is lost for good unless
qkt fetch --depth/qkt fetch --open-interestcopied it first. By default (0) nothing is deleted. WithGATEWAY_SETTING_DEPTH_RETENTION_DAYSorGATEWAY_SETTING_OPEN_INTEREST_RETENTION_DAYSset to N, the gateway keeps today's file and the N days before it, and deletes every older day file, of every contract, when it connects and then once a UTC day (on the 10-minute refresh timer; each deletion is logged). A read of a range that reaches before the window answers what remains. Fetch into qkt more often than every N days. A value that is not a whole number of days fails the start, naming the setting. -
Tape and liquidations.
/v1/tradesand/v1/liquidationsread the sameget_last_trades_by_instrument_and_timepages as marks, on the same hosts (mainnet's history host; testnet's own, about a day deep). A print'sdirectionis its taker's, the aggressor side;amountis in coins, the order quantity. A print that liquidated a position carriesliquidation:Tthe taker was liquidated (the liquidation order is on the print's own side),Mthe maker (its resting order on the other side),MTboth (Deribit's documented value, served as one liquidation of each side; none was seen in about six days of mainnet USDC futures history scanned on 2026-10-05, 2,314 liquidation prints, allTorM). A page cut at its count may end inside a millisecond (tape-liquidation-taker.jsonholds one of the eleven prints of 1791096884055 ms), so each page starts at the millisecond the last one ended in. Liquidations are not indexed: finding them reads the whole tape of the range, a call per 1000 prints. Measured 2026-10-05 over a day: mainnet BTC_USDC-PERPETUAL about 150,000 prints, testnet about 15,000; mainnet USDC futures about 270 liquidation prints across all contracts, testnet none.
The full list of API calls is in design.md §10.
Contract suite¶
Runs against testnet when a testnet key is set, and is skipped without one:
DERIBIT_CLIENT_ID=… DERIBIT_CLIENT_SECRET=… ./gradlew :adapter-deribit:test --tests '*ContractTest' --rerun
It places, fills and cancels real testnet orders at the smallest size. Its market-remainder check buys one
step more than every ask inside the price band of a thin stock perpetual (AAPL_USDC-PERPETUAL, then
AMZN, AMD; at most $50), checks nothing of it is left working, and sells back what filled; it is
a pass without trading when none of them is that thin.