goal-based-allocation 0.1.1__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Artur Sepp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,434 @@
1
+ Metadata-Version: 2.4
2
+ Name: goal-based-allocation
3
+ Version: 0.1.1
4
+ Summary: Dynamic Mean-Variance Portfolio Allocation under Regime-Switching Jump-Diffusions
5
+ Author: Artur Sepp
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ArturSepp/GoalBasedAllocation
8
+ Project-URL: Repository, https://github.com/ArturSepp/GoalBasedAllocation
9
+ Project-URL: Issues, https://github.com/ArturSepp/GoalBasedAllocation/issues
10
+ Keywords: portfolio-optimization,mean-variance,regime-switching,jump-diffusion,goal-based-investing,laplace-transform,quantitative-finance
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Office/Business :: Financial :: Investment
20
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy>=1.24
25
+ Requires-Dist: scipy>=1.10
26
+ Requires-Dist: matplotlib>=3.7
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest; extra == "dev"
29
+ Requires-Dist: black; extra == "dev"
30
+ Requires-Dist: ruff; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # GoalBasedAllocation
34
+
35
+ <p align="center">
36
+ <em>Dynamic Mean-Variance Portfolio Allocation under Regime-Switching Jump-Diffusions</em>
37
+ </p>
38
+
39
+ <p align="center">
40
+ <a href="https://github.com/ArturSepp/GoalBasedAllocation/blob/main/LICENSE">
41
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT">
42
+ </a>
43
+ <a href="https://pypi.org/project/goal-based-allocation/">
44
+ <img src="https://img.shields.io/pypi/v/goal-based-allocation.svg" alt="PyPI">
45
+ </a>
46
+ <a href="https://pypi.org/project/goal-based-allocation/">
47
+ <img src="https://img.shields.io/pypi/pyversions/goal-based-allocation.svg" alt="Python">
48
+ </a>
49
+ <a href="https://pepy.tech/project/goal-based-allocation">
50
+ <img src="https://pepy.tech/badge/goal-based-allocation" alt="Downloads">
51
+ </a>
52
+ </p>
53
+
54
+ Companion code to:
55
+
56
+ > **Sepp, A. (2026). Dynamic Mean-Variance Portfolio Allocation under Regime-Switching
57
+ > Jump-Diffusions with Absorbing Barriers.**
58
+ > [SSRN: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=XXXXXXX](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=XXXXXXX)
59
+
60
+ ## Overview
61
+
62
+ This package provides a **fully analytical Laplace-transform framework** for dynamic
63
+ mean-variance (MV) portfolio allocation under a two-state regime-switching model
64
+ with exponential jumps at regime transitions and an absorbing wealth floor.
65
+
66
+ The MV-optimal strategy takes the form
67
+
68
+ $$\omega^*(t) = |\omega^*_a| \cdot \left(\frac{\Pi^*(t)}{\Pi_t} - 1\right)$$
69
+
70
+ where $\Pi^*(t)$ is the target wealth trajectory derived from the Riccati ODE system,
71
+ and $|\omega^*_a|$ is the regime-dependent allocation intensity. This produces an
72
+ **endogenous de-risking glide path**: early in the horizon the funding gap is large
73
+ and allocation is aggressive; as the portfolio approaches the target, allocation
74
+ moderates automatically.
75
+
76
+ The terminal wealth density has three analytically tractable components:
77
+
78
+ 1. **Survived density** — wealth above the floor at horizon, computed via
79
+ Laplace inversion of the bounded transition density
80
+ 2. **Floor atom** — probability mass from diffusion paths hitting the absorbing barrier
81
+ 3. **Jump-overshoot density** — wealth below the floor from crash jumps that gap
82
+ through the barrier, computed via the overshoot distribution
83
+
84
+ All three components are computed semi-analytically using the Abate-Whitt (1995)
85
+ Euler acceleration method for numerical Laplace inversion. No Monte Carlo
86
+ simulation is needed for pricing; MC is used only for validation.
87
+
88
+ ### Key features
89
+
90
+ - Analytical survival probability, conditional moments, and tilted survival via Laplace transforms
91
+ - Riccati ODE system for the MV-optimal policy with regime-dependent coefficients
92
+ - Full terminal wealth density decomposition (survived + floor atom + overshoot)
93
+ - Exact buy-and-hold moments under regime-switching via 2×2 matrix exponential
94
+ - Portfolio mandate construction with deterministic quadrature for jump aggregation
95
+ - Expected allocation glide paths with variance bands
96
+ - Investment opportunity set construction for client-facing portfolio advice
97
+ - Monte Carlo simulator for validation of all analytical results
98
+ - Integration tests and all paper figures reproducible from a single command
99
+
100
+ ## Model
101
+
102
+ The portfolio wealth $\Pi_t$ follows a regime-switching jump-diffusion
103
+ with two states: **growth** ($i=1$) and **stress** ($i=2$).
104
+
105
+ ### Notation (paper → code)
106
+
107
+ | Symbol | Name | Code variable |
108
+ |---|---|---|
109
+ | $\bar{\mu}^{[i]}$ | Total expected return (CMA observable) | `mu_growth`, `mu_stress` |
110
+ | $\mu^{[i]} = \bar{\mu}^{[i]} - \lambda \alpha^{[i]}$ | Diffusion drift | `mu_bar` |
111
+ | $r_h = \max(r, c)$ | Hurdle rate | `r_h` |
112
+ | $r_c = r_h - c = \max(0, r-c)$ | Net floor growth rate | `r_c` |
113
+ | $\omega^*_a = -\tilde{a}^{[i]}/\Sigma^{[i]}$ | Risky allocation coefficient | `w_a` |
114
+
115
+ ### Process specification
116
+
117
+ | Component | Description |
118
+ |---|---|
119
+ | Diffusion | Regime-dependent drift $\mu^{[i]}$ and volatility $\sigma^{[i]}$ |
120
+ | Jumps | Exponential crash at growth→stress ($\eta^{[1]}$), exponential recovery at stress→growth ($\eta^{[2]}$) |
121
+ | Regime switching | Poisson rates $\lambda^{[12]}$ (crash) and $\lambda^{[21]}$ (recovery) |
122
+ | MV-optimal control | $\omega^*(t) = \|\omega_a^*\| \cdot (\Pi^*(t)/\Pi_t - 1)$, from Riccati system |
123
+ | Absorbing floor | Wealth stopped at $L_t = L_0 \cdot e^{r_c t}$, converting to cash |
124
+
125
+ ### Asset class parameters
126
+
127
+ | | Bonds | Equity | Private Equity |
128
+ |---|---|---|---|
129
+ | $\bar{\mu}^{[1]}$ / $\bar{\mu}^{[2]}$ | 2.5% / 2.0% | 4.5% / 0.0% | 7.0% / 0.0% |
130
+ | $\sigma^{[1]}$ / $\sigma^{[2]}$ | 6% / 9% | 15% / 22.5% | 20% / 30% |
131
+ | Crash loss / Recovery gain | 8% / 5% | 25% / 15% | 30% / 20% |
132
+
133
+ Shared: $\lambda^{[12]} = 0.1$, $\lambda^{[21]} = 1.0$, $r = 2\%$, $T = 10$y, $\Pi_0 = 100$.
134
+
135
+ ### Mandate specification
136
+
137
+ Mandates are defined by bond weight $w$ with equity-PE split $g = 2/3$:
138
+ $w_{\text{eq}} = g(1-w)$, $w_{\text{pe}} = (1-g)(1-w)$.
139
+
140
+ | Mandate | Weights (Bd/Eq/PE) | $\sigma_g$ / $\sigma_s$ | $\bar{\mu}_g$ / $\bar{\mu}_s$ | $\eta^{[1]}$ |
141
+ |---|---|---|---|---|
142
+ | Income | 100/0/0 | 6.0 / 9.0% | 2.50 / 2.00% | 0.087 |
143
+ | Conservative | 65/23/12 | 7.7 / 11.6% | 3.49 / 1.30% | 0.161 |
144
+ | Balanced | 35/43/22 | 11.1 / 16.7% | 4.34 / 0.70% | 0.233 |
145
+ | Growth | 0/67/33 | 15.8 / 23.8% | 5.33 / 0.00% | 0.334 |
146
+
147
+ ### MV-optimal results ($c=0\%$, $\omega^*(0)=1$, $q_{dd}=2$)
148
+
149
+ | Mandate | $r_{\text{impl}}$ | $\Pi^*_T$ | $\mathbb{E}[\Pi_T]$ | Std | Survival | $r^{\text{BH}}_{\text{impl}}$ | $\text{Std}^{\text{BH}}$ |
150
+ |---|---|---|---|---|---|---|---|
151
+ | Income | 2.39% | 240 | 127 | 24.7 | 85.5% | 2.46% | 30.4 |
152
+ | Conservative | 2.92% | 200 | 134 | 33.7 | 83.2% | 3.33% | 48.3 |
153
+ | Balanced | 3.30% | 221 | 139 | 46.1 | 78.7% | 4.10% | 74.8 |
154
+ | Growth | 3.68% | 254 | 144 | 61.9 | 73.8% | 5.01% | 117.6 |
155
+
156
+ Floor protection cost ranges from 6bp (income) to 117bp (growth). BH moments
157
+ are exact under the RS-JD model via 2×2 matrix exponential (Proposition B.7).
158
+
159
+ ## Installation
160
+
161
+ ```bash
162
+ # From PyPI
163
+ pip install goal-based-allocation
164
+
165
+ # Or directly from GitHub
166
+ pip install git+https://github.com/ArturSepp/GoalBasedAllocation.git
167
+
168
+ # Or clone and install in development mode
169
+ git clone https://github.com/ArturSepp/GoalBasedAllocation.git
170
+ cd GoalBasedAllocation
171
+ pip install -e .
172
+ ```
173
+
174
+ Requires Python >= 3.10 with NumPy >= 1.24, SciPy >= 1.10, and Matplotlib >= 3.7.
175
+
176
+ ## Package Structure
177
+
178
+ ```
179
+ GoalBasedAllocation/
180
+ ├── goal_based_allocation/ # Core library
181
+ │ ├── regime_switch_paper.py # Laplace framework: density, survival, overshoot, BH moments
182
+ │ ├── riccati_solver.py # Riccati ODE system + MC simulator
183
+ │ ├── laplace_inversion.py # Abate-Whitt & Stehfest numerical inversion
184
+ │ ├── client_solver.py # Effective asset construction, portfolio eta quadrature
185
+ │ ├── mandate_utils.py # Portfolio mandate construction from assets
186
+ │ └── opportunity_set.py # Investment opportunity set & advisor framework
187
+ ├── paper_figures/ # Paper reproduction
188
+ │ └── generate_paper_figures.py # All 10 figures + integration tests
189
+ ├── figures/ # Pre-generated figures
190
+ ├── pyproject.toml
191
+ ├── LICENSE
192
+ └── README.md
193
+ ```
194
+
195
+ ### Module reference
196
+
197
+ | Module | Description | Key functions |
198
+ |---|---|---|
199
+ | `regime_switch_paper` | Core Laplace framework for the RS-JD gap process | `compute_density`, `compute_survival`, `compute_tilted_survival`, `compute_overshoot_density`, `bh_moments_rsjd` |
200
+ | `riccati_solver` | Riccati ODE for MV-optimal policy, gap process, MC validation | `find_ell`, `gap_process_asset`, `simulate_mv_optimal` |
201
+ | `laplace_inversion` | Numerical Laplace inversion algorithms | `laplace_invert_abate_whitt`, `laplace_invert_stehfest` |
202
+ | `client_solver` | Effective single-asset from multi-asset portfolios | `build_effective_asset`, `portfolio_sigma_unc`, `portfolio_eta_quadrature` |
203
+ | `mandate_utils` | Named mandates (Income, Conservative, Balanced, Growth) | `mandate_effective_asset` |
204
+ | `opportunity_set` | Two-step advisor framework: opportunity set + client profile | `AdvisorSpec`, `compute_opportunity_point`, `build_opportunity_set` |
205
+
206
+ ## Quick Start
207
+
208
+ ### 1. Compute survival probability for a single asset
209
+
210
+ ```python
211
+ from goal_based_allocation import create_paper_assets, compute_survival
212
+
213
+ assets = create_paper_assets()
214
+ eq = assets['equity']
215
+
216
+ for T in [1, 2, 5, 10]:
217
+ S = compute_survival(T, eq.x0, eq)
218
+ print(f"T={T:2d}y: survival={S:.4f}, stopping={1-S:.4f}")
219
+ ```
220
+
221
+ ### 2. Solve the MV-optimal allocation and compute the terminal wealth density
222
+
223
+ ```python
224
+ import numpy as np
225
+ from goal_based_allocation import (
226
+ create_paper_assets, compute_density, compute_survival,
227
+ compute_overshoot_density
228
+ )
229
+ from goal_based_allocation.riccati_solver import find_ell, gap_process_asset
230
+
231
+ assets = create_paper_assets()
232
+ eq = assets['equity']
233
+ T = 10.0
234
+
235
+ # Solve Riccati ODE for target return of 4%
236
+ ell, ric = find_ell(eq, T, target_return=0.04, r=0.02, c=0.02)
237
+ gap = gap_process_asset(ric)
238
+
239
+ # Terminal targets
240
+ PiT = ric.derived_at_tau(0)['Pi_star'][0] # target wealth at T
241
+ L_T = eq.pi_floor # floor at T (with r=c)
242
+ B_T = PiT - L_T # buffer
243
+
244
+ # Bounded gap density -> wealth density
245
+ x_grid = np.linspace(0.001, 4.0, 800)
246
+ d0, d1 = compute_density(T, x_grid, gap)
247
+ density_total = d0 + d1
248
+
249
+ # Survival and overshoot
250
+ S = compute_survival(T, gap.x0, gap)
251
+ d_ov = np.linspace(0.001, 8.0, 400)
252
+ f_ov = compute_overshoot_density(T, d_ov, gap)
253
+
254
+ print(f"target wealth = {PiT:.1f}, floor = {L_T:.1f}")
255
+ print(f"Survival = {S:.4f}")
256
+ print(f"Overshoot mass = {np.trapezoid(f_ov, d_ov):.4f}")
257
+ print(f"Floor atom = {1 - S - np.trapezoid(f_ov, d_ov):.4f}")
258
+ ```
259
+
260
+ ### 3. Compare MV-optimal vs buy-and-hold
261
+
262
+ ```python
263
+ from goal_based_allocation import (
264
+ build_effective_asset, bh_moments_rsjd, compute_survival
265
+ )
266
+ from goal_based_allocation.riccati_solver import find_ell, gap_process_asset
267
+
268
+ # Build balanced mandate (35% bonds, 43% equity, 22% PE)
269
+ eff = build_effective_asset(w_eq=0.43, w_pe=0.22, k=3.0)
270
+ T, PI0 = 10.0, 100.0
271
+
272
+ # MV-optimal survival (analytical via Laplace)
273
+ ell, ric = find_ell(eff, T, target_return=0.04, r=0.02, c=0.0)
274
+ gap = gap_process_asset(ric)
275
+ S = compute_survival(T, gap.x0, gap)
276
+ print(f"MV survival: {S:.3f}")
277
+
278
+ # BH moments (exact via matrix exponential)
279
+ bh = bh_moments_rsjd(T, PI0, eff, c=0.0)
280
+ print(f"BH: E={bh['E']:.0f}, Std={bh['Std']:.1f}, r_impl={bh['r_impl']*100:.2f}%")
281
+ ```
282
+
283
+ ### 4. Build an investment opportunity set
284
+
285
+ ```python
286
+ from goal_based_allocation import AdvisorSpec, build_opportunity_set
287
+
288
+ spec = AdvisorSpec(omega_0=1.0, c=0.0, q=2/3, q_dd=2.0)
289
+ opp = build_opportunity_set(spec)
290
+
291
+ for p in opp:
292
+ print(f"Bonds={p['w_bd']:4.0%} r_impl={p['r_impl']:5.2%} "
293
+ f"Surv={p['S']:5.1%} Median={p['q50']:6.0f}")
294
+ ```
295
+
296
+ ## Reproducing Paper Results
297
+
298
+ All figures and integration tests can be reproduced with a single command:
299
+
300
+ ```bash
301
+ # Run integration tests (9 assertions) + generate all 10 figures
302
+ python -m paper_figures.generate_paper_figures --test
303
+
304
+ # Generate all figures only
305
+ python -m paper_figures.generate_paper_figures
306
+
307
+ # Generate a single figure
308
+ python -m paper_figures.generate_paper_figures --figure 10
309
+
310
+ # Custom output directory
311
+ python -m paper_figures.generate_paper_figures --outdir my_figures/
312
+ ```
313
+
314
+ ### Integration tests
315
+
316
+ The `--test` flag runs 7 tests with 9 assertions covering:
317
+
318
+ | Test | Description | Tolerance |
319
+ |---|---|---|
320
+ | 1 | Unbounded density normalization (with and without jumps) | < 1e-4 |
321
+ | 2 | Barrier density vs analytical survival consistency | < 1e-4 |
322
+ | 3 | Survival probability monotonicity across horizons | -- |
323
+ | 4 | Three-asset survival comparison | -- |
324
+ | 5 | Riccati ODE initial conditions a(0) = [1, 1] | < 1e-6 |
325
+ | 6 | Gap-process survival: analytical vs MC (100K paths) | < 5 pp |
326
+ | 7 | Table 1 parameter validation (3 assets) | exact |
327
+
328
+ ### Figure catalog
329
+
330
+ | # | Description | File |
331
+ |---|---|---|
332
+ | 1 | Investment opportunity set, c=0% | `opportunity_set_c0.png` |
333
+ | 2 | Investment opportunity set, c=2.5% | `opportunity_set_c25.png` |
334
+ | 3 | Expected allocation glide paths, c=0% | `allocation_paths_c0.png` |
335
+ | 4 | Expected allocation glide paths, c=2.5% | `allocation_paths_c25.png` |
336
+ | 5 | Allocation ±1σ uncertainty bands | `risky_allocation_subplots_c0.png` |
337
+ | 6 | Path dynamics: survived vs stopped | `path_dynamics_balanced.png` |
338
+ | 7 | Three wealth distributions: Growth mandate | `floor_vs_lipton.png` |
339
+ | 8 | Three wealth distributions: Balanced mandate | `floor_vs_lipton_balanced.png` |
340
+ | 9 | MV-optimal vs buy-and-hold density overlay (4 mandates) | `mandate_density_overlay_c0.png` |
341
+ | 10 | Mandate comparison: analytical vs MC (3 mandates) | `mandate_comparison.png` |
342
+
343
+ ### Selected figures
344
+
345
+ <p align="center">
346
+ <img src="figures/mandate_density_overlay_c0.png" width="48%" />
347
+ <img src="figures/mandate_comparison.png" width="48%" />
348
+ </p>
349
+
350
+ <p align="center">
351
+ <em>Left:</em> MV-optimal vs buy-and-hold terminal wealth densities for four mandates.
352
+ <em>Right:</em> Analytical density (survived + overshoot) vs MC histograms.
353
+ </p>
354
+
355
+ <p align="center">
356
+ <img src="figures/path_dynamics_balanced.png" width="48%" />
357
+ <img src="figures/opportunity_set_c0.png" width="48%" />
358
+ </p>
359
+
360
+ <p align="center">
361
+ <em>Left:</em> Survived and stopped sample paths with MV-optimal allocation.
362
+ <em>Right:</em> Investment opportunity set with CDF quantiles.
363
+ </p>
364
+
365
+ ## Methodology
366
+
367
+ The analytical framework proceeds in three steps:
368
+
369
+ **Step 1: Riccati ODE system.** The pre-commitment MV problem reduces to a
370
+ system of coupled Riccati ODEs for the value function coefficients
371
+ in each regime. The solution yields the optimal allocation
372
+ intensity $|\omega^*_a|$ and the target wealth trajectory $\Pi^*(t)$.
373
+
374
+ **Step 2: Gap process.** Under the optimal policy, the log-cushion ratio
375
+ $X_t = \ln(B_t/Z_t)$ is a regime-switching jump-diffusion with an absorbing
376
+ barrier at zero. Its Laplace-domain transition density satisfies a degree-6
377
+ characteristic polynomial with three positive and three negative roots.
378
+
379
+ **Step 3: Laplace inversion.** The bounded transition density, survival probability,
380
+ tilted survival moments, and overshoot density are all expressed as Laplace transforms
381
+ and inverted numerically using the Abate-Whitt (1995) Euler acceleration algorithm.
382
+
383
+ **Portfolio aggregation.** Multi-asset mandates are reduced to a single effective
384
+ asset using portfolio volatility with full correlation structure, and portfolio
385
+ jump sizes via deterministic numerical integration (`portfolio_eta_quadrature`).
386
+ Buy-and-hold benchmark moments are computed exactly via the 2×2 matrix exponential
387
+ of Proposition B.7.
388
+
389
+ ## Related Packages
390
+
391
+ | Package | Description |
392
+ |---|---|
393
+ | [OptimalPortfolios](https://github.com/ArturSepp/OptimalPortfolios) | Optimal portfolio construction and backtesting |
394
+ | [StochVolModels](https://github.com/ArturSepp/StochVolModels) | Stochastic volatility models for options pricing |
395
+ | [QuantInvestStrats](https://github.com/ArturSepp/QuantInvestStrats) | Quantitative investment strategies and analytics |
396
+ | [BloombergFetch](https://github.com/ArturSepp/BloombergFetch) | Bloomberg data API wrapper |
397
+ | [VanillaOptionPricers](https://github.com/ArturSepp/VanillaOptionPricers) | Vanilla option pricing models |
398
+ | [factorlasso](https://github.com/ArturSepp/factorlasso) | Factor model estimation with LASSO |
399
+
400
+ ## Citation
401
+
402
+ If you use this package in your research, please cite:
403
+
404
+ ```bibtex
405
+ @article{Sepp2026GoalBased,
406
+ author = {Sepp, Artur},
407
+ title = {Dynamic Mean-Variance Portfolio Allocation under Regime-Switching
408
+ Jump-Diffusions with Absorbing Barriers},
409
+ year = {2026},
410
+ note = {Available at SSRN: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6534579}
411
+ }
412
+ ```
413
+
414
+ ## Key References
415
+
416
+ - Sepp, A., Ossa, I., and Kastenholz, M. (2026). Robust optimization of strategic and
417
+ tactical asset allocation for multi-asset portfolios. *Journal of Portfolio Management*, 52(4), 86-120.
418
+ - Sepp, A., Hansen, E., and Kastenholz, M. (2026). Capital market assumptions and strategic
419
+ asset allocation using multi-asset tradable factors. Under revision at the
420
+ *Journal of Portfolio Management*.
421
+ - Sepp, A. (2004). Analytical pricing of double-barrier options under a double-exponential
422
+ jump-diffusion process. *International Journal of Theoretical and Applied Finance*, 7(2), 151-175.
423
+ - Sepp, A. (2006). Extended CreditGrades model with stochastic volatility and jumps.
424
+ *Wilmott Magazine*, September, 50-62.
425
+ - Lipton, A. (2001). *Mathematical Methods for Foreign Exchange*. World Scientific.
426
+ - Lipton, A. (2001). Assets with jumps. *Risk*, 14(9), 149-153.
427
+ - Cont, R. and Tankov, P. (2009). Constant proportion portfolio insurance in the presence
428
+ of jumps in asset prices. *Mathematical Finance*, 19(3), 379-401.
429
+ - Abate, J. and Whitt, W. (1995). Numerical inversion of Laplace transforms of probability
430
+ distributions. *ORSA Journal on Computing*, 7(1), 36-43.
431
+
432
+ ## License
433
+
434
+ MIT — see [LICENSE](LICENSE) for details.