{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "54352345",
   "metadata": {},
   "source": [
    "# Quickstart for Heston option pricing model using QuantLib \n",
    "\n",
    "Welcome! Let's get you started with the basic process of documenting models with ValidMind.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "The Heston option pricing model is a popular stochastic volatility model used to price options. Developed by Steven Heston in 1993, the model assumes that the asset's volatility follows a mean-reverting square-root process, allowing it to capture the empirical observation of volatility \"clustering\" in financial markets. This model is particularly useful for assets where volatility is not constant, making it a favored approach in quantitative finance for pricing complex derivatives.\n",
    "\n",
    "Here’s an overview of the Heston model as implemented in QuantLib, a powerful library for quantitative finance:\n",
    "\n",
    "### Model Assumptions and Characteristics\n",
    "1. **Stochastic Volatility**: The volatility is modeled as a stochastic process, following a mean-reverting square-root process (Cox-Ingersoll-Ross process).\n",
    "2. **Correlated Asset and Volatility Processes**: The asset price and volatility are assumed to be correlated, allowing the model to capture the \"smile\" effect observed in implied volatilities.\n",
    "3. **Risk-Neutral Dynamics**: The Heston model is typically calibrated under a risk-neutral measure, which allows for direct application to pricing.\n",
    "\n",
    "### Heston Model Parameters\n",
    "The model is governed by a set of key parameters:\n",
    "- **S0**: Initial stock price\n",
    "- **v0**: Initial variance of the asset price\n",
    "- **kappa**: Speed of mean reversion of the variance\n",
    "- **theta**: Long-term mean level of variance\n",
    "- **sigma**: Volatility of volatility (vol of vol)\n",
    "- **rho**: Correlation between the asset price and variance processes\n",
    "\n",
    "The dynamics of the asset price \\( S \\) and the variance \\( v \\) under the Heston model are given by:\n",
    "\n",
    "$$\n",
    "dS_t = r S_t \\, dt + \\sqrt{v_t} S_t \\, dW^S_t\n",
    "$$\n",
    "\n",
    "$$\n",
    "dv_t = \\kappa (\\theta - v_t) \\, dt + \\sigma \\sqrt{v_t} \\, dW^v_t\n",
    "$$\n",
    "\n",
    "where \\( $dW^S$ \\) and \\( $dW^v$ \\) are Wiener processes with correlation \\( $\\rho$ \\).\n",
    "\n",
    "### Advantages and Limitations\n",
    "- **Advantages**:\n",
    "  - Ability to capture volatility smiles and skews.\n",
    "  - More realistic pricing for options on assets with stochastic volatility.\n",
    "- **Limitations**:\n",
    "  - Calibration can be complex due to the number of parameters.\n",
    "  - Computationally intensive compared to simpler models like Black-Scholes.\n",
    "\n",
    "This setup provides a robust framework for pricing and analyzing options with stochastic volatility dynamics. QuantLib’s implementation makes it easy to experiment with different parameter configurations and observe their effects on pricing.\n",
    "\n",
    "You will learn how to initialize the ValidMind Library, develop a option pricing model, and then write custom tests that can be used for sensitivity and stress testing to quickly generate documentation about model."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6517f34",
   "metadata": {},
   "source": [
    "<a id='toc0_'></a>\n",
    "\n",
    "## Contents    \n",
    "- [About ValidMind](#toc1_)    \n",
    "  - [Before you begin](#toc1_1_)    \n",
    "  - [New to ValidMind?](#toc1_2_)    \n",
    "  - [Key concepts](#toc1_3_)    \n",
    "- [Install the ValidMind Library](#toc2_)\n",
    "  - [Get your code snippet](#toc3_1_)\n",
    "- [Initialize the ValidMind Library](#toc3_)    \n",
    "- [Initialize the Python environment](#toc4_)    \n",
    "  - [Preview the documentation template](#toc4_1_)    \n",
    "- [Data preparation](#toc5_)\n",
    "  - [Data quality](#toc5_1_)\n",
    "- [Model development](#toc6_)   \n",
    "  - [Model Calibration](#toc6_1_)    \n",
    "- [Model Evaluation](#toc7_)    \n",
    "  - [Benchmark Testing](#toc7_1_)    \n",
    "  - [Sensitivity Testing](#toc7_2_)    \n",
    "  - [Stress Testing](#toc7_3_)    \n",
    "- [Next steps](#toc8_)    \n",
    "  - [Work with your model documentation](#toc8_1_)    \n",
    "  - [Discover more learning resources](#toc8_2_)    \n",
    "\n",
    "<!-- vscode-jupyter-toc-config\n",
    "\tnumbering=false\n",
    "\tanchor=true\n",
    "\tflat=false\n",
    "\tminLevel=2\n",
    "\tmaxLevel=4\n",
    "\t/vscode-jupyter-toc-config -->\n",
    "<!-- THIS CELL WILL BE REPLACED ON TOC UPDATE. DO NOT WRITE YOUR TEXT IN THIS CELL -->"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d23a4c78",
   "metadata": {},
   "source": [
    "<a id='toc1_'></a>\n",
    "\n",
    "## About ValidMind\n",
    "\n",
    "ValidMind is a suite of tools for managing model risk, including risk associated with AI and statistical models.\n",
    "\n",
    "You use the ValidMind Library to automate documentation and validation tests, and then use the ValidMind Platform to collaborate on model documentation. Together, these products simplify model risk management, facilitate compliance with regulations and institutional standards, and enhance collaboration between yourself and model validators.\n",
    "\n",
    "<a id='toc1_1_'></a>\n",
    "\n",
    "### Before you begin\n",
    "\n",
    "This notebook assumes you have basic familiarity with Python, including an understanding of how functions work. If you are new to Python, you can still run the notebook but we recommend further familiarizing yourself with the language. \n",
    "\n",
    "If you encounter errors due to missing modules in your Python environment, install the modules with `pip install`, and then re-run the notebook. For more help, refer to [Installing Python Modules](https://docs.python.org/3/installing/index.html).\n",
    "\n",
    "<a id='toc1_2_'></a>\n",
    "\n",
    "### New to ValidMind?\n",
    "\n",
    "If you haven't already seen our documentation on the [ValidMind Library](https://docs.validmind.ai/developer/validmind-library.html), we recommend you begin by exploring the available resources in this section. There, you can learn more about documenting models and running tests, as well as find code samples and our Python Library API reference.\n",
    "\n",
    "<div class=\"alert alert-block alert-info\" style=\"background-color: #f7e4ee; color: black; border: 1px solid black;\"><b>For access to all features available in this notebook, create a free ValidMind account.</b>\n",
    "<br></br>\n",
    "Signing up is FREE — <a href=\"https://docs.validmind.ai/guide/configuration/register-with-validmind.html\"><b>Register with ValidMind</b></a></div>\n",
    "\n",
    "<a id='toc1_3_'></a>\n",
    "\n",
    "### Key concepts\n",
    "\n",
    "**Model documentation**: A structured and detailed record pertaining to a model, encompassing key components such as its underlying assumptions, methodologies, data sources, inputs, performance metrics, evaluations, limitations, and intended uses. It serves to ensure transparency, adherence to regulatory requirements, and a clear understanding of potential risks associated with the model’s application.\n",
    "\n",
    "**Documentation template**: Functions as a test suite and lays out the structure of model documentation, segmented into various sections and sub-sections. Documentation templates define the structure of your model documentation, specifying the tests that should be run, and how the results should be displayed.\n",
    "\n",
    "**Tests**: A function contained in the ValidMind Library, designed to run a specific quantitative test on the dataset or model. Tests are the building blocks of ValidMind, used to evaluate and document models and datasets, and can be run individually or as part of a suite defined by your model documentation template.\n",
    "\n",
    "**Custom tests**: Custom tests are functions that you define to evaluate your model or dataset. These functions can be registered via the ValidMind Library to be used with the ValidMind Platform.\n",
    "\n",
    "**Inputs**: Objects to be evaluated and documented in the ValidMind Library. They can be any of the following:\n",
    "\n",
    "  - **model**: A single model that has been initialized in ValidMind with [`vm.init_model()`](https://docs.validmind.ai/validmind/validmind.html#init_model).\n",
    "  - **dataset**: Single dataset that has been initialized in ValidMind with [`vm.init_dataset()`](https://docs.validmind.ai/validmind/validmind.html#init_dataset).\n",
    "  - **models**: A list of ValidMind models - usually this is used when you want to compare multiple models in your custom test.\n",
    "  - **datasets**: A list of ValidMind datasets - usually this is used when you want to compare multiple datasets in your custom test. See this [example](https://docs.validmind.ai/notebooks/how_to/run_tests_that_require_multiple_datasets.html) for more information.\n",
    "\n",
    "**Parameters**: Additional arguments that can be passed when running a ValidMind test, used to pass additional information to a test, customize its behavior, or provide additional context.\n",
    "\n",
    "**Outputs**: Custom tests can return elements like tables or plots. Tables may be a list of dictionaries (each representing a row) or a pandas DataFrame. Plots may be matplotlib or plotly figures.\n",
    "\n",
    "**Test suites**: Collections of tests designed to run together to automate and generate model documentation end-to-end for specific use-cases.\n",
    "\n",
    "Example: the [`classifier_full_suite`](https://docs.validmind.ai/validmind/validmind/test_suites/classifier.html#ClassifierFullSuite) test suite runs tests from the [`tabular_dataset`](https://docs.validmind.ai/validmind/validmind/test_suites/tabular_datasets.html) and [`classifier`](https://docs.validmind.ai/validmind/validmind/test_suites/classifier.html) test suites to fully document the data and model sections for binary classification model use-cases.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b9b8bd96",
   "metadata": {},
   "source": [
    "<a id='toc2_'></a>\n",
    "\n",
    "## Install the ValidMind Library\n",
    "\n",
    "To install the library:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c9cbaa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install -q validmind"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b4b9200",
   "metadata": {},
   "source": [
    "To install the QuantLib library:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd973756",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install -q QuantLib"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "428ec832",
   "metadata": {},
   "source": [
    "<a id='toc3_'></a>\n",
    "\n",
    "## Initialize the ValidMind Library\n",
    "\n",
    "ValidMind generates a unique _code snippet_ for each registered model to connect with your developer environment. You initialize the ValidMind Library with this code snippet, which ensures that your documentation and tests are uploaded to the correct model when you run the notebook.\n",
    "\n",
    "<a id='toc3_1_'></a>\n",
    "\n",
    "### Get your code snippet\n",
    "\n",
    "1. In a browser, [log in to ValidMind](https://docs.validmind.ai/guide/configuration/log-in-to-validmind.html).\n",
    "\n",
    "2. In the left sidebar, navigate to **Model Inventory** and click **+ Register Model**.\n",
    "\n",
    "3. Enter the model details and click **Continue**. ([Need more help?](https://docs.validmind.ai/guide/model-inventory/register-models-in-inventory.html))\n",
    "\n",
    "   For example, to register a model for use with this notebook, select:\n",
    "\n",
    "   - Documentation template: `Capital markets`\n",
    "\n",
    "   You can fill in other options according to your preference.\n",
    "\n",
    "4. Go to **Getting Started** and click **Copy snippet to clipboard**.\n",
    "\n",
    "Next, [load your model identifier credentials from an `.env` file](https://docs.validmind.ai/developer/model-documentation/store-credentials-in-env-file.html) or replace the placeholder with your own code snippet:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2f8e48dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load your model identifier credentials from an `.env` file\n",
    "\n",
    "%load_ext dotenv\n",
    "%dotenv .env\n",
    "\n",
    "# Or replace with your code snippet\n",
    "\n",
    "import validmind as vm\n",
    "\n",
    "vm.init(\n",
    "    # api_host=\"...\",\n",
    "    # api_key=\"...\",\n",
    "    # api_secret=\"...\",\n",
    "    # model=\"...\",\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee58640b",
   "metadata": {},
   "source": [
    "<a id='toc4_'></a>\n",
    "\n",
    "## Initialize the Python environment\n",
    "\n",
    "Next, let's import the necessary libraries and set up your Python environment for data analysis:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "86a8da01",
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from scipy.optimize import minimize\n",
    "import yfinance as yf\n",
    "import QuantLib as ql\n",
    "from validmind.tests import run_test"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d41d310b",
   "metadata": {},
   "source": [
    "<a id='toc4_1_'></a>\n",
    "\n",
    "### Preview the documentation template\n",
    "\n",
    "A template predefines sections for your model documentation and provides a general outline to follow, making the documentation process much easier.\n",
    "\n",
    "You will upload documentation and test results into this template later on. For now, take a look at the structure that the template provides with the `vm.preview_template()` function from the ValidMind library and note the empty sections:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6e72dd6",
   "metadata": {},
   "outputs": [],
   "source": [
    "vm.preview_template()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "880ed312",
   "metadata": {},
   "source": [
    "<a id='toc5_'></a>\n",
    "## Data Preparation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d557482e",
   "metadata": {},
   "source": [
    "### Market Data Sources\n",
    "#### Helper functions\n",
    "Let's define helper function retrieve to option data from Yahoo Finance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "b27d7fc3",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_market_data(ticker, expiration_date_str):\n",
    "    \"\"\"\n",
    "    Fetch option market data from Yahoo Finance for the given ticker and expiration date.\n",
    "    Returns a list of tuples: (strike, maturity, option_price).\n",
    "    \"\"\"\n",
    "    # Create a Ticker object for the specified stock\n",
    "    stock = yf.Ticker(ticker)\n",
    "\n",
    "    # Get all available expiration dates for options\n",
    "    option_dates = stock.options\n",
    "\n",
    "    # Check if the requested expiration date is available\n",
    "    if expiration_date_str not in option_dates:\n",
    "        raise ValueError(f\"Expiration date {expiration_date_str} not available for {ticker}. Available dates: {option_dates}\")\n",
    "\n",
    "    # Get the option chain for the specified expiration date\n",
    "    option_chain = stock.option_chain(expiration_date_str)\n",
    "\n",
    "    # Get call options (or you can use puts as well based on your requirement)\n",
    "    calls = option_chain.calls\n",
    "\n",
    "    # Convert expiration_date_str to QuantLib Date\n",
    "    expiry_date_parts = list(map(int, expiration_date_str.split('-')))  # Split YYYY-MM-DD\n",
    "    maturity_date = ql.Date(expiry_date_parts[2], expiry_date_parts[1], expiry_date_parts[0])  # Convert to QuantLib Date\n",
    "\n",
    "    # Create a list to store strike prices, maturity dates, and option prices\n",
    "    market_data = []\n",
    "    for index, row in calls.iterrows():\n",
    "        strike = row['strike']\n",
    "        option_price = row['lastPrice']  # You can also use 'bid', 'ask', 'mid', etc.\n",
    "        market_data.append((strike, maturity_date, option_price))\n",
    "    df = pd.DataFrame(market_data, columns = ['strike', 'maturity_date', 'option_price'])\n",
    "    return df"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "131fa2a8",
   "metadata": {},
   "source": [
    "Let's define helper function retrieve to stock data from Yahoo Finance. This helper function to calculate spot price, dividend yield, volatility and risk free rate using the underline stock data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f29f07ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_option_parameters(ticker):\n",
    "    # Fetch historical data for the stock\n",
    "    stock_data = yf.Ticker(ticker)\n",
    "    \n",
    "    # Get the current spot price\n",
    "    spot_price = stock_data.history(period=\"1d\")['Close'].iloc[-1]\n",
    "    \n",
    "    # Get dividend yield\n",
    "    dividend_rate = stock_data.dividends.mean() / spot_price if not stock_data.dividends.empty else 0.0\n",
    "    \n",
    "    # Estimate volatility (standard deviation of log returns)\n",
    "    hist_data = stock_data.history(period=\"1y\")['Close']\n",
    "    log_returns = np.log(hist_data / hist_data.shift(1)).dropna()\n",
    "    volatility = np.std(log_returns) * np.sqrt(252)  # Annualized volatility\n",
    "    \n",
    "    # Assume a risk-free rate from some known data (can be fetched from market data, here we use 0.001)\n",
    "    risk_free_rate = 0.001\n",
    "    \n",
    "    # Return the calculated parameters\n",
    "    return {\n",
    "        \"spot_price\": spot_price,\n",
    "        \"volatility\": volatility,\n",
    "        \"dividend_rate\": dividend_rate,\n",
    "        \"risk_free_rate\": risk_free_rate\n",
    "    }"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c34bc58a",
   "metadata": {},
   "source": [
    "### Market Data Quality and Availability\n",
    "Next, let's specify ticker and expiration date to get market data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "0e129f19",
   "metadata": {},
   "outputs": [],
   "source": [
    "ticker = \"MSFT\"\n",
    "expiration_date =  \"2024-12-13\" # Example expiration date in 'YYYY-MM-DD' form\n",
    "\n",
    "market_data = get_market_data(ticker=ticker, expiration_date_str=expiration_date)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebdc4336",
   "metadata": {},
   "source": [
    "### Initialize the ValidMind datasets\n",
    "\n",
    "Before you can run tests, you must first initialize a ValidMind dataset object using the [`init_dataset`](https://docs.validmind.ai/validmind/validmind.html#init_dataset) function from the ValidMind (`vm`) module."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fc11db86",
   "metadata": {},
   "outputs": [],
   "source": [
    "vm_market_data = vm.init_dataset(\n",
    "    dataset=market_data,\n",
    "    input_id=\"market_data\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d3e119e",
   "metadata": {},
   "source": [
    "<a id='toc5_1'></a>\n",
    "\n",
    "### Data Quality\n",
    "Let's check quality of the data using outliers and missing data tests."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "886e1b88",
   "metadata": {},
   "source": [
    "#### Isolation Forest Outliers Test\n",
    "Let's detects anomalies in the dataset using the Isolation Forest algorithm, visualized through scatter plots."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6fe3def",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"validmind.data_validation.IsolationForestOutliers\",\n",
    "    inputs={\n",
    "        \"dataset\": vm_market_data,\n",
    "    },\n",
    "    title=\"Outliers detection using Isolation Forest\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Missing Values Test\n",
    "Let's evaluates dataset quality by ensuring the missing value ratio across all features does not exceed a set threshold."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3180e5fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"validmind.data_validation.MissingValues\",\n",
    "    inputs={\n",
    "        \"dataset\": vm_market_data,\n",
    "    },\n",
    "    title=\"Missing Values detection\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1db5f835",
   "metadata": {},
   "source": [
    "\n",
    "#### Model parameters \n",
    "Let's calculate the model parameters using from stock data "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "d28df598",
   "metadata": {},
   "outputs": [],
   "source": [
    "option_params = get_option_parameters(ticker=ticker)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d6a8447",
   "metadata": {},
   "source": [
    "<a id='toc6_'></a>\n",
    "## Model development - Heston Option price"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "dc070da8",
   "metadata": {},
   "outputs": [],
   "source": [
    "class HestonModel:\n",
    "\n",
    "    def __init__(self, ticker, expiration_date_str, calculation_date, spot_price, dividend_rate, risk_free_rate):\n",
    "        self.ticker = ticker\n",
    "        self.expiration_date_str = expiration_date_str,\n",
    "        self.calculation_date = calculation_date\n",
    "        self.spot_price = spot_price\n",
    "        self.dividend_rate = dividend_rate\n",
    "        self.risk_free_rate = risk_free_rate\n",
    "    \n",
    "    def predict_option_price(self, strike, maturity_date, spot_price, v0=None, theta=None, kappa=None, sigma=None, rho=None):\n",
    "        # Set the evaluation date\n",
    "        ql.Settings.instance().evaluationDate = self.calculation_date\n",
    "\n",
    "        # Construct the European Option\n",
    "        payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike)\n",
    "        exercise = ql.EuropeanExercise(maturity_date)\n",
    "        european_option = ql.VanillaOption(payoff, exercise)\n",
    "\n",
    "        # Yield term structures for risk-free rate and dividend\n",
    "        riskFreeTS = ql.YieldTermStructureHandle(ql.FlatForward(calculation_date, self.risk_free_rate, ql.Actual365Fixed()))\n",
    "        dividendTS = ql.YieldTermStructureHandle(ql.FlatForward(calculation_date, self.dividend_rate, ql.Actual365Fixed()))\n",
    "\n",
    "        # Initial stock price\n",
    "        initialValue = ql.QuoteHandle(ql.SimpleQuote(spot_price))\n",
    "\n",
    "        # Heston process parameters\n",
    "        heston_process = ql.HestonProcess(riskFreeTS, dividendTS, initialValue, v0, kappa, theta, sigma, rho)\n",
    "        hestonModel = ql.HestonModel(heston_process)\n",
    "\n",
    "        # Use the Heston analytic engine\n",
    "        engine = ql.AnalyticHestonEngine(hestonModel)\n",
    "        european_option.setPricingEngine(engine)\n",
    "\n",
    "        # Calculate the Heston model price\n",
    "        h_price = european_option.NPV()\n",
    "\n",
    "        return h_price\n",
    "\n",
    "    def predict_american_option_price(self, strike, maturity_date, spot_price, v0=None, theta=None, kappa=None, sigma=None, rho=None):\n",
    "        # Set the evaluation date\n",
    "        ql.Settings.instance().evaluationDate = self.calculation_date\n",
    "\n",
    "        # Construct the American Option\n",
    "        payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike)\n",
    "        exercise = ql.AmericanExercise(self.calculation_date, maturity_date)\n",
    "        american_option = ql.VanillaOption(payoff, exercise)\n",
    "\n",
    "        # Yield term structures for risk-free rate and dividend\n",
    "        riskFreeTS = ql.YieldTermStructureHandle(ql.FlatForward(self.calculation_date, self.risk_free_rate, ql.Actual365Fixed()))\n",
    "        dividendTS = ql.YieldTermStructureHandle(ql.FlatForward(self.calculation_date, self.dividend_rate, ql.Actual365Fixed()))\n",
    "\n",
    "        # Initial stock price\n",
    "        initialValue = ql.QuoteHandle(ql.SimpleQuote(spot_price))\n",
    "\n",
    "        # Heston process parameters\n",
    "        heston_process = ql.HestonProcess(riskFreeTS, dividendTS, initialValue, v0, kappa, theta, sigma, rho)\n",
    "        heston_model = ql.HestonModel(heston_process)\n",
    "\n",
    "\n",
    "        payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike)\n",
    "        exercise = ql.AmericanExercise(self.calculation_date, maturity_date)\n",
    "        american_option = ql.VanillaOption(payoff, exercise)\n",
    "        heston_fd_engine = ql.FdHestonVanillaEngine(heston_model)\n",
    "        american_option.setPricingEngine(heston_fd_engine)\n",
    "        option_price = american_option.NPV()\n",
    "\n",
    "        return option_price\n",
    "\n",
    "    def objective_function(self, params, market_data, spot_price, dividend_rate, risk_free_rate):\n",
    "        v0, theta, kappa, sigma, rho = params\n",
    "\n",
    "        # Sum of squared differences between market prices and model prices\n",
    "        error = 0.0\n",
    "        for i, row in market_data.iterrows():\n",
    "            model_price = self.predict_option_price(row['strike'], row['maturity_date'], spot_price, \n",
    "                                            v0, theta, kappa, sigma, rho)\n",
    "            error += (model_price - row['option_price']) ** 2\n",
    "        \n",
    "        return error\n",
    "\n",
    "    def calibrate_model(self, ticker, expiration_date_str):\n",
    "        # Get the option market data dynamically from Yahoo Finance\n",
    "        market_data = get_market_data(ticker, expiration_date_str)\n",
    "\n",
    "        # Initial guesses for Heston parameters\n",
    "        initial_params = [0.04, 0.04, 0.1, 0.1, -0.75]\n",
    "\n",
    "        # Bounds for the parameters to ensure realistic values\n",
    "        bounds = [(0.0001, 1.0),  # v0\n",
    "                (0.0001, 1.0),  # theta\n",
    "                (0.001, 2.0),   # kappa\n",
    "                (0.001, 1.0),   # sigma\n",
    "                (-0.75, 0.0)]    # rho\n",
    "\n",
    "        # Optimize the parameters to minimize the error between model and market prices\n",
    "        result = minimize(self.objective_function, initial_params, args=(market_data, self.spot_price, self.dividend_rate, self.risk_free_rate),\n",
    "                        bounds=bounds, method='L-BFGS-B')\n",
    "\n",
    "        # Optimized Heston parameters\n",
    "        v0_opt, theta_opt, kappa_opt, sigma_opt, rho_opt = result.x\n",
    "\n",
    "        return v0_opt, theta_opt, kappa_opt, sigma_opt, rho_opt\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7eb0ce1e",
   "metadata": {},
   "source": [
    "<a id='toc6_1'></a>\n",
    "### Model Calibration\n",
    "* The calibration process aims to optimize the Heston model parameters (v0, theta, kappa, sigma, rho) by minimizing the difference between model-predicted option prices and observed market prices.\n",
    "* In this implementation, the model is calibrated to current market data, specifically using option prices from the selected ticker and expiration date.\n",
    "\n",
    "Let's specify `calculation_date` and `strike_price` as input parameters for the model to verify its functionality and confirm it operates as expected."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ba00e92",
   "metadata": {},
   "outputs": [],
   "source": [
    "calculation_date = ql.Date(26, 11, 2024)\n",
    "# Convert expiration date string to QuantLib.Date\n",
    "expiry_date_parts = list(map(int, expiration_date.split('-')))\n",
    "maturity_date = ql.Date(expiry_date_parts[2], expiry_date_parts[1], expiry_date_parts[0])\n",
    "strike_price = 460.0\n",
    "\n",
    "hm = HestonModel(\n",
    "    ticker=ticker,\n",
    "    expiration_date_str= expiration_date,\n",
    "    calculation_date= calculation_date,\n",
    "    spot_price= option_params['spot_price'],\n",
    "    dividend_rate = option_params['dividend_rate'],\n",
    "    risk_free_rate = option_params['risk_free_rate']\n",
    ")\n",
    "\n",
    "# Let's calibrate model\n",
    "v0_opt, theta_opt, kappa_opt, sigma_opt, rho_opt = hm.calibrate_model(ticker, expiration_date)\n",
    "print(f\"Optimized Heston parameters: v0={v0_opt}, theta={theta_opt}, kappa={kappa_opt}, sigma={sigma_opt}, rho={rho_opt}\")\n",
    "\n",
    "\n",
    "# option price\n",
    "h_price = hm.predict_option_price(strike_price, maturity_date, option_params['spot_price'], v0_opt, theta_opt, kappa_opt, sigma_opt, rho_opt)\n",
    "print(\"The Heston model price for the option is:\", h_price)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3066572",
   "metadata": {},
   "source": [
    "<a id='toc7_'></a>\n",
    "\n",
    "### Model Evaluation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cfbc333b",
   "metadata": {},
   "source": [
    "<a id='toc7_1'></a>\n",
    "#### Benchmark Testing\n",
    "The benchmark testing framework provides a robust way to validate the Heston model implementation and understand the relationships between European and American option prices under stochastic volatility conditions.\n",
    "Let's compares European and American option prices using the Heston model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "810cf887",
   "metadata": {},
   "outputs": [],
   "source": [
    "@vm.test(\"my_custom_tests.BenchmarkTest\")\n",
    "def benchmark_test(hm_model, strikes, maturity_date, spot_price, v0=None, theta=None, kappa=None, sigma=None, rho=None):\n",
    "    \"\"\"\n",
    "    Compares European and American option prices using the Heston model.\n",
    "\n",
    "    This test evaluates the price differences between European and American options\n",
    "    across multiple strike prices while keeping other parameters constant. The comparison\n",
    "    helps understand the early exercise premium of American options over their European\n",
    "    counterparts under stochastic volatility conditions.\n",
    "\n",
    "    Args:\n",
    "        hm_model: HestonModel instance for option pricing calculations\n",
    "        strikes (list[float]): List of strike prices to test\n",
    "        maturity_date (ql.Date): Option expiration date in QuantLib format\n",
    "        spot_price (float): Current price of the underlying asset\n",
    "        v0 (float, optional): Initial variance. Defaults to None.\n",
    "        theta (float, optional): Long-term variance. Defaults to None.\n",
    "        kappa (float, optional): Mean reversion rate. Defaults to None.\n",
    "        sigma (float, optional): Volatility of variance. Defaults to None.\n",
    "        rho (float, optional): Correlation between asset and variance. Defaults to None.\n",
    "\n",
    "    Returns:\n",
    "        dict: Contains a DataFrame with the following columns:\n",
    "            - Strike: Strike prices tested\n",
    "            - Maturity date: Expiration date for all options\n",
    "            - Spot price: Current underlying price\n",
    "            - european model price: Prices for European options\n",
    "            - american model price: Prices for American options\n",
    "\"\"\"\n",
    "    american_derived_prices = []\n",
    "    european_derived_prices = []\n",
    "    for K in strikes:\n",
    "        european_derived_prices.append(hm_model.predict_option_price(K, maturity_date, spot_price, v0, theta, kappa, sigma, rho))\n",
    "        american_derived_prices.append(hm_model.predict_american_option_price(K, maturity_date, spot_price, v0, theta, kappa, sigma, rho))\n",
    "\n",
    "    data = {\n",
    "        \"Strike\": strikes,\n",
    "        \"Maturity date\": [maturity_date] * len(strikes),\n",
    "        \"Spot price\": [spot_price] * len(strikes),\n",
    "        \"european model price\": european_derived_prices,\n",
    "        \"american model price\": american_derived_prices,\n",
    "\n",
    "    }\n",
    "    df1 = pd.DataFrame(data)\n",
    "    return {\"strikes variation benchmarking\": df1}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7478cf7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"my_custom_tests.BenchmarkTest\",\n",
    "    params={\n",
    "        \"hm_model\": hm,\n",
    "        \"strikes\": [400, 425, 460, 495, 520],\n",
    "        \"maturity_date\": maturity_date,\n",
    "        \"spot_price\": option_params['spot_price'],\n",
    "        \"v0\":v0_opt,\n",
    "        \"theta\": theta_opt,\n",
    "        \"kappa\":kappa_opt ,\n",
    "        \"sigma\": sigma_opt,\n",
    "        \"rho\":rho_opt\n",
    "    },\n",
    ").log()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "127fa1b6",
   "metadata": {},
   "source": [
    "<a id='toc7_2'></a>\n",
    "#### Sensitivity Testing\n",
    "The sensitivity testing framework provides a systematic approach to understanding how the Heston model responds to parameter changes, which is crucial for both model validation and practical application in trading and risk management."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "80492c82",
   "metadata": {},
   "outputs": [],
   "source": [
    "@vm.test(\"my_test_provider.Sensitivity\")\n",
    "def SensitivityTest(\n",
    "    model,\n",
    "    strike_price,\n",
    "    maturity_date,\n",
    "    spot_price,\n",
    "    v0_opt,\n",
    "    theta_opt,\n",
    "    kappa_opt,\n",
    "    sigma_opt,\n",
    "    rho_opt,\n",
    "):\n",
    "    \"\"\"\n",
    "    Evaluates the sensitivity of American option prices to changes in model parameters.\n",
    "\n",
    "    This test calculates option prices using the Heston model with optimized parameters.\n",
    "    It's designed to analyze how changes in various model inputs affect the option price,\n",
    "    which is crucial for understanding model behavior and risk management.\n",
    "\n",
    "    Args:\n",
    "        model (HestonModel): Initialized Heston model instance wrapped in ValidMind model object\n",
    "        strike_price (float): Strike price of the option\n",
    "        maturity_date (ql.Date): Expiration date of the option in QuantLib format\n",
    "        spot_price (float): Current price of the underlying asset\n",
    "        v0_opt (float): Optimized initial variance parameter\n",
    "        theta_opt (float): Optimized long-term variance parameter\n",
    "        kappa_opt (float): Optimized mean reversion rate parameter\n",
    "        sigma_opt (float): Optimized volatility of variance parameter\n",
    "        rho_opt (float): Optimized correlation parameter between asset price and variance\n",
    "    \"\"\"\n",
    "    price = model.model.predict_american_option_price(\n",
    "        strike_price,\n",
    "        maturity_date,\n",
    "        spot_price,\n",
    "        v0_opt,\n",
    "        theta_opt,\n",
    "        kappa_opt,\n",
    "        sigma_opt,\n",
    "        rho_opt,\n",
    "    )\n",
    "\n",
    "    return price\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0e9c224",
   "metadata": {},
   "source": [
    "##### Common plot function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "4862005d",
   "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",
    "        \n",
    "        plt.title(params[\"title\"])\n",
    "        plt.legend()\n",
    "        plt.grid(True)\n",
    "        plt.show()  # close the plot to avoid displaying it"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34d4b121",
   "metadata": {},
   "source": [
    "Let's create ValidMind model object"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "b2115371",
   "metadata": {},
   "outputs": [],
   "source": [
    "hm_model = vm.init_model(model=hm, input_id=\"HestonModel\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43893a9a",
   "metadata": {},
   "source": [
    "##### Strike sensitivity\n",
    "Let's analyzes how option prices change as the strike price varies. We create a range of strike prices around the current strike (460) and observe the impact on option prices while keeping all other parameters constant."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "199af470",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"my_test_provider.Sensitivity:ToStrike\",\n",
    "    inputs = {\n",
    "        \"model\": hm_model\n",
    "    },\n",
    "    param_grid={\n",
    "        \"strike_price\": list(np.linspace(460-50, 460+50, 10)),\n",
    "        \"maturity_date\": [maturity_date],\n",
    "        \"spot_price\": [option_params[\"spot_price\"]],\n",
    "        \"v0_opt\": [v0_opt],\n",
    "        \"theta_opt\": [theta_opt],\n",
    "        \"kappa_opt\": [kappa_opt],\n",
    "        \"sigma_opt\": [sigma_opt],\n",
    "        \"rho_opt\":[rho_opt]\n",
    "    },\n",
    ")\n",
    "result.log()\n",
    "# Visualize how option prices change with different strike prices\n",
    "plot_results(\n",
    "    pd.DataFrame(result.tables[0].data),\n",
    "    params={\n",
    "        \"x\": \"strike_price\",\n",
    "        \"y\":\"Value\",\n",
    "        \"label\":\"Strike price\",\n",
    "        \"xlabel\":\"Strike price\",\n",
    "        \"ylabel\":\"option price\",\n",
    "        \"title\":\"Heston option - Strike price Sensitivity\",\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a39a80a9",
   "metadata": {},
   "source": [
    "<a id='toc7_3'></a>\n",
    "#### Stress Testing\n",
    "This stress testing framework provides a comprehensive view of how the Heston model behaves under different market conditions and helps identify potential risks in option pricing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "b977f2e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "@vm.test(\"my_custom_tests.Stressing\")\n",
    "def StressTest(\n",
    "    model,\n",
    "    strike_price,\n",
    "    maturity_date,\n",
    "    spot_price,\n",
    "    v0_opt,\n",
    "    theta_opt,\n",
    "    kappa_opt,\n",
    "    sigma_opt,\n",
    "    rho_opt,\n",
    "):\n",
    "    \"\"\"\n",
    "    Performs stress testing on Heston model parameters to evaluate option price sensitivity.\n",
    "\n",
    "    This test evaluates how the American option price responds to stressed market conditions\n",
    "    by varying key model parameters. It's designed to:\n",
    "    1. Identify potential model vulnerabilities\n",
    "    2. Understand price behavior under extreme scenarios\n",
    "    3. Support risk management decisions\n",
    "    4. Validate model stability across parameter ranges\n",
    "\n",
    "    Args:\n",
    "        model (HestonModel): Initialized Heston model instance wrapped in ValidMind model object\n",
    "        strike_price (float): Option strike price\n",
    "        maturity_date (ql.Date): Option expiration date in QuantLib format\n",
    "        spot_price (float): Current price of the underlying asset\n",
    "        v0_opt (float): Initial variance parameter under stress testing\n",
    "        theta_opt (float): Long-term variance parameter under stress testing\n",
    "        kappa_opt (float): Mean reversion rate parameter under stress testing\n",
    "        sigma_opt (float): Volatility of variance parameter under stress testing\n",
    "        rho_opt (float): Correlation parameter under stress testing\n",
    "    \"\"\"\n",
    "    price = model.model.predict_american_option_price(\n",
    "        strike_price,\n",
    "        maturity_date,\n",
    "        spot_price,\n",
    "        v0_opt,\n",
    "        theta_opt,\n",
    "        kappa_opt,\n",
    "        sigma_opt,\n",
    "        rho_opt,\n",
    "    )\n",
    "\n",
    "    return price\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "032c2481",
   "metadata": {},
   "source": [
    "##### Rho (correlation) and Theta (long term vol) stress test\n",
    "Next, 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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "911e5973",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"my_custom_tests.Stressing:TheRhoAndThetaParameters\",\n",
    "    inputs = {\n",
    "        \"model\": hm_model,\n",
    "    },\n",
    "    param_grid={\n",
    "        \"strike_price\": [460],\n",
    "        \"maturity_date\": [maturity_date],\n",
    "        \"spot_price\": [option_params[\"spot_price\"]],\n",
    "        \"v0_opt\": [v0_opt],\n",
    "        \"theta_opt\": list(np.linspace(0.1, theta_opt+0.4, 5)),\n",
    "        \"kappa_opt\": [kappa_opt],\n",
    "        \"sigma_opt\": [sigma_opt],\n",
    "        \"rho_opt\":list(np.linspace(rho_opt-0.2, rho_opt+0.2, 5))\n",
    "    },\n",
    ").log()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ec508ea",
   "metadata": {},
   "source": [
    "##### Sigma stress test\n",
    "Let's evaluates the sensitivity of a model's output to changes in the volatility parameter, sigma. This test is crucial for understanding how variations in market volatility impact the model's valuation of financial instruments, particularly options.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "961ab2a9",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"my_custom_tests.Stressing:TheSigmaParameter\",\n",
    "    inputs = {\n",
    "        \"model\": hm_model,\n",
    "    },\n",
    "    param_grid={\n",
    "        \"strike_price\": [460],\n",
    "        \"maturity_date\": [maturity_date],\n",
    "        \"spot_price\": [option_params[\"spot_price\"]],\n",
    "        \"v0_opt\": [v0_opt],\n",
    "        \"theta_opt\": [theta_opt],\n",
    "        \"kappa_opt\": [kappa_opt],\n",
    "        \"sigma_opt\": list(np.linspace(0.1, sigma_opt+0.6, 5)),\n",
    "        \"rho_opt\": [rho_opt]\n",
    "    },\n",
    ").log()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8efacbe2",
   "metadata": {},
   "source": [
    "\n",
    "##### Stress kappa \n",
    "Let's evaluates the sensitivity of a model's output to changes in the kappa parameter, which is a mean reversion rate in stochastic volatility models."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6bb248f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"my_custom_tests.Stressing:TheKappaParameter\",\n",
    "    inputs = {\n",
    "        \"model\": hm_model,\n",
    "    },\n",
    "    param_grid={\n",
    "        \"strike_price\": [460],\n",
    "        \"maturity_date\": [maturity_date],\n",
    "        \"spot_price\": [option_params[\"spot_price\"]],\n",
    "        \"v0_opt\": [v0_opt],\n",
    "        \"theta_opt\": [theta_opt],\n",
    "        \"kappa_opt\": list(np.linspace(kappa_opt, kappa_opt+0.2, 5)),\n",
    "        \"sigma_opt\": [sigma_opt],\n",
    "        \"rho_opt\": [rho_opt]\n",
    "    },\n",
    ").log()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f6c867e",
   "metadata": {},
   "source": [
    "##### Stress theta\n",
    "Let's evaluates the sensitivity of a model's output to changes in the parameter theta, which represents the long-term variance in a stochastic volatility model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50ff49eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"my_custom_tests.Stressing:TheThetaParameter\",\n",
    "    inputs = {\n",
    "        \"model\": hm_model,\n",
    "    },\n",
    "    param_grid={\n",
    "        \"strike_price\": [460],\n",
    "        \"maturity_date\": [maturity_date],\n",
    "        \"spot_price\": [option_params[\"spot_price\"]],\n",
    "        \"v0_opt\": [v0_opt],\n",
    "        \"theta_opt\": list(np.linspace(0.1, theta_opt+0.9, 5)),\n",
    "        \"kappa_opt\": [kappa_opt],\n",
    "        \"sigma_opt\": [sigma_opt],\n",
    "        \"rho_opt\": [rho_opt]\n",
    "    },\n",
    ").log()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77a534f9",
   "metadata": {},
   "source": [
    "##### Stress rho\n",
    "Let's evaluates the sensitivity of a model's output to changes in the correlation parameter, rho, within a stochastic volatility (SV) model framework. This test is crucial for understanding how variations in rho, which represents the correlation between the asset price and its volatility, impact the model's valuation output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6a16d37",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_test(\n",
    "    \"my_custom_tests.Stressing:TheRhoParameter\",\n",
    "    inputs = {\n",
    "        \"model\": hm_model,\n",
    "    },\n",
    "    param_grid={\n",
    "        \"strike_price\": [460],\n",
    "        \"maturity_date\": [maturity_date],\n",
    "        \"spot_price\": [option_params[\"spot_price\"]],\n",
    "        \"v0_opt\": [v0_opt],\n",
    "        \"theta_opt\": [theta_opt],\n",
    "        \"kappa_opt\": [kappa_opt],\n",
    "        \"sigma_opt\": [sigma_opt],\n",
    "        \"rho_opt\": list(np.linspace(rho_opt-0.2, rho_opt+0.2, 5))\n",
    "    },\n",
    ").log()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee5050a9",
   "metadata": {},
   "source": [
    "<a id='toc8_'></a>\n",
    "\n",
    "## Next steps\n",
    "\n",
    "You can look at the results of this test suite right in the notebook where you ran the code, as you would expect. But there is a better way — use the ValidMind Platform to work with your model documentation.\n",
    "\n",
    "<a id='toc8_1_'></a>\n",
    "\n",
    "### Work with your model documentation\n",
    "\n",
    "1. From the **Model Inventory** in the ValidMind Platform, go to the model you registered earlier. ([Need more help?](https://docs.validmind.ai/guide/model-inventory/working-with-model-inventory.html))\n",
    "\n",
    "2. Click and expand the **Model Development** section.\n",
    "\n",
    "What you see is the full draft of your model documentation in a more easily consumable version. From here, you can make qualitative edits to model documentation, view guidelines, collaborate with validators, and submit your model documentation for approval when it's ready. [Learn more ...](https://docs.validmind.ai/guide/model-documentation/working-with-model-documentation.html)\n",
    "\n",
    "<a id='toc8_2_'></a>\n",
    "\n",
    "### Discover more learning resources\n",
    "\n",
    "We offer many interactive notebooks to help you document models:\n",
    "\n",
    "- [Run tests & test suites](https://docs.validmind.ai/developer/model-testing/testing-overview.html)\n",
    "- [Code samples](https://docs.validmind.ai/developer/samples-jupyter-notebooks.html)\n",
    "\n",
    "Or, visit our [documentation](https://docs.validmind.ai/) to learn more about ValidMind."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "validmind-1QuffXMV-py3.10",
   "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.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
