pynbpapi 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ name: Lint
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ flake8-lint:
7
+ runs-on: ubuntu-latest
8
+
9
+ steps:
10
+ - name: Checkout code
11
+ uses: actions/checkout@v3
12
+
13
+ - name: Set up Python
14
+ uses: actions/setup-python@v4
15
+ with:
16
+ python-version: '3.11'
17
+
18
+ - name: Install dependencies
19
+ run: |
20
+ python -m pip install --upgrade pip
21
+ pip install flake8
22
+ pip install -r requirements.txt
23
+
24
+ - name: Run Flake8
25
+ run: |
26
+ flake8 pynbpapi
@@ -0,0 +1,34 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - name: Checkout code
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.11"
19
+
20
+ - name: Install dependencies
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ pip install -r requirements.txt
24
+ pip install pytest pytest-cov
25
+
26
+ - name: Run tests with coverage
27
+ run: |
28
+ pytest --cov=pynbp --cov-report=xml
29
+
30
+ - name: Upload coverage to Codecov
31
+ uses: codecov/codecov-action@v4
32
+
33
+ - name: List files
34
+ run: ls -la
@@ -0,0 +1,6 @@
1
+ .venv
2
+ .idea
3
+ **/__pycache__/
4
+ .coverage
5
+ temp.py
6
+ dist/**
pynbpapi-1.0.0/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ MIT License
2
+ Copyright (c) 2026 ARTUR WEGRZYN
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+ The above copyright notice and this permission notice shall be included in all
10
+ copies or substantial portions of the Software.
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17
+ SOFTWARE.
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.4
2
+ Name: pynbpapi
3
+ Version: 1.0.0
4
+ Summary: Tidy downloading of National Bank of Poland interest rates, FX rates and gold price data
5
+ Keywords: NBP,FX,PLN,gold,API,REST-API,FX-rates,zloty
6
+ Author-email: Artur Wegrzyn <awegrzyn17@gmail.com>
7
+ Maintainer-email: Artur Wegrzyn <awegrzyn17@gmail.com>
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: pandas
12
+ Requires-Dist: requests
13
+ Requires-Dist: python-dateutil
14
+ Requires-Dist: loguru
15
+
16
+ # pynbpapi
17
+
18
+ ![Flake8 Lint Check](https://github.com/wegar-2/pynbp/actions/workflows/flake8-lint.yml/badge.svg)
19
+ ![CI](https://github.com/wegar-2/pynbp/actions/workflows/python-tests.yml/badge.svg)
20
+ [![codecov](https://codecov.io/github/wegar-2/pynbp/graph/badge.svg?token=XJY6OWPSPI)](https://codecov.io/github/wegar-2/pycs)
21
+
22
+
23
+ This trivial package provides simple interface (consisting of three functions described below)
24
+ that allows you to download the selected types of time series made available
25
+ via [National Bank of Poland's REST API](http://api.nbp.pl/en.html).
26
+
27
+
28
+ More specifically, the following time series can be downloaded using PyNBP:
29
+
30
+ 1. Average daily FX rates for select currencies
31
+ (cf. [NBP's table of daily FX rates against various currencies]()
32
+ for list of currencies for which exchange rates are published).
33
+ 2. Table of historical central bank interest rates.
34
+ 3. Gold prices in PLN (note: the gold prices published by NBP are prices of 1g of gold).
35
+ 4. Downloading of the daily central bank's [FX rates tables](https://nbp.pl/statystyka-i-sprawozdawczosc/kursy/) (A, B and C)
36
+
37
+ Note: the table of historical interest is not *per se* provided via NBP's API.
38
+
39
+ I provide code snippets illustrating how to get the data below.
40
+
41
+
42
+ ### Import *pynbpapi*
43
+
44
+ ```python
45
+ import pynbpapi
46
+ ```
47
+
48
+ or:
49
+
50
+ ```python
51
+ from pynbpapi import (
52
+ get_gold_prices, get_interest_rates_table, get_fx_rate,
53
+ get_nbp_fx_tables, get_fx_rates)
54
+ ```
55
+
56
+ ### FX Rate For a Single Pair Against PLN
57
+ To download USDPLN daily average FX rates for the dates range 2018-01-03 to 2021-04-05
58
+ (these dates are given in the ISO / %Y-%m-%d format) run:
59
+
60
+ ```python
61
+ from pynbpapi import get_fx_rate
62
+ from datetime import date
63
+
64
+ data = get_fx_rate(ccy="usd", start=date(2018, 1, 3), end=date(2021, 4, 5))
65
+ print(data.head())
66
+ ```
67
+
68
+ Output:
69
+ ```
70
+ date usdpln_rate
71
+ 0 2018-01-03 3.4616
72
+ 1 2018-01-04 3.4472
73
+ 2 2018-01-05 3.4488
74
+ 3 2018-01-08 3.4735
75
+ 4 2018-01-09 3.4992
76
+ ```
77
+
78
+ ### FX Rates For Multiple Pairs Against PLN
79
+ To download USDPLN, EURPLN and CNYPLN daily average FX rates for the dates
80
+ range 2018-01-03 to 2021-04-05
81
+ (these dates are given in the ISO / %Y-%m-%d format)
82
+ in the long format (alternatively - wide format can be chosen) run:
83
+
84
+ ```python
85
+ from pynbpapi import get_fx_rates, Ccy
86
+ from datetime import date
87
+
88
+ data = get_fx_rates(
89
+ ccys=[Ccy.EUR, Ccy.USD, Ccy.CNY],
90
+ start=date(2018, 1, 3),
91
+ end=date(2021, 4, 5),
92
+ fmt="long"
93
+ )
94
+ print(data.head())
95
+ print(data.tail())
96
+ ```
97
+
98
+ Output - head:
99
+ ```
100
+ date rate pair
101
+ 0 2018-01-03 4.1673 eurpln
102
+ 1 2018-01-04 4.1515 eurpln
103
+ 2 2018-01-05 4.1544 eurpln
104
+ 3 2018-01-08 4.1647 eurpln
105
+ 4 2018-01-09 4.1779 eurpln
106
+ ```
107
+ And tail:
108
+ ```
109
+ date rate pair
110
+ 816 2021-03-29 0.6027 cnypln
111
+ 817 2021-03-30 0.6030 cnypln
112
+ 818 2021-03-31 0.6053 cnypln
113
+ 819 2021-04-01 0.5998 cnypln
114
+ 820 2021-04-02 0.5941 cnypln
115
+ ```
116
+
117
+
118
+
119
+ ### Table of Historical Interest Rates
120
+ To download the table of historical interest rates set by NBP run:
121
+
122
+ ```python
123
+ from pynbpapi import get_interest_rates_table
124
+ from datetime import date
125
+
126
+ data = get_interest_rates_table()
127
+ print(data.tail())
128
+ ```
129
+
130
+ Output:
131
+ ```
132
+ valid_from_date lombard_rate reference_rate rediscount_rate discount_rate deposit_rate
133
+ 82 2022-04-07 0.0500 0.0450 0.0455 0.0460 0.0400
134
+ 83 2022-05-06 0.0575 0.0525 0.0530 0.0535 0.0475
135
+ 84 2022-06-09 0.0650 0.0600 0.0605 0.0610 0.0550
136
+ 85 2022-07-08 0.0700 0.0650 0.0655 0.0660 0.0600
137
+ 86 2022-09-08 0.0725 0.0675 0.0680 0.0685 0.0625
138
+ ```
139
+
140
+
141
+
142
+ ### Gold Prices in PLN
143
+ To download gold prices in PLN for the dates range 2018-01-03 to 2021-04-05 run:
144
+
145
+ ```python
146
+ from pynbpapi import get_gold_prices
147
+ from datetime import date
148
+
149
+ data = get_gold_prices(start=date(2018, 1, 3), end=date(2021, 4, 5))
150
+ print(data.head())
151
+ ```
152
+
153
+ Please keep in mind that the daily prices of gold published by
154
+ NBP are prices of 1g of gold in PLN.
155
+
156
+ Output:
157
+ ```
158
+ date price_of_1g_of_gold_in_pln
159
+ 0 2018-01-03 145.72
160
+ 1 2018-01-04 146.36
161
+ 2 2018-01-05 145.68
162
+ 3 2018-01-08 146.06
163
+ 4 2018-01-09 147.42
164
+ ```
165
+
166
+
167
+ ### Download Daily NBP's FX Rates Tables
168
+ To download FX rates table A for dates range 2026-02-18 to 2026-02-19 run:
169
+
170
+ ```python
171
+ from pynbpapi import get_nbp_fx_tables
172
+ from datetime import date
173
+ data = get_nbp_fx_tables(table="A", start=date(2026, 2, 18), end=date(2026, 2, 19))
174
+ ```
175
+
176
+ Please keep in mind that the daily prices of gold published by
177
+ NBP are prices of 1g of gold in PLN.
178
+
179
+ Output - head:
180
+ ```
181
+ currency code mid table no date
182
+ 0 bat (Tajlandia) THB 0.1137 A 033/A/NBP/2026 2026-02-18
183
+ 1 dolar amerykański USD 3.5610 A 033/A/NBP/2026 2026-02-18
184
+ 2 dolar australijski AUD 2.5204 A 033/A/NBP/2026 2026-02-18
185
+ 3 dolar Hongkongu HKD 0.4556 A 033/A/NBP/2026 2026-02-18
186
+ 4 dolar kanadyjski CAD 2.6078 A 033/A/NBP/2026 2026-02-18
187
+ ```
188
+
189
+ And tail:
190
+ ```
191
+ currency code mid table no date
192
+ 59 rupia indonezyjska IDR 0.000212 A 034/A/NBP/2026 2026-02-19
193
+ 60 rupia indyjska INR 0.039242 A 034/A/NBP/2026 2026-02-19
194
+ 61 won południowokoreański KRW 0.002472 A 034/A/NBP/2026 2026-02-19
195
+ 62 yuan renminbi (Chiny) CNY 0.515200 A 034/A/NBP/2026 2026-02-19
196
+ 63 SDR (MFW) XDR 4.909800 A 034/A/NBP/2026 2026-02-19
197
+ ```
@@ -0,0 +1,182 @@
1
+ # pynbpapi
2
+
3
+ ![Flake8 Lint Check](https://github.com/wegar-2/pynbp/actions/workflows/flake8-lint.yml/badge.svg)
4
+ ![CI](https://github.com/wegar-2/pynbp/actions/workflows/python-tests.yml/badge.svg)
5
+ [![codecov](https://codecov.io/github/wegar-2/pynbp/graph/badge.svg?token=XJY6OWPSPI)](https://codecov.io/github/wegar-2/pycs)
6
+
7
+
8
+ This trivial package provides simple interface (consisting of three functions described below)
9
+ that allows you to download the selected types of time series made available
10
+ via [National Bank of Poland's REST API](http://api.nbp.pl/en.html).
11
+
12
+
13
+ More specifically, the following time series can be downloaded using PyNBP:
14
+
15
+ 1. Average daily FX rates for select currencies
16
+ (cf. [NBP's table of daily FX rates against various currencies]()
17
+ for list of currencies for which exchange rates are published).
18
+ 2. Table of historical central bank interest rates.
19
+ 3. Gold prices in PLN (note: the gold prices published by NBP are prices of 1g of gold).
20
+ 4. Downloading of the daily central bank's [FX rates tables](https://nbp.pl/statystyka-i-sprawozdawczosc/kursy/) (A, B and C)
21
+
22
+ Note: the table of historical interest is not *per se* provided via NBP's API.
23
+
24
+ I provide code snippets illustrating how to get the data below.
25
+
26
+
27
+ ### Import *pynbpapi*
28
+
29
+ ```python
30
+ import pynbpapi
31
+ ```
32
+
33
+ or:
34
+
35
+ ```python
36
+ from pynbpapi import (
37
+ get_gold_prices, get_interest_rates_table, get_fx_rate,
38
+ get_nbp_fx_tables, get_fx_rates)
39
+ ```
40
+
41
+ ### FX Rate For a Single Pair Against PLN
42
+ To download USDPLN daily average FX rates for the dates range 2018-01-03 to 2021-04-05
43
+ (these dates are given in the ISO / %Y-%m-%d format) run:
44
+
45
+ ```python
46
+ from pynbpapi import get_fx_rate
47
+ from datetime import date
48
+
49
+ data = get_fx_rate(ccy="usd", start=date(2018, 1, 3), end=date(2021, 4, 5))
50
+ print(data.head())
51
+ ```
52
+
53
+ Output:
54
+ ```
55
+ date usdpln_rate
56
+ 0 2018-01-03 3.4616
57
+ 1 2018-01-04 3.4472
58
+ 2 2018-01-05 3.4488
59
+ 3 2018-01-08 3.4735
60
+ 4 2018-01-09 3.4992
61
+ ```
62
+
63
+ ### FX Rates For Multiple Pairs Against PLN
64
+ To download USDPLN, EURPLN and CNYPLN daily average FX rates for the dates
65
+ range 2018-01-03 to 2021-04-05
66
+ (these dates are given in the ISO / %Y-%m-%d format)
67
+ in the long format (alternatively - wide format can be chosen) run:
68
+
69
+ ```python
70
+ from pynbpapi import get_fx_rates, Ccy
71
+ from datetime import date
72
+
73
+ data = get_fx_rates(
74
+ ccys=[Ccy.EUR, Ccy.USD, Ccy.CNY],
75
+ start=date(2018, 1, 3),
76
+ end=date(2021, 4, 5),
77
+ fmt="long"
78
+ )
79
+ print(data.head())
80
+ print(data.tail())
81
+ ```
82
+
83
+ Output - head:
84
+ ```
85
+ date rate pair
86
+ 0 2018-01-03 4.1673 eurpln
87
+ 1 2018-01-04 4.1515 eurpln
88
+ 2 2018-01-05 4.1544 eurpln
89
+ 3 2018-01-08 4.1647 eurpln
90
+ 4 2018-01-09 4.1779 eurpln
91
+ ```
92
+ And tail:
93
+ ```
94
+ date rate pair
95
+ 816 2021-03-29 0.6027 cnypln
96
+ 817 2021-03-30 0.6030 cnypln
97
+ 818 2021-03-31 0.6053 cnypln
98
+ 819 2021-04-01 0.5998 cnypln
99
+ 820 2021-04-02 0.5941 cnypln
100
+ ```
101
+
102
+
103
+
104
+ ### Table of Historical Interest Rates
105
+ To download the table of historical interest rates set by NBP run:
106
+
107
+ ```python
108
+ from pynbpapi import get_interest_rates_table
109
+ from datetime import date
110
+
111
+ data = get_interest_rates_table()
112
+ print(data.tail())
113
+ ```
114
+
115
+ Output:
116
+ ```
117
+ valid_from_date lombard_rate reference_rate rediscount_rate discount_rate deposit_rate
118
+ 82 2022-04-07 0.0500 0.0450 0.0455 0.0460 0.0400
119
+ 83 2022-05-06 0.0575 0.0525 0.0530 0.0535 0.0475
120
+ 84 2022-06-09 0.0650 0.0600 0.0605 0.0610 0.0550
121
+ 85 2022-07-08 0.0700 0.0650 0.0655 0.0660 0.0600
122
+ 86 2022-09-08 0.0725 0.0675 0.0680 0.0685 0.0625
123
+ ```
124
+
125
+
126
+
127
+ ### Gold Prices in PLN
128
+ To download gold prices in PLN for the dates range 2018-01-03 to 2021-04-05 run:
129
+
130
+ ```python
131
+ from pynbpapi import get_gold_prices
132
+ from datetime import date
133
+
134
+ data = get_gold_prices(start=date(2018, 1, 3), end=date(2021, 4, 5))
135
+ print(data.head())
136
+ ```
137
+
138
+ Please keep in mind that the daily prices of gold published by
139
+ NBP are prices of 1g of gold in PLN.
140
+
141
+ Output:
142
+ ```
143
+ date price_of_1g_of_gold_in_pln
144
+ 0 2018-01-03 145.72
145
+ 1 2018-01-04 146.36
146
+ 2 2018-01-05 145.68
147
+ 3 2018-01-08 146.06
148
+ 4 2018-01-09 147.42
149
+ ```
150
+
151
+
152
+ ### Download Daily NBP's FX Rates Tables
153
+ To download FX rates table A for dates range 2026-02-18 to 2026-02-19 run:
154
+
155
+ ```python
156
+ from pynbpapi import get_nbp_fx_tables
157
+ from datetime import date
158
+ data = get_nbp_fx_tables(table="A", start=date(2026, 2, 18), end=date(2026, 2, 19))
159
+ ```
160
+
161
+ Please keep in mind that the daily prices of gold published by
162
+ NBP are prices of 1g of gold in PLN.
163
+
164
+ Output - head:
165
+ ```
166
+ currency code mid table no date
167
+ 0 bat (Tajlandia) THB 0.1137 A 033/A/NBP/2026 2026-02-18
168
+ 1 dolar amerykański USD 3.5610 A 033/A/NBP/2026 2026-02-18
169
+ 2 dolar australijski AUD 2.5204 A 033/A/NBP/2026 2026-02-18
170
+ 3 dolar Hongkongu HKD 0.4556 A 033/A/NBP/2026 2026-02-18
171
+ 4 dolar kanadyjski CAD 2.6078 A 033/A/NBP/2026 2026-02-18
172
+ ```
173
+
174
+ And tail:
175
+ ```
176
+ currency code mid table no date
177
+ 59 rupia indonezyjska IDR 0.000212 A 034/A/NBP/2026 2026-02-19
178
+ 60 rupia indyjska INR 0.039242 A 034/A/NBP/2026 2026-02-19
179
+ 61 won południowokoreański KRW 0.002472 A 034/A/NBP/2026 2026-02-19
180
+ 62 yuan renminbi (Chiny) CNY 0.515200 A 034/A/NBP/2026 2026-02-19
181
+ 63 SDR (MFW) XDR 4.909800 A 034/A/NBP/2026 2026-02-19
182
+ ```
@@ -0,0 +1,31 @@
1
+ from .ccy import Ccy
2
+ from .fx import get_fx_rate, get_fx_rates, get_nbp_fx_tables
3
+ from .gold import get_gold_prices
4
+ from .interest_rates import get_interest_rates_table
5
+
6
+ __all__ = [
7
+ "Ccy",
8
+ "get_fx_rate",
9
+ "get_fx_rates",
10
+ "get_gold_prices",
11
+ "get_interest_rates_table",
12
+ "get_nbp_fx_tables",
13
+ ]
14
+
15
+ import logging
16
+
17
+
18
+ def configure_logging():
19
+ console_handler = logging.StreamHandler()
20
+ formatter = logging.Formatter(
21
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
22
+ console_handler.setFormatter(formatter)
23
+
24
+ logger = logging.getLogger()
25
+ if logger.hasHandlers():
26
+ logger.handlers.clear()
27
+ logger.setLevel(logging.INFO)
28
+ logger.addHandler(console_handler)
29
+
30
+
31
+ configure_logging()
@@ -0,0 +1,36 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Ccy(Enum):
5
+ THB = "THB"
6
+ USD = "USD"
7
+ AUD = "AUD"
8
+ HKD = "HKD"
9
+ CAD = "CAD"
10
+ NZD = "NZD"
11
+ SGD = "SGD"
12
+ EUR = "EUR"
13
+ HUF = "HUF"
14
+ CHF = "CHF"
15
+ GBP = "GBP"
16
+ UAH = "UAH"
17
+ JPY = "JPY"
18
+ CZK = "CZK"
19
+ DKK = "DKK"
20
+ ISK = "ISK"
21
+ NOK = "NOK"
22
+ SEK = "SEK"
23
+ RON = "RON"
24
+ BGN = "BGN"
25
+ TRY = "TRY"
26
+ ILS = "ILS"
27
+ CLP = "CLP"
28
+ PHP = "PHP"
29
+ MXN = "MXN"
30
+ ZAR = "ZAR"
31
+ BRL = "BRL"
32
+ MYR = "MYR"
33
+ IDR = "IDR"
34
+ INR = "INR"
35
+ KRW = "KRW"
36
+ CNY = "CNY"
@@ -0,0 +1,37 @@
1
+ from datetime import date, timedelta
2
+ from json import loads
3
+ from dateutil.relativedelta import relativedelta
4
+ from requests import get
5
+
6
+ __all__ = [
7
+ "get_default_dates_range",
8
+ "run_web_api_query",
9
+ "split_dates_range_into_smaller_chunks"
10
+ ]
11
+
12
+
13
+ def get_default_dates_range() -> tuple[date, date]:
14
+ date_end = date.today() - timedelta(days=1)
15
+ date_start = date_end + relativedelta(years=-1) + timedelta(days=1)
16
+ return date_start, date_end
17
+
18
+
19
+ def run_web_api_query(url: str) -> dict:
20
+ return loads(get(url=url, timeout=180).text)
21
+
22
+
23
+ def split_dates_range_into_smaller_chunks(
24
+ start: date,
25
+ end: date
26
+ ) -> list[tuple[date, date]]:
27
+ if (end - start).days >= 367:
28
+ temp = start
29
+ out = []
30
+ while temp < end:
31
+ out.append(
32
+ (temp, temp + relativedelta(years=1) - timedelta(days=1))
33
+ )
34
+ temp = temp + relativedelta(years=1)
35
+ out[-1] = (out[-1][0], end)
36
+ return out
37
+ return [(start, end)]
@@ -0,0 +1,17 @@
1
+ from datetime import date
2
+
3
+ __all__ = [
4
+ "InvalidStartEndDatesException",
5
+ "InvalidCurrencyException"
6
+ ]
7
+
8
+
9
+ class InvalidStartEndDatesException(ValueError):
10
+ def __init__(self, start: date, end: date):
11
+ self.message: str = (f"Inconsistent start={start.isoformat()} and "
12
+ f"end={end.isoformat()} dates! ")
13
+
14
+
15
+ class InvalidCurrencyException(ValueError):
16
+ def __init__(self, ccy: str):
17
+ self.message = f"Encountered invalid currency: {ccy}"
@@ -0,0 +1,135 @@
1
+ from datetime import date, datetime
2
+ from functools import reduce
3
+ from typing import Literal, Union
4
+
5
+ import pandas as pd
6
+
7
+ from pynbpapi.common import (
8
+ split_dates_range_into_smaller_chunks, run_web_api_query)
9
+ from pynbpapi.ccy import Ccy
10
+ from pynbpapi.exceptions import (
11
+ InvalidStartEndDatesException, InvalidCurrencyException)
12
+
13
+ __all__ = ["get_fx_rate", "get_fx_rates", "get_nbp_fx_tables"]
14
+
15
+
16
+ def _validate_fx_table_code(code: str) -> None:
17
+ if code not in ["A", "B", "C"]:
18
+ raise ValueError(f"Invalid NBP code value {code}")
19
+
20
+
21
+ def _validate_start_end_dates(start: date, end: date) -> None:
22
+ if start > end:
23
+ raise InvalidStartEndDatesException(start, end)
24
+
25
+
26
+ def _validate_ccys(ccys: list) -> None:
27
+ for i in range(len(ccys)):
28
+ ccy = ccys[i]
29
+ if not (isinstance(ccy, str) or isinstance(ccy, Ccy)):
30
+ raise TypeError(
31
+ f"Encountered ccy of invalid type: {type(ccy)=}"
32
+ )
33
+ try:
34
+ if isinstance(ccy, str):
35
+ ccy = Ccy(ccy)
36
+ except ValueError:
37
+ raise InvalidCurrencyException(ccy=ccy)
38
+ ccys[i] = ccy
39
+
40
+
41
+ def _parse_fx_json(json_) -> pd.DataFrame:
42
+ return pd.DataFrame(data=json_["rates"]).rename(
43
+ columns={"effectiveDate": "date", "mid": "rate"}
44
+ ).drop(columns=["no"], inplace=False)
45
+
46
+
47
+ def _get_fx_api_query(ccy: Ccy, start: date, end: date) -> str:
48
+ return (f"https://api.nbp.pl/api/exchangerates"
49
+ f"/rates/a/{ccy.name.lower()}/{start.isoformat()}/"
50
+ f"{end.isoformat()}/")
51
+
52
+
53
+ def get_fx_rate(
54
+ ccy: Ccy | str,
55
+ start: date,
56
+ end: date
57
+ ) -> pd.DataFrame:
58
+ _validate_ccys(ccys=[ccy.upper() if isinstance(ccy, str) else ccy])
59
+ if not isinstance(ccy, Ccy):
60
+ ccy = Ccy(ccy.upper())
61
+ chunks = split_dates_range_into_smaller_chunks(start=start, end=end)
62
+ list_dfs = []
63
+ for start_, end_ in chunks:
64
+ list_dfs.append(
65
+ _parse_fx_json(
66
+ json_=run_web_api_query(
67
+ url=_get_fx_api_query(ccy=ccy, start=start_, end=end_)
68
+ )
69
+ )
70
+ )
71
+ return pd.concat(
72
+ list_dfs,
73
+ axis=0
74
+ ).rename(columns={
75
+ "rate": f"{ccy.name.lower()}pln"
76
+ }, inplace=False).reset_index(
77
+ inplace=False,
78
+ drop=True
79
+ ).copy(deep=True)[["date", f"{ccy.name.lower()}pln"]]
80
+
81
+
82
+ def get_fx_rates(
83
+ ccys: Union[str, Ccy, list[Union[str, Ccy]]],
84
+ start: date,
85
+ end: date,
86
+ fmt: Literal["long", "wide"] = "long",
87
+ fill_nas: bool = True
88
+ ) -> pd.DataFrame:
89
+ _validate_start_end_dates(start, end)
90
+ if isinstance(ccys, str) or isinstance(ccys, Ccy):
91
+ ccys = [ccys]
92
+ _validate_ccys(ccys)
93
+
94
+ datas: list[pd.DataFrame] = []
95
+ for ccy in ccys:
96
+ data = get_fx_rate(ccy, start, end)
97
+ if fmt == "long":
98
+ pair_name: str = f"{ccy.name.lower()}pln"
99
+ data["pair"] = pair_name
100
+ data = data.rename(columns={pair_name: "rate"})
101
+ datas.append(data)
102
+
103
+ if fmt == "long":
104
+ return pd.concat(datas, axis=0)
105
+
106
+ data = reduce(lambda x, y: pd.merge(x, y, on="date", how="outer"), datas)
107
+ if fill_nas:
108
+ data = data.ffill(axis=0)
109
+ data = data.bfill(axis=0)
110
+ return data.sort_values(by="date", ascending=True)
111
+
112
+
113
+ def get_nbp_fx_tables(
114
+ table: Literal["A", "B", "C"],
115
+ start: date,
116
+ end: date
117
+ ) -> pd.DataFrame:
118
+ _validate_fx_table_code(code=table)
119
+ _validate_start_end_dates(start, end)
120
+ tables = run_web_api_query(
121
+ url=f"https://api.nbp.pl/api/exchangerates/tables/"
122
+ f"{table}/{start.isoformat()}/{end.isoformat()}/"
123
+ )
124
+ parsed_tables: list[pd.DataFrame] = []
125
+ for table_ in tables:
126
+ rates_data = pd.DataFrame(table_["rates"])
127
+ rates_data["table"] = table_["table"]
128
+ rates_data["no"] = table_["no"]
129
+ rates_data["date"] = datetime.strptime(
130
+ table_["effectiveDate"], "%Y-%m-%d").date()
131
+ parsed_tables.append(rates_data)
132
+
133
+ return pd.concat(
134
+ parsed_tables, axis=0
135
+ ).reset_index(drop=True)
@@ -0,0 +1,32 @@
1
+ from datetime import date
2
+ import pandas as pd
3
+ from pynbpapi.common import (
4
+ run_web_api_query, split_dates_range_into_smaller_chunks)
5
+
6
+ __all__ = ["get_gold_prices"]
7
+
8
+
9
+ def _get_gold_prices_url(start: date, end: date) -> str:
10
+ return (f"https://api.nbp.pl/api/cenyzlota/"
11
+ f"{start.isoformat()}/{end.isoformat()}/")
12
+
13
+
14
+ def _get_gold_prices_chunk(start: date, end: date) -> pd.DataFrame:
15
+ return pd.DataFrame(
16
+ data=run_web_api_query(
17
+ url=_get_gold_prices_url(start=start, end=end)
18
+ )
19
+ ).rename(
20
+ columns={"data": "date", "cena": "price_of_1g_of_gold_in_pln"},
21
+ inplace=False
22
+ ).copy(deep=True)
23
+
24
+
25
+ def get_gold_prices(start: date, end: date):
26
+ chunks = split_dates_range_into_smaller_chunks(start=start, end=end)
27
+ dfs = []
28
+ for start_, end_ in chunks:
29
+ dfs.append(_get_gold_prices_chunk(start=start_, end=end_))
30
+ return pd.concat(
31
+ dfs, axis=0
32
+ ).reset_index(drop=True, inplace=False).copy(deep=True)
@@ -0,0 +1,62 @@
1
+ from datetime import datetime
2
+ from collections import defaultdict
3
+ import xml.etree.ElementTree as ET
4
+ import requests
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ __all__ = ["get_interest_rates_table"]
9
+
10
+
11
+ RATES_NAMES_DICT: dict[str, str] = {
12
+ "dys": "discount_rate", "dep": "deposit_rate", "lom": "lombard_rate",
13
+ "ref": "reference_rate", "red": "rediscount_rate"
14
+ }
15
+
16
+
17
+ def get_interest_rates_table() -> pd.DataFrame:
18
+ xml = load_xml()
19
+ data_dict = parse_xml(xml=xml)
20
+ return data_dict_to_dataframe(data_dict)
21
+
22
+
23
+ def load_xml() -> str:
24
+ response = requests.get( # noqa
25
+ "https://static.nbp.pl/dane/stopy/stopy_procentowe_archiwum.xml",
26
+ timeout=120
27
+ )
28
+ response_text = response.text
29
+ return response_text[response_text.find("<?xml version"):]
30
+
31
+
32
+ def parse_xml(xml: str):
33
+ tree_root = ET.fromstring(xml) # pylint: disable=E1101
34
+ entries = tree_root.findall("pozycje") # noqa
35
+ data_dict: defaultdict = defaultdict(dict)
36
+ for entry in entries:
37
+ rates_list = []
38
+ for el in entry.findall("pozycja"):
39
+ rates_list.append((el.attrib["id"], el.attrib["oprocentowanie"]))
40
+ data_dict[entry.attrib["obowiazuje_od"]] = dict(rates_list)
41
+ rates_to_keep: list = list(RATES_NAMES_DICT.keys())
42
+ out_dict = {}
43
+ for k in data_dict:
44
+ out_dict[k] = {
45
+ el: float(data_dict[k][el].replace(",", "."))/100
46
+ for el in rates_to_keep if el in data_dict[k].keys()
47
+ }
48
+ for rate in rates_to_keep:
49
+ if rate not in out_dict[k].keys():
50
+ out_dict[k][rate] = np.nan
51
+ return out_dict
52
+
53
+
54
+ def data_dict_to_dataframe(data_dict: dict) -> pd.DataFrame:
55
+ df = pd.DataFrame.from_dict(data=data_dict, orient="index")
56
+ df.index = pd.Index([datetime.strptime(el, "%Y-%m-%d") for el in df.index])
57
+ df.reset_index(inplace=True, drop=False)
58
+ dict_cols_renaming = {
59
+ **RATES_NAMES_DICT, **{"index": "valid_from_date"}
60
+ }
61
+ df.rename(columns=dict_cols_renaming, inplace=True)
62
+ return df
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "pynbpapi"
3
+ version = "1.0.0"
4
+ description = "Tidy downloading of National Bank of Poland interest rates, FX rates and gold price data"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ keywords = [
8
+ "NBP", "FX", "PLN", "gold", "API", "REST-API", "FX-rates", "zloty"
9
+ ]
10
+ authors = [
11
+ {name = "Artur Wegrzyn", email = "awegrzyn17@gmail.com" }
12
+ ]
13
+ maintainers = [
14
+ {name = "Artur Wegrzyn", email = "awegrzyn17@gmail.com" }
15
+ ]
16
+ dependencies = [
17
+ "pandas",
18
+ "requests",
19
+ "python-dateutil",
20
+ "loguru"
21
+ ]
22
+
23
+ [build-system]
24
+ requires = ["setuptools>=69.0.3", "wheel"]
25
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,28 @@
1
+ certifi==2022.12.7
2
+ charset-normalizer==3.1.0
3
+ coverage==7.4.1
4
+ flake8==7.3.0
5
+ idna==3.4
6
+ iniconfig==2.0.0
7
+ loguru==0.7.2
8
+ mccabe==0.7.0
9
+ mypy==1.8.0
10
+ mypy-extensions==1.0.0
11
+ numpy==1.26.4
12
+ packaging==23.2
13
+ pandas==2.0.1
14
+ pandas-stubs==2.2.0.240218
15
+ pluggy==1.4.0
16
+ pycodestyle==2.14.0
17
+ pyflakes==3.4.0
18
+ pytest==8.0.1
19
+ python-dateutil==2.8.2
20
+ pytz==2023.3
21
+ requests==2.31.0
22
+ six==1.16.0
23
+ types-python-dateutil==2.8.19.20240106
24
+ types-pytz==2024.1.0.20240203
25
+ types-requests==2.31.0.20240218
26
+ typing_extensions==4.9.0
27
+ tzdata==2023.3
28
+ urllib3==2.2.1
File without changes
@@ -0,0 +1,19 @@
1
+ from datetime import date, timedelta
2
+ from dateutil.relativedelta import relativedelta
3
+ from pynbpapi.common import (get_default_dates_range,
4
+ split_dates_range_into_smaller_chunks)
5
+
6
+
7
+ def test_get_default_dates_range():
8
+ start, end = get_default_dates_range()
9
+ assert end == date.today() - timedelta(days=1)
10
+ assert start == end + relativedelta(years=-1) + timedelta(days=1)
11
+
12
+
13
+ def test_split_dates_range_into_smaller_chunks():
14
+ list_ = split_dates_range_into_smaller_chunks(
15
+ start=date(2019, 1, 1), end=date(2022, 9, 30))
16
+ assert isinstance(list_, list)
17
+ assert len(list_) == 4
18
+ assert list_[0][0] == date(2019, 1, 1)
19
+ assert list_[2][1] == date(2021, 12, 31)
@@ -0,0 +1,13 @@
1
+ from datetime import date
2
+ import pandas as pd
3
+ from pynbpapi.fx import get_fx_rate
4
+ from pynbpapi.ccy import Ccy
5
+
6
+
7
+ def test_get_fx_rates_for_currency_usd():
8
+ data = get_fx_rate(
9
+ ccy=Ccy.USD, start=date(2021, 3, 13), end=date(2022, 4, 15)
10
+ )
11
+ assert isinstance(data, pd.DataFrame)
12
+ assert data.shape[1] == 2
13
+ assert data.iloc[10, 1] == 3.957
@@ -0,0 +1,10 @@
1
+ from datetime import date
2
+ import numpy as np
3
+ import pandas as pd
4
+ from pynbpapi.gold import get_gold_prices
5
+
6
+
7
+ def test_get_gold_prices():
8
+ data = get_gold_prices(start=date(2017, 8, 3), end=date(2021, 3, 13))
9
+ assert isinstance(data, pd.DataFrame)
10
+ assert np.all(data.columns == ["date", "price_of_1g_of_gold_in_pln"])
@@ -0,0 +1,13 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from pynbpapi.interest_rates import get_interest_rates_table
4
+
5
+
6
+ def test_df_get_interest_rates_archive():
7
+ data = get_interest_rates_table()
8
+ assert isinstance(data, pd.DataFrame)
9
+ assert data.shape[1] == 6
10
+ assert (np.all(data.columns == [
11
+ 'valid_from_date', 'lombard_rate', 'reference_rate',
12
+ 'rediscount_rate', 'discount_rate', 'deposit_rate']
13
+ ))