Exchange configuration struct and constructor.
Holds everything needed to make API calls for a specific exchange instance: resolved base URLs, rate limits, credentials, capabilities, and lean spec data.
This is a pure data struct — no process. Rate limiting, HTTP execution, and
signing are handled by other modules that receive %Exchange{} as input.
Loaded market metadata lives on the struct as :markets (nil until
Bourse.load_markets/1 or put_markets/2). Production caches contain
%Bourse.Market{} structs; static replay caches retain raw Bourse maps so their
oracle inputs stay byte-faithful. Callers thread the enriched value; there is
no hidden global cache.
Examples
# Public-only (no credentials)
{:ok, exchange} = Bourse.Exchange.new("bybit")
exchange.base_urls
#=> %{"public" => "https://api.bybit.com", ...}
# With credentials
{:ok, exchange} = Bourse.Exchange.new("bybit", api_key: "abc", secret: "xyz")
exchange.credentials
#=> %Bourse.Credentials{api_key: "abc", secret: "xyz", ...}
# Sandbox mode (uses testnet URLs)
{:ok, exchange} = Bourse.Exchange.new("bybit", sandbox: true)
# Cache markets for symbol→market_id resolution (loadMarkets equivalent)
{:ok, exchange} = Bourse.load_markets(exchange)
Summary
Types
Authored exception scope selected by the request's market family.
Functions
Generates introspection functions and endpoint wrappers from a spec ID.
Macro entry point: use Bourse.Exchange, spec: "bybit" generates an exchange module.
Pre-computes a flat list of endpoint configs from the nested API tree.
Builds quoted endpoint wrapper functions from a list of endpoint configs.
Builds @moduledoc text for a generated exchange module from spec metadata.
Builds the quoted module body from prepared generate data.
Builds quoted parse_<slot>/2 wrapper functions from the spec's normalization
field maps.
Builds unified method mapping from spec data and pre-computed endpoint configs.
Returns the derived config section — deterministic describe() metadata
(credentials, limits, status, routing, rate-limit meta, flags).
Returns compile-time currency metadata for code from the spec's
markets.currencies catalog (Task 97), or nil when absent.
Returns per-network metadata for currency_code + network_code, or nil.
Returns the documentation URL doc-set (logo, www, doc, fees,
api_management) folded from the derived urls section, or %{}.
Returns exact error-code mappings for scope, with scoped entries taking precedence.
Returns the authored exception scope for a request base URL.
Returns the static default fee schedule from the derived fees spec section.
Returns the config.flags map (e.g. dex), or %{}.
Checks if the exchange supports a given capability.
Returns the global default config.limits map (amount/cost/leverage/price), or %{}.
Returns the caller-threaded markets cache, or nil when not loaded.
Creates an exchange configuration from an exchange ID and options.
Creates an exchange configuration, raising on error.
Prepares all compile-time data for the generator macro.
Returns a copy of exchange with the markets cache set to markets.
Returns the config.routing map (accountsByType / networks / timeInForce), or %{}.
Reads the explicit signing executor and configuration from an owned runtime spec.
Returns the config.status map (status/eta/url/info/updated), or %{}.
Returns the unified-to-native OHLCV timeframe map from capabilities.timeframes.
Returns a copy whose active error maps are selected for scope.
Types
@type error_body_check() :: %{ field: String.t() | nil, field2: String.t() | nil, roles: [error_body_role()], sentinel_values: [sentinel_value()] }
@type error_body_role() :: :error_code | :status_sentinel
@type error_handler_check() :: %{ status_guard: error_status_guard(), body_contains: [String.t()], error_type: Bourse.Error.error_type() }
Authored exception scope selected by the request's market family.
@type error_status_guard() :: {:gte, non_neg_integer()} | {:in, [non_neg_integer()]}
@type fees() :: map() | nil
@type market_cache() :: [Bourse.Market.t() | raw_market()] | nil
@type request_contract() :: %{ optional(:method) => atom(), optional(:path) => String.t(), optional(:path_params) => [String.t()], optional(:body_encoding) => String.t(), optional(:content_type) => String.t() | nil, optional(:timestamp_recipe) => map(), optional(:weight) => number(), optional(:rate_limit) => map() }
@type sentinel_operator() :: String.t()
@type sentinel_value() :: %{operator: sentinel_operator(), value: String.t()}
@type t() :: %Bourse.Exchange{ base_urls: map(), broad_error_patterns: %{required(String.t()) => Bourse.Error.error_type()}, common_currencies: %{required(String.t()) => String.t()}, config: config(), credentials: Bourse.Credentials.t() | nil, currencies: %{required(String.t()) => map()}, default_family: String.t() | nil, doc_urls: doc_urls(), endpoint_selection: %{required(String.t()) => map()}, error_body_checks: [error_body_check()], error_class_ancestors: %{required(String.t()) => [String.t()]}, error_code_fields: [String.t()], error_codes: %{required(String.t()) => Bourse.Error.error_type()}, error_handler_checks: [error_handler_check()], features: %{required(String.t()) => map()} | nil, fees: fees(), has: %{required(String.t()) => boolean() | String.t()}, hostname: String.t() | nil, http_exceptions: %{required(String.t()) => Bourse.Error.error_type()}, id: String.t(), markets: market_cache(), module: module() | nil, name: String.t(), network_options: map(), options: map(), outbound_aliases: %{required(String.t()) => String.t()}, rate_limit_ms: number(), request_contracts: %{required(request_contract_key()) => request_contract()}, request_defaults: %{required(String.t()) => %{required(String.t()) => term()}}, request_param_shape: %{ required(String.t()) => %{required(String.t()) => map()} }, required_credentials: %{required(String.t()) => boolean()}, retry_classification: %{required(String.t()) => Bourse.Error.retry_class()}, sandbox: boolean(), sandbox_headers: %{optional(String.t()) => String.t()}, signing_config: map(), signing_pattern: Bourse.Signing.pattern() | nil, spec: map(), status_map: %{required(String.t()) => Bourse.Error.error_type()}, symbol_patterns: %{required(atom()) => Bourse.Symbol.pattern_config()}, timeframes: %{required(String.t()) => String.t()} }
Functions
Generates introspection functions and endpoint wrappers from a spec ID.
Macro entry point: use Bourse.Exchange, spec: "bybit" generates an exchange module.
Pre-computes a flat list of endpoint configs from the nested API tree.
Called at compile time by __generate__/1. Recursively traverses the spec's
API tree until it finds HTTP method keys (get, post, etc.), then extracts
endpoint configs from the level below.
Handles three spec patterns:
- Standard:
%{visibility => %{method => %{path => weight}}} - Deep nesting:
%{api_type => %{version => %{visibility => %{method => ...}}}} - Array endpoints:
%{... => %{method => [list_of_paths]}}
All intermediate keys above the HTTP method become the :sections list.
Examples
Bourse.Exchange.build_endpoint_configs(%{
"public" => %{"get" => %{"v5/market/tickers" => 5}},
"private" => %{"post" => %{"v5/order/create" => 2.5}}
})
#=> [
#=> %{name: :public_get_v5_market_tickers, method: :get,
#=> path: "v5/market/tickers", sections: ["public"], weight: 5},
#=> %{name: :private_post_v5_order_create, method: :post,
#=> path: "v5/order/create", sections: ["private"], weight: 2.5}
#=> ]
Builds quoted endpoint wrapper functions from a list of endpoint configs.
Called at compile time by __generate__/1. Each generated function embeds
its endpoint config as a literal and delegates to Bourse.Dispatch.call/4.
Example
For a config %{name: :public_get_v5_market_tickers, ...}, generates:
def public_get_v5_market_tickers(exchange, params \\ %{}, opts \\ [])
def public_get_v5_market_tickers(%Bourse.Exchange{} = exchange, params, opts) do
Bourse.Dispatch.call(exchange, %{...}, params, opts)
end
Builds @moduledoc text for a generated exchange module from spec metadata.
Used by Bourse.Exchanges at compile time. The returned string is injected into
each generated module (e.g. Bourse.Bybit) for ExDoc and IDE discovery.
Builds the quoted module body from prepared generate data.
Called by both __generate__/1 (macro path) and Bourse.Exchanges
(Module.create path) to ensure a single source of truth.
Options
:moduledoc— optional@moduledocstring to inject. The macro path leaves this to the caller;Bourse.Exchangesprovides one per exchange.
Builds quoted parse_<slot>/2 wrapper functions from the spec's normalization
field maps.
One function per @parse_slots entry (parse_ticker/2, parse_trade/2, …).
Each embeds its slot mapping as a literal and delegates to Bourse.Parser.parse/4,
which applies the Honesty Rule (nil slot → {:error, :no_field_map},
non-nil _unresolved_reason → {:error, {:unresolved, reason}}).
Builds unified method mapping from spec data and pre-computed endpoint configs.
Returns the derived config section — deterministic describe() metadata
(credentials, limits, status, routing, rate-limit meta, flags).
Empty when an authored spec has no matching configuration. Individual
sub-sections are also available via limits/1, status/1, routing/1,
and flags/1.
Returns compile-time currency metadata for code from the spec's
markets.currencies catalog (Task 97), or nil when absent.
Records include networks when the exchange surfaces per-network deposit/
withdraw metadata via loadMarkets().
Network coverage is declared by each supported venue's owned runtime spec.
Returns per-network metadata for currency_code + network_code, or nil.
Returns nil when the currency is absent, the exchange spec has empty
networks maps (see currency/2), or the requested network code is missing.
Returns the documentation URL doc-set (logo, www, doc, fees,
api_management) folded from the derived urls section, or %{}.
Call URLs live on base_urls; this surface is metadata for introspection.
@spec error_codes_for(t(), error_scope()) :: %{ required(String.t()) => Bourse.Error.error_type() }
Returns exact error-code mappings for scope, with scoped entries taking precedence.
Returns the authored exception scope for a request base URL.
Scope is never inferred from URL path segments or host labels. Construction
projects the venue's authored errors.handle_errors.exception_scopes
(API-section → scope) onto production and sandbox base URLs into
exchange.spec["error_scopes"]; this lookup is a pure map read against that
projection. Venues that declare no scopes return nil for every URL.
Returns the static default fee schedule from the derived fees spec section.
This is the exchange-level describe().fees default, not live per-market
loadMarkets() fees and not dynamic fetch_trading_fees endpoint data.
Returns the config.flags map (e.g. dex), or %{}.
Checks if the exchange supports a given capability.
Returns true for capabilities marked true or "emulated" in the derived
capabilities.has map. Returns false for false, "__undefined", or missing capabilities.
Capability names use camelCase strings matching the Bourse spec
(e.g., "fetchTicker", "createOrder").
Examples
Bourse.Exchange.has?(exchange, "fetchTicker")
#=> true
Bourse.Exchange.has?(exchange, "fetchFundingRateHistory")
#=> false
Returns the global default config.limits map (amount/cost/leverage/price), or %{}.
@spec markets(t()) :: market_cache()
Returns the caller-threaded markets cache, or nil when not loaded.
Bourse.load_markets/1 and put_markets/2 store %Bourse.Market{} structs.
Static response replay stores raw Bourse string-keyed maps to preserve its
oracle input. Pure data — no process or global store. Reload by calling
Bourse.load_markets/1 again and threading the returned struct.
Creates an exchange configuration from an exchange ID and options.
Loads the spec, resolves base URLs (with hostname interpolation and sandbox/testnet switching), and optionally builds credentials.
Options
:api_key- API key string (builds credentials automatically):secret- API secret string (builds credentials automatically):password- API password (OKX, KuCoin):uid- User ID:credentials- Pre-built%Bourse.Credentials{}(overrides key/secret opts):sandbox- Use testnet URLs (default:false; OKX defaults towww.okx.com):hostname- Override the default hostname:options- Exchange-specific options map
Examples
{:ok, exchange} = Bourse.Exchange.new("bybit")
{:ok, exchange} = Bourse.Exchange.new("okx", api_key: "k", secret: "s", password: "p")
{:error, :missing_secret} = Bourse.Exchange.new("bybit", api_key: "k")
Creates an exchange configuration, raising on error.
Examples
exchange = Bourse.Exchange.new!("bybit")
exchange = Bourse.Exchange.new!("bybit", api_key: "abc", secret: "xyz")
Prepares all compile-time data for the generator macro.
@spec put_markets(t(), [Bourse.Market.t()]) :: t()
Returns a copy of exchange with the markets cache set to markets.
Use when you already hold a fetch_markets result and want subsequent
market-metadata consumers (e.g. symbol→market_id resolution) to reuse it
without another network round-trip. Prefer Bourse.load_markets/1 for the
usual fetch-and-attach path.
Returns the config.routing map (accountsByType / networks / timeInForce), or %{}.
@spec signing_from_spec(map()) :: {Bourse.Signing.pattern(), map()}
Reads the explicit signing executor and configuration from an owned runtime spec.
Returns the config.status map (status/eta/url/info/updated), or %{}.
Returns the unified-to-native OHLCV timeframe map from capabilities.timeframes.
Keys are Bourse unified labels (e.g. "1h", "15m"); values are exchange-native
labels (e.g. "60" on Bybit, "1h" on Binance). Empty when upstream omitted the map.
@spec with_error_scope(t(), error_scope()) :: t()
Returns a copy whose active error maps are selected for scope.