From 2fc1ab4cff2b182ae893e69f81c8e212822a622f Mon Sep 17 00:00:00 2001 From: PCoelho89 <44276159+PCoelho89@users.noreply.github.com> Date: Sat, 6 Mar 2021 19:19:51 -0300 Subject: [PATCH 1/6] Lista 4 --- dataapi/AWS/fixed_income_portfolio.ipynb | 298 ++++++++++++++++++++ dataapi/AWS/getb3derivatives.py | 5 +- finmath/brazilian_bonds/government_bonds.py | 86 ++++-- 3 files changed, 363 insertions(+), 26 deletions(-) create mode 100644 dataapi/AWS/fixed_income_portfolio.ipynb diff --git a/dataapi/AWS/fixed_income_portfolio.ipynb b/dataapi/AWS/fixed_income_portfolio.ipynb new file mode 100644 index 0000000..760f115 --- /dev/null +++ b/dataapi/AWS/fixed_income_portfolio.ipynb @@ -0,0 +1,298 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 148, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "from dataapi.AWS.dbutils import DBConnect\n", + "from dataapi.AWS.getb3derivatives import DI1\n", + "from finmath.brazilian_bonds.government_bonds import LTN, NTNF\n", + "\n", + "\n", + "class FixedIncomePortfolio(object):\n", + "\n", + " def __init__(self, bonds, ref_date, di1_connect, bond_quantities=None, bond_market_values=None,\n", + " adjust_for_convexity=False):\n", + "\n", + " self.bonds = bonds\n", + " self.ref_date = ref_date\n", + " self.di1_connect = di1_connect\n", + " self.adjust_for_convexity = adjust_for_convexity\n", + " self.bond_quantities = bond_quantities\n", + " self.bond_market_values = bond_market_values\n", + " self.portfolio_value = 0\n", + " if self.bond_quantities is None and self.bond_market_values is None:\n", + " msg = 'Please input the bond quantities or the bond market values.'\n", + " raise AttributeError(msg)\n", + " elif self.bond_quantities is not None:\n", + " if len(self.bonds) != len(self.bond_quantities):\n", + " msg = f\"Bonds list length ({len(self.bonds)}) don't match the bond quantities length \" \\\n", + " f\"({len(self.bond_quantities)}).\"\n", + " raise ValueError(msg)\n", + " else:\n", + " self.bond_market_values = list()\n", + " for bond, quantity in zip(self.bonds, self.bond_quantities):\n", + " self.portfolio_value += bond.price*quantity\n", + " self.bond_market_values.append(bond.price*quantity)\n", + " elif self.bond_market_values is not None:\n", + " if len(self.bonds) != len(self.bond_market_values):\n", + " msg = f\"Bonds list length ({len(self.bonds)}) don't match the bond market values length \" \\\n", + " f\"({len(self.bond_market_values)}).\"\n", + " raise ValueError(msg)\n", + " else:\n", + " self.bond_quantities = list()\n", + " for bond, market_value in zip(self.bonds, self.bond_market_values):\n", + " self.portfolio_value += market_value\n", + " self.bond_quantities.append(market_value/bond.price)\n", + "\n", + " self.rate = sum([\n", + " bond.rate * market_value / self.portfolio_value\n", + " for bond, market_value in zip(self.bonds, self.bond_market_values)\n", + " ])\n", + " self.duration = sum([\n", + " bond.mod_duration * market_value / self.portfolio_value\n", + " for bond, market_value in zip(self.bonds, self.bond_market_values)\n", + " ])\n", + "\n", + " self.convexity = sum([\n", + " bond.convexity * market_value / self.portfolio_value\n", + " for bond, market_value in zip(self.bonds, self.bond_market_values)\n", + " ])\n", + "\n", + " self.dv01 = sum([\n", + " bond.dv01 * market_value / self.portfolio_value\n", + " for bond, market_value in zip(self.bonds, self.bond_market_values)\n", + " ])\n", + "\n", + " def flat_rate_change(self, rate_change, is_bps=False):\n", + "\n", + " if is_bps:\n", + " rate_change /= 10000\n", + "\n", + " new_market_value = 0\n", + " for bond, quantity in zip(self.bonds, self.bond_quantities):\n", + " new_price = bond.price_from_rate(rate=bond.rate + rate_change)\n", + " new_market_value += new_price*quantity\n", + "\n", + " return new_market_value\n", + "\n", + "\n", + "class FixedIncomeHedge(DI1):\n", + "\n", + " def __init__(self, fi_portfolio, di1_connect):\n", + " super().__init__(di1_connect)\n", + " self.fi_portfolio = fi_portfolio\n", + " self.ref_date = self.fi_portfolio.ref_date\n", + " self.di_df = self.get_di1_information(self.ref_date)\n", + "\n", + " def get_di1_information(self, ref_date=None):\n", + "\n", + " ref_date = self.ref_date if ref_date is None else ref_date\n", + " contracts = self.market_menu(self.ref_date)\n", + " df = pd.DataFrame(index=contracts)\n", + " df['MATURITY'] = [self.maturity(code) for code in contracts]\n", + " df['VOLUME'] = [self.volume(code, ref_date) for code in contracts]\n", + " df['DURATION'] = [-self.duration(code, ref_date) for code in contracts]\n", + " df['CONVEXITY'] = [self.convexity(code, ref_date) for code in contracts]\n", + " df['DV01'] = [-self.dv01(code, ref_date) for code in contracts]\n", + " df['PRICE'] = [self.theoretical_price(code, ref_date) for code in contracts]\n", + " df['IMPLIED_YIELD'] = [self.implied_yield(code, ref_date) for code in contracts]\n", + " return df.sort_values(['MATURITY'])\n", + "\n", + " def di1_price(self, code, rate_change, ref_date=None, is_bps=False):\n", + "\n", + " ref_date = self.ref_date if ref_date is None else ref_date\n", + " if is_bps:\n", + " rate_change /= 10000\n", + " initial_rate = self.implied_yield(code, ref_date)\n", + " du = self.du2maturity(ref_date, code)\n", + " return 100000 / ((1 + initial_rate + rate_change)**(du/252))\n", + "\n", + " def duration_hedge(self, min_contract_pct_volume=0.5):\n", + "\n", + " portfolio_dur = self.fi_portfolio.duration\n", + " portfolio_value = self.fi_portfolio.portfolio_value\n", + " min_volume = np.round(self.fi_portfolio.portfolio_value / 100000, 0)\n", + " filtered_df = self.di_df[self.di_df['DURATION'] < portfolio_dur]\n", + " filtered_df = filtered_df[filtered_df['VOLUME'] > min_volume/min_contract_pct_volume]\n", + " last_row = filtered_df.shape[0]\n", + " for n in reversed(range(last_row)):\n", + " di_dur = filtered_df['DURATION'].iloc[n]\n", + " di_pu = filtered_df['PRICE'].iloc[n]\n", + " n_contracts = (portfolio_value*portfolio_dur)/(di_pu*di_dur)\n", + " contract_code = filtered_df.index[n]\n", + " if n_contracts < filtered_df['VOLUME'].iloc[n]:\n", + " return {contract_code: np.round(n_contracts, 0)}\n", + "\n", + " return np.nan\n", + "\n", + " def duration_convexity_hedge(self, min_contract_pct_volume=0.5, min_upper_dur_distance=1):\n", + " portfolio_dur = self.fi_portfolio.duration\n", + " portfolio_value = self.fi_portfolio.portfolio_value\n", + " portfolio_convex = self.fi_portfolio.convexity\n", + "\n", + " min_volume = np.round(self.fi_portfolio.portfolio_value / 100000, 0)\n", + "\n", + " filtered_df = self.di_df[self.di_df['VOLUME'] > min_volume/min_contract_pct_volume]\n", + " lower_di = filtered_df[filtered_df['DURATION'] < portfolio_dur]\n", + " lower_di_code = lower_di['VOLUME'].idxmax()\n", + " lower_di = lower_di[lower_di.index == lower_di_code]\n", + " upper_di = filtered_df[filtered_df['DURATION'] > portfolio_dur + min_upper_dur_distance]\n", + " upper_di_code = upper_di['VOLUME'].idxmax()\n", + " upper_di = upper_di[upper_di.index == upper_di_code]\n", + "\n", + " pu_lower = lower_di['PRICE'].values[0]\n", + " dur_lower = lower_di['DURATION'].values[0]\n", + " convex_lower = lower_di['CONVEXITY'].values[0]\n", + "\n", + " pu_upper = upper_di['PRICE'].values[0]\n", + " dur_upper = upper_di['DURATION'].values[0]\n", + " convex_upper = upper_di['CONVEXITY'].values[0]\n", + "\n", + " a = np.array([[pu_lower*dur_lower*(-1/100), pu_upper*dur_upper*(-1/100)],\n", + " [pu_lower*((convex_lower/2)*((1/100)**2)), pu_upper*((convex_upper/2)*((1/100)**2))]])\n", + " b = np.array([portfolio_value*(portfolio_dur*(-1/100)), portfolio_value*(portfolio_convex/2*((1/100)**2))])\n", + " quantities = np.linalg.inv(a) @ b\n", + " return {lower_di_code: np.round(quantities[0], 0), upper_di_code: np.round(quantities[1], 0)}" + ] + }, + { + "cell_type": "code", + "execution_count": 149, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reference Date 2021-02-05 00:00:00\n", + "Portfolio Rate 4.47%\n", + "Portfolio Duration 1.76\n", + "Portfolio Convexity 7.47\n", + "Portfolio DV01 17.84\n", + "Initial Value: 1,738,213,151.82\n" + ] + } + ], + "source": [ + "\n", + "path = 'D:/Pedro/OneDrive/MPE Insper/Renda Fixa/'\n", + "bond_df = pd.read_excel(f'{path}BZRFIRFM Index as of Feb 05 20211.xlsx')\n", + "\n", + "ref_date = pd.to_datetime('2021-02-05')\n", + "print(f'Reference Date {ref_date}')\n", + "\n", + "bond_names = list(bond_df['Description'].values)\n", + "market_values = list(bond_df['Market Value'].values)\n", + "prices = list(bond_df['Vendor Price'].values)\n", + "\n", + "bond_instruments = list()\n", + "for bond_name, market_value, price in zip(bond_names, market_values, prices):\n", + " split_name = bond_name.split(' ')\n", + " expiry = pd.to_datetime(split_name[-1]).date()\n", + " if split_name[0] == 'BLTN':\n", + " bond_instruments.append(LTN(expiry=expiry,\n", + " principal=1000,\n", + " price=price,\n", + " ref_date=ref_date))\n", + " if split_name[0] == 'BNTNF':\n", + " bond_instruments.append(NTNF(expiry=expiry,\n", + " principal=1000,\n", + " price=price,\n", + " ref_date=ref_date))\n", + "\n", + "\n", + "fi_portfolio = FixedIncomePortfolio(bonds=bond_instruments, ref_date=ref_date, di1_connect=None,\n", + " bond_market_values=market_values)\n", + "\n", + "\n", + "print(f'Portfolio Rate {fi_portfolio.rate:.2%}')\n", + "print(f'Portfolio Duration {fi_portfolio.duration:.2f}')\n", + "print(f'Portfolio Convexity {fi_portfolio.convexity:.2f}')\n", + "print(f'Portfolio DV01 {fi_portfolio.dv01:.2f}')\n", + "initial_value = fi_portfolio.portfolio_value\n", + "print(f'Initial Value: {fi_portfolio.portfolio_value:,.2f}')\n", + "\n", + "db_connect = DBConnect('fhreadonly', 'finquant')\n", + "fi_hedge = FixedIncomeHedge(fi_portfolio, db_connect)\n", + "\n", + "duration_hedge = fi_hedge.duration_hedge(0.5)\n", + "duration_convexity_hedge = fi_hedge.duration_convexity_hedge(0.5)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 150, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "N22 24229.0\n", + "Duration Hedge\n", + "Portfolio PNL -44,352,249.04 Hedge PNL 44,995,136.29 Difference 642,887.25\n", + "F22 18406.0\n", + "F25 5185.0\n", + "Portfolio PNL -44,352,249.04 Hedge PNL 44,696,675.23 Difference 344,426.20\n" + ] + } + ], + "source": [ + "# 150 bps increase\n", + "hedge_pnl = 0\n", + "port_pnl = fi_portfolio.flat_rate_change(rate_change=150, is_bps=True) - initial_value\n", + "for code, quantity in duration_hedge.items():\n", + " print(code, quantity)\n", + " hedge_pnl += -quantity*(fi_hedge.di1_price(code, 150, ref_date, True) - fi_hedge.theoretical_price(code, ref_date))\n", + "\n", + "print('Duration Hedge')\n", + "print(f'Portfolio PNL {port_pnl:,.2f} Hedge PNL {hedge_pnl:,.2f} Difference {hedge_pnl+port_pnl:,.2f}')\n", + "\n", + "hedge_pnl_2 = 0\n", + "for code, quantity in duration_convexity_hedge.items():\n", + " print(code, quantity)\n", + " hedge_pnl_2 += -quantity*(fi_hedge.di1_price(code, 150, ref_date, True) - fi_hedge.theoretical_price(code, ref_date))\n", + "\n", + "print(f'Portfolio PNL {port_pnl:,.2f} Hedge PNL {hedge_pnl_2:,.2f} Difference {hedge_pnl_2+port_pnl:,.2f}')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/dataapi/AWS/getb3derivatives.py b/dataapi/AWS/getb3derivatives.py index 39c608b..bdc5c73 100644 --- a/dataapi/AWS/getb3derivatives.py +++ b/dataapi/AWS/getb3derivatives.py @@ -8,7 +8,6 @@ from calendars import DayCounts - class B3AbstractDerivative(object): monthdict = {'F': 1, 'G': 2, 'H': 3, 'J': 4, 'K': 5, 'M': 6, @@ -188,7 +187,7 @@ def theoretical_price(self, code, t): """ y = self.implied_yield(code, t) du = self.du2maturity(t, code) - price = 100000 / ((1 + y / 100.)**(du/252)) + price = 100000 / ((1 + y)**(du/252)) return price def dv01(self, code, t): @@ -211,7 +210,7 @@ def duration(self, code, t): :param t: reference date """ dPdy = self.dv01(code, t) - duration = dPdy / self.theoretical_price(code, t) + duration = 10000 * dPdy / self.theoretical_price(code, t) return duration def convexity(self, code, t): diff --git a/finmath/brazilian_bonds/government_bonds.py b/finmath/brazilian_bonds/government_bonds.py index a816156..8b119d2 100644 --- a/finmath/brazilian_bonds/government_bonds.py +++ b/finmath/brazilian_bonds/government_bonds.py @@ -12,6 +12,18 @@ dc = DayCounts('bus/252', calendar='cdr_anbima') +def truncate(number, decimals=0): + """Returns a value truncated to a specific number of decimal places""" + if not isinstance(decimals, int): + raise TypeError("decimal places must be an integer.") + elif decimals < 0: + raise ValueError("decimal places has to be 0 or more.") + elif decimals == 0: + return np.trunc(number) + factor = 10.0 ** decimals + return np.trunc(number * factor) / factor + + class LTN(object): def __init__(self, @@ -57,16 +69,30 @@ def __init__(self, self.dv01 = (self.mod_duration / 100.) * self.price self.convexity = self.ytm * (1. + self.ytm) / (1. + self.rate) ** 2 - @staticmethod - def price_from_rate(principal: float = 1e6, + def price_from_rate(self, + principal: Optional[float] = None, rate: Optional[float] = None, - ytm: Optional[float] = None): - return principal / (1. + rate) ** ytm + ytm: Optional[float] = None, + truncate_price: bool = True): + + principal = self.principal if principal is None else principal + rate = self.rate if rate is None else rate + ytm = self.ytm if ytm is None else ytm + # Adjusting according the Anbima specifications + pu = 10*np.round(100/((1 + rate)**ytm), 10) + if truncate_price: + pu = truncate(pu, 6) - @staticmethod - def rate_from_price(principal: float = 1e6, + return principal/1000 * pu + + def rate_from_price(self, + principal: Optional[float] = None, price: Optional[float] = None, ytm: Optional[float] = None): + + principal = self.principal if principal is None else principal + price = self.price if price is None else price + ytm = self.ytm if ytm is None else ytm return (principal / price) ** (1. / ytm) - 1. @@ -95,24 +121,25 @@ def __init__(self, self.expiry = pd.to_datetime(expiry).date() self.ref_date = pd.to_datetime(ref_date).date() + self.principal = principal - interest = ((1. + coupon_rate) ** (1. / 2.) - 1.) * principal + interest = ((1. + coupon_rate) ** (1. / 2.) - 1.) * self.principal cash_flows = pd.Series(index=self.payment_dates(), data=interest).sort_index() - cash_flows.iloc[-1] += principal + cash_flows.iloc[-1] += self.principal self.cash_flows = cash_flows if rate is not None and price is None: self.rate: float = float(rate) - self.price = self.price_from_rate() + self.price = self.price_from_rate(principal=self.principal, rate=self.rate) elif rate is None and price is not None: self.price = float(price) - self.rate = self.rate_from_price() + self.rate = self.rate_from_price(price=self.price) else: - pt = self.price_from_rate() - if np.abs(pt - float(price)) / principal > 0.1: + pt = self.price_from_rate(principal=self.principal, rate=rate) + if np.abs(pt - float(price)) / self.principal > 0.1: msg = 'Given price and rate are incompatible!' warnings.warn(msg) self.rate = rate @@ -132,19 +159,32 @@ def payment_dates(self): return sorted(payd) - def price_from_rate(self) -> float: + def price_from_rate(self, + principal: Optional[float] = None, + rate: Optional[float] = None, + truncate_price: bool = True) -> float: pv = 0. + principal = self.principal if principal is None else principal + rate = self.rate if rate is None else rate for d, p in self.cash_flows.items(): - cf = LTN(d, rate=self.rate, principal=p, - ref_date=self.ref_date) - pv += cf.price - return float(pv) - - def rate_from_price(self): - theor_p = lambda x: sum([LTN(d, rate=x, principal=p, - ref_date=self.ref_date).price - for d, p in self.cash_flows.items()]) - error = lambda x: (self.price - float(theor_p(x))) + # Adjusting according the Anbima specifications + p = np.round(100 * p / principal, 6) + pv += LTN(d, ref_date=self.ref_date, price=p).price_from_rate(p, rate, None, False) + + if truncate_price: + pv = truncate(10*pv, 6) + # Adjusting back according to the intended principal + return pv * principal / 1000 + + def rate_from_price(self, + price: Optional[float] = None): + + price = self.price if price is None else price + theor_p = lambda x: sum([ + LTN(d, ref_date=self.ref_date, price=p).price_from_rate(p, x, None, False) + for d, p in self.cash_flows.items() + ]) + error = lambda x: (price - float(theor_p(x))) return optimize.brentq(error, 0., 1.) From 4a5a6979b35548aac02e6230e62a0ef526ace20f Mon Sep 17 00:00:00 2001 From: PCoelho89 <44276159+PCoelho89@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:54:03 -0300 Subject: [PATCH 2/6] Lista 4 --- Experimental/CubicSpline.ipynb | 81 +++++ Experimental/PCA.ipynb | 90 +++++ ...ixa - Lista 3 - Pedro Coelho F Costa.ipynb | 324 ++++++++++++++++++ .../fixed_income_portfolio.ipynb | 6 +- 4 files changed, 498 insertions(+), 3 deletions(-) create mode 100644 Experimental/CubicSpline.ipynb create mode 100644 Experimental/PCA.ipynb create mode 100644 Experimental/Renda Fixa - Lista 3 - Pedro Coelho F Costa.ipynb rename {dataapi/AWS => Experimental}/fixed_income_portfolio.ipynb (99%) diff --git a/Experimental/CubicSpline.ipynb b/Experimental/CubicSpline.ipynb new file mode 100644 index 0000000..7176459 --- /dev/null +++ b/Experimental/CubicSpline.ipynb @@ -0,0 +1,81 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n", + "4\n", + "5\n", + "6\n", + "7\n", + "8\n", + "9\n", + "10\n", + "11\n", + "12\n", + "13\n", + "14\n", + "15\n", + "16\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "strikes = np.asarray([80, 90, 100, 110, 120])\n", + "vol = np.asarray([0.18, 0.14, 0.12, 0.11, 0.11])\n", + "\n", + "n_points = len(strikes) - 1\n", + "n_polynomials = 2*n_points\n", + "nodes = n_points - 1\n", + "restrictions = 2\n", + "\n", + "cubic_fun = lambda x: x**3\n", + "cubic_der = lambda x: 3*x**2\n", + "cubic_convex = lambda x: 6*x\n", + "\n", + "total_columns = 4*n_points\n", + "total_rows = n_polynomials + nodes*2 + restrictions\n", + "\n", + "cubic_matrix = np.zeros([total_rows, total_columns])\n", + "\n", + "for n_pol in range(n_polynomials):\n", + " n_max = (n_pol + 1) * 2\n", + " for n in range(n_max - 1, n_max + 1):\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/Experimental/PCA.ipynb b/Experimental/PCA.ipynb new file mode 100644 index 0000000..a2184bb --- /dev/null +++ b/Experimental/PCA.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "from sklearn.decomposition import PCA\n", + "from dataapi.FRED.getfreddata import FRED\n", + "\n", + "fred = FRED()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1M 3M 6M 1Y 2Y 3Y 5Y 7Y 10Y 20Y 30Y\n", + "Date \n", + "1962-01-02 NaN NaN NaN 3.22 NaN 3.70 3.88 NaN 4.06 4.07 NaN\n", + "1962-01-03 NaN NaN NaN 3.24 NaN 3.70 3.87 NaN 4.03 4.07 NaN\n", + "1962-01-04 NaN NaN NaN 3.24 NaN 3.69 3.86 NaN 3.99 4.06 NaN\n", + "1962-01-05 NaN NaN NaN 3.26 NaN 3.71 3.89 NaN 4.02 4.07 NaN\n", + "1962-01-08 NaN NaN NaN 3.31 NaN 3.71 3.91 NaN 4.03 4.08 NaN\n", + "... ... ... ... ... ... ... ... ... ... ... ...\n", + "2021-02-26 0.04 0.04 0.05 0.08 0.14 0.30 0.75 1.15 1.44 2.08 2.17\n", + "2021-03-01 0.03 0.05 0.07 0.08 0.13 0.27 0.71 1.12 1.45 2.11 2.23\n", + "2021-03-02 0.04 0.04 0.06 0.08 0.13 0.26 0.67 1.08 1.42 2.09 2.21\n", + "2021-03-03 0.04 0.05 0.07 0.08 0.14 0.29 0.73 1.14 1.47 2.12 2.25\n", + "2021-03-04 0.03 0.04 0.07 0.08 0.14 0.32 0.77 1.21 1.54 2.18 2.30\n", + "\n", + "[14780 rows x 11 columns]\n" + ] + } + ], + "source": [ + "US_rates = {'DGS1MO': '1M',\n", + " 'DGS3MO': '3M',\n", + " 'DGS6MO': '6M',\n", + " 'DGS1': '1Y',\n", + " 'DGS2': '2Y',\n", + " 'DGS3': '3Y',\n", + " 'DGS5': '5Y',\n", + " 'DGS7': '7Y',\n", + " 'DGS10': '10Y',\n", + " 'DGS20': '20Y',\n", + " 'DGS30': '30Y'}\n", + "\n", + "df_rates = fred.fetch(US_rates)\n", + "print(df_rates)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/Experimental/Renda Fixa - Lista 3 - Pedro Coelho F Costa.ipynb b/Experimental/Renda Fixa - Lista 3 - Pedro Coelho F Costa.ipynb new file mode 100644 index 0000000..9fa5121 --- /dev/null +++ b/Experimental/Renda Fixa - Lista 3 - Pedro Coelho F Costa.ipynb @@ -0,0 +1,324 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from finmath.brazilian_bonds.government_bonds import LTN, NTNF\n", + "from finmath.termstructure.curve_models import CurveBootstrap, NelsonSiegelSvensson\n", + "from calendars import DayCounts\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.ticker as mtick\n", + "%matplotlib inline\n", + "\n", + "\n", + "def format_graph(ax):\n", + " ax.yaxis.set_major_formatter(mtick.PercentFormatter(xmax=1.0, decimals=2))\n", + " plt.grid(b=True, which='major', color='#666666', linestyle='-')\n", + " plt.minorticks_on()\n", + " plt.grid(b=True, which='minor', color='#999999', linestyle='-', alpha=0.2)\n", + " plt.show()\n", + "\n", + "\n", + "############ Anbima Data ################\n", + "\n", + "ltn_anbima_yields = [2.0580, 2.3885, 2.9904, 3.4463, 4.0148, 4.4847, 4.9137,\n", + " 5.25, 5.7519, 6.115, 6.4247]\n", + "ltn_anbima_prices = [997.013474, 990.769777, 981.006668, 969.939968, 955.861261, 940.724187, 923.868863,\n", + " 907.503999, 874.939934, 842.628298, 810.360245]\n", + "ltn_maturities = ['2021-04-01', '2021-07-01', '2021-10-01', '2022-01-01', '2022-04-01', '2022-07-01', '2022-10-01',\n", + " '2023-01-01', '2023-07-01', '2024-01-01', '2024-07-01']\n", + "\n", + "ntnf_anbima_yields = [5.1132, 6.2152, 6.8692, 7.3165, 7.6390]\n", + "ntnf_anbima_prices = [1094.209749, 1134.782985, 1154.793279, 1164.388278, 1167.734580]\n", + "ntnf_maturities = ['2023-01-01', '2025-01-01', '2027-01-01','2029-01-01', '2031-01-01']" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LTN\n", + "2021-04-01 997.013474 997.013474 2.0580 2.0580\n", + "2021-07-01 990.769777 990.769777 2.3885 2.3885\n", + "2021-10-01 981.006668 981.006668 2.9904 2.9904\n", + "2022-01-01 969.939968 969.939968 3.4463 3.4463\n", + "2022-04-01 955.861261 955.861261 4.0148 4.0148\n", + "2022-07-01 940.724187 940.724187 4.4847 4.4847\n", + "2022-10-01 923.868863 923.868863 4.9137 4.9137\n", + "2023-01-01 907.503999 907.503999 5.2500 5.2500\n", + "2023-07-01 874.939934 874.939934 5.7519 5.7519\n", + "2024-01-01 842.628298 842.628298 6.1150 6.1150\n", + "2024-07-01 810.360245 810.360245 6.4247 6.4247\n" + ] + } + ], + "source": [ + "######## LTN ##########\n", + "ref_date = pd.to_datetime('2021-02-05').date()\n", + "dc = DayCounts('bus/252', calendar='cdr_anbima')\n", + "\n", + "dt_ltn_maturities = [pd.to_datetime(maturity).date() for maturity in ltn_maturities]\n", + "ltns = dict()\n", + "print('LTN')\n", + "for anbima_yield, maturity, anbima_price in zip(ltn_anbima_yields, dt_ltn_maturities, ltn_anbima_prices):\n", + " ltns[maturity] = LTN(expiry=maturity, rate=anbima_yield/100, principal=1000, ref_date=ref_date)\n", + " print(f'{ltns[maturity].expiry} {ltns[maturity].price} {anbima_price} '\n", + " f'{ltns[maturity].rate_from_price(price=anbima_price)*100:.4f} {anbima_yield:.4f}')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NTN-F\n", + "2023-01-01 109420.9749 109420.97490000002 5.1132 5.1132\n", + "2025-01-01 113478.2985 113478.2985 6.2152 6.2152\n", + "2027-01-01 115479.32789999999 115479.3279 6.8692 6.8692\n", + "2029-01-01 116438.8278 116438.82779999998 7.3165 7.3165\n", + "2031-01-01 116773.458 116773.458 7.6390 7.6390\n" + ] + } + ], + "source": [ + "######## NTN-F ##########\n", + "dt_ntnf_maturities = [pd.to_datetime(maturity).date() for maturity in ntnf_maturities]\n", + "ntnfs = dict()\n", + "print('NTN-F')\n", + "for anbima_yield, maturity, anbima_price in zip(ntnf_anbima_yields, dt_ntnf_maturities, ntnf_anbima_prices):\n", + " ntnfs[maturity] = NTNF(expiry=maturity, rate=anbima_yield/100, principal=100000, ref_date=ref_date)\n", + " print(f'{ntnfs[maturity].expiry} {ntnfs[maturity].price} {anbima_price*100} '\n", + " f'{ntnfs[maturity].rate_from_price(price=anbima_price*100)*100:.4f} {anbima_yield:.4f}')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 4, + "outputs": [], + "source": [ + "######## Flat Forward Curve ##########\n", + "# print('Flat Forward Curve')\n", + "# ltn_prices = list()\n", + "# ltn_cash_flows = list()\n", + "# for mat, bond in ltns.items():\n", + "# ltn_prices.append(bond.price)\n", + "# ltn_cash_flows.append(pd.Series(index=[mat], data=[bond.principal]))\n", + "#\n", + "# ntnf_prices = list()\n", + "# ntnf_cash_flows = list()\n", + "# for mat, bond in ntnfs.items():\n", + "# ntnf_prices.append(bond.price)\n", + "# ntnf_cash_flows.append(bond.cash_flows)\n", + "#\n", + "# all_prices = ltn_prices+ntnf_prices\n", + "# all_cash_flows = ltn_cash_flows+ntnf_cash_flows\n", + "#\n", + "# bz_curve = CurveBootstrap(prices=all_prices,\n", + "# cash_flows=all_cash_flows,\n", + "# ref_date=ref_date)\n", + "#\n", + "# unique_maturities = set(dt_ltn_maturities + dt_ntnf_maturities)\n", + "# unique_maturities = sorted([dc.busdateroll(dt_date, 'modifiedfollowing') for dt_date in unique_maturities])\n", + "# flat_forward_curve = pd.Series(data=bz_curve.zero_curve.values, index=unique_maturities)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 5, + "outputs": [], + "source": [ + "######## Nelson Siegel Svensson Curve ##########\n", + "# print('Nelson Siegel Svensson Curve')\n", + "# lambdas = [2.2648, 0.3330]\n", + "# nelson_siegel = NelsonSiegelSvensson(prices=all_prices,\n", + "# cash_flows=all_cash_flows,\n", + "# ref_date=ref_date,\n", + "# lambdas=lambdas)\n", + "#\n", + "# nss_curve = pd.Series(index=unique_maturities, dtype='float64')\n", + "# betas = nelson_siegel.betas\n", + "# for dt in unique_maturities:\n", + "# year_fraction = dc.tf(ref_date, dt)\n", + "# nss_curve[dt] = nelson_siegel.rate_for_ytm(betas, lambdas, year_fraction)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [], + "source": [ + "######## DI1 Curve ##########\n", + "# print('DI1 Curve')\n", + "# path = 'D:/Pedro/OneDrive/MPE Insper/Renda Fixa/'\n", + "# di_df = pd.read_excel(f'{path}DI_table_20210205.xlsx')\n", + "# di_df['Maturity'] = [dc.busdateroll(dt_date, 'modifiedfollowing') for dt_date in di_df['Maturity'].values]\n", + "#\n", + "# di_df.set_index(keys=['Maturity'], inplace=True)\n", + "# di_df['Last Price'] = di_df['Last Price'] / 100\n", + "# di_series = pd.Series(data=di_df['Last Price'].values, index=di_df.index)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [], + "source": [ + "######## All Curves ##########\n", + "# all_curves = pd.DataFrame(index=di_series.index, columns=['DI1', 'Flat Forward', 'NSS'])\n", + "# all_curves['DI1'] = di_series.values\n", + "#\n", + "# for dt in all_curves.index:\n", + "# year_fraction = dc.tf(ref_date, dt)\n", + "# all_curves.at[dt, 'Flat Forward'] = bz_curve.rate_for_date(year_fraction)\n", + "# all_curves.at[dt, 'NSS'] = nelson_siegel.rate_for_ytm(betas, lambdas, year_fraction)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 8, + "outputs": [], + "source": [ + "######## Flat Forward x NSS ##########\n", + "# ax = all_curves[['Flat Forward', 'NSS']].plot(figsize=(15,10), fontsize=16, marker='o')\n", + "# format_graph(ax)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 9, + "outputs": [], + "source": [ + "######## Flat Forward x DI1 ##########\n", + "# ax = all_curves[['Flat Forward', 'DI1']].plot(figsize=(15,10), fontsize=16, marker='o')\n", + "# format_graph(ax)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 10, + "outputs": [], + "source": [ + "######## NSS x DI1 ##########\n", + "# ax = all_curves[['NSS', 'DI1']].plot(figsize=(15,10), fontsize=16, marker='o')\n", + "# format_graph(ax)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 11, + "outputs": [], + "source": [ + "###### Arbitrage ##########\n", + "# bond_maturity = pd.to_datetime('2024-01-01').date()\n", + "# tenor_date = dc.busdateroll(pd.to_datetime('2024-01-01').date(), 'modifiedfollowing')\n", + "# di1_rate = all_curves[all_curves.index==tenor_date]['DI1']\n", + "# principal = 1000000\n", + "# bond_rate = all_curves[all_curves.index==tenor_date]['NSS'][0]\n", + "# ltn_2024 = LTN(expiry=bond_maturity, rate=bond_rate, ref_date=ref_date,\n", + "# principal=principal)\n", + "# loan_at_future = ltn_2024.price*((1+di1_rate[0])**dc.tf(ref_date, bond_maturity))\n", + "# yearly_spread = (principal/loan_at_future)**(1/dc.tf(ref_date, bond_maturity))-1\n", + "\n", + "\n", + "# print(f'Buy Bond with Maturity at {bond_maturity} and principal of {principal:,.2f} @{bond_rate:.4%}')\n", + "# print(f'Bond {bond_maturity} price: {ltn_2024.price:,.2f}')\n", + "# print(f'Take a loan of {ltn_2024.price:,.2f} @{di1_rate[0]:.4%}')\n", + "# print(f'Gain the yearly spread of {yearly_spread:.4%}')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/dataapi/AWS/fixed_income_portfolio.ipynb b/Experimental/fixed_income_portfolio.ipynb similarity index 99% rename from dataapi/AWS/fixed_income_portfolio.ipynb rename to Experimental/fixed_income_portfolio.ipynb index 760f115..03b53f9 100644 --- a/dataapi/AWS/fixed_income_portfolio.ipynb +++ b/Experimental/fixed_income_portfolio.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 148, + "execution_count": 1, "metadata": { "collapsed": true }, @@ -164,7 +164,7 @@ }, { "cell_type": "code", - "execution_count": 149, + "execution_count": 2, "outputs": [ { "name": "stdout", @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 150, + "execution_count": 3, "outputs": [ { "name": "stdout", From 4b25484c70192ae9a49609ee0f6d974ef98ef270 Mon Sep 17 00:00:00 2001 From: Pedro Coelho Date: Mon, 8 Mar 2021 20:00:42 -0300 Subject: [PATCH 3/6] Delete Experimental directory --- Experimental/CubicSpline.ipynb | 81 ----- Experimental/PCA.ipynb | 90 ----- ...ixa - Lista 3 - Pedro Coelho F Costa.ipynb | 324 ------------------ Experimental/fixed_income_portfolio.ipynb | 298 ---------------- 4 files changed, 793 deletions(-) delete mode 100644 Experimental/CubicSpline.ipynb delete mode 100644 Experimental/PCA.ipynb delete mode 100644 Experimental/Renda Fixa - Lista 3 - Pedro Coelho F Costa.ipynb delete mode 100644 Experimental/fixed_income_portfolio.ipynb diff --git a/Experimental/CubicSpline.ipynb b/Experimental/CubicSpline.ipynb deleted file mode 100644 index 7176459..0000000 --- a/Experimental/CubicSpline.ipynb +++ /dev/null @@ -1,81 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "2\n", - "3\n", - "4\n", - "5\n", - "6\n", - "7\n", - "8\n", - "9\n", - "10\n", - "11\n", - "12\n", - "13\n", - "14\n", - "15\n", - "16\n" - ] - } - ], - "source": [ - "import numpy as np\n", - "\n", - "strikes = np.asarray([80, 90, 100, 110, 120])\n", - "vol = np.asarray([0.18, 0.14, 0.12, 0.11, 0.11])\n", - "\n", - "n_points = len(strikes) - 1\n", - "n_polynomials = 2*n_points\n", - "nodes = n_points - 1\n", - "restrictions = 2\n", - "\n", - "cubic_fun = lambda x: x**3\n", - "cubic_der = lambda x: 3*x**2\n", - "cubic_convex = lambda x: 6*x\n", - "\n", - "total_columns = 4*n_points\n", - "total_rows = n_polynomials + nodes*2 + restrictions\n", - "\n", - "cubic_matrix = np.zeros([total_rows, total_columns])\n", - "\n", - "for n_pol in range(n_polynomials):\n", - " n_max = (n_pol + 1) * 2\n", - " for n in range(n_max - 1, n_max + 1):\n", - "\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/Experimental/PCA.ipynb b/Experimental/PCA.ipynb deleted file mode 100644 index a2184bb..0000000 --- a/Experimental/PCA.ipynb +++ /dev/null @@ -1,90 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "from sklearn.decomposition import PCA\n", - "from dataapi.FRED.getfreddata import FRED\n", - "\n", - "fred = FRED()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 1M 3M 6M 1Y 2Y 3Y 5Y 7Y 10Y 20Y 30Y\n", - "Date \n", - "1962-01-02 NaN NaN NaN 3.22 NaN 3.70 3.88 NaN 4.06 4.07 NaN\n", - "1962-01-03 NaN NaN NaN 3.24 NaN 3.70 3.87 NaN 4.03 4.07 NaN\n", - "1962-01-04 NaN NaN NaN 3.24 NaN 3.69 3.86 NaN 3.99 4.06 NaN\n", - "1962-01-05 NaN NaN NaN 3.26 NaN 3.71 3.89 NaN 4.02 4.07 NaN\n", - "1962-01-08 NaN NaN NaN 3.31 NaN 3.71 3.91 NaN 4.03 4.08 NaN\n", - "... ... ... ... ... ... ... ... ... ... ... ...\n", - "2021-02-26 0.04 0.04 0.05 0.08 0.14 0.30 0.75 1.15 1.44 2.08 2.17\n", - "2021-03-01 0.03 0.05 0.07 0.08 0.13 0.27 0.71 1.12 1.45 2.11 2.23\n", - "2021-03-02 0.04 0.04 0.06 0.08 0.13 0.26 0.67 1.08 1.42 2.09 2.21\n", - "2021-03-03 0.04 0.05 0.07 0.08 0.14 0.29 0.73 1.14 1.47 2.12 2.25\n", - "2021-03-04 0.03 0.04 0.07 0.08 0.14 0.32 0.77 1.21 1.54 2.18 2.30\n", - "\n", - "[14780 rows x 11 columns]\n" - ] - } - ], - "source": [ - "US_rates = {'DGS1MO': '1M',\n", - " 'DGS3MO': '3M',\n", - " 'DGS6MO': '6M',\n", - " 'DGS1': '1Y',\n", - " 'DGS2': '2Y',\n", - " 'DGS3': '3Y',\n", - " 'DGS5': '5Y',\n", - " 'DGS7': '7Y',\n", - " 'DGS10': '10Y',\n", - " 'DGS20': '20Y',\n", - " 'DGS30': '30Y'}\n", - "\n", - "df_rates = fred.fetch(US_rates)\n", - "print(df_rates)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/Experimental/Renda Fixa - Lista 3 - Pedro Coelho F Costa.ipynb b/Experimental/Renda Fixa - Lista 3 - Pedro Coelho F Costa.ipynb deleted file mode 100644 index 9fa5121..0000000 --- a/Experimental/Renda Fixa - Lista 3 - Pedro Coelho F Costa.ipynb +++ /dev/null @@ -1,324 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import pandas as pd\n", - "from finmath.brazilian_bonds.government_bonds import LTN, NTNF\n", - "from finmath.termstructure.curve_models import CurveBootstrap, NelsonSiegelSvensson\n", - "from calendars import DayCounts\n", - "import matplotlib.pyplot as plt\n", - "import matplotlib.ticker as mtick\n", - "%matplotlib inline\n", - "\n", - "\n", - "def format_graph(ax):\n", - " ax.yaxis.set_major_formatter(mtick.PercentFormatter(xmax=1.0, decimals=2))\n", - " plt.grid(b=True, which='major', color='#666666', linestyle='-')\n", - " plt.minorticks_on()\n", - " plt.grid(b=True, which='minor', color='#999999', linestyle='-', alpha=0.2)\n", - " plt.show()\n", - "\n", - "\n", - "############ Anbima Data ################\n", - "\n", - "ltn_anbima_yields = [2.0580, 2.3885, 2.9904, 3.4463, 4.0148, 4.4847, 4.9137,\n", - " 5.25, 5.7519, 6.115, 6.4247]\n", - "ltn_anbima_prices = [997.013474, 990.769777, 981.006668, 969.939968, 955.861261, 940.724187, 923.868863,\n", - " 907.503999, 874.939934, 842.628298, 810.360245]\n", - "ltn_maturities = ['2021-04-01', '2021-07-01', '2021-10-01', '2022-01-01', '2022-04-01', '2022-07-01', '2022-10-01',\n", - " '2023-01-01', '2023-07-01', '2024-01-01', '2024-07-01']\n", - "\n", - "ntnf_anbima_yields = [5.1132, 6.2152, 6.8692, 7.3165, 7.6390]\n", - "ntnf_anbima_prices = [1094.209749, 1134.782985, 1154.793279, 1164.388278, 1167.734580]\n", - "ntnf_maturities = ['2023-01-01', '2025-01-01', '2027-01-01','2029-01-01', '2031-01-01']" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "LTN\n", - "2021-04-01 997.013474 997.013474 2.0580 2.0580\n", - "2021-07-01 990.769777 990.769777 2.3885 2.3885\n", - "2021-10-01 981.006668 981.006668 2.9904 2.9904\n", - "2022-01-01 969.939968 969.939968 3.4463 3.4463\n", - "2022-04-01 955.861261 955.861261 4.0148 4.0148\n", - "2022-07-01 940.724187 940.724187 4.4847 4.4847\n", - "2022-10-01 923.868863 923.868863 4.9137 4.9137\n", - "2023-01-01 907.503999 907.503999 5.2500 5.2500\n", - "2023-07-01 874.939934 874.939934 5.7519 5.7519\n", - "2024-01-01 842.628298 842.628298 6.1150 6.1150\n", - "2024-07-01 810.360245 810.360245 6.4247 6.4247\n" - ] - } - ], - "source": [ - "######## LTN ##########\n", - "ref_date = pd.to_datetime('2021-02-05').date()\n", - "dc = DayCounts('bus/252', calendar='cdr_anbima')\n", - "\n", - "dt_ltn_maturities = [pd.to_datetime(maturity).date() for maturity in ltn_maturities]\n", - "ltns = dict()\n", - "print('LTN')\n", - "for anbima_yield, maturity, anbima_price in zip(ltn_anbima_yields, dt_ltn_maturities, ltn_anbima_prices):\n", - " ltns[maturity] = LTN(expiry=maturity, rate=anbima_yield/100, principal=1000, ref_date=ref_date)\n", - " print(f'{ltns[maturity].expiry} {ltns[maturity].price} {anbima_price} '\n", - " f'{ltns[maturity].rate_from_price(price=anbima_price)*100:.4f} {anbima_yield:.4f}')" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 3, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "NTN-F\n", - "2023-01-01 109420.9749 109420.97490000002 5.1132 5.1132\n", - "2025-01-01 113478.2985 113478.2985 6.2152 6.2152\n", - "2027-01-01 115479.32789999999 115479.3279 6.8692 6.8692\n", - "2029-01-01 116438.8278 116438.82779999998 7.3165 7.3165\n", - "2031-01-01 116773.458 116773.458 7.6390 7.6390\n" - ] - } - ], - "source": [ - "######## NTN-F ##########\n", - "dt_ntnf_maturities = [pd.to_datetime(maturity).date() for maturity in ntnf_maturities]\n", - "ntnfs = dict()\n", - "print('NTN-F')\n", - "for anbima_yield, maturity, anbima_price in zip(ntnf_anbima_yields, dt_ntnf_maturities, ntnf_anbima_prices):\n", - " ntnfs[maturity] = NTNF(expiry=maturity, rate=anbima_yield/100, principal=100000, ref_date=ref_date)\n", - " print(f'{ntnfs[maturity].expiry} {ntnfs[maturity].price} {anbima_price*100} '\n", - " f'{ntnfs[maturity].rate_from_price(price=anbima_price*100)*100:.4f} {anbima_yield:.4f}')" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 4, - "outputs": [], - "source": [ - "######## Flat Forward Curve ##########\n", - "# print('Flat Forward Curve')\n", - "# ltn_prices = list()\n", - "# ltn_cash_flows = list()\n", - "# for mat, bond in ltns.items():\n", - "# ltn_prices.append(bond.price)\n", - "# ltn_cash_flows.append(pd.Series(index=[mat], data=[bond.principal]))\n", - "#\n", - "# ntnf_prices = list()\n", - "# ntnf_cash_flows = list()\n", - "# for mat, bond in ntnfs.items():\n", - "# ntnf_prices.append(bond.price)\n", - "# ntnf_cash_flows.append(bond.cash_flows)\n", - "#\n", - "# all_prices = ltn_prices+ntnf_prices\n", - "# all_cash_flows = ltn_cash_flows+ntnf_cash_flows\n", - "#\n", - "# bz_curve = CurveBootstrap(prices=all_prices,\n", - "# cash_flows=all_cash_flows,\n", - "# ref_date=ref_date)\n", - "#\n", - "# unique_maturities = set(dt_ltn_maturities + dt_ntnf_maturities)\n", - "# unique_maturities = sorted([dc.busdateroll(dt_date, 'modifiedfollowing') for dt_date in unique_maturities])\n", - "# flat_forward_curve = pd.Series(data=bz_curve.zero_curve.values, index=unique_maturities)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 5, - "outputs": [], - "source": [ - "######## Nelson Siegel Svensson Curve ##########\n", - "# print('Nelson Siegel Svensson Curve')\n", - "# lambdas = [2.2648, 0.3330]\n", - "# nelson_siegel = NelsonSiegelSvensson(prices=all_prices,\n", - "# cash_flows=all_cash_flows,\n", - "# ref_date=ref_date,\n", - "# lambdas=lambdas)\n", - "#\n", - "# nss_curve = pd.Series(index=unique_maturities, dtype='float64')\n", - "# betas = nelson_siegel.betas\n", - "# for dt in unique_maturities:\n", - "# year_fraction = dc.tf(ref_date, dt)\n", - "# nss_curve[dt] = nelson_siegel.rate_for_ytm(betas, lambdas, year_fraction)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 6, - "outputs": [], - "source": [ - "######## DI1 Curve ##########\n", - "# print('DI1 Curve')\n", - "# path = 'D:/Pedro/OneDrive/MPE Insper/Renda Fixa/'\n", - "# di_df = pd.read_excel(f'{path}DI_table_20210205.xlsx')\n", - "# di_df['Maturity'] = [dc.busdateroll(dt_date, 'modifiedfollowing') for dt_date in di_df['Maturity'].values]\n", - "#\n", - "# di_df.set_index(keys=['Maturity'], inplace=True)\n", - "# di_df['Last Price'] = di_df['Last Price'] / 100\n", - "# di_series = pd.Series(data=di_df['Last Price'].values, index=di_df.index)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 7, - "outputs": [], - "source": [ - "######## All Curves ##########\n", - "# all_curves = pd.DataFrame(index=di_series.index, columns=['DI1', 'Flat Forward', 'NSS'])\n", - "# all_curves['DI1'] = di_series.values\n", - "#\n", - "# for dt in all_curves.index:\n", - "# year_fraction = dc.tf(ref_date, dt)\n", - "# all_curves.at[dt, 'Flat Forward'] = bz_curve.rate_for_date(year_fraction)\n", - "# all_curves.at[dt, 'NSS'] = nelson_siegel.rate_for_ytm(betas, lambdas, year_fraction)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 8, - "outputs": [], - "source": [ - "######## Flat Forward x NSS ##########\n", - "# ax = all_curves[['Flat Forward', 'NSS']].plot(figsize=(15,10), fontsize=16, marker='o')\n", - "# format_graph(ax)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 9, - "outputs": [], - "source": [ - "######## Flat Forward x DI1 ##########\n", - "# ax = all_curves[['Flat Forward', 'DI1']].plot(figsize=(15,10), fontsize=16, marker='o')\n", - "# format_graph(ax)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 10, - "outputs": [], - "source": [ - "######## NSS x DI1 ##########\n", - "# ax = all_curves[['NSS', 'DI1']].plot(figsize=(15,10), fontsize=16, marker='o')\n", - "# format_graph(ax)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 11, - "outputs": [], - "source": [ - "###### Arbitrage ##########\n", - "# bond_maturity = pd.to_datetime('2024-01-01').date()\n", - "# tenor_date = dc.busdateroll(pd.to_datetime('2024-01-01').date(), 'modifiedfollowing')\n", - "# di1_rate = all_curves[all_curves.index==tenor_date]['DI1']\n", - "# principal = 1000000\n", - "# bond_rate = all_curves[all_curves.index==tenor_date]['NSS'][0]\n", - "# ltn_2024 = LTN(expiry=bond_maturity, rate=bond_rate, ref_date=ref_date,\n", - "# principal=principal)\n", - "# loan_at_future = ltn_2024.price*((1+di1_rate[0])**dc.tf(ref_date, bond_maturity))\n", - "# yearly_spread = (principal/loan_at_future)**(1/dc.tf(ref_date, bond_maturity))-1\n", - "\n", - "\n", - "# print(f'Buy Bond with Maturity at {bond_maturity} and principal of {principal:,.2f} @{bond_rate:.4%}')\n", - "# print(f'Bond {bond_maturity} price: {ltn_2024.price:,.2f}')\n", - "# print(f'Take a loan of {ltn_2024.price:,.2f} @{di1_rate[0]:.4%}')\n", - "# print(f'Gain the yearly spread of {yearly_spread:.4%}')" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/Experimental/fixed_income_portfolio.ipynb b/Experimental/fixed_income_portfolio.ipynb deleted file mode 100644 index 03b53f9..0000000 --- a/Experimental/fixed_income_portfolio.ipynb +++ /dev/null @@ -1,298 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "from dataapi.AWS.dbutils import DBConnect\n", - "from dataapi.AWS.getb3derivatives import DI1\n", - "from finmath.brazilian_bonds.government_bonds import LTN, NTNF\n", - "\n", - "\n", - "class FixedIncomePortfolio(object):\n", - "\n", - " def __init__(self, bonds, ref_date, di1_connect, bond_quantities=None, bond_market_values=None,\n", - " adjust_for_convexity=False):\n", - "\n", - " self.bonds = bonds\n", - " self.ref_date = ref_date\n", - " self.di1_connect = di1_connect\n", - " self.adjust_for_convexity = adjust_for_convexity\n", - " self.bond_quantities = bond_quantities\n", - " self.bond_market_values = bond_market_values\n", - " self.portfolio_value = 0\n", - " if self.bond_quantities is None and self.bond_market_values is None:\n", - " msg = 'Please input the bond quantities or the bond market values.'\n", - " raise AttributeError(msg)\n", - " elif self.bond_quantities is not None:\n", - " if len(self.bonds) != len(self.bond_quantities):\n", - " msg = f\"Bonds list length ({len(self.bonds)}) don't match the bond quantities length \" \\\n", - " f\"({len(self.bond_quantities)}).\"\n", - " raise ValueError(msg)\n", - " else:\n", - " self.bond_market_values = list()\n", - " for bond, quantity in zip(self.bonds, self.bond_quantities):\n", - " self.portfolio_value += bond.price*quantity\n", - " self.bond_market_values.append(bond.price*quantity)\n", - " elif self.bond_market_values is not None:\n", - " if len(self.bonds) != len(self.bond_market_values):\n", - " msg = f\"Bonds list length ({len(self.bonds)}) don't match the bond market values length \" \\\n", - " f\"({len(self.bond_market_values)}).\"\n", - " raise ValueError(msg)\n", - " else:\n", - " self.bond_quantities = list()\n", - " for bond, market_value in zip(self.bonds, self.bond_market_values):\n", - " self.portfolio_value += market_value\n", - " self.bond_quantities.append(market_value/bond.price)\n", - "\n", - " self.rate = sum([\n", - " bond.rate * market_value / self.portfolio_value\n", - " for bond, market_value in zip(self.bonds, self.bond_market_values)\n", - " ])\n", - " self.duration = sum([\n", - " bond.mod_duration * market_value / self.portfolio_value\n", - " for bond, market_value in zip(self.bonds, self.bond_market_values)\n", - " ])\n", - "\n", - " self.convexity = sum([\n", - " bond.convexity * market_value / self.portfolio_value\n", - " for bond, market_value in zip(self.bonds, self.bond_market_values)\n", - " ])\n", - "\n", - " self.dv01 = sum([\n", - " bond.dv01 * market_value / self.portfolio_value\n", - " for bond, market_value in zip(self.bonds, self.bond_market_values)\n", - " ])\n", - "\n", - " def flat_rate_change(self, rate_change, is_bps=False):\n", - "\n", - " if is_bps:\n", - " rate_change /= 10000\n", - "\n", - " new_market_value = 0\n", - " for bond, quantity in zip(self.bonds, self.bond_quantities):\n", - " new_price = bond.price_from_rate(rate=bond.rate + rate_change)\n", - " new_market_value += new_price*quantity\n", - "\n", - " return new_market_value\n", - "\n", - "\n", - "class FixedIncomeHedge(DI1):\n", - "\n", - " def __init__(self, fi_portfolio, di1_connect):\n", - " super().__init__(di1_connect)\n", - " self.fi_portfolio = fi_portfolio\n", - " self.ref_date = self.fi_portfolio.ref_date\n", - " self.di_df = self.get_di1_information(self.ref_date)\n", - "\n", - " def get_di1_information(self, ref_date=None):\n", - "\n", - " ref_date = self.ref_date if ref_date is None else ref_date\n", - " contracts = self.market_menu(self.ref_date)\n", - " df = pd.DataFrame(index=contracts)\n", - " df['MATURITY'] = [self.maturity(code) for code in contracts]\n", - " df['VOLUME'] = [self.volume(code, ref_date) for code in contracts]\n", - " df['DURATION'] = [-self.duration(code, ref_date) for code in contracts]\n", - " df['CONVEXITY'] = [self.convexity(code, ref_date) for code in contracts]\n", - " df['DV01'] = [-self.dv01(code, ref_date) for code in contracts]\n", - " df['PRICE'] = [self.theoretical_price(code, ref_date) for code in contracts]\n", - " df['IMPLIED_YIELD'] = [self.implied_yield(code, ref_date) for code in contracts]\n", - " return df.sort_values(['MATURITY'])\n", - "\n", - " def di1_price(self, code, rate_change, ref_date=None, is_bps=False):\n", - "\n", - " ref_date = self.ref_date if ref_date is None else ref_date\n", - " if is_bps:\n", - " rate_change /= 10000\n", - " initial_rate = self.implied_yield(code, ref_date)\n", - " du = self.du2maturity(ref_date, code)\n", - " return 100000 / ((1 + initial_rate + rate_change)**(du/252))\n", - "\n", - " def duration_hedge(self, min_contract_pct_volume=0.5):\n", - "\n", - " portfolio_dur = self.fi_portfolio.duration\n", - " portfolio_value = self.fi_portfolio.portfolio_value\n", - " min_volume = np.round(self.fi_portfolio.portfolio_value / 100000, 0)\n", - " filtered_df = self.di_df[self.di_df['DURATION'] < portfolio_dur]\n", - " filtered_df = filtered_df[filtered_df['VOLUME'] > min_volume/min_contract_pct_volume]\n", - " last_row = filtered_df.shape[0]\n", - " for n in reversed(range(last_row)):\n", - " di_dur = filtered_df['DURATION'].iloc[n]\n", - " di_pu = filtered_df['PRICE'].iloc[n]\n", - " n_contracts = (portfolio_value*portfolio_dur)/(di_pu*di_dur)\n", - " contract_code = filtered_df.index[n]\n", - " if n_contracts < filtered_df['VOLUME'].iloc[n]:\n", - " return {contract_code: np.round(n_contracts, 0)}\n", - "\n", - " return np.nan\n", - "\n", - " def duration_convexity_hedge(self, min_contract_pct_volume=0.5, min_upper_dur_distance=1):\n", - " portfolio_dur = self.fi_portfolio.duration\n", - " portfolio_value = self.fi_portfolio.portfolio_value\n", - " portfolio_convex = self.fi_portfolio.convexity\n", - "\n", - " min_volume = np.round(self.fi_portfolio.portfolio_value / 100000, 0)\n", - "\n", - " filtered_df = self.di_df[self.di_df['VOLUME'] > min_volume/min_contract_pct_volume]\n", - " lower_di = filtered_df[filtered_df['DURATION'] < portfolio_dur]\n", - " lower_di_code = lower_di['VOLUME'].idxmax()\n", - " lower_di = lower_di[lower_di.index == lower_di_code]\n", - " upper_di = filtered_df[filtered_df['DURATION'] > portfolio_dur + min_upper_dur_distance]\n", - " upper_di_code = upper_di['VOLUME'].idxmax()\n", - " upper_di = upper_di[upper_di.index == upper_di_code]\n", - "\n", - " pu_lower = lower_di['PRICE'].values[0]\n", - " dur_lower = lower_di['DURATION'].values[0]\n", - " convex_lower = lower_di['CONVEXITY'].values[0]\n", - "\n", - " pu_upper = upper_di['PRICE'].values[0]\n", - " dur_upper = upper_di['DURATION'].values[0]\n", - " convex_upper = upper_di['CONVEXITY'].values[0]\n", - "\n", - " a = np.array([[pu_lower*dur_lower*(-1/100), pu_upper*dur_upper*(-1/100)],\n", - " [pu_lower*((convex_lower/2)*((1/100)**2)), pu_upper*((convex_upper/2)*((1/100)**2))]])\n", - " b = np.array([portfolio_value*(portfolio_dur*(-1/100)), portfolio_value*(portfolio_convex/2*((1/100)**2))])\n", - " quantities = np.linalg.inv(a) @ b\n", - " return {lower_di_code: np.round(quantities[0], 0), upper_di_code: np.round(quantities[1], 0)}" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Reference Date 2021-02-05 00:00:00\n", - "Portfolio Rate 4.47%\n", - "Portfolio Duration 1.76\n", - "Portfolio Convexity 7.47\n", - "Portfolio DV01 17.84\n", - "Initial Value: 1,738,213,151.82\n" - ] - } - ], - "source": [ - "\n", - "path = 'D:/Pedro/OneDrive/MPE Insper/Renda Fixa/'\n", - "bond_df = pd.read_excel(f'{path}BZRFIRFM Index as of Feb 05 20211.xlsx')\n", - "\n", - "ref_date = pd.to_datetime('2021-02-05')\n", - "print(f'Reference Date {ref_date}')\n", - "\n", - "bond_names = list(bond_df['Description'].values)\n", - "market_values = list(bond_df['Market Value'].values)\n", - "prices = list(bond_df['Vendor Price'].values)\n", - "\n", - "bond_instruments = list()\n", - "for bond_name, market_value, price in zip(bond_names, market_values, prices):\n", - " split_name = bond_name.split(' ')\n", - " expiry = pd.to_datetime(split_name[-1]).date()\n", - " if split_name[0] == 'BLTN':\n", - " bond_instruments.append(LTN(expiry=expiry,\n", - " principal=1000,\n", - " price=price,\n", - " ref_date=ref_date))\n", - " if split_name[0] == 'BNTNF':\n", - " bond_instruments.append(NTNF(expiry=expiry,\n", - " principal=1000,\n", - " price=price,\n", - " ref_date=ref_date))\n", - "\n", - "\n", - "fi_portfolio = FixedIncomePortfolio(bonds=bond_instruments, ref_date=ref_date, di1_connect=None,\n", - " bond_market_values=market_values)\n", - "\n", - "\n", - "print(f'Portfolio Rate {fi_portfolio.rate:.2%}')\n", - "print(f'Portfolio Duration {fi_portfolio.duration:.2f}')\n", - "print(f'Portfolio Convexity {fi_portfolio.convexity:.2f}')\n", - "print(f'Portfolio DV01 {fi_portfolio.dv01:.2f}')\n", - "initial_value = fi_portfolio.portfolio_value\n", - "print(f'Initial Value: {fi_portfolio.portfolio_value:,.2f}')\n", - "\n", - "db_connect = DBConnect('fhreadonly', 'finquant')\n", - "fi_hedge = FixedIncomeHedge(fi_portfolio, db_connect)\n", - "\n", - "duration_hedge = fi_hedge.duration_hedge(0.5)\n", - "duration_convexity_hedge = fi_hedge.duration_convexity_hedge(0.5)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 3, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "N22 24229.0\n", - "Duration Hedge\n", - "Portfolio PNL -44,352,249.04 Hedge PNL 44,995,136.29 Difference 642,887.25\n", - "F22 18406.0\n", - "F25 5185.0\n", - "Portfolio PNL -44,352,249.04 Hedge PNL 44,696,675.23 Difference 344,426.20\n" - ] - } - ], - "source": [ - "# 150 bps increase\n", - "hedge_pnl = 0\n", - "port_pnl = fi_portfolio.flat_rate_change(rate_change=150, is_bps=True) - initial_value\n", - "for code, quantity in duration_hedge.items():\n", - " print(code, quantity)\n", - " hedge_pnl += -quantity*(fi_hedge.di1_price(code, 150, ref_date, True) - fi_hedge.theoretical_price(code, ref_date))\n", - "\n", - "print('Duration Hedge')\n", - "print(f'Portfolio PNL {port_pnl:,.2f} Hedge PNL {hedge_pnl:,.2f} Difference {hedge_pnl+port_pnl:,.2f}')\n", - "\n", - "hedge_pnl_2 = 0\n", - "for code, quantity in duration_convexity_hedge.items():\n", - " print(code, quantity)\n", - " hedge_pnl_2 += -quantity*(fi_hedge.di1_price(code, 150, ref_date, True) - fi_hedge.theoretical_price(code, ref_date))\n", - "\n", - "print(f'Portfolio PNL {port_pnl:,.2f} Hedge PNL {hedge_pnl_2:,.2f} Difference {hedge_pnl_2+port_pnl:,.2f}')" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file From 56fa9ebbb579e37656c5b529c78ab030961007e1 Mon Sep 17 00:00:00 2001 From: PCoelho89 <44276159+PCoelho89@users.noreply.github.com> Date: Sat, 3 Apr 2021 16:22:29 -0300 Subject: [PATCH 4/6] NTN-B class --- finmath/brazilian_bonds/government_bonds.py | 134 +++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/finmath/brazilian_bonds/government_bonds.py b/finmath/brazilian_bonds/government_bonds.py index 8b119d2..cfe5e0a 100644 --- a/finmath/brazilian_bonds/government_bonds.py +++ b/finmath/brazilian_bonds/government_bonds.py @@ -131,7 +131,7 @@ def __init__(self, self.cash_flows = cash_flows if rate is not None and price is None: - self.rate: float = float(rate) + self.rate = float(rate) self.price = self.price_from_rate(principal=self.principal, rate=self.rate) elif rate is None and price is not None: self.price = float(price) @@ -202,3 +202,135 @@ def calculate_risk(self): convexity = (convexity / self.price) / (1. + self.rate) ** 2 return mod_duration, convexity + + +class NTNB(object): + + def __init__(self, + expiry: Date, + rate: Optional[float] = None, + price: Optional[float] = None, + principal: float = 1e6, + coupon_rate: float = 0.06, + vna: Optional[float] = None, + ref_date: Date = TODAY): + """ + Class constructor. + This is a Brazilian government bond that pays coupons every six months + :param expiry: bond expiry date + :param rate: bond yield + :param price: bond price + :param principal: bond principal + :param coupon_rate: bond coupon rate + :param ref_date: reference date for price or rate calculation + """ + + msg = 'Parameters rate and price cannot be both None!' + assert rate is not None or price is not None, msg + + if rate is not None: + msg_2 = 'Parameters price and vna cannot be both None!' + assert price is not None or vna is not None, msg_2 + + if price is not None: + msg_3 = 'Parameters rate and vna cannot be both None!' + assert rate is not None or vna is not None, msg_3 + + self.expiry = pd.to_datetime(expiry).date() + self.ref_date = pd.to_datetime(ref_date).date() + self.principal = principal + self.vna = vna + + interest = ((1. + coupon_rate) ** (1. / 2.) - 1.) * self.principal + cash_flows = pd.Series(index=self.payment_dates(), + data=interest).sort_index() + cash_flows.iloc[-1] += self.principal + + self.cash_flows = cash_flows + + if price is not None and rate is not None: + self.price = float(price) + self.rate = float(rate) + base_price = self.price_from_rate(principal=self.principal, rate=self.rate, vna=1000) + self.vna = np.round(self.price / base_price * 1000, 6) + if rate is not None and price is None and vna is not None: + self.rate = float(rate) + self.price = self.price_from_rate(principal=self.principal, rate=self.rate, vna=self.vna) + elif rate is None and price is not None and vna is not None: + self.price = float(price) + self.rate = self.rate_from_price(price=self.price, vna=self.vna) + + else: + pt = self.price_from_rate(principal=self.principal, rate=rate) + if np.abs(pt - float(price)) / self.principal > 0.1: + msg = 'Given price and rate are incompatible!' + warnings.warn(msg) + self.rate = rate + self.price = price + + self.mod_duration, self.convexity = self.calculate_risk + self.macaulay = self.mod_duration * (1. + self.rate) + self.dv01 = (self.mod_duration / 100.) * self.price + + def payment_dates(self): + + payd = [dc.following(self.expiry)] + d = dc.workday(dc.eom(dc.following(self.expiry), offset=-7) + pd.DateOffset(days=14), 1) + while d > dc.following(self.ref_date): + payd += [d] + d = dc.workday(dc.eom(d, offset=-7)+pd.DateOffset(days=14), 1) + + return sorted(payd) + + def price_from_rate(self, + principal: Optional[float] = None, + rate: Optional[float] = None, + vna: Optional[float] = None, + truncate_price: bool = True) -> float: + pv = 0. + principal = self.principal if principal is None else principal + rate = self.rate if rate is None else rate + vna = self.vna if vna is None else vna + + for d, p in self.cash_flows.items(): + # Adjusting according the Anbima specifications + p = np.round(100 * p / principal, 6) + pv += LTN(d, ref_date=self.ref_date, price=p).price_from_rate(p, rate, None, False) + + pv = truncate(pv, 4) * np.round(vna, 6) / 100 + if truncate_price: + pv = truncate(pv, 6) + # Adjusting back according to the intended principal + return pv * principal / 1000 + + def rate_from_price(self, + price: Optional[float] = None, + vna: Optional[float] = None): + + price = self.price if price is None else price + vna = self.vna if vna is None else vna + price /= vna + price *= self.principal + + theor_p = lambda x: sum([ + LTN(d, ref_date=self.ref_date, price=p).price_from_rate(p, x, None, False) + for d, p in self.cash_flows.items() + ]) + error = lambda x: (price - float(theor_p(x))) + + return optimize.brentq(error, -0.99, 1.) + + @property + def calculate_risk(self): + macaulay = 0. + convexity = 0. + for d, p in self.cash_flows.items(): + pv = p / (1. + self.rate) ** dc.tf(self.ref_date, d) + t = dc.tf(self.ref_date, d) + macaulay += t * pv + convexity += t * (1 + t) * pv + macaulay = macaulay / self.price + mod_duration = macaulay / (1. + self.rate) + convexity = (convexity / self.price) / (1. + self.rate) ** 2 + + return mod_duration, convexity \ No newline at end of file From a5b8b65e49bc653b7a9cbcf205ce201d375a2a85 Mon Sep 17 00:00:00 2001 From: PCoelho89 <44276159+PCoelho89@users.noreply.github.com> Date: Sat, 3 Apr 2021 16:55:48 -0300 Subject: [PATCH 5/6] adding param description and fixing the input checks --- finmath/brazilian_bonds/government_bonds.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/finmath/brazilian_bonds/government_bonds.py b/finmath/brazilian_bonds/government_bonds.py index cfe5e0a..78c8096 100644 --- a/finmath/brazilian_bonds/government_bonds.py +++ b/finmath/brazilian_bonds/government_bonds.py @@ -222,6 +222,7 @@ def __init__(self, :param price: bond price :param principal: bond principal :param coupon_rate: bond coupon rate + :param vna: the inflation index used for the price calculation :param ref_date: reference date for price or rate calculation """ @@ -239,7 +240,6 @@ def __init__(self, self.expiry = pd.to_datetime(expiry).date() self.ref_date = pd.to_datetime(ref_date).date() self.principal = principal - self.vna = vna interest = ((1. + coupon_rate) ** (1. / 2.) - 1.) * self.principal cash_flows = pd.Series(index=self.payment_dates(), @@ -248,25 +248,28 @@ def __init__(self, self.cash_flows = cash_flows - if price is not None and rate is not None: + if price is not None and rate is not None and vna is None: self.price = float(price) self.rate = float(rate) base_price = self.price_from_rate(principal=self.principal, rate=self.rate, vna=1000) self.vna = np.round(self.price / base_price * 1000, 6) - if rate is not None and price is None and vna is not None: + elif rate is not None and price is None and vna is not None: + self.vna = float(vna) self.rate = float(rate) self.price = self.price_from_rate(principal=self.principal, rate=self.rate, vna=self.vna) elif rate is None and price is not None and vna is not None: + self.vna = float(vna) self.price = float(price) self.rate = self.rate_from_price(price=self.price, vna=self.vna) else: - pt = self.price_from_rate(principal=self.principal, rate=rate) + pt = self.price_from_rate(principal=self.principal, rate=rate, vna=vna) if np.abs(pt - float(price)) / self.principal > 0.1: msg = 'Given price and rate are incompatible!' warnings.warn(msg) self.rate = rate self.price = price + self.vna = float(vna) self.mod_duration, self.convexity = self.calculate_risk self.macaulay = self.mod_duration * (1. + self.rate) @@ -333,4 +336,4 @@ def calculate_risk(self): mod_duration = macaulay / (1. + self.rate) convexity = (convexity / self.price) / (1. + self.rate) ** 2 - return mod_duration, convexity \ No newline at end of file + return mod_duration, convexity From 4fd77477b2f2eb6a9c1d183c354543fc526e53f1 Mon Sep 17 00:00:00 2001 From: PCoelho89 <44276159+PCoelho89@users.noreply.github.com> Date: Tue, 4 May 2021 19:21:24 -0300 Subject: [PATCH 6/6] removing principal from NTNB --- finmath/brazilian_bonds/government_bonds.py | 24 ++++++++------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/finmath/brazilian_bonds/government_bonds.py b/finmath/brazilian_bonds/government_bonds.py index 78c8096..6e33839 100644 --- a/finmath/brazilian_bonds/government_bonds.py +++ b/finmath/brazilian_bonds/government_bonds.py @@ -210,7 +210,6 @@ def __init__(self, expiry: Date, rate: Optional[float] = None, price: Optional[float] = None, - principal: float = 1e6, coupon_rate: float = 0.06, vna: Optional[float] = None, ref_date: Date = TODAY): @@ -220,7 +219,6 @@ def __init__(self, :param expiry: bond expiry date :param rate: bond yield :param price: bond price - :param principal: bond principal :param coupon_rate: bond coupon rate :param vna: the inflation index used for the price calculation :param ref_date: reference date for price or rate calculation @@ -239,32 +237,31 @@ def __init__(self, self.expiry = pd.to_datetime(expiry).date() self.ref_date = pd.to_datetime(ref_date).date() - self.principal = principal - interest = ((1. + coupon_rate) ** (1. / 2.) - 1.) * self.principal + interest = ((1. + coupon_rate) ** (1. / 2.) - 1.) * 100 cash_flows = pd.Series(index=self.payment_dates(), data=interest).sort_index() - cash_flows.iloc[-1] += self.principal + cash_flows.iloc[-1] += 100 self.cash_flows = cash_flows if price is not None and rate is not None and vna is None: self.price = float(price) self.rate = float(rate) - base_price = self.price_from_rate(principal=self.principal, rate=self.rate, vna=1000) + base_price = self.price_from_rate(rate=self.rate, vna=1000) self.vna = np.round(self.price / base_price * 1000, 6) elif rate is not None and price is None and vna is not None: self.vna = float(vna) self.rate = float(rate) - self.price = self.price_from_rate(principal=self.principal, rate=self.rate, vna=self.vna) + self.price = self.price_from_rate(rate=self.rate, vna=self.vna) elif rate is None and price is not None and vna is not None: self.vna = float(vna) self.price = float(price) self.rate = self.rate_from_price(price=self.price, vna=self.vna) else: - pt = self.price_from_rate(principal=self.principal, rate=rate, vna=vna) - if np.abs(pt - float(price)) / self.principal > 0.1: + pt = self.price_from_rate(rate=rate, vna=vna) + if np.abs(pt - float(price)) > 0.1: msg = 'Given price and rate are incompatible!' warnings.warn(msg) self.rate = rate @@ -286,25 +283,22 @@ def payment_dates(self): return sorted(payd) def price_from_rate(self, - principal: Optional[float] = None, rate: Optional[float] = None, vna: Optional[float] = None, truncate_price: bool = True) -> float: pv = 0. - principal = self.principal if principal is None else principal rate = self.rate if rate is None else rate vna = self.vna if vna is None else vna for d, p in self.cash_flows.items(): # Adjusting according the Anbima specifications - p = np.round(100 * p / principal, 6) + p = np.round(p , 6) pv += LTN(d, ref_date=self.ref_date, price=p).price_from_rate(p, rate, None, False) pv = truncate(pv, 4) * np.round(vna, 6) / 100 if truncate_price: pv = truncate(pv, 6) - # Adjusting back according to the intended principal - return pv * principal / 1000 + return pv def rate_from_price(self, price: Optional[float] = None, @@ -313,7 +307,7 @@ def rate_from_price(self, price = self.price if price is None else price vna = self.vna if vna is None else vna price /= vna - price *= self.principal + price *= 100 theor_p = lambda x: sum([ LTN(d, ref_date=self.ref_date, price=p).price_from_rate(p, x, None, False)