For agents and developers using ZenQuant as a library. Whether you're a Claude instance building a dashboard, a trading bot making decisions, or a human exploring the API — start here.
The 10-Second Discovery
ZenQuant.describe()That's it. You'll get all discoverable modules with their purpose, namespace, function count, and whether they have full API annotations. Everything else flows from here.
Progressive Discovery
Three levels of detail, each building on the last:
# Level 1: What modules exist?
ZenQuant.describe()
# => [%{module: ZenQuant.Funding, short_name: :funding, function_count: ..., ...}, ...]
# Level 2: What functions does a module have?
ZenQuant.describe(:funding)
# => [%{name: :annualize, arity: 2, description: "Annualize a per-period...", spec: "..."}, ...]
# Level 3: Full detail for one function
ZenQuant.describe(:funding, :annualize)
# => %{name: :annualize, params: [%{name: :rate, kind: :value, ...}], returns: %{type: :float, ...}, returns_example: 0.1095, ...}Short names are the last segment of the module name, lowercased: ZenQuant.PowerLaw → :power_law, ZenQuant.Options.Deribit → :deribit.
Machine-Readable Access
For runtime agents that need structured data (not formatted docs):
# Per-module hints (all functions)
ZenQuant.Funding.__api__()
# Per-function hints
ZenQuant.Funding.__api__(:annualize)
# Full manifest as map (all modules, all functions)
ZenQuant.Manifest.build()Static export for non-BEAM consumers: mix zen_quant.manifest → api_manifest.json
What Can ZenQuant Do?
Navigate by intent — what question are you trying to answer?
"What's the market doing right now?"
| Module | Short Name | Key Functions |
|---|---|---|
ZenQuant.Execution | :execution | best_price/2 (cross-venue BBO), from_multi/1 (Bourse.Multi bridge), best_for_side/2 |
ZenQuant.Orderflow | :orderflow | cvd/1 (total CVD), cvd_delta/1 (per-trade), vwap/1, imbalance/2 (orderbook), footprint_cells/3 |
ZenQuant.MM | :mm | fair_value/3 (micro-price from depth), spread_calculator/2 (Avellaneda-Stoikov) |
"What are options telling us?"
| Module | Short Name | Key Functions |
|---|---|---|
ZenQuant.Options | :options | max_pain/1, put_call_ratio/1, oi_by_strike/1, gex_by_strike/2, atm_iv/2, atm_iv_term_structure/3, moneyness_skew/3, pin_risk/3 |
ZenQuant.Options.Pricing | :pricing | price/2, greeks/2, implied_volatility/3 (Black-Scholes-Merton — this is where pricing lives) |
ZenQuant.Options.Surface | :surface | build/1 (sparse IV surface from a chain) |
ZenQuant.Options.Skew | :skew | term_structure/1 (from normalized skew observations) |
ZenQuant.Options.Probability | :probability | estimate/1 (terminal risk-neutral probability from a vertical) |
ZenQuant.Options.Snapshot | :snapshot | to_report/2 (structured briefing; sections fail independently) |
ZenQuant.Options.BlockTrades | :block_trades | summarize/2 (aggressor side preserved, never inferred from size) |
ZenQuant.Options.Deribit | :deribit | chain/3 (fetch + enrich), gamma_walls/3, dvol/3, parse_option/1 |
ZenQuant.Options.GammaWalls | :gamma_walls | aggregate/3 (GEX walls with dealer/customer side) |
ZenQuant.Options.ZeroDTE | :zero_dte | near_strikes/3, theta_acceleration/3, gamma_exposure/3 |
ZenQuant.Greeks | :greeks | position_greeks/1, dollar_delta/2, delta_neutral?/2, hedge_ratio/2 (aggregation only — for pricing use :pricing) |
"How risky is this?"
| Module | Short Name | Key Functions |
|---|---|---|
ZenQuant.Risk | :risk | sharpe_ratio/2, sortino_ratio/2, max_drawdown/1, var/2, portfolio_delta/1, check_limits/2 |
ZenQuant.Sizing | :sizing | fixed_fractional/3, kelly/2, volatility_scaled/4 |
ZenQuant.Portfolio | :portfolio | total_exposure/1, unrealized_pnl/1, realized_pnl/1 |
"What's the yield?"
| Module | Short Name | Key Functions |
|---|---|---|
ZenQuant.Funding | :funding | annualize/2, trend/2, mean_reversion_signal/2, rank/2, compare/1 |
ZenQuant.Basis | :basis | spot_perp/2, annualized/3, futures_curve/2, implied_funding/3 |
"What's BTC's valuation?"
| Module | Short Name | Key Functions |
|---|---|---|
ZenQuant.PowerLaw | :power_law | fair_value/1, z_score/2, fair_value_range/1, forecast/2, classify/2 |
"Record and replay?"
| Module | Short Name | Key Functions |
|---|---|---|
ZenQuant.Recorder.JSONL | :jsonl | append/3, stream/1, read_all/1, info/1 |
ZenQuant.Recorder.Replay | :replay | stream/2, run/3, reduce/4 |
ZenQuant.WS | :ws | stale_check/2, reconnect_metrics/2 |
Order lifecycle
| Module | Short Name | Key Functions |
|---|---|---|
ZenQuant.OrderState | :order_state | new/2, on_fill/3, cancel/2, summary/1 |
ZenQuant.Volatility | :volatility | realized/2, parkinson/1, garman_klass/1, yang_zhang/1, iv_percentile/2 |
Module Short Name Reference
Discoverable modules, sorted alphabetically:
All 26 discoverable modules, sorted alphabetically. The function count is what
ZenQuant.describe() reports.
| Short Name | Module | Fns | Purpose |
|---|---|---|---|
:basis | ZenQuant.Basis | 6 | Futures basis/premium, annualized carry |
:block_trades | ZenQuant.Options.BlockTrades | 1 | Block-trade aggregation (side never inferred) |
:deribit | ZenQuant.Options.Deribit | 11 | Deribit symbol parsing, chain fetch, DVOL |
:execution | ZenQuant.Execution | 4 | Cross-venue BBO, arb detection, split plans |
:funding | ZenQuant.Funding | 11 | Funding rates, APR/APY, trend, ranking |
:gamma_walls | ZenQuant.Options.GammaWalls | 1 | GEX wall computation |
:greeks | ZenQuant.Greeks | 9 | Portfolio greeks aggregation/exposure (no pricing — see :pricing) |
:jsonl | ZenQuant.Recorder.JSONL | 7 | JSONL snapshot recording |
:mean_reversion | ZenQuant.MeanReversion | 1 | Ornstein-Uhlenbeck half-life estimation |
:mm | ZenQuant.MM | 5 | Micro-price, optimal spread, fill rate |
:options | ZenQuant.Options | 21 | Options chain analytics |
:order_state | ZenQuant.OrderState | 4 | Immutable order lifecycle |
:orderflow | ZenQuant.Orderflow | 8 | CVD, VWAP, imbalance, footprint |
:portfolio | ZenQuant.Portfolio | 4 | Portfolio exposure, PnL |
:power_law | ZenQuant.PowerLaw | 9 | Bitcoin power law model |
:pricing | ZenQuant.Options.Pricing | 3 | Black-Scholes-Merton price, greeks, implied volatility |
:probability | ZenQuant.Options.Probability | 1 | Risk-neutral probability from verticals |
:replay | ZenQuant.Recorder.Replay | 3 | Filtered replay with speed control |
:risk | ZenQuant.Risk | 13 | Sharpe, Sortino, VaR, drawdown, stress test |
:sizing | ZenQuant.Sizing | 6 | Position sizing strategies |
:skew | ZenQuant.Options.Skew | 1 | Skew term structures |
:snapshot | ZenQuant.Options.Snapshot | 1 | Structured options briefing |
:surface | ZenQuant.Options.Surface | 1 | Sparse implied-volatility surfaces |
:volatility | ZenQuant.Volatility | 11 | Realized, Parkinson, Garman-Klass, Yang-Zhang |
:ws | ZenQuant.WS | 2 | WebSocket health monitoring |
:zero_dte | ZenQuant.Options.ZeroDTE | 3 | Near-expiry (0DTE) analytics |
Not in describe() (public but not Descripex-annotated): ZenQuant.Backtest,
ZenQuant.OrderBook, ZenQuant.Manifest, and the three ZenQuant.Helpers.*
formatting modules.
| :zero_dte | ZenQuant.Options.ZeroDTE | 0DTE analytics |
Need live counts? Query runtime:
modules = ZenQuant.describe()
module_count = length(modules)
function_count = Enum.reduce(modules, 0, &(&1.function_count + &2))Param Kinds
Every function parameter has a kind — this tells you where the data comes from:
:value— You provide this directly (a number, a date, a config option):exchange_data— Must be fetched from an exchange first
Exchange data params include a source: field telling you what to fetch:
ZenQuant.describe(:orderflow, :cvd_delta)
# => params: [%{name: :trades, kind: :exchange_data, source: "fetch_trades(symbol)", ...}]
ZenQuant.describe(:funding, :annualize)
# => params: [%{name: :rate, kind: :value, ...}, %{name: :period_hours, kind: :value, default: 8, ...}]Common Composition Chains
Live Market Overview
Tickers from multiple exchanges → best price + funding + orderflow:
# 1. Build exchange handles, then fetch tickers from all of them
{:ok, bybit} = Bourse.exchange(:bybit)
{:ok, okx} = Bourse.exchange(:okx)
multi = Bourse.Multi.fetch_tickers([bybit, okx], "BTC/USDT:USDT")
# 2. Cross-venue price comparison
venues = ZenQuant.Execution.from_multi(Bourse.Multi.successes(multi))
bbo = ZenQuant.Execution.best_price(venues)
# => %{best_bid: %{price: 67801.0, exchange: "bybit"}, spread_bps: 0.74, ...}
# 3. Funding analysis
rates = [%{funding_rate: 0.0001}, %{funding_rate: 0.00015}]
ZenQuant.Funding.annualize(0.0001) # => 10.95% APR
ZenQuant.Funding.trend(rates) # => :rising | :falling | :stable
# 4. Orderflow from recent trades
{:ok, trades} = Bourse.fetch_trades(bybit, "BTC/USDT:USDT", limit: 100)
ZenQuant.Orderflow.vwap(trades) # => volume-weighted average priceOptions Analysis Pipeline
Chain → OI distribution → max pain → gamma walls:
# 1. Build the exchange handle, then fetch an enriched chain.
# bourse: dispatcher module + %Bourse.Exchange{} handle, not a
# per-exchange module. `chain/3` returns %{chain: ..., spot: ..., ...}.
{:ok, ex} = Bourse.exchange(:deribit)
{:ok, %{chain: chain, spot: spot}} =
ZenQuant.Options.Deribit.chain(Bourse, ex, currency: "BTC", enrich: :greeks)
# 2. Open interest distribution
oi = ZenQuant.Options.oi_by_strike(chain)
{:ok, pain} = ZenQuant.Options.max_pain(chain)
# 3. Gamma walls (pure — from enriched chain)
walls = ZenQuant.Options.GammaWalls.aggregate(chain, spot)
# 4. Or use the composition shortcut
{:ok, result} = ZenQuant.Options.Deribit.gamma_walls(Bourse, ex, currency: "BTC")
# => %{walls: [...], spot: 67800.0, chain_size: 880, enriched_count: 50}Pricing an Option Yourself
Options.Deribit reads greeks from the exchange. When you need to compute
them — or back out implied volatility from a mark price — use
ZenQuant.Options.Pricing, which is fully pure and needs no exchange at all:
inputs = %{
spot: 67_800.0, strike: 70_000.0,
time_to_expiry_years: 21 / 365,
risk_free_rate: 0.05, dividend_yield: 0.0,
volatility: 0.55
}
{:ok, price} = ZenQuant.Options.Pricing.price(:call, inputs)
{:ok, greeks} = ZenQuant.Options.Pricing.greeks(:call, inputs)
# Back out IV from an observed mark price (drop :volatility from the inputs)
{:ok, %{volatility: iv, converged: true}} =
ZenQuant.Options.Pricing.implied_volatility(:call, 1_240.0, Map.delete(inputs, :volatility))Time is a year fraction you compute — the module imposes no calendar or trading-day count.
Orderflow from Trades
Fetch → CVD + VWAP + footprint:
# Fetch trades (Bourse normalized — works directly)
{:ok, ex} = Bourse.exchange(:bybit)
{:ok, trades} = Bourse.fetch_trades(ex, "BTC/USDT:USDT", limit: 100)
# Total CVD for the list (single float)
total_cvd = ZenQuant.Orderflow.cvd(trades)
# Per-trade CVD contribution (single trade → single float)
delta = ZenQuant.Orderflow.cvd_delta(hd(trades))
# Aggregate metrics
vwap = ZenQuant.Orderflow.vwap(trades)
# Footprint: bucketed buy/sell volume (positional args: price_step, time_window_ms)
cells = ZenQuant.Orderflow.footprint_cells(trades, 100, 60_000)
# => [%{price_bucket: 67800.0, buy_volume: ..., sell_volume: ..., delta: ...}]Gotchas
Things that trip up first-time users:
cvd/1vscvd_delta/1—cvd/1takes a trade list and returns total CVD as a single float.cvd_delta/1takes ONE trade and returns ONE float (its contribution). To sum a list, usecvd/1.dom_level/2accepts orderbook directly — Pass either a wrapped map%{orderbook: ob, trades: trades}or an orderbook directly%{bids: [...], asks: [...]}. Both work. The bids/asks entries must be[price, size]tuples — which is exactly what a Bourse normalized OrderBook provides.IV is in percentage format — Deribit returns
mark_iv: 48.6(not0.486). All ZenQuant IV functions expect and return percentages.expected_range/3divides by 100 internally.fetch_tradestakes keyword options — in bourse,fetch_trades(exchange, symbol, opts);sinceandlimitare keywords, e.g.Bourse.fetch_trades(ex, "BTC/USDT", limit: 100). Only the symbol is positional.Networked functions take a dispatcher module AND an exchange handle —
dvol/3,chain/3, andgamma_walls/3are(dispatcher, exchange, opts): the top-level dispatcher module (Bourse) followed by a built%Bourse.Exchange{}handle fromBourse.exchange(:deribit). Per-exchange modules likeBourse.Deribitdo not exist — passing one raises. These are the only functions in the whole library that touch the network; everything else is pure.Greeksaggregates,Options.Pricingcomputes —ZenQuant.Greekstakes greeks supplied by the exchange and sums/scales them. It contains no Black-Scholes. If you need a price, analytic greeks, or implied volatility, that'sZenQuant.Options.Pricing.All functions accept plain maps — ZenQuant has zero struct dependencies. Bourse normalized structs work because they're maps underneath. Raw exchange data works too (dual atom/string key access).
Deribit
fetch_ohlcvreturns very few candles — With"1d"timeframe, Deribit returns only 1 candle. Use Bybit or Binance for historical OHLCV data.Bourse.Multi.fetch_tickerstakes a single symbol string — Each exchange must support that exact symbol format. For cross-exchange with different conventions (e.g. Deribit"BTC-PERPETUAL"vs Bybit"BTC/USDT:USDT"), use the map form keyed by exchange handles:Bourse.Multi.fetch_tickers(%{bybit => "BTC/USDT:USDT", deribit => "BTC-PERPETUAL"}).Call
Bourse.Multi.successes/1beforeZenQuant.Execution.from_multi/1—Bourse.Multireturns%{%Bourse.Exchange{} => {:ok, ticker} | {:error, reason}}. Unwrap and filter errors first:multi |> Bourse.Multi.successes() |> ZenQuant.Execution.from_multi().
The bourse Connection
ZenQuant is the computation layer. bourse provides the data. Here's what feeds what:
| bourse Function | ZenQuant Domain | Example |
|---|---|---|
fetch_tickers / Multi.fetch_tickers | Execution, MM | from_multi → best_price |
fetch_trades | Orderflow | cvd_delta, vwap, footprint_cells |
fetch_order_book | Orderflow, MM | imbalance, heatmap_points, fair_value |
fetch_funding_rates | Funding | annualize, trend, rank |
fetch_option_chain | Options | oi_by_strike, max_pain, gex_by_strike |
fetch_greeks | Options, Greeks | chain/2 enrichment, position_greeks |
fetch_volatility_history | Options.Deribit | dvol/2 |
fetch_ticker (spot + futures) | Basis | spot_perp, annualized |
| Historical closes | Volatility | realized, parkinson, yang_zhang |
| Position data | Risk, Portfolio, Sizing | portfolio_delta, total_exposure, kelly |
Static Manifest
For non-BEAM consumers (Python, TypeScript, Rust, HTTP agents):
mix zen_quant.manifest
# => Writes api_manifest.json
The manifest includes every module, function, param (with kinds and descriptions), return type, returns_example, composition hints (composes_with), and error cases. It's the same data as ZenQuant.Manifest.build() serialized to JSON.
Use this for:
- HTTP API endpoint generation
- Agent capability registration (EIP-8004)
- SDK generation for other languages
- CI validation that the API contract hasn't changed unexpectedly