# `CCXT.Exchange`
[🔗](https://github.com/ZenHive/ccxt_client/blob/main/lib/ccxt/exchange.ex#L1)

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.

## Examples

    # Public-only (no credentials)
    {:ok, exchange} = CCXT.Exchange.new("bybit")
    exchange.base_urls
    #=> %{"public" => "https://api.bybit.com", ...}

    # With credentials
    {:ok, exchange} = CCXT.Exchange.new("bybit", api_key: "abc", secret: "xyz")
    exchange.credentials
    #=> %CCXT.Credentials{api_key: "abc", secret: "xyz", ...}

    # Sandbox mode (uses testnet URLs)
    {:ok, exchange} = CCXT.Exchange.new("bybit", sandbox: true)

# `error_body_check`

```elixir
@type error_body_check() :: %{
  field: String.t() | nil,
  field2: String.t() | nil,
  roles: [error_body_role()],
  sentinel_values: [sentinel_value()]
}
```

# `error_body_role`

```elixir
@type error_body_role() :: :error_code | :status_sentinel
```

# `sentinel_operator`

```elixir
@type sentinel_operator() :: String.t()
```

# `sentinel_value`

```elixir
@type sentinel_value() :: %{operator: sentinel_operator(), value: String.t()}
```

# `t`

```elixir
@type t() :: %CCXT.Exchange{
  base_urls: map(),
  broad_error_patterns: %{required(String.t()) =&gt; CCXT.Error.error_type()},
  common_currencies: %{required(String.t()) =&gt; String.t()},
  credentials: CCXT.Credentials.t() | nil,
  error_body_checks: [error_body_check()],
  error_code_fields: [String.t()],
  error_codes: %{required(String.t()) =&gt; CCXT.Error.error_type()},
  has: %{required(String.t()) =&gt; boolean() | String.t()},
  hostname: String.t() | nil,
  http_exceptions: %{required(String.t()) =&gt; CCXT.Error.error_type()},
  id: String.t(),
  module: module() | nil,
  name: String.t(),
  options: map(),
  outbound_aliases: %{required(String.t()) =&gt; String.t()},
  rate_limit_ms: number(),
  request_defaults: %{required(String.t()) =&gt; %{required(String.t()) =&gt; term()}},
  required_credentials: %{required(String.t()) =&gt; boolean()},
  sandbox: boolean(),
  signing_config: map(),
  signing_pattern: CCXT.Signing.pattern() | nil,
  spec: map(),
  symbol_patterns: %{required(atom()) =&gt; CCXT.Symbol.pattern_config()},
  tier: String.t() | nil
}
```

# `__generate__`
*macro* 

Generates introspection functions and endpoint wrappers from a spec ID.

# `__using__`
*macro* 

Macro entry point: `use CCXT.Exchange, spec: "bybit"` generates an exchange module.

# `build_endpoint_configs`

```elixir
@spec build_endpoint_configs(map(), map(), [String.t()]) :: [map()]
```

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

    CCXT.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}
    #=> ]

# `build_endpoint_functions`

```elixir
@spec build_endpoint_functions([map()]) :: [Macro.t()]
```

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 `CCXT.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(%CCXT.Exchange{} = exchange, params, opts) do
      CCXT.Dispatch.call(exchange, %{...}, params, opts)
    end

# `build_module_body`

```elixir
@spec build_module_body(
  map(),
  keyword()
) :: Macro.t()
```

Builds the quoted module body from prepared generate data.

Called by both `__generate__/1` (macro path) and `CCXT.Exchanges`
(`Module.create` path) to ensure a single source of truth.

## Options

  * `:moduledoc` — optional `@moduledoc` string to inject. The macro path
    leaves this to the caller; `CCXT.Exchanges` provides one per exchange.

# `build_unified_method_mapping`

```elixir
@spec build_unified_method_mapping(map(), [map()]) :: %{required(atom()) =&gt; [map()]}
```

Builds unified method mapping from spec data and pre-computed endpoint configs.

# `classify_and_track_parent`

```elixir
@spec classify_and_track_parent(map(), String.t()) :: {atom(), map(), [Macro.t()]}
```

Classifies signing pattern and builds `@external_resource` AST for parent specs.

# `has?`

```elixir
@spec has?(t(), String.t()) :: boolean()
```

Checks if the exchange supports a given capability.

Returns `true` for capabilities marked `true` or `"emulated"` in the spec.
Returns `false` for `false`, `"__undefined"`, or missing capabilities.

Capability names use camelCase strings matching the CCXT spec
(e.g., `"fetchTicker"`, `"createOrder"`).

## Examples

    CCXT.Exchange.has?(exchange, "fetchTicker")
    #=> true

    CCXT.Exchange.has?(exchange, "fetchFundingRateHistory")
    #=> false

# `new`

```elixir
@spec new(
  String.t() | atom(),
  keyword()
) :: {:ok, t()} | {:error, term()}
```

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 `%CCXT.Credentials{}` (overrides key/secret opts)
  * `:sandbox` - Use testnet URLs (default: `false`)
  * `:hostname` - Override the default hostname
  * `:options` - Exchange-specific options map

## Examples

    {:ok, exchange} = CCXT.Exchange.new("bybit")
    {:ok, exchange} = CCXT.Exchange.new("okx", api_key: "k", secret: "s", password: "p")
    {:error, :missing_secret} = CCXT.Exchange.new("bybit", api_key: "k")

# `new!`

```elixir
@spec new!(
  String.t() | atom(),
  keyword()
) :: t()
```

Creates an exchange configuration, raising on error.

## Examples

    exchange = CCXT.Exchange.new!("bybit")
    exchange = CCXT.Exchange.new!("bybit", api_key: "abc", secret: "xyz")

# `prepare_generate_data`

```elixir
@spec prepare_generate_data(String.t()) :: map()
```

Prepares all compile-time data for the generator macro.

# `tier`

```elixir
@spec tier(t()) :: String.t() | nil
```

Returns the exchange's tier string, or `nil` if the spec omits it.

Values mirror `priv/priority_tiers.json` from ccxt_extract: `"tier1"`,
`"tier2"`, `"dex"`, `"tier3"`, `"unclassified"`. Specs emitted before
ccxt_extract schema 1.8.0 (2026-04-14) have no tier field and return `nil`.

## Examples

    CCXT.Exchange.tier(CCXT.Exchange.new!("bybit"))
    #=> "tier1"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
