Options analytics and aggregation functions.
Pure functions for analyzing option chains, calculating open interest
distributions, and identifying key market levels. Works with plain maps
containing option data (e.g., :open_interest, :raw with greeks).
time_to_expiry/2 and session_phase/2 are deterministic given a reference
DateTime; the one-argument forms read DateTime.utc_now/0.
All functions that need strike/expiry data parse it from symbols using
ZenQuant.Options.Deribit.parse_option/1.
Example
chain = %{
"BTC-31JAN26-84000-C" => %{open_interest: 100.0, raw: %{"gamma" => 0.00001}},
"BTC-31JAN26-84000-P" => %{open_interest: 50.0, raw: %{"gamma" => 0.00001}}
}
ZenQuant.Options.oi_by_strike(chain)
# => %{84000.0 => 150.0}
ZenQuant.Options.put_call_ratio(chain)
# => {:ok, 0.5}API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
breakeven_move | 2 | Calculate percentage move to breakeven for an option. | option: value, spot: value |
aggregate_oi | 1 | Aggregate open interest across multiple option chains. | chains: exchange_data |
pin_risk | 3 | Assess pin risk for strikes near spot price. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
moneyness_skew | 3 | Build per-expiry moneyness skew observations from an option chain. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
atm_iv_term_structure | 3 | Build per-expiry ATM IV observations from an option chain. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
atm_iv | 2 | Extract ATM implied volatility from an option chain. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
expected_range | 3 | Calculate expected 1-sigma price range for given IV and time horizon. Scalar inputs only; derive chain IV with atm_iv/2, which rejects mixed underlyings or settlement regimes with {:error, :heterogeneous_chain}. | spot: value, iv: value, hours_to_expiry: value |
theta_per_hour | 1 | Calculate hourly theta decay rate for an option. | option: value |
greeks_sum | 1 | Sum Greeks across all options in a chain. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data |
hot_zone | 3 | Detect hot zone / pin risk at expiry. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
pin_magnets | 3 | Find high-gamma strikes near spot (pin magnets). Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
zero_gamma | 3 | Find aggregate dealer zero-gamma spot levels under sticky-strike repricing. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
gamma_flip | 2 | Find the first per-strike GEX sign change scanning strikes upward. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
gex_by_strike | 2 | Calculate gamma exposure (GEX) by strike. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, spot: value |
in_play? | 3 | Check if a strike is within threshold of spot price. | strike: value, spot: value, threshold_pct: value |
strike_distance | 2 | Calculate percentage distance from spot to strike. | strike: value, spot: value |
largest_positions | 2 | Get the N largest positions by open interest. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data, n: value |
session_phase | 2 | Determine trading session phase based on time to expiry. | expiry: value, now: value |
put_call_ratio | 1 | Calculate put/call ratio from open interest. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data |
max_pain | 1 | Calculate max pain strike where most options expire worthless. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data |
filter_by_dte | 3 | Filter option chain by maximum days to expiry. | chain: exchange_data, max_dte: value, now_dt: value |
time_to_expiry | 2 | Calculate time remaining until expiry. | expiry: value, now: value |
oi_by_expiry | 1 | Aggregate open interest by expiry date. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data |
oi_by_strike | 1 | Aggregate open interest by strike price. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering. | chain: exchange_data |
Summary
Types
Aggregate zero-gamma validation or solver failure.
A leg excluded from zero-gamma analysis by explicit caller policy.
Aggregate dealer zero-gamma result under sticky-strike repricing.
Functions
Aggregate open interest across multiple option chains.
Extract ATM implied volatility from an option chain. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Per-expiry ATM implied volatility term structure from a chain.
Calculate percentage move to breakeven for an option.
Calculate expected 1-sigma price range for given IV and time horizon. Scalar inputs only; derive chain IV with atm_iv/2, which rejects mixed underlyings or settlement regimes with {:error, :heterogeneous_chain}.
Filter option chain by maximum days to expiry.
Returns the first per-strike GEX sign change while scanning strikes upward.
Calculate gamma exposure (GEX) by strike. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Sum Greeks across all options in a chain. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Detect hot zone / pin risk at expiry. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Check if a strike is within threshold of spot price.
Get the N largest positions by open interest. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Calculate max pain strike where most options expire worthless. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Per-expiry moneyness skew from a chain at caller-supplied moneyness levels.
Aggregate open interest by expiry date. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Aggregate open interest by strike price. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Find high-gamma strikes near spot (pin magnets). Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Assess pin risk for strikes near spot price. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Calculate put/call ratio from open interest. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Determine trading session phase based on time to expiry.
Calculate percentage distance from spot to strike.
Calculate hourly theta decay rate for an option.
Calculate time remaining until expiry.
Solves for aggregate dealer GEX sign changes as spot varies.
Types
@type expiry() :: Date.t()
@type strike() :: float()
@type zero_gamma_error_reason() :: {:invalid_zero_gamma_input, :chain | :spot | :options} | {:invalid_zero_gamma_option, :search_range | :relative_tolerance | :min_gross_gex | :on_unpriceable} | {:unpriceable_leg, zero_gamma_omission()} | :heterogeneous_chain | :no_gamma_signal | :no_zero_gamma_found | :no_zero_gamma_in_populated_range | {:zero_gamma_did_not_converge, map()}
Aggregate zero-gamma validation or solver failure.
@type zero_gamma_omission() :: %{ symbol: term(), reason: :missing_iv | :degenerate_input }
A leg excluded from zero-gamma analysis by explicit caller policy.
@type zero_gamma_result() :: %{ level: float(), roots: [float()], current_gex: float(), current_gross_gex: float(), gex_at_level: float(), gross_gex_at_level: float(), searched_range: {float(), float()}, populated_strike_range: {float(), float()}, relative_tolerance: float(), omitted: [zero_gamma_omission()] }
Aggregate dealer zero-gamma result under sticky-strike repricing.
Functions
@spec aggregate_oi([option_chain()]) :: %{ calls: float(), puts: float(), total: float(), ratio: float() | nil }
Aggregate open interest across multiple option chains.
Parameters
chains- List of option chain maps (exchange_data)
Returns
Map with :calls, :puts, :total, :ratio (puts/calls) (map)
Example
%{calls: 1800.0, total: 3000.0, puts: 1200.0, ratio: 0.667}# descripex:contract
%{
params: %{
chains: %{
scope: %{
reason: "Takes a LIST of chains, so summing across them is the caller's explicit request.",
mode: :cross_chain
},
description: "List of option chain maps",
source: "multiple Options.Deribit.chain() calls",
kind: :exchange_data
}
},
returns: %{
type: :map,
description: "Map with :calls, :puts, :total, :ratio (puts/calls)"
},
returns_example: %{calls: 1800.0, total: 3000.0, puts: 1200.0, ratio: 0.667}
}
@spec atm_iv(option_chain(), number()) :: {:ok, float()} | {:error, :no_iv_data} | {:error, :heterogeneous_chain}
Extract ATM implied volatility from an option chain. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain with IV data in :raw (exchange_data)spot- Current spot price (value)
Returns
{:ok, iv_percentage} or {:error, :no_iv_data} (tuple)
Example
{:ok, 46.3}Errors
:heterogeneous_chain:no_iv_data
# descripex:contract
%{
params: %{
spot: %{description: "Current spot price", kind: :value},
chain: %{
scope: :single,
description: "Option chain with IV data in :raw",
source: "Options.Deribit.chain(Bourse, exchange, enrich: :greeks) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain, :no_iv_data],
returns: %{
type: :tuple,
description: "{:ok, iv_percentage} or {:error, :no_iv_data}"
},
returns_example: {:ok, 46.3}
}
@spec atm_iv_term_structure(option_chain() | term(), number() | term(), keyword()) :: {:ok, [map()]} | {:error, atom()}
Per-expiry ATM implied volatility term structure from a chain.
Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Groups legs by expiry, selects ATM strikes relative to spot, and returns
one observation per expiry. Observations carry the
Options.Skew.term_structure/1 contract (:expiry, :tenor_days,
:measure, :units, :value) plus :open_interest and an explicit
:status — feed the list straight into Skew.term_structure/1 without
reshaping.
ATM selection (:window_pct)
0(default) — the single strike nearestspot(ties break to the lower strike). Call and put IVs at that strike are averaged.> 0— arithmetic mean of per-strike IVs for every strike within±window_pctpercent of spot. An empty window yieldsstatus: :no_ivandvalue: nilfor that expiry (no silent nearest-strike fallback).
Missing IV
Every expiry present in the chain appears in the result. When no readable IV
is available under the selection policy, value is nil and
status: :no_iv — the expiry is never dropped and never given a fabricated
numeric IV.
:tenor_days is max(0, Date.diff(expiry, as_of_date)) from the required
caller-supplied :as_of (Date or DateTime); the function never reads
system time.
Calculate percentage move to breakeven for an option.
Parameters
option- Option map with :symbol, :mark_price, :underlying_price (value)spot- Current spot price (value)
Returns
{:ok, percentage} or {:error, :missing_data} (tuple)
Example
{:ok, 4.2}Errors
:missing_data
# descripex:contract
%{
params: %{
option: %{
description: "Option map with :symbol, :mark_price, :underlying_price",
kind: :value
},
spot: %{description: "Current spot price", kind: :value}
},
errors: [:missing_data],
returns: %{
type: :tuple,
description: "{:ok, percentage} or {:error, :missing_data}"
},
returns_example: {:ok, 4.2}
}
@spec expected_range(number(), number(), number()) :: %{ lower: float(), upper: float(), move_pct: float() }
Calculate expected 1-sigma price range for given IV and time horizon. Scalar inputs only; derive chain IV with atm_iv/2, which rejects mixed underlyings or settlement regimes with {:error, :heterogeneous_chain}.
Parameters
spot- Current spot price (value)iv- Annualized IV as percentage (e.g., 46.3) (value)hours_to_expiry- Hours until expiry (value)
Returns
Map with :lower, :upper, :move_pct (map)
Example
%{upper: 101.0, lower: 99.0, move_pct: 1.0}# descripex:contract
%{
params: %{
spot: %{description: "Current spot price", kind: :value},
iv: %{description: "Annualized IV as percentage (e.g., 46.3)", kind: :value},
hours_to_expiry: %{description: "Hours until expiry", kind: :value}
},
returns: %{type: :map, description: "Map with :lower, :upper, :move_pct"},
returns_example: %{upper: 101.0, lower: 99.0, move_pct: 1.0}
}
@spec filter_by_dte(option_chain(), pos_integer(), DateTime.t()) :: option_chain()
Filter option chain by maximum days to expiry.
Parameters
chain- Option chain map (exchange_data)max_dte- Maximum days to expiry (value)now_dt- Current time (for testing) (default:"DateTime.utc_now()", value)
Returns
Filtered chain with only options within max_dte (map)
Example
%{"BTC-31JAN26-84000-C" => %{open_interest: 1200.0}}# descripex:contract
%{
params: %{
chain: %{
scope: %{
reason: "Returns a filtered chain without aggregating across legs.",
mode: :per_leg
},
description: "Option chain map",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
},
max_dte: %{description: "Maximum days to expiry", kind: :value},
now_dt: %{
default: "DateTime.utc_now()",
description: "Current time (for testing)",
kind: :value
}
},
returns: %{
type: :map,
description: "Filtered chain with only options within max_dte"
},
returns_example: %{"BTC-31JAN26-84000-C" => %{open_interest: 1200.0}}
}
@spec gamma_flip(option_chain(), number()) :: {:ok, strike()} | {:error, :no_flip_found} | {:error, :heterogeneous_chain}
Returns the first per-strike GEX sign change while scanning strikes upward.
Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
This is the put/call open-interest boundary evaluated at the supplied spot.
It is not the aggregate zero-gamma level; use zero_gamma/3 for that.
@spec gex_by_strike(option_chain(), number()) :: %{required(strike()) => float()} | {:error, :heterogeneous_chain}
Calculate gamma exposure (GEX) by strike. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain with greeks in :raw data (exchange_data)spot- Current spot price (value)
Returns
Map of %{strike => gex_value} (map)
Example
%{84000.0 => 141120.0}Errors
:heterogeneous_chain
Composes With
gamma_flip
# descripex:contract
%{
params: %{
spot: %{description: "Current spot price", kind: :value},
chain: %{
scope: :single,
description: "Option chain with greeks in :raw data",
source: "Options.Deribit.chain(Bourse, exchange, enrich: :greeks) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain],
returns: %{type: :map, description: "Map of %{strike => gex_value}"},
returns_example: %{84000.0 => 141120.0},
composes_with: [:gamma_flip]
}
@spec greeks_sum(option_chain()) :: %{delta: float(), gamma: float(), theta: float(), vega: float()} | {:error, :heterogeneous_chain}
Sum Greeks across all options in a chain. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain with greeks in :raw data (exchange_data)
Returns
Map with aggregated :delta, :gamma, :theta, :vega (map)
Example
%{delta: 1.0, gamma: 1.0, theta: 1.0, vega: 1.0}Errors
:heterogeneous_chain
# descripex:contract
%{
params: %{
chain: %{
scope: :single,
description: "Option chain with greeks in :raw data",
source: "Options.Deribit.chain(Bourse, exchange, enrich: :greeks) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain],
returns: %{
type: :map,
description: "Map with aggregated :delta, :gamma, :theta, :vega"
},
returns_example: %{delta: 1.0, gamma: 1.0, theta: 1.0, vega: 1.0}
}
@spec hot_zone(option_chain(), number(), keyword()) :: {:hot, strike(), float()} | :clear | {:error, :heterogeneous_chain}
Detect hot zone / pin risk at expiry. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain map (exchange_data)spot- Current spot price (value)
Options
expiry- Expiry date to check (default:"Date.utc_today()")threshold_pct- Max distance for hot zone (default:1.0)
Returns
{:hot, strike, oi} or :clear (tuple)
Example
{:hot, 84000.0, 1250.0}Errors
:heterogeneous_chain
# descripex:contract
%{
opts: %{
expiry: %{
default: "Date.utc_today()",
type: :date,
description: "Expiry date to check"
},
threshold_pct: %{
default: 1.0,
type: :float,
description: "Max distance for hot zone"
}
},
params: %{
spot: %{description: "Current spot price", kind: :value},
chain: %{
scope: :single,
description: "Option chain map",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain],
returns: %{type: :tuple, description: "{:hot, strike, oi} or :clear"},
returns_example: {:hot, 84000.0, 1250.0}
}
Check if a strike is within threshold of spot price.
Parameters
strike- Strike price (value)spot- Current spot price (value)threshold_pct- Maximum distance percentage (value)
Returns
true if abs(distance) <= threshold (boolean)
Example
true# descripex:contract
%{
params: %{
spot: %{description: "Current spot price", kind: :value},
threshold_pct: %{description: "Maximum distance percentage", kind: :value},
strike: %{description: "Strike price", kind: :value}
},
returns: %{type: :boolean, description: "true if abs(distance) <= threshold"},
returns_example: true
}
@spec largest_positions(option_chain(), pos_integer()) :: [{String.t(), map()}] | {:error, :heterogeneous_chain}
Get the N largest positions by open interest. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain map (exchange_data)n- Number of positions to return (value)
Returns
List of {symbol, option_map} sorted by OI desc (list)
Example
[{"BTC-31JAN26-84000-C", %{open_interest: 1200.0}}]Errors
:heterogeneous_chain
# descripex:contract
%{
params: %{
n: %{description: "Number of positions to return", kind: :value},
chain: %{
scope: :single,
description: "Option chain map",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain],
returns: %{
type: :list,
description: "List of {symbol, option_map} sorted by OI desc"
},
returns_example: [{"BTC-31JAN26-84000-C", %{open_interest: 1200.0}}]
}
@spec max_pain(option_chain()) :: {:ok, strike()} | {:error, :empty_chain} | {:error, :heterogeneous_chain}
Calculate max pain strike where most options expire worthless. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain map (exchange_data)
Returns
{:ok, strike} or {:error, :empty_chain} (tuple)
Example
{:ok, 84000.0}Errors
:heterogeneous_chain:empty_chain
# descripex:contract
%{
params: %{
chain: %{
scope: :single,
description: "Option chain map",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain, :empty_chain],
returns: %{
type: :tuple,
description: "{:ok, strike} or {:error, :empty_chain}"
},
returns_example: {:ok, 84000.0}
}
@spec moneyness_skew(option_chain() | term(), number() | term(), keyword()) :: {:ok, [map()]} | {:error, atom()}
Per-expiry moneyness skew from a chain at caller-supplied moneyness levels.
Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
For each expiry, selects the strike(s) nearest spot * low and
spot * high (defaults 0.9 and 1.1), reads IV with the same policy as
atm_iv/2, and returns value = iv_low - iv_high (percentage points).
Observations match the Options.Skew.term_structure/1 contract and can be
passed through without reshaping. Missing wings are never dropped: value
is nil and :status is :no_iv (unreadable IV) or :no_strike (no
strike within selection policy / :max_distance_pct).
ATM/wing selection uses the same :window_pct rules as
atm_iv_term_structure/3. Optional :max_distance_pct rejects a nearest
strike farther than that percent of spot from the target moneyness level.
@spec oi_by_expiry(option_chain()) :: %{required(expiry()) => float()} | {:error, :heterogeneous_chain}
Aggregate open interest by expiry date. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain map %{symbol => option_map} (exchange_data)
Returns
Map of %{Date => total_oi} (map)
Example
%{~D[2026-01-31] => 1200.0}Errors
:heterogeneous_chain
# descripex:contract
%{
params: %{
chain: %{
scope: :single,
description: "Option chain map %{symbol => option_map}",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain],
returns: %{type: :map, description: "Map of %{Date => total_oi}"},
returns_example: %{~D[2026-01-31] => 1200.0}
}
@spec oi_by_strike(option_chain()) :: %{required(strike()) => float()} | {:error, :heterogeneous_chain}
Aggregate open interest by strike price. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain map %{symbol => option_map} (exchange_data)
Returns
Map of %{strike => total_oi} (map)
Example
%{84000.0 => 1200.0}Errors
:heterogeneous_chain
Composes With
max_pain
# descripex:contract
%{
params: %{
chain: %{
scope: :single,
description: "Option chain map %{symbol => option_map}",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain],
returns: %{type: :map, description: "Map of %{strike => total_oi}"},
returns_example: %{84000.0 => 1200.0},
composes_with: [:max_pain]
}
@spec pin_magnets(option_chain(), number(), keyword()) :: [{strike(), float()}] | {:error, :heterogeneous_chain}
Find high-gamma strikes near spot (pin magnets). Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain map (exchange_data)spot- Current spot price (value)
Options
threshold_pct- Max distance from spot (default:5.0)min_oi- Minimum OI to consider (default:0)
Returns
List of {strike, oi} sorted by OI desc (list)
Example
[{84000.0, 1250.0}]Errors
:heterogeneous_chain
# descripex:contract
%{
opts: %{
threshold_pct: %{
default: 5.0,
type: :float,
description: "Max distance from spot"
},
min_oi: %{default: 0, type: :number, description: "Minimum OI to consider"}
},
params: %{
spot: %{description: "Current spot price", kind: :value},
chain: %{
scope: :single,
description: "Option chain map",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain],
returns: %{type: :list, description: "List of {strike, oi} sorted by OI desc"},
returns_example: [{84000.0, 1250.0}]
}
@spec pin_risk(option_chain(), number(), keyword()) :: [map()] | {:error, :heterogeneous_chain}
Assess pin risk for strikes near spot price. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain map (exchange_data)spot- Current spot price (value)
Options
threshold_pct- Max distance from spot (default:5.0)min_oi- Minimum OI to include (default:0)
Returns
List of %{strike, oi, distance_pct, risk: :high|:medium|:low} (list)
Example
[%{strike: 84000.0, oi: 1250.0, distance_pct: 0.6, risk: :high}]Errors
:heterogeneous_chain
# descripex:contract
%{
opts: %{
threshold_pct: %{
default: 5.0,
type: :float,
description: "Max distance from spot"
},
min_oi: %{default: 0, type: :number, description: "Minimum OI to include"}
},
params: %{
spot: %{description: "Current spot price", kind: :value},
chain: %{
scope: :single,
description: "Option chain map",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain],
returns: %{
type: :list,
description: "List of %{strike, oi, distance_pct, risk: :high|:medium|:low}"
},
returns_example: [
%{strike: 84000.0, oi: 1250.0, distance_pct: 0.6, risk: :high}
]
}
@spec put_call_ratio(option_chain()) :: {:ok, float()} | {:error, :no_calls} | {:error, :heterogeneous_chain}
Calculate put/call ratio from open interest. Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Parameters
chain- Option chain map (exchange_data)
Returns
{:ok, float()} put/call ratio, or {:error, :no_calls} if no call OI (result_tuple)
Example
{:ok, 0.53}Errors
:heterogeneous_chain:no_calls
# descripex:contract
%{
params: %{
chain: %{
scope: :single,
description: "Option chain map",
source: "Options.Deribit.chain(Bourse, exchange) :chain",
kind: :exchange_data
}
},
errors: [:heterogeneous_chain, :no_calls],
returns: %{
type: :result_tuple,
description: "{:ok, float()} put/call ratio, or {:error, :no_calls} if no call OI"
},
returns_example: {:ok, 0.53}
}
@spec session_phase(Date.t() | DateTime.t(), DateTime.t()) :: :early | :final_hour | :last_15min | :expired
Determine trading session phase based on time to expiry.
Parameters
expiry- Expiry Date (assumes 08:00 UTC) or DateTime (value)now- Reference DateTime for the session-phase thresholds. Omit to read the wall clock. (default:"DateTime.utc_now()", value)
Returns
:early | :final_hour | :last_15min | :expired (atom)
Example
:final_hour# descripex:contract
%{
params: %{
now: %{
default: "DateTime.utc_now()",
description: "Reference DateTime for the session-phase thresholds. Omit to read the wall clock.",
kind: :value
},
expiry: %{
description: "Expiry Date (assumes 08:00 UTC) or DateTime",
kind: :value
}
},
returns: %{
type: :atom,
description: ":early | :final_hour | :last_15min | :expired"
},
returns_example: :final_hour
}
Calculate percentage distance from spot to strike.
Parameters
strike- Strike price (value)spot- Current spot price (value)
Returns
Percentage distance (positive = strike above spot) (float)
Example
4.761904761904762# descripex:contract
%{
params: %{
spot: %{description: "Current spot price", kind: :value},
strike: %{description: "Strike price", kind: :value}
},
returns: %{
type: :float,
description: "Percentage distance (positive = strike above spot)"
},
returns_example: 4.761904761904762
}
Calculate hourly theta decay rate for an option.
Parameters
option- Option map with :raw containing theta (value)
Returns
Per-hour theta decay, or nil if theta unavailable (float)
Example
-0.5208333333333334# descripex:contract
%{
params: %{
option: %{
description: "Option map with :raw containing theta",
kind: :value
}
},
returns: %{
type: :float,
description: "Per-hour theta decay, or nil if theta unavailable"
},
returns_example: -0.5208333333333334
}
@spec time_to_expiry(Date.t() | DateTime.t(), DateTime.t()) :: %{ hours: float(), minutes: float() }
Calculate time remaining until expiry.
Parameters
expiry- Expiry Date (assumes 08:00 UTC) or DateTime (value)now- Reference DateTime for the remaining-time calculation. Omit to read the wall clock. (default:"DateTime.utc_now()", value)
Returns
Map with :hours and :minutes remaining (map)
Example
%{hours: 1.0, minutes: 60.0}# descripex:contract
%{
params: %{
now: %{
default: "DateTime.utc_now()",
description: "Reference DateTime for the remaining-time calculation. Omit to read the wall clock.",
kind: :value
},
expiry: %{
description: "Expiry Date (assumes 08:00 UTC) or DateTime",
kind: :value
}
},
returns: %{type: :map, description: "Map with :hours and :minutes remaining"},
returns_example: %{hours: 1.0, minutes: 60.0}
}
@spec zero_gamma(option_chain() | term(), number() | term(), keyword() | term()) :: {:ok, zero_gamma_result()} | {:error, zero_gamma_error_reason()}
Solves for aggregate dealer GEX sign changes as spot varies.
Requires a single underlying and settlement regime across all parseable legs; returns {:error, :heterogeneous_chain} otherwise, before filtering.
Each candidate replaces only :spot in every enriched leg's retained
:pricing_inputs; IV, time to expiry, rates, and carry remain fixed. This is
the sticky-strike assumption. The default search range is 0.5–1.5 times the
supplied spot and can be replaced with an absolute :search_range tuple.
The range is scanned over 512 equal intervals and
every observed sign-changing bracket is bisected. Roots outside the minimum
and maximum strikes carrying positive open interest are discarded. The
primary :level is the remaining root nearest the supplied spot, while
:roots contains every remaining root in ascending order.
Calls contribute positive dealer GEX and puts negative dealer GEX. A
candidate is zero when abs(net GEX) <= relative_tolerance * gross GEX.
Empty or near-empty gamma mass returns :no_gamma_signal instead of treating
every spot as a root.