#!/usr/bin/env python # coding: utf-8 # # Derivatives framework example # This example is the accompanied code to https://ssrn.com/abstract=4217884. It illustrates how to use the relative market values v parameter in MeanCVaR and MeanVariance. # In[1]: import numpy as np import pandas as pd import seaborn as sns import fortitudo.tech as ft import matplotlib.pyplot as plt # In[2]: pnl = ft.load_pnl() np.round(ft.simulation_moments(pnl), 3) # In[3]: # Price some options put_90 = ft.put_option(1, 0.9, 0.16, 0, 1) put_95 = ft.put_option(1, 0.95, 0.155, 0, 1) put_atmf = ft.put_option(1, 1, 0.15, 0, 1) call_atmf = ft.call_option(1, 1, 0.15, 0, 1) call_105 = ft.call_option(1, 1.05, 0.145, 0, 1) call_110 = ft.call_option(1, 1.1, 0.14, 0, 1) # Compute relative P&L S, I = pnl.shape zeros_vec = np.zeros(S) dm_equity_price = 1 + pnl['DM Equity'].values put_90_pnl = np.maximum(zeros_vec, 0.9 - dm_equity_price) - put_90 put_95_pnl = np.maximum(zeros_vec, 0.95 - dm_equity_price) - put_95 put_atmf_pnl = np.maximum(zeros_vec, 1 - dm_equity_price) - put_atmf call_atmf_pnl = np.maximum(zeros_vec, dm_equity_price - 1) - call_atmf call_105_pnl = np.maximum(zeros_vec, dm_equity_price - 1.05) - call_105 call_110_pnl = np.maximum(zeros_vec, dm_equity_price - 1.1) - call_110 # In[4]: # Plot ATMF P&L vs underlying for illustration plt.scatter(pnl['DM Equity'], call_atmf_pnl, marker='.') plt.scatter(pnl['DM Equity'], put_atmf_pnl, marker='.') plt.title('ATMF options P&L') plt.legend(['ATMF Call', 'ATMF Put']) plt.ylabel('Option return') plt.xlabel('DM Equity return') plt.show() # In[5]: # Add option simulations to P&L pnl['Put 90'] = put_90_pnl pnl['Put 95'] = put_95_pnl pnl['Put ATMF'] = put_atmf_pnl pnl['Call ATMF'] = call_atmf_pnl pnl['Call 105'] = call_105_pnl pnl['Call 110'] = call_110_pnl stats_prior = ft.simulation_moments(pnl) np.round(stats_prior, 3) # In[6]: # Plot option P&L distributions pnl.iloc[:, -6:].plot(kind='density') plt.xlim([-0.1, 0.1]) plt.xlabel('Relative P&L') plt.show() # # Prior portfolio optimization # In[7]: # Optimization constraints v = np.hstack((np.ones(I), [put_90, put_95, put_atmf, call_atmf, call_105, call_110])) G = np.vstack((np.eye(len(v)), -np.eye(len(v)))) options_bounds = 0.5 * np.ones(6) h = np.hstack((0.25 * np.ones(I), options_bounds, np.zeros(I), options_bounds)) # In[8]: R = pnl.values alpha = 0.9 cvar_opt_prior = ft.MeanCVaR(R, G, h, v=v, alpha=alpha) mean_prior = stats_prior['Mean'].values cov_prior = ft.covariance_matrix(pnl).values var_opt_prior = ft.MeanVariance(mean_prior, cov_prior, G, h, v=v) port_cvar = cvar_opt_prior.efficient_portfolio(0.05) port_var = var_opt_prior.efficient_portfolio(0.05) prior_results = np.hstack((port_cvar, port_var)) portfolio_names = [f'{int(100 * alpha)}%-CVaR optimized', 'Variance optimized'] pd.DataFrame( np.round(100 * prior_results, 2), index=pnl.columns, columns=portfolio_names) # In[9]: print(f'Portfolio exposures: {np.round(np.sum(prior_results, axis=0), 2)}.') print(f'Portfolio market values: {v @ prior_results}.') # In[10]: # Plot portfolio P&L distributions pfs_pnl = pd.DataFrame(pnl.values @ prior_results, columns=portfolio_names) pfs_pnl.plot(kind='density') plt.title('Prior optimization') plt.xlim([-0.2, 0.5]) plt.xlabel('Portfolio return') plt.show() # In[11]: vol = ft.portfolio_vol(prior_results, R) var = ft.portfolio_var(prior_results, R, demean=False) cvar = ft.portfolio_cvar(prior_results, R, demean=False) risk_prior = np.vstack((vol, var, cvar)) display(np.round(pd.DataFrame( 100 * risk_prior, index=['Vol', 'VaR', 'CVaR'], columns=portfolio_names), 1)) # In[12]: np.round(np.min(pfs_pnl, axis=0), 2) # In[13]: np.round(np.max(pfs_pnl, axis=0), 2) # # Entropy Pooling views # In[14]: dm_eqt_pnl = pnl['DM Equity'].values[np.newaxis, :] dm_eqt_mean = mean_prior[4] dm_eqt_demean = dm_eqt_pnl - dm_eqt_mean p = np.ones((S, 1)) / S A_ep = np.vstack(( np.ones((1, S)), dm_eqt_pnl, dm_eqt_demean**2, (dm_eqt_demean / 0.2)**3, (dm_eqt_demean / 0.2)**4)) b_ep = np.array([[1.], [dm_eqt_mean], [0.2**2], [-0.1], [2.75]]) q = ft.entropy_pooling(p, A_ep, b_ep) # In[15]: stats_post = ft.simulation_moments(pnl, q) np.round(stats_post, 3) # In[16]: # Plot DM Equity P&L distribution sns.kdeplot(x=pnl['DM Equity']) sns.kdeplot(x=pnl['DM Equity'], weights=q[:, 0]) plt.title('DM Equity marginal distributions') plt.legend(['Prior', 'Posterior']) plt.xlabel('DM Equity return') plt.show() # # Posterior portfolio optimization # # In[17]: cvar_opt_post = ft.MeanCVaR(R, G, h, v=v, p=q, alpha=alpha) mean_post = stats_post['Mean'].values cov_post = ft.covariance_matrix(R, q).values var_opt_post = ft.MeanVariance(mean_post, cov_post, G, h, v=v) port_cvar_post = cvar_opt_post.efficient_portfolio(0.05) port_var_post = var_opt_post.efficient_portfolio(0.05) post_results = np.hstack((port_cvar_post, port_var_post)) # In[18]: pd.DataFrame( np.round(100 * post_results, 2), index=pnl.columns, columns=portfolio_names) # In[19]: print(f'Posterior portfolio exposures: {np.round(np.sum(post_results, axis=0), 2)}.') print(f'Posterior portfolio market values: {v @ post_results}.') # In[20]: pfs_pnl_post = pnl @ post_results pfs_pnl_post.columns = portfolio_names sns.kdeplot(x=pfs_pnl_post.values[:, 0], weights=q[:, 0]) sns.kdeplot(x=pfs_pnl_post.values[:, 1], weights=q[:, 0]) plt.title('Posterior optimization') plt.legend(portfolio_names) plt.xlabel('Portfolio return') plt.xlim([-0.2, 0.5]) plt.show() # In[21]: vol_post = ft.portfolio_vol(post_results, R, q) var_post = ft.portfolio_var(post_results, R, q, demean=False) cvar_post = ft.portfolio_cvar(post_results, R, q, demean=False) risk_post = np.vstack((vol_post, var_post, cvar_post)) display(np.round(pd.DataFrame( 100 * risk_post, index=['Vol', 'VaR', 'CVaR'], columns=portfolio_names), 1)) # In[22]: np.round(np.min(pfs_pnl_post, axis=0), 2) # In[23]: np.round(np.max(pfs_pnl_post, axis=0), 2) # # License # In[24]: # fortitudo.tech - Novel Investment Technologies. # Copyright (C) 2021-2023 Fortitudo Technologies. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see .