2025-12-10 18:54:32 +01:00
|
|
|
|
{
|
|
|
|
|
|
"cells": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 1,
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"id": "118782fe",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:57:28.465029Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:57:28.464741Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:57:30.666978Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:57:30.665798Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"✅ Libraries loaded successfully\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"📚 This tutorial covers:\n",
|
|
|
|
|
|
" 1. Regime Switching Systems\n",
|
|
|
|
|
|
" 2. Jump Diffusion Processes\n",
|
|
|
|
|
|
" 3. Combined MRSJD Models\n",
|
|
|
|
|
|
" 4. Numerical Methods (Finite Differences, Upwind Schemes)\n",
|
|
|
|
|
|
" 5. Practical Parameter Selection\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"source": [
|
|
|
|
|
|
"# Import required libraries\n",
|
|
|
|
|
|
"import numpy as np\n",
|
|
|
|
|
|
"import matplotlib.pyplot as plt\n",
|
|
|
|
|
|
"from matplotlib import cm\n",
|
|
|
|
|
|
"from mpl_toolkits.mplot3d import Axes3D\n",
|
|
|
|
|
|
"import seaborn as sns\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Set style\n",
|
|
|
|
|
|
"sns.set_style('whitegrid')\n",
|
|
|
|
|
|
"plt.rcParams['figure.figsize'] = (14, 6)\n",
|
|
|
|
|
|
"plt.rcParams['font.size'] = 11\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(\"✅ Libraries loaded successfully\")\n",
|
|
|
|
|
|
"print(\"\\n📚 This tutorial covers:\")\n",
|
|
|
|
|
|
"print(\" 1. Regime Switching Systems\")\n",
|
|
|
|
|
|
"print(\" 2. Jump Diffusion Processes\")\n",
|
|
|
|
|
|
"print(\" 3. Combined MRSJD Models\")\n",
|
|
|
|
|
|
"print(\" 4. Numerical Methods (Finite Differences, Upwind Schemes)\")\n",
|
|
|
|
|
|
"print(\" 5. Practical Parameter Selection\")"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
|
"id": "dcfec9d8",
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"## 2. Mathematical Background <a id=\"math\"></a>\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Stochastic Differential Equations (SDEs)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"A general SDE has the form:\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"dX_t = \\mu(X_t)dt + \\sigma(X_t)dW_t\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"where:\n",
|
|
|
|
|
|
"- $\\mu(X_t)$ = **drift** (deterministic trend)\n",
|
|
|
|
|
|
"- $\\sigma(X_t)$ = **diffusion** (volatility)\n",
|
|
|
|
|
|
"- $dW_t$ = **Wiener process** increment: $dW_t \\sim \\mathcal{N}(0, dt)$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Key Properties\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Itô's Lemma** (chain rule for SDEs):\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"For $Y_t = f(X_t)$:\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"dY_t = f'(X_t)dX_t + \\frac{1}{2}f''(X_t)\\sigma^2(X_t)dt\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Feynman-Kac Formula** (connects PDEs to expectations):\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"V(x,t) = \\mathbb{E}_x\\left[ \\int_t^T e^{-\\rho(s-t)} L(X_s)ds + e^{-\\rho(T-t)}\\Phi(X_T) \\right]\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"satisfies the PDE:\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\\frac{\\partial V}{\\partial t} + \\mu(x)\\frac{\\partial V}{\\partial x} + \\frac{1}{2}\\sigma^2(x)\\frac{\\partial^2 V}{\\partial x^2} - \\rho V + L(x) = 0\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Example: Ornstein-Uhlenbeck Process\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Mean-reverting process:\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"dX_t = \\theta(\\mu - X_t)dt + \\sigma dW_t\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"- $\\theta$ = speed of mean reversion\n",
|
|
|
|
|
|
"- $\\mu$ = long-term mean\n",
|
|
|
|
|
|
"- $\\sigma$ = volatility\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Half-life**: $t_{1/2} = \\frac{\\ln 2}{\\theta}$"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 2,
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"id": "b25e1746",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:57:30.670266Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:57:30.669891Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:57:31.331171Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:57:31.330002Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"image/png": "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
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1400x500 with 2 Axes>"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"📊 OU Process Analysis:\n",
|
|
|
|
|
|
" Half-life: 1.39 seconds\n",
|
|
|
|
|
|
" Theoretical equilibrium std: 2.00\n",
|
|
|
|
|
|
" Observed equilibrium std: 2.31\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"source": [
|
|
|
|
|
|
"# Simulate Ornstein-Uhlenbeck process\n",
|
|
|
|
|
|
"def simulate_ou(theta, mu, sigma, x0, T, dt):\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" Simulate Ornstein-Uhlenbeck process using Euler-Maruyama method\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" dX_t = θ(μ - X_t)dt + σ dW_t\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" n_steps = int(T / dt)\n",
|
|
|
|
|
|
" t = np.linspace(0, T, n_steps)\n",
|
|
|
|
|
|
" X = np.zeros(n_steps)\n",
|
|
|
|
|
|
" X[0] = x0\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" for i in range(1, n_steps):\n",
|
|
|
|
|
|
" dW = np.random.normal(0, np.sqrt(dt))\n",
|
|
|
|
|
|
" X[i] = X[i-1] + theta * (mu - X[i-1]) * dt + sigma * dW\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" return t, X\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Example: Temperature control\n",
|
|
|
|
|
|
"theta = 0.5 # Mean reversion speed\n",
|
|
|
|
|
|
"mu = 20.0 # Target temperature (°C)\n",
|
|
|
|
|
|
"sigma = 2.0 # Noise level\n",
|
|
|
|
|
|
"x0 = 10.0 # Initial temperature\n",
|
|
|
|
|
|
"T = 10.0 # Time horizon (seconds)\n",
|
|
|
|
|
|
"dt = 0.01 # Time step\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"t, X = simulate_ou(theta, mu, sigma, x0, T, dt)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Plot\n",
|
|
|
|
|
|
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Trajectory\n",
|
|
|
|
|
|
"ax1.plot(t, X, linewidth=1.5, color='steelblue', label='Temperature')\n",
|
|
|
|
|
|
"ax1.axhline(y=mu, color='red', linestyle='--', label=f'Target μ={mu}')\n",
|
|
|
|
|
|
"ax1.fill_between(t, mu-sigma, mu+sigma, alpha=0.2, color='red', label='±σ band')\n",
|
|
|
|
|
|
"ax1.set_xlabel('Time (s)')\n",
|
|
|
|
|
|
"ax1.set_ylabel('Temperature (°C)')\n",
|
|
|
|
|
|
"ax1.set_title('Ornstein-Uhlenbeck Process (Mean-Reverting System)')\n",
|
|
|
|
|
|
"ax1.legend()\n",
|
|
|
|
|
|
"ax1.grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Distribution at equilibrium\n",
|
|
|
|
|
|
"equilibrium_samples = X[len(X)//2:] # Second half (near equilibrium)\n",
|
|
|
|
|
|
"ax2.hist(equilibrium_samples, bins=30, density=True, alpha=0.7, color='steelblue', edgecolor='black')\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Theoretical distribution: N(μ, σ²/(2θ))\n",
|
|
|
|
|
|
"x_range = np.linspace(X.min(), X.max(), 100)\n",
|
|
|
|
|
|
"theoretical_std = sigma / np.sqrt(2 * theta)\n",
|
|
|
|
|
|
"from scipy.stats import norm\n",
|
|
|
|
|
|
"ax2.plot(x_range, norm.pdf(x_range, mu, theoretical_std), \n",
|
|
|
|
|
|
" 'r-', linewidth=2, label=f'Theory: N({mu:.1f}, {theoretical_std:.2f}²)')\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"ax2.set_xlabel('Temperature (°C)')\n",
|
|
|
|
|
|
"ax2.set_ylabel('Probability Density')\n",
|
|
|
|
|
|
"ax2.set_title('Equilibrium Distribution')\n",
|
|
|
|
|
|
"ax2.legend()\n",
|
|
|
|
|
|
"ax2.grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"plt.tight_layout()\n",
|
|
|
|
|
|
"plt.show()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(f\"\\n📊 OU Process Analysis:\")\n",
|
|
|
|
|
|
"print(f\" Half-life: {np.log(2)/theta:.2f} seconds\")\n",
|
|
|
|
|
|
"print(f\" Theoretical equilibrium std: {theoretical_std:.2f}\")\n",
|
|
|
|
|
|
"print(f\" Observed equilibrium std: {equilibrium_samples.std():.2f}\")"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
|
"id": "78636a0b",
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"## 3. Regime Switching Systems <a id=\"regime\"></a>\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Motivation\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Many real systems exhibit **multiple operating modes** or **regimes**:\n",
|
|
|
|
|
|
"- Weather: sunny ↔ rainy ↔ stormy\n",
|
|
|
|
|
|
"- Manufacturing: normal ↔ maintenance ↔ failure\n",
|
|
|
|
|
|
"- Traffic: free-flow ↔ congested ↔ gridlock\n",
|
|
|
|
|
|
"- Economic activity: expansion ↔ recession\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Continuous-Time Markov Chain\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"The regime $i_t \\in \\{1, 2, ..., N\\}$ follows a Markov chain with **transition rate matrix** $Q$:\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\\mathbb{P}(i_{t+dt} = j | i_t = i) = \n",
|
|
|
|
|
|
"\\begin{cases}\n",
|
|
|
|
|
|
"q_{ij} dt & \\text{if } i \\neq j \\\\\n",
|
|
|
|
|
|
"1 + q_{ii} dt & \\text{if } i = j\n",
|
|
|
|
|
|
"\\end{cases}\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"where $q_{ii} = -\\sum_{j \\neq i} q_{ij}$ (rows sum to zero).\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Coupled HJB System\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"The value function $V^i(x)$ in regime $i$ satisfies:\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\\rho V^i(x) = \\sup_u \\left[ \\mu^i(x,u) (V^i)'(x) + \\frac{1}{2}(\\sigma^i)^2(x,u) (V^i)''(x) + L^i(x,u) + \\sum_{j \\neq i} q_{ij}[V^j(x) - V^i(x)] \\right]\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Key insight: The term $\\sum_{j \\neq i} q_{ij}[V^j(x) - V^i(x)]$ represents the **expected change in value due to regime switching**.\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Stationary Distribution\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"The long-run probability of being in each regime solves:\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"Q^T \\pi = 0, \\quad \\sum_i \\pi_i = 1\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Parameter Selection Tips\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"| Parameter | Typical Range | Effect | How to Choose |\n",
|
|
|
|
|
|
"|-----------|--------------|--------|---------------|\n",
|
|
|
|
|
|
"| $q_{ij}$ | 0.1 - 10.0 | Regime persistence | Higher = faster switching. Set $q_{ij} = 1/\\text{expected duration}$ |\n",
|
|
|
|
|
|
"| $\\mu^i$ | Problem-specific | Drift in regime $i$ | Estimate from data or physics |\n",
|
|
|
|
|
|
"| $\\sigma^i$ | $> 0$ | Volatility in regime $i$ | Measure from observations or experiments |\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Example**: If regime 1 typically lasts 5 time units, set $q_{12} \\approx 0.2$."
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 3,
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"id": "79238099",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:57:31.335006Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:57:31.334669Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:57:35.959770Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:57:35.957422Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABWsAAAPaCAYAAADhlgoDAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4k9UbxvFv0gl0UAqUvffeU/ZQEWSjyBBkg4AKAooKuBAFEdmIigxZCgiC7CEie2+QJZsyS+lO8vvj/ChUVgsNHdyf6+rV9M15z/sk1pbeOXmOxeFwOBARERERERERERGRBGVN6AJERERERERERERERGGtiIiIiIiIiIiISKKgsFZEREREREREREQkEVBYKyIiIiIiIiIiIpIIKKwVERERERERERERSQQU1oqIiIiIiIiIiIgkAgprRURERERERERERBIBhbUiIiIiIiIiIiIiiYDCWhEREREREREREZFEQGGtiIiISBIzevRo8ufPf89H4cKFqVChAh06dGDLli1OreHMmTPkz5+f4cOHO/U6cXXo0CH69OlDlSpVKFKkCBUrVqRTp06sWrUqXuYfMGAA+fPnJzw8PPpYZGQk58+fj9M8cXn+atasSYsWLeJc65N60PdY+fLl6dKlC7t3737qNYmIiIgkd64JXYCIiIiIPJ6uXbuSK1eu6K8jIyM5duwYM2fOpH379sycOZNixYo55dpp0qThyy+/JF++fE6Z/3Fs3LiRTp06kSVLFl599VXSp0/PlStXWLx4Md27d6dbt2689dZbT3SNV155hYoVK+Lm5gbA2bNn6dChA6+//jotW7aMh0dxr/fffx8PDw+nzP0ouXLlomvXrtFf22w2AgMDmTZtGq1bt2bGjBlO+x4TEREReRYprBURERFJoipVqkT58uXvOV6rVi1at27N2LFjmThxolOunTJlSho2bOiUuR/XkCFDyJ49O/PmzYsRbnbq1Il27doxceJEGjVqRI4cOR77GiVLlqRkyZLRX585c4YTJ048SdmPVLt2bafO/zBp06a973/natWq0ahRI0aPHs13332XAJWJiIiIJE9qgyAiIiKSzJQpU4YcOXKwc+fOhC7lqbl69SonTpygXLly96xCdXFxoU2bNjgcDnbt2pUwBSYzBQoUIG/evOzYsSOhSxERERFJVhTWioiIiCRDKVOmvOfYxYsX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
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1400x1000 with 3 Axes>"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"📊 Regime Switching Analysis:\n",
|
|
|
|
|
|
" Observed frequencies: [0.3002 0.3802 0.3196]\n",
|
|
|
|
|
|
" Theoretical stationary: [0.41573034 0.3258427 0.25842697]\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"source": [
|
|
|
|
|
|
"# Simulate regime-switching process\n",
|
|
|
|
|
|
"def simulate_regime_switching(Q, regime_params, x0, T, dt):\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" Simulate regime-switching stochastic process\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" Args:\n",
|
|
|
|
|
|
" Q: Transition rate matrix (N x N)\n",
|
|
|
|
|
|
" regime_params: List of (mu, sigma) for each regime\n",
|
|
|
|
|
|
" x0: Initial state\n",
|
|
|
|
|
|
" T: Time horizon\n",
|
|
|
|
|
|
" dt: Time step\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" n_steps = int(T / dt)\n",
|
|
|
|
|
|
" n_regimes = Q.shape[0]\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" t = np.linspace(0, T, n_steps)\n",
|
|
|
|
|
|
" X = np.zeros(n_steps)\n",
|
|
|
|
|
|
" regimes = np.zeros(n_steps, dtype=int)\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" X[0] = x0\n",
|
|
|
|
|
|
" regimes[0] = 0 # Start in regime 0\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" for i in range(1, n_steps):\n",
|
|
|
|
|
|
" current_regime = regimes[i-1]\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" # Check for regime transition\n",
|
|
|
|
|
|
" for j in range(n_regimes):\n",
|
|
|
|
|
|
" if j != current_regime:\n",
|
|
|
|
|
|
" if np.random.rand() < Q[current_regime, j] * dt:\n",
|
|
|
|
|
|
" current_regime = j\n",
|
|
|
|
|
|
" break\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" regimes[i] = current_regime\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" # Evolve state according to current regime\n",
|
|
|
|
|
|
" mu, sigma = regime_params[current_regime]\n",
|
|
|
|
|
|
" dW = np.random.normal(0, np.sqrt(dt))\n",
|
|
|
|
|
|
" X[i] = X[i-1] + mu * dt + sigma * dW\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" return t, X, regimes\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Example: 3-regime system (Slow/Normal/Fast)\n",
|
|
|
|
|
|
"Q = np.array([\n",
|
|
|
|
|
|
" [-0.5, 0.3, 0.2], # Slow regime\n",
|
|
|
|
|
|
" [ 0.4, -0.7, 0.3], # Normal regime\n",
|
|
|
|
|
|
" [ 0.3, 0.4, -0.7] # Fast regime\n",
|
|
|
|
|
|
"])\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"regime_params = [\n",
|
|
|
|
|
|
" (0.1, 0.2), # Slow: low drift, low vol\n",
|
|
|
|
|
|
" (0.3, 0.4), # Normal: medium drift, medium vol\n",
|
|
|
|
|
|
" (0.5, 0.8) # Fast: high drift, high vol\n",
|
|
|
|
|
|
"]\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"t, X, regimes = simulate_regime_switching(Q, regime_params, x0=0.0, T=50.0, dt=0.01)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Plot\n",
|
|
|
|
|
|
"fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(14, 10), sharex=True)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# State trajectory\n",
|
|
|
|
|
|
"colors = ['blue', 'green', 'red']\n",
|
|
|
|
|
|
"for i in range(len(t)-1):\n",
|
|
|
|
|
|
" ax1.plot(t[i:i+2], X[i:i+2], color=colors[regimes[i]], alpha=0.8, linewidth=0.8)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"ax1.set_ylabel('State X')\n",
|
|
|
|
|
|
"ax1.set_title('Regime-Switching Process')\n",
|
|
|
|
|
|
"ax1.grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Regime evolution\n",
|
|
|
|
|
|
"ax2.step(t, regimes, where='post', linewidth=1.5, color='black')\n",
|
|
|
|
|
|
"ax2.set_ylabel('Regime')\n",
|
|
|
|
|
|
"ax2.set_yticks([0, 1, 2])\n",
|
|
|
|
|
|
"ax2.set_yticklabels(['Slow', 'Normal', 'Fast'])\n",
|
|
|
|
|
|
"ax2.set_title('Regime Evolution')\n",
|
|
|
|
|
|
"ax2.grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Regime distribution\n",
|
|
|
|
|
|
"regime_counts = np.bincount(regimes, minlength=3) / len(regimes)\n",
|
|
|
|
|
|
"ax3.bar([0, 1, 2], regime_counts, color=colors, alpha=0.7, edgecolor='black')\n",
|
|
|
|
|
|
"ax3.set_xlabel('Regime')\n",
|
|
|
|
|
|
"ax3.set_ylabel('Frequency')\n",
|
|
|
|
|
|
"ax3.set_xticks([0, 1, 2])\n",
|
|
|
|
|
|
"ax3.set_xticklabels(['Slow', 'Normal', 'Fast'])\n",
|
|
|
|
|
|
"ax3.set_title('Regime Distribution')\n",
|
|
|
|
|
|
"ax3.grid(alpha=0.3, axis='y')\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"plt.tight_layout()\n",
|
|
|
|
|
|
"plt.show()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Compute stationary distribution\n",
|
|
|
|
|
|
"from scipy.linalg import null_space\n",
|
|
|
|
|
|
"pi_stationary = null_space(Q.T)\n",
|
|
|
|
|
|
"pi_stationary = pi_stationary / pi_stationary.sum()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(\"\\n📊 Regime Switching Analysis:\")\n",
|
|
|
|
|
|
"print(f\" Observed frequencies: {regime_counts}\")\n",
|
|
|
|
|
|
"print(f\" Theoretical stationary: {pi_stationary.flatten()}\")"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
|
"id": "b3751412",
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"## 4. Jump Diffusion Processes <a id=\"jumps\"></a>\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Motivation\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Continuous diffusion models fail to capture **sudden, discrete events**:\n",
|
|
|
|
|
|
"- Market crashes/rallies\n",
|
|
|
|
|
|
"- Equipment failures\n",
|
|
|
|
|
|
"- Policy changes\n",
|
|
|
|
|
|
"- Natural disasters\n",
|
|
|
|
|
|
"- Phase transitions\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Lévy Processes and Compound Poisson\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"A jump diffusion process combines:\n",
|
|
|
|
|
|
"1. **Continuous diffusion**: $\\sigma dW_t$\n",
|
|
|
|
|
|
"2. **Discrete jumps**: $dJ_t = \\sum_{i=1}^{N_t} Y_i$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"dX_t = \\mu dt + \\sigma dW_t + dJ_t\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"where:\n",
|
|
|
|
|
|
"- $N_t \\sim \\text{Poisson}(\\lambda t)$ = number of jumps by time $t$\n",
|
|
|
|
|
|
"- $Y_i \\sim F$ = jump size distribution\n",
|
|
|
|
|
|
"- $\\lambda$ = **jump intensity** (expected jumps per unit time)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### HJB with Jump Integral\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\\rho V(x) = \\sup_u \\left[ \\mu(x,u) V'(x) + \\frac{1}{2}\\sigma^2(x,u) V''(x) + L(x,u) + \\lambda \\int [V(x+y) - V(x)] F(dy) \\right]\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"The integral term $\\lambda \\mathbb{E}[V(x+Y) - V(x)]$ represents the **expected value change from jumps**.\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Jump Size Distributions\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"| Distribution | Density | Use Case |\n",
|
|
|
|
|
|
"|--------------|---------|----------|\n",
|
|
|
|
|
|
"| Normal | $\\mathcal{N}(\\mu_j, \\sigma_j^2)$ | Symmetric jumps (up/down equally likely) |\n",
|
|
|
|
|
|
"| Exponential | $\\lambda e^{-\\lambda y}$ | One-sided jumps (failures, crashes) |\n",
|
|
|
|
|
|
"| Laplace | $\\frac{1}{2b}e^{-|y-\\mu|/b}$ | Heavy-tailed jumps |\n",
|
|
|
|
|
|
"| Uniform | $U(a, b)$ | Bounded jumps |\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Parameter Selection\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"| Parameter | Typical Range | Effect | How to Choose |\n",
|
|
|
|
|
|
"|-----------|--------------|--------|---------------|\n",
|
|
|
|
|
|
"| $\\lambda$ | 0.01 - 5.0 | Jump frequency | Count events per unit time from data |\n",
|
|
|
|
|
|
"| $\\mu_j$ | Problem-specific | Average jump size | Measure typical event magnitude |\n",
|
|
|
|
|
|
"| $\\sigma_j$ | $> 0$ | Jump size variability | Standard deviation of observed jumps |\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Rule of thumb**: If you expect ~1 jump per 10 time units, set $\\lambda = 0.1$."
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 4,
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"id": "733539e5",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:57:35.963250Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:57:35.962947Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:57:36.683892Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:57:36.682687Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABWsAAAPaCAYAAADhlgoDAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XdcleX/x/HXYRzwyFJUxIE4EMG9V2k5MJti09LKsrKybWa/sqXt+c1sL1c7UUtNmpZalpVpTpy4QAUBEeEA5/z+uDocEFTQg4i+n4/HecC553Xf3fbt+/Zzfy6L0+l0IiIiIiIiIiIiIiJVyquqByAiIiIiIiIiIiIiCmtFRERERERERERETgkKa0VEREREREREREROAQprRURERERERERERE4BCmtFRERERERERERETgEKa0VEREREREREREROAQprRURERERERERERE4BCmtFRERERERERERETgEKa0VEREREREREREROAQprRUREpFqKjo5mxIgRVT2MCpk8eTLR0dElPq1bt6ZHjx6MHDmSxMTEUvvMmjWL6Ohofv7556Jl69ev5/LLL6dt27b06NGDffv2MX/+fAYOHEibNm246qqrPD72fv36ccUVV3j8uMdy+P1y3bPu3btzyy238M8//5z0MZ0pRowYUerex8TE0KlTJ6688krmzJlT1UMsoV+/fqXG265dO/r168dDDz1EcnJyqX1GjBhB7969SyybMWMGffr0oW3bttxzzz3Y7XYeeeQRunTpQseOHZk+fbpHx71s2TKio6P5+OOPPXpcERERqZ58qnoAIiIiImea0aNH06xZMwDsdjv79u3jm2++4Y477mDUqFHcf//9Rdt27dqV5557jlatWhUte/DBB9m4cSN33HEHwcHBeHt788ADD1CvXj0eeugh6tWr5/Ex/9///R9+fn4eP255NGvWjNGjRxd9LywsZO/evUyfPp3hw4czc+ZM2rVrVyVjOxM899xzRb87nU4OHDjA7NmzGTduHBkZGVx33XVVOLqSatWqxYMPPgiYsebk5LBlyxa+/PJL5s+fz9SpU0s8K6NHjyY7O7vo+4YNG5g4cSKxsbGMGTOGxo0b89lnn/Hpp58yePBgevfuTefOnT065ubNm/Pcc8/Rvn17jx5XREREqieFtSIiIiInWa9evejevXuJZaNGjeLee+/l3XffpVOnTvTv3x+Axo0b07hx4xLbbtiwgZ49e3LzzTcD8Ndff2G327nqqqsYNmxYpYx5wIABlXLc8qhTpw6XXHJJqeV9+/ZlyJAhTJ48mXfeeacKRnZmKOveX3rppVxwwQW89tprDBs2DKvVWgUjK81ms5U53uHDh3P55Zdz++238+233+Lv7w9Qqqp2/fr1ANx0002cf/75AMyfPx+ACRMmEBoa6vExH+n5FhERkTOT2iCIiIiInAJ8fX2ZNGkSAQEBvPnmm0fdNj8/n4CAgBLfgRLLzgStWrUiKiqKv/76q6qHcsax2Wz069ePrKwsNm7cWNXDOaYmTZowbtw49uzZw6xZs464XVl/ls7UP18iIiJSNRTWioiIyGmhrN6TAPfccw/R0dFF3109YFetWsV9991H586d6dKlC+PHj+fgwYP8+uuvXHrppbRv357zzjuPr7/+usTxoqOjefnll/nggw/o27cvHTp0YNiwYfz2228nfA3BwcH069ePVatWkZ6eXmK8P//8c9HvYKr9oqOjGT9+PNdeey0Ajz32GNHR0SxbtqyoP+6mTZtKnOPjjz8u2sblzz//ZMSIEXTr1o327dszdOhQvvjiixL7ldWzdsWKFYwaNYpOnTrRvn17rrrqKr777rsS27jGsWPHDsaMGUPnzp3p1KkTY8aMYceOHSd8z7y8vCgoKCj6Hh0dzQsvvMCdd95J27ZtOeecc8jMzCz3eAHS0tJ45JFH6NOnD+3bt+eiiy7is88+K7GN3W5n8uTJRX2CzznnHJ555pkSr9RD+e5tamoqd911F2effTZt2rQhLi6Ol156idzc3CNet+uf/9y5c0ute+ihh2jbti1ZWVkAfP/991x++eV06tSJjh07MmzYsDKvu6K8vMz/lXDd/yP1Nb7iiivo169f0XfXM7Fo0SL69OlDhw4deOONN4Dy39fjccEFF+Dr68uiRYuKlhX/98aIESOKWijcdNNNRX1vExISAIr631bkWgHeeustBg8eTLt27ejevTtjxoxhw4YNRevL6lnrcDiYOnUqF1xwAW3atKFnz57cf//97Ny5s8Sx+/Xrx/jx41mwYAGXXHIJbdu25dxzz+W1117D4XCcyO0SERGRKqI2CCIiInJGGjNmDLGxsYwbN46lS5eSkJBASkoKa9asYdiwYQwdOpQPP/yQcePGERMTQ/PmzYv2nTt3blGvzsDAQD766CNGjRrFO++8Q8+ePU9oXC1btsTpdLJu3Tp69epVYp2rf+24cePo0KEDV199NeHh4YSFhfHmm28ydOhQevToQfPmzfn999/Ldb6tW7dy00030bBhQ26//Xb8/PyYN28eDz30EACXXXZZmfstWrSI2267jbCwMG666Sb8/f2ZPXs2t99+OxMmTGD48OEltr/22mtp3bo1999/Pxs3bmTmzJmkpKSUCi4rIiUlhc2bN9O2bdsSy2fOnElsbCwPP/wwaWlpBAcHl3u8mZmZXHbZZezdu5dhw4bRvHlzfvrpJyZMmEBmZiY33XQTDoeDW2+9lWXLlnHZZZcRHR1NUlISM2bMYPny5Xz00UdYrdZy3duCggJGjRrFnj17uPbaa6lXrx5///03b731FikpKSX6xRbXv39/AgMDmT9/PhdffHHRcrvdzrfffss555xDUFAQy5cv584776R9+/bcd999AHz22WeMGTOGDz744LifV4fDwW+//Yafnx8tWrQ4rmOMGzeOa6+9Fl9fX7p161bu+3q8atSoQUREBGvXri1z/ejRo2natCmffvopN954I9HR0TgcDr744guWL1/OU089RXBwcIXO+fbbb/PSSy8xZMgQRo4cSXp6OlOnTmX48OEsXLiQWrVqlbnf/fffz9dff03fvn25+uqr2bVrFx999BGLFy/ms88+K9EaZdmyZXz77bcMHz6cYcOGMXv2bCZPnkytWrW45pprKjReERERqXoKa0VEROSM1LJly6Jqvssuu4zly5fz66+/MnnyZOLi4gCIjIzkhhtuYOnSpSXC2l27djFz5ky6dOkCmJ6e5513Hs8++yyzZ88+oXGFhIQAkJGRUWqdq3/tuHHjaNCgQVGfS6fTyZtvvkm7du0q3Pvyu+++4+DBgzz11FNFoefQoUO56qqrSlXluhQWFvLoo48SEhLCrFmzisZ89dVXM2zYMJ577jnOO+886tSpU7TP2WefzeOPP170PTs7m4SEBLZu3UpkZORRx5ifn19UaQxw6NAhkpKSePnll7Hb7dx0000ltnc6nbz11ltFr61XZLzvvPMOu3bt4v333y+quLzyyiu59tpreeedd7juuuuYP38+ixcv5rXXXmPgwIFF5+3duze33XYbn376KSNGjCjXvV2zZg0bNmxg3Lhx3HjjjQBcfvnlOJ1OduzYgdPpxGKxlLonfn5+DBo0iDlz5pCVlUVQUBAAv/zyC5mZmUXPwbx58ygoKOC1116jdu3aAJx//vlcddVVrF+/vlxhbfF7X1BQwM6dO/nggw/YsGEDN9xwAzab7ZjHKMtVV13F7bffXvR99uzZ5bqvJyI4OPiIFd29e/cmNTWVTz/9lB49etCnTx/AhKHLly/nwgsvrPAke3PmzCEqKopnn322aFlMTAzPPfccSUlJdOvWrdQ+ixcv5uuvv+byyy9n0qRJRcvj4uK48soreeaZZ5gyZUrR8l27dvHZZ58VTVB20UUXcdZZZ/HVV18prBUREamG1AZBREREzkiuQBbA29ubiIgIvL29S7zC7Kpe27t3b4l9u3XrVhTUgpkg6OKLL2bt2rXs3r37hMbl6o95stSvXx+AF198keXLl1NYWIjVamXWrFk88MADZe6zevVqdu/ezbBhw4qCTzAB4o033kh
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1400x1000 with 2 Axes>"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"📊 Jump Diffusion Analysis:\n",
|
|
|
|
|
|
" Expected jumps: 40.0\n",
|
|
|
|
|
|
" Observed jumps: 50\n",
|
|
|
|
|
|
" Average jump size: -0.536 (theoretical: -0.5)\n",
|
|
|
|
|
|
" Std of jumps: 0.178 (theoretical: 0.2)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
" Impact: Final value with jumps = -7.04 vs 17.97 without jumps\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"source": [
|
|
|
|
|
|
"# Simulate jump diffusion process\n",
|
|
|
|
|
|
"def simulate_jump_diffusion(mu, sigma, lambda_jump, jump_mean, jump_std, x0, T, dt):\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" Simulate Merton jump diffusion model\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" dX_t = μ dt + σ dW_t + dJ_t\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" where J_t is compound Poisson with Normal jumps\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" n_steps = int(T / dt)\n",
|
|
|
|
|
|
" t = np.linspace(0, T, n_steps)\n",
|
|
|
|
|
|
" X = np.zeros(n_steps)\n",
|
|
|
|
|
|
" jumps = np.zeros(n_steps)\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" X[0] = x0\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" for i in range(1, n_steps):\n",
|
|
|
|
|
|
" # Diffusion component\n",
|
|
|
|
|
|
" dW = np.random.normal(0, np.sqrt(dt))\n",
|
|
|
|
|
|
" dX = mu * dt + sigma * dW\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" # Jump component\n",
|
|
|
|
|
|
" n_jumps = np.random.poisson(lambda_jump * dt)\n",
|
|
|
|
|
|
" if n_jumps > 0:\n",
|
|
|
|
|
|
" jump_sizes = np.random.normal(jump_mean, jump_std, n_jumps)\n",
|
|
|
|
|
|
" total_jump = jump_sizes.sum()\n",
|
|
|
|
|
|
" dX += total_jump\n",
|
|
|
|
|
|
" jumps[i] = total_jump\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" X[i] = X[i-1] + dX\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" return t, X, jumps\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Example: System with occasional failures/shocks\n",
|
|
|
|
|
|
"mu = 0.5 # Baseline drift\n",
|
|
|
|
|
|
"sigma = 0.3 # Continuous volatility\n",
|
|
|
|
|
|
"lambda_jump = 2.0 # 2 jumps per time unit (on average)\n",
|
|
|
|
|
|
"jump_mean = -0.5 # Negative jumps (failures)\n",
|
|
|
|
|
|
"jump_std = 0.2 # Jump size variability\n",
|
|
|
|
|
|
"x0 = 10.0 # Initial state\n",
|
|
|
|
|
|
"T = 20.0 # Time horizon\n",
|
|
|
|
|
|
"dt = 0.01\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"t, X, jumps = simulate_jump_diffusion(mu, sigma, lambda_jump, jump_mean, jump_std, x0, T, dt)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Also simulate without jumps for comparison\n",
|
|
|
|
|
|
"t_nodiff, X_nodiff, _ = simulate_jump_diffusion(mu, sigma, 0.0, 0.0, 0.0, x0, T, dt)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Plot\n",
|
|
|
|
|
|
"fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Trajectories comparison\n",
|
|
|
|
|
|
"ax1.plot(t, X, linewidth=1.5, color='red', label='With Jumps', alpha=0.8)\n",
|
|
|
|
|
|
"ax1.plot(t_nodiff, X_nodiff, linewidth=1.5, color='blue', label='Pure Diffusion', alpha=0.6)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Mark jump times\n",
|
|
|
|
|
|
"jump_times = t[jumps != 0]\n",
|
|
|
|
|
|
"jump_values = X[jumps != 0]\n",
|
|
|
|
|
|
"ax1.scatter(jump_times, jump_values, color='black', s=50, zorder=5, label='Jump Events', alpha=0.7)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"ax1.set_ylabel('State X')\n",
|
|
|
|
|
|
"ax1.set_title('Jump Diffusion Process vs Pure Diffusion')\n",
|
|
|
|
|
|
"ax1.legend(fontsize=11)\n",
|
|
|
|
|
|
"ax1.grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Jump sizes over time\n",
|
|
|
|
|
|
"ax2.stem(t, jumps, linefmt='red', markerfmt='ro', basefmt=' ', label='Jump Sizes')\n",
|
|
|
|
|
|
"ax2.axhline(y=0, color='black', linewidth=0.8)\n",
|
|
|
|
|
|
"ax2.set_xlabel('Time')\n",
|
|
|
|
|
|
"ax2.set_ylabel('Jump Size')\n",
|
|
|
|
|
|
"ax2.set_title('Jump Events')\n",
|
|
|
|
|
|
"ax2.grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"plt.tight_layout()\n",
|
|
|
|
|
|
"plt.show()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Statistics\n",
|
|
|
|
|
|
"n_observed_jumps = np.sum(jumps != 0)\n",
|
|
|
|
|
|
"expected_jumps = lambda_jump * T\n",
|
|
|
|
|
|
"avg_jump_size = jumps[jumps != 0].mean() if n_observed_jumps > 0 else 0\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"print(\"\\n📊 Jump Diffusion Analysis:\")\n",
|
|
|
|
|
|
"print(f\" Expected jumps: {expected_jumps:.1f}\")\n",
|
|
|
|
|
|
"print(f\" Observed jumps: {n_observed_jumps}\")\n",
|
|
|
|
|
|
"print(f\" Average jump size: {avg_jump_size:.3f} (theoretical: {jump_mean})\")\n",
|
|
|
|
|
|
"print(f\" Std of jumps: {jumps[jumps != 0].std() if n_observed_jumps > 0 else 0:.3f} (theoretical: {jump_std})\")\n",
|
|
|
|
|
|
"print(f\"\\n Impact: Final value with jumps = {X[-1]:.2f} vs {X_nodiff[-1]:.2f} without jumps\")"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
|
"id": "fca7aba3",
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"## 5. Combined MRSJD Models <a id=\"mrsjd\"></a>\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Why Combine Regime Switching and Jumps?\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Real systems often exhibit **both**:\n",
|
|
|
|
|
|
"1. **State-dependent behavior** (regimes)\n",
|
|
|
|
|
|
"2. **Sudden shocks** (jumps)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Examples:\n",
|
|
|
|
|
|
"- **Manufacturing**: Normal/maintenance regimes + equipment failures (jumps)\n",
|
|
|
|
|
|
"- **Power grid**: Low/high demand regimes + blackout events (jumps)\n",
|
|
|
|
|
|
"- **Epidemic**: Endemic/outbreak regimes + super-spreader events (jumps)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Full MRSJD Dynamics\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"dX_t = \\mu^{i_t}(X_t)dt + \\sigma^{i_t}(X_t)dW_t + dJ_t^{i_t}\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"where:\n",
|
|
|
|
|
|
"- Drift $\\mu^i$ and volatility $\\sigma^i$ depend on current regime $i_t$\n",
|
|
|
|
|
|
"- Jump intensity $\\lambda^i$ and distribution $F^i$ also regime-dependent\n",
|
|
|
|
|
|
"- Regime switches according to $Q$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Coupled HJB with Both Effects\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\\boxed{\n",
|
|
|
|
|
|
"\\rho V^i(x) = \\sup_u \\left[ \\mu^i V^i_x + \\frac{(\\sigma^i)^2}{2} V^i_{xx} + L^i(x,u) + \\lambda^i \\int [V^i(x+y) - V^i(x)] F^i(dy) + \\sum_{j \\neq i} q_{ij}[V^j(x) - V^i(x)] \\right]\n",
|
|
|
|
|
|
"}\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"This is the **most general** formulation combining:\n",
|
|
|
|
|
|
"1. ✅ Diffusion: $(\\sigma^i)^2 V^i_{xx}$\n",
|
|
|
|
|
|
"2. ✅ Jumps: $\\lambda^i \\int [V^i(x+y) - V^i(x)] F^i(dy)$\n",
|
|
|
|
|
|
"3. ✅ Regime Switching: $\\sum_{j \\neq i} q_{ij}[V^j(x) - V^i(x)]$\n",
|
|
|
|
|
|
"4. ✅ Optimal Control: $\\sup_u$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Numerical Solution: Finite Differences with Upwind Schemes\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Grid discretization**: $x_k = x_{\\min} + k \\Delta x$, $k = 0, ..., N$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Value function approximation**: $V^i(x_k) \\approx V^i_k$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Derivatives**:\n",
|
|
|
|
|
|
"- Forward: $V_x \\approx (V_{k+1} - V_k) / \\Delta x$\n",
|
|
|
|
|
|
"- Backward: $V_x \\approx (V_k - V_{k-1}) / \\Delta x$\n",
|
|
|
|
|
|
"- Central: $V_{xx} \\approx (V_{k+1} - 2V_k + V_{k-1}) / (\\Delta x)^2$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Upwind scheme** (for stability when $\\mu \\neq 0$):\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"V_x \\approx \n",
|
|
|
|
|
|
"\\begin{cases}\n",
|
|
|
|
|
|
"(V_{k+1} - V_k) / \\Delta x & \\text{if } \\mu > 0 \\text{ (forward)} \\\\\n",
|
|
|
|
|
|
"(V_k - V_{k-1}) / \\Delta x & \\text{if } \\mu < 0 \\text{ (backward)}\n",
|
|
|
|
|
|
"\\end{cases}\n",
|
|
|
|
|
|
"$$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Why upwind?** Prevents numerical oscillations when advection dominates diffusion.\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Algorithm: Value Iteration\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"```\n",
|
|
|
|
|
|
"1. Initialize V^i_k = 0 for all regimes i and grid points k\n",
|
|
|
|
|
|
"2. Repeat until convergence:\n",
|
|
|
|
|
|
" For each regime i:\n",
|
|
|
|
|
|
" For each grid point k:\n",
|
|
|
|
|
|
" a. Compute derivatives V_x, V_xx\n",
|
|
|
|
|
|
" b. Compute jump integral ∫[V(x+y) - V(x)]F(dy)\n",
|
|
|
|
|
|
" c. Compute regime switching term Σ q_ij[V^j - V^i]\n",
|
|
|
|
|
|
" d. Optimize over control: u* = argmax_u RHS(u)\n",
|
|
|
|
|
|
" e. Update: V^i_k ← RHS(u*) / ρ\n",
|
|
|
|
|
|
"3. Convergence check: ||V_new - V_old|| < tol\n",
|
|
|
|
|
|
"```\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Computational Complexity\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"- **Per iteration**: $O(N \\cdot M \\cdot K)$\n",
|
|
|
|
|
|
" - $N$ = number of regimes\n",
|
|
|
|
|
|
" - $M$ = grid points\n",
|
|
|
|
|
|
" - $K$ = control discretization\n",
|
|
|
|
|
|
"- **Iterations**: Typically 100-1000\n",
|
|
|
|
|
|
"- **Total**: $O(10^5 - 10^7)$ operations\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Speedup techniques**:\n",
|
|
|
|
|
|
"- Parallel computation across regimes (Rayon)\n",
|
|
|
|
|
|
"- Adaptive grid refinement\n",
|
|
|
|
|
|
"- Policy iteration instead of value iteration\n",
|
|
|
|
|
|
"- Sparse matrix operations"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "code",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"execution_count": 5,
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"id": "f104f849",
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"iopub.execute_input": "2026-02-16T15:57:36.687408Z",
|
|
|
|
|
|
"iopub.status.busy": "2026-02-16T15:57:36.687121Z",
|
|
|
|
|
|
"iopub.status.idle": "2026-02-16T15:57:41.069722Z",
|
|
|
|
|
|
"shell.execute_reply": "2026-02-16T15:57:41.068651Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"outputs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABWsAAASjCAYAAAAIHGiEAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4U2UbBvA7SXdLF5uWAKVQZJclWFaBgmBBNipSQZaAKHuIAgICooCKoqAIVFGGMsuQAkUsIPNjQxkFQtmrm87k++MxaUP3TGnv33X1anNycs57kpygd57zvAqdTqcDEREREREREREREZmU0tQDICIiIiIiIiIiIiKGtURERERERERERERFAsNaIiIiIiIiIiIioiKAYS0RERERERERERFREcCwloiIiIiIiIiIiKgIYFhLREREREREREREVAQwrCUiIiIiIiIiIiIqAhjWEhERERERERERERUBDGuJiIiIiIiIiIiIigCGtURERCXAlClT4OHhAQ8PD9y8eTPD9T799FN4eHjAy8vLsOzIkSOGx6b+qVWrFpo2bYrevXtj3bp16W7v0qVLGD9+PFq1aoW6deuiRYsWGDp0KPbu3ZvlmMPCwuDh4YEpU6akexz6n7p168LLywvvvfce/v333xw8Kxk7e/YspkyZgvbt2xvGPXr0aJw5cyZftp9TS5YsgYeHB65du5bpevrX6vfffy+kkaUYMGCA0fsmPfrXNL2fRo0aoWvXrli+fDmSkpJMPtbC9OTJE8yfPx+dOnVCvXr10KRJE/Tr1w+rV69GYmJinre/ceNGeHh44MCBA0bLNRpNjrfVrl079O3bN8v19OdqfHx8jvdRlOifu40bN5p6KDnSrl27NOdY/fr10a5dO0ybNi3d1z698+LXX39F69atUa9ePYwdOxYJCQmYPn06mjRpAk9PT/zyyy/5Om5TfoYREREVFWamHgAREREVrj179mDw4MFplut0OgQGBmb4OB8fH/j4+Bitf+fOHWzYsAHTp09HbGwsBg0aZLj/8OHDGDp0KFxdXfHGG2+gXLlyePz4MbZv346RI0dixIgRGDNmTK6OYerUqXBycgIAxMfH4969e9i6dSsGDhyITz75BP3798/VdgHghx9+wNdffw21Wo3u3bujfPnyuH37NjZs2IC9e/di0aJFePXVV3O9/YJUvXp1LFiwAA0a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
|
|
|
|
|
|
"text/plain": [
|
|
|
|
|
|
"<Figure size 1400x1200 with 3 Axes>"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"output_type": "display_data"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
|
"text": [
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"📊 MRSJD Analysis:\n",
|
|
|
|
|
|
" Total jumps: 32\n",
|
|
|
|
|
|
" Time in Stable regime: 18.2 (90.8%)\n",
|
|
|
|
|
|
" Time in Volatile regime: 11.8 (59.2%)\n",
|
|
|
|
|
|
" Average jump size (Stable): -0.106\n",
|
|
|
|
|
|
" Average jump size (Volatile): -0.290\n"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-10 18:54:32 +01:00
|
|
|
|
"source": [
|
|
|
|
|
|
"# Simplified MRSJD simulation (for illustration)\n",
|
|
|
|
|
|
"def simulate_mrsjd(Q, regime_params_list, x0, T, dt):\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" Simulate Markov Regime Switching Jump Diffusion\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" Each regime has: (mu, sigma, lambda_jump, jump_mean, jump_std)\n",
|
|
|
|
|
|
" \"\"\"\n",
|
|
|
|
|
|
" n_steps = int(T / dt)\n",
|
|
|
|
|
|
" n_regimes = Q.shape[0]\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" t = np.linspace(0, T, n_steps)\n",
|
|
|
|
|
|
" X = np.zeros(n_steps)\n",
|
|
|
|
|
|
" regimes = np.zeros(n_steps, dtype=int)\n",
|
|
|
|
|
|
" jump_events = []\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" X[0] = x0\n",
|
|
|
|
|
|
" regimes[0] = 0\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" for i in range(1, n_steps):\n",
|
|
|
|
|
|
" current_regime = regimes[i-1]\n",
|
|
|
|
|
|
" mu, sigma, lam, jmu, jsig = regime_params_list[current_regime]\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" # Check regime transition\n",
|
|
|
|
|
|
" for j in range(n_regimes):\n",
|
|
|
|
|
|
" if j != current_regime and np.random.rand() < Q[current_regime, j] * dt:\n",
|
|
|
|
|
|
" current_regime = j\n",
|
|
|
|
|
|
" break\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" regimes[i] = current_regime\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" # Diffusion\n",
|
|
|
|
|
|
" dW = np.random.normal(0, np.sqrt(dt))\n",
|
|
|
|
|
|
" dX = mu * dt + sigma * dW\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" # Jumps (regime-dependent)\n",
|
|
|
|
|
|
" n_jumps = np.random.poisson(lam * dt)\n",
|
|
|
|
|
|
" if n_jumps > 0:\n",
|
|
|
|
|
|
" jump_size = np.sum(np.random.normal(jmu, jsig, n_jumps))\n",
|
|
|
|
|
|
" dX += jump_size\n",
|
|
|
|
|
|
" jump_events.append((t[i], jump_size, current_regime))\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" X[i] = X[i-1] + dX\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" return t, X, regimes, jump_events\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Example: 2-regime system with regime-dependent jumps\n",
|
|
|
|
|
|
"Q = np.array([\n",
|
|
|
|
|
|
" [-0.3, 0.3],\n",
|
|
|
|
|
|
" [0.5, -0.5]\n",
|
|
|
|
|
|
"])\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Regime 0: Stable (low vol, rare small jumps)\n",
|
|
|
|
|
|
"# Regime 1: Volatile (high vol, frequent large jumps)\n",
|
|
|
|
|
|
"regime_params_list = [\n",
|
|
|
|
|
|
" (0.2, 0.3, 0.5, -0.1, 0.05), # Stable: mu, sigma, lambda, jump_mu, jump_sigma\n",
|
|
|
|
|
|
" (0.1, 0.8, 2.0, -0.3, 0.15) # Volatile\n",
|
|
|
|
|
|
"]\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"t, X, regimes, jump_events = simulate_mrsjd(Q, regime_params_list, x0=5.0, T=30.0, dt=0.01)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Plot\n",
|
|
|
|
|
|
"fig, axes = plt.subplots(3, 1, figsize=(14, 12), sharex=True)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# State trajectory\n",
|
|
|
|
|
|
"regime_colors = ['blue', 'red']\n",
|
|
|
|
|
|
"for i in range(len(t)-1):\n",
|
|
|
|
|
|
" axes[0].plot(t[i:i+2], X[i:i+2], color=regime_colors[regimes[i]], alpha=0.8, linewidth=1.0)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Mark jumps\n",
|
|
|
|
|
|
"if jump_events:\n",
|
|
|
|
|
|
" jump_t = [j[0] for j in jump_events]\n",
|
|
|
|
|
|
" jump_idx = [np.argmin(np.abs(t - jt)) for jt in jump_t]\n",
|
|
|
|
|
|
" axes[0].scatter([t[i] for i in jump_idx], [X[i] for i in jump_idx], \n",
|
|
|
|
|
|
" color='black', s=60, zorder=5, marker='x', label='Jumps')\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"axes[0].set_ylabel('State X')\n",
|
|
|
|
|
|
"axes[0].set_title('MRSJD: Combined Regime Switching + Jump Diffusion')\n",
|
|
|
|
|
|
"axes[0].legend()\n",
|
|
|
|
|
|
"axes[0].grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Regime evolution\n",
|
|
|
|
|
|
"axes[1].step(t, regimes, where='post', linewidth=1.5, color='black')\n",
|
|
|
|
|
|
"axes[1].fill_between(t, regimes, alpha=0.3, step='post', \n",
|
|
|
|
|
|
" color=['blue' if r==0 else 'red' for r in regimes])\n",
|
|
|
|
|
|
"axes[1].set_ylabel('Regime')\n",
|
|
|
|
|
|
"axes[1].set_yticks([0, 1])\n",
|
|
|
|
|
|
"axes[1].set_yticklabels(['Stable', 'Volatile'])\n",
|
|
|
|
|
|
"axes[1].set_title('Regime Transitions')\n",
|
|
|
|
|
|
"axes[1].grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Jump events by regime\n",
|
|
|
|
|
|
"if jump_events:\n",
|
|
|
|
|
|
" regime_0_jumps = [j for j in jump_events if j[2] == 0]\n",
|
|
|
|
|
|
" regime_1_jumps = [j for j in jump_events if j[2] == 1]\n",
|
|
|
|
|
|
" \n",
|
|
|
|
|
|
" if regime_0_jumps:\n",
|
|
|
|
|
|
" axes[2].scatter([j[0] for j in regime_0_jumps], [j[1] for j in regime_0_jumps],\n",
|
|
|
|
|
|
" color='blue', s=50, alpha=0.7, label='Stable Regime Jumps')\n",
|
|
|
|
|
|
" if regime_1_jumps:\n",
|
|
|
|
|
|
" axes[2].scatter([j[0] for j in regime_1_jumps], [j[1] for j in regime_1_jumps],\n",
|
|
|
|
|
|
" color='red', s=50, alpha=0.7, label='Volatile Regime Jumps')\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"axes[2].axhline(y=0, color='black', linewidth=0.8)\n",
|
|
|
|
|
|
"axes[2].set_xlabel('Time')\n",
|
|
|
|
|
|
"axes[2].set_ylabel('Jump Size')\n",
|
|
|
|
|
|
"axes[2].set_title('Jump Events by Regime')\n",
|
|
|
|
|
|
"axes[2].legend()\n",
|
|
|
|
|
|
"axes[2].grid(alpha=0.3)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"plt.tight_layout()\n",
|
|
|
|
|
|
"plt.show()\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"# Statistics\n",
|
|
|
|
|
|
"print(\"\\n📊 MRSJD Analysis:\")\n",
|
|
|
|
|
|
"print(f\" Total jumps: {len(jump_events)}\")\n",
|
|
|
|
|
|
"regime_times = [np.sum(regimes == i) * dt for i in range(2)]\n",
|
|
|
|
|
|
"print(f\" Time in Stable regime: {regime_times[0]:.1f} ({regime_times[0]/T*100:.1f}%)\")\n",
|
|
|
|
|
|
"print(f\" Time in Volatile regime: {regime_times[1]:.1f} ({regime_times[1]/T*100:.1f}%)\")\n",
|
|
|
|
|
|
"if jump_events:\n",
|
|
|
|
|
|
" avg_jump_0 = np.mean([j[1] for j in jump_events if j[2] == 0]) if len([j for j in jump_events if j[2] == 0]) > 0 else 0\n",
|
|
|
|
|
|
" avg_jump_1 = np.mean([j[1] for j in jump_events if j[2] == 1]) if len([j for j in jump_events if j[2] == 1]) > 0 else 0\n",
|
|
|
|
|
|
" print(f\" Average jump size (Stable): {avg_jump_0:.3f}\")\n",
|
|
|
|
|
|
" print(f\" Average jump size (Volatile): {avg_jump_1:.3f}\")"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
|
"id": "822b77f8",
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"## 6. Practical Parameter Selection Guide <a id=\"params\"></a>\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### How to Choose Parameters for Your Problem\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"#### Step 1: Identify Regimes\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Ask: Does the system have distinct \"modes\" or \"states\"?\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Examples**:\n",
|
|
|
|
|
|
"- Manufacturing: Normal / Degraded / Failed\n",
|
|
|
|
|
|
"- Weather: Clear / Cloudy / Storm\n",
|
|
|
|
|
|
"- Network: Low / Medium / High traffic\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Tip**: Start with 2-3 regimes. More regimes = more parameters to estimate.\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"#### Step 2: Estimate Regime Persistence\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Question**: How long does each regime typically last?\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Formula**: $q_{ij} = \\frac{1}{\\text{expected duration in regime } i}$\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Example**: If \"Normal\" regime lasts ~10 time units:\n",
|
|
|
|
|
|
"- Total exit rate from Normal: $q_{01} + q_{02} = 0.1$\n",
|
|
|
|
|
|
"- Split based on transition probabilities\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"#### Step 3: Characterize Within-Regime Dynamics\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"For each regime $i$:\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"| Parameter | Method | Example |\n",
|
|
|
|
|
|
"|-----------|--------|----------|\n",
|
|
|
|
|
|
"| $\\mu^i$ | Sample mean of increments | $\\bar{\\Delta X} / \\Delta t$ |\n",
|
|
|
|
|
|
"| $\\sigma^i$ | Sample std of increments | $\\text{std}(\\Delta X) / \\sqrt{\\Delta t}$ |\n",
|
|
|
|
|
|
"| $\\lambda^i$ | Count events per time | $N_{\\text{jumps}} / T$ |\n",
|
|
|
|
|
|
"| Jump mean | Average jump size | $\\bar{Y}$ |\n",
|
|
|
|
|
|
"| Jump std | Std of jump sizes | $\\text{std}(Y)$ |\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"#### Step 4: Validate with Simulations\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Before solving the HJB:\n",
|
|
|
|
|
|
"1. Simulate the process with chosen parameters\n",
|
|
|
|
|
|
"2. Check if trajectories \"look right\"\n",
|
|
|
|
|
|
"3. Compare summary statistics to data\n",
|
|
|
|
|
|
"4. Adjust and iterate\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Common Pitfalls and Solutions\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"| Problem | Symptom | Solution |\n",
|
|
|
|
|
|
"|---------|---------|----------|\n",
|
|
|
|
|
|
"| Too many regimes | Overfitting, unstable estimates | Use 2-3 regimes; combine similar ones |\n",
|
|
|
|
|
|
"| Wrong time scale | Unrealistic dynamics | Match $q_{ij}$ to actual durations |\n",
|
|
|
|
|
|
"| Numerical instability | Oscillations, divergence | Reduce grid spacing, use upwind scheme |\n",
|
|
|
|
|
|
"| Slow convergence | Many iterations needed | Better initial guess, increase tolerance |\n",
|
|
|
|
|
|
"| High dimensionality | Curse of dimensionality | Reduce state space, use approximations |\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Sensitivity Analysis\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"Always check how results change with parameters:\n",
|
|
|
|
|
|
"1. Vary each parameter by ±20%\n",
|
|
|
|
|
|
"2. Observe impact on optimal policy and value function\n",
|
|
|
|
|
|
"3. Identify which parameters matter most\n",
|
|
|
|
|
|
"4. Focus calibration efforts on sensitive parameters\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### When to Use vs. Not Use\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"✅ **Good fit for HJB optimal control**:\n",
|
|
|
|
|
|
"- Continuous state space (position, temperature, concentration)\n",
|
|
|
|
|
|
"- Known or learnable dynamics\n",
|
|
|
|
|
|
"- Quantifiable objectives\n",
|
|
|
|
|
|
"- Medium-dimensional problems (1-3 state variables)\n",
|
|
|
|
|
|
"- Offline planning acceptable\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"❌ **Not recommended**:\n",
|
|
|
|
|
|
"- Purely discrete decisions (use dynamic programming)\n",
|
|
|
|
|
|
"- Unknown dynamics (use reinforcement learning)\n",
|
|
|
|
|
|
"- High-dimensional state (>5 variables)\n",
|
|
|
|
|
|
"- Real-time requirements (<1ms response)\n",
|
|
|
|
|
|
"- Purely deterministic problems (use calculus of variations)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Alternative Approaches\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"| Method | When to Use | Pros | Cons |\n",
|
|
|
|
|
|
"|--------|-------------|------|------|\n",
|
|
|
|
|
|
"| **LQR/LQG** | Linear dynamics, quadratic cost | Fast, analytical solution | Limited to LQ problems |\n",
|
|
|
|
|
|
"| **MPC** | Need real-time receding horizon | Handles constraints well | Computational cost |\n",
|
|
|
|
|
|
"| **RL (DQN, PPO)** | Unknown dynamics | Model-free, flexible | Sample inefficient |\n",
|
|
|
|
|
|
"| **PID Control** | Simple SISO systems | Easy to tune | No optimality guarantee |\n",
|
|
|
|
|
|
"| **Bang-Bang** | Hard constraints | Simple implementation | Non-smooth control |\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Further Reading\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"1. **Books**:\n",
|
|
|
|
|
|
" - Fleming & Rishel: \"Deterministic and Stochastic Optimal Control\"\n",
|
|
|
|
|
|
" - Øksendal & Sulem: \"Applied Stochastic Control of Jump Diffusions\"\n",
|
|
|
|
|
|
" - Bertsekas: \"Dynamic Programming and Optimal Control\"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"2. **Papers**:\n",
|
|
|
|
|
|
" - Guo & Hernandez-Lerma: \"Continuous-Time Markov Decision Processes\"\n",
|
|
|
|
|
|
" - Pham: \"Continuous-time Stochastic Control and Optimization with Financial Applications\"\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"3. **Software**:\n",
|
|
|
|
|
|
" - This library (optimizr): Generic optimal control solvers\n",
|
|
|
|
|
|
" - PROPT: MATLAB optimal control toolbox\n",
|
|
|
|
|
|
" - CasADi: Nonlinear optimization and optimal control"
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
|
"id": "432ef4af",
|
|
|
|
|
|
"metadata": {},
|
|
|
|
|
|
"source": [
|
|
|
|
|
|
"## Summary and Next Steps\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### What We Covered\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"1. ✅ **Optimal Control Theory**: HJB equations, value functions\n",
|
|
|
|
|
|
"2. ✅ **Regime Switching**: Markov chains, coupled HJB systems\n",
|
|
|
|
|
|
"3. ✅ **Jump Diffusion**: Lévy processes, compound Poisson\n",
|
|
|
|
|
|
"4. ✅ **MRSJD Models**: Combined framework for complex systems\n",
|
|
|
|
|
|
"5. ✅ **Numerical Methods**: Finite differences, upwind schemes, value iteration\n",
|
|
|
|
|
|
"6. ✅ **Parameter Selection**: Practical guidance and sensitivity analysis\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Key Takeaways\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"- Optimal control finds the **best** policy, not just a good one\n",
|
|
|
|
|
|
"- HJB equations require **solving PDEs** (computational cost)\n",
|
|
|
|
|
|
"- Regime switching captures **state-dependent behavior**\n",
|
|
|
|
|
|
"- Jumps model **sudden events** and tail risk\n",
|
|
|
|
|
|
"- Start simple (2 regimes, pure diffusion) then add complexity\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Exercises for Practice\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"1. **Temperature Control**: Design an optimal heating/cooling policy to maintain room temperature near 20°C while minimizing energy cost\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"2. **Inventory Management**: Optimize reorder policy for warehouse with regime-switching demand (normal/holiday)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"3. **Robot Navigation**: Find optimal path for robot avoiding obstacles with uncertain dynamics and occasional sensor failures (jumps)\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"### Next Tutorial\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"- **Hidden Markov Models (HMM)**: When regime is not directly observable\n",
|
|
|
|
|
|
"- **MCMC Sampling**: Bayesian inference for parameter estimation\n",
|
|
|
|
|
|
"- **Sparse Optimization**: High-dimensional problems with sparsity\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"---\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Questions?** Open an issue on the repository or consult the API documentation.\n",
|
|
|
|
|
|
"\n",
|
|
|
|
|
|
"**Happy Optimizing! 🚀**"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"language_info": {
|
2026-02-16 16:59:21 +01:00
|
|
|
|
"codemirror_mode": {
|
|
|
|
|
|
"name": "ipython",
|
|
|
|
|
|
"version": 3
|
|
|
|
|
|
},
|
|
|
|
|
|
"file_extension": ".py",
|
|
|
|
|
|
"mimetype": "text/x-python",
|
|
|
|
|
|
"name": "python",
|
|
|
|
|
|
"nbconvert_exporter": "python",
|
|
|
|
|
|
"pygments_lexer": "ipython3",
|
|
|
|
|
|
"version": "3.13.2"
|
2025-12-10 18:54:32 +01:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"nbformat": 4,
|
|
|
|
|
|
"nbformat_minor": 5
|
|
|
|
|
|
}
|