DeribitEx.Client (deribit_ex v0.1.0)

View Source

User-friendly client interface for interacting with the Deribit WebSocket API.

This module provides simplified methods for:

  • Connecting and disconnecting
  • Authentication (public/auth, token exchange, token forking, and logout)
  • Utility endpoints (get_time, hello, status, test)
  • Session management (heartbeat, cancel on disconnect)
  • Subscription management for various Deribit channels

Public Utility Endpoints

# Sync clocks with the server
{:ok, timestamp} = Client.get_time(conn)

# Introduce the client to the server
{:ok, server_info} = Client.hello(conn, "my_bot", "1.0.0")

# Check system status
{:ok, status} = Client.status(conn)

# Simple connectivity test
{:ok, result} = Client.test(conn, "echo_me")

Authentication

# Authenticate with credentials from environment variables
{:ok, conn} = Client.authenticate(conn)

# Exchange token for a different subaccount
{:ok, _} = Client.exchange_token(conn, refresh_token, subject_id)

# Create a named session
{:ok, _} = Client.fork_token(conn, refresh_token, "analytics_session")

# Logout
{:ok, conn} = Client.logout(conn)

Subscriptions

# Subscribe to trades for BTC-PERPETUAL
{:ok, sub_id} = Client.subscribe_to_trades(conn, "BTC-PERPETUAL")

# Subscribe to orderbook with depth=10
{:ok, sub_id} = Client.subscribe_to_orderbook(conn, "BTC-PERPETUAL", "100ms", 10)

Summary

Functions

Authenticate with the Deribit API using client credentials.

Connect to the Deribit WebSocket API.

Get the current server time from the TimeSyncService without making a request.

Disable Cancel on Disconnect (COD) for the specified scope.

Disable heartbeat messages from the Deribit server.

Close the WebSocket connection to the Deribit API.

Enable Cancel on Disconnect (COD) for the current session.

Generate a new access token for switching between subaccounts.

Generate a token for a new named session.

Get the current Cancel on Disconnect (COD) configuration.

Fetch Deribit's current server time (ms since epoch).

Send a hello message to introduce the client to Deribit.

Initialize the Deribit connection with a bootstrap sequence.

Send a custom JSON-RPC request to the Deribit API.

Logout and optionally invalidate tokens.

Parse a JSON-RPC response and extract the result or error.

Enable heartbeat messages from the Deribit server.

Get the system status from Deribit.

Subscribe to a Deribit orderbook channel for a specific instrument.

Subscribe to a Deribit ticker channel for a specific instrument.

Subscribe to a Deribit trades channel for a specific instrument.

Subscribe to user's orders for a specific instrument. Requires authentication.

Subscribe to user's trades for a specific instrument. Requires authentication.

Send a test request to Deribit.

Unsubscribe from specific channels.

Unsubscribe from all channels.

Unsubscribe from private channels.

Functions

authenticate(conn, credentials \\ %{}, opts \\ nil)

@spec authenticate(pid(), map(), map() | nil) :: {:ok, any()} | {:error, any()}

Authenticate with the Deribit API using client credentials.

Uses credentials from the environment variables DERIBIT_CLIENT_ID and DERIBIT_CLIENT_SECRET if not provided.

Options

  • :api_key - Deribit API key (optional if in environment)
  • :secret - Deribit API secret (optional if in environment)
  • opts - Additional options passed to the authentication request (e.g., timeout settings)

Examples

# Authenticate using environment variables
{:ok, conn} = Client.authenticate(conn)

# Authenticate with explicit credentials
{:ok, conn} = Client.authenticate(conn, %{
  api_key: "your_api_key",
  secret: "your_secret"
})

connect(opts \\ %{})

@spec connect(map()) :: {:ok, pid()} | {:error, any()}

Connect to the Deribit WebSocket API.

Uses environment-specific endpoints (test.deribit.com for dev/test, www.deribit.com for production) unless overridden.

Options

