# `DpExchange.Core.SymbolNormalizer`
[🔗](https://github.com/DistortionPoint/dp-exchange-core/blob/v0.3.116/lib/dp_exchange/core/symbol_normalizer.ex#L1)

The **symbol-normalisation contract** every venue package must satisfy.

A venue's `SymbolFormat` module `@behaviour`s this so the contract is
*obligatory*: a package that ships without both directions of the conversion fails
the compiler's missing-callback check. This is what makes the
multi-exchange promise safe — one caller's canonical `BASE-QUOTE` pairs mean the
same thing on every venue, because every venue is forced to round-trip them through
the shared `DpExchange.Core.CanonicalPair` normaliser with its own mapping.

Two directions, both total (never raise on malformed input — through
`DpExchange.Core.CanonicalPair`, unparseable input is returned uppercased rather than
dropped, so a bad feed message can't crash a parser and is still visible):

- `to_canonical_symbol/1` — exchange-native → canonical `BASE-QUOTE` (uppercase,
  dash). The form the rest of the system stores and reasons about.
- `to_exchange_symbol/1` — canonical `BASE-QUOTE` → exchange-native. The form a
  connector puts on the wire (orders, subscriptions).

Venues with more than one native channel — a WebSocket vocabulary differing from
the REST one is common — implement these two against their **account/REST** form — the authoritative native form
for orders and balances — and expose channel-specific helpers alongside.

# `to_canonical_symbol`

```elixir
@callback to_canonical_symbol(native :: String.t()) :: String.t()
```

Exchange-native symbol → canonical `BASE-QUOTE` (uppercase, dash-separated).

# `to_exchange_symbol`

```elixir
@callback to_exchange_symbol(canonical :: String.t()) :: String.t()
```

Canonical `BASE-QUOTE` → this exchange's native symbol form.

---

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