diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/notebooks/Asset allocation.ipynb | 222 | ||||
| -rw-r--r-- | python/notebooks/Portfolio analysis.ipynb | 101 | ||||
| -rw-r--r-- | python/notebooks/tranches numbers.ipynb | 323 |
3 files changed, 646 insertions, 0 deletions
diff --git a/python/notebooks/Asset allocation.ipynb b/python/notebooks/Asset allocation.ipynb new file mode 100644 index 00000000..ac122400 --- /dev/null +++ b/python/notebooks/Asset allocation.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import interact, interactive, fixed, FloatSlider\n", + "from IPython.display import display\n", + "import ipywidgets as widgets\n", + "import numpy as np\n", + "import math\n", + "import cvxpy\n", + "from inspect import signature\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We model the assets in a 1-factor model setting. Namely conditionally on the factor $F$, the assets\n", + "are independent, and $r_i$, the return of asset $i$ is given by:\n", + "$$r_i = \\mu_i + \\delta_i(F-\\bar F) + \\sigma_i \\varepsilon_i$$\n", + "with $\\forall i,\\,\\textrm{Cov}(F, \\varepsilon_i)=0$, and $\\varepsilon\\sim\\mathcal{N}(0, \\mathbf{1})$ \n", + "\n", + "We can show that:\n", + "$$\\textrm{Corr}(r_i, F)=\\rho_i = \\frac{\\delta_i \\sqrt{\\textrm{Var}(F)}}{\\sqrt{\\textrm{Var}(r_i)}}$$\n", + "and with $\\textrm{Var(r_i)}=\\delta_i^2\\textrm{Var}(F) + \\sigma_i^2$, we get:\n", + "$$\\rho_i = \\frac{1}{\\sqrt{1+\\frac{\\sigma_i^2}{\\delta_i^2\\textrm{Var(F)}}}}$$\n", + "$$\\forall i\\neq j,\\;\\textrm{Cov}(r_i, r_j)=\\delta_i\\delta_j\\textrm{Var}(F)$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def cor2cov(Rho, vol):\n", + " return np.diag(vol) @ Rho @ np.diag(vol)\n", + "\n", + "def rho(sigma, delta, volF):\n", + " \"\"\" computes the correlation between the asset and the factor \"\"\"\n", + " return 1/math.sqrt(1+sigma**2/(delta**2*volF**2))\n", + "\n", + "def resid_vol(rho, delta, volF):\n", + " \"\"\" computes the residual of the asset \"\"\"\n", + " return math.sqrt(delta**2*volF**2*(1/rho**2-1))\n", + "\n", + "def var(rho, delta, volF):\n", + " \"\"\" computes the variance of the asset \"\"\"\n", + " return delta**2*volF**2+resid_vol(rho, delta, volF)**2\n", + "\n", + "volHY = 0.07\n", + "## Edwin's parameters\n", + "rho = {'CLO': 0.6,\n", + " 'CSO': 0.5,\n", + " 'Subprime': 0.3}\n", + "delta = {'CLO': 0.4,\n", + " 'CSO': 0.2,\n", + " 'Subprime': 0.6}\n", + "mu = np.array([0.01, 0.075, 0.065, 0.25])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "plt.style.use('ggplot')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def compute_allocation(rho_clo = 0.9, rho_cso=0.6, rho_subprime=0.2,\n", + " delta_clo=1.2, delta_cso=0.4, delta_subprime=0.8,\n", + " mu_hy=0.02, mu_clo=0.07, mu_cso=0.08, mu_subprime=0.25):\n", + " rho = {'CLO': rho_clo,\n", + " 'CSO': rho_cso,\n", + " 'Subprime': rho_subprime}\n", + " delta = {'CLO': delta_clo,\n", + " 'CSO': delta_cso,\n", + " 'Subprime': delta_subprime}\n", + " assets = ['CLO', 'CSO', 'Subprime']\n", + " mu = np.array([mu_hy, mu_clo, mu_cso, mu_subprime])\n", + " u = volHY * np.array([delta[a] for a in assets])\n", + " Sigma = np.outer(u, u) + np.diag([resid_vol(rho[a], delta[a], volHY)**2\n", + " for a in assets])\n", + " v = volHY**2 * np.array([1] + [delta[a] for a in assets])\n", + " Sigma = np.vstack((v, np.c_[v[1:], Sigma]))\n", + "\n", + " sharpe = mu/np.sqrt(np.diag(Sigma))\n", + "\n", + " gamma = cvxpy.Parameter(sign='positive')\n", + " w = cvxpy.Variable(4)\n", + " ret = mu.T*w\n", + " risk = cvxpy.quad_form(w, Sigma)\n", + " prob = cvxpy.Problem(cvxpy.Maximize(ret-gamma*risk),\n", + " [cvxpy.sum_entries(w[1:]) - 0.1*w[0] == 1,\n", + " w[1:] >= 0,\n", + " w[0] <= 0])\n", + "\n", + " gamma_x = np.linspace(0, 20, 500)\n", + " W = np.empty((4, gamma_x.size))\n", + " for i, val in enumerate(gamma_x):\n", + " gamma.value = val\n", + " prob.solve()\n", + " W[:,i] = np.asarray(w.value).squeeze()\n", + " fund_return = mu@W\n", + " fund_vol = np.array([math.sqrt(W[:,i]@Sigma@W[:,i]) for i in range(gamma_x.size)])\n", + " return (W, fund_return, fund_vol)\n", + "\n", + "def plot_allocation(plot_vol=True, **kwargs):\n", + " W, fund_return, fund_vol = compute_allocation(**kwargs)\n", + " gamma_x = np.linspace(0, 20, 500)\n", + " fig, ax1 = plt.subplots()\n", + " ax1.stackplot(gamma_x, W[1:,], labels=['CLO', 'CSO', 'Subprime'])\n", + " ax1.set_xlabel('risk factor')\n", + " ax1.set_ylabel('portfolio weights')\n", + " ax1.legend()\n", + " # ax1.text(0.3, 0.82, 'RMBS')\n", + " # ax1.text(0.5, 0.45, 'CSO')\n", + " # ax1.text(0.5, 0.15, 'CLO')\n", + " ax1.set_ylim([0, 1])\n", + " ax2 = ax1.twinx()\n", + " if plot_vol:\n", + " ax2.plot(gamma_x, fund_vol, lw=1, color=\"grey\")\n", + " ax2.set_ylabel('fund volatility')\n", + " else:\n", + " ax2.plot(gamma_x, fund_return, lw=1, color=\"grey\")\n", + " ax2.set_ylabel('fund return')\n", + " plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def desc(greek, asset):\n", + " \"\"\"returns the latex label\"\"\"\n", + " return r'${0}_{{\\textrm{{{1}}}}}$'.format('\\\\'+greek, asset)\n", + "\n", + "desc_mapping = {}\n", + "assets = ['CLO', 'CSO', 'Subprime']\n", + "for greek in ['rho', 'delta', 'mu']:\n", + " if greek == 'mu':\n", + " assets.append('HY')\n", + " desc_mapping.update({desc(greek, a): '{0}_{1}'.format(greek, a.lower()) for a in assets})\n", + " \n", + "w = interactive(plot_allocation, rho_clo=FloatSlider(description=desc('rho', 'CLO'), min=0., max=1., step=0.1, value=0.9),\n", + " rho_cso=FloatSlider(description=desc('rho', 'CSO'), min=0., max=1., step=0.1, value=0.6),\n", + " rho_subprime=FloatSlider(description=desc('rho', 'Subprime'), min=0., max=1., step=0.1, value=0.2),\n", + " delta_clo=FloatSlider(description=desc('delta', 'CLO'), min=0., max=2., step=0.1, value=1.2),\n", + " delta_cso=FloatSlider(description=desc('delta', 'CSO'), min=0., max=2., step=0.1, value=0.4),\n", + " delta_subprime=FloatSlider(description=desc('delta', 'Subprime'), min=0., max=2., step=0.1, value=0.8),\n", + " mu_hy = FloatSlider(description=desc('mu', 'HY'), min=0., max=.1, step=0.01, value=0.02),\n", + " mu_clo = FloatSlider(description=desc('mu', 'CLO'), min=0., max=.12, step=0.01, value=0.07),\n", + " mu_cso = FloatSlider(description=desc('mu', 'CSO'), min=0., max=.12, step=0.01, value=0.08),\n", + " mu_subprime = FloatSlider(description=desc('mu', 'Subprime'), min=0.1, max=.35, step=0.01, value=0.25))\n", + "\n", + "## black magic to get back to the function order\n", + "s = signature(compute_allocation)\n", + "param_ordering = {p:i for i, p in enumerate(s.parameters)}\n", + "param_ordering[None] = -1\n", + "w_ordered = sorted([c for c in w.children], key=lambda x: param_ordering[desc_mapping.get(x.description)])\n", + "display(*w_ordered)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "args = w.kwargs.copy()\n", + "args.pop('plot_vol')\n", + "W, fund_return, fund_vol = compute_allocation(**args)\n", + "import pandas as pd\n", + "df = pd.DataFrame(np.c_[W.T, fund_return, fund_vol], columns=['HY', 'CLO', 'CSO', 'Subprime', 'return', 'vol'])\n", + "df.to_csv('weights.csv', index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/python/notebooks/Portfolio analysis.ipynb b/python/notebooks/Portfolio analysis.ipynb new file mode 100644 index 00000000..ce0395b3 --- /dev/null +++ b/python/notebooks/Portfolio analysis.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cd ~/projects/code/python" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from analytics import Portfolio, BlackSwaption, Index\n", + "import datetime\n", + "option_delta = Index.from_tradeid(874)\n", + "option1 = BlackSwaption.from_tradeid(7, option_delta)\n", + "option2 = BlackSwaption.from_tradeid(8, option_delta)\n", + "portf = Portfolio([option_delta, option1, option2])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "option1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "option2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "portf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "portf.trade_date = datetime.date.today()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "portf.mark()\n", + "portf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/python/notebooks/tranches numbers.ipynb b/python/notebooks/tranches numbers.ipynb new file mode 100644 index 00000000..71cb44f8 --- /dev/null +++ b/python/notebooks/tranches numbers.ipynb @@ -0,0 +1,323 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib\n", + "import seaborn\n", + "seaborn.mpl.rcParams['figure.figsize'] = (12.0, 8.0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from ipywidgets import interact\n", + "from collections import OrderedDict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "SQL_CON = \"postgresql://serenitas_user@debian/serenitasdb\"\n", + "runs = (pd.\n", + " read_sql(\"SELECT DISTINCT index, series, tenor from risk_numbers ORDER BY index, series\", SQL_CON).\n", + " itertuples(index=False, name='run'))\n", + "runs = OrderedDict([(\"%s %s %s\" % (r.index, r.series, r.tenor), (r.index, r.series, r.tenor)) for r in runs])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_sql(\"SELECT * FROM risk_numbers\", SQL_CON, index_col=['date', 'index', 'series', 'tenor'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "risk_numbers = ['skew', 'Dealer Deltas', 'Model Deltas', 'Forward Deltas', 'gammas', 'durations', 'thetas']\n", + "@interact(index=runs, what=risk_numbers)\n", + "def corrplot(index, what):\n", + " selection = df.xs(index, level=[1,2,3], drop_level=True)\n", + " cols = selection.attach.iloc[0]\n", + " cols = [\"{}-{}\".format(a,d) for a, d in zip(cols[:-2], cols[1:-1])]\n", + " selection = selection[what].apply(pd.Series)\n", + " selection.drop(selection.columns[-1], axis=1, inplace=True)\n", + " selection.columns = cols\n", + " selection.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_tranche_quotes(index, series, tenor):\n", + " df = pd.read_sql(\"SELECT * FROM tranche_quotes WHERE index=%s and series=%s and tenor =%s\",\n", + " SQL_CON, params=(index, series, tenor), index_col='quotedate')\n", + " df.sort_index(inplace=True)\n", + " return df\n", + "def get_index_quotes(index, series, tenor):\n", + " df = pd.read_sql(\"SELECT * FROM index_quotes WHERE index=%s and series=%s and tenor=%s\",\n", + " SQL_CON, params = (index, series, tenor), index_col=['date'], parse_dates=['date'])\n", + " df.sort_index(inplace=True)\n", + " return df\n", + "ig25_tranches = get_tranche_quotes('IG', 25, '5yr')\n", + "ig25 = get_index_quotes('IG', 25, '5yr')\n", + "ig25_tranches = ig25_tranches[['attach', 'trancheupfrontmid','indexrefspread','tranchedelta']]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ig25_tranches = ig25_tranches.groupby([pd.TimeGrouper('D', level=0), 'attach']).last().dropna()\n", + "ig25_tranches = ig25_tranches.reset_index('attach', drop=False)\n", + "ig25 = ig25[['closeprice','duration','closespread']]\n", + "ig25_data = ig25_tranches.join(ig25)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ig25_data = ig25_data.assign(tranche_adjusted =\n", + " lambda x: x.trancheupfrontmid - \\\n", + " x.tranchedelta * (x.indexrefspread-x.closespread) * x.duration/100)\n", + "ig25_data = ig25_data.set_index('attach', append=True)\n", + "ig25_data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#compute the accrued\n", + "ig25_data['accrued'] = ig25_data.groupby(level='attach')['tranche_adjusted'].transform(lambda x: x.index.levels[0].to_series().diff().astype('timedelta64[D]')*1/360)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#compute the daily pnl\n", + "ig25_data['index_pnl'] = (ig25_data.groupby(level='attach')['closeprice'].\n", + " apply(lambda x:x.diff()))\n", + "ig25_data['tranche_pnl'] = (ig25_data.groupby(level='attach')['tranche_adjusted'].\n", + " apply(lambda x:-x.diff()))\n", + "for col in ['index_pnl', 'tranche_pnl']:\n", + " ig25_data[col] += ig25_data.accrued" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ig25_data.xs(3, level='attach').plot(x='index_pnl', y='tranche_pnl', kind='scatter')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import statsmodels.api as sm\n", + "import statsmodels.formula.api as smf\n", + "import numpy as np\n", + "model = smf.ols('tranche_pnl~0+index_pnl', data=ig25_data.xs(15, level='attach'))\n", + "results = model.fit()\n", + "results.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def rolling_ols(df, formula, window=20):\n", + " r = []\n", + " for i in range(len(df)-window):\n", + " model = smf.ols(formula, data=df.iloc[i:(20+i),])\n", + " results = model.fit()\n", + " r.append(results.params)\n", + " r = pd.concat(r, axis=1).T\n", + " return r.set_index(df.index[20:])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "rols = rolling_ols(ig25_data, 'tranche_pnl~0+index_pnl+np.square(index_pnl)')\n", + "rols.columns = ['delta','gamma']\n", + "test = rols.join(ig25_data)[['delta','tranchedelta']]\n", + "test.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ig25_data.closespread.apply(np.log).diff().std()*np.sqrt(250)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ig25 = get_index_quotes('IG', 25, '5yr')\n", + "ig25['closespread'].rolling(20).apply(lambda x: np.std(np.diff(np.log(x)))*np.sqrt(250)).plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ig25 = get_index_quotes('IG', 25, '5yr')\n", + "returns = ig25.closespread.pct_change().dropna()\n", + "returns.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from arch import arch_model\n", + "am = arch_model(returns, mean='ARX', lags=1, vol='GARCH', o=1)\n", + "res = am.fit(update_freq=5)\n", + "res.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "res.plot(annualize='D')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "annualized_vol = res.conditional_volatility * math.sqrt(252)\n", + "annualized_vol.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "returns[returns.abs()>0.04].groupby(pd.TimeGrouper('M')).count()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "returns['2016-03']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ig25.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ig25.index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.read_sql?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} |
