Forex (Forex v1.1.2)

Copy Markdown View Source

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.

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

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.

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:

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:

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 section) to the relevant functions.

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

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:

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:

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:

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:

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:

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:

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

Summary

Types

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

t()

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

Functions

Return a list of all available currencies ISO 4217 codes.

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

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.

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

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

Get the currency information for the given ISO code.

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

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.

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

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

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

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

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

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

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

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

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

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

Return a list of all available currencies.

Types

rate()

@type rate() :: %{required(Forex.Currency.code()) => 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()

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

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

Functions

available_currencies(keys \\ :atoms)

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

Return a list of all available currencies ISO 4217 codes.

currency_options(keys \\ :atoms)

@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(amount, from_currency, to_currency, opts \\ [])

@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

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!(amount, from_currency, to_currency, opts \\ [])

@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(date, amount, from, to, opts \\ [])

@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

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!(date, amount, from, to, opts \\ [])

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

get_currency(currency_code)

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

Get the currency information for the given ISO code.

get_currency!(currency_code)

@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(date, opts \\ [])

@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 (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!(date, opts \\ [])

@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(start_date, end_date, opts \\ [])

@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 (boolean/0) - Whether to use cached rates The default value is true.

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

Examples

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!(start_date, end_date, opts \\ [])

@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(opts \\ [])

@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 (boolean/0) - Whether to use cached rates The default value is true.

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

historic_rates!(opts \\ [])

@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(opts \\ [])

@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 (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!(opts \\ [])

@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()

@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(opts \\ [])

@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 (boolean/0) - Whether to use cached rates The default value is true.

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

Examples

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

latest_rates!(opts \\ [])

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

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

list_currencies(keys \\ :atoms)

@spec list_currencies(keys :: :atoms | :strings) :: %{
  required(Forex.Currency.code()) => Forex.Currency.t()
}

Return a list of all available currencies.