riskfolio-lib 7.2.0__cp313-cp313-macosx_10_13_x86_64.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.
@@ -0,0 +1,433 @@
1
+ """""" #
2
+
3
+ """
4
+ Copyright (c) 2020-2026, Dany Cajas
5
+ All rights reserved.
6
+ This work is licensed under BSD 3-Clause "New" or "Revised" License.
7
+ License available at https://github.com/dcajasn/Riskfolio-Lib/blob/master/LICENSE.txt
8
+ """
9
+
10
+ import numpy as np
11
+ import cvxpy as cp
12
+ import math
13
+ from scipy.special import binom
14
+
15
+ __all__ = [
16
+ "owa_l_moment",
17
+ "owa_gmd",
18
+ "owa_cvar",
19
+ "owa_wcvar",
20
+ "owa_tg",
21
+ "owa_wr",
22
+ "owa_rg",
23
+ "owa_cvrg",
24
+ "owa_wcvrg",
25
+ "owa_tgrg",
26
+ "owa_l_moment_crm",
27
+ ]
28
+
29
+
30
+ def owa_l_moment(T, k=2):
31
+ r"""
32
+ Calculate the OWA weights to calculate the kth linear moment (l-moment)
33
+ of a returns series as shown in :cite:`d-Cajas6`.
34
+
35
+ Parameters
36
+ ----------
37
+ T : int
38
+ Number of observations of the returns series.
39
+ k : int
40
+ Order of the l-moment. Must be an integer higher or equal than 1.
41
+
42
+ Returns
43
+ -------
44
+ value : 1d-array
45
+ An OWA weights vector of size Tx1.
46
+ """
47
+ w = []
48
+ T_ = int(T)
49
+ for i in range(1, T_ + 1):
50
+ a = 0
51
+ for j in range(k):
52
+ a += (
53
+ (-1) ** j * binom(k - 1, j) * binom(i - 1, k - 1 - j) * binom(T_ - i, j)
54
+ )
55
+ a *= 1 / (k * binom(T_, k))
56
+ w.append(a)
57
+ return np.array(w).reshape(-1, 1)
58
+
59
+
60
+ def owa_gmd(T):
61
+ r"""
62
+ Calculate the OWA weights to calculate the Gini mean difference (GMD)
63
+ of a returns series as shown in :cite:`d-Cajas3`.
64
+
65
+ Parameters
66
+ ----------
67
+ T : int
68
+ Number of observations of the returns series.
69
+
70
+ Returns
71
+ -------
72
+ value : 1d-array
73
+ An OWA weights vector of size Tx1.
74
+ """
75
+
76
+ w_ = []
77
+ T_ = int(T)
78
+ for i in range(1, T_ + 1):
79
+ w_.append(2 * i - 1 - T_)
80
+ w_ = 2 * np.array(w_) / (T_ * (T_ - 1))
81
+ w_ = w_.reshape(-1, 1)
82
+
83
+ return w_
84
+
85
+
86
+ def owa_cvar(T, alpha=0.05):
87
+ r"""
88
+ Calculate the OWA weights to calculate the Conditional Value at Risk (CVaR)
89
+ of a returns series as shown in :cite:`d-Cajas3`.
90
+
91
+ Parameters
92
+ ----------
93
+ T : int
94
+ Number of observations of the returns series.
95
+ alpha : float, optional
96
+ Significance level of CVaR. The default is 0.05.
97
+
98
+ Returns
99
+ -------
100
+ value : 1d-array
101
+ An OWA weights vector of size Tx1.
102
+ """
103
+
104
+ T_ = int(T)
105
+ k = int(np.ceil(T_ * alpha)) - 1
106
+ w_ = np.zeros((T_, 1))
107
+ w_[:k, :] = -1 / (T_ * alpha)
108
+ w_[k, :] = -1 - np.sum(w_[:k, :])
109
+
110
+ return w_
111
+
112
+
113
+ def owa_wcvar(T, alphas, weights):
114
+ r"""
115
+ Calculate the OWA weights to calculate the Weighted Conditional Value at
116
+ Risk (WCVaR) of a returns series as shown in :cite:`d-Cajas3`.
117
+
118
+ Parameters
119
+ ----------
120
+ T : int
121
+ Number of observations of the returns series.
122
+ alphas : list
123
+ List of significance levels of each CVaR model.
124
+ weights : list
125
+ List of weights of each CVaR model.
126
+
127
+ Returns
128
+ -------
129
+ value : 1d-array
130
+ An OWA weights vector of size Tx1.
131
+ """
132
+
133
+ w_ = 0
134
+ T_ = int(T)
135
+ for i, j in zip(alphas, weights):
136
+ w_ += owa_cvar(T_, i) * j
137
+
138
+ return w_
139
+
140
+
141
+ def owa_tg(T, alpha=0.05, a_sim=100):
142
+ r"""
143
+ Calculate the OWA weights to calculate the Tail Gini of a
144
+ returns series as shown in :cite:`d-Cajas3`.
145
+
146
+ Parameters
147
+ ----------
148
+ T : int
149
+ Number of observations of the returns series.
150
+ alpha : float, optional
151
+ Significance level of TaiL Gini. The default is 0.05.
152
+ a_sim : float, optional
153
+ Number of CVaRs used to approximate the Tail Gini. The default is 100.
154
+
155
+ Returns
156
+ -------
157
+ value : 1d-array
158
+ A OWA weights vector of size Tx1.
159
+ """
160
+ T_ = int(T)
161
+ a_sim_ = int(a_sim)
162
+ alphas = np.linspace(alpha, 0.0001, a_sim_)[::-1]
163
+ w_ = [(alphas[1] - 0) * alphas[0] / alphas[-1] ** 2]
164
+ for i in range(1, len(alphas) - 1):
165
+ w_.append((alphas[i + 1] - alphas[i - 1]) * alphas[i] / alphas[-1] ** 2)
166
+ w_.append((alphas[-1] - alphas[-2]) / alphas[-1])
167
+ w_ = owa_wcvar(T_, alphas, w_)
168
+
169
+ return w_
170
+
171
+
172
+ def owa_wr(T):
173
+ r"""
174
+ Calculate the OWA weights to calculate the Worst realization (minimum)
175
+ of a returns series as shown in :cite:`d-Cajas3`.
176
+
177
+ Parameters
178
+ ----------
179
+ T : int
180
+ Number of observations of the returns series.
181
+
182
+ Returns
183
+ -------
184
+ value : 1d-array
185
+ A OWA weights vector of size Tx1.
186
+ """
187
+
188
+ T_ = int(T)
189
+ w_ = np.zeros((T_, 1))
190
+ w_[0, :] = -1
191
+
192
+ return w_
193
+
194
+
195
+ def owa_rg(T):
196
+ r"""
197
+ Calculate the OWA weights to calculate the range of a returns series
198
+ as shown in :cite:`d-Cajas3`.
199
+
200
+ Parameters
201
+ ----------
202
+ T : int
203
+ Number of observations of the returns series.
204
+
205
+ Returns
206
+ -------
207
+ value : 1d-array
208
+ A OWA weights vector of size Tx1.
209
+ """
210
+
211
+ T_ = int(T)
212
+ w_ = np.zeros((T_, 1))
213
+ w_[0, :] = -1
214
+ w_[-1, :] = 1
215
+
216
+ return w_
217
+
218
+
219
+ def owa_cvrg(T, alpha=0.05, beta=None):
220
+ r"""
221
+ Calculate the OWA weights to calculate the CVaR range of a returns series
222
+ as shown in :cite:`d-Cajas3`.
223
+
224
+ Parameters
225
+ ----------
226
+ T : int
227
+ Number of observations of the returns series.
228
+ alpha : float, optional
229
+ Significance level of CVaR of losses. The default is 0.05.
230
+ beta : float, optional
231
+ Significance level of CVaR of gains. If None it duplicates alpha.
232
+ The default is None.
233
+
234
+ Returns
235
+ -------
236
+ value : 1d-array
237
+ A OWA weights vector of size Tx1.
238
+ """
239
+
240
+ T_ = int(T)
241
+ if beta is None:
242
+ beta = alpha
243
+
244
+ w_ = owa_cvar(T_, alpha) - owa_cvar(T_, beta)[::-1]
245
+
246
+ return w_
247
+
248
+
249
+ def owa_wcvrg(T, alphas, weights_a, betas=None, weights_b=None):
250
+ r"""
251
+ Calculate the OWA weights to calculate the WCVaR range of a returns series
252
+ as shown in :cite:`d-Cajas3`.
253
+
254
+ Parameters
255
+ ----------
256
+ T : int
257
+ Number of observations of the returns series.
258
+ alphas : list
259
+ List of significance levels of each CVaR of losses model.
260
+ weights_a : list
261
+ List of weights of each CVaR of losses model.
262
+ betas : list, optional
263
+ List of significance levels of each CVaR of gains model. If None it duplicates alpha.
264
+ The default is None.
265
+ weights_b : list, optional
266
+ List of weights of each CVaR of gains model. If None it duplicates weights_a.
267
+ The default is None.
268
+
269
+ Returns
270
+ -------
271
+ value : 1d-array
272
+ A OWA weights vector of size Tx1.
273
+ """
274
+
275
+ T_ = int(T)
276
+ if betas is None or weights_b is None:
277
+ betas = alphas
278
+ weights_b = weights_a
279
+
280
+ w_ = owa_wcvar(T_, alphas, weights_a) - owa_wcvar(T_, betas, weights_b)[::-1]
281
+
282
+ return w_
283
+
284
+
285
+ def owa_tgrg(T, alpha=0.05, a_sim=100, beta=None, b_sim=None):
286
+ r"""
287
+ Calculate the OWA weights to calculate the Tail Gini range of a returns
288
+ series as shown in :cite:`d-Cajas3`.
289
+
290
+ Parameters
291
+ ----------
292
+ T : int
293
+ Number of observations of the returns series.
294
+ alpha : float, optional
295
+ Significance level of Tail Gini of losses. The default is 0.05.
296
+ a_sim : float, optional
297
+ Number of CVaRs used to approximate Tail Gini of losses. The default is 100.
298
+ beta : float, optional
299
+ Significance level of Tail Gini of gains. If None it duplicates alpha value.
300
+ The default is None.
301
+ b_sim : float, optional
302
+ Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value.
303
+ The default is None.
304
+
305
+ Returns
306
+ -------
307
+ value : 1d-array
308
+ A OWA weights vector of size Tx1.
309
+ """
310
+
311
+ if beta is None:
312
+ beta = alpha
313
+ if b_sim is None:
314
+ b_sim = a_sim
315
+
316
+ T_ = int(T)
317
+ a_sim_ = int(a_sim)
318
+ b_sim_ = int(b_sim)
319
+
320
+ w_ = owa_tg(T_, alpha, a_sim_) - owa_tg(T_, beta, b_sim_)[::-1]
321
+
322
+ return w_
323
+
324
+
325
+ def owa_l_moment_crm(T, k=4, method="MSD", g=0.5, max_phi=0.5, solver="CLARABEL"):
326
+ r"""
327
+ Calculate the OWA weights to calculate a convex risk measure that considers
328
+ higher linear moments or L-moments as shown in :cite:`d-Cajas6`.
329
+
330
+ Parameters
331
+ ----------
332
+ T : int
333
+ Number of observations of the returns series.
334
+ k : int
335
+ Order of the l-moment. Must be an integer higher or equal than 2.
336
+ method : str, optional
337
+ Method to calculate the weights used to combine the l-moments with order higher than 2.
338
+ The default value is 'MSD'. Possible values are:
339
+
340
+ - 'CRRA': Normalized Constant Relative Risk Aversion coefficients.
341
+ - 'ME': Maximum Entropy.
342
+ - 'MSS': Minimum Sum Squares.
343
+ - 'MSD': Minimum Square Distance.
344
+
345
+ g : float, optional
346
+ Risk aversion coefficient of CRRA utility function. The default is 0.5.
347
+ max_phi : float, optional
348
+ Maximum weight constraint of L-moments.
349
+ The default is 0.5.
350
+ solver: str, optional
351
+ Solver available for CVXPY. Used to calculate 'ME', 'MSS' and 'MSD' weights.
352
+ The default value is 'CLARABEL'.
353
+
354
+ Returns
355
+ -------
356
+ value : 1d-array
357
+ A OWA weights vector of size Tx1.
358
+ """
359
+
360
+ if k < 2 or (not isinstance(k, int)):
361
+ raise ValueError("k must be an integer higher equal than 2")
362
+ if method not in ["CRRA", "ME", "MSS", "MSD"]:
363
+ raise ValueError("Available methods are 'CRRA', 'ME', 'MSS' and 'MSD'")
364
+ if g >= 1 or g <= 0:
365
+ raise ValueError("The risk aversion coefficient mus be between 0 and 1")
366
+ if max_phi >= 1 or max_phi <= 0:
367
+ raise ValueError(
368
+ "The constraint on maximum weight of L-moments must be between 0 and 1"
369
+ )
370
+
371
+ T_ = int(T)
372
+ ws = np.empty((T_, 0))
373
+ for i in range(2, k + 1):
374
+ w_i = (-1) ** i * owa_l_moment(T_, i)
375
+ ws = np.concatenate([ws, w_i], axis=1)
376
+
377
+ if method == "CRRA":
378
+ phis = []
379
+ e = 1
380
+ for i in range(1, k):
381
+ e *= g + i - 1
382
+ phis.append(e / math.factorial(i + 1))
383
+ phis = np.array(phis)
384
+ phis = phis / np.sum(phis)
385
+ phis = phis.reshape(-1, 1)
386
+ a = ws @ phis
387
+
388
+ w = np.zeros_like(a)
389
+ w[0] = a[0]
390
+ for i in range(1, len(a)):
391
+ w[i, 0] = np.max(a[: i + 1, 0])
392
+
393
+ else:
394
+ theta = cp.Variable((T_, 1))
395
+ n = ws.shape[1]
396
+ phi = cp.Variable((n, 1))
397
+
398
+ constraints = [
399
+ cp.sum(phi) == 1,
400
+ theta == ws @ phi,
401
+ phi <= max_phi,
402
+ phi >= 0,
403
+ phi[1:] <= phi[:-1],
404
+ theta[1:] >= theta[:-1],
405
+ ]
406
+
407
+ if method == "ME":
408
+ theta_ = cp.Variable((T_, 1))
409
+ obj = cp.sum(cp.entr(theta_)) * 1000
410
+ constraints += [
411
+ theta_ >= theta,
412
+ theta_ >= -theta,
413
+ ]
414
+ objective = cp.Maximize(obj)
415
+ elif method == "MSS":
416
+ obj = cp.pnorm(theta, p=2) * 1000
417
+ objective = cp.Minimize(obj)
418
+ elif method == "MSD":
419
+ obj = cp.pnorm(theta[1:] - theta[:-1], p=2) * 1000
420
+ objective = cp.Minimize(obj)
421
+
422
+ problem = cp.Problem(objective, constraints)
423
+ if solver is not None:
424
+ problem.solve(solver=solver)
425
+ else:
426
+ problem.solve()
427
+
428
+ phis = phi.value
429
+ phis = phis / np.sum(phis)
430
+ phis = phis.reshape(-1, 1)
431
+ w = ws @ phis
432
+
433
+ return w