All WebsockexNova.Client options are supported, plus:

  • :host - Override the default host (optional)
  • :callback_pid - Process to receive WebSocket events (optional)
  • :auth_refresh_threshold - Seconds before token expiration to refresh (defaults to 180s)
    • Deribit tokens usually last 900 seconds (15 minutes)
    • Recommended refresh threshold is 180-300 seconds before expiration
    • Can also be set via DERIBIT_AUTH_REFRESH_THRESHOLD environment variable
    • Valid range: 1-899 seconds
    • Smaller values risk token expiration during high latency periods
    • Larger values cause more frequent token refreshes
    • Value of 180s balances reliability with refresh frequency
  • :rate_limit_mode - Rate limiting strategy (defaults to :normal)
    • :cautious - Strict limits to avoid 429s completely (60 req/sec)
    • :normal - Balanced approach (120 req/sec)
    • :aggressive - Higher throughput (200 req/sec), might get occasional 429s
    • Can also be set via DERIBIT_RATE_LIMIT_MODE environment variable

Rate Limiting Behavior

The connection uses a custom adaptive rate limiter that:

  • Adjusts rate limits dynamically based on Deribit API responses
  • Implements exponential backoff when 429 responses are received
  • Prioritizes critical operations like order cancellation
  • Gradually recovers request rates after backoff periods
  • Emits detailed telemetry for monitoring rate limiting behavior

Examples

# Connect with default options
{:ok, conn} = Client.connect()

# Connect with custom host
{:ok, conn} = Client.connect(%{host: "custom.deribit.com"})

# Connect with callback process
{:ok, conn} = Client.connect(%{callback_pid: self()})

# Connect with custom auth refresh threshold (5 minutes)
{:ok, conn} = Client.connect(%{auth_refresh_threshold: 300})

# Connect with cautious rate limiting
{:ok, conn} = Client.connect(%{rate_limit_mode: :cautious})

current_server_time(conn)

@spec current_server_time(pid()) :: {:ok, integer()} | {:error, any()}

Get the current server time from the TimeSyncService without making a request.

This function returns the estimated server time based on the local time and the time delta maintained by the TimeSyncService. It is useful when you need to know the server time without making an additional request to the server.

Parameters

  • conn - Connection process PID

Returns

  • {:ok, server_time_ms} - The current server time in milliseconds
  • {:error, :no_time_sync} - If time sync service is not running for this connection

Examples

# Get the current estimated server time
{:ok, server_time} = Client.current_server_time(conn)

disable_cancel_on_disconnect(conn, scope \\ "connection", opts \\ nil)

@spec disable_cancel_on_disconnect(pid(), String.t(), map() | nil) ::
  {:ok, String.t()} | {:error, any()}

Disable Cancel on Disconnect (COD) for the specified scope.

Parameters

  • conn - Connection process PID
  • scope - Scope of the COD setting ("connection" or "account", defaults to "connection")
  • opts - Optional request options

Returns

  • {:ok, "ok"} - Success response indicating COD is disabled
  • {:error, reason} - If the request fails

Examples

# Disable COD for current connection
{:ok, _} = Client.disable_cancel_on_disconnect(conn)

# Disable COD for the entire account
{:ok, _} = Client.disable_cancel_on_disconnect(conn, "account")

disable_heartbeat(conn, opts \\ nil)

@spec disable_heartbeat(pid(), map() | nil) :: {:ok, String.t()} | {:error, any()}

Disable heartbeat messages from the Deribit server.

Parameters

  • conn - Connection process PID
  • opts - Optional request options

Returns

  • {:ok, "ok"} - Success response indicating heartbeat is disabled
  • {:error, reason} - If the request fails

Examples

# Disable heartbeat
{:ok, _} = Client.disable_heartbeat(conn)

disconnect(conn, reason \\ :normal)

@spec disconnect(pid(), any()) :: :ok

Close the WebSocket connection to the Deribit API.

Parameters

  • conn - Connection process PID
  • reason - Reason for disconnection (optional)

Examples

# Close with normal reason
:ok = Client.disconnect(conn)

# Close with custom reason
:ok = Client.disconnect(conn, :shutdown)

enable_cancel_on_disconnect(conn, scope \\ "connection", opts \\ nil)

