diff --git a/notebooks/code_samples/capital_markets/quickstart_option_pricing_models.ipynb b/notebooks/code_samples/capital_markets/quickstart_option_pricing_models.ipynb index b7446349d..39ed384b4 100644 --- a/notebooks/code_samples/capital_markets/quickstart_option_pricing_models.ipynb +++ b/notebooks/code_samples/capital_markets/quickstart_option_pricing_models.ipynb @@ -35,6 +35,8 @@ " - [Preview the documentation template](#toc4_1_) \n", "- [Model development](#toc5_) \n", "- [Data preparation](#toc6_) \n", + "- [Model development](#toc5_) \n", + "- [Data preparation](#toc6_) \n", " - [Synthetic data generation](#toc6_1_)\n", " - [Data quality](#toc6_2_)\n", " - [Model Calibration](#toc6_3_) \n", @@ -190,7 +192,7 @@ " # api_key=\"...\",\n", " # api_secret=\"...\",\n", " # model=\"...\",\n", - ")\n" + ")" ] }, { @@ -509,6 +511,83 @@ "Let's check quality of the data using outliers and missing data tests." ] }, + { + "cell_type": "markdown", + "id": "8f52ac93", + "metadata": {}, + "source": [ + "#### Outliers detection using IQR method\n", + "Let's visualizes the distribution of outliers in the option_price feature using the Interquartile Range (IQR) method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f1c1ab6f", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_test(\n", + " \"validmind.data_validation.IQROutliersBarPlot:BlackScholes\",\n", + " inputs={\n", + " \"dataset\": vm_bs_market_data,\n", + " },\n", + " title=\"Outliers detection using IQR method for BlackScholes\",\n", + ")\n", + "result.log()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b5e8654", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_test(\n", + " \"validmind.data_validation.IQROutliersTable:BlackScholes\",\n", + " inputs={\n", + " \"dataset\": vm_bs_market_data,\n", + " },\n", + " title=\"Outliers table using IQR method for BlackScholes\",\n", + ")\n", + "result.log()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d96f10c7", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_test(\n", + " \"validmind.data_validation.IQROutliersBarPlot:StochasticVolatility\",\n", + " inputs={\n", + " \"dataset\": vm_sv_market_data,\n", + " },\n", + " title=\"Outliers detection using IQR method for StochasticVolatility\",\n", + ")\n", + "result.log()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5b821d3", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_test(\n", + " \"validmind.data_validation.IQROutliersTable:StochasticVolatility\",\n", + " inputs={\n", + " \"dataset\": vm_sv_market_data,\n", + " },\n", + " title=\"Outliers table using IQR method for StochasticVolatility\",\n", + ")\n", + "result.log()" + ] + }, { "cell_type": "markdown", "id": "8dfc7cc3", @@ -575,7 +654,7 @@ " },\n", " title=\"Missing Values detection for BlackScholes\",\n", ")\n", - "result.log()\n" + "result.log()" ] }, { @@ -609,7 +688,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 18, "id": "5e709e0e", "metadata": {}, "outputs": [], @@ -707,7 +786,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 20, "id": "ac733262", "metadata": {}, "outputs": [], @@ -788,11 +867,12 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 22, "id": "80492c82", "metadata": {}, "outputs": [], "source": [ + "\n", "@vm.test(\"my_custom_tests.Sensitivity\")\n", "def sensitivity_test(model_type, S0, T, r, N, M, strike=None, barrier=None, sigma=None, v0=None, kappa=None,theta=None, xi=None, rho=None):\n", " \"\"\"\n", @@ -819,7 +899,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 23, "id": "b2115371", "metadata": {}, "outputs": [], @@ -838,25 +918,58 @@ "id": "9a798ff8", "metadata": {}, "source": [ - "##### Common plot function" + "##### Common plot function\n", + "Let's create a line plot using the default result output data and log it by passing the function through the `post_process_fn` parameter in the `run_test()` method." ] }, { "cell_type": "code", - "execution_count": 20, - "id": "f6c98fbe", + "execution_count": 24, + "id": "d96969b7", "metadata": {}, "outputs": [], "source": [ - "def plot_results(df, params: dict = None):\n", - " fig2 = plt.figure(figsize=(10, 6))\n", - " plt.plot(df[params[\"x\"]], df[params[\"y\"]], label=params[\"label\"])\n", - " plt.xlabel(params[\"xlabel\"])\n", - " plt.ylabel(params[\"ylabel\"])\n", - " plt.title(params[\"title\"])\n", - " plt.legend()\n", - " plt.grid(True)\n", - " plt.show() " + "from plotly_express import bar\n", + "from validmind.vm_models.figure import Figure\n", + "from validmind.vm_models.result import TestResult\n", + "import plotly.graph_objects as go\n", + "import random\n", + "\n", + "def process_results(result: TestResult):\n", + " # Convert to DataFrame\n", + " df = pd.DataFrame(result.tables[0].data)\n", + " \n", + " # Get the first two column names\n", + " x_col = df.columns[0]\n", + " y_col = df.columns[1]\n", + " \n", + " # Create figure\n", + " fig = go.Figure()\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=df[x_col],\n", + " y=df[y_col],\n", + " mode='lines',\n", + " name=y_col # Use y-axis column name as trace name\n", + " )\n", + " )\n", + " \n", + " fig.update_layout(\n", + " xaxis_title=x_col,\n", + " yaxis_title=y_col,\n", + " showlegend=True,\n", + " template=\"plotly_white\"\n", + " )\n", + "\n", + " result.add_figure(\n", + " Figure(\n", + " figure=fig,\n", + " key=\"sensitivity_plot_\" + str(random.randint(0, 1000000)),\n", + " ref_id=result.ref_id,\n", + " )\n", + " )\n", + "\n", + " return result" ] }, { @@ -893,19 +1006,9 @@ " \"xi\": [0.1],\n", " \"rho\": [-0.5],\n", " },\n", + " post_process_fn= process_results\n", ")\n", - "result.log()\n", - "plot_results(\n", - " pd.DataFrame(result.tables[0].data),\n", - " params={\n", - " \"x\": \"strike\",\n", - " \"y\":\"Option price\",\n", - " \"label\":\"Option price\",\n", - " \"xlabel\":\"strike\",\n", - " \"ylabel\":\"option price\",\n", - " \"title\":\"Knockout Option Price - Strike Level sensitivity\",\n", - " }\n", - ")" + "result.log()" ] }, { @@ -941,19 +1044,10 @@ " \"xi\": [0.1],\n", " \"rho\": [-0.5],\n", " },\n", + " post_process_fn=process_results\n", + "\n", ")\n", - "result.log()\n", - "plot_results(\n", - " pd.DataFrame(result.tables[0].data),\n", - " params={\n", - " \"x\": \"barrier\",\n", - " \"y\":\"Option price\",\n", - " \"label\":\"Option price\",\n", - " \"xlabel\":\"barrier\",\n", - " \"ylabel\":\"option price\",\n", - " \"title\":\"Knockout Option Price - Barrier Level Sensitivity\",\n", - " }\n", - ")" + "result.log()" ] }, { @@ -967,7 +1061,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 27, "id": "7bdd02ed", "metadata": {}, "outputs": [], @@ -994,9 +1088,83 @@ "metadata": {}, "source": [ "##### Rho (correlation) and Theta (long term vol) stress test\n", + "First, we create a surface plot to visualize the option price with respect to two variables." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "77d49b6a", + "metadata": {}, + "outputs": [], + "source": [ + "def two_parameters_stress_surface_plot(result: TestResult):\n", + " import plotly.graph_objects as go\n", + " import numpy as np\n", + " import pandas as pd\n", + " # Convert to DataFrame\n", + " data = pd.DataFrame(result.tables[0].data)\n", + " \n", + " # Get column names (assuming first column is x, next two are y1 and y2)\n", + " z_col = data.columns[2]\n", + " x_col = data.columns[0]\n", + " y_col = data.columns[1]\n", + " \n", + " # Get unique values for x and y\n", + " x_unique = np.sort(data[x_col].unique())\n", + " y_unique = np.sort(data[y_col].unique())\n", + " \n", + " # Create meshgrid\n", + " X, Y = np.meshgrid(x_unique, y_unique)\n", + " \n", + " # Create Z matrix\n", + " Z = np.zeros_like(X)\n", + " for i, x_val in enumerate(x_unique):\n", + " for j, y_val in enumerate(y_unique):\n", + " mask = (data[x_col] == x_val) & (data[y_col] == y_val)\n", + " if mask.any():\n", + " Z[j, i] = data.loc[mask, z_col].iloc[0]\n", + " \n", + " # Create the 3D surface plot\n", + " fig = go.Figure(data=[go.Surface(x=X, y=Y, z=Z)])\n", + " \n", + " # Update the layout\n", + " fig.update_layout(\n", + " title=f'3D Surface Plot of {z_col}',\n", + " scene=dict(\n", + " xaxis_title=x_col,\n", + " yaxis_title=y_col,\n", + " zaxis_title=z_col,\n", + " camera=dict(\n", + " up=dict(x=0, y=0, z=1),\n", + " center=dict(x=0, y=0, z=0),\n", + " eye=dict(x=1.5, y=1.5, z=1.5)\n", + " )\n", + " ),\n", + " width=900,\n", + " height=700,\n", + " margin=dict(l=65, r=50, b=65, t=90)\n", + " )\n", + "\n", + " result.add_figure(\n", + " Figure(\n", + " figure=fig,\n", + " key=\"sensitivity_plot_\" + str(random.randint(0, 1000000)),\n", + " ref_id=result.ref_id,\n", + " )\n", + " )\n", + "\n", + " return result" + ] + }, + { + "cell_type": "markdown", + "id": "6b586190", + "metadata": {}, + "source": [ "Let's evaluates the sensitivity of a model's output to changes in the correlation parameter (rho) and the long-term variance parameter (theta) within a stochastic volatility framework.\n", "\n", - "This test is useful for understanding how variations in these parameters affect the model's valuation, which is crucial for risk management and model validation.\n" + "This test is useful for understanding how variations in these parameters affect the model's valuation, which is crucial for risk management and model validation." ] }, { @@ -1021,10 +1189,11 @@ " \"r\": [r],\n", " \"v0\": [0.2],\n", " \"kappa\": [2],\n", - " \"theta\": [0, 0.2, 0.4],\n", + " \"theta\": list(np.linspace(0,0.8, 10)),\n", " \"xi\": [0.1],\n", - " \"rho\": [-1, -0.5, 0, 0.5, 1],\n", + " \"rho\": list(np.linspace(-1,0.8, 10)),\n", " },\n", + " post_process_fn=two_parameters_stress_surface_plot\n", ")\n", "result.log()\n" ] @@ -1044,6 +1213,8 @@ "metadata": {}, "outputs": [], "source": [ + "\n", + "\n", "result = run_test(\n", " \"my_custom_tests.Stressing:TheRhoAndXiParameters\",\n", " param_grid={\n", @@ -1058,9 +1229,10 @@ " \"v0\": [0.2],\n", " \"kappa\": [2],\n", " \"theta\": [0.2],\n", - " \"xi\": [0, 0.2, 0.4],\n", - " \"rho\": [-1, -0.5, 0, 0.5, 1],\n", + " \"xi\": list(np.linspace(0,0.8, 10)),\n", + " \"rho\": list(np.linspace(-1,0.8, 10)),\n", " },\n", + " post_process_fn=two_parameters_stress_surface_plot\n", ")\n", "result.log()\n" ] @@ -1096,19 +1268,9 @@ " \"r\": [r],\n", " \"sigma\": list(np.linspace(0.2, 0.8, 10)),\n", " },\n", + " post_process_fn=process_results\n", ")\n", - "result.log()\n", - "plot_results(\n", - " pd.DataFrame(result.tables[0].data),\n", - " params={\n", - " \"x\": \"sigma\",\n", - " \"y\":\"Option price\",\n", - " \"label\":\"Option price\",\n", - " \"xlabel\":\"Sigma\",\n", - " \"ylabel\":\"Option price\",\n", - " \"title\":\"Knockout Option Price - Stress sigma\",\n", - " }\n", - ")" + "result.log()\n" ] }, { @@ -1145,19 +1307,9 @@ " \"xi\": [0.1],\n", " \"rho\": [-0.5],\n", " },\n", + " post_process_fn=process_results\n", ")\n", - "result.log()\n", - "plot_results(\n", - " pd.DataFrame(result.tables[0].data),\n", - " params={\n", - " \"x\": \"kappa\",\n", - " \"y\":\"Option price\",\n", - " \"label\":\"Option price\",\n", - " \"xlabel\":\"kappa\",\n", - " \"ylabel\":\"option price\",\n", - " \"title\":\"Knockout Option Price - stress kappa\",\n", - " }\n", - ")" + "result.log()\n" ] }, { @@ -1193,19 +1345,9 @@ " \"xi\": [0.1],\n", " \"rho\": [-0.5],\n", " },\n", + " post_process_fn=process_results\n", ")\n", - "result.log()\n", - "plot_results(\n", - " pd.DataFrame(result.tables[0].data),\n", - " params={\n", - " \"x\": \"theta\",\n", - " \"y\":\"Option price\",\n", - " \"label\":\"Option price\",\n", - " \"xlabel\":\"theta\",\n", - " \"ylabel\":\"option price\",\n", - " \"title\":\"Knockout Option Price - stress theta\",\n", - " }\n", - ")" + "result.log()\n" ] }, { @@ -1241,19 +1383,9 @@ " \"xi\": list(np.linspace(0.05, 0.95, 10)),\n", " \"rho\": [-0.5],\n", " },\n", + " post_process_fn=process_results\n", ")\n", - "result.log()\n", - "plot_results(\n", - " pd.DataFrame(result.tables[0].data),\n", - " params={\n", - " \"x\": \"xi\",\n", - " \"y\":\"Option price\",\n", - " \"label\":\"Option price\",\n", - " \"xlabel\":\"xi\",\n", - " \"ylabel\":\"option price\",\n", - " \"title\":\"Knockout Option Price - stress xi\",\n", - " }\n", - ")" + "result.log()\n" ] }, { @@ -1289,19 +1421,9 @@ " \"xi\": [0.1],\n", " \"rho\": list(np.linspace(-1.0, 1.0, 20)),\n", " },\n", + " post_process_fn=process_results\n", ")\n", - "result.log()\n", - "plot_results(\n", - " pd.DataFrame(result.tables[0].data),\n", - " params={\n", - " \"x\": \"rho\",\n", - " \"y\":\"Option price\",\n", - " \"label\":\"Option price\",\n", - " \"xlabel\":\"rho\",\n", - " \"ylabel\":\"option price\",\n", - " \"title\":\"Knockout Option Price - stress rho\",\n", - " }\n", - ")" + "result.log()\n" ] }, { diff --git a/pyproject.toml b/pyproject.toml index baeee3750..8b137eaec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ description = "ValidMind Library" license = "Commercial License" name = "validmind" readme = "README.pypi.md" -version = "2.6.11" +version = "2.7.0" [tool.poetry.dependencies] aiohttp = {extras = ["speedups"], version = "*"} diff --git a/validmind/__version__.py b/validmind/__version__.py index 396adecc4..2614ce9d9 100644 --- a/validmind/__version__.py +++ b/validmind/__version__.py @@ -1 +1 @@ -__version__ = "2.6.11" +__version__ = "2.7.0"