/- Copyright (c) 2026 Raphael Coelho. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Raphael Coelho -/ module -- `import Mathlib` comes transitively through `BlackScholes.PDE` / -- `Foundations.FeynmanKacHeatEquation`. public import MathFin.Foundations.FeynmanKacHeatEquation public import MathFin.BlackScholes.PDE public import MathFin.BlackScholes.Call /-! # The Black–Scholes PDE, derived from Feynman–Kac `BlackScholes/PDE.lean` proves `bs_pde_holds` *backward*: it has the closed form `bsV` and checks, by differentiating it, that it satisfies `∂_t V + ½σ²S²∂_SS V + rS∂_S V − rV = 0`. `BlackScholes/PDEFromIto.lean` records the *algebraic* shape of the no-arbitrage relation but is explicit that the continuous-time martingale step is deferred — its `… = 0` is a `ring` identity, not a derivation. This file closes that gap from the **probabilistic** side. The Black–Scholes value is a Gaussian convolution of the payoff — a Feynman–Kac representation — and it satisfies the PDE *because* the heat kernel does. The deep machinery of `Foundations/FeynmanKacHeatEquation.lean` — previously an orphan, consumed by no pricing result — is now load-bearing for pricing. ## The program (four steps) 1. **Kernel-side heat equation** *(in `Foundations/FeynmanKacHeatEquation.lean`)*: for `g` locally integrable with sub-Gaussian growth and `τ > 0`, `u(τ, x) := feynmanU g τ x` is smooth and `∂_τ u = ½ ∂_xx u`. The derivatives fall on the smooth, fast-decaying *kernel* (`∂_τ K = ½ ∂_yy K`, already proved), so `g` needs no regularity — the call payoff's kink and exponential growth are irrelevant. 2. **Feynman–Kac representation of the price** *(`bsV_eq_feynmanU`, below)*: `bsV K r σ S τ = feynmanU (fun ξ ↦ e^{−rτ}·(e^ξ − K)⁺) (σ²τ) (log S + (r − σ²/2)τ)`. This is the milestone that wires `feynmanU` into the pricing layer. 3. **Log-price + discount change of variables** `S = eˣ`, `t = T − τ`: transport step 1's heat equation through the substitution onto the Black–Scholes operator. 4. **`bsV` solves the BS PDE, via Feynman–Kac**: assemble 1–3 — an independent, conceptually grounded derivation of `bs_pde_holds`. Step 1 is proved in `Foundations/FeynmanKacHeatEquation`. This file establishes steps 2–4. Step 2 is `bsV_eq_feynmanU` with the discounted-heat-flow bridge `bsV_eq_discount_feynmanU` (the result that makes `feynmanU` load-bearing for pricing). Step 3 is the three Greeks via Feynman–Kac: `hasDerivAt_bsV_S_fk` (Delta — the `S`-derivative as a kernel moment), `hasDerivAt_bsV_tau_fk` (Theta — the product rule on the discount `e^{−rτ'}` times the discounted heat flow, with *both* kernel arguments `σ²τ'` and `log S + (r−σ²/2)τ'` moving with `τ'`), and `hasDerivAt_bsV_SS_fk` (Gamma). The Theta consumes `hasDerivAt_feynmanU_comp` and through it the heat kernel's joint Fréchet differentiability `hasFDerivAt_heatKernel`; the curve domination bounds the two kernel-derivative terms separately (`curve_sq_ratio_le` / `curve_abs_ratio_le` + `heatKernel_loc_le`), avoiding the single-mega-constant blow-up that defeated the brute force. Step 4 is the capstone `bsV_satisfies_bs_pde_via_feynmanKac`: the PDE assembly `−V_τ + ½σ²S²V_SS + rSV_S − rV = e^{−rτ}σ²(½U_xx − U_t) = 0` via `feynmanU_heat_equation` and the operator cancellation `−(r−σ²/2) − ½σ² + r = 0`, on top of the in-place Greeks above. The benchmark `sc-bs-pde-feynman-kac` re-exports it. -/ @[expose] public section open MeasureTheory ProbabilityTheory open scoped NNReal ENNReal namespace MathFin open FeynmanKacHeatEquation /-- **Step 2 — Feynman–Kac representation of the Black–Scholes call value.** -/ theorem bsV_eq_feynmanU {K r σ S τ : ℝ} (hS : 0 < S) (hK : 0 < K) (hσ : 0 < σ) (hτ : 0 < τ) : bsV K r σ S τ = FeynmanKacHeatEquation.feynmanU (fun ξ => Real.exp (-(r * τ)) * max (Real.exp ξ - K) 0) (σ ^ 2 * τ) (Real.log S + (r - σ ^ 2 / 2) * τ) := by have hvar : (0:ℝ) < σ ^ 2 * τ := by positivity set g : ℝ → ℝ := fun ξ => Real.exp (-(r * τ)) * max (Real.exp ξ - K) 0 with hg have hg_cont : Continuous g := by rw [hg] exact continuous_const.mul ((Real.continuous_exp.sub continuous_const).max continuous_const) have hmap : Measure.map (fun ω => σ * Real.sqrt τ * ω) (gaussianReal 0 1) = gaussianReal 0 (σ ^ 2 * τ).toNNReal := by rw [gaussianReal_map_const_mul (σ * Real.sqrt τ), mul_zero, mul_one] congr 1 apply NNReal.coe_injective rw [NNReal.coe_mk, Real.coe_toNNReal _ hvar.le, mul_pow, Real.sq_sqrt hτ.le] rw [FeynmanKacHeatEquation.feynmanU_eq_integral_of_map (B := fun _ ω => σ * Real.sqrt τ * ω) (μ := gaussianReal 0 1) (measurable_const.mul measurable_id).aemeasurable hmap hg_cont hvar (Real.log S + (r - σ ^ 2 / 2) * τ)] have hpoint : ∀ ω : ℝ, g (Real.log S + (r - σ ^ 2 / 2) * τ + σ * Real.sqrt τ * ω) = Real.exp (-r * τ) * max (bsTerminal S r σ τ ω - K) 0 := by intro ω have hexp : Real.exp (Real.log S + (r - σ ^ 2 / 2) * τ + σ * Real.sqrt τ * ω) = bsTerminal S r σ τ ω := by simp only [bsTerminal] rw [show Real.log S + (r - σ ^ 2 / 2) * τ + σ * Real.sqrt τ * ω = Real.log S + ((r - σ ^ 2 / 2) * τ + σ * Real.sqrt τ * ω) from by ring, Real.exp_add, Real.exp_log hS] simp only [hg, hexp, neg_mul] simp_rw [hpoint] have hcf := bs_call_formula (Q := gaussianReal 0 1) (Z := (id : ℝ → ℝ)) (S_0 := S) (K := K) (r := r) (σ := σ) (T := τ) ⟨hS, hK, hσ, hτ, HasLaw.id⟩ simp only [id_eq] at hcf rw [hcf, bsV, neg_mul] private lemma callPayoff_continuous (K : ℝ) : Continuous (fun ξ => max (Real.exp ξ - K) 0) := (Real.continuous_exp.sub continuous_const).max continuous_const private lemma callPayoff_le_exp {K : ℝ} (hK : 0 < K) (z : ℝ) : |max (Real.exp z - K) 0| ≤ Real.exp z := by rw [abs_of_nonneg (le_max_right _ _)] exact max_le (by linarith) (Real.exp_nonneg z) /-- **Step 3 (bridge) — the Black–Scholes value is a discounted heat flow.** -/ theorem bsV_eq_discount_feynmanU {K r σ S τ : ℝ} (hS : 0 < S) (hK : 0 < K) (hσ : 0 < σ) (hτ : 0 < τ) : bsV K r σ S τ = Real.exp (-(r * τ)) * feynmanU (fun ξ => max (Real.exp ξ - K) 0) (σ ^ 2 * τ) (Real.log S + (r - σ ^ 2 / 2) * τ) := by rw [bsV_eq_feynmanU hS hK hσ hτ] simp only [feynmanU] rw [← integral_const_mul] congr 1 ext z ring /-- **Step 3 (Delta) — the `S`-derivative of `bsV`, via Feynman–Kac.** -/ private lemma hasDerivAt_bsV_S_fk {K r σ τ : ℝ} (hK : 0 < K) (hσ : 0 < σ) (hτ : 0 < τ) {S : ℝ} (hS : 0 < S) : HasDerivAt (fun S' => bsV K r σ S' τ) (Real.exp (-(r * τ)) * ((∫ z, max (Real.exp z - K) 0 * ((z - (Real.log S + (r - σ ^ 2 / 2) * τ)) / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - (Real.log S + (r - σ ^ 2 / 2) * τ)))) * S⁻¹)) S := by have ht₀ : (0 : ℝ) < σ ^ 2 * τ := by positivity have hchain := (hasDerivAt_feynmanU_x ht₀ (callPayoff_continuous K) (callPayoff_le_exp hK) (Real.log S + (r - σ ^ 2 / 2) * τ)).comp S ((Real.hasDerivAt_log hS.ne').add_const ((r - σ ^ 2 / 2) * τ)) refine (hchain.const_mul (Real.exp (-(r * τ)))).congr_of_eventuallyEq ?_ filter_upwards [isOpen_Ioi.mem_nhds hS] with S' hS' exact bsV_eq_discount_feynmanU hS' hK hσ hτ /-- **Step 3 (Theta) — the `τ`-derivative of `bsV`, via Feynman–Kac.** The price's time-decay, derived from the heat kernel's curve derivative (`hasDerivAt_feynmanU_comp`): product rule on the discount `e^{−rτ'}` and the discounted heat flow (both kernel arguments `σ²τ'` and `log S + (r−σ²/2)τ'` move with `τ'`), with the bridge `bsV_eq_discount_feynmanU` for the `τ' > 0` domain. This is the consumer that makes `hasDerivAt_feynmanU_comp` — and through it the heat kernel's *joint* differentiability `hasFDerivAt_heatKernel` — load-bearing for the Black–Scholes time-derivative. -/ private lemma hasDerivAt_bsV_tau_fk {K r σ : ℝ} (hK : 0 < K) (hσ : 0 < σ) {S : ℝ} (hS : 0 < S) {τ : ℝ} (hτ : 0 < τ) : HasDerivAt (fun τ' => bsV K r σ S τ') (-r * Real.exp (-(r * τ)) * feynmanU (fun ξ => max (Real.exp ξ - K) 0) (σ ^ 2 * τ) (Real.log S + (r - σ ^ 2 / 2) * τ) + Real.exp (-(r * τ)) * (∫ z, max (Real.exp z - K) 0 * (σ ^ 2 * (heatKernel (σ ^ 2 * τ) (z - (Real.log S + (r - σ ^ 2 / 2) * τ)) * ((z - (Real.log S + (r - σ ^ 2 / 2) * τ)) ^ 2 - σ ^ 2 * τ) / (2 * (σ ^ 2 * τ) ^ 2)) + (r - σ ^ 2 / 2) * ((z - (Real.log S + (r - σ ^ 2 / 2) * τ)) / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - (Real.log S + (r - σ ^ 2 / 2) * τ)))) ∂volume)) τ := by have hσ2 : (0 : ℝ) < σ ^ 2 := by positivity have hexp : HasDerivAt (fun τ' => Real.exp (-(r * τ'))) (-r * Real.exp (-(r * τ))) τ := by have h1 : HasDerivAt (fun τ' : ℝ => -(r * τ')) (-r) τ := by have h := (hasDerivAt_id τ).const_mul (-r) simp only [neg_mul, mul_one] at h exact h have h2 := h1.exp convert h2 using 1 ring have hfk := hasDerivAt_feynmanU_comp (h := fun ξ => max (Real.exp ξ - K) 0) (callPayoff_continuous K) (callPayoff_le_exp hK) (α := σ ^ 2) (β := r - σ ^ 2 / 2) (x₀ := Real.log S) hσ2 hτ refine (hexp.mul hfk).congr_of_eventuallyEq ?_ filter_upwards [isOpen_Ioi.mem_nhds hτ] with τ' hτ' exact bsV_eq_discount_feynmanU hS hK hσ hτ' /-- **Step 3 (Gamma) — the second `S`-derivative of `bsV`, via Feynman–Kac.** Differentiating the Delta `e^{−rτ}·U_x·S⁻¹` (from `hasDerivAt_bsV_S_fk`) in `S`: the first space-derivative integral `U_x` moves through `log S` (chain rule on `hasDerivAt_feynmanU_xx`, giving `U_xx·S⁻¹`) and the `S⁻¹` factor contributes `−U_x·S⁻²`, so `∂_SS V = e^{−rτ}·(U_xx − U_x)/S²`. The third in-place Greek-via-FK awaiting the step-4 PDE assembly. -/ private lemma hasDerivAt_bsV_SS_fk {K r σ τ : ℝ} (hK : 0 < K) (hσ : 0 < σ) (hτ : 0 < τ) {S : ℝ} (hS : 0 < S) : HasDerivAt (fun S' => Real.exp (-(r * τ)) * ((∫ z, max (Real.exp z - K) 0 * ((z - (Real.log S' + (r - σ ^ 2 / 2) * τ)) / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - (Real.log S' + (r - σ ^ 2 / 2) * τ)))) * S'⁻¹)) (Real.exp (-(r * τ)) * (((∫ z, max (Real.exp z - K) 0 * (heatKernel (σ ^ 2 * τ) (z - (Real.log S + (r - σ ^ 2 / 2) * τ)) * ((z - (Real.log S + (r - σ ^ 2 / 2) * τ)) ^ 2 - σ ^ 2 * τ) / (σ ^ 2 * τ) ^ 2)) - (∫ z, max (Real.exp z - K) 0 * ((z - (Real.log S + (r - σ ^ 2 / 2) * τ)) / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - (Real.log S + (r - σ ^ 2 / 2) * τ))))) / S ^ 2)) S := by have ht₀ : (0 : ℝ) < σ ^ 2 * τ := by positivity have hg := (hasDerivAt_feynmanU_xx ht₀ (callPayoff_continuous K) (callPayoff_le_exp hK) (Real.log S + (r - σ ^ 2 / 2) * τ)).comp S ((Real.hasDerivAt_log hS.ne').add_const ((r - σ ^ 2 / 2) * τ)) have hprod := (hg.mul (hasDerivAt_inv hS.ne')).const_mul (Real.exp (-(r * τ))) have hSne : S ≠ 0 := hS.ne' set Uxx := ∫ z, max (Real.exp z - K) 0 * (heatKernel (σ ^ 2 * τ) (z - (Real.log S + (r - σ ^ 2 / 2) * τ)) * ((z - (Real.log S + (r - σ ^ 2 / 2) * τ)) ^ 2 - σ ^ 2 * τ) / (σ ^ 2 * τ) ^ 2) set Ux := ∫ z, max (Real.exp z - K) 0 * ((z - (Real.log S + (r - σ ^ 2 / 2) * τ)) / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - (Real.log S + (r - σ ^ 2 / 2) * τ))) have hUx_eq : ((fun x' => ∫ (z : ℝ), max (Real.exp z - K) 0 * ((z - x') / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - x'))) ∘ fun x => Real.log x + (r - σ ^ 2 / 2) * τ) S = Ux := by simp only [Function.comp_def] rfl refine hprod.congr_deriv ?_ rw [hUx_eq] field_simp [hS.ne'] ring /-- Integrability of the call payoff against the kernel's time-derivative integrand `h·∂_t K` (`|h| ≤ eᶻ`), dominated by the sub-Gaussian envelope. Needed to split the combined `τ`-derivative integral in the PDE assembly. -/ private lemma integrable_payoff_mul_dtK {t : ℝ} (ht : 0 < t) {K : ℝ} (hK : 0 < K) (x : ℝ) : Integrable (fun z => max (Real.exp z - K) 0 * (heatKernel t (z - x) * ((z - x) ^ 2 - t) / (2 * t ^ 2))) volume := by apply ((integrable_exp_mul_poly_heatKernel ht x t).const_mul (1 / (2 * t ^ 2))).mono' · exact ((callPayoff_continuous K).mul (((continuous_heatKernel t).comp (continuous_id.sub continuous_const)).mul (((continuous_id.sub continuous_const).pow 2).sub continuous_const) |>.div_const _)).aestronglyMeasurable · refine ae_of_all _ fun z => ?_ have hKnn := heatKernel_nonneg ht (z - x) have hnorm : ‖max (Real.exp z - K) 0 * (heatKernel t (z - x) * ((z - x) ^ 2 - t) / (2 * t ^ 2))‖ = 1 / (2 * t ^ 2) * (|max (Real.exp z - K) 0| * (heatKernel t (z - x) * |(z - x) ^ 2 - t|)) := by rw [Real.norm_eq_abs, abs_mul, abs_div, abs_mul, abs_of_nonneg hKnn, abs_of_nonneg (by positivity : (0:ℝ) ≤ 2 * t ^ 2)]; ring rw [hnorm] have habs : |(z - x) ^ 2 - t| ≤ (z - x) ^ 2 + t := by rw [abs_le]; constructor <;> nlinarith [sq_nonneg (z - x)] apply mul_le_mul_of_nonneg_left _ (by positivity : (0:ℝ) ≤ 1 / (2 * t ^ 2)) calc |max (Real.exp z - K) 0| * (heatKernel t (z - x) * |(z - x) ^ 2 - t|) ≤ Real.exp z * (heatKernel t (z - x) * ((z - x) ^ 2 + t)) := by refine mul_le_mul (callPayoff_le_exp hK z) ?_ (mul_nonneg hKnn (abs_nonneg _)) (Real.exp_nonneg z) exact mul_le_mul_of_nonneg_left habs hKnn _ = Real.exp z * (((z - x) ^ 2 + t) * heatKernel t (z - x)) := by ring /-- Integrability of the call payoff against the kernel's space-derivative integrand `h·∂_x K`. -/ private lemma integrable_payoff_mul_dxK {t : ℝ} (ht : 0 < t) {K : ℝ} (hK : 0 < K) (x : ℝ) : Integrable (fun z => max (Real.exp z - K) 0 * ((z - x) / t * heatKernel t (z - x))) volume := by apply ((integrable_exp_mul_poly_heatKernel ht x 1).const_mul (1 / t)).mono' · exact ((callPayoff_continuous K).mul (((continuous_id.sub continuous_const).div_const t).mul ((continuous_heatKernel t).comp (continuous_id.sub continuous_const)))).aestronglyMeasurable · refine ae_of_all _ fun z => ?_ have hKnn := heatKernel_nonneg ht (z - x) have hnorm : ‖max (Real.exp z - K) 0 * ((z - x) / t * heatKernel t (z - x))‖ = 1 / t * (|max (Real.exp z - K) 0| * (|z - x| * heatKernel t (z - x))) := by rw [Real.norm_eq_abs, abs_mul, abs_mul, abs_div, abs_of_nonneg hKnn, abs_of_nonneg ht.le]; ring rw [hnorm] have habs : |z - x| ≤ (z - x) ^ 2 + 1 := by nlinarith [sq_nonneg (|z - x| - 1), sq_abs (z - x), abs_nonneg (z - x)] apply mul_le_mul_of_nonneg_left _ (by positivity : (0:ℝ) ≤ 1 / t) refine mul_le_mul (callPayoff_le_exp hK z) ?_ (mul_nonneg (abs_nonneg _) hKnn) (Real.exp_nonneg z) exact mul_le_mul_of_nonneg_right habs hKnn /-- **Step 4 — the Black–Scholes PDE, derived from Feynman–Kac.** The call value's actual derivatives satisfy `−∂_τ V + ½σ²S²·∂_SS V + rS·∂_S V − rV = 0`. The three derivatives are the Feynman–Kac Greeks (`hasDerivAt_bsV_{S,SS,tau}_fk`), each a heat-kernel integral; the operator vanishes *because the heat kernel does*: the `U_x` drift terms cancel algebraically and `∂_t U = ½ ∂_xx U` is the kernel heat equation (`feynmanU_heat_equation`). This is the independent, probabilistically-grounded re-derivation of `bs_pde_holds` — closing the two-tower gap by making `feynmanU` load-bearing for the PDE. (The hard differentiability work — uniform domination, differentiation under the integral — already lives in `hasDerivAt_feynmanU_comp` and the Greeks; `feynmanU_heat_equation` supplies only the algebraic kernel identity `∂_t K = ½ ∂_xx K`, and the operator vanishing is then exact algebra.) -/ theorem bsV_satisfies_bs_pde_via_feynmanKac {K r σ : ℝ} (hK : 0 < K) (hσ : 0 < σ) {S τ : ℝ} (hS : 0 < S) (hτ : 0 < τ) : ∃ Vτ VS VSS : ℝ, HasDerivAt (fun τ' => bsV K r σ S τ') Vτ τ ∧ HasDerivAt (fun S' => bsV K r σ S' τ) VS S ∧ HasDerivAt (fun S' => deriv (fun S'' => bsV K r σ S'' τ) S') VSS S ∧ -Vτ + (1 / 2) * σ ^ 2 * S ^ 2 * VSS + r * S * VS - r * bsV K r σ S τ = 0 := by refine ⟨_, _, _, hasDerivAt_bsV_tau_fk hK hσ hS hτ, hasDerivAt_bsV_S_fk hK hσ hτ hS, (hasDerivAt_bsV_SS_fk (r := r) hK hσ hτ hS).congr_of_eventuallyEq ?_, ?_⟩ · filter_upwards [isOpen_Ioi.mem_nhds hS] with S' hS' exact (hasDerivAt_bsV_S_fk hK hσ hτ hS').deriv · have ht₀ : (0 : ℝ) < σ ^ 2 * τ := by positivity have hSne : S ≠ 0 := hS.ne' set c₀ : ℝ := Real.log S + (r - σ ^ 2 / 2) * τ have hheat := feynmanU_heat_equation ht₀ (fun ξ => max (Real.exp ξ - K) 0) c₀ rw [bsV_eq_discount_feynmanU hS hK hσ hτ, show (∫ z, max (Real.exp z - K) 0 * (σ ^ 2 * (heatKernel (σ ^ 2 * τ) (z - c₀) * ((z - c₀) ^ 2 - σ ^ 2 * τ) / (2 * (σ ^ 2 * τ) ^ 2)) + (r - σ ^ 2 / 2) * ((z - c₀) / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - c₀)))) = σ ^ 2 * (∫ z, max (Real.exp z - K) 0 * (heatKernel (σ ^ 2 * τ) (z - c₀) * ((z - c₀) ^ 2 - σ ^ 2 * τ) / (2 * (σ ^ 2 * τ) ^ 2))) + (r - σ ^ 2 / 2) * (∫ z, max (Real.exp z - K) 0 * ((z - c₀) / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - c₀))) from by rw [← integral_const_mul, ← integral_const_mul, ← integral_add ((integrable_payoff_mul_dtK ht₀ hK c₀).const_mul _) ((integrable_payoff_mul_dxK ht₀ hK c₀).const_mul _)] congr 1; ext z; ring, hheat] set U := feynmanU (fun ξ => max (Real.exp ξ - K) 0) (σ ^ 2 * τ) c₀ set Uxx := ∫ z, max (Real.exp z - K) 0 * (heatKernel (σ ^ 2 * τ) (z - c₀) * ((z - c₀) ^ 2 - σ ^ 2 * τ) / (σ ^ 2 * τ) ^ 2) set Ux := ∫ z, max (Real.exp z - K) 0 * ((z - c₀) / (σ ^ 2 * τ) * heatKernel (σ ^ 2 * τ) (z - c₀)) field_simp ring end MathFin