{ "cells": [ { "cell_type": "markdown", "id": "0afcdf47-6e69-4905-a0f3-5270a82584f0", "metadata": {}, "source": [ "# Comparing Surface Interpolation for FX Options\n", "\n", "This notebook will give a demonstration of *rateslib* interpolating its two different FX vol surface parametrisations: the **FXDeltaVolSurface** and the **FXSabrSurface**.\n", "\n", "To reference a publication we will use Iain Clark's *Foreign Exchange\n", "Option Pricing: A Practitioner's Guide*, and establish an **FXForwards** market similar to the values he uses in his *Table 4.4* and and *Table 4.5*.\n", "\n", "The ``eval_date`` is fictionally assumed to be 3rd May 2009 and the FX spot rate is 1.34664,\n", "and the continuously compounded EUR and USD rates are 1.0% and 0.4759..% respectively. With these\n", "we will be able to closely match his values for option strikes." ] }, { "cell_type": "code", "execution_count": null, "id": "38a2c0e1-2400-47c8-b665-4a5e2d71b15b", "metadata": {}, "outputs": [], "source": [ "from rateslib import *\n", "from pandas import DataFrame\n", "\n", "eur = Curve({dt(2009, 5, 3): 1.0, dt(2011, 5, 10): 1.0})\n", "usd = Curve({dt(2009, 5, 3): 1.0, dt(2011, 5, 10): 1.0})\n", "fxf = FXForwards(\n", " fx_rates=FXRates({\"eurusd\": 1.34664}, settlement=dt(2009, 5, 5)),\n", " fx_curves={\"eureur\": eur, \"usdusd\": usd, \"eurusd\": eur},\n", ")\n", "fx_solver = Solver(\n", " curves=[eur, usd],\n", " instruments=[\n", " Value(dt(2009, 5, 4), curves=eur, metric=\"cc_zero_rate\"),\n", " Value(dt(2009, 5, 4), curves=usd, metric=\"cc_zero_rate\")\n", " ],\n", " s=[1.00, 0.4759550366220911],\n", " fx=fxf,\n", ")" ] }, { "cell_type": "markdown", "id": "c6689cea-98a5-491b-9b44-369f1e85252f", "metadata": {}, "source": [ "## The Data Used\n", "\n", "Usually 1Y Options use spot delta definitions, whilst 2Y Options use a forward delta. Clark, in his publication, noted this and also pre-computed the forward delta values, for a consistent representation. This will be used to calibrate the *Surface*." ] }, { "cell_type": "code", "execution_count": null, "id": "43b3242b-a661-41b5-9c05-bc62c8ac7a9f", "metadata": {}, "outputs": [], "source": [ "DataFrame(\n", " data=[\n", " [1.1964, 1.3620, 1.5501], [19.590, 18.250, 18.967],\n", " [1.1733, 1.3689, 1.5974], [19.068, 17.870, 18.485],\n", " [1.1538, 1.3748, 1.6393], [18.801, 17.677, 18.239]\n", " ],\n", " index=[(\"1y\", \"k\"), (\"1y\", \"vol\"), (\"18m\", \"k\"), (\"18m\", \"vol\"), (\"2y\", \"k\"), (\"2y\", \"vol\")],\n", " columns=[\"25d Put\", \"ATM Put\", \"25d Call\"]\n", ")" ] }, { "cell_type": "markdown", "id": "54b29c11-75cd-4f8b-afd7-500d4bb7915b", "metadata": {}, "source": [ "## Create a DeltaVolSurface\n", "\n", "This surface matches conventions and delta values at the relevant expiries." ] }, { "cell_type": "code", "execution_count": null, "id": "cc0709b7-701b-48f6-a2b4-b0fbbced91fd", "metadata": {}, "outputs": [], "source": [ "fxs = FXDeltaVolSurface(\n", " eval_date=dt(2009, 5, 3),\n", " expiries=[dt(2010, 5, 3), dt(2011, 5, 3)], # 1Y and 2Y\n", " delta_indexes=[0.25, 0.5, 0.75],\n", " node_values=[[5, 5, 5], [5, 5, 5]],\n", " delta_type=\"forward\",\n", " id=\"dv\"\n", ")" ] }, { "cell_type": "markdown", "id": "4bf7bfe3-38c9-48d4-bbfe-6557c521f0a9", "metadata": {}, "source": [ "Calibrate to the stated volatilities." ] }, { "cell_type": "code", "execution_count": null, "id": "8452940d-6c54-4b9b-99e5-6d050f47ef3e", "metadata": {}, "outputs": [], "source": [ "op_args = dict(pair=\"eurusd\", delta_type=\"forward\", curves=[None, eur, None, usd], eval_date=dt(2009, 5, 3), vol=fxs, metric=\"vol\")\n", "\n", "vol_solver = Solver(\n", " surfaces=[fxs],\n", " instruments=[\n", " FXPut(expiry=\"1y\", strike=\"-25d\", **op_args),\n", " FXCall(expiry=\"1y\", strike=\"atm_delta\", **op_args),\n", " FXCall(expiry=\"1y\", strike=\"25d\", **op_args),\n", " FXPut(expiry=\"2y\", strike=\"-25d\", **op_args),\n", " FXCall(expiry=\"2y\", strike=\"atm_delta\", **op_args),\n", " FXCall(expiry=\"2y\", strike=\"25d\", **op_args),\n", " ],\n", " s=[19.59, 18.25, 18.967, 18.801, 17.677, 18.239],\n", " fx=fxf,\n", ")" ] }, { "cell_type": "markdown", "id": "5e6f8391-4ad1-47f4-86b2-44bf234831f6", "metadata": {}, "source": [ "For the *DeltaVolSurface*, the method *rateslib* employs is to interpolate, temporally, between **delta indexes**, and then construct a *DeltaVolSmile* with those parameters. Finally deriving the volatility for a given *strike* or *delta* using the usual methods for a *Smile*. " ] }, { "cell_type": "code", "execution_count": null, "id": "4000cb1f-63b2-4222-a4de-b049866db4ca", "metadata": {}, "outputs": [], "source": [ "fxs.get_smile(dt(2010, 11, 3))" ] }, { "cell_type": "markdown", "id": "8688b9ae-69d4-4f10-9b87-43323ab3a244", "metadata": {}, "source": [ "Now we will derive the the values for the 18 month *Options*." ] }, { "cell_type": "code", "execution_count": null, "id": "b80badbe-fb13-4e1a-8945-d38dfb119a68", "metadata": {}, "outputs": [], "source": [ "result = FXPut(expiry=\"18m\", strike=\"-25d\", **op_args).analytic_greeks(fx=fxf)\n", "{\"strike\": result[\"__strike\"], \"vol\": result[\"__vol\"]*100}" ] }, { "cell_type": "code", "execution_count": null, "id": "547263f9-2775-46e2-a357-6c385650ba8e", "metadata": {}, "outputs": [], "source": [ "result = FXCall(expiry=\"18m\", strike=\"atm_delta\", **op_args).analytic_greeks(fx=fxf)\n", "{\"strike\": result[\"__strike\"], \"vol\": result[\"__vol\"]*100}" ] }, { "cell_type": "code", "execution_count": null, "id": "ac902805-167f-4597-a669-a049dadd6ae1", "metadata": {}, "outputs": [], "source": [ "result = FXCall(expiry=\"18m\", strike=\"25d\", **op_args).analytic_greeks(fx=fxf)\n", "{\"strike\": result[\"__strike\"], \"vol\": result[\"__vol\"]*100}" ] }, { "cell_type": "markdown", "id": "51f94650-ec93-497f-b0df-e51e081d1162", "metadata": {}, "source": [ "Formatted for easy display this gives the following for the **DeltaVolSmile** at 18M:" ] }, { "cell_type": "code", "execution_count": null, "id": "738bfe55-0e41-4b72-967f-395d6b91dd6d", "metadata": {}, "outputs": [], "source": [ "DataFrame(\n", " data=[[1.1726, 1.3684, 1.5971], [19.065, 17.868, 18.482]],\n", " index=[(\"18m\", \"k\"), (\"18m\", \"vol\")],\n", " columns=[\"25d Put\", \"ATM Put\", \"25d Call\"]\n", ")" ] }, { "cell_type": "markdown", "id": "49bc7edd-240e-4e1c-9ed1-d88091bdddb6", "metadata": {}, "source": [ "## Create a SabrSurface\n", "\n", "The SABRSurface behaves differently in the way it interpolates. \n", "For a given *strike* it will interpolate, temporally, between the volatility values obtained for that **strike** on neighboring *SabrSmiles*.\n", "It does not generate an intermediate *SabrSmile* for a given expiry." ] }, { "cell_type": "code", "execution_count": null, "id": "d0bc0efb-4b3b-4318-9861-0c2f6e6c9bfe", "metadata": {}, "outputs": [], "source": [ "fxs2 = FXSabrSurface(\n", " eval_date=dt(2009, 5, 3),\n", " expiries=[dt(2010, 5, 3), dt(2011, 5, 3)],\n", " node_values=[[0.05, 1.0, 0.01, 0.01]]*2,\n", " pair=\"eurusd\",\n", " id=\"sabr\",\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "43ed060e-c055-48cc-aa56-b9d59633b9bf", "metadata": {}, "outputs": [], "source": [ "op_args2 = dict(pair=\"eurusd\", delta_type=\"forward\", curves=[None, eur, None, usd], eval_date=dt(2009, 5, 3), vol=fxs2, metric=\"vol\")\n", "vol_solver2 = Solver(\n", " surfaces=[fxs2],\n", " instruments=[\n", " FXPut(expiry=\"1y\", strike=\"-25d\", **op_args2),\n", " FXCall(expiry=\"1y\", strike=\"atm_delta\", **op_args2),\n", " FXCall(expiry=\"1y\", strike=\"25d\", **op_args2),\n", " FXPut(expiry=\"2y\", strike=\"-25d\", **op_args2),\n", " FXCall(expiry=\"2y\", strike=\"atm_delta\", **op_args2),\n", " FXCall(expiry=\"2y\", strike=\"25d\", **op_args2),\n", " ],\n", " s=[19.59, 18.25, 18.967, 18.801, 17.677, 18.239],\n", " fx=fxf,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "d329919d-be2c-43f8-ae30-d670ad2c5d9f", "metadata": {}, "outputs": [], "source": [ "result = FXPut(expiry=\"18m\", strike=\"-25d\", **op_args2).analytic_greeks(fx=fxf)\n", "{\"strike\": result[\"__strike\"], \"vol\": result[\"__vol\"]*100}" ] }, { "cell_type": "code", "execution_count": null, "id": "5b4ef8ee-6fcd-477a-afa2-d6ca47c5dc9f", "metadata": {}, "outputs": [], "source": [ "result = FXCall(expiry=\"18m\", strike=\"atm_delta\", **op_args2).analytic_greeks(fx=fxf)\n", "{\"strike\": result[\"__strike\"], \"vol\": result[\"__vol\"]*100}" ] }, { "cell_type": "code", "execution_count": null, "id": "1fb5c0b2-552f-43a8-88d5-117687e30a15", "metadata": {}, "outputs": [], "source": [ "result = FXCall(expiry=\"18m\", strike=\"25d\", **op_args2).analytic_greeks(fx=fxf)\n", "{\"strike\": result[\"__strike\"], \"vol\": result[\"__vol\"]*100}" ] }, { "cell_type": "markdown", "id": "429cafcf-f858-4e8d-9a89-f335c18d84a7", "metadata": {}, "source": [ "Again for ease of display the values for the **SabrSmile** are as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "7e2c7721-8cad-453a-a232-93099bde061d", "metadata": {}, "outputs": [], "source": [ "DataFrame(\n", " data=[[1.1722, 1.3685, 1.5985], [19.081, 17.870, 18.511]],\n", " index=[(\"18m\", \"k\"), (\"18m\", \"vol\")],\n", " columns=[\"25d Put\", \"ATM Put\", \"25d Call\"]\n", ")" ] }, { "cell_type": "markdown", "id": "4584019f-a349-4b44-96b7-3f688117f0e2", "metadata": {}, "source": [ "## Comparing the interpolated values of the Surface\n", "\n", "We can make a plot of the comparison between the volatility values on of the interpolated *DeltaVolSurface* and the *SabrSurface*." ] }, { "cell_type": "code", "execution_count": null, "id": "47dd7f47-573b-41bd-b59c-45512e5dc249", "metadata": {}, "outputs": [], "source": [ "strikes = [1.15 + _ * 0.0025 for _ in range(200)]\n", "\n", "import matplotlib.pyplot as plt\n", "\n", "fix, ax = plt.subplots(1,1)\n", "ax.plot(strikes, [fxs.get_from_strike(_, fxf.rate(\"eurusd\", dt(2010, 11, 5)), dt(2010, 11, 3))[1] for _ in strikes], label=\"DeltaVol\")\n", "ax.plot(strikes, [fxs2.get_from_strike(_, fxf, dt(2010, 11, 3))[1] for _ in strikes], label=\"Sabr\")\n", "ax.legend()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.0" } }, "nbformat": 4, "nbformat_minor": 5 }