@spec enable_cancel_on_disconnect(pid(), String.t(), map() | nil) ::
  {:ok, String.t()} | {:error, any()}

Enable Cancel on Disconnect (COD) for the current session.

When enabled, all orders created during the connection will be automatically cancelled if the connection is closed or interrupted. This is a safety feature to prevent orphaned orders in case of connection issues.

Parameters

  • conn - Connection process PID
  • scope - Scope of the COD setting ("connection" or "account", defaults to "connection")
  • opts - Optional request options

Returns

  • {:ok, "ok"} - Success response indicating COD is enabled
  • {:error, reason} - If the request fails

Examples

# Enable COD for current connection only
{:ok, _} = Client.enable_cancel_on_disconnect(conn)

# Enable COD for the entire account
{:ok, _} = Client.enable_cancel_on_disconnect(conn, "account")

exchange_token(conn, refresh_token, subject_id, opts \\ nil)

@spec exchange_token(
  pid(),
  String.t() | nil,
  integer() | String.t() | nil,
  map() | nil
) ::
  {:ok, any()} | {:error, any()}

Generate a new access token for switching between subaccounts.

Uses the refresh token to create a new authentication session for a different subject ID.

Parameters

  • conn - Connection process PID
  • refresh_token - Valid refresh token (optional, uses stored token if available)
  • subject_id - ID of the subaccount to switch to
  • opts - Additional options for the request (optional)

Examples

# Exchange token to switch to subaccount with ID 10
{:ok, response} = Client.exchange_token(conn, "refresh_token_value", 10)

# Exchange token using a stored refresh token
{:ok, response} = Client.exchange_token(conn, nil, 10)

fork_token(conn, refresh_token, session_name, opts \\ nil)

@spec fork_token(pid(), String.t() | nil, String.t() | atom() | nil, map() | nil) ::
  {:ok, any()} | {:error, any()}

Generate a token for a new named session.

Uses the refresh token to create a new authentication session with the specified name.

Parameters

  • conn - Connection process PID
  • refresh_token - Valid refresh token (optional, uses stored token if available)
  • session_name - Name for the new session
  • opts - Additional options for the request (optional)

Examples

# Fork token to create a new named session
{:ok, response} = Client.fork_token(conn, "refresh_token_value", "trading_session")

# Fork token using a stored refresh token
{:ok, response} = Client.fork_token(conn, nil, "analytics_session")

get_cancel_on_disconnect(conn, opts \\ nil)

@spec get_cancel_on_disconnect(pid(), map() | nil) :: {:ok, map()} | {:error, any()}

Get the current Cancel on Disconnect (COD) configuration.

Parameters

  • conn - Connection process PID
  • opts - Optional request options

Returns

  • {:ok, %{"scope" => scope, "enabled" => enabled}} - The current COD configuration
  • {:error, reason} - If the request fails

Examples

# Check if COD is enabled for the connection
{:ok, %{"enabled" => true, "scope" => "connection"}} = Client.get_cancel_on_disconnect(conn)

get_time(conn, opts \\ nil)

@spec get_time(pid(), map() | nil) :: {:ok, integer()} | {:error, any()}

Fetch Deribit's current server time (ms since epoch).

Parameters

  • conn – the WebSocket client PID
  • opts – optional JSON-RPC options

Returns

  • {:ok, timestamp} on success
  • {:error, reason} on failure

hello(conn, client_name \\ nil, client_version \\ nil, opts \\ nil)

@spec hello(pid(), String.t() | nil, String.t() | nil, map() | nil) ::
  {:ok, any()} | {:error, any()}

Send a hello message to introduce the client to Deribit.

Used to identify the client to the server and establish the initial session.

Parameters

  • conn - Connection process PID
  • client_name - The name of the client (defaults to "market_maker")
  • client_version - The version of the client (defaults to "1.0.0")
  • opts - Optional request options

Returns

  • {:ok, result} - Success response with server version
  • {:error, reason} - If the request fails

Examples

# Send hello with default client info
{:ok, server_info} = Client.hello(conn)

