# `Forex`
[🔗](https://github.com/greven/forex/blob/1.1.2/lib/forex.ex#L1)

`Forex` is a simple Elixir library that serves as a wrapper to the
foreign exchange reference rates provided by the European Central Bank, no API keys,
no authentication, no rate limits.

> ### ECB Exchange Rates Important Notice
>
> The reference rates are usually updated at **around 16:00 CET** every working day, except on
> [TARGET closing days](https://www.ecb.europa.eu/ecb/contacts/working-hours/html/index.en.html).
>
> They are based on the daily concertation procedure between central banks across Europe, which normally takes place around 14:10 CET.
> The reference rates are published for **information purposes only**. Using the rates for transaction purposes is **strongly discouraged**.
>
> _Source: [European Central Bank](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html)_

## Motivation

Even though there are other libraries in the Elixir ecosystem that provide
similar functionality (example: `ex_money`), `Forex` was created with the intent
of providing access to currency exchange rates for projects that want to self-host
the data and not rely on third-party paid services in a simple and straightforward
manner. No API keys, no authentication, no rate limits, just a simple Elixir library
that fetches the data from the European Central Bank and caches it for later use.

If you need a more advanced library that provides additional features and allows you
to fetch exchange rates from multiple sources, you should check out
the excellent [money](https://github.com/kipcole9/money).

## Configuration

### Supervision

By default, `Forex` starts with your application which in turn starts the `Forex.Supervisor`. If you
don't want to start the `Forex` supervision tree by default, you can pass the `runtime: false` option
to the `forex` dependency in your `mix.exs` file:

```elixir
def deps do
  [
    {:forex, "~> 1.1.2", runtime: false}
  ]
end
```

The `Forex` supervision tree is responsible for fetching the exchange rates from the European Central Bank
and caching them for later use. If you want to start the `Forex.Fetcher` manually, you can do so by calling
the `Forex.Fetcher.Supervisor.start_link/1` function in your application supervision tree.

### HTTP Client

By default, `Forex` uses the `Req` HTTP client in `Forex.Feed.Req` to fetch the exchange rates from
the European Central Bank. To use a different HTTP client, define your own implementation of
the `Forex.Feed.API` behaviour and pass it to the `Forex` module in your `config.exs` file:

```elixir
config :forex, feed_api: MyApp.Forex.APIClient
```

## Usage

By default the `base` currency is the Euro (EUR), the same as the European Central Bank,
but you can change the base currency by passing the `base` option (for other options see the
[Options](#options) section) to the relevant functions.

To fetch the latest exchange rates, you can use the `latest_rates/1` function:

```elixir
iex> Forex.latest_rates()
{:ok,
  %Forex{
    base: :eur,
    date: ~D[2025-03-12],
    rates: %{
      usd: Decimal.new("1.1234"),
      jpy: Decimal.new("120.1234"),
      ...
      zar: Decimal.new("24.1442")
    }}
  }
```

To fetch the exchange rates for the last ninety days, you can use the `last_ninety_days_rates/1` function:

```elixir
iex> Forex.last_ninety_days_rates()
{:ok,
  [
    %Forex{date: ~D[2025-03-12], base: :eur, rates: %{usd: Decimal.new("1.1234"), ...}},
    ...
  ]}
```

To fetch the historic exchange rates (for any working day since 4 January 1999),
you can use the `historic_rates/1` function:

```elixir
iex> Forex.historic_rates()
{:ok,
  [
    %Forex{date: ~D[2025-03-12], base: :eur, rates: %{usd: Decimal.new("1.1234"), ...}},
    ...
  ]}
```

To fetch the exchange rates for a specific date, you can use the `get_historic_rate/2` function:

```elixir
iex> Forex.get_historic_rate(~D[2025-02-25])
{:ok,
  [
    %Forex{date: ~D[2025-03-12], base: :eur, rates: %{usd: Decimal.new("1.1234"), ...}},
    ...
  ]}
```

To fetch the exchange rates between two dates, you can use the `get_historic_rates_between/3` function:

```elixir
iex> Forex.get_historic_rates_between(~D[2025-02-25], ~D[2025-02-28])

{:ok,
  [
    %Forex{date: ~D[2025-03-12], base: :eur, rates: %{usd: Decimal.new("1.1234"), ...}},
    ...
  ]}
```

To convert an amount from one currency to another, you can use the `exchange/4` function:

```elixir
iex> Forex.exchange(100, "USD", "EUR")
{:ok, Decimal.new("91.86100")}

iex>  Forex.exchange(420, :eur, :gbp)
{:ok, Decimal.new("353.12760")}
```

To list all available currencies from the European Central Bank,
you can use the `available_currencies/1` function:

```elixir
iex> Forex.available_currencies()
[:try, :eur, :aud, :bgn, :brl, ...]
```

# `rate`

```elixir
@type rate() :: %{required(Forex.Currency.code()) =&gt; Forex.Currency.output_amount()}
```

A currency rate, represented as a map with the currency code as
the key and the rate amount as the value.

# `t`

```elixir
@type t() :: %Forex{base: Forex.Currency.code(), date: Date.t(), rates: rate()}
```

A Forex struct, representing the exchange rates for a given date.

# `available_currencies`

```elixir
@spec available_currencies(keys :: :atoms | :strings) :: [Forex.Currency.code()]
```

Return a list of all available currencies ISO 4217 codes.

# `currency_options`

```elixir
@spec currency_options(keys :: :atoms | :strings) :: [
  {String.t(), Forex.Currency.code()}
]
```

Return a list of all available currencies in the format
`%{currency_code() => currency_name()}`.
Useful for input forms, selects, etc.

# `exchange`

```elixir
@spec exchange(
  amount :: Forex.Currency.input_amount(),
  from_currency :: Forex.Currency.code(),
  to_currency :: Forex.Currency.code(),
  opts :: [Forex.Options.currency_option()]
) :: {:ok, Forex.Currency.output_amount()} | {:error, term()}
```

Exchange a given amount from one currency to another.
It will use the cached exchange rates from the European Central
Bank (ECB) or fetch the latest rates if the cache is disabled.

## Options
* `:format` - Format of rate values The default value is `:decimal`.

* `:round` - Decimal places for rounding The default value is `5`.

## Examples

```elixir
iex> Forex.exchange(100, "USD", "EUR")
{:ok, Decimal.new("91.86100")}

iex> Forex.exchange(420, :eur, :gbp, format: :string)
{:ok, "353.12760"}

iex> Forex.exchange(123, :gbp, :usd, format: :string, round: 1)
{:ok, "159.3"}
```

# `exchange!`

```elixir
@spec exchange!(
  amount :: Forex.Currency.input_amount(),
  from_currency :: Forex.Currency.code(),
  to_currency :: Forex.Currency.code(),
  opts :: [Forex.Options.currency_option()]
) :: Forex.Currency.output_amount()
```

Same as `exchange/3`, but raises an error if the request fails.

# `exchange_historic_rate`

```elixir
@spec exchange_historic_rate(
  date :: parsable_date(),
  amount :: Forex.Currency.input_amount(),
  from :: Forex.Currency.code(),
  to :: Forex.Currency.code(),
  opts :: [Forex.Options.rates_option()]
) :: {:ok, Forex.Currency.output_amount()} | {:error, term()}
```

Given a specific date, amount, and two currencies, it will return the
exchange rate for that date.

## Options
* `:format` - Format of rate values The default value is `:decimal`.

* `:round` - Decimal places for rounding The default value is `5`.

## Examples

```elixir
iex> Forex.exchange_historic_rate("2023-01-01", 100, "USD", "EUR")
{:ok, Decimal.new("91.86100")}
iex> Forex.exchange_historic_rate(~D[2023-01-01], 420, :eur, :gbp, format: :string)
{:ok, "353.12760"}
```

# `exchange_historic_rate!`

Same as `exchange_historic_rate/5`, but raises an error if the request fails.

# `get_currency`

```elixir
@spec get_currency(Forex.Currency.code()) ::
  {:ok, Forex.Currency.t()} | {:error, term()}
```

Get the currency information for the given ISO code.

# `get_currency!`

```elixir
@spec get_currency!(Forex.Currency.code()) :: Forex.Currency.t()
```

Get the currency information for the given ISO code.
Like `get_currency/1`, but raises an error if the currency is not found.

# `get_historic_rate`

```elixir
@spec get_historic_rate(parsable_date(), opts :: [Forex.Options.rates_option()]) ::
  {:ok, t()} | {:error, term()}
```

Get a specific date from the historic exchange rates feed.
It returns either an `{:ok, rate()}` if the rate was successfully
retrieved or an `{:error, reasons}` if the rate was not found.

## Options
* `:base` - The base currency to convert rates to The default value is `:eur`.

* `:format` - Format of rate values The default value is `:decimal`.

* `:round` - Decimal places for rounding The default value is `5`.

* `:keys` - Map key format The default value is `:atoms`.

* `:symbols` - Currency codes to include

* `:use_cache` (`t:boolean/0`) - Whether to use cached rates The default value is `true`.

* `:feed_fn` - Optional custom feed function as `{module, function, args}`

# `get_historic_rate!`

```elixir
@spec get_historic_rate!(parsable_date(), opts :: [Forex.Options.rates_option()]) ::
  t()
```

Same as `get_historic_rate/2`, but raises an error if the request fails.

# `get_historic_rates_between`

```elixir
@spec get_historic_rates_between(
  start_date :: parsable_date(),
  end_date :: parsable_date(),
  opts :: [Forex.Options.rates_option()]
) :: {:ok, [t()]} | {:error, term()}
```

Get exchange rates between two dates from the historic exchange rates feed.

Returns a list of exchange rates for each working day between the start and end date.

## Options
* `:base` - The base currency to convert rates to The default value is `:eur`.

* `:format` - Format of rate values The default value is `:decimal`.

* `:round` - Decimal places for rounding The default value is `5`.

* `:keys` - Map key format The default value is `:atoms`.

* `:symbols` - Currency codes to include

* `:use_cache` (`t:boolean/0`) - Whether to use cached rates The default value is `true`.

* `:feed_fn` - Optional custom feed function as `{module, function, args}`

## Examples

```elixir
iex> Forex.get_historic_rates_between("2023-01-01", "2023-01-05")
{:ok, [
  %Forex{date: ~D[2023-01-02], base: :eur, rates: %{usd: Decimal.new("1.0678", ...}},
  %Forex{date: ~D[2023-01-03], base: :eur, rates: %{usd: Decimal.new("1.0545", ...}},
  %Forex{date: ~D[2023-01-04], base: :eur, rates: %{usd: Decimal.new("1.0599", ...}},
  %Forex{date: ~D[2023-01-05], base: :eur, rates: %{usd: Decimal.new("1.0556", ...}}
]}
```

# `get_historic_rates_between!`

```elixir
@spec get_historic_rates_between!(
  start_date :: parsable_date(),
  end_date :: parsable_date(),
  opts :: [Forex.Options.rates_option()]
) :: [t()]
```

Same as `get_historic_rates_between/3`, but raises an error if the request fails.

# `historic_rates`

```elixir
@spec historic_rates(opts :: [Forex.Options.rates_option()]) ::
  {:ok, [t()]} | {:error, term()}
```

Fetch the historic exchange rates feed from the European
Central Bank (ECB) for any working day since 4 January 1999.

By default, the historic rates are not automatically fetched when using
the Fetcher (scheduler) module, since the whole file is returned this avoids excessive memory
usage when caching the results if not needed. To fetch and cache the historic rates,
you need to manually call this function.

 ## Options
 * `:base` - The base currency to convert rates to The default value is `:eur`.

* `:format` - Format of rate values The default value is `:decimal`.

* `:round` - Decimal places for rounding The default value is `5`.

* `:keys` - Map key format The default value is `:atoms`.

* `:symbols` - Currency codes to include

* `:use_cache` (`t:boolean/0`) - Whether to use cached rates The default value is `true`.

* `:feed_fn` - Optional custom feed function as `{module, function, args}`

# `historic_rates!`

```elixir
@spec historic_rates!(opts :: [Forex.Options.rates_option()]) :: [t()]
```

Same as `historic_rates/1`, but raises an error if the request fails.

# `json_library`

Returns the configured JSON encoding library for Forex.
The default is the Elixir built-in `JSON` module (it requires Elixir 1.18+).

The JSON library must implement the `encode/1` function.

The JSON library is only required when using the mix tasks
to export the exchange rates to a JSON file, otherwise this
setting can be ignored.

To customize the JSON library, including the following
in your `config/config.exs`:

    config :forex, :json_library, AlternativeJsonLibrary

The library must implement the `encode_to_iodata!/2` function.

# `last_ninety_days_rates`

```elixir
@spec last_ninety_days_rates(opts :: [Forex.Options.rates_option()]) ::
  {:ok, [t()]} | {:error, term()}
```

Fetch the exchange rates for the last ninety days
from the European Central Bank (ECB).

Note that rates are only available on working days.

## Options
* `:base` - The base currency to convert rates to The default value is `:eur`.

* `:format` - Format of rate values The default value is `:decimal`.

* `:round` - Decimal places for rounding The default value is `5`.

* `:keys` - Map key format The default value is `:atoms`.

* `:symbols` - Currency codes to include

* `:use_cache` (`t:boolean/0`) - Whether to use cached rates The default value is `true`.

* `:feed_fn` - Optional custom feed function as `{module, function, args}`

# `last_ninety_days_rates!`

```elixir
@spec last_ninety_days_rates!(opts :: [Forex.Options.rates_option()]) :: [t()]
```

Same as `last_ninety_days_rates/1`, but raises an error if the request fails.

# `last_updated`

```elixir
@spec last_updated() ::
  [
    latest_rates: DateTime.t(),
    historic_rates: DateTime.t(),
    last_ninety_days_rates: DateTime.t()
  ]
  | nil
```

Last updated date of the exchange rates feed.
Lists the last date the exchange rates were updated from the cache.

Example:

    iex> Forex.last_updated()
    [
      latest_rates: ~U[2024-11-23 18:19:38.974337Z],
      historic_rates: ~U[2024-11-23 18:27:07.391035Z],
      last_ninety_days_rates: ~U[2024-11-23 18:19:39.111818Z],
    ]

# `latest_rates`

```elixir
@spec latest_rates(opts :: [Forex.Options.rates_option()]) ::
  {:ok, t()} | {:error, term()}
```

Fetch the latest exchange rates from the European Central Bank (ECB).

## Options
* `:base` - The base currency to convert rates to The default value is `:eur`.

* `:format` - Format of rate values The default value is `:decimal`.

* `:round` - Decimal places for rounding The default value is `5`.

* `:keys` - Map key format The default value is `:atoms`.

* `:symbols` - Currency codes to include

* `:use_cache` (`t:boolean/0`) - Whether to use cached rates The default value is `true`.

* `:feed_fn` - Optional custom feed function as `{module, function, args}`

## Examples

```elixir
{:ok, %Forex{
  base: :eur,
  date: ~D[2025-03-12],
  rates: %{usd: Decimal.new("1.1234"), jpy: Decimal.new("120.1234"), ...}}
}
```

# `latest_rates!`

```elixir
@spec latest_rates!(opts :: [Forex.Options.rates_option()]) :: t()
```

Same as `latest_rates/1`, but raises an error if the request fails.

# `list_currencies`

```elixir
@spec list_currencies(keys :: :atoms | :strings) :: %{
  required(Forex.Currency.code()) =&gt; Forex.Currency.t()
}
```

Return a list of all available currencies.

---

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