Packages

Elixir client library for cryptocurrency exchanges — generated from CCXT specs via compile-time macros.

Current section

Files

Jump to
ccxt_client CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
Completed roadmap tasks. For upcoming work, see ROADMAP.md.
---
## [Unreleased]
---
## [0.4.0] - 2026-03-12
### Added
- **Param-derived response parser mappings** — generator now derives parser instructions from param analysis
- **CCXT v4.5.42 sync** — added Lighter (DEX), 4 new methods (TWAP, ADL rank), parse methods 1544→1560, error codes 6137→6212
- **Symbol format v2**`forward_aliases`, `prefix` field, colon separator, `split_no_separator`; symbol data 3x richer
- **Spec format v2** — pagination metadata, `_by_market_type` defaults, `path_params`, overlay merge system
- **Sync verification script** (`scripts/verify_sync.sh`) — validates file changes, exchange counts, spec keys, compilation
- **Order sanity checks** — validation for order responses
- **Descripex integration** — self-describing APIs with `expects` field and 993 response transformers
- **Named params** — replaced positional `arg0`/`arg1` with named params across 52 specs
- **Column-oriented OHLCV**`transpose_columns_to_rows` and `compose` response transformers
- **Shared runtime dispatch** (`Dispatch.call/5`) — 93% compile-time reduction
- **OHLCV timeframe translation** — unified timeframes → exchange-native for REST and WebSocket
- **CCXT.Health module**`ping/2`, `latency/2`, `all/0`, `status/1`
- **Normalization contract tests and parser coverage**
### Fixed
- **`denormalize_symbol_param` error propagation** — propagates `{:error, :no_symbol_format}` instead of silently passing bad data
- **Generator handling for custom base URLs and timeframes**
- **Exchange discovery guard** against invalid specs
- **WS symbol backfill** from routed channels
- **Gate and Bybit normalization** spec sync
- **Timestamp and trade side code normalization**
- **String coercion regressions** in normalization
- **Variable-length order book level coercion**
- **Generated timestamp placeholders** for more params
- **Additional response error patterns**
- **HTX ticker bid/ask array coercion** to scalars
- **Multi call arg forwarding** and module load checks
- **Pipeline default fallback**`@pipeline_default` module attribute replaces fragile `Code.ensure_loaded?`
- **Param precedence**`strip_overridden_defaults/3`
- **Order book metadata fallback normalization**
- **Dialyzer and doc warnings** blocking pre-commit
---
## Roadmap Archive (2026-03-11)
Bulk archive of completed items from ROADMAP.md cleanup. Grouped by category.
### Sync History
| Date | Summary |
|------|---------|
| 2026-03-10 (#2) | CCXT v4.5.42. +Lighter (DEX), -Probit. 4 new methods (TWAP, ADL rank). Parse methods 1544→1560, error codes 6137→6212. |
| 2026-03-10 | Kraken `fetch_trades` partial fix — `:extract_first_list_value` transformer. `JsonLoader` macro. 5 normalization regressions from changed mappings. |
| 2026-03-09 | Symbol system v2 — `forward_aliases`, `prefix` field, colon separator, `split_no_separator`. Symbol data 3x richer. |
| 2026-03-02 | Spec format v2 — pagination metadata, `_by_market_type` defaults, `path_params`, overlay merge system. |
| 2026-02-28 | Column-oriented OHLCV — `{:transpose_columns_to_rows}` + `{:compose}` transformers. |
| 2026-02-24 | Pipeline default fix — `@pipeline_default` module attribute replaces `Code.ensure_loaded?` fallback. |
| 2026-02-23 | New `parseADLRank` method (Binance, Bitmex, Bybit, Htx). 6 specs refreshed. |
| 2026-02-22 | Param precedence fix — `strip_overridden_defaults/3`. Symbol removed from `default_params`. |
| 2026-02-20 | 3 bugs synced. Adapter refactor, `response_transformers`, `auth_required` flags. |
### Completed Bugs (Current Focus)
- **Bug 7: WS Symbol Fallback** [D:3/B:6/U:5] — Already implemented. `normalize/4` accepts channel string, `maybe_inject_symbol/2` extracts market ID from channel via `MessageRouter.extract_market_id/1` when payload lacks symbol. Handles single structs and lists, Deribit `.raw` suffix, graceful nil-channel degradation. 6 tests in `normalizer_test.exs`.
- **HTX Ticker bid/ask Arrays** [D:4/B:7]`Safe.to_number/to_integer` extract first element from `[price, qty]` arrays. Generic fix for any exchange.
- **Kraken Ticker Field Names** [D:5/B:7] — ccxt_ex Task 241 added `array_indexed_accessor` to P1 MappingAnalyzer. ccxt_client Task 229b added `{:array_index, ...}` coercion.
- **OKX Order Book String Coercion** [D:3/B:6] — Generic list coercion in `coerce_levels/1` handles any-length level arrays.
- **OrderBook metadata nil** — Fallback key lookups for timestamp/symbol/nonce/datetime in `OrderBook.from_map/1`.
- **Normalization String Coercion Regressions** [D:3/B:7] — Schema-driven `ensure_numeric_types/2` safety net + fixture key fixes.
- **Pre-commit blocking** — Dialyzer PLT fix, runtime `Mix.env()` removed, doc warnings fixed.
### Completed Tasks (Current Focus)
- **Task 229c: MappingCompiler Hard Categories** [D:5/B:6/U:6]`passthrough` and `param_derived` P1 analysis categories. New `{:param, key}` coercion type reads values from endpoint function parameters (symbol, since, code) instead of response data. Threading: `param_map` injected into `opts` as `:_param_map` in `Dispatch.call/5`, extracted in `ResponseCoercer.coerce/4`, passed to `ResponseParser.parse_single/3`. Files: `mapping_compiler.ex` (2 new `compile_field` clauses + constant maps), `response_parser.ex` (`parse_single/3`, `apply_coercion/4`), `response_coercer.ex` (`coerce_typed/4`, `coerce_single/4`), `dispatch.ex` (both ccxt_client and ccxt_ex copies). Deferred `param_array_accessor` (19 Bitfinex-only entries) to Task 229d.
- **Task 229a: MappingCompiler Easy Categories** [D:3/B:7/U:7]`literal`, `wrapped_safe_accessor`, `dynamic_field_accessor` instruction types.
- **Task 229b: MappingCompiler Medium Categories** [D:5/B:8/U:8]`composed_parse_call` (enum_table + whitelisted functions) and `array_indexed_accessor`. New coercions: `{:enum_map}`, `{:array_index}`, `:iso8601_timestamp`, `:string_upper`.
- **Task 228: OHLCV Timeframe Translation** [D:3/B:7]`Helpers.translate_timeframe/2` + `translate_timeframe_value/2` for REST + WS. `ohlcv?` flag covers 4 endpoints.
- **Task 227: Compile-Time Performance** [D:6/B:9]`Dispatch.call/5` shared runtime dispatcher, `strip_dead_weight/1`. 93% compile-time reduction (35.7s → 2.5s).
- **Task 225: Normalization QA Sweep** [D:5/B:10] — 54 contract tests + 20 coverage tests across tier1 exchanges. No bugs found.
- **Task 125: Order Sanity Checks** [D:4/B:8] — Pre-submit validation.
### Completed Phases
- **Phase 1: Response Quality & Documentation** — Feature #1 (WS Reconnection Docs: llms.txt section 12, README guide, 15 behavioral tests) + Feature #2 (Symbol Precision Metadata: `CCXT.MarketPrecision` with `from_market/2`, `from_markets/2`, 3 precision modes).
- **Phase 5: Health & Monitoring**`CCXT.Health` module: `ping/1`, `latency/1`, `all/1`, `status/2`. 4 tasks complete.
### Quantex Extraction (v0.2.0)
Extracted `CCXT.Trading.*` (13 source + 17 test files, ~85 functions) to **quantex** (`../quantex`). 5 tasks: create project, move source/tests (struct→map decoupling, 355 tests), rename namespace (`Quantex.*`), update ccxt_client docs, prep v0.2.0 (delete trading files, bump version). See [quantex/ROADMAP.md](../quantex/ROADMAP.md).
---
## 🐛 Bug 9: Gate Normalization Gaps (2026-03-11)
### What was done
- **9a: Ticker string volumes** — Wrapped `base_volume`, `quote_volume`, `bid_volume`, `ask_volume` with `Safe.to_number/1` in `Ticker.from_map/1`. Root cause: Gate returns snake_case `"base_volume"` (string) which bypasses `ensure_numeric_types` (looks for `"baseVolume"`), and `from_map/1` had no coercion on the fallback path.
- **9b: Trade missing timestamp** — Added `"create_time_ms"`, `"create_time"`, `"time"` to `Trade.from_map/1` timestamp fallback key list. `Safe.timestamp/2` handles string→number parsing and seconds→ms conversion automatically.
- **9c: OrderBook missing timestamp** — Added `"current"` to `OrderBook.from_map/1` timestamp fallback key list (Gate uses `"current"` for ms timestamp in order book responses).
- **8 new tests** in `normalization_contract_test.exs` covering all three sub-issues: `"current"` key extraction, `"create_time_ms"` string parsing, `"create_time"` seconds→ms conversion, `"time"` float parsing, string volume coercion, numeric volume passthrough.
### Files changed
- `lib/ccxt/types/ticker.ex``Safe.to_number/1` wrapping on volume fields
- `lib/ccxt/types/trade.ex` — Gate fallback keys for timestamp
- `lib/ccxt/types/order_book.ex``"current"` fallback key for timestamp
- `test/ccxt/normalization_contract_test.exs` — 8 new Gate-specific tests
---
## Fix: ADL Type Module (2026-03-11)
### What was done
- **`CCXT.Types.Schema.ADL`** — Schema macro with 7 fields (raw, symbol, rank, rating, percentage, timestamp, datetime), following the established schema pattern.
- **`CCXT.Types.ADL`** — Concrete type module with `from_map/1` override for timestamp/datetime normalization via `Safe.timestamp` and `Helpers.derive_datetime`.
- **Resolved 11 Dialyzer warnings**`CCXT.Types.ADL.t/0` was referenced in generated typespecs for 11 exchange modules (Binance, Binance USDM/COINM, Bybit, HTX, etc.) but the module didn't exist. The `parseADLRank` method was added upstream (2026-02-23 sync) with return type `"ADL"` — the generator auto-constructs `CCXT.Types.ADL.t()` via `Module.concat`.
### Files created
- `lib/ccxt/types/schema/adl.ex`
- `lib/ccxt/types/adl.ex`
---
## 🐛 Bug 8: Timestamp/Datetime Normalization for Ticker, Order, Position (2026-03-11)
### What was done
- **Ticker, Order, Position `from_map/1`** — Added `Safe.timestamp` (seconds→ms detection via `< 10_000_000_000` threshold) and `derive_datetime`/`validate_datetime` to all three type modules, matching the pattern established in `Trade.from_map/1`. Fixes both REST and WS paths since they share the same pipeline.
- **12 new normalization tests** — 4 tests per type (Ticker, Order, Position): ms timestamp preserved, seconds→ms conversion, numeric string datetime rejected, valid ISO 8601 preserved.
### Files changed
- `lib/ccxt/types/ticker.ex``Safe` alias, timestamp/datetime normalization
- `lib/ccxt/types/order.ex``Safe` alias, timestamp/datetime normalization
- `lib/ccxt/types/position.ex``Safe` alias, timestamp/datetime normalization
- `test/ccxt/normalization_contract_test.exs` — 12 new tests
---
## 🐛 Bug: Kraken fetch_trades Field Mapping/Coercion (2026-03-11)
### What was done
- **`normalize_side` single-letter codes** — Added `"b"``:buy`, `"s"``:sell`, `"B"`, `"S"` clauses to `Helpers.normalize_side/1`. Generic — multiple exchanges use abbreviated side codes.
- **`Trade.from_map/1` timestamp + datetime** — Mirrors `OrderBook.from_map/1` pattern: `Safe.timestamp` normalizes seconds→ms, `derive_datetime/1` converts ms timestamp to ISO 8601 string. Handles Kraken's unix-seconds timestamps and exchanges that omit datetime.
- **`validate_datetime/1` guard** — Kraken's parser mapping incorrectly puts raw unix timestamp string (`"1773187841.535293"`) into `datetime` field. `validate_datetime` checks for ISO 8601 format (starts with `YYYY-`); rejects numeric strings so `derive_datetime` can produce correct ISO 8601 from the ms timestamp instead.
- **6 new normalization tests** — Kraken buy/sell side codes, seconds→ms timestamp conversion, ms timestamp passthrough with datetime derivation, numeric datetime rejection, valid ISO 8601 preservation, unit test for single-letter `normalize_side` variants.
### Files changed
- `lib/ccxt/types/helpers.ex` — 4 new `normalize_side` clauses
- `lib/ccxt/types/trade.ex``Safe` alias, timestamp normalization, datetime derivation, `derive_datetime/1`, `validate_datetime/1`
- `test/ccxt/normalization_contract_test.exs` — 6 new tests
### Verified
- 63/63 normalization contract tests pass
- Dialyzer: 0 new warnings
- Tidewave live: `CCXT.Kraken.fetch_trades("BTC/USD")` returns 1000 trades with correct `side`, `timestamp` (ms), `datetime` (ISO 8601), `price`, `amount`, `cost`
---
## ccxt_ex Sync #2 (2026-03-10)
### What was done
- **CCXT v4.5.40 → v4.5.42** — Upstream version bump
- **New exchange: Lighter** — DEX exchange added (module, WS module, spec, integration tests)
- **Removed exchange: Probit** — Dropped upstream (module, WS module, spec, integration tests removed)
- **4 new method signatures**`createTwapOrder` (TWAP order support), `fetchADLRank`, `fetchPositionADLRank`, `fetchPositionsADLRank` (ADL rank methods now have named params). Method count 172 → 176
- **16 new parse methods** — Own method count 1544 → 1560 across 111 exchanges (was 110)
- **75 new error codes** — Entry count 6137 → 6212 across 111 exchanges
- **Richer extractor data** — Significant expansion in `ccxt_parse_methods.json` (+2502 lines changed), `ccxt_error_codes.json` (+963 lines), `ccxt_symbol_formats.json` (+545 lines), `ccxt_emulated_methods.json` (+217 lines)
- **All 107 local specs refreshed** — Updated endpoints, line numbers, and metadata from upstream
### Exchange count
107 (net unchanged — lighter added, probit removed)
### Verified
- Compiles clean (2 minor pre-existing warnings: `:duration`/`:status` param types, `:watch_position` default)
- `verify_sync.sh`: spec integrity pass, contract tests pass (57/57), exchange count matches baseline
---
## ccxt_ex Sync (2026-03-10)
### What was done
- **Kraken `fetch_trades` partial fix** — New `:extract_first_list_value` response transformer successfully unwraps dynamic pair key (e.g., `"XXBTZUSD"`). Composed transformer `{:compose, [{:extract_path, ["result"]}, :extract_first_list_value]}` now returns a flat list of trade arrays. Still returns positional arrays, not `%Trade{}` — upstream needs a reshape transformer.
- **`CCXT.Extract.JsonLoader` macro** — Extracted shared JSON loading pattern (path resolution, persistent_term caching, `load/0`, `reload!/0`) from `EmulatedMethods` and `WsHandlerMappings`.
- **`Discovery.ex` fix** — Reverted `CCXT.Sync.Paths` reference (ccxt_ex-only module) back to inline path resolution.
### Known regressions
- ~~5 normalization contract tests fail (string coercion)~~**Resolved** in Bug Fix: Normalization String Coercion Regressions (below).
### Files changed
- `lib/ccxt/extract/json_loader.ex` — New shared macro
- `lib/ccxt/extract/emulated_methods.ex` — Uses JsonLoader
- `lib/ccxt/extract/ws_handler_mappings.ex` — Uses JsonLoader
- `lib/ccxt/exchange/discovery.ex` — Reverted Sync.Paths reference
- `lib/ccxt/response_transformer.ex` — New `extract_first_list_value` transformer + tests
- `priv/specs/extracted/kraken.exs` — Updated fetch_trades transformer
---
## Bug Fix: Normalization String Coercion Regressions (2026-03-10)
**Fixed** | [D:3/B:7 → 2.33]
### What was done
- **Schema-driven coercion safety net** — New compile-time `@type_coercion_maps` in `ResponseCoercer` maps each type atom to `%{source_key => :number | :integer}` from schema `@fields` metadata. `ensure_numeric_types/2` runs after `parse_single` but before `from_map`, coercing any remaining string values in numeric fields via `Safe.to_number/1` or `Safe.to_integer/1`. Zero runtime schema introspection.
- **Test fixture key corrections** — Binance ticker fixture: `"baseVolume"``"volume"` (real exchange key). OKX order fixture: `"amount"``"sz"` (real exchange key). Fixtures now match actual exchange response keys that parser instructions reference.
### Root cause
Two-fold: (1) test fixtures used unified CCXT key names instead of real exchange keys, so parser instructions couldn't find them; (2) `from_map/1` fallback lookups (e.g., `get_camel_value(map, :base_volume, :baseVolume)`) found the raw string values with no type coercion. The safety net ensures numeric fields are always numbers regardless of parser coverage gaps.
### Files changed
- `lib/ccxt/response_coercer.ex``@type_coercion_maps`, `ensure_numeric_types/2`, `coerce_field/2`, `apply_coercion/2`
- `test/ccxt/normalization_contract_test.exs` — Fixture key corrections (2 fixtures)
### Verified
- 57/57 normalization tests pass, 0 Dialyzer warnings, 0 regressions
---
## Task 125: Order Sanity Checks (2026-03-09)
**Completed** | [D:4/B:8 → 2.0]
### What was done
- **`CCXT.Order.Sanity` module** — Pure pre-submit order validation with 7 individual checks: `check_side`, `check_order_type`, `check_symbol`, `check_amount`, `check_price`, `check_cost`, `check_price_deviation`. Main `validate/3` runs all checks and collects all failures (non-short-circuit).
- **Dual input support** — Accepts both plain maps and `%CCXT.Order.Builder{}` structs via `normalize_order_params/1`.
- **Market-aware validation** — Checks limits (min/max) and precision (increment) from raw market maps or `%MarketPrecision{}` structs. Contract-aware notional calculation (linear vs inverse).
- **Exchange capability checks** — Uses `has` map for order type support, `features` for `market_buy_requires_price` (side-aware: only affects buys).
- **Nested feature normalization** — Accepts full exchange `spec.features`, market wrappers, and flattened `create_order` flags so `market_buy_requires_price` and related checks work with real extracted specs.
- **`market_buy_by_cost` support** — Market-buy quote-cost flows now validate directly against `limits.cost` and skip base-amount validation inside `validate/3`.
- **Precision-mode guardrails**`CCXT.MarketPrecision.from_market/2` and `from_markets/2` now return `{:error, :unsupported_precision_mode}` for modes `2` and `3`, preventing false increment rejections in sanity checks.
- **Warning system** — Price deviation returns warnings (not errors). `warnings: :strict` opt promotes warnings to errors. Three return shapes: `{:ok, params}`, `{:ok, params, warnings}`, `{:error, {:sanity_check, reasons}}`.
- **Discoverable via Descripex** — 7 `api()` declarations, added to `CCXT.Discoverable` modules list.
- **Bug fix during implementation**`||` operator treats `false` as falsy, causing `Map.get(map, key) || fallback` to skip valid `false` values. Fixed with `Map.fetch/2`-based `map_get_dual/3` helper.
### Files changed
- `lib/ccxt/order/sanity.ex` — New module
- `lib/ccxt/market_precision.ex` — Unsupported precision-mode handling for sanity-check consumers
- `test/ccxt/order/sanity_test.exs` — Comprehensive test suite
- `test/ccxt/market_precision_test.exs` — Unsupported precision-mode regression coverage
- `lib/ccxt.ex` — Added `CCXT.Order.Sanity` to Discoverable modules
- `test/ccxt/descripex_test.exs` — Added to `@all_annotated_modules` and `@discoverable_modules`
---
## Bug Fix: OKX Order Book String Coercion (2026-03-09)
**Fixed** | [D:3/B:6 → 2.0]
### What was done
- **Generic list coercion in `coerce_levels/1`** — Replaced exact 2-element `[price, amount]` pattern match with `level when is_list(level) -> Enum.map(level, &Safe.to_number/1)`. Coerces all elements to numbers regardless of array length (2, 3, 4+ elements).
- **Contract test assertion relaxed**`assert_order_book_contract/1` now uses `[price, amount | _rest]` to validate first two elements are numbers without requiring exactly 2 elements.
- **Tests added** — 4-element OKX-style levels, 3-element levels, mixed-length levels, and helper functions (`best_bid`/`best_ask`/`spread`) with 4-element levels.
### Files changed
- `lib/ccxt/types/order_book.ex``coerce_levels/1` generic list coercion
- `test/ccxt/types/order_book_test.exs` — 4 new tests for variable-length levels
- `test/ccxt/normalization_contract_test.exs` — Relaxed bid/ask level assertions
---
## Bug Resolved: Kraken Ticker Field Names (2026-03-09)
**Verified** — No code change needed in ccxt_client. Fixed by upstream work.
Kraken ticker single-letter keys (`"a"` → ask, `"b"` → bid, `"c"` → last, etc.) and array value extraction are now handled end-to-end:
- **ccxt_ex Task 241**: P1 MappingAnalyzer now generates `array_indexed_accessor` mappings for Kraken's ticker fields
- **ccxt_client Task 229b**: `{:array_index, index, inner_coercion}` coercion type in ResponseParser handles the extraction
Verified via live Tidewave audit: `CCXT.Kraken.fetch_ticker("BTC/USD")` returns populated `bid`, `ask`, `last`, `high`, `low`, `open`, `volume` fields as floats.
---
## Task 229b: MappingCompiler Medium Categories (2026-03-09)
**Completed** | [D:5/B:8/U:8 → Eff:1.6]
### What was done
- **`composed_parse_call` with `enum_table`** — Compiles to `{field, {:enum_map, extraction, table}, source_keys}` instruction. Extracts raw value via inner accessor, then looks up in the enum table (e.g., `parseOrderStatus` maps `"open"``"open"`, `"canceled"``"canceled"`). Falls through to the raw value when not in the table.
- **`composed_parse_call` with whitelisted functions**`parse8601`/`parseDate``:iso8601_timestamp`, `parseNumber``:number`, `parseToInt``:integer`. Three-level ISO 8601 fallback: full datetime with tz → naive (assumes UTC) → date-only (midnight UTC).
- **`array_indexed_accessor`** — Compiles to `{field, {:array_index, index, inner_coercion}, [source_key]}`. Indexes into array values at specific positions (e.g., Kraken/HTX returning `[price, qty]` arrays).
- **`:string_upper` coercion** — New `Safe.string_upper/2` for `safeStringUpper` safe_fn override.
- **ResponseParser `parse_single/2`** — Removed `source_keys != []` guard to allow `{:literal, _, []}` instructions.
- **Updated coercion typespec** — Added `{:enum_map, ...}`, `{:array_index, ...}`, `{:literal, ...}` to `@type coercion`.
### Tests
- `composed_parse_call` with enum_table: compilation, missing source keys, enum lookup/passthrough/nil behavior.
- `composed_parse_call` with whitelisted functions: parse8601 → iso8601_timestamp, parseNumber → number, parseToInt → integer, non-whitelisted functions skipped.
- `array_indexed_accessor`: compilation with safe_fn override, missing source key, nil at index.
- `string_upper`, `enum_map`, `iso8601_timestamp`, `array_index` coercion unit tests in `response_parser_test.exs`.
- No normalization contract test regressions.
---
## Task 229a: MappingCompiler Easy Categories (2026-03-09)
**Completed** | [D:3/B:7/U:7 → Eff:2.33]
### What was done
- **`literal` category** — Compiles to `{field, {:literal, value}, []}` instruction. ResponseParser returns the constant value directly without consulting response data. Used for hardcoded fields like `marginType: "isolated"`.
- **`wrapped_safe_accessor` category** — Unwraps `inner.fields` for source keys and `inner.safe_fn` for coercion overrides. Skips instances where inner has no `fields` key (computed inner values).
- **`dynamic_field_accessor` category** — Uses `possible_fields` list as source keys when present, enabling field lookup across multiple possible exchange key names.
- **ResponseParser `{:literal, value}` coercion** — New `apply_coercion/3` clause returns literal value directly.
- **`passthrough` deferred to 229c** — Requires param context (same architectural limitation as `param_derived`).
### Tests
- Unit tests for all three categories in `mapping_compiler_test.exs` covering compilation, edge cases (missing fields, missing schema keys), and coercion override behavior.
- No normalization contract test regressions.
---
## Bug Fix: `<generated>` placeholder resolution (2026-03-03)
### Fix
- **Expanded `@generated_timestamp_params`** in `lib/ccxt/generator/helpers.ex` — Added 11 missing timestamp param keys (`"to"`, `"from"`, `"end"`, `"start_ts"`, `"end_at"`, `"endAt"`, `"end_ts"`, `"before"`, `"after"`, `"until"`, `"time"`) to match the upstream ccxt_ex version. Fixes Bitopro regression where `"to" => "<generated>"` was dropped instead of resolved to a numeric timestamp.
- **Added `@start_timestamp_keys` MapSet** — Replaced fragile `String.contains?(k, "start")` heuristic with explicit set membership for determining start-of-window vs end-of-window timestamps.
### Root cause
`helpers.ex` is a diverged file (preserved during `mix ccxt.sync`). The upstream ccxt_ex version was updated with broader timestamp param coverage, but this copy was stale.
---
## ccxt_ex Sync #8 (2026-03-02)
### New Features
- **Spec format v2**`CCXT.Spec` now uses format version 2 with migration support from v1. Endpoint schema docs/types now include pagination metadata, path param transforms, and market-type-aware defaults.
- **Curated overlay merge system** — Added `CCXT.Spec.Merger` and `SpecLoader.load_spec_with_merge!/3`. When curated specs set `_curated_overrides: true`, extracted specs are used as base and deep-merged (including endpoint merge-by-name); otherwise curated remains winner-take-all.
- **Runtime dispatch support for new spec metadata**`Dispatch.call/5` now resolves `_by_market_type` defaults, injects transformed `path_params` placeholders, and passes pagination metadata through endpoint config.
- **Generator resource tracking for merged specs** — REST and WS generators now register all loaded spec files as `@external_resource` (curated and extracted when merge mode is active).
### Tests
- Added `CCXT.Spec.Merger` unit tests (`test/ccxt/spec/merger_test.exs`)
- Expanded spec loader merge-path coverage (`test/ccxt/generator/spec_loader_test.exs`)
- Expanded dispatch coverage for pagination metadata, `_by_market_type` defaults, path-parameter injection behavior, and related call-path scenarios (`test/ccxt/generator/dispatch_test.exs`)
- Updated `CCXT.Spec` tests for v2 format expectations and migration behavior (`test/ccxt/spec_test.exs`)
### Other
- Refreshed extracted exchange specs and extractor artifacts to emit v2-compatible endpoint metadata (pagination/default/path-param annotations) across the synced exchange set.
- Updated roadmap sync notes to 2026-03-02 with the new spec format and pagination groundwork.
---
## ccxt_ex Sync #7 (2026-02-28)
### New Features
- **Named params replace positional `arg0`/`arg1` placeholders** — 9 new method signatures extracted (`fetchOpenOrder`, `fetchClosedOrder`, `fetchDeposit`, `fetchWithdrawal`, `fetchDepositMethodId`, `fetchOrdersByStatus`, `fetchBorrowRateHistory`, `fetchVolatilityHistory`, `fetchWithdrawAddresses`), method count 163 → 172. Across 52 specs, positional `"arg0"`/`"arg1"` keys in `param_mappings` replaced with actual names (`:id`, `:symbol`, `:code`), and endpoint `params` lists populated with real param atoms.
### Other
- Refreshed `ccxt_symbol_formats.json` (minor market data fluctuations, example symbol changes)
---
## ccxt_ex Sync #6 (2026-02-28)
### New Features
- **`:expects` field on endpoints** — 3,716 new entries across all 107 specs (1,948 `:single`, 1,768 `:list`). Indicates whether an endpoint returns one item or a collection. Derived by the extractor's `infer_expects/1` from method names.
- **993 new `response_transformer` entries** — Automatically generated from `:expects`. Single-item endpoints get `extract_path_unwrap`, list endpoints get `extract_path`. Top paths: `["data"]` (593), `["result"]` (105), `["config"]` (48), `["results"]` (28).
- **`spec.ex` documentation** — Added doc comments for `:expects` and other optional endpoint fields.
### Tests
- Updated test helper message to reference `:expects` field instead of `@singular_endpoints`
### Other
- Refreshed all 107 exchange specs (non-functional: regenerated client_order_ids, timestamps)
---
## ccxt_ex Sync #5 (2026-02-28)
### New Features
- **Descripex integration**`CCXT.Credentials`, `CCXT.Validate`, and `CCXT.Multi` now have `api()` annotations for self-describing APIs. `CCXT` module uses `Descripex.Discoverable` for progressive discovery (`CCXT.describe/0-2`).
- **`descripex` added to deps** — Full dep (not dev-only), macros expand at compile time.
### Exchange Specs Updated
- **Coinbase** — 5 new `response_transformer` entries (`:extract_path` for accounts/orders/fills, `:extract_path_unwrap` for trades)
- **Gate** — 3 new `response_transformer` entries for deposit address endpoints
### Tests
- 6 descripex tests (hints metadata, discoverable modules, required keys)
- Test file now uses `Code.ensure_loaded?/1` to filter factory-only modules — same file works in both `ccxt_ex` and `ccxt_client`
### Other
- Refreshed all exchange specs (non-functional: regenerated client_order_ids)
- New lines in krakenfutures, onetrading, whitebit, zonda specs
---
## ccxt_ex Sync #4 (2026-02-28)
### New Features
- **`{:transpose_columns_to_rows, column_keys}` transformer** — Transposes column-oriented OHLCV responses (`%{"ticks" => [...], "open" => [...]}`) into standard row format (`[[ts, o, h, l, c, v], ...]`). This is CCXT's `convertTradingViewToOHLCV` pattern. Defensive: returns data unchanged if input is not a map, keys are missing, or values aren't lists.
- **`{:compose, [transformer]}` transformer** — Chains multiple transformers left-to-right via `Enum.reduce`. Enables multi-step response processing (e.g., extract envelope path, then transpose columns). Supports nesting.
### Exchange Specs Updated
- **Deribit** `fetch_ohlcv``{:compose, [extract_path: ["result"], transpose_columns_to_rows: ["ticks", "open", "high", "low", "close", "volume"]]}`
- **Mercado** `fetch_ohlcv``{:compose, [extract_path: ["response_data"], transpose_columns_to_rows: ["t", "o", "h", "l", "c", "v"]]}`
- **MEXC** `fetch_ohlcv``{:compose, [extract_path: ["data"], transpose_columns_to_rows: ["time", "open", "high", "low", "close", "vol"]]}`
### Verified
- Deribit `fetch_ohlcv("BTC-PERPETUAL", "1h")` returns `[%OHLCVBar{timestamp: ..., open: ..., ...}, ...]` end-to-end (column data → transpose → normalize → typed structs)
- Mercado/MEXC have pre-existing endpoint config issues (wrong route / invalid interval) unrelated to the transpose — transformer never reached
### Tests
- 6 compose tests (identity, single-element, Deribit pattern, generality proof, nested, nil propagation)
- 10 transpose tests (basic, ordering, single column, empty, missing key, non-list, non-map, mismatched lengths)
### Other
- Refreshed all exchange specs and extractor data (non-functional: timestamps, client_order_ids, sample market data)
---
## v0.3.1 (2026-02-25)
### New Features
- **`Multi` map variant** — All `Multi` functions now accept `%{exchange_module => symbol}` maps for per-exchange symbol formats (e.g., Bitfinex `"tBTCUSD"` alongside Bybit `"BTC/USDT"`). Applies to `fetch_tickers/1`, `fetch_order_books/1`, `fetch_ohlcv/2`, `fetch_trades/1`, and `parallel_call/3`.
- **`Multi.from_multi/1`** — Convenience alias for `successes/1`. Unwraps `{:ok, value}` tuples, discards errors.
- **`Multi.fetch_ohlcv` category inference** — Map variant auto-infers Bybit `"linear"`/`"inverse"` category from unified symbol format (`"BTC/USDT:USDT"``"linear"`, `"BTC/USD:BTC"``"inverse"`). Override via `:category` opt.
### Bug Fixes
- **HTTP Client response error patterns**`body_indicates_error?/2` now handles `:success_message` (BitMart-style), `:http_status_only` (BitMEX-style), and unknown types with a safe catch-all. `response_error_config` `:field` is now optional to support exchanges that only use HTTP status codes.
- **HTX Ticker bid/ask arrays**`Safe.to_number/1` and `Safe.to_integer/1` now extract the first element from `[price, qty]` arrays before coercion. HTX (and similar exchanges) return bid/ask as `[65428.46, 0.002025]`; `ticker.bid` and `ticker.ask` are now scalar floats. Generic fix — works for any exchange returning arrays where a scalar is expected.
- **Bybit OrderBook normalization**`fetch_order_book` with `normalize: true` now returns proper `CCXT.Types.OrderBook` struct with `:asks`/`:bids` keys instead of raw exchange keys (`"a"`/`"b"`)
- **Bybit Ticker normalization**`fetch_ticker` with `normalize: true` now returns proper `CCXT.Types.Ticker` struct with snake_case keys instead of raw exchange keys (`"lastPrice"`)
- **Multi / Deribit export**`fetch_ticker/1` now exported correctly; multi call arg forwarding and module load checks fixed
- **Pipeline default** — Replaced fragile `Code.ensure_loaded?/apply` with `@pipeline_default` module attribute in both REST and WS generators. Proper compile-time dependency instead of silent `[]` fallback.
### Other
- Responses are now proper typed structs with raw data preserved in `:raw` key
- Refreshed all exchange specs via extractor (non-functional: regenerated timestamps and client_order_ids)
---
## ccxt_ex Sync #3 (2026-02-25)
**What was synced:**
- **Multi map variant** — Per-exchange symbol support in all `Multi` functions + `from_multi/1` alias + OHLCV category inference (see v0.3.1 New Features above)
- **Full spec refresh** — All 42 locally-tracked exchange specs re-extracted (non-functional: timestamps, client_order_ids)
- **Extractor data refresh**`ccxt_emulated_methods.json`, `ccxt_error_codes.json`, `ccxt_parse_methods.json`, `ccxt_symbol_formats.json` updated
---
## v0.3.0 (2026-02-24)
### Highlights
- **93% compile-time reduction** — New `Dispatch.call/5` shared runtime dispatcher replaces per-endpoint inline code generation. Binance module: 28.1s → 1.05s. Full suite: 35.7s → 2.5s.
- **CCXT.Health module** — Stateless exchange health checks: `ping/1`, `latency/1`, `status/2`, `all/1` (concurrent bulk check).
- **OHLCV timeframe translation** — Unified timeframes (`"1h"`, `"1d"`) automatically translated to exchange-native format for both REST and WebSocket.
- **Normalization QA sweep** — 54 contract tests across 5 tier1 exchanges × 3 endpoints. Parser coverage tests with hard assertions for tier1. No bugs found.
- **OrderBook metadata fix**`timestamp`, `symbol`, `nonce`, `datetime` now correctly populated via fallback key lookups for exchanges using non-standard keys (Bybit, OKX, etc.).
- **CCXT v4.5.40 sync** — Updated specs and extractor data, including new `parseADLRank` method and Deribit funding rate improvements.
### Breaking Changes
None. All changes are additive or internal.
### New Features
- `CCXT.Health``ping/1`, `latency/1`, `all/1`, `status/2` for exchange health monitoring
- `CCXT.Generator.Dispatch` — Shared runtime dispatcher with pre-computed `@endpoint_configs`
- `CCXT.Generator.Helpers.translate_timeframe/2` — REST timeframe translation in `Dispatch.call/5` pipeline
- `CCXT.Generator.Helpers.translate_timeframe_value/2` — WS timeframe translation in generated OHLCV subscriptions
- Pipeline module — Single source of truth for default pipeline config (fixes normalization as path dep)
- `MappingCompiler``boolean_derivation` category and `safe_fn` override support
- `ResponseParser``{:bool_enum, true_value, false_value}` coercion for boolean-derived fields
- `ResponseCoercer` — Auto-injects `"info"` key before parsing for raw field population
### Bug Fixes
- OrderBook metadata (`timestamp`, `symbol`, `nonce`, `datetime`) nil for non-standard exchange keys
- OrderBook levels returning strings instead of floats
- Pipeline default not loaded when compiled as path dependency
- Dialyzer warnings from `Mix.env()` runtime call in `CCXT.Defaults`
- Doc warnings from ex_doc-incompatible link syntax
- Param precedence: `default_params` no longer overrides function args when `param_mappings` renames keys
### Performance
- Compile time: 35.7s → 2.5s (93% reduction)
- Binance module: 28.1s → 1.05s
- Root cause: 98% of .beam size was inline code per endpoint
- `strip_dead_weight/1` removes extraction-only data before `Macro.escape`
### Syncs
- CCXT v4.5.39 → v4.5.40
- New `parseADLRank` parse method (Binance, Bitmex, Bybit, Htx)
- Deribit `parseFundingRate` improvements (nextFundingRate, nextFundingTimestamp, nextFundingDatetime)
- Param precedence fix and symbol defaults removed from specs
- Spec cost/weight sync across all tier1 exchanges
- 107 exchanges bundled (synced for publish)
### Tests
- 54 normalization contract tests (5 exchanges × 3 endpoints + order book)
- 20 parser coverage tests with tier1 hard assertions
- 29 dispatch tests (config building, param merging, path interpolation, auth, OHLCV)
- 18 timeframe translation tests (REST + WS)
- 13 OrderBook metadata tests
- 11 Health module tests
---
## Fix: OrderBook metadata fields nil for non-standard keys (2026-02-24)
**What was done:**
- **Fallback key lookups in `OrderBook.from_map/1`**`timestamp`, `symbol`, `nonce`, and `datetime` were all nil for exchanges using non-standard keys (e.g., Bybit: `"ts"`, `"s"`, `"u"`). Added `Safe.timestamp/2`, `Safe.string/2`, `Safe.integer/2` with fallback key lists.
- **`derive_datetime/1`** — New private helper derives ISO 8601 datetime string from millisecond timestamp via `DateTime.from_unix/2`.
- **Fallback key coverage**: `timestamp``["timestamp", "ts", "T"]`, `symbol``["s", "instrument_name", "instId"]`, `nonce``["u", "lastUpdateId", "change_id", "seq"]`
- **Contract test assertions strengthened**`assert_order_book_contract/1` now checks all metadata fields (timestamp, symbol, nonce, datetime) for correct types.
- **Bybit-specific contract test** — Verifies all metadata fields are populated from non-standard keys through the full normalization pipeline.
**Safe from sync:** `lib/ccxt/types/order_book.ex` is runtime code owned by this repo — not overwritten by `mix ccxt.sync`. When the extractor improves upstream, fallbacks become no-ops.
**Tests:** 13 new tests (12 unit in `order_book_test.exs`, 1 contract in `normalization_contract_test.exs`). 81 total, 0 failures.
**Live verified:** Bybit `fetch_order_book("BTC/USDT:USDT")``timestamp=1771929865793`, `symbol="BTCUSDT"`, `nonce=21077445`, `datetime="2026-02-24T10:44:25.793Z"`. Deribit `fetch_order_book("BTC-PERPETUAL")` → all fields populated (no regression).
---
## ccxt_ex Sync #2 (2026-02-24)
**What was synced:**
- **Pipeline default fix** — Replaced fragile `Code.ensure_loaded?/apply` pattern with `@pipeline_default Pipeline.default()` module attribute in both `CCXT.Generator` and `CCXT.WS.Generator`. The old pattern silently fell back to `[]` when `Pipeline` wasn't compiled yet, breaking normalization when ccxt_client was used as a dependency. The new approach creates a proper compile-time dependency.
- **Refreshed 15 exchange specs** — alpaca, aster, binancecoinm, binanceusdm, bitmart, cex, coinbase, coinex, delta, gemini, indodax, phemex, poloniex, probit, whitebit. Non-functional changes: regenerated `client_order_id` UUIDs and timestamps from re-extraction.
---
## ccxt_ex Sync (2026-02-24)
**What was synced:**
- **CCXT v4.5.40** — Updated from v4.5.39
- **Deribit `parseFundingRate` improvements**`nextFundingRate`, `nextFundingTimestamp`, `nextFundingDatetime` now populated (were `undefined`). `fundingRate` now uses `safeNumber2` for fallback key support.
- **New WS handler mappings**`push.funding.rate` and `funding_data` channel handlers added
- **Parse methods** — 1542 → 1544 methods across 110 exchanges
- Updated extractor data: `ccxt_emulated_methods.json`, `ccxt_error_codes.json`, `ccxt_parse_methods.json`, `ccxt_symbol_formats.json`, `ccxt_ws_handler_mappings.json`
- Updated specs: Binance, Bitmex, Coinbase Exchange, Deribit, Gate, Htx, OKX
---
## Fix: Pre-commit Blocking Issues (2026-02-23)
**What was done:**
- **Dialyzer PLT** — Added `plt_add_apps: [:mix]` to `mix.exs` project config. Resolves 8 warnings for legitimate Mix usage in generator and mix task code.
- **Runtime `Mix.env()` removed**`CCXT.Defaults.retry_policy/0` no longer calls `Mix.env()` at runtime (architectural violation, breaks releases). Now uses `Application.get_env/3` with `:safe_transient` default.
- **Per-environment config** — Added `config/test.exs` (`retry_policy: false`), `config/dev.exs`, `config/prod.exs`, and `import_config` in `config/config.exs`.
- **Doc warnings fixed** — Removed ex_doc-incompatible link syntax: ROADMAP.md link → plain text (not in `extras` list), backtick-quoted Pipeline module → plain text (has `@moduledoc false`).
**Verified:** 0 dialyzer warnings, 0 doc warnings, 2795 tests passed.
---
## ccxt_ex Sync (2026-02-23)
**What was synced:**
- **New `parseADLRank` parse method** — Auto-deleverage rank parsing added upstream for Binance, Bitmex, Bybit, Htx. Returns ADL type with `rank`, `rating`, `symbol`, `timestamp` fields. Exchange-specific field mappings (`adlRisk`, `adlQuantile`, `deleveragePercentile`, `adlRankIndicator`, `adl_risk_percent`).
- Updated extractor data: `ccxt_emulated_methods.json`, `ccxt_error_codes.json`, `ccxt_parse_methods.json`, `ccxt_symbol_formats.json`
- Updated specs: Binance, Bitmex, Bybit, Coinbase Exchange, Gate, Htx
---
## Task 228: OHLCV Timeframe Translation (2026-02-23)
**Completed** | [D:3/B:7 → Priority:2.33]
**What was done:**
- **`Helpers.translate_timeframe/2`** — Translates `:timeframe` value in a params map to exchange-native format (e.g., `"1h"``"60"` for Bybit). Called by `Dispatch.call/5` for all OHLCV endpoints, before param mapping. Empty-map guard for fast-path no-op.
- **`Helpers.translate_timeframe_value/2`** — Translates a single timeframe string. Called by generated WS OHLCV subscription functions. Simple `Map.get` with passthrough fallback.
- **`Dispatch.call/5` integration** — Timeframe translation runs after timestamp conversion and before path interpolation. Applied to all 4 OHLCV endpoints: `fetch_ohlcv`, `fetch_index_ohlcv`, `fetch_mark_ohlcv`, `fetch_premium_index_ohlcv`.
- **WS generator integration**`generate_watch_functions/2` accepts optional `timeframes` map. OHLCV subscription functions call `translate_timeframe_value/2` before channel formatting.
- **`ohlcv?` flag expanded** — Now covers all 4 OHLCV endpoint variants (previously only `fetch_ohlcv`).
**Tests:** 8 new unit tests in `helpers_test.exs` (translate/passthrough/empty map for both functions). 5 tests in `dispatch_test.exs` (REST translation, passthrough, non-OHLCV unaffected). 5 tests in `generator_functions_test.exs` (WS translation, passthrough, default timeframe, empty map, non-OHLCV unaffected).
---
## Task 227: Compile-Time Performance — Shared Runtime Dispatch (2026-02-23)
**Completed** | [D:6/B:9 → Priority:1.5]
**What was done:**
- **`CCXT.Generator.Dispatch`** — New shared runtime dispatcher. Generated endpoint functions are now thin wrappers calling `Dispatch.call/5` instead of inlining ~30 lines of pipeline logic per endpoint (87-99 times per exchange). Pipeline: param merging → emulation dispatch → path interpolation → prefix handling → HTTP execution.
- **`build_configs/2` / `build_config/3`** — Called at compile time to pre-compute per-endpoint configuration (path style, reversed mappings, prefix strategy). Stored as `@endpoint_configs` module attribute, accessed via `__endpoint_config__/1`.
- **`strip_dead_weight/1`** — Removes extraction-only data (`parse_methods` ~170KB, `handle_errors_source` ~4KB) from specs before `Macro.escape`. Spec `.exs` files remain complete; stripping happens only at compile time.
- **Introspection deduplication** — Map-typed introspection functions (e.g., `__ccxt_raw_endpoints__/0`) now reference `@ccxt_spec` directly instead of each having its own `Macro.escape` literal. Only scalars (comment, certified, pro, dex, precision_mode) remain inlined.
- **Endpoints.ex simplification** — Removed ~250 lines of per-endpoint inline code generation (path interpolation, prefix building, client opts assembly, OHLCV conversion). All moved to `Dispatch`.
- **Updated extractor data**`ccxt_emulated_methods.json`, `ccxt_error_codes.json`, `ccxt_parse_methods.json`, `ccxt_symbol_formats.json` synced from ccxt_ex.
- **Updated specs** — Binance, Bitmex, Coinbase Exchange, Deribit, Gate, Htx, OKX specs refreshed.
**Performance impact:**
- Before: 35.7s wall clock, Binance alone 28.1s
- After: 2.5s wall clock, Binance 1.05s — **93% reduction**
- Root cause: 98% of .beam size was inline code per endpoint, not spec data
**Tests:** 29 new tests in `dispatch_test.exs` (config building, param merging, path interpolation, prefix strategies, auth, OHLCV, emulation, coercer). 3 new tests in `generator_test.exs` for `__endpoint_config__/1`.
---
## ccxt_ex Sync (2026-02-22)
**What was synced:**
- **Param precedence fix:** New `strip_overridden_defaults/3` in `CCXT.Generator.Helpers` prevents `default_params` from overriding function args when `param_mappings` renames keys (e.g., `symbol``instId` on OKX). Called in generated endpoint functions before the merge chain.
- **Symbol defaults removed from specs:** Hardcoded `"symbol"` entries removed from `default_params` across all 9 tier1 exchange specs (binance, bitmex, bybit, coinbaseexchange, deribit, gate, kraken, kucoin, okx). Symbol now flows exclusively through `param_mappings`.
- **Updated extractor data:** `ccxt_parse_methods.json` (expanded parse method coverage), `ccxt_error_codes.json`, `ccxt_symbol_formats.json`, `ccxt_emulated_methods.json`
**Verified:** 2947 passed, 0 code failures (15 failures + 178 invalid all credential-related)
---
## Phase 5: Health & Monitoring
### CCXT.Health Module
**Completed** | `Health.latency/1` [D:1/B:7 → 7.0], `Health.ping/1` [D:2/B:8 → 4.0], `Health.all/1` [D:3/B:8 → 2.67], `Health.status/2` [D:3/B:7 → 2.33]
**What was done:**
- `CCXT.Health` module at `lib/ccxt/health.ex` — stateless, one-shot exchange health checks
- `ping/1` — checks exchange reachability via `fetch_time/1`, returns `:ok` or `{:error, Error.t()}`
- `latency/1` — wall-clock round-trip measurement in milliseconds (float), wraps `fetch_time/1` with `System.monotonic_time()`
- `all/1` — concurrent bulk health check via `Task.async_stream/3` with `on_timeout: :kill_task`, returns `%{exchange => :ok | {:error, term()}}`
- `status/2` — composite snapshot combining reachability, latency, and `CircuitBreaker.status/1`
- Private `resolve_module/1` validates exchange atom → module with `__ccxt_spec__/0` check
- 11 tests (6 unit, 5 integration) covering error paths, concurrency, and real exchange calls
**Design decisions:**
- Wall-clock measurement around `fetch_time` (not Finch telemetry) — simpler, gives total round-trip including signing and rate-limit wait
- No struct for status result — plain map keeps the API simple and extensible
- `resolve_module/1` checks `__ccxt_spec__/0` export to distinguish real exchange modules from other `CCXT.*` modules
---
## Task 225: Normalization QA Sweep (2026-02-22)
**Completed** | [D:5/B:10 → Priority:2.0]
**What was done:**
- **Normalization contract tests** (`test/ccxt/normalization_contract_test.exs`) — 54 tests covering 5 tier1 exchanges × 3 endpoints (ticker, trade, order) + 2 order book tests (Bybit with parser mapping, baseline without)
- **Parser coverage tests** (`test/ccxt/parser_coverage_test.exs`) — 20 tests: tier1 hard assertions, cross-validation (method_schemas ⊆ type_modules), coverage inventory, known exceptions
- **Contract assertions**: numeric fields are `number() | nil` (never strings), enum fields (`side`, `status`, `taker_or_maker`) are atoms (never strings/booleans), `timestamp` is `integer() | nil`, `raw` is always a map
- **List-shape contracts**: `:trades` and `:orders` coerce lists of raw maps to `[%Trade{}]` and `[%Order{}]`
- **Exchange-specific coercion tests**: Binance bool→side, Bybit isMaker→taker_or_maker, Deribit liquidity→taker_or_maker, string_lower normalization, string timestamp→integer
- **Known exceptions documented**: `:order_book` (unified keys), `:balance` (nested maps)
**Triage result:** No bugs found — pipeline works correctly for all tier1 exchanges.
**Coverage snapshot:** Binance/Bybit 76.7%, OKX/Gate 63.3%, Htx 53.3%, Deribit 43.3%, Kucoin 40%, Bitmex 36.7%, Kraken 26.7%, Coinbase 20%
**Run with:** `mix test.json --quiet --only normalization` and `mix test.json --quiet test/ccxt/parser_coverage_test.exs`
---
## Fix: OrderBook levels return strings instead of floats (2026-02-21)
**What was done:**
- **Level coercion**`OrderBook.from_map/1` now coerces string bid/ask level values to floats via `Safe.to_number/1`. Previously, `fetch_order_book(normalize: true)` returned `[["67795.8", "1.636265"]]` instead of `[[67795.8, 1.636265]]`. Root cause: MappingCompiler's `type_to_coercion/1` fell through to `:value` (passthrough) for `[[number() | nil]]` schema type.
- **Raw field fix**`book.raw` now uses precedence chain `raw || info || map` instead of always falling back to the enriched parsed map. Preserves original exchange payload.
- **Test update** — WS normalizer test updated to assert coerced floats instead of raw strings.
**Related to:** Task 225 (Normalization QA Sweep) — proactive fix before sweep
---
## Pipeline default for deps + maybe_coerce warning (2026-02-21)
**What was done:**
- **Pipeline default hardcoded** — Pipeline module provides single source of truth for default pipeline config. Both REST (`CCXT.Generator`) and WS (`CCXT.WS.Generator`) generators use `Pipeline.default()` as fallback instead of `[]`. Fixes normalization not working when ccxt_client is compiled as a path dependency (dep config files not loaded by parent app).
- **maybe_coerce warning**`maybe_coerce/5` nil-coercer path now logs actionable warning with `response_type` when `normalize: true` (the default) is active but no coercer is configured.
**Verified:** 2649 tests, 0 failures (398 excluded)
---
## Task 224: Normalization — boolean_derivation, safe_fn, info injection (2026-02-21)
**What was done:**
- **MappingCompiler: `boolean_derivation` category** — Compiles P1 `boolean_derivation` entries to `{:bool_enum, true_value, false_value}` tagged tuple coercion (34 occurrences across exchanges, e.g., Binance `side` from `"m"` boolean)
- **MappingCompiler: `safe_fn` override**`resolved_safe_accessor` entries with `safe_fn` field now override schema-derived coercion (e.g., Bybit `side``:string_lower`, `timestamp``:integer`)
- **ResponseParser: `{:bool_enum, tv, fv}` coercion** — New `apply_coercion/3` clause with dedicated `find_boolean/2` that correctly handles `false` as a found value (unlike `Safe.value` which treats `false` as falsy)
- **ResponseCoercer: info injection**`coerce_single/3` injects `"info" => data` before parsing so `from_map`'s `:raw`/`:info` field gets populated. Doesn't overwrite existing `"info"` or `:info` keys
- **Helpers: capitalized side normalization** — Added `"Buy"`, `"Sell"`, `"Long"`, `"Short"` clauses to `normalize_side/1`
- **Typespecs** — Widened instruction type across 7 locations to accept tagged tuple coercion
**ccxt_ex:** Linked to Tasks 221-223 (P1 analysis improvements)
**Verified:** 2645 tests, 0 failures (398 excluded)
---
## v0.2.1 — Full Exchange Sync (2026-02-21)
**107 exchanges** now bundled (up from 10 tier1/tier2). Synced from CCXT v4.5.39.
- Full exchange sync: 97 new exchange modules, specs, and test files (433 files total)
- New WS methods: `watch_position_for_symbols`, `watch_private_multiple`, `watch_public`, `watch_multiple`, `watch_public_multiple`
- Updated extractor data: symbol formats, emulated methods, error codes, parse methods
- Fixed version references in README.md and llms.txt (`~> 0.1``~> 0.2`)
- Moved `TRADING_BACKLOG.md` to quantex as `ROADMAP.md`
**Verified:** 6650 tests, 3852 passed, 0 code failures (all failures credential-related)
---
## v0.2.0 — Trading Modules Removed (Breaking)
**Breaking:** `CCXT.Trading.*` modules (Funding, Greeks, Risk, Basis, Sizing,
Volatility, Options, Portfolio, PowerLaw, and helpers) have been removed.
These trading analytics have been extracted to a separate library.
ccxt_client is now a focused exchange-access library — HTTP, WebSocket,
signing, symbol normalization, and types only.
No changes to exchange API functionality.
---
## Spec Cost Sync + Test Improvement (2026-02-20)
**What was done:**
- **Spec cost/weight sync:** Updated rate limit costs across all 10 Tier 1 exchange specs (binance, bitmex, bybit, coinbaseexchange, deribit, gate, htx, kraken, kucoin, okx) from upstream CCXT
- **Test improvement:** `test_since_value/2` in `PublicEndpointsTest` now derives `since` requirements from endpoint spec metadata (`required_params`, `param_mappings`, path patterns) instead of hardcoding exchange IDs — resolves inline TODO
**Verified:** 3380 tests, 0 code failures (15 failures + 178 invalid all credential-related)
---
## ccxt_ex Sync (2026-02-20)
**What was synced:**
- **Bug #1 resolved:** `CCXT.Deribit.fetch_trades/1` now exists (optional param arity fix, Task 176)
- **Bug #2 resolved:** Deribit WS balance subscription builds correct channel (Task 177)
- **Bug #3 resolved:** Deribit WS ticker subscription includes interval suffix (Task 177)
- **Adapter refactor:** Monolithic generator functions decomposed into focused sub-generators (`_ast` naming convention)
- **Deribit spec updates:** `response_transformer` added to ~12 endpoints, `auth_required: true` on all private WS channel templates, updated symbol format samples, `api_sections` map
- **Generator updates:** Pipeline-based coercer/parser decoupling, version-override path prefix handling, dynamic app name resolution
**Verified:** 3380 tests, 0 code failures (15 failures + 178 invalid all credential-related)
---
## Phase 1: Response Quality & Documentation
### Feature #2: Symbol Precision Metadata
**Completed** | [D:4/B:8 → Priority:2.0]
**What was done:**
- `CCXT.MarketPrecision` module at `lib/ccxt/market_precision.ex`
- `from_market/2` — builds precision struct from a single market map
- `from_markets/2` — builds precision map for all symbols on an exchange
- `tradingview_price_format/1` — TradingView-compatible price format
- `decimal_places/1`, `increment_from_decimals/1` — precision mode conversions
- Handles all 3 CCXT precision modes: TICK_SIZE, DECIMALS, SIGNIFICANT_DIGITS
**Linked to:** ccxt_ex Task 178 (Normalized Market Precision Metadata)
---
## Pre-existing (included in v0.1.1)
The following roadmap items were found to already exist in the initial release:
- **`Symbol.normalize/2` + `Symbol.denormalize/2`** (Phase 17) — `CCXT.Symbol` provides bidirectional normalization
- **`Symbol.to_exchange_id/3` + `Symbol.from_exchange_id/3`** (Phase 17) — pattern-based exchange format conversion
- **`RateLimit` visibility** (Phase 20) — `CCXT.HTTP.RateLimiter` provides per-credential sliding window rate tracking
---
## v0.1.1 — Initial Release
- Elixir client for 110+ cryptocurrency exchanges
- 7 signing patterns covering 95%+ of exchanges
- Unified API: fetch_ticker, fetch_order_book, create_order, fetch_balance, etc.
- WebSocket support with automatic reconnection
- Type-safe response structs (Ticker, Order, Balance, Position, etc.)
- Bidirectional symbol normalization (unified ↔ exchange format)
- Circuit breaker and rate limiting
- Selective compilation (configure which exchanges to compile)
Generated from [ccxt_ex](https://github.com/ZenHive/ccxt_ex), extracted from
CCXT's 7+ years of accumulated exchange knowledge.