# Send hello with custom client info
{:ok, server_info} = Client.hello(conn, "my_trading_bot", "2.1.0")

initialize(conn, opts \\ %{})

@spec initialize(pid(), map() | nil) :: {:ok, map()} | {:error, atom(), any()}

Initialize the Deribit connection with a bootstrap sequence.

After connecting to Deribit via WebSocket, this function performs a bootstrap sequence to properly initialize the session:

  1. /public/hello to introduce client name & version
  2. /public/get_time to sync clocks
  3. /public/status to check account status
  4. /public/set_heartbeat to enable heartbeats (minimum 10s)
  5. /private/enable_cancel_on_disconnect with default scope for COD safety (requires authentication first)

The adapter is also set up to automatically handle incoming test_request messages by responding with /public/test.

Parameters

  • conn - Connection process PID
  • opts - Optional bootstrap configuration:
    • :client_name - Custom client name (defaults to "market_maker")
    • :client_version - Custom client version (defaults to "1.0.0")
    • :heartbeat_interval - Heartbeat interval in seconds (defaults to 30, minimum 10)
    • :cod_scope - Scope for cancel-on-disconnect (defaults to "connection")
    • :authenticate - Whether to authenticate before other steps (defaults to true)

Returns

  • {:ok, bootstrap_results} - Success with a map of all bootstrap operation results
  • {:error, step, reason} - First failed operation with the reason

Examples

# Initialize with default settings
{:ok, results} = Client.initialize(conn)

# Custom initialization
{:ok, results} = Client.initialize(conn, %{
  client_name: "trading_bot",
  client_version: "2.0.0",
  heartbeat_interval: 15,
  cod_scope: "account"
})

# Skip authentication
{:ok, results} = Client.initialize(conn, %{authenticate: false})

json_rpc(conn, method, params, opts \\ nil)

@spec json_rpc(pid(), String.t(), map(), map() | nil) ::
  {:ok, any()} | {:error, any()}

Send a custom JSON-RPC request to the Deribit API.

Parameters

  • conn - Connection process PID
  • method - RPC method name (e.g., "public/get_instruments")
  • params - Method parameters (map)
  • opts - Optional request options

Examples

# Get available instruments for BTC
{:ok, response} = Client.json_rpc(conn, "public/get_instruments", %{
  currency: "BTC",
  kind: "future"
})

logout(conn, invalidate_token \\ true, opts \\ nil)

@spec logout(pid(), boolean(), map() | nil) :: {:ok, pid()} | {:error, any()}

Logout and optionally invalidate tokens.

Gracefully closes the session with the Deribit API and optionally invalidates all tokens created in the current session. This is important for secure session management.

This function:

  1. Sends the private/logout RPC request
  2. Cleans up authentication state in the adapter
  3. Automatically closes the WebSocket connection afterwards

The invalidate_token parameter determines whether to invalidate all tokens:

  • When set to true (default), all access and refresh tokens become invalid
  • When set to false, tokens remain valid but the current session ends

Parameters

  • conn - Connection process PID
  • invalidate_token - Whether to invalidate all tokens (default: true)
  • opts - Additional options for the request (optional):
    • :timeout - Request timeout in milliseconds (default: 10000)

Returns

  • {:ok, conn} - On successful logout, even though the connection is closed
  • {:error, reason} - If the logout request fails

Notes

  • The WebSocket connection is automatically closed after logout
  • The adapter state is updated to remove authentication data
  • In high-latency environments, the request may timeout

Examples

# Logout and invalidate all tokens
{:ok, conn} = Client.logout(conn)

# Logout without invalidating tokens
{:ok, conn} = Client.logout(conn, false)

# Logout with custom timeout
{:ok, conn} = Client.logout(conn, true, %{timeout: 30000})

parse_response(response)

@spec parse_response(map()) :: {:ok, any()} | {:error, any()}

Parse a JSON-RPC response and extract the result or error.

This helper function provides consistent handling of JSON-RPC responses, extracting the relevant result or error from the response envelope.

Parameters

  • response - The raw response from a JSON-RPC call

