{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "39981e04",
   "metadata": {},
   "source": [
    "<div style=\"background:linear-gradient(135deg,#0b132b 0%,#1c2541 50%,#3a506b 100%);padding:32px 36px;border-radius:14px;color:#e0fbfc;font-family:'SF Pro Display',sans-serif;\">\n",
    "<h1 style=\"margin:0 0 8px 0;color:#d4af37;\">📚 Prerequisites — Multi-asset OU optimal execution</h1>\n",
    "<h4 style=\"margin:6px 0 16px 0;font-weight:300;color:#5bc0be;\">Free sample · Companion course €34.99 / Education tier €19/mo</h4>\n",
    "<p style=\"font-size:14px;line-height:1.6;margin:0;\">Mathematical scaffolding required to fully understand <a href='https://hfthot-lab.eu/blog/microstructure-cross-asset-ou.html' style='color:#d4af37;'>Cross-Asset Microstructure & Portfolio-Level Short-Term PnL</a>: multi-dim Itô calculus, Ornstein–Uhlenbeck dynamics, HJB on $\\mathbb{R}^d$, the quadratic ansatz, matrix Riccati ODEs, Hayashi–Yoshida non-synchronous covariance, optimal trading speed and a fully-solved 2D toy example with rich visuals.</p>\n",
    "</div>\n",
    "\n",
    "> **License**: Free sample notebook. CC BY-NC 4.0 — non-commercial use only. Citation: HFThot Research Lab, *Microstructure OU Prerequisites*, 2026."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0287a764",
   "metadata": {},
   "source": [
    "## 📑 Table of contents\n",
    "\n",
    "1. [Multi-dimensional Itô calculus](#1) · *visuals: 1D & 2D Brownian paths*\n",
    "2. [Ornstein–Uhlenbeck (OU) dynamics](#2) · *visuals: paths & stationary distribution*\n",
    "3. [Hamilton–Jacobi–Bellman equation](#3)\n",
    "4. [The quadratic ansatz](#4)\n",
    "5. [Matrix Riccati system — solved 2D example](#5) · *visuals: norms, components, heatmap*\n",
    "6. [Optimal trading speed under OU](#6) · *visuals: inventory & speed trajectories*\n",
    "7. [Hayashi–Yoshida non-synchronous covariance](#7) · *visuals: Epps effect*\n",
    "8. [What the full course covers (paid)](#8)\n",
    "\n",
    "---\n",
    "\n",
    "**Reproducibility:** all cells use `numpy` + `matplotlib` only. Run top-to-bottom on Python ≥ 3.10. Set the random seed in the next cell for identical figures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "425c5152",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ─── Setup ────────────────────────────────────────────────\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Reproducibility\n",
    "RNG_SEED = 42\n",
    "rng = np.random.default_rng(RNG_SEED)\n",
    "\n",
    "# HFThot dark theme palette\n",
    "HF_BG     = '#0b132b'\n",
    "HF_PANEL  = '#1c2541'\n",
    "HF_GOLD   = '#d4af37'\n",
    "HF_TEAL   = '#5bc0be'\n",
    "HF_AMBER  = '#fbbf24'\n",
    "HF_RED    = '#ef4444'\n",
    "HF_GREEN  = '#10b981'\n",
    "HF_TEXT   = '#e0fbfc'\n",
    "\n",
    "def hf_fig(figsize=(10, 4), nrows=1, ncols=1):\n",
    "    fig, ax = plt.subplots(nrows, ncols, figsize=figsize, facecolor=HF_BG)\n",
    "    axes = np.atleast_1d(ax).ravel() if (nrows*ncols > 1) else [ax]\n",
    "    for a in axes:\n",
    "        a.set_facecolor(HF_BG)\n",
    "        a.tick_params(colors=HF_TEXT)\n",
    "        for spine in a.spines.values():\n",
    "            spine.set_color(HF_TEXT); spine.set_alpha(0.4)\n",
    "        a.grid(True, ls=':', alpha=0.3, color=HF_TEXT)\n",
    "    return fig, ax\n",
    "\n",
    "print(f'NumPy {np.__version__} · seed = {RNG_SEED}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dea81848",
   "metadata": {},
   "source": [
    "<a id=\"1\"></a>\n",
    "## 1 · Multi-dimensional Itô calculus"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4f84777",
   "metadata": {},
   "source": [
    "### 1.1 The setting\n",
    "\n",
    "Let $X_t \\in \\mathbb{R}^d$ be an Itô process driven by an $m$-dimensional Brownian motion $W_t$:\n",
    "\n",
    "$$\n",
    "dX_t \\;=\\; \\mu(X_t, t)\\,dt \\;+\\; \\sigma(X_t, t)\\,dW_t,\n",
    "\\qquad \\mu \\in \\mathbb{R}^d, \\quad \\sigma \\in \\mathbb{R}^{d\\times m}.\n",
    "$$\n",
    "\n",
    "The **drift** $\\mu$ describes the deterministic trend, the **diffusion** $\\sigma$ the local volatility/correlation structure."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d3a1465",
   "metadata": {},
   "source": [
    "### 1.2 The Itô formula\n",
    "\n",
    "For any sufficiently smooth $f: \\mathbb{R}^d \\times [0,T] \\to \\mathbb{R}$ ($C^{1,2}$):\n",
    "\n",
    "$$\n",
    "df(X_t, t) \\;=\\; \\Bigl[\\partial_t f \\;+\\; (\\nabla_x f)^{\\top} \\mu \\;+\\; \\tfrac12\\,\\mathrm{tr}\\bigl(\\sigma\\sigma^{\\top}\\,\\nabla_x^2 f\\bigr)\\Bigr]\\,dt \\;+\\; (\\nabla_x f)^{\\top}\\sigma\\,dW_t.\n",
    "$$\n",
    "\n",
    "The last (martingale) term has **zero expectation**; the bracketed term — the *Itô drift* — is what dynamic programming will extremize."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ca7a7f6",
   "metadata": {},
   "source": [
    "### 1.3 Why it matters here\n",
    "\n",
    "When $\\mu = \\Phi(\\bar\\mu - X_t)$ (mean-reverting) and $\\sigma\\sigma^\\top = \\Sigma$ (constant), and $f$ is the **quadratic value function**\n",
    "\n",
    "$$J(t,q,s) = q^\\top A(t)\\,q + q^\\top B(t)(s-\\bar\\mu) + q^\\top C(t) + D(t),$$\n",
    "\n",
    "the Itô drift collapses into a *finite-dimensional system of matrix ODEs* — the **Riccati system** of section 5. This is the same trick that powers the LQR controller in classical engineering."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cea7bde9",
   "metadata": {},
   "source": [
    "### 1.4 Visual — single Brownian path with quadratic-variation envelope"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ceedecb8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Single 1D Brownian motion + ±√t envelope\n",
    "T = 1.0; N = 1000\n",
    "t = np.linspace(0, T, N+1)\n",
    "dW = rng.normal(scale=np.sqrt(T/N), size=N)\n",
    "W = np.concatenate([[0.0], np.cumsum(dW)])\n",
    "\n",
    "fig, ax = hf_fig((10, 4))\n",
    "ax.plot(t, W, color=HF_TEAL, lw=1.5, label='$W_t$')\n",
    "ax.fill_between(t, -np.sqrt(t), np.sqrt(t), color=HF_GOLD, alpha=0.15, label=r'$\\pm\\sqrt{t}$ envelope')\n",
    "ax.axhline(0, color=HF_TEXT, lw=0.5, alpha=0.4)\n",
    "ax.set_xlabel('time $t$', color=HF_TEXT)\n",
    "ax.set_ylabel('$W_t$',   color=HF_TEXT)\n",
    "ax.set_title('1D Brownian motion sample path', color=HF_GOLD)\n",
    "ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT, loc='upper left')\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67b11c1b",
   "metadata": {},
   "source": [
    "### 1.5 Visual — 2D correlated Brownian motion"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9bf1a2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2D correlated Brownian motion — Cholesky factorisation\n",
    "rho = 0.6\n",
    "L = np.linalg.cholesky(np.array([[1.0, rho], [rho, 1.0]]))\n",
    "\n",
    "dW = rng.normal(scale=np.sqrt(T/N), size=(N, 2)) @ L.T\n",
    "W2 = np.vstack([np.zeros((1, 2)), np.cumsum(dW, axis=0)])\n",
    "\n",
    "fig, ax = hf_fig((6, 6))\n",
    "ax.plot(W2[:, 0], W2[:, 1], color=HF_AMBER, lw=1.2, alpha=0.85)\n",
    "ax.scatter(W2[0, 0], W2[0, 1], color=HF_GREEN, zorder=5, s=60, label='start')\n",
    "ax.scatter(W2[-1, 0], W2[-1, 1], color=HF_RED, zorder=5, s=60, label='end')\n",
    "ax.set_xlabel('$W^{(1)}_t$', color=HF_TEXT)\n",
    "ax.set_ylabel('$W^{(2)}_t$', color=HF_TEXT)\n",
    "ax.set_title(f'2D Brownian path · correlation $\\\\rho={rho}$', color=HF_GOLD)\n",
    "ax.set_aspect('equal')\n",
    "ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12557032",
   "metadata": {},
   "source": [
    "<a id=\"2\"></a>\n",
    "## 2 · Ornstein–Uhlenbeck (OU) dynamics"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02a8f7b7",
   "metadata": {},
   "source": [
    "### 2.1 SDE\n",
    "\n",
    "The OU process is the canonical mean-reverting Itô process:\n",
    "\n",
    "$$\n",
    "dS_t \\;=\\; \\Phi\\,(\\bar\\mu - S_t)\\,dt \\;+\\; \\Sigma^{1/2}\\,dW_t.\n",
    "$$\n",
    "\n",
    "Here $\\Phi \\in \\mathbb{R}^{d\\times d}$ is the **mean-reversion matrix** (eigenvalues control the speed of return to $\\bar\\mu$), $\\bar\\mu$ is the long-run mean, and $\\Sigma$ is the (constant) covariance."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ceec4308",
   "metadata": {},
   "source": [
    "### 2.2 Half-life\n",
    "\n",
    "For a diagonal $\\Phi = \\mathrm{diag}(\\phi_1, \\ldots, \\phi_d)$, each component has the half-life\n",
    "\n",
    "$$\n",
    "\\tau^{1/2}_i \\;=\\; \\frac{\\ln 2}{\\phi_i}.\n",
    "$$\n",
    "\n",
    "In HFT/microstructure, $\\tau^{1/2}$ ranges from **seconds** (tick noise) to **hours** (basket convergence trades)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3037efc3",
   "metadata": {},
   "source": [
    "### 2.3 Visual — OU paths with three different half-lives"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f18a0c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Compare three OU half-lives on the same horizon\n",
    "def simulate_ou(phi, sigma, T, N, x0=0.0, mu_bar=0.0, seed=0):\n",
    "    rng_ = np.random.default_rng(seed)\n",
    "    dt = T/N\n",
    "    x = np.empty(N+1); x[0] = x0\n",
    "    sqdt = np.sqrt(dt)\n",
    "    for k in range(N):\n",
    "        x[k+1] = x[k] + phi*(mu_bar - x[k])*dt + sigma*sqdt*rng_.normal()\n",
    "    return x\n",
    "\n",
    "T, N = 600.0, 6000  # 10 min @ 100 ms\n",
    "half_lives = [10.0, 60.0, 300.0]   # 10s, 1min, 5min\n",
    "colors_ou  = [HF_TEAL, HF_AMBER, HF_RED]\n",
    "\n",
    "fig, ax = hf_fig((11, 4.5))\n",
    "t = np.linspace(0, T, N+1)\n",
    "for hl, col in zip(half_lives, colors_ou):\n",
    "    phi = np.log(2)/hl\n",
    "    x   = simulate_ou(phi, sigma=0.05, T=T, N=N, seed=int(hl))\n",
    "    ax.plot(t, x, color=col, lw=1.2, alpha=0.9, label=fr'$\\tau_{{1/2}} = {hl:.0f}$ s')\n",
    "\n",
    "ax.axhline(0, color=HF_GOLD, lw=0.8, ls='--', alpha=0.6, label=r'$\\bar\\mu$')\n",
    "ax.set_xlabel('time $t$ (s)', color=HF_TEXT)\n",
    "ax.set_ylabel('$S_t$', color=HF_TEXT)\n",
    "ax.set_title('OU paths · varying half-lives (same noise scale)', color=HF_GOLD)\n",
    "ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT, loc='upper right')\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef6cbb79",
   "metadata": {},
   "source": [
    "### 2.4 Visual — empirical stationary distribution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5295acae",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Long simulation → stationary histogram vs theoretical Gaussian N(0, sigma^2/(2*phi))\n",
    "phi = np.log(2)/30.0     # half-life 30s\n",
    "sigma = 0.05\n",
    "T_long, N_long = 50_000.0, 500_000\n",
    "x = simulate_ou(phi, sigma, T_long, N_long, seed=7)\n",
    "x_stationary = x[N_long//5:]   # discard burn-in\n",
    "\n",
    "# theoretical std of the stationary distribution\n",
    "sd_theory = sigma / np.sqrt(2*phi)\n",
    "xs = np.linspace(x_stationary.min(), x_stationary.max(), 400)\n",
    "pdf = np.exp(-xs**2 / (2*sd_theory**2)) / (sd_theory*np.sqrt(2*np.pi))\n",
    "\n",
    "fig, ax = hf_fig((10, 4))\n",
    "ax.hist(x_stationary, bins=80, density=True, color=HF_TEAL, alpha=0.55,\n",
    "        edgecolor=HF_GOLD, lw=0.4, label='empirical')\n",
    "ax.plot(xs, pdf, color=HF_AMBER, lw=2.5, label=fr'$\\mathcal{{N}}(0,\\,\\sigma^2/2\\phi)$, sd={sd_theory:.4f}')\n",
    "ax.set_xlabel('$S_\\\\infty$', color=HF_TEXT)\n",
    "ax.set_ylabel('density', color=HF_TEXT)\n",
    "ax.set_title('OU stationary distribution — empirical vs. theory', color=HF_GOLD)\n",
    "ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT)\n",
    "plt.tight_layout(); plt.show()\n",
    "print(f'empirical std = {x_stationary.std():.5f} · theoretical = {sd_theory:.5f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f41d920",
   "metadata": {},
   "source": [
    "<a id=\"3\"></a>\n",
    "## 3 · Hamilton–Jacobi–Bellman equation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd40e7df",
   "metadata": {},
   "source": [
    "### 3.1 The control problem\n",
    "\n",
    "The trader chooses a **trading speed** $v_t \\in \\mathbb{R}^d$ (positive = buy, negative = sell). The state evolves as\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "dq_t &= v_t\\,dt && \\text{(inventory)} \\\\\n",
    "dS_t &= \\Phi(\\bar\\mu - S_t)\\,dt + \\Sigma^{1/2} dW_t && \\text{(asset prices, OU)} \\\\\n",
    "dX_t &= -\\,v_t \\!\\cdot\\! S_t\\,dt \\;-\\; \\tfrac12\\,v_t^\\top \\eta\\, v_t\\,dt && \\text{(cash, with quadratic impact)}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "where $\\eta \\in \\mathbb{R}^{d\\times d}$ (s.p.d.) is the temporary market-impact matrix."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1005cf2f",
   "metadata": {},
   "source": [
    "### 3.2 The objective\n",
    "\n",
    "Maximise the **mean–variance functional**\n",
    "\n",
    "$$\n",
    "J(t, q, s) \\;=\\; \\sup_{v} \\mathbb{E}\\Bigl[\\; X_T \\;+\\; q_T^\\top S_T \\;-\\; \\tfrac{\\gamma}{2}\\!\\int_t^T\\! q_u^\\top \\Sigma\\, q_u\\,du \\;\\Big|\\; \\mathcal{F}_t\\Bigr],\n",
    "$$\n",
    "\n",
    "with the running risk-aversion penalty $\\gamma > 0$ on inventory exposure to volatility."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd6457a8",
   "metadata": {},
   "source": [
    "### 3.3 The HJB equation\n",
    "\n",
    "By dynamic programming + Itô on $J$:\n",
    "\n",
    "$$\n",
    "\\partial_t J \\;+\\; \\sup_{v\\in\\mathbb{R}^d}\\Bigl\\{(\\nabla_q J)^\\top v - v^\\top s - \\tfrac12 v^\\top\\eta v\\Bigr\\} \\;+\\; (\\nabla_s J)^\\top \\Phi(\\bar\\mu-s) \\;+\\; \\tfrac12\\,\\mathrm{tr}(\\Sigma\\,\\nabla_s^2 J) \\;-\\; \\tfrac\\gamma2\\,q^\\top\\Sigma q \\;=\\; 0.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2831d857",
   "metadata": {},
   "source": [
    "### 3.4 Hamiltonian — closed-form maximiser\n",
    "\n",
    "The bracketed term is concave-quadratic in $v$, so the first-order condition gives the **optimal trading speed**:\n",
    "\n",
    "$$\n",
    "\\boxed{\\;v^\\star \\;=\\; \\eta^{-1}\\bigl(\\nabla_q J - s\\bigr)\\;}\n",
    "$$\n",
    "\n",
    "Substituting $v^\\star$ back yields a *parabolic, semilinear PDE* for $J$ — solved next by ansatz."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2199b0c",
   "metadata": {},
   "source": [
    "<a id=\"4\"></a>\n",
    "## 4 · The quadratic ansatz"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d54d84aa",
   "metadata": {},
   "source": [
    "### 4.1 Postulate (Bergault–Drissi–Guéant 2022)\n",
    "\n",
    "$$\n",
    "J(t,q,s) \\;=\\; q^\\top A(t)\\,q \\;+\\; q^\\top B(t)\\,(s-\\bar\\mu) \\;+\\; q^\\top C(t) \\;+\\; D(t).\n",
    "$$\n",
    "\n",
    "Here $A(t) \\in \\mathbb{S}^d_+$, $B(t) \\in \\mathbb{R}^{d\\times d}$, $C(t) \\in \\mathbb{R}^d$, $D(t) \\in \\mathbb{R}$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03cf921f",
   "metadata": {},
   "source": [
    "### 4.2 The Riccati system\n",
    "\n",
    "Plugging the ansatz into the HJB equation and matching by powers of $(q, s)$ produces:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\dot A(t) &= -\\,2\\,A(t)\\,\\eta^{-1}\\,A(t) \\;+\\; \\tfrac{\\gamma}{2}\\,\\Sigma \\\\\n",
    "\\dot B(t) &= -\\,2\\,A(t)\\,\\eta^{-1}\\,B(t) \\;+\\; \\Phi^\\top\\,B(t) \\\\\n",
    "\\dot C(t) &= -\\,2\\,A(t)\\,\\eta^{-1}\\,C(t)\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "with **terminal conditions** (mark-to-market at horizon $T$):\n",
    "\n",
    "$$\n",
    "A(T) = 0, \\qquad B(T) = I, \\qquad C(T) = 0.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01fc3f1e",
   "metadata": {},
   "source": [
    "### 4.3 Why this works\n",
    "\n",
    "The HJB operator on linear-Gaussian dynamics **preserves quadratic forms**: a quadratic guess produces only quadratic, linear and constant terms after differentiation and the trace operation. Matching powers of $(q,s)$ therefore *closes* the equations into an ODE system — exactly the same trick as in the **Linear-Quadratic Regulator** of classical control."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22eb15f5",
   "metadata": {},
   "source": [
    "<a id=\"5\"></a>\n",
    "## 5 · Matrix Riccati system — solved 2D example"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d39910e0",
   "metadata": {},
   "source": [
    "### 5.1 Toy parameters\n",
    "\n",
    "Two crypto-style assets with **30-min** and **1-h** half-lives, 60 % correlation, identical impact and a moderate risk aversion. Horizon $T = 30$ min."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ea2935d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2-asset toy parameters\n",
    "d     = 2\n",
    "Phi   = np.diag([np.log(2)/3600.0, np.log(2)/1800.0])      # half-lives 1 h, 30 min\n",
    "Sigma = np.array([[1.0, 0.6], [0.6, 1.0]])                 # 60 % correlation\n",
    "eta   = np.diag([1e-4, 1e-4])                              # impact\n",
    "gamma = 1e-3                                               # risk aversion\n",
    "T     = 1800.0                                             # horizon: 30 min\n",
    "N     = 600                                                # outer steps\n",
    "n_sub = 20                                                 # inner RK4 sub-steps\n",
    "\n",
    "print('Φ (1/s)  =', np.diag(Phi))\n",
    "print('half-lives (s) =', np.log(2)/np.diag(Phi))\n",
    "print('Σ =\\n', Sigma)\n",
    "print('η =\\n', eta)\n",
    "print(f'γ = {gamma}, T = {T:.0f}s, N = {N}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "258135ee",
   "metadata": {},
   "source": [
    "### 5.2 Backward RK4 integrator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cba01c04",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Allocate trajectories\n",
    "t_grid = np.linspace(0, T, N+1)\n",
    "A = np.zeros((N+1, d, d))\n",
    "B = np.zeros((N+1, d, d))\n",
    "C = np.zeros((N+1, d))\n",
    "A[-1] = np.zeros((d, d))\n",
    "B[-1] = np.eye(d)\n",
    "C[-1] = np.zeros(d)\n",
    "\n",
    "eta_inv = np.linalg.inv(eta)\n",
    "\n",
    "def rhs(A_, B_, C_):\n",
    "    dA = -2*A_ @ eta_inv @ A_ + 0.5*gamma*Sigma\n",
    "    dB = -2*A_ @ eta_inv @ B_ + Phi.T @ B_\n",
    "    dC = -2*A_ @ eta_inv @ C_\n",
    "    return dA, dB, dC\n",
    "\n",
    "dt_outer = T / N\n",
    "for k in range(N, 0, -1):\n",
    "    Ak, Bk, Ck = A[k], B[k], C[k]\n",
    "    h = dt_outer / n_sub\n",
    "    for _ in range(n_sub):\n",
    "        k1 = rhs(Ak,                Bk,                Ck)\n",
    "        k2 = rhs(Ak - 0.5*h*k1[0],  Bk - 0.5*h*k1[1],  Ck - 0.5*h*k1[2])\n",
    "        k3 = rhs(Ak - 0.5*h*k2[0],  Bk - 0.5*h*k2[1],  Ck - 0.5*h*k2[2])\n",
    "        k4 = rhs(Ak -     h*k3[0],  Bk -     h*k3[1],  Ck -     h*k3[2])\n",
    "        Ak = Ak - (h/6)*(k1[0] + 2*k2[0] + 2*k3[0] + k4[0])\n",
    "        Bk = Bk - (h/6)*(k1[1] + 2*k2[1] + 2*k3[1] + k4[1])\n",
    "        Ck = Ck - (h/6)*(k1[2] + 2*k2[2] + 2*k3[2] + k4[2])\n",
    "    A[k-1], B[k-1], C[k-1] = Ak, Bk, Ck\n",
    "\n",
    "print('A(0) =\\n', A[0])\n",
    "print('B(0) =\\n', B[0])\n",
    "print('C(0) =', C[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2687a502",
   "metadata": {},
   "source": [
    "### 5.3 Visual — Frobenius norms grow backward in time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7849b05b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Plot ||A(t)||_F and ||B(t)||_F\n",
    "fig, ax = hf_fig((10, 4.5))\n",
    "ax.plot(t_grid/60, np.linalg.norm(A, axis=(1,2)), color=HF_TEAL,  lw=2.2, label='$\\\\|A(t)\\\\|_F$')\n",
    "ax.plot(t_grid/60, np.linalg.norm(B, axis=(1,2)), color=HF_AMBER, lw=2.2, label='$\\\\|B(t)\\\\|_F$')\n",
    "ax.axvline(T/60, color=HF_GOLD, ls='--', alpha=0.6, label='terminal $T$')\n",
    "ax.set_xlabel('time $t$ (min)', color=HF_TEXT)\n",
    "ax.set_ylabel('Frobenius norm',  color=HF_TEXT)\n",
    "ax.set_title('2D Riccati — Frobenius norms of $A(t)$ and $B(t)$', color=HF_GOLD)\n",
    "ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05ea1b4a",
   "metadata": {},
   "source": [
    "### 5.4 Visual — individual entries of $A(t)$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3f2b9b2d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Decompose A(t) into its 3 unique entries (symmetric)\n",
    "fig, ax = hf_fig((10, 4.5))\n",
    "ax.plot(t_grid/60, A[:, 0, 0], color=HF_TEAL,  lw=2, label='$A_{11}(t)$')\n",
    "ax.plot(t_grid/60, A[:, 1, 1], color=HF_AMBER, lw=2, label='$A_{22}(t)$')\n",
    "ax.plot(t_grid/60, A[:, 0, 1], color=HF_RED,   lw=2, label='$A_{12}(t)$')\n",
    "ax.set_xlabel('time $t$ (min)', color=HF_TEXT)\n",
    "ax.set_ylabel('entry value',    color=HF_TEXT)\n",
    "ax.set_title('Individual entries of $A(t)$ — symmetric matrix has 3 d.o.f.', color=HF_GOLD)\n",
    "ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a686d6d9",
   "metadata": {},
   "source": [
    "### 5.5 Visual — heatmap of $A(0)$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9dc3b79f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Visualise A(0) as a heatmap\n",
    "fig, ax = hf_fig((5, 4))\n",
    "im = ax.imshow(A[0], cmap='magma', aspect='equal')\n",
    "for i in range(d):\n",
    "    for j in range(d):\n",
    "        ax.text(j, i, f'{A[0,i,j]:.2e}', ha='center', va='center',\n",
    "                color='white' if abs(A[0,i,j]) < A[0].max()/2 else 'black',\n",
    "                fontsize=11, fontweight='bold')\n",
    "ax.set_xticks([0,1]); ax.set_yticks([0,1])\n",
    "ax.set_xticklabels(['asset 1', 'asset 2'], color=HF_TEXT)\n",
    "ax.set_yticklabels(['asset 1', 'asset 2'], color=HF_TEXT)\n",
    "ax.set_title('$A(0)$ — initial value-function quadratic form', color=HF_GOLD)\n",
    "cbar = fig.colorbar(im, ax=ax)\n",
    "cbar.ax.tick_params(colors=HF_TEXT)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf60fe4a",
   "metadata": {},
   "source": [
    "<a id=\"6\"></a>\n",
    "## 6 · Optimal trading speed under OU"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a343cacb",
   "metadata": {},
   "source": [
    "### 6.1 Closed-form\n",
    "\n",
    "Plugging the quadratic ansatz back into $v^\\star = \\eta^{-1}(\\nabla_q J - s)$ gives the **time-varying linear feedback law**:\n",
    "\n",
    "$$\n",
    "v^\\star(t,q,s) \\;=\\; \\eta^{-1}\\bigl(\\,2\\,A(t)\\,q \\;+\\; B(t)(s-\\bar\\mu) \\;+\\; C(t) \\;-\\; s\\,\\bigr).\n",
    "$$\n",
    "\n",
    "The trader liquidates inventory ($A(t)\\,q$ term) while exploiting the OU drift ($B(t)(s-\\bar\\mu)$ term)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76f656f1",
   "metadata": {},
   "source": [
    "### 6.2 Closed-loop simulation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ca3bb7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Forward simulation of the closed-loop optimal policy\n",
    "n_paths = 200\n",
    "S0      = np.array([100.0, 100.0])\n",
    "q0      = np.array([5.0, -3.0])         # long asset 1, short asset 2\n",
    "mu_bar  = S0.copy()\n",
    "dt      = T / N\n",
    "sqdt    = np.sqrt(dt)\n",
    "L_Sigma = np.linalg.cholesky(Sigma)\n",
    "\n",
    "q_paths = np.zeros((n_paths, N+1, d))\n",
    "S_paths = np.zeros((n_paths, N+1, d))\n",
    "v_paths = np.zeros((n_paths, N,   d))\n",
    "q_paths[:, 0] = q0\n",
    "S_paths[:, 0] = S0\n",
    "\n",
    "for p in range(n_paths):\n",
    "    rng_p = np.random.default_rng(1000 + p)\n",
    "    for k in range(N):\n",
    "        s = S_paths[p, k]\n",
    "        q = q_paths[p, k]\n",
    "        v = eta_inv @ (2*A[k] @ q + B[k] @ (s - mu_bar) + C[k] - s)\n",
    "        v_paths[p, k] = v\n",
    "        q_paths[p, k+1] = q + v*dt\n",
    "        dW_ = sqdt * rng_p.normal(size=d)\n",
    "        S_paths[p, k+1] = s + Phi @ (mu_bar - s)*dt + L_Sigma @ dW_\n",
    "\n",
    "# Median + IQR for inventory\n",
    "median_q = np.median(q_paths, axis=0)\n",
    "q25 = np.quantile(q_paths, 0.25, axis=0)\n",
    "q75 = np.quantile(q_paths, 0.75, axis=0)\n",
    "print(f'Simulated {n_paths} closed-loop paths, {N} steps each.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d5e5222",
   "metadata": {},
   "source": [
    "### 6.3 Visual — inventory trajectories with confidence band"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42d1b731",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = hf_fig((11, 4.5), ncols=2)\n",
    "for j in range(d):\n",
    "    ax = axes[j]\n",
    "    ax.plot(t_grid/60, q_paths[:, :, j].T, color=HF_TEAL, lw=0.4, alpha=0.15)\n",
    "    ax.plot(t_grid/60, median_q[:, j],     color=HF_GOLD, lw=2.5, label='median')\n",
    "    ax.fill_between(t_grid/60, q25[:, j], q75[:, j], color=HF_AMBER, alpha=0.35, label='IQR')\n",
    "    ax.axhline(0, color=HF_TEXT, ls='--', alpha=0.4)\n",
    "    ax.set_xlabel('time (min)', color=HF_TEXT)\n",
    "    ax.set_ylabel(f'$q^{{({j+1})}}_t$', color=HF_TEXT)\n",
    "    ax.set_title(f'Inventory · asset {j+1}', color=HF_GOLD)\n",
    "    ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT, loc='upper right')\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d53cd7a0",
   "metadata": {},
   "source": [
    "### 6.4 Visual — distribution of optimal trading speed at $t=0$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "783c08e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Histogram of v*(0) across the 200 paths\n",
    "fig, axes = hf_fig((11, 4), ncols=2)\n",
    "for j in range(d):\n",
    "    ax = axes[j]\n",
    "    ax.hist(v_paths[:, 0, j], bins=30, color=(HF_TEAL if j==0 else HF_AMBER),\n",
    "            edgecolor=HF_GOLD, alpha=0.75)\n",
    "    ax.axvline(np.median(v_paths[:, 0, j]), color=HF_RED, lw=2, ls='--', label='median')\n",
    "    ax.set_xlabel(f'$v^\\\\star_0$ — asset {j+1}', color=HF_TEXT)\n",
    "    ax.set_ylabel('count', color=HF_TEXT)\n",
    "    ax.set_title(f'Initial optimal speed — asset {j+1}', color=HF_GOLD)\n",
    "    ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f8c9e7b",
   "metadata": {},
   "source": [
    "<a id=\"7\"></a>\n",
    "## 7 · Hayashi–Yoshida non-synchronous covariance"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d22b3f79",
   "metadata": {},
   "source": [
    "### 7.1 The non-synchronous problem\n",
    "\n",
    "Crypto venues emit price updates at **different cadences** (BTC: ~10 ms ticks, SOL: ~50 ms, ETH options: ~1 s). Aggregating to a common grid of width $\\Delta t$ introduces the **Epps effect**: observed correlation decays toward zero as $\\Delta t \\to 0$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00b641d7",
   "metadata": {},
   "source": [
    "### 7.2 The Hayashi–Yoshida estimator\n",
    "\n",
    "Hayashi & Yoshida (2005) avoid grid aggregation by summing return products over **all overlapping native intervals**:\n",
    "\n",
    "$$\n",
    "\\widehat\\Sigma^{HY}_{ij} \\;=\\; \\sum_{k}\\sum_{\\ell}\\, \\Delta r_i^{(k)}\\,\\Delta r_j^{(\\ell)}\\;\\mathbb{1}_{\\bigl\\{I_i^{(k)} \\cap I_j^{(\\ell)} \\neq \\emptyset\\bigr\\}}\n",
    "$$\n",
    "\n",
    "where $I_i^{(k)} = (t_{i,k-1}, t_{i,k}]$ is the $k$-th native interval of asset $i$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29d251f4",
   "metadata": {},
   "source": [
    "### 7.3 Key properties\n",
    "\n",
    "| Property | Statement |\n",
    "|---|---|\n",
    "| **Consistency** | $\\widehat\\Sigma^{HY}_{ij} \\xrightarrow{p} \\langle r_i, r_j\\rangle_T$ as observation density → ∞ |\n",
    "| **Unbiasedness** | unbiased under non-synchronous Poisson sampling |\n",
    "| **Asymptotic normality** | central limit theorem → direct confidence intervals |\n",
    "| **Robustness** | no bandwidth, no bias-variance trade-off (unlike kernel methods) |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60a054dc",
   "metadata": {},
   "source": [
    "### 7.4 Visual — the Epps effect, simulated"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6489721a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Simulate two correlated assets sampled at different rates,\n",
    "# then compute naive grid-aggregated correlation as a function of grid width.\n",
    "T_sim = 60.0   # 1 minute\n",
    "N_fine = 60_000\n",
    "true_rho = 0.6\n",
    "L_corr = np.linalg.cholesky(np.array([[1.0, true_rho], [true_rho, 1.0]]))\n",
    "\n",
    "dt_fine = T_sim / N_fine\n",
    "dW = rng.normal(scale=np.sqrt(dt_fine), size=(N_fine, 2)) @ L_corr.T\n",
    "prices = np.exp(np.cumsum(0.001 * dW, axis=0))   # log-normal\n",
    "times = np.linspace(0, T_sim, N_fine, endpoint=False)\n",
    "\n",
    "# Naive grid aggregation at multiple Δt\n",
    "grid_widths_ms = [10, 25, 50, 100, 250, 500, 1000, 2500, 5000]\n",
    "empirical_rho = []\n",
    "for ms in grid_widths_ms:\n",
    "    dt_grid = ms / 1000.0\n",
    "    n_grid = int(T_sim / dt_grid)\n",
    "    edges = np.linspace(0, T_sim, n_grid + 1)\n",
    "    bin_idx = np.searchsorted(edges, times, side='right') - 1\n",
    "    bin_idx = np.clip(bin_idx, 0, n_grid-1)\n",
    "    last_in_bin = np.full((n_grid, 2), np.nan)\n",
    "    for b, p in zip(bin_idx, prices):\n",
    "        last_in_bin[b] = p\n",
    "    # forward-fill\n",
    "    for b in range(1, n_grid):\n",
    "        if np.isnan(last_in_bin[b, 0]): last_in_bin[b] = last_in_bin[b-1]\n",
    "    rets = np.diff(np.log(last_in_bin), axis=0)\n",
    "    rets = rets[~np.isnan(rets).any(axis=1)]\n",
    "    empirical_rho.append(np.corrcoef(rets.T)[0, 1])\n",
    "\n",
    "fig, ax = hf_fig((10, 4.5))\n",
    "ax.plot(grid_widths_ms, empirical_rho, 'o-', color=HF_TEAL, lw=2, ms=8, label='naive grid correlation')\n",
    "ax.axhline(true_rho, color=HF_GOLD, ls='--', lw=2, label=f'true $\\\\rho$ = {true_rho}')\n",
    "ax.set_xscale('log')\n",
    "ax.set_xlabel('grid width $\\\\Delta t$ (ms, log)', color=HF_TEXT)\n",
    "ax.set_ylabel('measured correlation', color=HF_TEXT)\n",
    "ax.set_title('Epps effect — naive correlation decays as $\\\\Delta t \\\\to 0$', color=HF_GOLD)\n",
    "ax.legend(facecolor=HF_PANEL, edgecolor=HF_GOLD, labelcolor=HF_TEXT)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "286e5ecf",
   "metadata": {},
   "source": [
    "<a id=\"8\"></a>\n",
    "## 8 · What the full course covers (paid)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0b9fcca",
   "metadata": {},
   "source": [
    "### 8.1 Topic ladder\n",
    "\n",
    "The full **80-page LaTeX course** (€34.99) and **Education tier** (€19/mo, 35+ notebooks) cover in rigorous detail:\n",
    "\n",
    "1. **Stochastic calculus prerequisites** — measure theory, filtered probability spaces, Brownian motion, Itô integral, Stratonovich, Girsanov change of measure.\n",
    "2. **HJB derivation from dynamic programming**, viscosity solutions, comparison theorem.\n",
    "3. **Quadratic ansatz proof** — why it solves the HJB exactly under linear-Gaussian dynamics, and what fails when leaving this class.\n",
    "4. **Matrix Riccati existence/uniqueness** — Bergault–Drissi–Guéant a-priori estimates, monotonicity, blow-up criteria.\n",
    "5. **Numerical integration** — backward RK4 stability analysis, sub-stepping, condition-number diagnostics.\n",
    "6. **Hayashi–Yoshida full derivation** — Doob–Meyer decomposition, CLT, finite-sample bias correction.\n",
    "7. **Multi-frequency $\\Phi$ calibration** — spectral decomposition, weighted least squares.\n",
    "8. **Option overlay theory** — Itô on $\\Pi_t = X_t + q_t S_t + \\Theta_t$, gamma harvest, theta decay, break-even constraint.\n",
    "9. **Adaptive Bayesian extension** (Baldacci–Manziuk 2020) — finite differences, deep RL, fill intensity models.\n",
    "10. **40+ solved exercises** with companion notebook reproducing every figure of the blog."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f344af3a",
   "metadata": {},
   "source": [
    "### 8.2 References\n",
    "\n",
    "1. Bergault, P., Drissi, F. & Guéant, O. (2022). *Multi-asset optimal execution and statistical arbitrage strategies under Ornstein–Uhlenbeck dynamics.* SIAM J. Fin. Math. **13**(1), 353–390.\n",
    "2. Baldacci, B. & Manziuk, I. (2020). *Adaptive trading strategies across liquidity pools.* arXiv:2008.07807.\n",
    "3. Almgren, R. & Chriss, N. (2001). *Optimal execution of portfolio transactions.* J. Risk **3**(2), 5–40.\n",
    "4. Avellaneda, M. & Stoikov, S. (2008). *High-frequency trading in a limit order book.* Quant. Finance **8**(3), 217–224.\n",
    "5. Hayashi, T. & Yoshida, N. (2005). *On covariance estimation of non-synchronously observed diffusion processes.* Bernoulli **11**(2), 359–379.\n",
    "6. Cartea, Á., Jaimungal, S. & Penalva, J. (2015). *Algorithmic and High-Frequency Trading.* Cambridge University Press."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d884e19",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "<div style=\"background:linear-gradient(135deg,rgba(212,175,55,0.15) 0%,rgba(244,208,63,0.05) 100%);border:1px solid #d4af37;border-radius:14px;padding:24px;margin-top:24px;\">\n",
    "<h3 style=\"color:#d4af37;margin-top:0;\">🎯 Get the full content</h3>\n",
    "<p style=\"font-size:14px;line-height:1.6;\">\n",
    "<b>One-off purchase</b> — LaTeX course PDF + companion notebook with 40+ solved exercises: <b>€34.99</b> · <a href='https://hfthot-lab.eu/store/microstructure-ou-course' style='color:#d4af37;'>Buy now</a><br>\n",
    "<b>Subscription</b> — Education tier with 35+ prerequisite notebooks, arXiv research hub, monthly updates: <b>€19/mo</b> · <a href='https://hfthot-lab.eu/pricing.html' style='color:#d4af37;'>Subscribe</a>\n",
    "</p>\n",
    "<p style=\"font-size:12px;color:#8aa0aa;margin-top:18px;margin-bottom:0;\">© 2026 HFThot Research Lab · CC BY-NC 4.0 sample · contact@hfthot-lab.eu</p>\n",
    "</div>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
