{ "cells": [ { "cell_type": "markdown", "id": "087fc973-33e9-4d99-a264-1b570dd119a2", "metadata": {}, "source": [ "# FX Volatility Surface Temporal Interpolation" ] }, { "cell_type": "code", "execution_count": null, "id": "8681bb4b-c22f-451d-a2dd-35b50dde60be", "metadata": {}, "outputs": [], "source": [ "from rateslib import *\n", "from pandas import Series\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "id": "9509d3c8-8fc0-4191-89c2-14983c590d4c", "metadata": {}, "source": [ "The default *FXDeltaVolSurface* is constructed with parametrised cross-sectional\n", "*FXDeltaVolSmiles*. The **temporal interpolation** method determines a *delta-node* between the two surrounding *Smiles* using linear total variance, which has been shown (see Clark: FX Option Pricing) to be equivalent to flat forward volatility within the interval.\n", "\n", "Consider Table 4.7 of that same publication, *Clark: FX Option Pricing*. To replicate the data there we will create a *Surface* here which has flat line *Smiles* (i.e. there is just one volatility datapoint at each expiry) in the following way:" ] }, { "cell_type": "code", "execution_count": null, "id": "e93ecafc-954a-40b0-a60d-4795f5bdde1c", "metadata": {}, "outputs": [], "source": [ "fxvs = FXDeltaVolSurface(\n", " expiries=[\n", " dt(2024, 2, 12), # Spot\n", " dt(2024, 2, 16), # 1W\n", " dt(2024, 2, 23), # 2W\n", " dt(2024, 3, 1), # 3W\n", " dt(2024, 3, 8), # 4W\n", " ],\n", " delta_indexes=[0.5],\n", " node_values=[[8.15], [11.95], [11.97], [11.75], [11.80]],\n", " eval_date=dt(2024, 2, 9),\n", " delta_type=\"forward\",\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "6c36686c-a22e-47bf-9dbb-49df07964bdc", "metadata": {}, "outputs": [], "source": [ "fxvs.plot()" ] }, { "cell_type": "markdown", "id": "d4d99760-ec13-461a-9b0d-939693e13347", "metadata": {}, "source": [ "In the time/expiry dimension we will plot the volatility as measured for every calendar day expiry for the four weeks" ] }, { "cell_type": "code", "execution_count": null, "id": "23279170-3494-4923-b8ad-bc26d818ff03", "metadata": {}, "outputs": [], "source": [ "cal = get_calendar(\"all\")\n", "x, y = [], []\n", "for date in cal.cal_date_range(dt(2024, 2, 10), dt(2024, 3, 8)):\n", " x.append(date)\n", " y.append(fxvs.get_smile(date).nodes[0.5])\n", "\n", "fig, ax = plt.subplots(1,1)\n", "plt.xticks(rotation=90)\n", "ax.plot(x,y)" ] }, { "cell_type": "markdown", "id": "c7c99238-acaf-413f-988a-8d182e84f66f", "metadata": {}, "source": [ "### Using Weights\n", "\n", "The comment in the publication is that markets do not assign volatility to calendar days when the market is closed.\n", "In this section we will provide weights that manipulate the forward volatility and align with table 4.7.\n", "\n", "| Date | Weight | Volatility to Expiry |\n", "| ---- | ------ | ------------ |\n", "| 10 Feb '24 | 0.0 | 0.0 |\n", "| 11 Feb '24 | 0.0 | 0.0 |\n", "| **12 Feb '24** | 1.0 | **8.15** |\n", "| 13 Feb '24 | 1.0 | 9.99 |\n", "| 14 Feb '24 | 1.0 | 10.95 |\n", "| 15 Feb '24 | 1.0 | 11.54 |\n", "| **16 Feb '24** | 1.0 | **11.95** |\n", "| 17 Feb '24 | 0.0 | 11.18 |\n", "| 18 Feb '24 | 0.0 | 10.54 |\n", "| 19 Feb '24 | 1.0 | 10.96 |\n", "| 20 Feb '24 | 1.0 | 11.29 |\n", "| 21 Feb '24 | 1.0 | 11.56 |\n", "| 22 Feb '24 | 1.0 | 11.78 |\n", "| **23 Feb '24** | 1.0 | **11.97** |\n", "| 24 Feb '24 | 0.0 | 11.56 |\n", "| 25 Feb '24 | 0.0 | 11.20 |\n", "| 26 Feb '24 | 1.0 | 11.34 |\n", "| 27 Feb '24 | 1.0 | 11.46 |\n", "| 28 Feb '24 | 1.0 | 11.57 |\n", "| 29 Feb '24 | 1.0 | 11.66 |\n", "| **1 Mar '24** | 1.0 | **11.75** |\n", "| 2 Mar '24 | 0.0 | 11.48 |\n", "| 3 Mar '24 | 0.0 | 11.23 |\n", "| 4 Mar '24 | 1.0 | 11.36 |\n", "| 5 Mar '24 | 1.0 | 11.49 |\n", "| 6 Mar '24 | 1.0 | 11.60 |\n", "| 7 Mar '24 | 1.0 | 11.70 |\n", "| **8 Mar '24** | 1.0 | **11.80** |\n", "| 9 Mar '24 | 0.0 | 11.59 |\n", "\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "id": "a330db8d-ae61-42f1-9be3-03aef7e3c32f", "metadata": {}, "source": [ "We can use the calendar methods in *rateslib* to create an indexed *Series* with zero weights where we want to have them." ] }, { "cell_type": "code", "execution_count": null, "id": "9937e9d7-37f9-4d8d-9c09-5734fbfdd192", "metadata": {}, "outputs": [], "source": [ "# Use a generic business day calendar to find the weekends\n", "cal = get_calendar(\"bus\")\n", "weekends = [\n", " _ for _ in cal.cal_date_range(dt(2024, 2, 9), dt(2024, 3, 11))\n", " if _ not in cal.bus_date_range(dt(2024, 2, 9), dt(2024, 3, 11))\n", "]\n", "weights = Series(0.0, index=weekends)\n", "weights" ] }, { "cell_type": "markdown", "id": "7fec8a77-0728-4805-a61a-fb2e914cf655", "metadata": {}, "source": [ "Now we will rebuild an *FXDeltaVolSurface* and plot the difference to before." ] }, { "cell_type": "code", "execution_count": null, "id": "3a809360-e35e-4c0c-8f84-1279ed2fc23e", "metadata": {}, "outputs": [], "source": [ "fxvs_2 = FXDeltaVolSurface(\n", " expiries=[\n", " dt(2024, 2, 12), # Spot\n", " dt(2024, 2, 16), # 1W\n", " dt(2024, 2, 23), # 2W\n", " dt(2024, 3, 1), # 3W\n", " dt(2024, 3, 8), # 4W\n", " ],\n", " delta_indexes=[0.5],\n", " node_values=[[8.15], [11.95], [11.97], [11.75], [11.80]],\n", " eval_date=dt(2024, 2, 9),\n", " delta_type=\"forward\",\n", " weights=weights,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "79a81db2-b221-4fec-8bab-91ac02c06f5a", "metadata": {}, "outputs": [], "source": [ "cal = get_calendar(\"all\")\n", "x, y, y2 = [], [], []\n", "for date in cal.cal_date_range(dt(2024, 2, 10), dt(2024, 3, 8)):\n", " x.append(date)\n", " y.append(fxvs.get_smile(date).nodes[0.5])\n", " y2.append(fxvs_2.get_smile(date).nodes[0.5])\n", "\n", "fig, ax = plt.subplots(1,1)\n", "plt.xticks(rotation=90)\n", "ax.plot(x,y, label=\"excl. weights\")\n", "ax.plot(x,y2, label=\"incl. weights\")\n", "ax.plot([dt(2024, 2, 12), dt(2024, 2, 16), dt(2024, 2, 23), dt(2024, 3, 1), dt(2024, 3, 8)],\n", " [8.15, 11.95, 11.97, 11.75, 11.80],\n", " \"o\", label=\"benchmarks\"\n", " )\n", "ax.legend()" ] }, { "cell_type": "markdown", "id": "30f05648-75b6-41d9-818c-db397cf946e0", "metadata": {}, "source": [ "We observe the familiar sawtooth pattern that is frequently observed in short dated FX market vol." ] } ], "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.4" } }, "nbformat": 4, "nbformat_minor": 5 }