-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME.Rmd
311 lines (241 loc) · 11.6 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
---
output: github_document
editor_options:
markdown:
wrap: 90
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%",
fig.width = 8,
fig.height = 4
)
options(tibble.print_min = 5, tibble.print_max = 10)
```
# EXR // EXchangeRates <img src="man/figures/logo.png" align="right" height="150"/>
<!-- badges: start -->
`r badger::badge_repostatus("WIP")`
`r badger::badge_license("MIT")`
`r badger::badge_devel(color = "blue")`
`r badger::badge_codecov()`
`r badger::badge_github_actions()`
`r badger::badge_cran_release("EXR", "orange")`
<!-- badges: end -->
## Overview
Get currency exchange rates ('EXR') or directly apply currency conversion on a tibble from
one to another currency based on spot prices provided by 'ECB Data Portal' (European
Central Bank). If neither the price currency nor base currency is EUR, unofficial
cross-rates based on EUR will be calculated. The main purpose of this package is to offer
an unlimited and free way to get (potentially approximate) important exchange rates.
| | |
|-----------------------------------|------------------------------------------------------|
| **Why use this package?** | **When not to use this package** |
| ✅Free API with decades of data | ❌If you need intraday data |
| ✅Unlimited API calls | ❌If you need ask / bid / mid prices |
| ✅No key or registration required | ❌If you need rates from a specific domain (non-ECB) |
| ✅Helpful errors and warnings | ❌If you need exchange rates for business purposes |
| ✅Works well with tables | ❌If you need exchange rates for all currencies |
## What this package can do
- Get exchange rate for a currency pair
- Get exchange rate history for a currency pair or multiple pairs
- Get all cross-rates for a specific day (or a subset)
- Get last N days of exchange rate history
- You have a `data.table` (or tibble) with a currency value and you want to transform it
to another currency
- maybe the base currency is dynamic and a separate column in your table?
- maybe the table contains different dates, specified in a column
## Limitations and considerations
- Only supports currencies that have a ECB Euro foreign exchange reference rate.
- Because only reference rates are available, the calculation of cross-rates might be
very different from official exchange rates, especially for currencies with less trade
volume
- API results are not deterministic. Data may be added or changed at any time
- API answer might take some seconds for huge requests or massive load on the web
service
## Installation
You can install the development version of EXR from [GitHub](https://github.com/) with:
``` r
# install.packages("pak")
pak::pak("obsaditelnost/EXR")
```
## Requirements
You need to have access to the [ECB Data Web Service
API](https://data-api.ecb.europa.eu/ "ECB Data Web Service API")
## Code examples
### Available currencies
Let's first see which currencies are actually available:
```{r example1}
EXR::get_available_currencies()
```
This can be displayed a bit prettier:
```{r example2, eval=FALSE}
EXR::get_available_currencies() |>
dplyr::mutate(flag = substr(ISOCODE, 1, 2)) |>
dplyr::arrange(TITLE) |>
gt::gt() |>
gt::fmt_flag(columns = flag) |>
gt::cols_merge(columns = c(flag, ISOCODE)) |>
gt::cols_label(flag = "ISOCODE", TITLE = "Currency") |>
gt::tab_header(
title = "Available currencies",
subtitle = gt::md(paste0("as of **", Sys.Date(), "**"))
) |>
gt::tab_source_note("Source: https://data.ecb.europa.eu/") |>
gt::tab_options(data_row.padding = gt::px(0))
```
::: {align="center"}
![](./man/figures/README-example2.png){style="display: block; margin-left: auto; margin-right: auto;"}
:::
### Discontinued currencies
Some exchange rates have been published in the past, but are not available any more:
```{r example3, eval=FALSE}
dplyr::anti_join(
EXR::get_available_currencies(as.Date("2014-01-02")),
EXR::get_available_currencies(as.Date("2024-01-02")),
by = dplyr::join_by(ISOCODE, TITLE)
) |>
dplyr::mutate(flag = substr(ISOCODE, 1, 2), TITLE = gt::md(paste0("~", TITLE, "~"))) |>
dplyr::arrange(TITLE) |>
gt::gt() |>
gt::fmt_flag(columns = flag) |>
gt::fmt_markdown(columns = TITLE) |>
gt::cols_merge(columns = c(flag, ISOCODE)) |>
gt::cols_label(flag = "ISOCODE", TITLE = "Currency") |>
gt::tab_header(
title = "Discontinued currencies (temporarily or forever)",
subtitle = gt::md(paste0("as of **", Sys.Date(), "**"))
) |>
gt::tab_source_note("Source: https://data.ecb.europa.eu/") |>
gt::tab_options(data_row.padding = gt::px(0)) |>
gt::cols_align(align = "left", columns = TITLE)
```
::: {align="center"}
![](./man/figures/README-example3.png){style="display: block; margin-left: auto; margin-right: auto;"}
:::
### Get history for one currency pair
This is a basic example which shows you how to solve a common problem, like getting the
history of USD/EUR exchange rates for the last year. Fill missing dates and look for
missing values up to 3 days in the past so there won't be exchange rates on very long
weekends that include holidays, like Christmas for example.
```{r example4}
EXR::get_exchange_rate_history(
base_currency = "EUR",
price_currency = "USD",
periodicity = "D",
fill_missing_dates = TRUE,
max_lookback_days = 3
)[1:10, ]
```
#### Use history to create plots
If we have a time series, we can do with it whatever we want:
```{r example5}
EXR::get_exchange_rate_history(base_currency = "EUR", price_currency = "USD") |>
dplyr::mutate(
end = value,
start = dplyr::lag(value, order_by = period),
period_end = EXR::sdmx_character_to_date(period),
sign = ifelse(end - start < 0, "negative", "positive")
) |>
dplyr::filter(!is.na(start)) |>
ggplot2::ggplot(ggplot2::aes(x = period_end, fill = sign)) +
ggplot2::geom_rect(ggplot2::aes(xmin = period_end - 1, xmax = period_end,
ymin = end, ymax = start)) +
ggplot2::scale_fill_manual(values = c("red", "green")) +
ggplot2::theme_minimal() +
ggplot2::labs(x = "day", y = "exchange rate", title = "Daily USD/EUR exchange rate",
subtitle = paste0("For 1 year as of ", Sys.Date() - 1))
```
#### Request last n elements from a history
Maybe we want to fetch the *last* 10 years of annual data for different currencies *as of
a specific period/date*. Let's assume we want data from 2020 up to 10 years into the past:
```{r example6}
EXR::get_exchange_rate_history(
base_currency = c("USD"),
price_currency = c("EUR", "THB", "GBP"), periodicity = "A", context = "E", show_metadata = FALSE,
filter = list(endPeriod = as.Date("2020-12-31"), lastNObservations = 10)
) |>
tidyr::pivot_wider(names_from = price_currency, values_from = value,
names_glue = "{price_currency}/USD") |>
dplyr::mutate(base_currency = NULL)
```
### Get exchange rate matrix for currency combinations
get all combinations for a set of currencies as of end 2023 and show as pivot table:
```{r example7}
xrates <- EXR::get_all_cross_rates(
currencies = c("EUR", "USD", "JPY", "GBP", "CNY"),
date = as.Date("2023-12-31"), output_method = "pivot"
)
xrates
```
Access a specific exchange rate by `xrates['base_currency', 'price_currency']`, for
example `xrates['GBP', 'USD']` which translates to: for a GBP, how many USD will I get?
```{r example7b}
xrates["GBP", "USD"]
```
## Further information
When talking about an "exchange rate" we (users of this package) *always* define the rate
as "*we get x units of the price currency if we pay 1 unit of the base currency*"
$$ \frac{price}{base} = x $$
So for a currency pair USD/EUR we'd expect an exchange rate of about 1.1 because we define
"EUR" as the base because it's the denominator.
In finance there are different approaches (direct / indirect quotations) so we need to
define this in advance.
### ECB foreign exchange reference rates
As
[stated](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html)
by the European Central Bank
> 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.
Some bank are not very transparent about their fees in FX transactions. That's why the ECB
wants consumers to be easily able to compare the different exchange rates (bank, airport,
financial transactions).
### bid, ask, mid, intraday rates
Let's assume that the current exchange rate for THB/USD is about 35 (average / mid price).
If you want to convert 1000 USD into THB, you would get around 35,000 THB.
- Some people what to sell THB but they have a minimum price expectation (sellers "ask"
for an offer - and the lowest ask price across all sellers is the ask-rate for this
currency)
- Some people what to buy THB but they have a maximum price expectation (buyers "bid" -
the highest rate across all buyers the the bid-rate)
The mid-price is just the middle of them. You are in a currency exchange booth and want to
"buy" THB with your USD, the booth asks for many USD (they want some fees of course)... so
the ask-price might be 34 THB/USD. The bid-price for THB might be around 36 THB/USD.
In many cases you only see one price though. For example you pay with a credit card and 1
week later you see some currency conversion - without any market knowledge you can't know
if 34.22 was a "fair" or "unfair" exchange rate for you. For this purpose the foreign
exchange reference rates may be an acceptable indicator.
Some exchange rates are static and don't change (very often) like the GBP and British
Overseas Territories such as Gibraltar. For other currencies, during a day the exchange
rates may have high fluctuations though - up so several percent. Therefore a single value
for a day can always only be a rough indicator and never an exact value.
### cross-rates
Let's assume you want to know the exchange rate for GBP/THB. Maybe nobody at the market
might want to do such a transaction or there is just no official exchange rate for that
pair.
If we know the exchange rate for GBP/EUR and THB/EUR, we can derive the GBP/THB exchange
rate by calculation a "cross-rate":
$$ \frac{GBP}{EUR} = 0.84 $$
$$ \frac{THB}{EUR}=38 $$
So if we get 0.84 GBP for 1€ and 38 THB for 1€, we should get more than 40 THB for a GBP
because as of "now" one GBP has more value that one Euro. To calculate this we can simply
apply:
$$
\frac{GBP}{THB} = \frac{(\frac{THB}{basecurrency})}{(\frac{GPB}{basecurrency})}= \frac{(\frac{THB}{EUR})}{(\frac{GPB}{EUR})} = \frac{38}{0.84} ≈ 45.238
$$
The API only offers EUR as base currency, so all cross-rates for non-available currency
pairs are calculated by using EUR-rates. If we look back at [bid, ask, mid, intraday
rates] we may see that this approach is quite simple and prone to inaccuracy because we
don't take bid and ask prices into account for such a calculation.
------------------------------------------------------------------------------------------
::: {align="center"}
[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/white_img.png)](https://www.buymeacoffee.com/obsaditelnost)
:::