solvency2-data 0.1.14__zip
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.
- Anaconda3/Lib/site-packages/solvency2_data/__init__.py +9 -0
- Anaconda3/Lib/site-packages/solvency2_data/__pycache__/__init__.cpython-37.pyc +0 -0
- Anaconda3/Lib/site-packages/solvency2_data/__pycache__/rfr.cpython-37.pyc +0 -0
- Anaconda3/Lib/site-packages/solvency2_data/rfr.py +480 -0
- Anaconda3/Lib/site-packages/solvency2_data/solvency2_data.cfg +3 -0
- Anaconda3/Lib/site-packages/solvency2_data-0.1.14-py3.7.egg-info/PKG-INFO +132 -0
- Anaconda3/Lib/site-packages/solvency2_data-0.1.14-py3.7.egg-info/SOURCES.txt +36 -0
- Anaconda3/Lib/site-packages/solvency2_data-0.1.14-py3.7.egg-info/dependency_links.txt +1 -0
- Anaconda3/Lib/site-packages/solvency2_data-0.1.14-py3.7.egg-info/not-zip-safe +1 -0
- Anaconda3/Lib/site-packages/solvency2_data-0.1.14-py3.7.egg-info/requires.txt +12 -0
- Anaconda3/Lib/site-packages/solvency2_data-0.1.14-py3.7.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""Main module."""
|
|
4
|
+
|
|
5
|
+
__author__ = """Willem Jan Willemse"""
|
|
6
|
+
__email__ = 'w.j.willemse@xs4all.nl'
|
|
7
|
+
__version__ = '0.1.14'
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from urllib.request import urlopen
|
|
11
|
+
import zipfile
|
|
12
|
+
import os
|
|
13
|
+
from os.path import join
|
|
14
|
+
import numpy as np
|
|
15
|
+
from numpy.linalg import inv
|
|
16
|
+
import configparser
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
countries_list = ['Euro', 'Austria', 'Belgium', 'Bulgaria', 'Croatia',
|
|
20
|
+
'Cyprus', 'Czech Republic', 'Denmark', 'Estonia',
|
|
21
|
+
'Finland', 'France', 'Germany', 'Greece', 'Hungary',
|
|
22
|
+
'Iceland', 'Ireland', 'Italy', 'Latvia', 'Liechtenstein',
|
|
23
|
+
'Lithuania', 'Luxembourg', 'Malta', 'Netherlands',
|
|
24
|
+
'Norway', 'Poland', 'Portugal', 'Romania', 'Russia',
|
|
25
|
+
'Slovakia', 'Slovenia', 'Spain', 'Sweden', 'Switzerland',
|
|
26
|
+
'United Kingdom', 'Australia', 'Brazil', 'Canada',
|
|
27
|
+
'Chile', 'China', 'Colombia', 'Hong Kong', 'India',
|
|
28
|
+
'Japan', 'Malaysia', 'Mexico', 'New Zealand', 'Singapore',
|
|
29
|
+
'South Africa', 'South Korea', 'Taiwan', 'Thailand',
|
|
30
|
+
'Turkey', 'United States']
|
|
31
|
+
|
|
32
|
+
currencies = ["EUR", "BGN", "HRK", "CZK", "DKK", "HUF", "LIC", "PLN",
|
|
33
|
+
"NOK", "RON", "RUB", "SEK", "CHF", "GBP", "AUD", "BRL",
|
|
34
|
+
"CAD", "CLP", "CNY", "COP", "HKD", "INR", "JPY", "MYR",
|
|
35
|
+
"MXN", "NZD", "SGD", "ZAR", "KRW", "TWD", "THB", "TRY",
|
|
36
|
+
"USD"]
|
|
37
|
+
|
|
38
|
+
class RiskFreeRate(dict):
|
|
39
|
+
|
|
40
|
+
def __init__(self, input_date):
|
|
41
|
+
self.update(read(input_date))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def RFR_reference_date(input_date=None, cache={}):
|
|
45
|
+
"""Returns the closest reference date prior to an input_date
|
|
46
|
+
The reference_date is put in a dict with the original input_date
|
|
47
|
+
If no input_date is given then today() is used
|
|
48
|
+
>>> RFR_reference_date(datetime(2018, 1, 1))
|
|
49
|
+
{'input_date': datetime.datetime(2018, 1, 1, 0, 0), 'reference_date':
|
|
50
|
+
'20171231'}
|
|
51
|
+
>>> RFR_reference_date(datetime(2018, 8, 15))
|
|
52
|
+
{'input_date': datetime.datetime(2018, 8, 15, 0, 0), 'reference_date':
|
|
53
|
+
'20180731'}
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
reference_date = input_date
|
|
57
|
+
|
|
58
|
+
if (reference_date is None) or (reference_date > datetime.today()):
|
|
59
|
+
reference_date = datetime.today()
|
|
60
|
+
|
|
61
|
+
if (reference_date.day < 5):
|
|
62
|
+
reference_date = reference_date.replace(day=1) - timedelta(days=1)
|
|
63
|
+
else:
|
|
64
|
+
reference_date = reference_date + timedelta(days=1)
|
|
65
|
+
|
|
66
|
+
# to do : check if end of month
|
|
67
|
+
reference_date = reference_date.replace(day=1) - timedelta(days=1)
|
|
68
|
+
|
|
69
|
+
cache["input_date"] = input_date
|
|
70
|
+
cache["reference_date"] = reference_date.strftime('%Y%m%d')
|
|
71
|
+
|
|
72
|
+
return cache
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def RFR_dict(input_date=None, cache={}):
|
|
76
|
+
"""Returns a dict with url and filenames from the EIOPA website based on the
|
|
77
|
+
input_date
|
|
78
|
+
>>> RFR_dict(datetime(2018,1,1))
|
|
79
|
+
{'input_date': datetime.datetime(2018, 1, 1, 0, 0),
|
|
80
|
+
'reference_date': '20171231',
|
|
81
|
+
'url': 'https://eiopa.europa.eu/Publications/Standards/',
|
|
82
|
+
'path_zipfile': '',
|
|
83
|
+
'name_zipfile': 'EIOPA_RFR_20171231.zip',
|
|
84
|
+
'path_excelfile': '',
|
|
85
|
+
'name_excelfile': 'EIOPA_RFR_20171231_Term_Structures.xlsx'}
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
cache = RFR_reference_date(input_date, cache)
|
|
89
|
+
|
|
90
|
+
reference_date = cache['reference_date']
|
|
91
|
+
cache['url'] = "https://www.eiopa.europa.eu/sites/default/files/risk_free_interest_rate/"
|
|
92
|
+
cache['name_zipfile'] = "eiopa_rfr_" + reference_date + ".zip"
|
|
93
|
+
cache['name_excelfile'] = "EIOPA_RFR_" + reference_date + \
|
|
94
|
+
"_Term_Structures" + ".xlsx"
|
|
95
|
+
cache['name_excelfile_spreads'] = "EIOPA_RFR_" + reference_date + \
|
|
96
|
+
"_PD_Cod" + ".xlsx"
|
|
97
|
+
|
|
98
|
+
return cache
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def download_RFR(input_date=None, cache={}):
|
|
102
|
+
"""Downloads the zipfile from the EIOPA website and extracts the Excel file
|
|
103
|
+
Returns the cache with info
|
|
104
|
+
>>> download_RFR(datetime(2018,1,1))
|
|
105
|
+
{'name_excelfile': 'EIOPA_RFR_20171231_Term_Structures.xlsx',
|
|
106
|
+
'input_date': datetime.datetime(2018, 1, 1, 0, 0),
|
|
107
|
+
'url': 'https://eiopa.europa.eu/Publications/Standards/',
|
|
108
|
+
'path_zipfile': '',
|
|
109
|
+
'name_zipfile': 'EIOPA_RFR_20171231.zip',
|
|
110
|
+
'path_excelfile': '',
|
|
111
|
+
'reference_date': '20171231',
|
|
112
|
+
"""
|
|
113
|
+
cache = RFR_dict(input_date, cache)
|
|
114
|
+
|
|
115
|
+
if not(os.path.isfile(join(cache["path_excelfile"],
|
|
116
|
+
cache["name_excelfile"])))\
|
|
117
|
+
or not(os.path.isfile(join(cache["path_excelfile"],
|
|
118
|
+
cache["name_excelfile_spreads"]))):
|
|
119
|
+
|
|
120
|
+
# download file
|
|
121
|
+
request = urlopen(cache["url"] + cache["name_zipfile"])
|
|
122
|
+
|
|
123
|
+
# save zip-file
|
|
124
|
+
output = open(join(cache['path_zipfile'], cache["name_zipfile"]), "wb")
|
|
125
|
+
output.write(request.read())
|
|
126
|
+
output.close()
|
|
127
|
+
|
|
128
|
+
# extract file from zip-file
|
|
129
|
+
zip_ref = zipfile.ZipFile(join(cache['path_zipfile'],
|
|
130
|
+
cache["name_zipfile"]))
|
|
131
|
+
zip_ref.extract(cache["name_excelfile"], cache['path_excelfile'])
|
|
132
|
+
zip_ref.extract(cache["name_excelfile_spreads"],
|
|
133
|
+
cache['path_excelfile'])
|
|
134
|
+
zip_ref.close()
|
|
135
|
+
|
|
136
|
+
# remove zip file
|
|
137
|
+
# os.remove(cache['path_zipfile'] + cache["name_zipfile"])
|
|
138
|
+
|
|
139
|
+
return cache
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def read_spreads(xls, cache={}):
|
|
143
|
+
|
|
144
|
+
cache["financial fundamental spreads"] = {}
|
|
145
|
+
for name in currencies:
|
|
146
|
+
df = pd.read_excel(io=xls,
|
|
147
|
+
sheet_name=name,
|
|
148
|
+
header=1,
|
|
149
|
+
usecols='W:AC',
|
|
150
|
+
nrows=30,
|
|
151
|
+
skiprows=8,
|
|
152
|
+
names=[0, 1, 2, 3, 4, 5, 6])
|
|
153
|
+
df.index = range(1, 31)
|
|
154
|
+
cache["financial fundamental spreads"][name] = df
|
|
155
|
+
|
|
156
|
+
cache["non-financial fundamental spreads"] = {}
|
|
157
|
+
for name in currencies:
|
|
158
|
+
df = pd.read_excel(io=xls,
|
|
159
|
+
sheet_name=name,
|
|
160
|
+
header=1,
|
|
161
|
+
usecols='W:AC',
|
|
162
|
+
nrows=30,
|
|
163
|
+
skiprows=48,
|
|
164
|
+
names=[0, 1, 2, 3, 4, 5, 6])
|
|
165
|
+
df.index = range(1, 31)
|
|
166
|
+
cache["non-financial fundamental spreads"][name] = df
|
|
167
|
+
|
|
168
|
+
cache["central government fundamental spreads"] = {}
|
|
169
|
+
for name in ['FS_Govts']:
|
|
170
|
+
df = pd.read_excel(io=xls,
|
|
171
|
+
sheet_name=name,
|
|
172
|
+
usecols='B:AF',
|
|
173
|
+
nrows=53,
|
|
174
|
+
index_col=0,
|
|
175
|
+
skiprows=9)
|
|
176
|
+
df.index = cache['RFR_spot_no_VA'].columns
|
|
177
|
+
cache["central government fundamental spreads"] = df.T
|
|
178
|
+
|
|
179
|
+
return cache
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def read_spot(xls, cache={}):
|
|
183
|
+
"""Reads the RFR spot from the Excel file
|
|
184
|
+
Returns the cache with the dataframes
|
|
185
|
+
>>>
|
|
186
|
+
"""
|
|
187
|
+
for name in ["RFR_spot_no_VA", "RFR_spot_with_VA",
|
|
188
|
+
"Spot_NO_VA_shock_UP", "Spot_NO_VA_shock_DOWN",
|
|
189
|
+
"Spot_WITH_VA_shock_UP", "Spot_WITH_VA_shock_DOWN"]:
|
|
190
|
+
df = pd.read_excel(io=xls,
|
|
191
|
+
sheet_name=name,
|
|
192
|
+
header=1,
|
|
193
|
+
nrows=158,
|
|
194
|
+
index_col=1)
|
|
195
|
+
# drop unnamed columns from the excel file
|
|
196
|
+
for col in df.columns:
|
|
197
|
+
if "Unnamed:" in col:
|
|
198
|
+
df = df.drop(col, axis=1)
|
|
199
|
+
df.loc["VA"].fillna(0, inplace=True)
|
|
200
|
+
df = df.iloc[8:]
|
|
201
|
+
df.index.names = ['Duration']
|
|
202
|
+
cache[name] = df
|
|
203
|
+
|
|
204
|
+
return cache
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def read_meta(xls, cache={}):
|
|
208
|
+
"""Reads the RFR metadata from the Excel file
|
|
209
|
+
Returns the cache with the dataframe
|
|
210
|
+
>>>
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
df_meta = pd.read_excel(xls,
|
|
214
|
+
sheet_name="RFR_spot_with_VA",
|
|
215
|
+
header=1,
|
|
216
|
+
index_col=1,
|
|
217
|
+
skipfooter=150)
|
|
218
|
+
# drop unnamed columns from the excel file
|
|
219
|
+
for col in df_meta.columns:
|
|
220
|
+
if "Unnamed:" in col:
|
|
221
|
+
df_meta = df_meta.drop(col, axis=1)
|
|
222
|
+
|
|
223
|
+
df_meta.loc["VA", :].fillna(0, inplace=True)
|
|
224
|
+
df_meta = df_meta.iloc[0:8]
|
|
225
|
+
df_meta.index.names = ['meta']
|
|
226
|
+
df_meta.index = df_meta.index.fillna("Info")
|
|
227
|
+
|
|
228
|
+
df_append = pd.DataFrame(index=['reference date'],
|
|
229
|
+
columns=df_meta.columns)
|
|
230
|
+
df_append.loc['reference date'] = cache["reference_date"]
|
|
231
|
+
df_meta = df_meta.append(df_append)
|
|
232
|
+
|
|
233
|
+
cache['meta'] = df_meta
|
|
234
|
+
|
|
235
|
+
return cache
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def read(input_date=None, path=None):
|
|
239
|
+
"""Reads the RFR for input_date and stores data in path
|
|
240
|
+
Returns the cache with the dataframe
|
|
241
|
+
>>>
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
if path is None:
|
|
245
|
+
# look in current directory for .cfg file
|
|
246
|
+
# if not exists then take the .cfg file in the package directory
|
|
247
|
+
config = configparser.RawConfigParser()
|
|
248
|
+
fname = 'solvency2_data.cfg'
|
|
249
|
+
if os.path.isfile(fname):
|
|
250
|
+
config.read(fname)
|
|
251
|
+
else:
|
|
252
|
+
config.read(os.path.join(os.path.dirname(__file__), fname))
|
|
253
|
+
|
|
254
|
+
path_zipfile = config.get('Directories', 'zip_files')
|
|
255
|
+
path_excelfile = config.get('Directories', 'excel_files')
|
|
256
|
+
else:
|
|
257
|
+
path_zipfile = path
|
|
258
|
+
path_excelfile = path
|
|
259
|
+
|
|
260
|
+
cache = {'path_zipfile': path_zipfile,
|
|
261
|
+
'path_excelfile': path_excelfile}
|
|
262
|
+
|
|
263
|
+
cache = download_RFR(input_date, cache)
|
|
264
|
+
xls = pd.ExcelFile(join(cache['path_excelfile'], cache["name_excelfile"]), engine='openpyxl')
|
|
265
|
+
cache = read_meta(xls, cache)
|
|
266
|
+
cache = read_spot(xls, cache)
|
|
267
|
+
xls_spreads = pd.ExcelFile(join(cache['path_excelfile'],
|
|
268
|
+
cache["name_excelfile_spreads"]), engine='openpyxl')
|
|
269
|
+
cache = read_spreads(xls_spreads, cache)
|
|
270
|
+
|
|
271
|
+
return cache
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def big_h(u, v):
|
|
275
|
+
"""The big h-function according to 139 of the specs.
|
|
276
|
+
"""
|
|
277
|
+
left = (u + v + np.exp(-u - v)) / 2
|
|
278
|
+
diff = np.abs(u - v)
|
|
279
|
+
right = (diff + np.exp(-diff)) / 2
|
|
280
|
+
return left - right
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def big_g(alfa, q, nrofcoup, t2, tau):
|
|
284
|
+
"""This function calculates 2 outputs:
|
|
285
|
+
output1: g(alfa)-tau where g(alfa) is according to 165 of the specs
|
|
286
|
+
output(2): gamma = Qb according to 156 of the specs
|
|
287
|
+
"""
|
|
288
|
+
n, m = q.shape
|
|
289
|
+
# construct m*m h-matrix
|
|
290
|
+
h = np.fromfunction(lambda i, j: big_h(alfa * (i+1) /
|
|
291
|
+
nrofcoup, alfa * (j+1) / nrofcoup), (m, m))
|
|
292
|
+
# Solving b according to 156 of specs, note: p = 1 by construction
|
|
293
|
+
res_1 = np.array([1 - np.sum(q[i]) for i in range(n)]).reshape((n, 1))
|
|
294
|
+
# b = ((Q'HQ)^(-1))(1-Q'1) according to 156 of the specs
|
|
295
|
+
b = np.matmul(inv(np.matmul(np.matmul(q, h), q.T)), res_1)
|
|
296
|
+
# gamma variable is used to store Qb according to 156 of specs
|
|
297
|
+
gamma = np.matmul(q.T, b)
|
|
298
|
+
res_2 = sum(gamma[i, 0] * (i+1) / nrofcoup for i in range(0, m))
|
|
299
|
+
res_3 = sum(gamma[i, 0] * np.sinh(alfa * (i+1) /
|
|
300
|
+
nrofcoup) for i in range(0, m))
|
|
301
|
+
kappa = (1 + alfa * res_2) / res_3
|
|
302
|
+
return (alfa / np.abs(1 - kappa * np.exp(t2 * alfa)) - tau, gamma)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def optimal_alfa(alfa, q, nrofcoup, t2, tau, precision):
|
|
306
|
+
|
|
307
|
+
new_alfa, gamma = big_g(alfa, q, nrofcoup, t2, tau)
|
|
308
|
+
if new_alfa > 0:
|
|
309
|
+
# scanning for the optimal alfa is based on the scan-procedure taken
|
|
310
|
+
# from Eiopa matlab production code in each for-next loop the next
|
|
311
|
+
# optimal alfa decimal is scanned for, starting with an stepsize
|
|
312
|
+
# of 0.1 (first decimal) followed by the a next decimal through
|
|
313
|
+
# stepsize = stepsize/10
|
|
314
|
+
stepsize = 0.1
|
|
315
|
+
# for alfa in range(alfamin + stepsize, 20, stepsize):
|
|
316
|
+
for i in range(0, 200):
|
|
317
|
+
alfa = alfa + stepsize + i / 10
|
|
318
|
+
new_alfa, gamma = big_g(alfa, q, nrofcoup, t2, tau)
|
|
319
|
+
if new_alfa <= 0:
|
|
320
|
+
break
|
|
321
|
+
for i in range(0, precision - 1):
|
|
322
|
+
alfa = alfa - stepsize
|
|
323
|
+
for i in range(1, 11):
|
|
324
|
+
alfa = alfa + stepsize / 10
|
|
325
|
+
new_alfa, gamma = big_g(alfa, q, nrofcoup, t2, tau)
|
|
326
|
+
if new_alfa <= 0:
|
|
327
|
+
break
|
|
328
|
+
stepsize = stepsize / 10
|
|
329
|
+
return (alfa, gamma)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def q_matrix(instrument, n, m, liquid_maturities, RatesIn, nrofcoup,
|
|
333
|
+
cra, log_ufr):
|
|
334
|
+
# Note: prices of all instruments are set to 1 by construction
|
|
335
|
+
# Hence 1: if Instrument = Zero then for Zero i there is only one pay-off
|
|
336
|
+
# of (1+r(i))^u(i) at time u(i)
|
|
337
|
+
# Hence 2: if Instrument = Swap or Bond then for Swap/Bond i there are
|
|
338
|
+
# pay-offs of r(i)/nrofcoup at time 1/nrofcoup, 2/nrofcoup, ...
|
|
339
|
+
# u(i)-1/nrofcoup plus a final pay-off of 1+r(i)/nrofcoup at time u(i)
|
|
340
|
+
q = np.zeros([n, m])
|
|
341
|
+
if instrument == "Zero":
|
|
342
|
+
for i, u in enumerate(liquid_maturities):
|
|
343
|
+
q[i, u-1] = np.exp(-log_ufr * u) *\
|
|
344
|
+
np.power(1 + RatesIn[u] - cra, u)
|
|
345
|
+
# elif instrument == "Swap" or instrument == "Bond":
|
|
346
|
+
# not yet correct
|
|
347
|
+
# for i in range(0, n):
|
|
348
|
+
# for j in range(1, u[i] * nrofcoup - 1):
|
|
349
|
+
# q[i, j] = np.exp(-log_ufr * j / nrofcoup) * (r[i] - cra)
|
|
350
|
+
# / nrofcoup
|
|
351
|
+
# q[i, j] = np.exp(-log_ufr * j / nrofcoup) * (1 + (r[i] - cra)
|
|
352
|
+
# / nrofcoup)
|
|
353
|
+
return q
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def smith_wilson(instrument="Zero",
|
|
357
|
+
liquid_maturities=[],
|
|
358
|
+
RatesIn={},
|
|
359
|
+
nrofcoup=1,
|
|
360
|
+
cra=0,
|
|
361
|
+
ufr=0,
|
|
362
|
+
min_alfa=0.05,
|
|
363
|
+
tau=1,
|
|
364
|
+
T2=60,
|
|
365
|
+
precision=6,
|
|
366
|
+
method="brute_force",
|
|
367
|
+
output_type="zero rates annual compounding"):
|
|
368
|
+
|
|
369
|
+
# Input:
|
|
370
|
+
# Instrument = {"Zero", "Bond", "Swap"},
|
|
371
|
+
# nrofcoup = Number of Coupon Payments per Year,
|
|
372
|
+
# CRA = Credit Risk Adjustment in basispoints,
|
|
373
|
+
# UFRac = Ultimate Forward Rate annual commpounded (perunage),
|
|
374
|
+
# T2 = Convergence Maturity
|
|
375
|
+
# DataIn = range (50 rows x 3 columns)
|
|
376
|
+
# Column1: Indicator Vector indicating if corresponding maturity
|
|
377
|
+
# is DLT to qualify as credible input
|
|
378
|
+
# Column2: Maturity Vector
|
|
379
|
+
|
|
380
|
+
assert (instrument == "Zero" and nrofcoup == 1),\
|
|
381
|
+
"instrument is zero bond, but with nrofcoup unequal to 1."
|
|
382
|
+
assert (method == "brute_force"),\
|
|
383
|
+
"Only brute force method is implemented."
|
|
384
|
+
assert (instrument == "Zero"), "No other instruments implemented yet."
|
|
385
|
+
|
|
386
|
+
# the number of liquid rates
|
|
387
|
+
n = len(liquid_maturities)
|
|
388
|
+
# nrofcoup * maximum liquid maturity
|
|
389
|
+
m = nrofcoup * max(liquid_maturities)
|
|
390
|
+
|
|
391
|
+
log_ufr = np.log(1 + ufr)
|
|
392
|
+
tau = tau / 10000
|
|
393
|
+
cra = cra / 10000
|
|
394
|
+
|
|
395
|
+
# Q' matrix according to 146 of specs;
|
|
396
|
+
q = q_matrix(instrument, n, m, liquid_maturities, RatesIn,
|
|
397
|
+
nrofcoup, cra, log_ufr)
|
|
398
|
+
|
|
399
|
+
# Determine optimal alfa with corresponding gamma
|
|
400
|
+
if method == "brute_force":
|
|
401
|
+
alfa, gamma = optimal_alfa(min_alfa, q, nrofcoup, T2, tau, precision)
|
|
402
|
+
alfa = np.round(alfa, 6)
|
|
403
|
+
|
|
404
|
+
# Now the SW-present value function according to 154 of the specs can be
|
|
405
|
+
# calculated: p(v)=exp(-lnUFR*v)*(1+H(v,u)*Qb)
|
|
406
|
+
# The G(v,u) matrix for maturities v = 1 to 121 according to 142 of the
|
|
407
|
+
# technical specs (Note: maturity 121 will not be used; see later)
|
|
408
|
+
g = np.fromfunction(lambda i, j:
|
|
409
|
+
np.where(j / nrofcoup > i,
|
|
410
|
+
alfa * (1 - np.exp(-alfa * j / nrofcoup) *
|
|
411
|
+
np.cosh(alfa * i)),
|
|
412
|
+
alfa * np.exp(-alfa * i) *
|
|
413
|
+
np.sinh(alfa * j / nrofcoup)), (121, m))
|
|
414
|
+
|
|
415
|
+
# The H(v,u) matrix for maturities v = 1 to 121 according to 139
|
|
416
|
+
# of the technical specs
|
|
417
|
+
# h[i, j] = big_h(alfa * i, alfa * (j+1) / nrofcoup) -> strange,
|
|
418
|
+
# is different from earlier def
|
|
419
|
+
h = np.fromfunction(lambda i, j: big_h(alfa * i / nrofcoup,
|
|
420
|
+
alfa * (j+1) / nrofcoup),
|
|
421
|
+
(122, m))
|
|
422
|
+
|
|
423
|
+
# First a temporary discount-vector will be used to store the in-between
|
|
424
|
+
# result H(v,u)*Qb = #(v,u)*gamma (see 154 of the specs)
|
|
425
|
+
temptempdiscount = np.matmul(h, gamma)
|
|
426
|
+
# Calculating G(v,u)*Qb according to 158 of the specs
|
|
427
|
+
temptempintensity = np.matmul(g, gamma)
|
|
428
|
+
|
|
429
|
+
tempdiscount = np.zeros(121)
|
|
430
|
+
tempintensity = np.zeros(121)
|
|
431
|
+
for i in range(0, 121):
|
|
432
|
+
tempdiscount[i] = temptempdiscount[i]
|
|
433
|
+
tempintensity[i] = temptempintensity[i]
|
|
434
|
+
|
|
435
|
+
# calculating (1'-exp(-alfa*u'))Qb as subresult for 160 of specs
|
|
436
|
+
res1 = sum((1 - np.exp(-alfa * (i + 1) / nrofcoup)) *
|
|
437
|
+
gamma[i, 0] for i in range(0, m))
|
|
438
|
+
|
|
439
|
+
# yield intensities
|
|
440
|
+
yldintensity = np.zeros(121)
|
|
441
|
+
yldintensity[0] = log_ufr - alfa * res1 # calculating 160 of the specs
|
|
442
|
+
# calculating 158 of the specs for maturity 1 year
|
|
443
|
+
yldintensity[1:] = log_ufr - np.log(1 + tempdiscount[1:]) / np.arange(1,
|
|
444
|
+
121)
|
|
445
|
+
|
|
446
|
+
# forward intensities # calculating 158 of the specs for higher maturities
|
|
447
|
+
fwintensity = np.zeros(121)
|
|
448
|
+
fwintensity[0] = log_ufr - alfa * res1 # calculating 160 of the specs
|
|
449
|
+
fwintensity[1:] = log_ufr - tempintensity[1:] / (1 + tempdiscount[1:])
|
|
450
|
+
|
|
451
|
+
# discount rates
|
|
452
|
+
discount = np.zeros(121)
|
|
453
|
+
discount[0] = 1
|
|
454
|
+
discount[1:] = np.exp(-log_ufr * np.arange(1, 121)) * (1 +
|
|
455
|
+
tempdiscount[1:])
|
|
456
|
+
|
|
457
|
+
# forward rates annual compounding
|
|
458
|
+
forwardac = np.zeros(121)
|
|
459
|
+
forwardac[0] = 0
|
|
460
|
+
forwardac[1:] = discount[:-1] / discount[1:] - 1
|
|
461
|
+
|
|
462
|
+
# zero rates annual compounding
|
|
463
|
+
zeroac = np.zeros(121)
|
|
464
|
+
zeroac[0] = 0
|
|
465
|
+
zeroac[1:] = np.power(discount[1:], -1 / np.arange(1, 121)) - 1
|
|
466
|
+
|
|
467
|
+
if output_type == "zero rates annual compounding":
|
|
468
|
+
output = zeroac
|
|
469
|
+
elif output_type == "forward rate annual compounding":
|
|
470
|
+
output = forwardac
|
|
471
|
+
elif output_type == "discount rates":
|
|
472
|
+
output = discount
|
|
473
|
+
elif output_type == "forward intensities":
|
|
474
|
+
output = fwintensity
|
|
475
|
+
elif output_type == "yield intensities":
|
|
476
|
+
output = yldintensity
|
|
477
|
+
elif output_type == "alfa":
|
|
478
|
+
output = alfa
|
|
479
|
+
|
|
480
|
+
return output
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 1.2
|
|
2
|
+
Name: solvency2-data
|
|
3
|
+
Version: 0.1.14
|
|
4
|
+
Summary: Package for reading the Solvency 2 Risk-Free Interest Rate Term Structures from the zip-files on the EIOPA website and deriving the term structures for alternative extrapolations
|
|
5
|
+
Home-page: https://github.com/wjwillemse/solvency2-data
|
|
6
|
+
Author: Willem Jan Willemse
|
|
7
|
+
Author-email: w.j.willemse@xs4all.nl
|
|
8
|
+
License: MIT/X license
|
|
9
|
+
Description: ==============
|
|
10
|
+
solvency2-data
|
|
11
|
+
==============
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
.. image:: https://img.shields.io/pypi/v/solvency2_data.svg
|
|
15
|
+
:target: https://pypi.python.org/pypi/solvency2-data
|
|
16
|
+
:alt: Pypi Version
|
|
17
|
+
.. image:: https://img.shields.io/travis/wjwillemse/solvency2-data.svg
|
|
18
|
+
:target: https://travis-ci.com/wjwillemse/solvency2-data
|
|
19
|
+
:alt: Build Status
|
|
20
|
+
.. image:: https://readthedocs.org/projects/solvency2-data/badge/?version=latest
|
|
21
|
+
:target: https://solvency2-data.readthedocs.io/en/latest/?badge=latest
|
|
22
|
+
:alt: Documentation Status
|
|
23
|
+
.. image:: https://img.shields.io/badge/License-MIT/X-blue.svg
|
|
24
|
+
:target: https://github.com/DeNederlandscheBank/solvency2-data/blob/master/LICENSE
|
|
25
|
+
:alt: License
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Package for reading the Solvency 2 Risk-Free Interest Rate Term Structures from the zip-files on the EIOPA website and deriving the term structures for alternative extrapolations.
|
|
30
|
+
|
|
31
|
+
* Free software: MIT/X license
|
|
32
|
+
* Documentation: https://solvency2-data.readthedocs.io.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
Features
|
|
36
|
+
--------
|
|
37
|
+
|
|
38
|
+
Here is what the package does:
|
|
39
|
+
|
|
40
|
+
- Downloading and extracting the zip-files from the EIOPA website
|
|
41
|
+
- Reading the term structures from Excel-files into Pandas DataFrames
|
|
42
|
+
- Deriving term structures with other parameters for alternative extrapolations
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
Overview
|
|
46
|
+
--------
|
|
47
|
+
|
|
48
|
+
To install the package enter the following in the command prompt.
|
|
49
|
+
|
|
50
|
+
::
|
|
51
|
+
|
|
52
|
+
pip install solvency2-data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
Credits
|
|
56
|
+
-------
|
|
57
|
+
|
|
58
|
+
This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
|
|
59
|
+
|
|
60
|
+
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
|
|
61
|
+
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
=======
|
|
65
|
+
History
|
|
66
|
+
=======
|
|
67
|
+
|
|
68
|
+
0.1.0 (2019-10-27)
|
|
69
|
+
------------------
|
|
70
|
+
|
|
71
|
+
* Development releases.
|
|
72
|
+
|
|
73
|
+
0.1.1 (2019-11-6)
|
|
74
|
+
-----------------
|
|
75
|
+
|
|
76
|
+
* First release on PyPI.
|
|
77
|
+
|
|
78
|
+
0.1.3
|
|
79
|
+
-----
|
|
80
|
+
|
|
81
|
+
* First working version.
|
|
82
|
+
|
|
83
|
+
0.1.4 (2019-11-28)
|
|
84
|
+
------------------
|
|
85
|
+
|
|
86
|
+
* Solvency 2 shocked curves added.
|
|
87
|
+
|
|
88
|
+
0.1.5 (2020-1-28)
|
|
89
|
+
-----------------
|
|
90
|
+
|
|
91
|
+
* Spreads from PD_Cod Excel file added
|
|
92
|
+
|
|
93
|
+
0.1.7 (2020-2-23)
|
|
94
|
+
-----------------
|
|
95
|
+
|
|
96
|
+
* Broken links from EIOPA website fixed
|
|
97
|
+
|
|
98
|
+
0.1.8 (2020-3-14)
|
|
99
|
+
-----------------
|
|
100
|
+
|
|
101
|
+
* Configuration file added to specify data directories
|
|
102
|
+
|
|
103
|
+
0.1.11 (2020-9-26)
|
|
104
|
+
------------------
|
|
105
|
+
|
|
106
|
+
* Bug fixes
|
|
107
|
+
|
|
108
|
+
0.1.13 (2020-9-27)
|
|
109
|
+
------------------
|
|
110
|
+
|
|
111
|
+
* Code satisfies Flake8
|
|
112
|
+
* solvency2_data.cfg added to pypi package
|
|
113
|
+
|
|
114
|
+
0.1.14 (2021-1-25)
|
|
115
|
+
------------------
|
|
116
|
+
|
|
117
|
+
* Bug nested module solved
|
|
118
|
+
|
|
119
|
+
Keywords: solvency2_data
|
|
120
|
+
Platform: UNKNOWN
|
|
121
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
122
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
123
|
+
Classifier: Intended Audience :: Developers
|
|
124
|
+
Classifier: Intended Audience :: Education
|
|
125
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
126
|
+
Classifier: Natural Language :: English
|
|
127
|
+
Classifier: Programming Language :: Python :: 3
|
|
128
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
129
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
130
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
131
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
132
|
+
Requires-Python: >=3.0, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
AUTHORS.rst
|
|
2
|
+
CONTRIBUTING.rst
|
|
3
|
+
HISTORY.rst
|
|
4
|
+
LICENSE
|
|
5
|
+
MANIFEST.in
|
|
6
|
+
README.rst
|
|
7
|
+
setup.cfg
|
|
8
|
+
setup.py
|
|
9
|
+
docs/Makefile
|
|
10
|
+
docs/authors.rst
|
|
11
|
+
docs/conf.py
|
|
12
|
+
docs/contributing.rst
|
|
13
|
+
docs/history.rst
|
|
14
|
+
docs/index.rst
|
|
15
|
+
docs/installation.rst
|
|
16
|
+
docs/make.bat
|
|
17
|
+
docs/readme.rst
|
|
18
|
+
docs/usage.rst
|
|
19
|
+
docs/_build/html/_static/file.png
|
|
20
|
+
docs/_build/html/_static/minus.png
|
|
21
|
+
docs/_build/html/_static/plus.png
|
|
22
|
+
solvency2_data/__init__.py
|
|
23
|
+
solvency2_data/rfr.py
|
|
24
|
+
solvency2_data/solvency2_data.cfg
|
|
25
|
+
solvency2_data.egg-info/PKG-INFO
|
|
26
|
+
solvency2_data.egg-info/SOURCES.txt
|
|
27
|
+
solvency2_data.egg-info/dependency_links.txt
|
|
28
|
+
solvency2_data.egg-info/not-zip-safe
|
|
29
|
+
solvency2_data.egg-info/requires.txt
|
|
30
|
+
solvency2_data.egg-info/top_level.txt
|
|
31
|
+
tests/__init__.py
|
|
32
|
+
tests/test_rfr.py
|
|
33
|
+
tests/test_data/.gitkeep
|
|
34
|
+
tests/test_data/EIOPA_RFR_20171231_PD_Cod.xlsx
|
|
35
|
+
tests/test_data/EIOPA_RFR_20171231_Term_Structures.xlsx
|
|
36
|
+
tests/test_data/eiopa_rfr_20171231.zip
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
solvency2_data
|