solvency2-data 0.2.0__py3-none-any.whl
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.
- solvency2_data/__init__.py +9 -0
- solvency2_data/alternative_extrapolation.py +132 -0
- solvency2_data/eiopa_data.py +441 -0
- solvency2_data/rfr.py +456 -0
- solvency2_data/scraping.py +189 -0
- solvency2_data/smith_wilson.py +374 -0
- solvency2_data/solvency2_data.cfg +5 -0
- solvency2_data/sqlite_handler.py +252 -0
- solvency2_data/util.py +60 -0
- solvency2_data-0.2.0.dist-info/LICENSE +25 -0
- solvency2_data-0.2.0.dist-info/METADATA +61 -0
- solvency2_data-0.2.0.dist-info/RECORD +13 -0
- solvency2_data-0.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
|
|
5
|
+
CRA = 0.001
|
|
6
|
+
MAX_ERROR = 0.0000000001
|
|
7
|
+
MAX_RUNS = 20
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def DiscountedValue4par2forwards(
|
|
11
|
+
sum_df: float = 0,
|
|
12
|
+
last_df: float = 0,
|
|
13
|
+
par_rate: float = 0,
|
|
14
|
+
forward_rate: float = 0,
|
|
15
|
+
t_min_k: int = 0,
|
|
16
|
+
) -> float:
|
|
17
|
+
"""
|
|
18
|
+
Calculates the discounted value for two-factor parallel forwards.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
sum_df (float, optional): The sum of discount factors. Defaults to 0.
|
|
22
|
+
last_df (float, optional): The last discount factor. Defaults to 0.
|
|
23
|
+
par_rate (float, optional): The par rate. Defaults to 0.
|
|
24
|
+
forward_rate (float, optional): The forward rate. Defaults to 0.
|
|
25
|
+
t_min_k (int, optional): The difference between the two terms. Defaults to 0.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
float: The calculated discounted value.
|
|
29
|
+
"""
|
|
30
|
+
disc_val_1 = sum_df * par_rate
|
|
31
|
+
disc_val_2 = 0
|
|
32
|
+
for i in range(1, t_min_k + 1):
|
|
33
|
+
disc_val_1 += par_rate * last_df / ((1 + forward_rate) ** i)
|
|
34
|
+
disc_val_2 -= i * par_rate * last_df / ((1 + forward_rate) ** (i + 1))
|
|
35
|
+
disc_val_1 += last_df / ((1 + forward_rate) ** t_min_k) - 1
|
|
36
|
+
disc_val_2 -= t_min_k * last_df / ((1 + forward_rate) ** (t_min_k + 1))
|
|
37
|
+
return disc_val_1, disc_val_2
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def FromParToForwards(
|
|
41
|
+
term_struct: pd.Series(dtype="float64") = None,
|
|
42
|
+
span: int = 120,
|
|
43
|
+
max_runs: int = MAX_RUNS,
|
|
44
|
+
max_error: float = MAX_ERROR,
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Converts a par rate term structure to forward rates.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
term_struct (pd.Series, optional): The par rate term structure. Defaults to None.
|
|
51
|
+
span (int, optional): The span of the forward rates. Defaults to 120.
|
|
52
|
+
max_runs (int, optional): The maximum number of iterations for convergence. Defaults to MAX_RUNS.
|
|
53
|
+
max_error (float, optional): The maximum error for convergence. Defaults to MAX_ERROR.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
pd.Series: The forward rates term structure.
|
|
57
|
+
"""
|
|
58
|
+
forwards_struct = np.zeros(span)
|
|
59
|
+
|
|
60
|
+
sum_df = 0
|
|
61
|
+
df = 1
|
|
62
|
+
previous_maturity = 0
|
|
63
|
+
for maturity in term_struct.keys():
|
|
64
|
+
f = 0
|
|
65
|
+
t_min_k = maturity - previous_maturity
|
|
66
|
+
disc_val_1, disc_val_2 = DiscountedValue4par2forwards(
|
|
67
|
+
sum_df, df, term_struct[maturity], f, t_min_k
|
|
68
|
+
)
|
|
69
|
+
k = 0
|
|
70
|
+
while np.abs(disc_val_1) >= max_error and k <= max_runs:
|
|
71
|
+
f = f - disc_val_1 / disc_val_2
|
|
72
|
+
disc_val_1, disc_val_2 = DiscountedValue4par2forwards(
|
|
73
|
+
sum_df, df, term_struct[maturity], f, t_min_k
|
|
74
|
+
)
|
|
75
|
+
k = k + 1
|
|
76
|
+
for i in range(previous_maturity + 1, maturity + 1):
|
|
77
|
+
forwards_struct[i - 1] = f
|
|
78
|
+
df /= 1 + forwards_struct[i - 1]
|
|
79
|
+
sum_df += df
|
|
80
|
+
previous_maturity = maturity
|
|
81
|
+
|
|
82
|
+
for i in range(term_struct.keys()[-1], span + 1):
|
|
83
|
+
forwards_struct[i - 1] = forwards_struct[i - 2]
|
|
84
|
+
|
|
85
|
+
return pd.Series(data=forwards_struct, index=range(1, span + 1), dtype="float64")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def create_swap_struct(
|
|
89
|
+
rfr: pd.Series(dtype="float64") = None, additional_swaps: dict = {}
|
|
90
|
+
) -> pd.Series(dtype="float64"):
|
|
91
|
+
"""
|
|
92
|
+
Creates a swap structure.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
rfr (pd.Series, optional): The risk-free rate term structure. Defaults to None.
|
|
96
|
+
additional_swaps (dict, optional): Additional swaps to be included. Defaults to {}.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
pd.Series: The swap structure.
|
|
100
|
+
"""
|
|
101
|
+
swap_struct = OrderedDict()
|
|
102
|
+
denom = 0
|
|
103
|
+
for duration in range(1, 21):
|
|
104
|
+
rate = rfr[duration]
|
|
105
|
+
denom += (1 + rate) ** (-duration)
|
|
106
|
+
swap_struct[duration] = (1 - (1 + rate) ** (-duration)) / denom
|
|
107
|
+
for key in additional_swaps.keys():
|
|
108
|
+
swap_struct[key] = additional_swaps[key] - CRA
|
|
109
|
+
return pd.Series(
|
|
110
|
+
index=swap_struct.keys(), data=swap_struct.values(), dtype="float64"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def forwardstruct2termstruct(
|
|
115
|
+
forward_struct: pd.Series(dtype="float64"),
|
|
116
|
+
) -> pd.Series(dtype="float64"):
|
|
117
|
+
"""
|
|
118
|
+
Converts a forward rate structure to a term structure.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
forward_struct (pd.Series): The forward rate structure.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
pd.Series: The term structure.
|
|
125
|
+
"""
|
|
126
|
+
alt_term_struct = pd.Series(index=forward_struct.index, dtype="float64")
|
|
127
|
+
alt_term_struct[1] = forward_struct[1]
|
|
128
|
+
previous_forward = 1 + alt_term_struct[1]
|
|
129
|
+
for i in range(2, len(forward_struct) + 1):
|
|
130
|
+
alt_term_struct[i] = (previous_forward * (1 + forward_struct[i])) ** (1 / i) - 1
|
|
131
|
+
previous_forward = (1 + alt_term_struct[i]) ** i
|
|
132
|
+
return pd.Series(data=alt_term_struct, index=forward_struct.index, dtype="float64")
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Downloads rfr and stores in sqlite database for future reference
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import zipfile
|
|
12
|
+
import pandas as pd
|
|
13
|
+
import urllib
|
|
14
|
+
from datetime import date
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
from solvency2_data.sqlite_handler import EiopaDB
|
|
18
|
+
from solvency2_data.util import get_config
|
|
19
|
+
from solvency2_data.rfr import read_spot, read_spreads, read_govies, read_meta
|
|
20
|
+
from solvency2_data.scraping import eiopa_link
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_workspace() -> dict:
|
|
24
|
+
"""
|
|
25
|
+
Retrieves workspace directories and paths from the configuration.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
dict: A dictionary containing workspace directories and paths.
|
|
29
|
+
The dictionary includes the following keys:
|
|
30
|
+
- "database": The path to the EIOPA database file.
|
|
31
|
+
- "raw_data": The path to the directory containing raw data.
|
|
32
|
+
"""
|
|
33
|
+
config = get_config().get("Directories")
|
|
34
|
+
path_db = config.get("db_folder")
|
|
35
|
+
database = os.path.join(path_db, "eiopa.db")
|
|
36
|
+
path_raw = config.get("raw_data")
|
|
37
|
+
return {"database": database, "raw_data": path_raw}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def download_file(url: str, raw_folder: str, filename: str = "") -> str:
|
|
41
|
+
"""
|
|
42
|
+
Downloads a file from a URL and saves it in a specified folder.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
url (str): The URL of the file to download.
|
|
46
|
+
raw_folder (str): The path to the directory where the file will be saved.
|
|
47
|
+
filename (str, optional): The desired filename. If not provided,
|
|
48
|
+
the filename will be derived from the URL. Defaults to "".
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
str: The path to the downloaded file.
|
|
52
|
+
"""
|
|
53
|
+
if filename:
|
|
54
|
+
extension = url[(url.rfind(".")) :]
|
|
55
|
+
if extension not in filename:
|
|
56
|
+
filename = filename + extension
|
|
57
|
+
else:
|
|
58
|
+
pass
|
|
59
|
+
else:
|
|
60
|
+
# if filename not specified, then the file name will be the original file name
|
|
61
|
+
filename = url[(url.rfind("/") + 1) :]
|
|
62
|
+
# make sure that the filename does not contain illegal characters
|
|
63
|
+
filename = re.sub(r"[^\w_. -]", "_", filename)
|
|
64
|
+
|
|
65
|
+
if filename[-4:] != ".zip":
|
|
66
|
+
filename = filename + ".zip"
|
|
67
|
+
|
|
68
|
+
target_file = os.path.join(raw_folder, filename)
|
|
69
|
+
|
|
70
|
+
if os.path.isfile(target_file):
|
|
71
|
+
logging.info("file already exists in this location, not downloading")
|
|
72
|
+
else:
|
|
73
|
+
if not os.path.exists(raw_folder):
|
|
74
|
+
os.makedirs(raw_folder)
|
|
75
|
+
urllib.request.urlretrieve(url, target_file) # simpler for file downloading
|
|
76
|
+
logging.info(
|
|
77
|
+
"file downloaded and saved in the following location: " + target_file
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return target_file
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def download_EIOPA_rates(url: str, ref_date: str, workspace: dict = None) -> dict:
|
|
84
|
+
"""
|
|
85
|
+
Downloads EIOPA RFR (Risk-Free Rate) files from a given URL and extracts them.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
url (str): The URL from which to download the EIOPA RFR files.
|
|
89
|
+
ref_date (str): The reference date in the format "%Y-%m-%d".
|
|
90
|
+
workspace (dict, optional): A dictionary containing workspace directories and paths.
|
|
91
|
+
If None, it retrieves workspace information using get_workspace() function.
|
|
92
|
+
Defaults to None.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
dict: A dictionary containing the paths to the downloaded files.
|
|
96
|
+
The dictionary includes the following keys:
|
|
97
|
+
- "rfr": The path to the downloaded EIOPA RFR term structures Excel file.
|
|
98
|
+
- "meta": The path to the downloaded EIOPA RFR meta Excel file.
|
|
99
|
+
- "spreads": The path to the downloaded EIOPA RFR PD Cod Excel file.
|
|
100
|
+
- "govies": The path to the downloaded EIOPA RFR govvies Excel file.
|
|
101
|
+
"""
|
|
102
|
+
if workspace is None:
|
|
103
|
+
workspace = get_workspace()
|
|
104
|
+
raw_folder = workspace["raw_data"]
|
|
105
|
+
zip_file = download_file(url, raw_folder)
|
|
106
|
+
|
|
107
|
+
# Change format of ref_date string for EIOPA Excel files from YYYY-mm-dd to YYYYmmdd:
|
|
108
|
+
reference_date = ref_date.replace("-", "")
|
|
109
|
+
|
|
110
|
+
name_excelfile = "EIOPA_RFR_" + reference_date + "_Term_Structures" + ".xlsx"
|
|
111
|
+
name_excelfile_spreads = "EIOPA_RFR_" + reference_date + "_PD_Cod" + ".xlsx"
|
|
112
|
+
# Making file paths string insensitve via regex
|
|
113
|
+
re_rfr = re.compile(f"(?i:{name_excelfile})")
|
|
114
|
+
re_spreads = re.compile(f"(?i:{name_excelfile_spreads})")
|
|
115
|
+
|
|
116
|
+
with zipfile.ZipFile(zip_file) as zipobj:
|
|
117
|
+
for file in zipobj.namelist():
|
|
118
|
+
res_rfr = re_rfr.search(file)
|
|
119
|
+
res_spreads = re_spreads.search(file)
|
|
120
|
+
if res_rfr:
|
|
121
|
+
rfr_file = res_rfr.group(0)
|
|
122
|
+
zipobj.extract(rfr_file, raw_folder)
|
|
123
|
+
if res_spreads:
|
|
124
|
+
spreads_file = res_spreads.group(0)
|
|
125
|
+
zipobj.extract(spreads_file, raw_folder)
|
|
126
|
+
return {
|
|
127
|
+
"rfr": os.path.join(raw_folder, name_excelfile),
|
|
128
|
+
"meta": os.path.join(raw_folder, name_excelfile),
|
|
129
|
+
"spreads": os.path.join(raw_folder, name_excelfile_spreads),
|
|
130
|
+
"govies": os.path.join(raw_folder, name_excelfile_spreads),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def extract_spot_rates(rfr_filepath: str) -> dict:
|
|
135
|
+
"""
|
|
136
|
+
Extracts spot rates from an EIOPA RFR Excel file.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
rfr_filepath (str): The path to the EIOPA RFR Excel file.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
dict: A dictionary containing extracted spot rates.
|
|
143
|
+
The dictionary includes spot rates indexed by scenario, currency code, and duration.
|
|
144
|
+
"""
|
|
145
|
+
logging.info("Extracting spots: " + rfr_filepath)
|
|
146
|
+
# TODO: Complete this remap dictionary
|
|
147
|
+
currency_codes_and_regions = {
|
|
148
|
+
"EUR": "Euro",
|
|
149
|
+
"PLN": "Poland",
|
|
150
|
+
"CHF": "Switzerland",
|
|
151
|
+
"USD": "United States",
|
|
152
|
+
"GBP": "United Kingdom",
|
|
153
|
+
"NOK": "Norway",
|
|
154
|
+
"SEK": "Sweden",
|
|
155
|
+
"DKK": "Denmark",
|
|
156
|
+
"HRK": "Croatia",
|
|
157
|
+
}
|
|
158
|
+
currency_dict = dict((v, k) for k, v in currency_codes_and_regions.items())
|
|
159
|
+
|
|
160
|
+
xls = pd.ExcelFile(rfr_filepath, engine="openpyxl")
|
|
161
|
+
rates_tables = read_spot(xls)
|
|
162
|
+
|
|
163
|
+
rates_tables = pd.concat(rates_tables)
|
|
164
|
+
rates_tables = rates_tables.rename(columns=currency_dict)[currency_dict.values()]
|
|
165
|
+
|
|
166
|
+
label_remap = {
|
|
167
|
+
"RFR_spot_no_VA": "base",
|
|
168
|
+
"RFR_spot_with_VA": "va",
|
|
169
|
+
"Spot_NO_VA_shock_UP": "up",
|
|
170
|
+
"Spot_NO_VA_shock_DOWN": "down",
|
|
171
|
+
"Spot_WITH_VA_shock_UP": "va_up",
|
|
172
|
+
"Spot_WITH_VA_shock_DOWN": "va_down",
|
|
173
|
+
}
|
|
174
|
+
rates_tables = rates_tables.rename(label_remap)
|
|
175
|
+
|
|
176
|
+
rates_tables = rates_tables.stack().rename("spot")
|
|
177
|
+
|
|
178
|
+
rates_tables.index.names = ["scenario", "duration", "currency_code"]
|
|
179
|
+
rates_tables.index = rates_tables.index.reorder_levels([0, 2, 1])
|
|
180
|
+
rates_tables = rates_tables.sort_index()
|
|
181
|
+
return rates_tables
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def extract_meta(rfr_filepath: str) -> dict:
|
|
185
|
+
"""
|
|
186
|
+
Extracts metadata from an EIOPA RFR Excel file.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
rfr_filepath (str): The path to the EIOPA RFR Excel file.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
dict: A dictionary containing extracted metadata.
|
|
193
|
+
The dictionary includes metadata indexed by country.
|
|
194
|
+
"""
|
|
195
|
+
logging.info("Extracting meta data :" + rfr_filepath)
|
|
196
|
+
meta = read_meta(rfr_filepath)
|
|
197
|
+
meta = pd.concat(meta).T
|
|
198
|
+
meta.columns = meta.columns.droplevel()
|
|
199
|
+
meta.index.name = "Country"
|
|
200
|
+
meta = meta.sort_index()
|
|
201
|
+
return meta
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def extract_spreads(spread_filepath):
|
|
205
|
+
"""
|
|
206
|
+
Extracts spreads data from an EIOPA RFR spreads Excel file.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
spread_filepath (str): The path to the EIOPA RFR spreads Excel file.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
pandas.DataFrame: A DataFrame containing extracted spreads data.
|
|
213
|
+
The DataFrame includes spreads indexed by type, currency code, credit curve step, and duration.
|
|
214
|
+
"""
|
|
215
|
+
logging.info("Extracting spreads: " + spread_filepath)
|
|
216
|
+
xls = pd.ExcelFile(spread_filepath, engine="openpyxl")
|
|
217
|
+
spreads = read_spreads(xls)
|
|
218
|
+
spreads_non_gov = pd.concat(
|
|
219
|
+
{
|
|
220
|
+
i: pd.concat(spreads[i])
|
|
221
|
+
for i in [
|
|
222
|
+
"financial fundamental spreads",
|
|
223
|
+
"non-financial fundamental spreads",
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
spreads_non_gov = spreads_non_gov.stack().rename("spread")
|
|
228
|
+
spreads_non_gov.index.names = ["type", "currency_code", "duration", "cc_step"]
|
|
229
|
+
spreads_non_gov.index = spreads_non_gov.index.reorder_levels([0, 1, 3, 2])
|
|
230
|
+
spreads_non_gov = spreads_non_gov.rename(
|
|
231
|
+
{
|
|
232
|
+
"financial fundamental spreads": "fin",
|
|
233
|
+
"non-financial fundamental spreads": "non_fin",
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
return spreads_non_gov
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def extract_govies(govies_filepath):
|
|
240
|
+
"""
|
|
241
|
+
Extracts government spreads data from an EIOPA RFR spreads Excel file.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
govies_filepath (str): The path to the EIOPA RFR spreads Excel file containing government spreads.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
pandas.DataFrame or None: A DataFrame containing extracted government spreads data,
|
|
248
|
+
indexed by country code and duration.
|
|
249
|
+
Returns None if no government spreads are found.
|
|
250
|
+
"""
|
|
251
|
+
logging.info("Extracting govies: " + govies_filepath)
|
|
252
|
+
xls = pd.ExcelFile(govies_filepath, engine="openpyxl")
|
|
253
|
+
cache = read_govies(xls)
|
|
254
|
+
if cache["central government fundamental spreads"] is not None:
|
|
255
|
+
spreads_gov = (
|
|
256
|
+
cache["central government fundamental spreads"]
|
|
257
|
+
.stack()
|
|
258
|
+
.rename("spread")
|
|
259
|
+
.to_frame()
|
|
260
|
+
)
|
|
261
|
+
spreads_gov.index.names = ["duration", "country_code"]
|
|
262
|
+
spreads_gov.index = spreads_gov.index.reorder_levels([1, 0])
|
|
263
|
+
else:
|
|
264
|
+
logging.error("No govies found: " + govies_filepath)
|
|
265
|
+
spreads_gov = None
|
|
266
|
+
return spreads_gov
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def extract_sym_adj(sym_adj_filepath: str, ref_date: str) -> pd.DataFrame:
|
|
270
|
+
"""
|
|
271
|
+
Extracts symmetric adjustment data from a file.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
sym_adj_filepath (str): The path to the file containing symmetric adjustment data.
|
|
275
|
+
ref_date (str): The reference date in the format "%Y-%m-%d".
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
pd.DataFrame or None: A DataFrame containing symmetric adjustment data.
|
|
279
|
+
Returns None if there is a date mismatch between the reference date provided
|
|
280
|
+
and the date in the file.
|
|
281
|
+
"""
|
|
282
|
+
df = pd.read_excel(
|
|
283
|
+
sym_adj_filepath,
|
|
284
|
+
sheet_name="Symmetric_adjustment",
|
|
285
|
+
usecols="E, K",
|
|
286
|
+
nrows=1,
|
|
287
|
+
skiprows=7,
|
|
288
|
+
header=None,
|
|
289
|
+
names=["ref_date", "sym_adj"],
|
|
290
|
+
).squeeze("columns")
|
|
291
|
+
|
|
292
|
+
input_ref = ref_date
|
|
293
|
+
ref_check = df.at[0, "ref_date"].strftime("%Y-%m-%d")
|
|
294
|
+
|
|
295
|
+
if input_ref != ref_check:
|
|
296
|
+
logging.warning("Date mismatch in sym_adj file: " + sym_adj_filepath)
|
|
297
|
+
logging.warning(
|
|
298
|
+
"Try opening this file and setting the date correctly then save and close, and rerun."
|
|
299
|
+
)
|
|
300
|
+
return None
|
|
301
|
+
else:
|
|
302
|
+
df = df.set_index("ref_date")
|
|
303
|
+
return df
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def add_to_db(
|
|
307
|
+
ref_date: str, db: EiopaDB, data_type: str = "rfr", workspace: dict = None
|
|
308
|
+
):
|
|
309
|
+
"""
|
|
310
|
+
Adds data to the EIOPA database, to use when you are missing data.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
ref_date (str): The reference date in the format "%Y-%m-%d".
|
|
314
|
+
db (EiopaDB): The EIOPA database instance.
|
|
315
|
+
data_type (str, optional): The type of data to add.
|
|
316
|
+
Options: "rfr" (default), "meta", "spreads", "govies", "sym_adj".
|
|
317
|
+
workspace (dict, optional): A dictionary containing workspace directories and paths.
|
|
318
|
+
If None, it retrieves workspace information using get_workspace() function.
|
|
319
|
+
Defaults to None.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
None
|
|
323
|
+
"""
|
|
324
|
+
url = eiopa_link(ref_date, data_type=data_type)
|
|
325
|
+
set_id = db.get_set_id(url)
|
|
326
|
+
|
|
327
|
+
if data_type != "sym_adj":
|
|
328
|
+
files = download_EIOPA_rates(url, ref_date)
|
|
329
|
+
if data_type == "rfr":
|
|
330
|
+
df = extract_spot_rates(files[data_type])
|
|
331
|
+
elif data_type == "meta":
|
|
332
|
+
df = extract_meta(files[data_type])
|
|
333
|
+
elif data_type == "spreads":
|
|
334
|
+
df = extract_spreads(files[data_type])
|
|
335
|
+
elif data_type == "govies":
|
|
336
|
+
df = extract_govies(files[data_type])
|
|
337
|
+
else:
|
|
338
|
+
raise KeyError
|
|
339
|
+
elif data_type == "sym_adj":
|
|
340
|
+
if workspace is None:
|
|
341
|
+
workspace = get_workspace()
|
|
342
|
+
raw_folder = workspace["raw_data"]
|
|
343
|
+
file = download_file(url, raw_folder)
|
|
344
|
+
df = extract_sym_adj(file, ref_date)
|
|
345
|
+
|
|
346
|
+
if df is not None:
|
|
347
|
+
df = df.reset_index()
|
|
348
|
+
df["url_id"] = set_id
|
|
349
|
+
df["ref_date"] = ref_date
|
|
350
|
+
df.to_sql(data_type, con=db.conn, if_exists="append", index=False)
|
|
351
|
+
set_types = {"govies": "rfr", "spreads": "rfr", "meta": "rfr"}
|
|
352
|
+
db.update_catalog(
|
|
353
|
+
url_id=set_id,
|
|
354
|
+
dict_vals={
|
|
355
|
+
"set_type": set_types.get(data_type, data_type),
|
|
356
|
+
"primary_set": True,
|
|
357
|
+
"ref_date": ref_date,
|
|
358
|
+
},
|
|
359
|
+
)
|
|
360
|
+
return None
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def validate_date_string(ref_date):
|
|
364
|
+
"""
|
|
365
|
+
Validates the input date string.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
ref_date (str or datetime.date): The input date string or datetime.date object.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
str or None: A validated date string in the format "%Y-%m-%d".
|
|
372
|
+
Returns None if the input date type is not recognized or cannot be converted.
|
|
373
|
+
"""
|
|
374
|
+
if type(ref_date) == datetime.date:
|
|
375
|
+
return ref_date.strftime("%Y-%m-%d")
|
|
376
|
+
elif isinstance(ref_date, str):
|
|
377
|
+
try:
|
|
378
|
+
return datetime.datetime.strptime(ref_date, "%Y-%m-%d").strftime("%Y-%m-%d")
|
|
379
|
+
except (TypeError, ValueError):
|
|
380
|
+
logging.warning("Date type not recognised. Try datetime.date or YYYY-mm-dd")
|
|
381
|
+
return None
|
|
382
|
+
else:
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def get(ref_date: str, data_type: str = "rfr", workspace: dict = None):
|
|
387
|
+
"""
|
|
388
|
+
Retrieves data from the EIOPA database for a given reference date and data type.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
ref_date (str): The reference date in the format "%Y-%m-%d".
|
|
392
|
+
data_type (str, optional): The type of data to retrieve.
|
|
393
|
+
Options: "rfr" (default), "meta", "spreads", "govies", "sym_adj".
|
|
394
|
+
workspace (dict, optional): A dictionary containing workspace directories and paths.
|
|
395
|
+
If None, it retrieves workspace information using get_workspace() function.
|
|
396
|
+
Defaults to None.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
pandas.DataFrame or None: A DataFrame containing retrieved data.
|
|
400
|
+
Returns None if no data is found for the given reference date and data type.
|
|
401
|
+
"""
|
|
402
|
+
# Validate the provided ref_date:
|
|
403
|
+
ref_date = validate_date_string(ref_date)
|
|
404
|
+
# Check if DB exists, if not, create it:
|
|
405
|
+
if workspace is None:
|
|
406
|
+
workspace = get_workspace()
|
|
407
|
+
database = workspace["database"]
|
|
408
|
+
db = EiopaDB(database)
|
|
409
|
+
|
|
410
|
+
sql_map = {
|
|
411
|
+
"rfr": "SELECT * FROM rfr WHERE ref_date = '" + ref_date + "'",
|
|
412
|
+
"meta": "SELECT * FROM meta WHERE ref_date = '" + ref_date + "'",
|
|
413
|
+
"spreads": "SELECT * FROM spreads WHERE ref_date = '" + ref_date + "'",
|
|
414
|
+
"govies": "SELECT * FROM govies WHERE ref_date = '" + ref_date + "'",
|
|
415
|
+
"sym_adj": "SELECT * FROM sym_adj WHERE ref_date = '" + ref_date + "'",
|
|
416
|
+
}
|
|
417
|
+
sql = sql_map.get(data_type)
|
|
418
|
+
df = pd.read_sql(sql, con=db.conn)
|
|
419
|
+
if df.empty:
|
|
420
|
+
add_to_db(ref_date, db, data_type)
|
|
421
|
+
df = pd.read_sql(sql, con=db.conn)
|
|
422
|
+
if not df.empty:
|
|
423
|
+
df = df.drop(columns=["url_id", "ref_date"])
|
|
424
|
+
return df
|
|
425
|
+
else:
|
|
426
|
+
return None
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def refresh():
|
|
430
|
+
"""
|
|
431
|
+
Refreshes the EIOPA database by updating data for each month from January 2016 to the current month.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
str: A message indicating that the database has been successfully rebuilt.
|
|
435
|
+
"""
|
|
436
|
+
dr = pd.date_range(date(2016, 1, 31), date.today(), freq="M")
|
|
437
|
+
# dr = pd.date_range(date(2021, 11, 30), date.today(), freq="M")
|
|
438
|
+
for ref_date in dr:
|
|
439
|
+
for data_type in ["rfr", "meta", "spreads", "govies", "sym_adj"]:
|
|
440
|
+
_ = get(ref_date.date(), data_type)
|
|
441
|
+
return "Database successfully rebuilt"
|