The generic, venue-agnostic symbol normaliser.
Canonical form is BASE-QUOTE, uppercase, dash-separated (BTC-USD). This
module holds no venue-specific knowledge — it converts given a mapping that
each venue package declares inside itself. That split is what keeps Core free of
venue facts: the venue supplies its separator, quote list and asset aliases; the
string surgery lives here, once.
Mapping
%{
sep: "-" | "", # native separator
quotes: ["USDC", "USD", ...], # known quote assets
asset_aliases: %{"XBT" => "BTC"} # exchange base code → canonical (optional)
}quotes is sorted longest-first internally, whatever order the caller gives it
A concat-style mapping (sep: "") has to find the SUFFIX among quotes that ends the
native symbol, and the wrong quote can end it too: quotes: ["USD", "BUSD"] against
"ETHBUSD" matches "USD" first and cuts "ETHB-USD" — wrong, because "BUSD" was the
actual quote. This module used to trust the caller to list quotes longest-first, and a
venue mapping that got the order wrong mis-split silently: the base/quote invariant below
does not catch it, because to_exchange(m, to_canonical(m, p)) concatenates
base <> quote either way and round-trips byte-for-byte regardless of where the cut
landed. So quotes is sorted by length, descending, before any match is attempted — a
caller cannot get the ordering wrong any more, whatever it hands in.
Invariant
to_canonical(m, to_exchange(m, p)) == p for every pair. The conformance suite
asserts this against each venue's own mapping, so a venue whose two directions
disagree fails before it ships.
Summary
Functions
Exchange-native symbol → canonical BASE-QUOTE, per the connector's mapping.
Unparseable / already-dashed input → uppercased input (never dropped).
Canonical BASE-QUOTE → exchange-native, per the connector's mapping.
Types
Functions
Exchange-native symbol → canonical BASE-QUOTE, per the connector's mapping.
Unparseable / already-dashed input → uppercased input (never dropped).
Canonical BASE-QUOTE → exchange-native, per the connector's mapping.