Returns

  • {:ok, result} - On successful responses
  • {:error, error} - On error responses

Examples

iex> Client.parse_response(%{"jsonrpc" => "2.0", "id" => 1, "result" => 123})
{:ok, 123}

iex> Client.parse_response(%{"jsonrpc" => "2.0", "id" => 1, "error" => %{"code" => 10001, "message" => "Error"}})
{:error, %{"code" => 10001, "message" => "Error"}}

set_heartbeat(conn, interval \\ 30, opts \\ nil)

@spec set_heartbeat(pid(), pos_integer(), map() | nil) ::
  {:ok, String.t()} | {:error, any()}

Enable heartbeat messages from the Deribit server.

Heartbeats are used to detect stale connections. When enabled, the server will send test_request messages periodically, and the adapter will automatically respond to them.

Parameters

  • conn - Connection process PID
  • interval - Heartbeat interval in seconds (min: 10, defaults to 30)
  • opts - Optional request options

Returns

  • {:ok, "ok"} - Success response indicating heartbeat is enabled
  • {:error, reason} - If the request fails

Examples

# Enable heartbeat with default 30 second interval
{:ok, _} = Client.set_heartbeat(conn)

# Enable heartbeat with custom interval
{:ok, _} = Client.set_heartbeat(conn, 15)

status(conn, opts \\ nil)

@spec status(pid(), map() | nil) :: {:ok, map()} | {:error, any()}

Get the system status from Deribit.

Returns information about the status of the API, including any service disruptions, scheduled maintenance, or important announcements.

Parameters

  • conn - Connection process PID
  • opts - Optional request options

Returns

  • {:ok, status} - The system status information
  • {:error, reason} - If the request fails

Examples

# Check if the system is operational
{:ok, status} = Client.status(conn)

# Handle possible maintenance mode
case Client.status(conn) do
  {:ok, %{"status" => "ok"}} -> # System is operational
  {:ok, %{"status" => "maintenance"}} -> # System is in maintenance mode
  {:error, reason} -> # Request failed
end

subscribe_to_orderbook(conn, instrument, interval \\ "raw", depth \\ nil, opts \\ nil)

@spec subscribe_to_orderbook(
  pid(),
  String.t(),
  String.t(),
  integer() | nil,
  map() | nil
) ::
  {:ok, any()} | {:error, any()}

Subscribe to a Deribit orderbook channel for a specific instrument.

Parameters

  • conn - Connection process PID
  • instrument - Instrument name (e.g., "BTC-PERPETUAL")
  • interval - Update interval (optional, e.g., "100ms", "raw")
  • depth - Orderbook depth (optional, default is full orderbook)

Example

{:ok, sub_id} = Client.subscribe_to_orderbook(conn, "BTC-PERPETUAL", "100ms", 10)

subscribe_to_ticker(conn, instrument, interval \\ "raw", opts \\ nil)

@spec subscribe_to_ticker(pid(), String.t(), String.t(), map() | nil) ::
  {:ok, any()} | {:error, any()}

Subscribe to a Deribit ticker channel for a specific instrument.

Parameters

  • conn - Connection process PID
  • instrument - Instrument name (e.g., "BTC-PERPETUAL")
  • interval - Update interval (optional, e.g., "100ms", "raw")

Example

{:ok, sub_id} = Client.subscribe_to_ticker(conn, "BTC-PERPETUAL", "100ms")

subscribe_to_trades(conn, instrument, interval \\ "raw", opts \\ nil)

@spec subscribe_to_trades(pid(), String.t(), String.t(), map() | nil) ::
  {:ok, any()} | {:error, any()}

Subscribe to a Deribit trades channel for a specific instrument.

Parameters

  • conn - Connection process PID
  • instrument - Instrument name (e.g., "BTC-PERPETUAL")
  • interval - Update interval (optional, e.g., "100ms", "raw")

Example

{:ok, sub_id} = Client.subscribe_to_trades(conn, "BTC-PERPETUAL", "100ms")

subscribe_to_user_orders(conn, instrument, interval \\ "raw", opts \\ nil)

