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

Bidirectional symbol normalization between unified and exchange-specific formats.

CCXT uses a unified symbol format: `BASE/QUOTE` (e.g., "BTC/USDT").
Different exchanges use different formats:

- Binance: "BTCUSDT" (no separator, uppercase)
- Coinbase: "BTC-USD" (dash separator)
- Gate.io: "BTC_USDT" (underscore separator)
- Bitstamp: "btcusd" (lowercase, no separator)
- Derivatives: "BTC/USDT:USDT" (with settle currency)
- Kraken: "XXBTZUSD" (X/Z prefixes for currencies)
- KrakenFutures: "PI_XBTUSD" (contract type prefixes)

## Format-Based Conversion

Use with a format map for simple normalization:

    format = %{separator: "-", case: :upper}
    CCXT.Symbol.normalize("BTC-USD", format)
    #=> "BTC/USD"

    CCXT.Symbol.denormalize("BTC/USD", format)
    #=> "BTC-USD"

## Parsing and Building

Parse unified symbols into components:

    CCXT.Symbol.parse("BTC/USDT:USDT")
    #=> {:ok, %{base: "BTC", quote: "USDT", settle: "USDT"}}

    CCXT.Symbol.build("BTC", "USDT", "USDT")
    #=> "BTC/USDT:USDT"

## Currency Aliases

Apply exchange-specific currency code mappings:

    aliases = %{"XBT" => "BTC", "XXRP" => "XRP"}
    CCXT.Symbol.apply_alias("XBT", aliases)
    #=> "BTC"

# `parsed_extended`

```elixir
@type parsed_extended() :: %{
  base: String.t(),
  quote: String.t(),
  settle: String.t() | nil,
  expiry: String.t() | nil,
  strike: String.t() | nil,
  option_type: String.t() | nil
}
```

# `parsed_symbol`

```elixir
@type parsed_symbol() :: %{
  base: String.t(),
  quote: String.t(),
  settle: String.t() | nil
}
```

# `pattern_config`

```elixir
@type pattern_config() :: %{
  pattern: atom(),
  separator: String.t(),
  case: :upper | :lower | :mixed,
  date_format: :yymmdd | :ddmmmyy | :yyyymmdd | nil,
  suffix: String.t() | nil,
  prefix: String.t() | nil
}
```

# `symbol_format`

```elixir
@type symbol_format() :: %{separator: String.t(), case: :upper | :lower | :mixed}
```

# `ws_symbol_format`

```elixir
@type ws_symbol_format() ::
  :dash_separated
  | :lowercase_no_slash
  | :uppercase_no_slash
  | :slash
  | :unknown
```

# `apply_alias`

```elixir
@spec apply_alias(String.t(), map()) :: String.t()
```

Applies a currency alias mapping to a single currency code.

Used with `commonCurrencies` data from exchange specs.

    CCXT.Symbol.apply_alias("XBT", %{"XBT" => "BTC"})
    #=> "BTC"

    CCXT.Symbol.apply_alias("ETH", %{"XBT" => "BTC"})
    #=> "ETH"

# `build`

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

Builds a unified symbol from components.

    CCXT.Symbol.build("BTC", "USDT")
    #=> "BTC/USDT"

    CCXT.Symbol.build("BTC", "USD", "BTC")
    #=> "BTC/USD:BTC"

# `convert_date`

```elixir
@spec convert_date(String.t(), atom(), atom()) :: String.t()
```

Converts dates between derivative symbol formats.

Supported formats: `:yymmdd`, `:ddmmmyy`, `:yyyymmdd`.

    CCXT.Symbol.convert_date("260327", :yymmdd, :ddmmmyy)
    #=> "27MAR26"

    CCXT.Symbol.convert_date("27MAR26", :ddmmmyy, :yymmdd)
    #=> "260327"

    CCXT.Symbol.convert_date("260327", :yymmdd, :yyyymmdd)
    #=> "20260327"

# `denormalize`

```elixir
@spec denormalize(String.t(), symbol_format()) :: String.t()
```

Converts a unified symbol to exchange-specific format.

Strips settle currency (colon suffix) before conversion, then applies
separator replacement and case transformation.

## Parameters

- `symbol` - The unified symbol (e.g., "BTC/USDT" or "BTC/USDT:USDT")
- `format` - Map with `:separator` and `:case` keys

# `denormalize_ws`

```elixir
@spec denormalize_ws(String.t(), ws_symbol_format()) :: String.t()
```

Converts a unified symbol to WebSocket channel format.

WebSocket channels often use different symbol formats than REST APIs.

# `detect_market_type`

```elixir
@spec detect_market_type(parsed_extended()) :: :spot | :swap | :future | :option
```

Detects market type from parsed extended symbol components.

Priority: option > future > swap > spot.

# `from_exchange_id`

```elixir
@spec from_exchange_id(String.t(), CCXT.Exchange.t(), atom()) :: String.t()
```

Converts an exchange-specific ID to unified symbol format.

Requires `market_type` since exchange IDs are ambiguous without context.

## Parameters

- `exchange_id` - The exchange-specific ID (e.g., "BTCUSDT_260327")
- `exchange` - A `%CCXT.Exchange{}` struct with `symbol_patterns` populated
- `market_type` - The market type (`:spot`, `:swap`, `:future`, `:option`)

## Examples

    CCXT.Symbol.from_exchange_id("BTCUSDT", binance_exchange, :spot)
    #=> "BTC/USDT"

    CCXT.Symbol.from_exchange_id("BTC-PERPETUAL", deribit_exchange, :swap)
    #=> "BTC/USD:BTC"

    CCXT.Symbol.from_exchange_id("BTC-12JAN26-84000-C", deribit_exchange, :option)
    #=> "BTC/USD:BTC-260112-84000-C"

