# `DpExchange.Core.Types.FxRate`
[🔗](https://github.com/DistortionPoint/dp-exchange-core/blob/v0.3.120/lib/dp_exchange/core/types/fx_rate.ex#L1)

A foreign-exchange reference rate a venue publishes for a past instant.

## This is not a price the venue traded at

Every other rate in this contract is the venue's own market. This one is not: Gemini's
documentation says plainly that it *"does not offer foreign exchange services"* and that
the endpoint is *"for historical reference only"*. The number comes from a third-party
source the venue names, and the venue is relaying it.

So `:source` and `:benchmark` are carried and are not decoration. `%{source: "bcb",
benchmark: "Spot"}` says which institution published the rate and which of its series it
came from. **Two venues relaying the same pair at the same instant can legitimately
disagree**, and a caller reconciling them needs to know it is comparing two sources rather
than finding a bug.

## `:as_of` is the rate's instant, not when it was fetched

The endpoint takes a timestamp and answers for it. `:as_of` is that instant, echoed by the
venue, and it is the whole content of the request — a rate without it is a number with no
time attached, which is not a rate.

## `:provider` is the venue; `:source` is whoever computed the rate

Two different facts that both look like "where this came from". Collapsing them would make
a Gemini-relayed BCB rate indistinguishable from one Gemini computed itself, and only the
second would be the venue's own claim.

# `t`

```elixir
@type t() :: %DpExchange.Core.Types.FxRate{
  as_of: DateTime.t(),
  benchmark: String.t() | nil,
  pair: String.t(),
  provider: atom() | String.t(),
  rate: Decimal.t(),
  source: String.t() | nil
}
```

# `convert`

```elixir
@spec convert(t(), Decimal.t()) :: Decimal.t()
```

Converts `amount` at this rate.

Straight multiplication, unrounded: `Decimal` is exact and the currency's precision is the
caller's to apply, knowing which currency it holds.

**This does not check the pair.** A caller converting AUD with a `GBPUSD` rate gets a
number, and no type can tell it that was wrong without knowing which side of the pair the
amount is in — which the venue's `fxPair` string alone does not say.

# `new`

```elixir
@spec new(keyword() | map()) :: t()
```

Builds a `t:t/0`, failing closed if a required field is absent or `nil`.

`@enforce_keys` guards presence, not `nil` — see `DpExchange.Core.Types.Validate`.

Also raises on a `:rate` of zero or less: a rate a caller multiplies an amount by is above
zero, and a zero would turn every conversion into zero with no error.

---

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