├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── IRB_charts.ipynb ├── README.md ├── app.py ├── hoods.zip ├── modelling.md ├── pipeline └── pipeline_irb.py ├── requirements.txt ├── tests ├── test_data_excel.xlsx ├── test_data_excel_csv.csv ├── test_data_excel_csv.xlsx └── test_utils.py ├── tox.ini └── utils ├── __init__.py ├── utils_irb.py └── utils_vasicek.py /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [3.7, 3.8] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install flake8 pytest 30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 31 | - name: Lint with flake8 32 | run: | 33 | # stop the build if there are Python syntax errors or undefined names 34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 37 | - name: Test with pytest 38 | run: | 39 | python -m pytest -rA 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /IRB_charts.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Vasicek model step by step" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Distance to default" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 3, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "from scipy.stats import norm\n", 24 | "import numpy as np\n", 25 | "import matplotlib.pyplot as plt" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "Parameters of one company:\n", 33 | "- Value of the current assets = 1,000,000\n", 34 | "- Standard deviation = 200,000\n", 35 | "- Value of the debt = 700,000" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 2, 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "data": { 45 | "image/png": "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\n", 46 | "text/plain": [ 47 | "
" 48 | ] 49 | }, 50 | "metadata": { 51 | "needs_background": "light" 52 | }, 53 | "output_type": "display_data" 54 | } 55 | ], 56 | "source": [ 57 | "mu = 1000000\n", 58 | "sigma = 200000\n", 59 | "x = np.linspace(mu - 3.09*sigma, mu + 3.09*sigma, 100)\n", 60 | "plt.plot(x, norm.cdf(x, mu, sigma))\n", 61 | "plt.xlabel(\"Value of the asset\")\n", 62 | "plt.ylabel(\"PD\")\n", 63 | "plt.show()" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 3, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "# What is the probabilty at which the value of the assets is equal to debt\n", 73 | "pd = norm.cdf(700000, mu, sigma)\n", 74 | "\n", 75 | "# Use PPF - Percent point function (inverse of cdf — percentiles) to conver PD to distance to default\n", 76 | "distance_to_default = norm.ppf(pd)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 4, 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "name": "stdout", 86 | "output_type": "stream", 87 | "text": [ 88 | "0.06680720126885807 is equivalent to -1.5000000000000004 distance to default in std\n" 89 | ] 90 | } 91 | ], 92 | "source": [ 93 | "print(pd,\"is equivalent to\",distance_to_default, \"distance to default in std\") \n", 94 | "# this is equivalent to a -1.5 std from the mean" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 5, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "text/plain": [ 105 | "3.090232306167813" 106 | ] 107 | }, 108 | "execution_count": 5, 109 | "metadata": {}, 110 | "output_type": "execute_result" 111 | } 112 | ], 113 | "source": [ 114 | "distance_from_economy = norm.ppf(0.999) # distance for worst economic level\n", 115 | "distance_from_economy" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "## Distance to default downturn\n", 123 | "DistanceToDefaultDownturn = (1-r)^-0.5 X DistanceToDefault + (r/(1-r))^0.5 X DistanceFromEconomy" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 6, 129 | "metadata": {}, 130 | "outputs": [ 131 | { 132 | "data": { 133 | "text/plain": [ 134 | "-1.4071247279470294" 135 | ] 136 | }, 137 | "execution_count": 6, 138 | "metadata": {}, 139 | "output_type": "execute_result" 140 | } 141 | ], 142 | "source": [ 143 | "rho = 0.12\n", 144 | "distance_to_default_downturn = (np.sqrt(1/1-rho)*distance_to_default) + (np.sqrt(rho/1-rho)*distance_from_economy)\n", 145 | "distance_to_default_downturn" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 7, 151 | "metadata": {}, 152 | "outputs": [ 153 | { 154 | "data": { 155 | "text/plain": [ 156 | "0.07969520338701691" 157 | ] 158 | }, 159 | "execution_count": 7, 160 | "metadata": {}, 161 | "output_type": "execute_result" 162 | } 163 | ], 164 | "source": [ 165 | "pd_downturn = norm.cdf(distance_to_default_downturn)\n", 166 | "pd_downturn" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 8, 172 | "metadata": {}, 173 | "outputs": [ 174 | { 175 | "data": { 176 | "text/plain": [ 177 | "0.01288800211815884" 178 | ] 179 | }, 180 | "execution_count": 8, 181 | "metadata": {}, 182 | "output_type": "execute_result" 183 | } 184 | ], 185 | "source": [ 186 | "impact = pd_downturn - pd\n", 187 | "impact" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "# Asset correlation" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 9, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "from utils.utils import get_rho_asset_correlation\n", 204 | "import numpy as np\n", 205 | "import matplotlib.pyplot as plt\n", 206 | "import matplotlib.ticker as ticker" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 10, 212 | "metadata": {}, 213 | "outputs": [], 214 | "source": [ 215 | "PD_list = np.arange(0.0, 0.11, 0.001)" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": 11, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "rho_list = []\n", 225 | "for p in PD_list:\n", 226 | " rho_list.append(get_rho_asset_correlation(p))" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": 12, 232 | "metadata": {}, 233 | "outputs": [ 234 | { 235 | "data": { 236 | "text/plain": [ 237 | "" 238 | ] 239 | }, 240 | "execution_count": 12, 241 | "metadata": {}, 242 | "output_type": "execute_result" 243 | }, 244 | { 245 | "data": { 246 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgsAAAGDCAYAAAClVFPmAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvOIA7rQAAIABJREFUeJzs3Xl4XHXZ//H3na3plnRJuqb7XmhL29AWWlqgiC2yieyUTRR9eERReRR/4gb6KIuiLCKyqIUHBKuyCQIiW4ECKXShLd23dN/TPU1y//44JziELNM0kzOTfF7Xda7MfM8y95mZzHzmnO85x9wdERERkZqkRV2AiIiIJDeFBREREamVwoKIiIjUSmFBREREaqWwICIiIrVSWBAREZFaKSyINFNmdoWZzTyC+Z83s8sbsqY4HrOFmS00s66HOV+ttZrZH83sp0deYcMxs55mtsfM0iOs4cdm9kiy1JMIZnatmd0SdR3JTmFBUoqZvWpmX4q6jniYWW8zczPLiLqWIxX7pVHJ3ae6+58auZSrgdfdfcPhzBRb65GGpMNxJO8Bd1/j7m3cvTwRtR2uhqonyv9hMzvRzIqrNN8PXGJmnaKoKVUoLEi9NfSXYFP4Uo2CBdLqamsivgo8HHUR0nS4+wHgeeCyqGtJau6uoRkOQA/gb8AWYBtwd9ieBtwIrAY2A9OB3HBcb8CBq4A1wOsxbVcD64ENwPUxj9MC+HU4bn14u0U47kSgGPgusJHgS6A98GxY147wdkE4/c+AcuAAsCem5sHAS8B2YDFwfi3rfSWwCNgNrAC+EjMuL3y8neGy3gDSwnHfBdaF8y0GJsc8XzcAy8Pn8QmgQzhuTfjc7AmH46qpJx34f+H8u4HZQI9w3PHAe8Cu8O/xMfO9Gj4fbwL7gf41tOUCD4avyzrgp0B6uIwrgJkxy/wNsBYoCes4IWyfApQCh8L1mBtTw5cO431zeficbAW+H/O4Y4Ci8HE3Ab+q4bXrGa5XRni/T/haVb5G9wObY6Z/GLgutlZgCMH7pzxcl53h+D8C9wD/CF+Hd4B+Mcuq7bVYBZwSc//HwCOH8R6odv1jnrcM4LiYZewJ12FVXe/Bah6rxv+vmOf0tfA5eAm4O2ZdPq4njvXOBh4J69kZPmedqfl/2IFrgKXhY98M9APeCp+XJ4CsmMc6HZgTLvstYHiV1+N6YF74ej0e1tOa4P1TEfM8dgvnuQR4JerP5WQeIi9AQwQvevAFNRe4I/wHygYmhOO+CCwD+gJtCALFw+G4yg+L6eF8LWPaHgvbhoUfRKeE89wEzAI6AfnhP/bN4bgTgTLgFoJQ0RLoCHwBaAW0Bf4CPBlT+6uEX1Dh/dYEX3BXEnyojiT4Mhpaw7p/LvwQMmASsA8YFY77OfA7IDMcTginGxQ+RreY56FfePsb4foVhOtwH/BYlecro5bX4n+A+eFjGDAifA46EHyYXxqu10Xh/Y4xz8Ma4KhwfGYNbX8Pa2odvgbvEgYkPh0WpoWPnQF8myDAZYfjfkz4RVDda0F875v7w9d4BHAQGBKOfxu4NLzdBhhXy2u3oErbGmB0eHsxQQAcEjNuZDW1fmK9w7Y/EnyxjQnX//+AP4fj6notVlHzl2Y874Fq17+mecPX9TXg53W9B6t5rLr+v94GfhUuZyLBF3d9wsJXgGfCx0kHRgM51f0Ph20OPAXkELx/DwIvE7yfcoGFwOXhtCMJAunYcNmXh7W0iKnrXaBb+NotAr4a85lTXM3zMgrYHvVnczIPkRegIYIXPfiVsqW6D7DwH/SamPuDCH5RZsR8WPSNGV/ZNjim7VbgwfD2cuC0mHGf5T+/iE4k+MWaXUutxwA7Yu5/4oMGuAB4o8o89wE/ivO5eBL4Rnj7pvADq3+VafqHH06nAJlVxi0i3MoQ3u9azfNV2xfFYuCsatovBd6t0vY2cEXM83BTlfGfaCP4JXcQaBnTdhHhLyiq+dKssrwdwIjw9o+pPSzE876J/QX7LnBhePt14CdAXh2v1SXArCptDwPfArqEz+WtBLsqqm51iK31U+tNEBYeiLl/GvBRnK/FKo4sLFS7/jXNC9xLsEWgct1qfA/G8f7/+P+LYMtNGdA6ZvyjNa1LHev9Rar84q/pfzhsc2B8zP3ZwHdj7v8S+HXM+t9czf/RpJi6psWMuxX4XXj7RKoPCwOA8rqer+Y8NMV9mlK3HsBqdy+rZlw3gk3JlVYTfOB3jmlbW818sW2rw+XUtLxuMfe3eLDPEAAza2Vm95nZajMrIfggbVdLD+xewFgz21k5EHypdKluYjObamazzGx7OO1pBLsfAG4j+HX8opmtMLMbANx9GXAdwYfhZjP7s5lVrkMv4O8xj72IYDNr7PNVmx4Egaqqqs8b4f3uMffreh16EfwK3RBT330EWxg+xcyuN7NFZrYrnDaX/zw3dYnnfbMx5vY+gl/REOzWGgh8ZGbvmdnpNTzGDoJfw7FeI/gCmEjwXnmVYIvRJIIQWRFn/bXVF89rcSTiXX/M7CsE63txzLrF/R6s4/+rG0Fw2BszS9X1jtfDwAvAn81svZndamaZdcyzKeb2/mruV74evYBvV/mf78EnP1dqei1r0pZgl4XUQGGheVoL9KyhQ+F6gn/GSpW/NmL/cb2a+XpUmWd9LctbH3O/6rK+TfCrdKy75xB8CUCwib666dcCr7l7u5ihjbv/V9UCzawF8FfgdqCzu7cDnqtctrvvdvdvu3tf4EzgW2Y2ORz3qLtPCNfFCXadVD7+1CqPn+3u62p4nqpaS7BbpKqqzxsEz926mPvVLT+2bS3BloW8mNpy3P2oqjOZ2QnAd4Dzgfbhc7OLmp/3uuqt7n1TLXdf6u4XEYSYW4AZZta6mknnAX2qvG9fI9hddGJ4eyYwniAsvFbTQ9ZVUxV1vRZ7CTa3V4oNqnU+VrzrH75GNxNsiSqJGVXbe7Cq2v6/NgDtqzx2z1pKr3G93f2Qu//E3YcS9Pc4nf90IDzc57+qtcDPqqxvK3d/LI55a3rsIQS7ZqUGCgvN07sEHwy/MLPWZpZtZuPDcY8B3zSzPmbWBvhf4PEatkLE+kH4q+Uogv4Dj8cs70YzyzezPOCHBB2fatKW4FfETjPrAPyoyvhNBPsxKz0LDDSzS80sMxyONbMh1Sw7i2Bf7BagzMymAqdWjjSz082sv5kZwRdlOVBhZoPM7OQwbBzgP52kIOjj8DMz6xUuI9/MzgrHbQmni623qgeAm81sQHgEw3Az60gQYgaa2cVmlmFmFwBDw/WNiweHF74I/NLMcswszcz6mdmkaiZvS/DlvgXIMLMfEuw/rrQJ6F3LERb1fd9gZtPMLD/8pbwzbP7UFgF3LybY8jMmpm0pwesxjSA0VnYS/AI1h4VNQIGZZdVVW6iu12IOcGH43isEzo2Zt873QDzrb2Y9CDr5XebuS6osorb3YFU1/n+5+2qCjpY/MbMsM5sAnFFT3bWtt5mdZGbDwi0WJQS7RSrXqer/8OG6H/iqmY0N/2dam9nnzKzqVqfqbAI6mllulfZJBEdESA0UFpohD46TPoNgX/wagiMSLghHP0SwCfF1YCXBl+O1cSz2NYIP8peB2939xbD9pwQfQPMIOvK9H7bV5NcEneC2EnTa+meV8b8BzjWzHWZ2p7vvJvjCv5DgF+BG/tNhsup67wa+TvChuwO4GHg6ZpIBwL8Iekm/DfzW3V8Jl/WLsKaNBL8AvxdTz9MEuy52hzWPDR9vH+HRCeHm0nHVrO+vwnpeJPhQfZCgj8E2gl9j3yboePcd4HR331rLc1edywhC0sJwnWcQ7NOu6gWC53oJwabnA3xyl8Zfwr/bzOz9auav7/sGgqMtFpjZHoLn80J331/DtPcR9CGI9Rqwzd3Xxtw3gvdadf4NLAA2mlmdz2ccr8UPCLYO7SDoe/BozLzxvAfiWf/JBLsVZlhwYqQ9ZrYgHFfje7Aadf1/XRzOu50gSEyvYTm1rjfBVoYZBO/pRQSvSeUhr5/4H65l+dVy9yLgywRHauwg+Ny5Is55PyIItivC16ObmWUT7I5s7HOGpBRzP9ItQtKcmVlvgi+HzHh+RYociXDrzgcEHfoO68RMcmTMrC9BmMz0JvTFYWbXEhyu/J2oa0lmOgmOiKQMdz9IsAtAGt/RBB2jm0xQAHD3u6KuIRVoN4SIiNTKzL4F/J7g5E/SDGk3hIiIiNRKWxZERESkVgoLIiIiUit1cAzl5eV57969oy5DRESkUcyePXuru+fHM63CQqh3794UFRVFXYaIiEijMLO4T+et3RAiIiJSK4UFERERqZXCgoiIiNRKYUFERERqpbAgIiIitVJYEBERkVopLIiIiEitFBZERESkVgoLIiIiUquEhgUzm2Jmi81smZl96tKmZvYtM1toZvPM7GUz61VlfI6ZFZvZ3TFto81sfrjMO83MwvZbwuVMj5l2mpldl8h1FBERaeoSFhbMLB24B5gKDAUuMrOhVSb7ACh09+HADODWKuNvBl6v0nYv8GVgQDhMMbNcYFS4nFIzG2ZmLYErwxpERESknhJ5bYgxwDJ3XwFgZn8GzgIWVk7g7q/ETD8LmFZ5x8xGA52BfwKFYVtXIMfdZ4X3pwNnAzOBzHArQyvgEHA9cJe7H4qr2sWL4cQT67Oen3KovIKSA2V0aJ2FNcgSRUREopPI3RDdgbUx94vDtppcBTwPYGZpwC8JvvCrLrO46jLdfTfwHMGWig3ALmCsuz9ZW4FmdrWZFZlZ0aFD8WWKeGzfW8rSTbs5cKi8wZYpIiISlaS46qSZTSPYejApbLoGeM7di8MuCXVy91sJd2OY2QPAD83sS8CpwDx3/2k18/we+D1AYWGh8+qrR7gmgX1b93Lh7a/y07OPZtq4XnXPICIi0tji/H6FxG5ZWAf0iLlfELZ9gpmdAnwfONPdD4bNxwFfM7NVwO3AZWb2i3D+gtqWaWYjAQMWA+e5+/lAPzMb0BArFY/eHVvRJSebt1dsa6yHFBERSZhEbll4DxhgZn0IvtAvBC6OnSD8Yr8PmOLumyvb3f2SmGmuIOgEeUN4v8TMxgHvAJcBd1V53JuBq4FMID1sqyDoy9AozIzj+3XktSVbcHfi3ToiIiKSjBK2ZcHdy4CvAS8Ai4An3H2Bmd1kZmeGk90GtAH+YmZzzOzpOBZ9DfAAsAxYTtjPAcDMzgaK3H29u+8E5pjZfCDb3ec22MrFYVy/jmzbW8qSTXsa82FFREQanLl71DUkhcLCQi8qKmqw5RXv2MeEW17hR2cM5crxfRpsuSIiIg3BzGa7e2E80+oMjglS0L4VPTu04q3l6rcgIiKpTWEhgY7r25F3VmyjvEJbb0REJHUpLCTQ8f07UnKgjIXrS6IuRUREpN4UFhLouL4dAXh7xdaIKxEREak/hYUE6pSTTb/81uq3ICIiKU1hIcGO75fHeyu3c6i8IupSRERE6kVhIcGO69eRvaXlzCveFXUpIiIi9aKwkGDjKvstLFe/BRERSU0KCwnWoXUWQ7rmqN+CiIikLIWFRnB8v44Urd6hS1aLiEhKUlhoBBMG5FFaVsG7K7dHXYqIiMhhU1hoBGP7dCArPY2Zy9RvQUREUo/CQiNolZXBqF7teGOpwoKIiKQehYVGcsKAfBZtKGHL7oNRlyIiInJYFBYayYT+eQC8pUMoRUQkxSgsNJKju+eS2zJTuyJERCTlKCw0kvQ0Y3z/jsxcuhV3XbJaRERSh8JCI5rQP5+NJQdYvmVP1KWIiIjETWGhEZ0wIOi3oF0RIiKSShQWGlGPDq3o1bEVMxUWREQkhSgsNLIJ/fOYtWKbLlktIiIpQ2GhkZ0wII+9peV8sGZn1KWIiIjERWGhkR3XL480g5lLt0RdioiISFwUFhpZbstMjunRjteWKCyIiEhqUFiIwImDOjFv3S627dGpn0VEJPkpLERg0sB83HUIpYiIpAaFhQgM655Lh9ZZ2hUhIiIpQWEhAmlpxsQBeby+ZAsVFTr1s4iIJDeFhYicOKgT2/aW8uH6XVGXIiIiUiuFhYicMCAPM3h1sXZFiIhIclNYiEjHNi0Y3j1X/RZERCTpKSxEaNKgTnywZgc795VGXYqIiEiNFBYiNGlgPhUOM5fpEEoREUleCgsROqZHO3JbZqrfgoiIJDWFhQilpxknDMjjtSVbcNchlCIikpwUFiJ24qBObNl9kIUbSqIuRUREpFoKCxGbNDAfgFc+2hxxJSIiItVTWIhYftsWjOjRjpcVFkREJEkpLCSByYM7MWftTrbqKpQiIpKEFBaSwOQhnXDXrggREUlOCgtJYGjXHLrkZPNvhQUREUlCCQ0LZjbFzBab2TIzu6Ga8d8ys4VmNs/MXjazXmH7MWb2tpktCMddEDNPHzN7J1zm42aWFbZfa2YfmtlzMW0TzOyORK5jQzAzTh7SideXbKG0rCLqckRERD4hYWHBzNKBe4CpwFDgIjMbWmWyD4BCdx8OzABuDdv3AZe5+1HAFODXZtYuHHcLcIe79wd2AFeF7ZcAw4G3gM+amQE/AG5OxPo1tMmDO7G3tJx3Vm6LuhQREZFPSOSWhTHAMndf4e6lwJ+Bs2IncPdX3H1feHcWUBC2L3H3peHt9cBmID8MACcTBAuAPwFnh7cNyARaAYeAacDz7r49QevXoMb3zyM7M42XF2lXhIiIJJdEhoXuwNqY+8VhW02uAp6v2mhmY4AsYDnQEdjp7mXVLPNugsDRE3gTuJJgy0aNzOxqMysys6ItW6I95XJ2Zjrj++Xx8kebdDZHERFJKknRwdHMpgGFwG1V2rsCDwNXunutO/Pd/WF3H+nu04BvAncCU81shpndYWafWld3/727F7p7YX5+foOtT32dPKQTa7fvZ9nmPVGXIiIi8rFEhoV1QI+Y+wVh2yeY2SnA94Ez3f1gTHsO8A/g++4+K2zeBrQzs4yalmlm3YAx7v4k8G3gAmAnMLkhViqRTh7cCUAnaBIRkaSSyLDwHjAgPHohC7gQeDp2AjMbCdxHEBQ2x7RnAX8Hprt7Zf8EPNg+/wpwbth0OfBUlce9GfhheLsl4EAFQV+GpNY1tyVHdcvh3+q3ICIiSSRhYSHsV/A14AVgEfCEuy8ws5vM7MxwstuANsBfzGyOmVWGifOBicAVYfscMzsmHPdd4FtmtoygD8ODlY8Zhg/c/f2w6VFgPjAe+Gei1rUhTR7ciaLV29m+tzTqUkRERAAwdaYLFBYWelFRUdRlML94F2fcPZPbzh3OeYU96p5BRESkHsxstrsXxjNtUnRwlP84unsO3XKzeXHhpqhLERERARQWko6ZcepRXXhj6Rb2l5ZHXY6IiIjCQjI6dWhnDhyq4PWl0Z77QUREBBQWktKxfTqQ2zKTFxdoV4SIiERPYSEJZaanMXlwJ17+aBNl5bqwlIiIREthIUmdelRndu47xHurdkRdioiINHMKC0lq4sB8WmSk8cKCjVGXIiIizZzCQpJqlZXBCQPyeGmhLiwlIiLRUlhIYqcO7cK6nftZsL4k6lJERKQZU1hIYpOHdCLN0AmaREQkUgoLSaxjmxYU9urAPz/cEHUpIiLSjCksJLmpw7qwZNMelm3eE3UpIiLSTCksJLkpR3cB0NYFERGJjMJCkuua25LRvdrzj/k6hFJERKKhsJACph7dhUUbSli5dW/UpYiISDOksJACpg7rCsDz2hUhIiIRUFhIAd3btWREj3Y8r10RIiISAYWFFPG5YV2Yv24Xa7bti7oUERFpZhQWUsTUo7UrQkREoqGwkCJ6dGjFsO65PPehdkWIiEjjUlhIIacN68rctTsp3qFdESIi0ngUFlLI1PAETeroKCIijUlhIYX0zmvNsO65PDNvfdSliIhIM6KwkGLOGNGVecW7WKUTNImISCNRWEgxpw/vBsAzc7V1QUREGofCQorp1q4lY3p30K4IERFpNAoLKeiMEV1ZsmkPH20siboUERFpBhQWUtBpw7qSnmbaFSEiIo1CYSEFdWzTgvH983hm7gbcPepyRESkiVNYSFFnDO/Kmu37mLN2Z9SliIhIE6ewkKI+e3QXsjLSeGaurhUhIiKJpbCQonKyMzlpUD7PzltPeYV2RYiISOIoLKSwM0d0Z/Pug7yzYlvUpYiISBOmsJDCJg/pRNsWGfztg3VRlyIiIk2YwkIKy85M57RhXXl+/gb2l5ZHXY6IiDRRCgsp7vOjurO3tJwXF+pKlCIikhhxhwUza29mR5lZXzNTyEgSY3p3oHu7lvxduyJERCRBav3SN7NcM/t/ZjYfmAXcBzwBrDazv5jZSY1RpNQsLc04e2Q3Xl+yhc27D0RdjoiINEF1bSGYAawFTnD3Qe4+wd0L3b0H8AvgLDO7KuFVSq0+P7KACoen5+j0zyIi0vAyahvp7p+pZdxsYHaDVySHrX+nNgwvyOXvH6zjSyf0jbocERFpYg6nz8KZZnZ7OJwR5zxTzGyxmS0zsxuqGf8tM1toZvPM7GUz6xUz7nIzWxoOl8e0jzaz+eEy7zQzC9tvCZczPWbaaWZ2XbzrmMo+P7I7C9aXsGTT7qhLERGRJiausGBmvwC+ASwMh6+b2f/WMU86cA8wFRgKXGRmQ6tM9gFQ6O7DCXZ53BrO2wH4ETAWGAP8yMzah/PcC3wZGBAOU8wsFxgVLqfUzIaZWUvgyrCGJu+MEd1ITzP+9r46OoqISMOKd8vCacBn3P0hd38ImAKcXsc8Y4Bl7r7C3UuBPwNnxU7g7q+4+77w7iygILz9WeAld9/u7juAlwhCQVcgx91neXC5xenA2UAFkBluZWgFHAKuB+5y90NxrmNKy2vTghMH5vPkB+t0+mcREWlQh3MIZLuY27lxTN+doHNkpeKwrSZXAc/XMW/38PYn2t19N/AcwZaKDcAuYKy7PxlHnU3GuaML2FhygJnLtkZdioiINCG1dnCM8XPgAzN7BTBgIvCpPgj1ZWbTgEJgUn2X4e638p/dGA8APzSzLwGnAvPc/afVPO7VwNUAPXv2rO9DJ43JQzrToXUWTxStZdLA/KjLERGRJiKuLQvu/hgwDvgb8FfgOHd/vI7Z1gE9Yu4XhG2fYGanAN8HznT3g3XMu47/7KqodplmNpIg0CwGznP384F+ZjagmvX6fXgoaGF+fup/uWZlpHH2Md15acEmduwtjbocERFpIg5nN0QasBXYCQw0s4l1TP8eMMDM+phZFnAh8HTsBOEX+30EQWFzzKgXgFPDs0a2J9g68IK7bwBKzGxc2D/hMuCpKo97M/ADIBNID9sqCPoyNHnnH1tAaXkFT81RR0cREWkYce2GMLNbgAuABQRfvAAOvF7TPO5eZmZfI/jiTwcecvcFZnYTUOTuTwO3AW2Av4RHQK5x9zPdfbuZ3UwQOABucvft4e1rgD8CLQn6OFT2c8DMzg6XvT68Pyc8++Q8d58bz7qmusFdchjWPZcnioq5YnyfqMsREZEmwIKDCuqYyGwxMDxmN0GTU1hY6EVFRVGX0SAefnsVP3hqAc9eO4Gju8fTF1VERJobM5vt7oXxTBvvbogVBJv1JQWcOaI7WRlp/KVobd0Ti4iI1KHW3RBmdhfB7oZ9wBwzexn4eOuCu389seVJfeS2ymTKUV14cs56vnfaELIz0+ueSUREpAZ19Vmo3C4/myqdEyW5nV/Yg6fnruelhZs4Y0S3qMsREZEUVteFpP5U0zgzG9/w5UhDOb5fR7q3a8nj761VWBARkSNSa58FM0s3s4vM7HozOzpsO93M3gLubpQKpV7S0owLj+3BzGVbWbV1b9TliIhICqurg+ODwJeAjsCdZvYIcDtwq7uPTHRxcmTOP7YH6WnGY++tiboUERFJYXX1WSgkOGSywsyygY1AP3fflvjS5Eh1zslm8uBOzCgq5tufGURWxuGcg0tERCRQ17dHqbtXALj7AWCFgkJquXhsT7btLeWFBRujLkVERFJUXWFhsJnNC4f5Mffnm9m8xihQjszEAfkUtG/Jo+9oV4SIiNRPXbshhjRKFZIwaWnGRWN6ctsLi1mxZQ9989tEXZKIiKSYurYsrHH31TUNAOEFnSSJnVdYQEaa8ef3dEZHERE5fHWFhVfM7Foz6xnbaGZZZnaymf0JuDxx5UlD6NQ2m88M7cyM2cUcLCuPuhwREUkxdYWFKUA58JiZrTezhWa2AlgKXAT82t3/mOAapQFcPLYn2/eW8vx8dXQUEZHDU9cZHA8AvwV+a2aZQB6w3913NkZx0nDG98ujT15rpr+9irNHdo+6HBERSSFxH3jv7ofcfYOCQmpKSzMuHdeL99fsZH7xrqjLERGRFKKz9DQj5xYW0Cornelvr4q6FBERSSEKC81ITnYm54zqzlNz17N9b2nU5YiISIpQWGhmLjuuN6VlFTyuwyhFRCROcYUFMzvHzJaa2S4zKzGz3WZWkujipOEN7NyW4/p25JFZqymv8KjLERGRFBDvloVbgTPdPdfdc9y9rbvnJLIwSZzLj+/Nup37eXnRpqhLERGRFBBvWNjk7osSWok0mlOGdKJbbjbT314ddSkiIpIC4g0LRWb2uJldFO6SOMfMzkloZZIwGelpXDKuFzOXbWXJpt1RlyMiIkku3rCQA+wDTgXOCIfTE1WUJN7FY3qSnZnGQzNXRl2KiIgkubquOgmAu1+Z6EKkcbVvncUXRhXwl9nFXP/ZQeS1aRF1SSIikqTiPRqiwMz+bmabw+GvZlaQ6OIksb44oQ+lZRU8Mkt9F0REpGbx7ob4A/A00C0cngnbJIX1y2/DyYM78fDbqzlwSFejFBGR6sUbFvLd/Q/uXhYOfwTyE1iXNJIvTejDtr2lPDVnXdSliIhIkoo3LGwzs2lmlh4O04BtiSxMGsdx/ToypGsOD85cibtO0iQiIp8Wb1j4InA+sBHYAJwLqNNjE2BmXDWhD0s27eGNpVujLkdERJJQXGHB3Ve7+5nunu/undz9bHdfk+jipHGcMaIr+W1bcP8bK6IuRUREklCth06a2Xfc/VYzuwv41DZqd/96wiqTRtMiI50rju/NbS8s5sN1uzi6e27UJYmISBKpa8tC5Smei4DZ1QzSREwb14vFeE8NAAAgAElEQVQ2LTK473VtXRARkU+qNSy4+zPhzX3u/qfYgeCMjtJE5LbM5JKxPfnHvPWs3rY36nJERCSJxNvB8XtxtkkK++KEPmSkpanvgoiIfEJdfRamAqcB3c3szphROUBZIguTxtc5J5tzRnXniaJivjF5IPltdQpoERGpe8vCeoL+Cgf4ZF+Fp4HPJrY0icLVE/tyqLyCP76lC0yJiEig1i0L7j4XmGtmj7r7oUaqSSLUN78NU4/uwvS3V/PVSf1om50ZdUkiIhKxePss9DazGWa20MxWVA4JrUwi89VJ/dh9oIz/e0en0hARkcO7kNS9BP0UTgKmA48kqiiJ1vCCdpwwII8H3ljB/lJdYEpEpLmLNyy0dPeXAQvP5vhj4HOJK0ui9vXJA9i6p5RH39XWBRGR5i7esHDQzNKApWb2NTP7PNAmgXVJxI7t3YHj+nbkd68t1+WrRUSauXjDwjeAVsDXgdHApcDldc1kZlPMbLGZLTOzG6oZP9HM3jezMjM7t8q4W81sgZktMrM7zczC9tFmNj9cZmz7LWY2z8ymxyxjmpldF+c6ShVfnzyALbsP8vh7a6MuRUREIhTvhaTec/c97l7s7le6+znuPqu2ecwsHbgHmAoMBS4ys6FVJlsDXAE8WmXe44HxwHDgaOBYYFI4+l7gy8CAcJhiZrnAKHcfDpSa2TAza0lwZcx74llH+bRxfTtwbO/2/O615Rws09YFEZHmqq6TMj1DNReQquTuZ9Yy+xhgmbuvCJf1Z+AsYGHM/KvCcRVVFw1kA1mAAZnAJjPrCuRUBpVwK8LZwEwgM9zK0Ao4BFwP3KVDPuvPzPj65AFc+uC7zJhdzCVje0VdkoiIRKDWsADcfgTL7g7Ebr8uBsbGM6O7v21mrwAbCMLC3e6+yMwKw+XELrO7u+82s+eAD4CXgV3AWHe/ubbHMbOrgasBevbsGd9aNTMT+ucxsmc7fvvKcs4b3YOsjHj3XImISFNR14WkXqscgHeBjVXaEsLM+gNDgAKC0HGymZ1QR623uvsx7v5t4Gbgh2b2JTN7wsxurGGe37t7obsX5ufnN/RqNAmVWxfW7dzPjNnFdc8gIiJNTlw/E83sDGAO8M/w/jFm9nQds60DesTcLwjb4vF5YFbYT2IP8DxwXDh/QW3LNLORBFsjFgPnufv5QD8zGxDnY0sVJw7MZ2TPdtz176U6MkJEpBmKd5vyjwn6IOwEcPc5QJ865nkPGGBmfcwsC7iQ4JoS8VgDTDKzDDPLJOjcuMjdNwAlZjYu7J9wGfBUlXlvBn5A0M8hPWyrIOjLIPVgZlx/6iA27DrAYzrvgohIsxNvWDjk7ruqtNXY8RHA3cuArwEvAIuAJ9x9gZndZGZnApjZsWZWDJwH3GdmC8LZZwDLgfnAXGCuuz8TjrsGeABYFk7zfOVjmtnZQJG7r3f3ncAcM5sPZIfXuZB6Or5fR8b17cA9ryxnX6kuOCoi0pyYe63f+cFEZg8SdBy8AfgCwfkWMt39q4ktr/EUFhZ6UVFR1GUktaJV2zn3d29zw9TBfHVSv6jLERGRI2Bms929MJ5p492ycC1wFHCQ4JwIuwCd7KiZKezdgRMH5fO715az+4COSBURaS7qDAvhyZVucvfvu/ux4XCjux9ohPokyXz7M4PYue8QD81cFXUpIiLSSOoMC+5eDkxohFokBQwryOWzR3XmgTdWsH1vadTliIhII4h3N8QHZva0mV1qZudUDgmtTJLW9acOYm9pGXf/e1nUpYiISCOINyxkA9uAk4EzwuH0RBUlyW1A57acX9iDh2etYu32fVGXIyIiCRZvn4V54QWkYocvNkJ9kqS++ZmBpKcZt7+4OOpSREQkweLts3BRI9QiKaRzTjZXTejDU3PW8+G6qqfgEBGRpiTe3RBvmtndZnaCmY2qHBJamSS9r0zqR/tWmfzi+Y+iLkVERBKorqtOVjom/HtTTJsT9GGQZionO5NrTx7ATc8u5PUlW5g4UBfjEhFpiuIKC+5+UqILkdR0ybie/OGtlfzvc4sY3z+P9DSLuiQREWlg8V51MtfMfmVmReHwSzPLTXRxkvxaZKTzvalD+Gjjbp4oWht1OSIikgDx9ll4CNgNnB8OJcAfElWUpJapR3dhTO8O3P7CYkp0GmgRkSYn3rDQz91/5O4rwuEnQN9EFiapw8z4welD2b6vlHte0YmaRESamnjDwn4z+/iUz2Y2HtifmJIkFQ0ryOXcUQX8YeYqVm/bG3U5IiLSgOINC18F7jGzVWa2Crg7bBP52P98dhAZ6cbPn9OhlCIiTUlcYcHd57r7CGA4MNzdR7r73MSWJqmmU042/31Sf/65YCNvLd8adTkiItJAag0LZvYtM7uq8r67l7h7iZldZWbXJb48STVXTehDjw4t+fHTCzhUXhF1OSIi0gDq2rJwCTC9mvaHAV0bQj4lOzOdH55+FEs27eFPb62KuhwREWkAdYWFDHf/1LFw7l4K6Ow7Uq1ThnTi5MGduOOlJWwqORB1OSIicoTqCgtpZta5amN1bSKVzIwfnTGUQxXO/z63KOpyRETkCNUVFm4D/mFmk8ysbTicCDwL3J7w6iRl9erYmq9O6sdTc9bz9vJtUZcjIiJHoNaw4O7TgR8QXEBqFbAS+AnwQ3f/U8Krk5R2zYn9KGjfkh89/aE6O4qIpLA6D5109+fdfZK7d3T3vPD2841RnKS27Mx0fnxG0Nnx/jdWRF2OiIjUU7wnZRKpl1OGdmbKUV34zb+W6syOIiIpSmFBEu7HZx5FZnoaNz75Ie4edTkiInKY4r1EdZ942kSq0yU3m+9OGcQbS7fy5Jx1UZcjIiKHKd4tC3+tpm1GQxYiTdslY3sxsmc7bn52ETv2lkZdjoiIHIa6Tvc82My+AOSa2TkxwxVAdqNUKE1CWprx83OGUbL/ED/9h869ICKSSurasjAIOB1oB5wRM4wCvpzY0qSpGdwlh69M6stf3y/m1cWboy5HRETiZPF0ODOz49z97UaoJzKFhYVeVFQUdRlN3sGyck6/cyZ7DpbxwjcnkpOdGXVJIiLNkpnNdvfCeKaNt8/CNjN72cw+DB9guJndWO8KpdlqkZHObeeNYFPJAX6uU0GLiKSEeMPC/cD3gEMA7j4PuDBRRUnTdkyPdnx5Yl8ee3ctry/ZEnU5IiJSh3jDQit3f7dKW1lDFyPNxzdPGUi//NZ872/z2X3gUxc2FRGRJBJvWNhqZv0ABzCzc4ENCatKmrzszHRuPXcE63ft52c6OkJEJKnFGxb+G7gPGGxm64DrgP9KWFXSLIzu1Z6rJ/blz++t5V8LN0VdjoiI1CCusODuK9z9FCAfGOzuE9x9VUIrk2bhW58ZyJCuOdzwt3ls3XMw6nJERKQa8Z7u+RtmlgPsA+4ws/fN7NTElibNQYuMdH59wTGU7C/jhr/O17UjRESSULy7Ib7o7iXAqUBH4FLgFwmrSpqVQV3a8p0pg/jXok08UbQ26nJERKSKeMOChX9PA6a7+4KYNpEj9sXxfTiub0d+8sxCXcpaRCTJxBsWZpvZiwRh4QUzawtUJK4saW7S0oxfnj+CjDTj2sc+oLRMby8RkWQRb1i4CrgBONbd9wGZwJUJq0qapW7tWnLrucOZV7yL219cHHU5IiISijcsHAcsdvedZjYNuBHYVddMZjbFzBab2TIzu6Ga8RPDzpJl4bkbYsf1NLMXzWyRmS00s95hex8zeydc5uNmlhW2X2tmH5rZczFtE8zsjjjXUZLAlKO7cum4Xvz+9RW8ootNiYgkhXjDwr3APjMbAXwbWA5Mr20GM0sH7gGmAkOBi8xsaJXJ1gBXAI9Ws4jpwG3uPgQYA1R+c9wC3OHu/YEdBFs9AC4BhgNvAZ81MwN+ANwc5zpKkvj+54YwuEtbrn9iLptLDkRdjohIsxdvWCjz4Ji2s4C73f0eoG0d84wBloXnaCgF/hzO/zF3XxVeZ+ITO6jDUJHh7i+F0+1x931hADgZmBFO+ifg7MrZCHaPtCK4hsU04Hl33x7nOkqSyM5M5+6LR7KvtJzrHp9DeYUOpxQRiVK8YWG3mX2P4Av4H2aWRvDFXJvuQOxxcMVhWzwGAjvN7G9m9oGZ3RZuqegI7HT3yutSxC7zbmAW0BN4k6BPxT21PYiZXW1mRWZWtGWLLmiUTPp3astPzjqKt5Zv4zf/WhJ1OSIizVq8YeEC4CBwlbtvBAqA2xJWFWQAJwDXA8cCfQl2V9TI3R9295HuPg34JnAnMNXMZpjZHWHAqTrP79290N0L8/PzG3wl5MicX9iD80YXcOe/l6n/gohIhOI93fNGd/+Vu78RNvUCxtYx2zqgR8z9grAtHsXAnHAXRhnwJDAK2Aa0M7OMmpZpZt2AMe7+JEH/iguAncDkOB9bksjNZx/NkK45fPPxORTv2Bd1OSIizVK8WxYws5Hh7oBVwE1AXZcKfA8YEB69kAVcCDwd58O9RxAKKn/unwwsDPtNvAJUHjlxOfBUlXlvBn4Y3m5JcKXMCoK+DJJisjPTufeSUZSXO9f83/scLCuPuiQRkWan1rBgZgPN7Edm9hFwF8HRC+buJ7n73bXNG24R+BrwAkGweMLdF5jZTWZ2Zrj8Y82sGDgPuM/MFoTzlhPsgnjZzOYTdF68P1z0d4Fvmdkygj4MD8bUOzKc//2w6VFgPjAe+Gdcz4gknd55rbn9/BHMK97FTc8sjLocEZFmx2q7cI+ZVQBvEPRVWBa2rXD3vo1UX6MpLCz0oqKiqMuQWvzi+Y/43WvL+fk5w7hoTM+oyxERSWlmNtvdC+OZtq7dEOcAG4BXzOx+M5uMrgkhEfmfzw5i0sB8fvjUhxSt0hGxIiKNpdaw4O5PuvuFwGCCvgLXAZ3M7F5doloaW3qaceeFIylo34qvPvI+G3btj7okEZFmId6jIfa6+6PufgbBEQgfEPQdEGlUua0yuf+y0Rw4VM5XHp7NgUPq8CgikmhxHw1Ryd13hOcn0KGIEon+ndpyxwXHMK94F/8zYx619bsREZEjd9hhQSQZfGZoZ747ZTDPzF3PHf9aGnU5IiJNWkbdk4gkp69O6svKrXu48+Wl9M1rzdkj4z2buIiIHA5tWZCUZWb89OxhjOvbge/MmMd7OkJCRCQhFBYkpWVlpPG7aaMpaN+Sq6cXsXLr3qhLEhFpchQWJOW1a5XFQ1cci5lx2UPvsGX3wahLEhFpUhQWpEnondeah644lq27S7nyj++y52BZ3TOJiEhcFBakyTimRzt+e8koFm3YzX89MpvSsoqoSxIRaRIUFqRJOWlwJ35+zjDeWLqV7/51HhUVOgeDiMiR0qGT0uScX9iDzSUHuP3FJeRkZ/DjM4/CTJc0ERGpL4UFaZL++6T+7Np/iPvfWEnb7Eyu/+ygqEsSEUlZCgvSJJkZ/++0Iew5WMbdryyjbXYGX5nUL+qyRERSksKCNFmVJ23afaCMnz//Ea1bZDBtXK+oyxIRSTkKC9KkpacZd1xwDPtLy7nxyQ/JSDMuHNMz6rJERFKKjoaQJi8zPY3fThvFiYPyueFv83nivbVRlyQiklIUFqRZaJGRzu+mjWbiwHy++7d5zJhdHHVJIiIpQ2FBmo3szHR+f+loJvTP439mzOWJIm1hEBGJh8KCNCtBYChkQv88vjNjHg/PWh11SSIiSU9hQZqdllnp3H9ZIZMHd+IHT37IA2+siLokEZGkprAgzVJ2Zjr3ThvN54Z15af/WMTd/16Ku04NLSJSHR06Kc1WVkYav7nwGFpkpHH7i0vYtf8Q35s6hLQ0nRpaRCSWwoI0axnpadx+3ghyWmZy/xsr2ba3lFu+MJzMdG10ExGppLAgzV5amvGjM4bSsXUWv3xpCTv3HeKei0fRMis96tJERJKCfj6JEJwa+trJA/jZ54/mlcWbueSBWWzfWxp1WSIiSUFhQSTGJWN78duLR7FgfQnn/PZNVm3dG3VJIiKRU1gQqWLqsK48+uVxlBwo4/O/fZPZq7dHXZKISKQUFkSqMbpXe/72X8eT2zKTi+5/h2fnrY+6JBGRyCgsiNSgd15r/nbNeIZ3z+Vrj37Ar15aQkWFzsUgIs2PwoJILTq0zuL/vjyW80YXcOfLS/nvR99nX2lZ1GWJiDQqhQWROrTISOfWc4dz4+eG8MKCjZx779sU79gXdVkiIo1GYUEkDmbGl07oy4NXHMva7fs4466ZzFy6NeqyREQahcKCyGE4aVAnnr52AvltW3DZQ+9w76vLdU0JEWnyFBZEDlOfvNb8/ZrxTB3WlVv++RH/9cj7lBw4FHVZIiIJo7AgUg+tW2Rw90Uj+f5pQ3hp0SZOv3Mm84t3RV2WiEhCKCyI1JOZ8eWJfXniK+MoK6/gC/e+xZ/eWqXdEiLS5CgsiByh0b068I+vn8CEAXn86OkFfOXh2ezQdSVEpAlRWBBpAO1bZ/HAZYV8/7QhvLJ4M1N+8zpvLtPREiLSNCQ0LJjZFDNbbGbLzOyGasZPNLP3zazMzM6tZnyOmRWb2d0xbaPNbH64zDvNzML2W8xsnplNj5l2mpldl6j1E4mVlhbslvj7NeNp0yKDaQ++w8+fW8TBsvKoSxMROSIJCwtmlg7cA0wFhgIXmdnQKpOtAa4AHq1hMTcDr1dpuxf4MjAgHKaYWS4wyt2HA6VmNszMWgJXhjWINJqju+fy7LUncPGYntz3+grOuvtNFqxX50cRSV2J3LIwBljm7ivcvRT4M3BW7ATuvsrd5wEVVWc2s9FAZ+DFmLauQI67z/KgF9l04Oxw/sxwK0Mr4BBwPXCXu+uYNml0LbPS+dnnh/Hg5YVs21vKWXe/yZ0vL+VQ+afe6iIiSS+RYaE7sDbmfnHYViczSwN+SfCFX3WZxVWX6e67geeAD4ANwC5grLs/Wb/SRRrG5CGdefG6iZw2rCu/emkJ5/z2LT7aWBJ1WSIihyVZOzheAzzn7sV1Thly91vd/Rh3/zbB7osfmtmXzOwJM7uxunnM7GozKzKzoi1btjRQ6SKf1L51FndeNJJ7Lh7F+p37Of3OmfzyxcXqyyAiKSORYWEd0CPmfkHYFo/jgK+Z2SrgduAyM/tFOH9Bbcs0s5GAAYuB89z9fKCfmQ2o+iDu/nt3L3T3wvz8/DhLE6mfzw3vykvfmsSZI7px17+X8bk7ZzJ79faoyxIRqVMiw8J7wAAz62NmWcCFwNPxzOjul7h7T3fvTbArYrq73+DuG4ASMxsX9k+4DHiqyuw3Az8AMoH0sK2CoC+DSKQ6tM7iVxccwx+vPJb9peV84d63ueGv83ReBhFJagkLC+5eBnwNeAFYBDzh7gvM7CYzOxPAzI41s2LgPOA+M1sQx6KvAR4AlgHLgecrR5jZ2UCRu693953AHDObD2S7+9yGXD+RI3HioE68+M2JXD2xL3+ZXczkX73GE0VrqajQ2R9FJPmYTk0bKCws9KKioqjLkGZo0YYSbnzyQ2av3sHoXu358RlHMawgN+qyRKSJM7PZ7l4Yz7TJ2sFRpNkY0jWHv3zlOG79wnBWb9vLmffM5Ia/zmPrnoNRlyYiAigsiCSFtDTj/GN78O/rT+Sq8X2YMbuYk25/lfteW86BQzpqQkSipbAgkkRysjO58fSh/PO6iRT2as/Pn/+IU371Gk/PXa+rWYpIZBQWRJJQ/05t+MOVY3jkqrG0zc7k6499wNn3vMlbujiViERAYUEkiU0YkMez107gtnOHs3n3QS5+4B2mPfAOc9fujLo0EWlGdDRESEdDSLI7cKicR2at5revLmf73lJOHdqZ604ZyNBuOVGXJiIp6HCOhlBYCCksSKrYfeAQD85cyYNvrGT3wTKmHNWFb5wygCFdFRpEJH4KC/WgsCCpZte+Qzz45kr+MDMIDacO7cx/n9SfET3aRV2aiKQAhYV6UFiQVLVzXykPvbmKP765kpIDZZwwII9rTuzPuL4dCM6KLiLyaQoL9aCwIKlu94FD/N87a3jgjZVs3XOQET3acfUJfZlydBfS0xQaROSTFBbqQWFBmooDh8r5y+xiHnxjBau27aNHh5ZcNb4P5xb2oE2LjKjLE5EkobBQDwoL0tSUVzgvLdzE719fzvtrdtK2RQbnFfbg8uN70atj66jLE5GIKSzUg8KCNGUfrNnBH99axT/mbaDcnZMGdeLScb2YODBfuyhEmimFhXpQWJDmYFPJAf5v1moefXctW/ccpKB9Sy4e25PzRvcgv22LqMsTkUaksFAPCgvSnJSWVfDiwo08Mms1s1ZsJyPNOGVIZy4Y04OJA7S1QaQ5OJywoN5OIs1QVkYapw/vxunDu7Fs8x6eKFrLX2cX888FG+mam83nR3bnC6ML6JffJupSRSQJaMtCSFsWpLkrLavg5UWbeLxoLa8v2UKFw8ie7ThnVAGfG9aVDq2zoi5RRBqQdkPUg8KCyH9sLjnAk3PWMWN2MUs27SEjzZg0MJ8zj+nGZ4Z2plWWNkqKpDqFhXpQWBD5NHdn0YbdPDVnHU/PXc+GXQfIzkxj8uDOnD68KycO6kTLrPSoyxSRelBYqAeFBZHaVVQ4767azj/mbeD5DzewdU8prbLSOWlQJ049qjMnD+5E2+zMqMsUkTgpLNSDwoJI/MrKK3h35Xaenb+BFxdsYuueg2Slp3F8/458ZmhnThnSmc452VGXKSK1UFioB4UFkfopr3A+WLODf364kRcWbmTt9v0ADC/IZfLgzpw0OJ+ju+WSpsMxRZKKwkI9KCyIHDl3Z+nmPby0cBP/WrSJOWt34g55bbKYNLATkwblM6F/no6sEEkCCgv1oLAg0vC27TnI60u38MpHW3htyRZ27T+EGQzrnssJA/IY3z+P0b3a0yJDnSRFGpvCQj0oLIgkVnmFM694J68v2cobS7fwwdqdlFc42ZlpHNu7A8f168hxfTtydPdcMtPToi5XpMlTWKgHhQWRxrX7wCHeWbGdN5dv5c1lW1myaQ8ArbLSKezdgbF9OnBs7w4ML8glO1NbHkQamsJCPSgsiERry+6DvLtyO++s3MasFds+Dg9Z6WkML8hldO/2jO7ZnlG92pPXRhe9EjlSCgv1oLAgklx27C2laPUOilZt591V21mwroTS8goAenVsxTE92n08DO2Wo34PIodJYaEeFBZEktuBQ+UsWL+L2at3MHv1Duas3cmmkoMAZKYbg7q0ZVj3dowoyOXo7rkM6NxGAUKkFrrqpIg0OdmZ6Yzu1YHRvTp83LZx1wHmrN3B3OJdzCveybPz1vPYu2uAIEAM7NyWo7rlMLRrDkO75TK4a1tydJZJkcOmLQshbVkQSX3uzqpt+1iwfhcfrithwfpdLFhfwva9pR9PU9C+JYO7tGVg57YM6hIMffPakJWhIzCkedGWBRFplsyMPnmt6ZPXmtOHdwOCALF590EWri9h4YYSFm0oYcmm3by6eAtlFcGPpfQ0o3fHVgzs3Jb+ndrQv1Mb+uW3oW9+a11hUwSFBRFp4syMzjnZdM7J5qTBnT5uP1hWzoote1myaTdLN+1hyabdfLRxNy8s2EhFzAbXbrnZ9MlvTd+8Nh8Hkd55rSlo31Lng5BmQ2FBRJqlFhnpDOmaw5CuOZ9oP1hWzupt+1i2eQ/LNu9h5da9rNiyhyc/WMfug2UfT5eeZhS0b0nPDq3o1bEVvTq0pkeHVvTo0JIeHVqpb4Q0KQoLIiIxWmSkM7Bz0KchlruzbW8pq7buZeXWvazatpdV2/axdvs+npm7gV37D31i+tyWmRS0b0n3di0paN+K7u1b0r1dNt3btaJbu2w6tM7CTBfXktSgsCAiEgczI69NC/LatKCwd4dPjd+17xBrdwThYc32fazdsY91O/azcute3li6lf2Hyj8xfVZGGl1zs+mam0233JZ0Dm93zsmmS7jbJK9NFhna1SFJQGFBRKQB5LbKJLdVcI6HqtydHfsOsX7nftbt3M/6nfvZsOvAx3/fWbmdTSUHPu5wWckM8tq0oFPbFuS3Df52aptNftsWYXDJIr9tCzq2aUFOdoa2VEjCKCyIiCSYmdGhdRYdWmdVGyYAKiqcrXsPsun/t3f3MXZU5x3Hv7/7urte/AIBlZe0NilVlQQCrTGRmlJERID+ESPFSSmlmDQVqSoq0YY2LySUkjYqVlTSlKqUEimA0poWKYlpmlhp2qgIlRbsEIhxnRiHOiakfrf3/d69+/SPc3Z9vWvfvbb3er27v480mjNnzpydeXyu99mZuTOHRvjJ4WF29w3zf4dH2H14mN19I+zuG2brG4fZ21+jMTb1K++VYoFzeiuc01vh7EVV3pR/3tm9Fc7uqbAsLy/rKbO0p8LS7rLPWljbnCyYmZ0BCgVx3lldnHdWF5dy7IQC0ts7DwzW2Ns/wp6+NO0fqLGnf4S9fTX2D6TlHXv62ddfm3L5o9lZXSWW9VRY2lNmSfeRJGJJ07R4Yl5icVda7q2WKBZ8FmMhcbJgZjaHFAtH7p34+Z+avv1QrcGBwRr7B9J0YLDGwcH6xPzgYI2DQ3UODNbZdWCIg4M1Dg3VOcbJi6P0Vkuc1TU+lSfm4/WLKiV6u0r0VossqpZYVC3RW8311RI91SK91RLVUsGXT+YAJwtmZvNYd6VId6WbC5Z2t73N2FjQXxvl0GCdw8N1Dg3VOTxU5/DwKH3Do7lcp294lL483z9QY+e+QfpGUt1wfaytn1UQLKqU6K6kpKK7XGRRtUh3pURPuUhPpZiOIZe7crm7nOq7ymnqLhfpKhcmytVcrpYKVIpOSE5VR5MFSTcAfwkUgUcj4s8nrb8a+BxwGXBzRDyV6y8H/gZYDDSAP4uIJ/O6FcB64BxgE/CbEVGT9HvAh4GdwE257l3A+yLi9zt5nGZm80mhoHTJ4RSeFTHaGGNgpEHfSJ2BkQb9I6MMjE+1BoO1UQZGGgyMjDI4vrp4zbcAAAnUSURBVFxrMJiXDw3V+cmhIQZrDYZqDYbqaTqZNxRI0FVKCUS1VKBaSklEWs4JRamQ50eWK8XCUeVynlea5uWJuSbalItNy8UCpaJyXSpXigVKBVEsaM4kMR1LFiQVgb8GrgN2Ac9L2hARrzQ12wncDtw9afNB4LaI+IGkC4BNkjZGxEHgAeDBiFgv6WHgQ6TE4jdISccngOsl/TPwKeDXO3WMZmZ2bKVigSU9BZb0zNzDqSKCkdGxieRhsNZguN5gZLTBUG2MoVwero8xXB9fd3R5pD420abWGKM2mpb7R0ZzeYyReoNaI5Vro6ldp16jVC6KUuFIQlEqpHmxoFRXSOVyUSzuLvPEh67qzI5Mo5NnFlYB2yNiB4Ck9cBqYCJZiIjX8rqjzldFxPebyj+WtBs4V9Ih4Frglrz6MeA+UrIgoAz0AHXgVuDrEbG/nZ3dtm8b13zxmhM9RjMzm8tKTPlNWMlTb1NdBATBWKSkZfI8gLGI1C5ymbzuqOUjdQE0IhjNfY9vO1FuQIweaVsYgGu+ePTDwk6XTiYLFwI/alreBZxwSiRpFenf7VXSpYeDETH+zNVd+ecAPAQ8B2wBngW+Clw/Td93AHcAVC+snuiumZnZAiGBEOlLIHPj0sFMOqNvcJR0PvAEsDYixlpd24mIJ3JbJN0LfB64UdJtpKTlIxEx+QzGI8AjkF5R/e3bv92JwzAzMzvj6IPtJz2dfCLH68Cbm5YvynVtkbQY+BpwT0Q8l6v3AUsljSc5U/rM9zisioivAB8Bfg04CLz7ZA7CzMxsoetksvA8cImkFZIqwM3AhnY2zO2/DDw+/g0JgIgI4N+BNblqLelyQ7NPA/fmcjfpUs8Y6V4GMzMzO0EdSxbyfQV3AhuBrcA/RsQWSfdLei+ApCsl7QLeD/ytpC158w8AVwO3S3oxT5fndR8F/kDSdtI9DF8Y/5mSrsg/e3Ou+nvgZeCXgG906ljNzMzmM0Wnvg8yx6xcuTJeeOGF2d4NMzOz00LSpohY2U5bv0XEzMzMWnKyYGZmZi05WTAzM7OWnCyYmZlZS04WzMzMrCUnC2ZmZtaSkwUzMzNrycmCmZmZteSHMmWS9gD/O4NdvgnYO4P9zWeOVXscp/Y5Vu1xnNo3H2P1MxFxbjsNnSx0iKQX2n0y1kLnWLXHcWqfY9Uex6l9Cz1WvgxhZmZmLTlZMDMzs5acLHTOI7O9A3OIY9Uex6l9jlV7HKf2LehY+Z4FMzMza8lnFszMzKwlJwvHIOkGSdskbZf0sWOsr0p6Mq//L0nLm9Z9PNdvk3T9dH1K+pKklyR9pqnuk5Ju6twRzpyTjZWk6yRtkvRynl/btM0v5vrtkj4vSbn+gRyrx5va3irprs4f6ak5lTGV1/+0pH5Jd0/X50IdU3ndZZL+U9KWPIa6cr3H1JHPXlnSYzkeWyV9fLo+5/KYaiNOV0vaLGlU0ppJ69ZK+kGe1jbVz7vxNK2I8NQ0AUXgVeBioAJ8F3jrpDa/CzycyzcDT+byW3P7KrAi91M8Xp/AZcCjedtvAkuA84GnZzsOpyFWVwAX5PLbgdebtvlv4J2AgK8DN+bYfDOvfxS4FOgGvgWUZzsWnYpT0/qngH8C7m7V5wIfUyXgJeAdefkcoOgxNSVOtwDrc7kHeA1YPh/HVJtxWp6P8XFgTVP92cCOPF+Wy8vm43hqZ/KZhalWAdsjYkdE1ID1wOpJbVYDj+XyU8C7c2a5mvQhHImIHwLbc3/H67MOdEsqAGWgAdwP/HFHj3DmnHSsIuI7EfHjXL+FFIeqpPOBxRHxXKRP3ePATcAYUM5x7iHF7m7gryKi3smDnAGnMqbIf739kBSn6fpcsGMKeA/wUkR8FyAi9kVEw2MKODpOASySVCL9MqsBh1v0OZfH1LRxiojXIuIl0nhodj3pl//+iDhASpRumKfjaVpOFqa6EPhR0/KuXHfMNhExChwi/RVzvG2PWR8RW4E9wGbgaeBngUJEbJ6pg+mwU4lVs/cBmyNiJLffNbnPiOgD/gX4DvBG7ueqiPjKzBxKR510nCT1Ah8F/qSdPhf4mPo5ICRtzKeV/6ipvcfUkTg9BQyQjnkn8NmI2H+8Puf4mGonTie67XwcT9MqzfYOLHQRMXEtS9LTwIcl3QO8g5TV/t2s7dxpIOltwAOkvwpbioh1wLq83aPAvZJ+myN/Uf5pJ/d1ltwHPBgR/flEw7QW8JgqAe8CrgQGgW9J2kT6T/uYFuiYWkU6O3AB6fT6M5L+tdUGC3hMnZD5PJ58ZmGq14E3Ny1flOuO2SafylsC7Gux7bR9SloNbAJ6gbdExAeANZJ6TvF4OulUYoWki4AvA7dFxKtN7S9q1aekK0jXCrcB78+xeoukS2bgmDrhVOJ0FbBO0mvAXcAnJN3ZTp8LcEztAv4jIvZGxCDpr7xfwGNqcpxuAb4REfWI2A08C6xsp885OKbaidOJbjsfx9O0nCxM9TxwiaQVkiqkG4M2TGqzARi/M3YN8G/52tUG4OZ87X0FcAnpRpiWfUoqk34RrCNdQxx/+EWRdFPOmeqkYyVpKfA14GMR8ex444h4Azgs6Z352t9twFcn9flp4FOk66fFXDdGuk54JjrpOEXEL0fE8ohYDnwO+ExEPDRdnwtxTAEbgUsl9eRfjr8CvOIxBRwdp53AtQCSFpFu1Puf6fqco2OqnTgdz0bgPZKWSVpGOjuwcZ6Op+l1+g7KuTgBvwp8n3QX7T257n7gvbncRbozfTspGbi4adt78nbbgBtb9dm07i7g9lwW8A/Ay8ADsx2LTsUK+CTpuumLTdN5ed1K4Hu5z4fIDw/L624C7mta/myO1ZdmOxadGlNNfdxH/jaEx9RxP3+3km4E/R6wrqneY+rIZ683128BXgH+cD6PqTbidCXprNQA6czLlqZtfyvHbzvwwfk8nqab/ARHMzMza8mXIczMzKwlJwtmZmbWkpMFMzMza8nJgpmZmbXkZMHMzMxa8hMczey0kNQgfYWsBGwF1kbEYFN9GRglPWv/wYiY/Kx+M5slPrNgZqfLUERcHhFvJ7286Hcm1b8NuI70Br+58JIiswXDyYKZzYZnSC8kOkqkxw/fAdw5/tZNM5t9ThbM7LTKj2K+kXTpYYqI2EF6RO55p3O/zOz4fM+CmZ0u3ZJezOVngC/M5s6YWfucLJjZ6TIUEZdP10jSxaRXKO/u/C6ZWTt8GcLMzhiSzgUeBh4Kv7jG7IzhMwtmNtvGL0+Mf3XyCeAvZneXzKyZ3zppZmZmLfkyhJmZmbXkZMHMzMxacrJgZmZmLTlZMDMzs5acLJiZmVlLThbMzMysJScLZmZm1pKTBTMzM2vp/wGGDtUKJgPM0QAAAABJRU5ErkJggg==\n", 247 | "text/plain": [ 248 | "
" 249 | ] 250 | }, 251 | "metadata": { 252 | "needs_background": "light" 253 | }, 254 | "output_type": "display_data" 255 | } 256 | ], 257 | "source": [ 258 | "fig = plt.figure(figsize=(8, 6))\n", 259 | "ax = fig.add_subplot(1,1,1)\n", 260 | "ax.plot(PD_list, rho_list)\n", 261 | "\n", 262 | "# X axis\n", 263 | "ax.set_xlabel(\"PD\")\n", 264 | "ax.xaxis.set_major_formatter(ticker.PercentFormatter())\n", 265 | "\n", 266 | "# Y axis\n", 267 | "ax.set_ylabel(\"Asset Correlation (Rho)\")\n", 268 | "ax.yaxis.set_major_formatter(ticker.PercentFormatter())\n", 269 | "\n", 270 | "# Title\n", 271 | "ax.set_title(\"corporate asset correlations (without size adjustment)\")\n", 272 | "\n", 273 | "# Lines\n", 274 | "ax.axhline(0.24, color='r')\n", 275 | "ax.axhline(0.12, color='g')" 276 | ] 277 | }, 278 | { 279 | "cell_type": "markdown", 280 | "metadata": {}, 281 | "source": [ 282 | "# Step by Step capital Requirements" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": 82, 288 | "metadata": {}, 289 | "outputs": [], 290 | "source": [ 291 | "from scipy.stats import norm\n", 292 | "\n", 293 | "EAD = 100\n", 294 | "PD = 0.0003\n", 295 | "LGD = 0.45\n", 296 | "M = 2.5" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": 83, 302 | "metadata": {}, 303 | "outputs": [ 304 | { 305 | "data": { 306 | "text/plain": [ 307 | "0.2382134327523675" 308 | ] 309 | }, 310 | "execution_count": 83, 311 | "metadata": {}, 312 | "output_type": "execute_result" 313 | } 314 | ], 315 | "source": [ 316 | "# asset correlation\n", 317 | "weighting = (1 - np.exp(-50 * PD)) / (1 - np.exp(-50))\n", 318 | "rho = 0.12 * weighting + 0.24 * (1 - weighting)\n", 319 | "rho" 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": 84, 325 | "metadata": {}, 326 | "outputs": [], 327 | "source": [ 328 | "b = (0.11852 - 0.05478 * np.log(PD))**2\n", 329 | "maturity_adj = (1 + (M - 2.5) * b) / (1 - 1.5 * b)\n", 330 | "\n", 331 | "# worst case default rate via Gaussian copula\n", 332 | "wcdr = norm.cdf(np.sqrt(1/(1-rho)) * norm.ppf(PD) + np.sqrt(rho/(1-rho)) * norm.ppf(0.999))\n", 333 | "\n", 334 | "K = (LGD * (wcdr - PD)) * maturity_adj" 335 | ] 336 | }, 337 | { 338 | "cell_type": "code", 339 | "execution_count": 88, 340 | "metadata": {}, 341 | "outputs": [ 342 | { 343 | "data": { 344 | "text/plain": [ 345 | "-3.931713166244386" 346 | ] 347 | }, 348 | "execution_count": 88, 349 | "metadata": {}, 350 | "output_type": "execute_result" 351 | } 352 | ], 353 | "source": [ 354 | "np.sqrt(1/(1-rho)) * norm.ppf(PD)" 355 | ] 356 | }, 357 | { 358 | "cell_type": "code", 359 | "execution_count": 85, 360 | "metadata": {}, 361 | "outputs": [ 362 | { 363 | "data": { 364 | "text/plain": [ 365 | "1.9056752706384454" 366 | ] 367 | }, 368 | "execution_count": 85, 369 | "metadata": {}, 370 | "output_type": "execute_result" 371 | } 372 | ], 373 | "source": [ 374 | "maturity_adj" 375 | ] 376 | }, 377 | { 378 | "cell_type": "code", 379 | "execution_count": 86, 380 | "metadata": {}, 381 | "outputs": [ 382 | { 383 | "data": { 384 | "text/plain": [ 385 | "0.011554853832932789" 386 | ] 387 | }, 388 | "execution_count": 86, 389 | "metadata": {}, 390 | "output_type": "execute_result" 391 | } 392 | ], 393 | "source": [ 394 | "K" 395 | ] 396 | }, 397 | { 398 | "cell_type": "code", 399 | "execution_count": 87, 400 | "metadata": {}, 401 | "outputs": [ 402 | { 403 | "data": { 404 | "text/plain": [ 405 | "0.14443567291165987" 406 | ] 407 | }, 408 | "execution_count": 87, 409 | "metadata": {}, 410 | "output_type": "execute_result" 411 | } 412 | ], 413 | "source": [ 414 | "RWperc = K * 12.5\n", 415 | "RWperc" 416 | ] 417 | }, 418 | { 419 | "cell_type": "markdown", 420 | "metadata": {}, 421 | "source": [ 422 | "https://www.bis.org/basel_framework/chapter/CRE/99.htm?inforce=20191215" 423 | ] 424 | } 425 | ], 426 | "metadata": { 427 | "kernelspec": { 428 | "display_name": "Python 3", 429 | "language": "python", 430 | "name": "python3" 431 | }, 432 | "language_info": { 433 | "codemirror_mode": { 434 | "name": "ipython", 435 | "version": 3 436 | }, 437 | "file_extension": ".py", 438 | "mimetype": "text/x-python", 439 | "name": "python", 440 | "nbconvert_exporter": "python", 441 | "pygments_lexer": "ipython3", 442 | "version": "3.7.5" 443 | } 444 | }, 445 | "nbformat": 4, 446 | "nbformat_minor": 2 447 | } 448 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Credit Risk 2 | 3 | Credit risk is the risk that a counterparty will fail to meet his payment obligation, resulting in a loss. This risk is historically considered the main risk for banks. Under BIS II a bank should asses its credit risk and retain capital for it. 4 | 5 | # IRB - The Internal Ratings-Based Approach 6 | 7 | ## Vasicek model - http://bis2information.org/content/Vasicek_model 8 | 9 | The formula used to determine the regulatory capital is commonly referred to as the Vasicek model. The purpose of this model is to determine the expected loss (EL) and unexpected loss (UL) for a counterparty, as explained in the previous section. The first step in this model is to determine the expected loss. This is the average credit loss. There is a 50% change of realizing this loss or less. 10 | 11 | The expected loss is determined using three main ingredients: 12 | - **PD**: Probability of default, the average probability of default over a full economic cycle; 13 | - **EAD**: Exposure at default, the amount of money owed at the moment a counterparty goes into default; 14 | - **LGD**: Percentage of the EAD lost during a default. 15 | 16 | The expected loss (EL) is equal to the PD times the LGD times the EAD: 17 | EL = PD X LGD X EAD 18 | 19 | The expected loss is half the work of the model. The EL determines (roughly) the amount of provisions which should be taken (the essence of any provision is to save money for losses you expect in the future). The second half of the work is to determine the Unexpected Loss (UL). The UL is the additional loss in atypical circumstances, for which tier capital should be retained. The Vasicek model estimates the UL by determining the PD in a downturn situation. The model assumes that the EAD and LGD are not affected by dire circumstances. Both parameters are considered constant for a company. The model calculates the loss during a downturn situation (for instance an exceptionally bad economy) by multiplying the downturn PD times the LGD times the EAD. The UL is calculated by subtracting the expected loss from the loss during a downturn situation. 20 | 21 | In formula’s this equates to: 22 | UL = (PDdownturn X LGD X EAD) – (PD X LGD X EAD) 23 | 24 | which is equal to: 25 | UL = (PDdownturn – PD) X LGD X EAD 26 | 27 | The PD in a downturn situation is determined using the average (through the cycle) PD. At this point Vasicek uses two different models. 28 | 29 | ### Merton Model 30 | First it uses the Merton model. This model states that a counterparty defaults because it cannot meet its obligations at a fixed assessment horizon, because the value of its assets is lower than its due amount. Basically it states that the value of assets serve to pay off debt. The value of a company’s assets vary through time. If the asset value drops below the total debt, the company is considered in default. This logic allows credit risk to be modelled as a distribution of asset values with a certain cut-off point (called a default threshold), beyond which the company is in default. The area under the normal distribution of the asset value below the debt level of a company therefore represents the PD. 31 | 32 | The logic used by Merton can also be reversed. In Vasicek a PD (for instance calculated with a scorecard) is given as input. 33 | Instead of taking the default threshold (debt value) and inferring the PD as Merton does, Vasicek takes the PD and infers the default threshold. Vasicek does this using a standard normal distribution. This is a distribution with an average of zero and a standard deviation of one. This way the model measures how many standard deviations the current asset value is higher than the current debt level. In other words it measures the distance to default. The graph below shows that a PD of 6.68% means that the company is currently 1.5 standard deviations of its asset value away from default. By using the standard normal distribution the actual asset value, standard deviation and debt level becomes irrelevant. It is only necessary to know a PD and the distance to default can be determined. 34 | 35 | 36 | ### Gordy Model 37 | Now that the PD has been transformed to a distance to default the second step of the model comes into play. In this step Vasicek uses the Gordy model. 38 | The distance to default is a through the cycle distance, because the PD used is through the cycle. In other words it is an average distance to default in an average situation. This distance to default will have to be transformed into a distance to default during an economic downturn. To do this a single factor model is used. It is assumed that the asset value of a company is correlated to a single factor. In other words, if the factor goes up the asset value goes up, if the factor goes down the asset value goes down. This factor is often referred to as the economy. This is done because it is intuitively logical that the asset value of a company is correlated to the economy. We will follow this tradition; however the factor is merely conceptual. It is assumed that there is a single common factor (whatever it may be) to which the asset value of all companies show some correlation. The common risk factor (the economy) is also assumed to be a standard normal distribution. 39 | 40 | To recap we have a standard normal distribution representing the possible asset values, a default threshold inferred using the PD, a standard normal distribution representing the economy to which the asset value is correlated and a correlation between the economy and the asset value. Using the correlation it is possible to determine the asset value distribution given a certain level of the economy. If the economy degrades the expected asset value will also decrease shifting the asset value distribution to the left. Furthermore the standard deviation will also decrease. In other words an asset value distribution given a certain level of the economy can be calculated using the correlation between the asset value and the economy. 41 | 42 | 43 | The degree in which the asset value distribution is deformed depends on the level of the economy which is assumed. The level of the economy is measured as the number of standard deviations the economy is from the average economy. For instance the economic level with a probability of 99.9% of occurring or better has a distance of 3.09 standard deviations from the average economy. 44 | 45 | The new distance to default can be calculated by taking the average of the distance of the level of the economy (used to determine the downturn PD) and the distance to default, weighted by the correlation (r). 46 | 47 | In formula’s this equates to: 48 | DistanceToDefaultDownturn = (1-r)^-0.5 X DistanceToDefault + (r/(1-r))^0.5 X DistanceFromEconomy. 49 | 50 | For example the PD was 6.68% and the distance to default was -1.5. 51 | Now assume a counterparty has a 9% correlation to the economy. 52 | Secondly determine that the economic downturn level is the 99.9% worst possible economic level (used in BIS II). 53 | At this level the distance between the downturn level and the average economy is 3.09. 54 | 55 | In our equation the new distance to default (given the 99.9% worst economy) is: 56 | -0.6 = (1-9%)^-0.5 X -1.5 + (9%/(1-9%))^0.5 X 3.09 57 | 58 | In other words the -1.5 distance to default decreases to a distance to default of -0.6. The new PD associated with a distance to default of -0.6 is 27.4%. 59 | 60 | Now the Vasicek model has finished its job. In short it has accomplished the following tasks: 61 | 62 | - It has determined the loss during normal circumstances (Expected Loss) using EL = PD X LGD X EAD. Where the PD is an average PD. 63 | - It has determined the downturn PD using DistanceToDefaultDownturn = (1-r)^-0.5 X DistanceToDefault+ (r/(1-r))^0.5 X DistanceFromEconomy. 64 | - It has determined the Unexpected Loss using UL = (PDdownturn – PD) X LGD X EAD 65 | 66 | ## Basel IRB 67 | 68 | The IRB approach allows the internal specifications of key model parameters such as unconditional default probabilites, exposures at rik (EAD) and Loss Given Default (LGD). 69 | 70 | Basel II/III recommend that financial institutions adopt the IRB approach for computation of regulatory capital. 71 | 72 | One of the key assumption made in the IRB approach is **portfolio invariance**. The portfolio invariance principle, assumes that the risk of the overall portfolio depends only on the characteristics of all individual exposures and therefore independt from the portfolio structure. 73 | 74 | ## The basic structure 75 | 76 | In the credit-default risk setting, it is assumed that the pricing of these instruments accounts for expected default losses. The main idea of economic capital is to create a link between theses unexpected losses and the amount of capital held by the instituion. 77 | As described in the previous section, the assumption of portfolio invariance implies that each credit counterparty computes its own contribution to the overall risk capital. 78 | 79 | We can denote the risk-capital contribution of the *nth* obligor as RCn(alpha) where alpha is level of confidence (for IRB 99.9%). Therefore, the total risk-capital can ne defined as: 80 | 81 | 82 | where N represents the number of obligors in the portfolio. 83 | 84 | According to the current Basel IRB guidance, the risk-capital contribution of the *nth* obligor is denoted as: 85 | 86 | 87 | where mun detones the obligor exposure and equal to EAD * LGD. 88 | 89 | Unexpected Loss (UL) = Worst Case Loss - Expected Loss 90 | = VaR(alpha) - EL 91 | The unexpected loss is thus essentially the worst-case loss, for a given level of confidence, less the expected default loss. 92 | 93 | We have defined Kalpha(n) as the *nth* risk-capital contribution. Practically, it is a function of two main arguments: the unconditional default probability, pn , and the tenor, or term to maturity, of the underlying credit obligation denoted Mn. 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | """ Streamlit app for portfolio analysis and capital requirements """ 2 | 3 | import matplotlib.pyplot as plt 4 | import pandas as pd 5 | import streamlit as st 6 | 7 | st.title("Credit risk portfolio analysis and capital requirements") 8 | 9 | 10 | @st.cache 11 | def load_portfolio(): 12 | """ 13 | Load portfolio data 14 | """ 15 | data = pd.read_csv("tests/test_data_excel_csv.csv") 16 | return data 17 | 18 | 19 | data_load_state = st.text("Loading data...") 20 | data = load_portfolio() 21 | data_load_state.text("Portfolio data has been loaded") 22 | 23 | if st.checkbox("Show raw data"): 24 | st.subheader("Raw data") 25 | st.write(data) 26 | 27 | st.subheader("PD distribution") 28 | plt.hist(data["PD"] * 100, bins=20) 29 | plt.xlabel("PD - Probability of defaults") 30 | plt.ylabel("Number of obligors") 31 | st.pyplot() 32 | -------------------------------------------------------------------------------- /hoods.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalepere/IRB/12ec5017f1a80419f79e160be584efaf16df0731/hoods.zip -------------------------------------------------------------------------------- /modelling.md: -------------------------------------------------------------------------------- 1 | # Modelling steps 2 | 3 | # Data preparation 4 | 5 | ## DataInitTrain: 6 | 1. Replace infinite values with NaN 7 | 2. 80/20 split to create train and test sets 8 | 9 | ## DataPreTrain: 10 | 1. For some of the features, we may have default values that we want to apply when the value is missing 11 | 2. For all the features that are categorical, there are to be replaced with a numeric value. To do so, we need to compute the default rate for each distinct categories within the feature, then sort in ascending order and finally apply the change. 12 | 3. All those transformation are to be saved in a json file so we can apply them to the test set as well as being used in PROD 13 | 4. Features with too many missing values are dropped based on a given threshold 14 | 15 | # Gini analysis 16 | 17 | ## Gini analysis: 18 | 1. Computes the Gini for each individual feature 19 | 2. Each features are to be sorted from the highest gini to the lowest 20 | 3. Depending on the number of features, we might want to only focus on the first 150ish 21 | 22 | # Single feature analysis 23 | 24 | # Notebook: 25 | 1. For each feature, we want to generate some cells in the notebook 26 | 2. Utils to be used in the SFA: 27 | - compute default rate 28 | - compute gini 29 | - feature distribution 30 | - relationship plot between the feature and the default rate 31 | - replace missing values in a dataset with mean, median, or mode 32 | - Remove a specified value from the column of a dataframe. To be used when removing outliers 33 | - group values together 34 | - winsorize 35 | - function fit a / (b + np.exp(m*x + t)) 36 | - discretization: MDLP, quantile or numpy 37 | - create own bins 38 | - own bins for the NaN; what to do with 0s 39 | 3. Save each parameters in a config file to be used for later 40 | 41 | # Transformation 42 | 43 | ## Transform to log odds 44 | 45 | ```python 46 | def transform_to_log_odds(dataframe, column, output_flag): 47 | """ 48 | Transform dataframe column to its log odds equivalent to ensure each column has the same score currency 49 | - Takes the default rate of a column 50 | - Replaces any extreme values (1's and 0's) 51 | - Converts the default rate to its log odds format 52 | - Formula: log( DR / (1-DR) ) 53 | - Saves all the different possible mappings from the original value to the log odds value 54 | - Renames the column with _log and drops original column 55 | - Returns the dataframe and set of mapping tuples 56 | """ 57 | dataframe = compute_default_rate(data=dataframe, feature=column, output_flag=output_flag) 58 | dataframe['{}_DR'.format(column)].replace(0, 0.000000000001, inplace=True) 59 | dataframe['{}_DR'.format(column)].replace(1, 0.999999999999, inplace=True) 60 | dataframe['{}_DR'.format(column)] = np.log(dataframe['{}_DR'.format(column)]/(1-dataframe['{}_DR'.format(column)])) 61 | mapping_tuples = set(zip(dataframe[column], dataframe['{}_DR'.format(column)])) 62 | dataframe.rename(columns={'{}_DR'.format(column): '{}_logOR'.format(column)}, inplace=True) 63 | dataframe.drop(column, axis=1, inplace=True) 64 | return dataframe, {column: mapping_tuples} 65 | ``` 66 | ## Scale features 67 | 68 | ```python 69 | dataframe[column] = dataframe[column].astype(float) 70 | standard_scaler = StandardScaler(copy=True, with_mean=True, with_std=True) 71 | standard_scaler.fit(dataframe[column].to_frame()) 72 | sc_mean = standard_scaler.mean_[0] 73 | sigma = math.sqrt(standard_scaler.var_[0]) 74 | dataframe['{}_stand'.format(column)] = standard_scaler.transform(dataframe[column].to_frame()) 75 | dataframe.drop(column, axis=1, inplace=True) 76 | scale_metrics = [sc_mean, sigma] 77 | return dataframe, {column: scale_metrics} 78 | ``` 79 | 80 | -------------------------------------------------------------------------------- /pipeline/pipeline_irb.py: -------------------------------------------------------------------------------- 1 | """ Pipeline to run IRB approach on a given portfolio """ 2 | import luigi 3 | 4 | 5 | class FileExists(luigi.Task): 6 | """ 7 | Check that file given as input is present in the file system 8 | """ 9 | # The location and file name of the dataset is passed as a parameter 10 | # to the pipeline 11 | input_file = luigi.Parameter() 12 | 13 | def output(self): 14 | """Saves the dataset locally""" 15 | return luigi.LocalTarget(str(self.input_file)) 16 | 17 | def run(self): 18 | """ Open the file passed as parameter; if the file doesnt exist this 19 | will fail the FileExists luigi task. """ 20 | open(str(self.input_file)) 21 | 22 | 23 | class ComputeRiskCapitalPerObligor(luigi.Task): 24 | """ 25 | XXX 26 | """ 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib==3.3.2 2 | streamlit==0.66.0 3 | bandit==1.6.2 4 | appdirs==1.4.4 5 | attrs==20.1.0 6 | distlib==0.3.1 7 | docutils==0.16 8 | filelock==3.0.12 9 | importlib-metadata==1.7.0 10 | iniconfig==1.0.1 11 | lockfile==0.12.2 12 | luigi==3.0.1 13 | more-itertools==8.4.0 14 | numpy==1.19.1 15 | packaging==20.4 16 | pandas==1.1.1 17 | pluggy==0.13.1 18 | py==1.9.0 19 | pyparsing==2.4.7 20 | pytest==6.0.1 21 | python-daemon==2.2.4 22 | python-dateutil==2.8.1 23 | pytz==2020.1 24 | scipy==1.5.2 25 | six==1.15.0 26 | toml==0.10.1 27 | tornado==5.1.1 28 | tox==3.19.0 29 | virtualenv==20.0.31 30 | zipp==3.1.0 31 | -------------------------------------------------------------------------------- /tests/test_data_excel.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalepere/IRB/12ec5017f1a80419f79e160be584efaf16df0731/tests/test_data_excel.xlsx -------------------------------------------------------------------------------- /tests/test_data_excel_csv.csv: -------------------------------------------------------------------------------- 1 | PD,M,LGD,Exponential_weights,Rho,Maturity_slope,Maturity_adj,Term1,Term2,Term3,K,RW 2 | 0.0003,2.5,0.45,0.01488806,0.238213433,0.316834417,1.905675271,-3.931713166,1.728055144,0.013774202,0.011554854,0.14440 3 | 0.0005,2.5,0.45,0.024690088,0.237037189,0.286115268,1.751843952,-3.767157159,1.722454221,0.020442077,0.015720933,0.19650 4 | 0.0010,2.5,0.45,0.048770575,0.234147531,0.246936279,1.588321183,-3.531169544,1.708690329,0.034191152,0.023723195,0.29650 5 | 0.0025,2.5,0.45,0.117503097,0.225899628,0.199569862,1.427255893,-3.190428375,1.669360944,0.064121458,0.039577315,0.49470 6 | 0.0040,2.5,0.45,0.181269247,0.21824769,0.1772289,1.362107119,-2.99951035,1.632793904,0.0858571,0.050174163,0.62720 7 | 0.0050,2.5,0.45,0.221199217,0.213456094,0.16708623,1.334453108,-2.904394424,1.609844478,0.097737764,0.055689389,0.69610 8 | 0.0075,2.5,0.45,0.312710721,0.202474713,0.149421248,1.288878823,-2.723698457,1.557056221,0.12167744,0.066222398,0.82780 9 | 0.0100,2.5,0.45,0.39346934,0.192783679,0.137486131,1.259809501,-2.58928402,1.510188968,0.140272678,0.073853441,0.92320 10 | 0.0130,2.5,0.45,0.477954223,0.182645493,0.127034438,1.235409287,-2.462414975,1.460798714,0.158264482,0.080757491,1.00950 11 | 0.0150,2.5,0.45,0.527633447,0.176683986,0.121507908,1.222885363,-2.391633015,1.431549738,0.168506652,0.084474467,1.05590 12 | 0.0200,2.5,0.45,0.632120559,0.164145533,0.110769565,1.199262714,-2.246373733,1.369431548,0.190259021,0.091883383,1.14850 13 | 0.0250,2.5,0.45,0.713495203,0.154380576,0.102782319,1.182275531,-2.131378673,1.320383174,0.208684128,0.097724362,1.22160 14 | 0.0300,2.5,0.45,0.77686984,0.146775619,0.096478101,1.169203851,-2.036148796,1.281700225,0.225289958,0.102750197,1.28440 15 | 0.0400,2.5,0.45,0.864664717,0.136240234,0.086936533,1.149960349,-1.883700231,1.22729039,0.25578023,0.111662419,1.39580 16 | 0.0500,2.5,0.45,0.917915001,0.1298502,0.079877577,1.136126554,-1.76331639,1.193755615,0.284487819,0.119883527,1.49850 17 | 0.0600,2.5,0.45,0.950212932,0.125974448,0.074331828,1.125489542,-1.663049177,1.173195251,0.312118634,0.127690599,1.59610 18 | 0.1000,2.5,0.45,0.993262053,0.120808554,0.059856368,1.098640989,-1.366766603,1.145508309,0.412445661,0.154469524,1.93090 19 | 0.1500,2.5,0.45,0.999446916,0.12006637,0.049481437,1.080172749,-1.104883372,1.141502484,0.514605648,0.177226688,2.21530 20 | 0.2000,2.5,0.45,0.9999546,0.120005448,0.042718693,1.068465152,-0.897174027,1.141173342,0.596384325,0.190585277,2.38230 -------------------------------------------------------------------------------- /tests/test_data_excel_csv.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalepere/IRB/12ec5017f1a80419f79e160be584efaf16df0731/tests/test_data_excel_csv.xlsx -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """ Unit tests for all the functions in utils.py """ 2 | import numpy as np 3 | import pandas as pd 4 | 5 | from utils.utils_irb import ( 6 | get_average_PD, 7 | get_basel_K, 8 | get_maturity_adjusment, 9 | get_maturity_slope, 10 | get_rho_asset_correlation, 11 | ) 12 | 13 | 14 | class TestAveragePD: 15 | """ 16 | Tests around calculating average PD 17 | """ 18 | 19 | def test_get_average_PD(self): 20 | """ 21 | Test that the functions correctly returns average PD. 22 | In this is example avg_PD = (0.0 + 1.0) / 2 = 0.5 23 | """ 24 | dict_frame = {"exp": [100, 200], "PD": [0.0, 1.0]} 25 | df = pd.DataFrame(data=dict_frame) 26 | avg_PD = get_average_PD(df, col="PD") 27 | assert avg_PD == 0.5 28 | 29 | 30 | class TestAssetCorrelation: 31 | """ 32 | Tests around calculating asset correlation: 33 | - PD = 0% 34 | - PD = 100% 35 | - PD = 1% 36 | 37 | Test that the fco works also when imput is an array 38 | """ 39 | 40 | def test_rho_pd_0(self): 41 | """ 42 | Test rho calculation when PD = 0%. 43 | Expected result 24% 44 | """ 45 | rho = get_rho_asset_correlation(np.array(0.0)) 46 | assert rho == 0.24 47 | 48 | def test_rho_pd_100(self): 49 | """ 50 | Test rho calculation when PD = 100% 51 | Expected result 12% 52 | """ 53 | rho = get_rho_asset_correlation(np.array(1.0)) 54 | assert rho == 0.12 55 | 56 | def test_rho_pd_1(self): 57 | """ 58 | Test rho calcularion when PD = 1% 59 | Expected results: 60 | exponential_weights = (1 - exp(-0.5)) / (1 - exp(-50)) 61 | exponential weights = 0.393469340287367 62 | rho = 0.12 * exponential_weights + 0.24 * (1 - exponential_weights) 63 | rho = 0.192783679165516 64 | """ 65 | exponential_weights = (1 - np.exp(-50 * 0.01)) / (1 - np.exp(-50)) 66 | rho = 0.12 * (exponential_weights) + 0.24 * (1 - exponential_weights) 67 | rho_calc = get_rho_asset_correlation(np.array(0.01)) 68 | assert rho == rho_calc 69 | assert rho_calc == 0.192783679165516 70 | 71 | def test_with_array(self): 72 | """ 73 | Test that when we have an array with the 3 above we get the same results. 74 | """ 75 | input = np.array([0.0, 1.0, 0.01]) 76 | output = np.array([0.24, 0.12, 0.192783679165516]) 77 | rho_calc = get_rho_asset_correlation(input) 78 | test = np.testing.assert_array_equal(rho_calc, output) 79 | assert test is None 80 | 81 | 82 | class TestMaturity: 83 | """ 84 | Test the function that computes maturity adjustement: 85 | - for an exposure 86 | - for multiple exposures 87 | """ 88 | 89 | def test_slope_one_value(self): 90 | """ 91 | test maturity slope calculation for one value 92 | """ 93 | slope = get_maturity_slope(0.1) 94 | test_slope = (0.11852 - 0.05478 * np.log(0.1)) ** 2 95 | slope2 = get_maturity_slope(0.2) 96 | test_slope2 = (0.11852 - 0.05478 * np.log(0.2)) ** 2 97 | assert test_slope == slope 98 | assert test_slope2 == slope2 99 | 100 | def test_slope_multiple_values(self): 101 | """ 102 | test maturity slope for multiple values 103 | """ 104 | test_slope = (0.11852 - 0.05478 * np.log(0.1)) ** 2 105 | test_slope2 = (0.11852 - 0.05478 * np.log(0.2)) ** 2 106 | test_array = np.array([test_slope, test_slope2]) 107 | slope_array = get_maturity_slope(np.array([0.1, 0.2])) 108 | test = np.testing.assert_array_equal(test_array, slope_array) 109 | assert test is None 110 | 111 | def test_maturity_adj_one_value(self): 112 | """ 113 | test maturity adjustment using one value 114 | """ 115 | test_slope = (0.11852 - 0.05478 * np.log(0.1)) ** 2 116 | test_ma = (1 + (1 - 2.5) * test_slope) / (1 - 1.5 * test_slope) 117 | ma = get_maturity_adjusment(0.1, 1) 118 | test_slope2 = (0.11852 - 0.05478 * np.log(0.2)) ** 2 119 | test_ma2 = (1 + (1 - 2.5) * test_slope2) / (1 - 1.5 * test_slope2) 120 | ma2 = get_maturity_adjusment(0.2, 1) 121 | assert test_ma == ma 122 | assert test_ma2 == ma2 123 | 124 | def test_maturity_adj_multi_value(self): 125 | """ 126 | test maturity adj multiple values 127 | """ 128 | test_slope = (0.11852 - 0.05478 * np.log(0.1)) ** 2 129 | test_ma = (1 + (1 - 2.5) * test_slope) / (1 - 1.5 * test_slope) 130 | test_slope2 = (0.11852 - 0.05478 * np.log(0.2)) ** 2 131 | test_ma2 = (1 + (1 - 2.5) * test_slope2) / (1 - 1.5 * test_slope2) 132 | ma_array = get_maturity_adjusment(pd=np.array([0.1, 0.2]), m=np.array([1, 1])) 133 | test_array = np.array([test_ma, test_ma2]) 134 | test = np.testing.assert_array_equal(test_array, ma_array) 135 | assert test is None 136 | 137 | 138 | class TestCapitalK: 139 | """ 140 | Test computation of basel capital requirements, for all obligors provided in a csv file 141 | """ 142 | 143 | def test_capital_k(self): 144 | """ 145 | Test capital K for all obligors in csv test file 146 | https://www.bis.org/basel_framework/chapter/CRE/99.htm?inforce=20191215 147 | """ 148 | import pandas as pd 149 | 150 | # Intialize parameters to be used 151 | df = pd.read_csv("tests/test_data_excel_csv.csv") 152 | lgd = 0.45 153 | alpha = 0.999 154 | pd = np.array(df["PD"]) 155 | m = np.array(df["M"]) 156 | 157 | results = get_basel_K(pd, m, lgd, alpha) 158 | compare_test = np.array(df["K"]) 159 | test = np.testing.assert_array_equal(results.round(4), compare_test.round(4)) 160 | assert test is None 161 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py37 3 | skipsdist=True 4 | 5 | [testenv] 6 | deps = 7 | -rrequirements.txt 8 | commands = 9 | python -m pytest -rA 10 | bandit {posargs:-r utils} 11 | bandit {posargs:-r pipeline} 12 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aalepere/IRB/12ec5017f1a80419f79e160be584efaf16df0731/utils/__init__.py -------------------------------------------------------------------------------- /utils/utils_irb.py: -------------------------------------------------------------------------------- 1 | """ Set of functions that will support any calc for the IRB approach """ 2 | import numpy as np 3 | from scipy.stats import norm 4 | 5 | 6 | def get_average_PD(df, col="PD"): 7 | """ 8 | DESCRIPTION: 9 | ------------ 10 | This function computes the average PD from all the exposures. 11 | 12 | PARAMS: 13 | ------- 14 | :param df: dataframe containing the exposures 15 | :param col: name of the column containing the PD 16 | :return: average PD in the dataframe 17 | """ 18 | return np.mean(df[col]) 19 | 20 | 21 | def get_rho_asset_correlation(pd, k_factor=-50, rho_min=0.12, rho_max=0.24): 22 | """ 23 | DESCRIPTION: 24 | ----------- 25 | The single systematic risk factor needed in the ASRF model may be interpreted as reflecting 26 | the state of the global economy. The degree of the obligor’s exposure to the systematic risk 27 | factor is expressed by the asset correlation. The asset correlations, in short, show how the 28 | asset value (e.g. sum of all asset values of a firm) of one borrower depends on the asset 29 | value of another borrower. 30 | 31 | The asset correlation function is built of two limit correlations of 12% and 24% for very 32 | high and very low PDs (100% and 0%, respectively). Correlations between these limits are 33 | modelled by an exponential weighting function that displays the dependency on PD. The 34 | exponential function decreases rather fast; its pace is determined by the so-called 35 | “k-factor”, which is set at 50 for corporate exposures. 36 | 37 | REFERENCE: 38 | ---------- 39 | https://www.bis.org/bcbs/irbriskweight.pdf 40 | 5.2. Supervisory estimates of asset correlations for corporate, bank and sovereign exposures 41 | 42 | PARAMS: 43 | ------- 44 | :param pd: arrays of PDs from portfolio 45 | :param k_factor: Set to 50 for corporates exposures 46 | :param rho_min: 12% min limit, for PDs = 100% 47 | :param rho_max: 14% max limit, for PDs = 0% 48 | :return rho: array of assets correlation 49 | """ 50 | exponential_weights = np.divide((1 - np.exp(k_factor * pd)), (1 - np.exp(k_factor))) 51 | return rho_min * exponential_weights + rho_max * (1 - exponential_weights) 52 | 53 | 54 | def get_maturity_slope(pd, a=0.11852, b=-0.05478): 55 | """ 56 | DESCRIPTION: 57 | ------------ 58 | Credit portfolios consist of instruments with different maturities. Both intuition and 59 | empirical evidence indicate that long-term credits are riskier than short-term credits. As a 60 | consequence, the capital requirement should increase with maturity. 61 | 62 | In order to derive the Basel maturity adjustment function, the grid of relative VaR figures 63 | (in relation to 2.5 years maturity) was smoothed by a statistical regression model. 64 | This includes the slope of the adjustment function with respect to M decreases as 65 | the PD increases. 66 | 67 | REFERENCE: 68 | ---------- 69 | https://www.bis.org/bcbs/irbriskweight.pdf 70 | 4.6. Maturity adjustments 71 | 72 | PARAMS: 73 | ------- 74 | :param pd: array of PDs from portfolio 75 | :param a: slope adjustment coefficient 1 76 | :param b: slope adjustement coefficient 2 77 | :return maturity_slope: array of Smoothed (regression) maturity adjustment (smoothed over PDs) 78 | """ 79 | return np.power((a + b * np.log(pd)), 2) 80 | 81 | 82 | def get_maturity_adjusment(pd, m, y=2.5): 83 | """ 84 | DESCRIPTION: 85 | ------------ 86 | Maturity adjustments are the ratios of each of these VaR figures to the VaR 87 | of a “standard” maturity, which was set at 2.5 years, for each maturity and each rating grade. 88 | 89 | REFERENCE: 90 | ---------- 91 | https://www.bis.org/bcbs/irbriskweight.pdf 92 | 4.6. Maturity adjustments 93 | 94 | PARAMS: 95 | ------- 96 | :param pd: array of PDs from portfolio 97 | :param tenor: array of tenors from portfolio or remaining term to maturity 98 | :param y: standard maturity, 2.5 years 99 | :return maturity_adj: array of maturity adjustments used in risk-capital allocation 100 | """ 101 | slope = get_maturity_slope(pd) 102 | return np.divide(1 + (m - y) * slope, 1 - 1.5 * slope) 103 | 104 | 105 | def get_basel_K(pd, m, lgd, alpha): 106 | """ 107 | DESCRIPTION: 108 | ------------ 109 | Capital requirement (K) = 110 | [LGD * N [(1 - R)^-0.5 * G (PD) + (R / (1 - R))^0.5 * G (0.999)] - PD * LGD] 111 | * (1 - 1.5 x b(PD))^ -1 × (1 + (M - 2.5) * b (PD)) 112 | 113 | Standard normal distribution(N) applied to threshold and conservative value of systematic 114 | factor. 115 | Inverse of the standard normal distribution (G, ppf) applied to PD to derive default threshold. 116 | Inverse of the standard normal distribution (G, ppf) applied to confidence level to derive 117 | conservative value of systematic factor 118 | 119 | REFERENCE: 120 | ---------- 121 | 4.2. Average and conditional PDs 122 | 123 | PARAMS: 124 | ------- 125 | :param pd: array of PD from portfolio 126 | :param m: array of tenor, i.e. remaining term to maturity 127 | :param lgd: loss given default 128 | :param alpha: level of confidence 129 | :return K: array of risk-capital contribution for each exposure in portfolio 130 | """ 131 | rho = get_rho_asset_correlation(pd) 132 | ma = get_maturity_adjusment(pd, m) 133 | print(rho) 134 | term_1 = np.multiply(np.sqrt(1/(1 - rho)), norm.ppf(pd)) 135 | term_2 = np.multiply((np.sqrt((rho) / (1 - rho))), norm.ppf(alpha)) 136 | term_3 = norm.cdf(np.add(term_1, term_2)) 137 | return (lgd * term_3 - pd * lgd) * ma 138 | -------------------------------------------------------------------------------- /utils/utils_vasicek.py: -------------------------------------------------------------------------------- 1 | """ MonteCarlo simulation utils based on Vasicek one-factor Gaussian copula model """ 2 | import numpy as np 3 | from scipy.stats import norm, uniform 4 | 5 | from utils_irb import get_rho_asset_correlation 6 | 7 | 8 | def conditional_pd(pd, rho, y): 9 | """ 10 | DESCRIPTION: 11 | ------------ 12 | The Vasicek model is a one period default model, i.e., loss only occurs when an 13 | obligor defaults in a fixed time horizon. Based on Merton‘s firm-value model, to describe 14 | the obligor’s default and its correlation structure, we assign each obligor a random 15 | variable called firm-value. 16 | 17 | The firm-value of obligor n is represented by a common, standard normally distributed factor 18 | Y component and an idiosyncratic standard normal noise component n. 19 | The Y factor is the state of the world or business cycle, usually called systematic factor. 20 | 21 | The probability of default of obligor n conditional to a realization of Y = y is given by 22 | this function. 23 | 24 | REFERENCE: 25 | ---------- 26 | https://core.ac.uk/download/pdf/41778167.pdf 27 | 28 | PARAMS: 29 | ------- 30 | :param pd: probability of default of obligor n 31 | :param rho: asset correlation, to be computed from IRB approach 32 | :param y: The Y factor is the state of the world or business cycle, usually called 33 | systematic factor. 34 | :return pd_conditional: conditional probability of default of obligor n to a realization of 35 | Y. 36 | """ 37 | 38 | numerator = np.subtract(norm.ppf(pd), np.multiply(np.sqrt(rho), y)) 39 | denominator = np.sqrt(1 - rho) 40 | return norm.cdf(np.divide(numerator, denominator)) 41 | 42 | 43 | def run_simulation(pd, ead, lgd, num_simulations=1000): 44 | """ 45 | DESCRIPTION: 46 | ------------ 47 | This functions runs a montecarlo simulation using the conditional PD computed through the 48 | vasicek model. 49 | 50 | 1) y is randomly generated from a normal distribution 51 | 2) conditional_pd is computed with the vasicek formula 52 | 3) we generate a uniform continuous random variable 53 | 4) we compare the random variable to conditional_pd to define if default there is 54 | 5) if default compute loss = default_indicator * EAD * LGD 55 | 6) aggregate all losses 56 | 57 | PARAMS: 58 | ------- 59 | :param pd: probability of default 60 | :param ead: exposure at risk 61 | :param lgd: loss given default 62 | :param num_simulations: number of simulation/iteration to be performed 63 | :return scenario_losses: array of losses 64 | """ 65 | scenario_losses = np.array([]) 66 | rho = get_rho_asset_correlation(pd) 67 | for _ in range(num_simulations): 68 | y = np.random.normal(size=1) 69 | pd_cond = conditional_pd(pd, rho, y) 70 | uniform_random_variable = uniform.rvs(size=pd.size) 71 | default_indicator = (uniform_random_variable < pd_cond) * 1.0 72 | loss = np.multiply(default_indicator, ead, lgd) 73 | total_loss = loss.sum() 74 | scenario_losses.append(total_loss) 75 | return scenario_losses 76 | --------------------------------------------------------------------------------