# `BankingCircle.FX`
[🔗](https://github.com/iamkanishka/banking_circle/blob/main/lib/banking_circle/fx.ex#L1)

Foreign exchange: market-order trading, Request-for-Quote (RFQ),
indicative rates, held rates, and trade/exposure lookups.

## The three trade-execution shapes

Banking Circle's FX API has one execution endpoint
(`POST /api/v2/fx/trading`) but three ways to arrive at it:

  1. **Market order** — `trade/2` with no `quote_id`: filled instantly at
     the prevailing market rate.
  2. **RFQ-then-trade** — `request_quotes/2` to get a firm, 30-second
     tradable quote, then `trade/2` passing that `quote_id`. Do **not**
     pass a `tenor` when trading against a quote — it's derived from the
     quote itself.
  3. **Held-rate-then-trade** — `request_quotes/2` with
     `request_type: :held_rate` (or `held_rate/3` for the simpler
     single-pair GET form) to lock a rate for up to 24 hours, then
     `trade/2` against its `quote_id`, any number of times within the
     held period and your daily limits.

`request_quotes/2` accepts a **list** of quote requests and returns a
matching list of quotes — Banking Circle supports batching multiple
currency pairs (even across `customer_id`s) into one round trip, matched
back up via the caller-supplied `quote_request_id` on each entry.

## Multi-entity note

If your Banking Circle setup spans multiple legal entities, `customer_id`
is required on every FX request and must match the entity that owns the
sell/debit account — fetch it from `BankingCircle.Accounts` (the
`companyNumber` field) if you don't already have it cached.

# `client`

```elixir
@type client() :: atom()
```

# `request_type`

```elixir
@type request_type() :: :indicative | :rfq | :payment_rfq | :held_rate
```

# `tenor`

```elixir
@type tenor() :: :on | :tn | :spot
```

# `exposure`

```elixir
@spec exposure(String.t(), client()) ::
  {:ok, map()} | {:error, BankingCircle.Error.t()}
```

FX exposure for a specific customer/legal entity.

# `get_transaction`

```elixir
@spec get_transaction(String.t(), client()) ::
  {:ok, map()} | {:error, BankingCircle.Error.t()}
```

Looks up a single FX trade by your `clientOrderId`. Also matchable via
`bankingCircleRef`, which additionally shows up in the account
reconciliation report's `PaymentDetails1` field for cross-referencing.

# `held_rate`

```elixir
@spec held_rate(String.t(), String.t(), pos_integer(), keyword(), client()) ::
  {:ok, map()} | {:error, BankingCircle.Error.t()}
```

Requests a held rate for a currency pair via the simpler GET form
(`GET /api/v1/fx/rates/held-rates/{ccy1}/{ccy2}`), valid for
`valid_for_minutes` (up to 1440 / 24h).

# `indicative_rate`

```elixir
@spec indicative_rate(map(), client()) ::
  {:ok, map()} | {:error, BankingCircle.Error.t()}
```

Convenience wrapper around `request_quotes/2` for a single indicative rate lookup.

# `list_held_rates`

```elixir
@spec list_held_rates(
  keyword(),
  client()
) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
```

Lists all currently active held rates for the client.

# `list_transactions`

```elixir
@spec list_transactions(
  keyword(),
  client()
) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
```

Paginated FX trade history, filterable (currency pair, date range, status, etc. — see API reference).

# `request_quotes`

```elixir
@spec request_quotes([map()], client()) ::
  {:ok, [map()]} | {:error, BankingCircle.Error.t()}
```

Requests one or more quotes in a single round trip. `requests` is a list
of maps, each requiring `:quote_request_id`, `:customer_id`,
`:amount_currency`, `:amount`, `:tenor`, `:request_type`, and either
`:currency_pair` or both `:currency_one`/`:currency_two`.

Returns `{:ok, [quote, ...]}` in the same order as the input, matched
via each entry's `quoteRequestId`.

# `settlement_dates`

```elixir
@spec settlement_dates(
  keyword(),
  client()
) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
```

Available settlement dates for FX trades, accounting for holidays and cutoff times.

# `trade`

```elixir
@spec trade(map(), client()) ::
  {:ok, map()} | {:error, BankingCircle.Error.t() | {:missing_field, atom()}}
```

Executes an FX trade — market order if `attrs` has no `:quote_id`,
or against a previously-obtained quote (RFQ or held rate) if it does.

Required: `:client_order_id`, `:buy_currency`, `:sell_currency`,
`:amount`, `:amount_currency`. Either `:tenor` (market order) or
`:quote_id` (trading against a quote) — not both.

# `transactions_csv`

```elixir
@spec transactions_csv(
  keyword(),
  client()
) :: {:ok, binary()} | {:error, BankingCircle.Error.t()}
```

Same trade history as `list_transactions/2` in CSV form, for bulk analysis/reporting.

---

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