@spec subscribe_to_user_orders(pid(), String.t(), String.t(), map() | nil) ::
  {:ok, any()} | {:error, any()}

Subscribe to user's orders for a specific instrument. Requires authentication.

Parameters

  • conn - Connection process PID
  • instrument - Instrument name (e.g., "BTC-PERPETUAL")
  • interval - Update interval (optional, defaults to "raw")

Example

{:ok, sub_id} = Client.subscribe_to_user_orders(conn, "BTC-PERPETUAL")

subscribe_to_user_trades(conn, instrument, interval \\ "raw", opts \\ nil)

@spec subscribe_to_user_trades(pid(), String.t(), String.t(), map() | nil) ::
  {:ok, any()} | {:error, any()}

Subscribe to user's trades for a specific instrument. Requires authentication.

Parameters

  • conn - Connection process PID
  • instrument - Instrument name (e.g., "BTC-PERPETUAL")
  • interval - Update interval (optional, defaults to "raw")

Example

{:ok, sub_id} = Client.subscribe_to_user_trades(conn, "BTC-PERPETUAL")

test(conn, expected_result \\ nil, opts \\ nil)

@spec test(pid(), String.t() | nil, map() | nil) :: {:ok, any()} | {:error, any()}

Send a test request to Deribit.

This endpoint is primarily used to respond to test_request messages sent by the server as part of the heartbeat mechanism. It can also be used as a simple connectivity check.

Parameters

  • conn - Connection process PID
  • expected_result - The expected result to be echoed back (optional)
  • opts - Optional request options

Returns

  • {:ok, result} - Success response, echoing back the expected_result if provided
  • {:error, reason} - If the request fails

Examples

# Simple connectivity test
{:ok, _} = Client.test(conn)

# Test with expected result echo
{:ok, "hello"} = Client.test(conn, "hello")

unsubscribe(conn, channels, opts \\ nil)

@spec unsubscribe(pid(), String.t() | [String.t()], map() | nil) ::
  {:ok, map()} | {:error, any()}

Unsubscribe from specific channels.

Parameters

  • conn - Connection process PID
  • channels - Single channel string or list of channels to unsubscribe from
  • opts - Optional request options

Returns

  • {:ok, result} - Success response with list of unsubscribed channels
  • {:error, reason} - If the request fails

Examples

# Unsubscribe from a trades channel
{:ok, _} = Client.unsubscribe(conn, "trades.BTC-PERPETUAL.100ms")

# Unsubscribe from multiple channels at once
{:ok, _} = Client.unsubscribe(conn, [
  "trades.BTC-PERPETUAL.100ms",
  "ticker.BTC-PERPETUAL.raw"
])

unsubscribe_all(conn, opts \\ nil)

@spec unsubscribe_all(pid(), map() | nil) :: {:ok, String.t()} | {:error, any()}

Unsubscribe from all channels.

Parameters

  • conn - Connection process PID
  • opts - Optional request options

Returns

  • {:ok, "ok"} - Success response
  • {:error, reason} - If the request fails

Examples

# Unsubscribe from all active channels
{:ok, _} = Client.unsubscribe_all(conn)

unsubscribe_private(conn, channels, opts \\ nil)

@spec unsubscribe_private(pid(), String.t() | [String.t()], map() | nil) ::
  {:ok, map()} | {:error, any()}

Unsubscribe from private channels.

A convenience function that ensures the private/unsubscribe method is used, which is required for channels that need authentication.

Parameters

  • conn - Connection process PID
  • channels - Single channel string or list of channels to unsubscribe from
  • opts - Optional request options

Returns

  • {:ok, result} - Success response with list of unsubscribed channels
  • {:error, reason} - If the request fails

Examples

# Unsubscribe from a user orders channel
{:ok, _} = Client.unsubscribe_private(conn, "user.orders.BTC-PERPETUAL.raw")

# Unsubscribe from multiple private channels
{:ok, _} = Client.unsubscribe_private(conn, [
  "user.orders.BTC-PERPETUAL.raw",
  "user.trades.BTC-PERPETUAL.raw"
])