# `from_exchange_id!`

```elixir
@spec from_exchange_id!(String.t(), CCXT.Exchange.t(), atom()) :: String.t()
```

Converts an exchange-specific ID to unified symbol, raising on failure.

# `get_quote_currencies`

```elixir
@spec get_quote_currencies([String.t()] | nil) :: [String.t()]
```

Returns the list of known quote currencies, optionally extended with
exchange-specific currencies.

Currencies are sorted by length descending for longest-match-first splitting.

    CCXT.Symbol.get_quote_currencies()
    #=> ["FDUSD", "USDD", "USDT", "USDC", "BUSD", "TUSD", ...]

    CCXT.Symbol.get_quote_currencies(["TRY", "BRL"])
    #=> ["FDUSD", "USDD", "USDT", ..., "TRY", "BRL"]

# `normalize`

```elixir
@spec normalize(String.t(), symbol_format(), keyword()) :: String.t()
```

Converts an exchange-specific symbol to unified format.

Takes a format map with `:separator` and `:case` keys. Optionally applies
currency aliases (from `commonCurrencies` spec data) to map exchange-specific
codes to unified codes.

## Parameters

- `symbol` - The exchange-specific symbol (e.g., "BTCUSDT")
- `format` - Map with `:separator` and `:case` keys
- `opts` - Keyword options:
  - `:aliases` - Currency alias map (e.g., `%{"XBT" => "BTC"}`)
  - `:quote_currencies` - Custom list of known quote currencies for no-separator splitting

## Returns

The unified symbol (e.g., "BTC/USDT"), or original if cannot parse.

# `parse`

```elixir
@spec parse(String.t()) :: {:ok, parsed_symbol()} | {:error, :invalid_format}
```

Parses a unified symbol into its components.

## Returns

`{:ok, %{base, quote, settle}}` or `{:error, :invalid_format}`.

    CCXT.Symbol.parse("BTC/USDT")
    #=> {:ok, %{base: "BTC", quote: "USDT", settle: nil}}

    CCXT.Symbol.parse("BTC/USDT:USDT")
    #=> {:ok, %{base: "BTC", quote: "USDT", settle: "USDT"}}

# `parse!`

```elixir
@spec parse!(String.t()) :: parsed_symbol()
```

Parses a unified symbol into its components, raising on error.

# `parse_extended`

```elixir
@spec parse_extended(String.t()) ::
  {:ok, parsed_extended()} | {:error, :invalid_format}
```

Parses a unified symbol into extended components including derivative fields.

    CCXT.Symbol.parse_extended("BTC/USDT:USDT-260327")
    #=> {:ok, %{base: "BTC", quote: "USDT", settle: "USDT", expiry: "260327", strike: nil, option_type: nil}}

    CCXT.Symbol.parse_extended("BTC/USD:BTC-260112-84000-C")
    #=> {:ok, %{base: "BTC", quote: "USD", settle: "BTC", expiry: "260112", strike: "84000", option_type: "C"}}

# `reverse_aliases`

```elixir
@spec reverse_aliases(map()) :: map()
```

Inverts an alias map for reverse lookups (unified → exchange code).

    CCXT.Symbol.reverse_aliases(%{"XBT" => "BTC", "ZEUR" => "EUR"})
    #=> %{"BTC" => "XBT", "EUR" => "ZEUR"}

# `strip_prefix`

```elixir
@spec strip_prefix(String.t()) :: {String.t() | nil, String.t()}
```

Strips known exchange prefixes from a symbol.

Handles KrakenFutures contract prefixes (PI_, PF_, FI_, FF_, PV_) and
Kraken currency prefixes (X for crypto, Z for fiat).

    CCXT.Symbol.strip_prefix("PI_XBTUSD")
    #=> {"PI_", "XBTUSD"}

    CCXT.Symbol.strip_prefix("XXBT")
    #=> {"X", "XBT"}

    CCXT.Symbol.strip_prefix("ZUSD")
    #=> {"Z", "USD"}

    CCXT.Symbol.strip_prefix("BTCUSDT")
    #=> {nil, "BTCUSDT"}

# `to_exchange_id`

```elixir
@spec to_exchange_id(String.t(), CCXT.Exchange.t()) :: String.t()
```

Converts a unified symbol to exchange-specific ID.

Uses pattern configs from the exchange's `symbol_patterns` to handle
spot, swap, future, and option symbols. Falls back to `denormalize/2`
when no pattern config exists for the detected market type.

Outbound currency aliases come from `exchange.outbound_aliases` (default
`%{}`), populated only for exchanges that accept the alias on input
(e.g. Kraken: `BTC` → `XBT`). Inbound aliasing (`from_exchange_id`) uses
`common_currencies` directly — that direction is universal.

## Parameters

- `unified_symbol` - The unified symbol (e.g., "BTC/USDT:USDT-260327")
- `exchange` - A `%CCXT.Exchange{}` struct with `symbol_patterns` populated

## Examples

    CCXT.Symbol.to_exchange_id("BTC/USDT", binance_exchange)
    #=> "BTCUSDT"

    CCXT.Symbol.to_exchange_id("BTC/USD:BTC-260112-84000-C", deribit_exchange)
    #=> "BTC-12JAN26-84000-C"

# `to_exchange_id!`

```elixir
@spec to_exchange_id!(String.t(), CCXT.Exchange.t()) :: String.t()
```

Converts a unified symbol to exchange-specific ID, raising on failure.

---

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