/-
Copyright (c) 2026 Raphael Coelho. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Raphael Coelho
-/
module

public import Mathlib
public import MathFin.Binomial.Model

/-!
# Cox-Ross-Rubinstein convergence to Black-Scholes

We formalize the **CRR parameterization** of the binomial tree:
with `Δt = T/n`, `u_n = e^{σ √Δt}`, `d_n = e^{-σ √Δt}`,
the n-step risk-neutral up-probability is

    `p_n = (e^{r Δt} − d_n) / (u_n − d_n).`

This file proves:
1. **One-step risk-neutral martingale identity** (exact, algebraic):
   `p_n · u_n + (1 − p_n) · d_n = e^{r Δt}`.
2. **Exponential-difference-quotient limits** at 0 used throughout the proof:
   * `(e^{c x} − 1) / x → c` as `x → 0` (first-order Taylor of exp at 0).
   * `(e^{c h²} − 1) / h² → c` (composition with `h ↦ h²`).
   * `(e^{σ h} − e^{-σ h}) / h → 2σ` (sinh-derivative at 0).
3. **CRR probability limit**: `p_n → 1/2` as `n → ∞`. This implies
   `4 p_n (1 − p_n) → 1`, which gives the variance limit
   `n · σ² Δt · 4 p_n (1 − p_n) → σ² T`.

## Scope

The **full pricing convergence**

    `binomialPrice (u_n) (d_n) (rΔt) (max(· − K, 0)) n S_0  →  bs_call_price`

is **proved** in `Binomial/CRRCharFun.lean` (`binomialPrice_call_tendsto_bs`):
a characteristic-function + Lévy-continuity route gives convergence in
distribution, and a put-call-parity argument sidesteps the triangular-array
CLT and the uniform-integrability/Vitali step entirely (the put payoff is
bounded, so weak convergence applies directly; parity lifts it to the call).

What this file provides is the **classical analytic CRR↔BS correspondence**
on the mean and variance of one log-return increment — the substantive
textbook computations feeding that limit.
-/

@[expose] public section

namespace MathFin

open Filter
open scoped Topology

/-! ### CRR parameterization -/

/-- CRR step size: `Δt = T / n`. -/
noncomputable def crrStep (T : ℝ) (n : ℕ) : ℝ := T / n

/-- CRR up-factor: `u_n = exp(σ √(T/n))`. -/
noncomputable def crrUp (σ T : ℝ) (n : ℕ) : ℝ := Real.exp (σ * Real.sqrt (crrStep T n))

/-- CRR down-factor: `d_n = exp(-σ √(T/n))`. -/
noncomputable def crrDown (σ T : ℝ) (n : ℕ) : ℝ := Real.exp (-(σ * Real.sqrt (crrStep T n)))

/-- CRR per-step continuously-compounded rate: `r · Δt = r T / n`. -/
noncomputable def crrPerStepRate (r T : ℝ) (n : ℕ) : ℝ := r * crrStep T n

/-- CRR risk-neutral up-probability. -/
noncomputable def crrProb (r σ T : ℝ) (n : ℕ) : ℝ :=
  (Real.exp (crrPerStepRate r T n) - crrDown σ T n) / (crrUp σ T n - crrDown σ T n)

/-! ### Positivity and basic facts -/

/-- The CRR up-factor `u = exp(σ√(T/n))` is positive. -/
lemma crrUp_pos (σ T : ℝ) (n : ℕ) : 0 < crrUp σ T n := Real.exp_pos _

/-- The CRR down-factor `d = exp(-σ√(T/n))` is positive. -/
lemma crrDown_pos (σ T : ℝ) (n : ℕ) : 0 < crrDown σ T n := Real.exp_pos _

/-- Under `σ > 0, T > 0, n ≥ 1`, the up-factor exceeds the down-factor. -/
lemma crrDown_lt_crrUp {σ T : ℝ} (hσ : 0 < σ) (hT : 0 < T) {n : ℕ} (hn : 1 ≤ n) :
    crrDown σ T n < crrUp σ T n := by
  have h_n_pos : (0 : ℝ) < n := by exact_mod_cast hn
  have h_step_pos : 0 < crrStep T n := div_pos hT h_n_pos
  have h_sqrt_pos : 0 < Real.sqrt (crrStep T n) := Real.sqrt_pos.mpr h_step_pos
  unfold crrDown crrUp
  rw [Real.exp_lt_exp]
  linarith [mul_pos hσ h_sqrt_pos]

/-- At `n = 0` the CRR step degenerates: `crrStep T 0 = T / 0 = 0`, so `u = d = 1`
and the risk-neutral probability is the junk value `0 / 0 = 0`.

This is why the no-arbitrage hypothesis of the convergence theorems is stated for
`0 < n`. `BinomialNoArb 1 1 0` is *false* — it demands `Real.exp 0 < 1` — so a
hypothesis quantified over every `n` is unsatisfiable and the theorem carrying it
says nothing. The `0 ≤ p ≤ 1` bound the limit actually consumes survives at
`n = 0` regardless, which is what makes the restricted hypothesis enough. -/
@[simp]
lemma crrProb_zero (r σ T : ℝ) : crrProb r σ T 0 = 0 := by
  simp [crrProb, crrUp, crrDown, crrPerStepRate, crrStep]

/-- **The CRR no-arbitrage condition is satisfiable, and generically so.** If the
volatility dominates the drift over the horizon (`|r|·√T < σ`), then every CRR
step with `n ≥ 1` is arbitrage-free.

Some restriction is unavoidable in both directions: `n = 0` is degenerate
(`crrProb_zero`), and for a fixed `n` a large enough `|r|` pushes `e^{rΔt}`
outside `[d, u]`. Since `√(T/n) ≤ √T` for `n ≥ 1`, the single bound `|r|·√T < σ`
covers every step at once — so this discharges the hypothesis of
`binomialPrice_call_tendsto_bs_closed` on a nonempty set of parameters. -/
lemma binomialNoArb_crr {r σ T : ℝ} (hT : 0 < T) (hrT : |r| * Real.sqrt T < σ)
    {n : ℕ} (hn : 0 < n) :
    BinomialNoArb (crrUp σ T n) (crrDown σ T n) (crrPerStepRate r T n) := by
  have hn1 : (1 : ℕ) ≤ n := hn
  have hnR : (1 : ℝ) ≤ n := by exact_mod_cast hn1
  have hstep : 0 < crrStep T n := div_pos hT (by linarith)
  have hsqrt : 0 < Real.sqrt (crrStep T n) := Real.sqrt_pos.mpr hstep
  -- `√Δt ≤ √T`, so the single bound `|r|·√T < σ` bounds every step.
  have hmono : Real.sqrt (crrStep T n) ≤ Real.sqrt T :=
    Real.sqrt_le_sqrt (by rw [crrStep]; exact div_le_self hT.le hnR)
  have hkey : |r| * Real.sqrt (crrStep T n) < σ :=
    lt_of_le_of_lt (mul_le_mul_of_nonneg_left hmono (abs_nonneg r)) hrT
  -- hence `|r·Δt| < σ·√Δt`, which is exactly `d < e^{rΔt} < u`.
  have hsq : crrStep T n = Real.sqrt (crrStep T n) * Real.sqrt (crrStep T n) :=
    (Real.mul_self_sqrt hstep.le).symm
  have hbound : |r * crrStep T n| < σ * Real.sqrt (crrStep T n) := by
    calc |r * crrStep T n| = |r| * crrStep T n := by rw [abs_mul, abs_of_pos hstep]
      _ = |r| * Real.sqrt (crrStep T n) * Real.sqrt (crrStep T n) := by
          rw [mul_assoc, ← hsq]
      _ < σ * Real.sqrt (crrStep T n) := mul_lt_mul_of_pos_right hkey hsqrt
  rw [abs_lt] at hbound
  refine ⟨Real.exp_pos _, ?_, ?_⟩
  · show Real.exp (-(σ * Real.sqrt (crrStep T n))) < Real.exp (r * crrStep T n)
    exact Real.exp_lt_exp.mpr hbound.1
  · show Real.exp (r * crrStep T n) < Real.exp (σ * Real.sqrt (crrStep T n))
    exact Real.exp_lt_exp.mpr hbound.2

/-! ### One-step risk-neutral martingale identity (algebraic, exact) -/

/-- **CRR one-step martingale identity** (exact, not asymptotic):
    `p · u + (1 − p) · d = e^{r Δt}`. The discrete-time risk-neutral measure makes
    the one-step discounted asset price a martingale.

    Requires the no-arbitrage condition `d < e^{rΔt}` (implied by `d < u` when
    we're between them, but explicit here so this works in either parameterization). -/
theorem crr_one_step_martingale {σ T r : ℝ} {n : ℕ}
    (h_du : crrDown σ T n < crrUp σ T n) :
    crrProb r σ T n * crrUp σ T n + (1 - crrProb r σ T n) * crrDown σ T n
      = Real.exp (crrPerStepRate r T n) := by
  have h_ud : 0 < crrUp σ T n - crrDown σ T n := sub_pos.mpr h_du
  have h_ud_ne : crrUp σ T n - crrDown σ T n ≠ 0 := h_ud.ne'
  unfold crrProb
  field_simp
  ring

/-! ### Step-size limits -/

/-- The CRR step `Δt = T/n` tends to 0 as `n → ∞`. -/
lemma tendsto_crrStep_zero (T : ℝ) : Tendsto (crrStep T) atTop (𝓝 0) := by
  unfold crrStep
  exact Tendsto.div_atTop tendsto_const_nhds (tendsto_natCast_atTop_atTop (R := ℝ))

/-- `√(T/n) → 0` as `n → ∞`. -/
lemma tendsto_sqrt_crrStep_zero (T : ℝ) :
    Tendsto (fun n ↦ Real.sqrt (crrStep T n)) atTop (𝓝 0) := by
  rw [show (0 : ℝ) = Real.sqrt 0 from Real.sqrt_zero.symm]
  exact (Real.continuous_sqrt.tendsto 0).comp (tendsto_crrStep_zero T)

/-! ### Exponential difference quotients (first-order Taylor at 0) -/

/-- **Difference quotient of `exp` at 0**: `(e^{c x} − 1) / x → c` as `x → 0` (`x ≠ 0`).
    Just `HasDerivAt (fun x ↦ e^{cx}) c 0` unpacked via `hasDerivAt_iff_tendsto_slope`. -/
lemma tendsto_exp_sub_one_div (c : ℝ) :
    Tendsto (fun x : ℝ ↦ (Real.exp (c * x) - 1) / x) (𝓝[≠] 0) (𝓝 c) := by
  have h_deriv : HasDerivAt (fun x : ℝ ↦ Real.exp (c * x)) c 0 := by
    have h_lin : HasDerivAt (fun x : ℝ ↦ c * x) c 0 := by
      simpa using (hasDerivAt_id (0 : ℝ)).const_mul c
    have := h_lin.exp
    simpa using this
  have h_slope := h_deriv.tendsto_slope
  have h_eq : ∀ x : ℝ,
      slope (fun x : ℝ ↦ Real.exp (c * x)) 0 x = (Real.exp (c * x) - 1) / x := by
    intro x
    rw [slope_def_field]
    simp [Real.exp_zero]
  exact h_slope.congr' (Eventually.of_forall h_eq)

private lemma tendsto_sq_nhdsWithin_ne_zero :
    Tendsto (fun h : ℝ ↦ h^2) (𝓝[≠] 0) (𝓝[≠] 0) := by
  refine tendsto_nhdsWithin_iff.mpr ⟨?_, ?_⟩
  · have h_cont : Continuous (fun h : ℝ ↦ h^2) := by continuity
    have h_tendsto_nhds : Tendsto (fun h : ℝ ↦ h^2) (𝓝 0) (𝓝 0) := by
      have := h_cont.tendsto 0
      simpa using this
    exact h_tendsto_nhds.mono_left nhdsWithin_le_nhds
  · filter_upwards [self_mem_nhdsWithin] with h hh
    exact pow_ne_zero 2 hh

/-- `(e^{c · h²} − 1) / h² → c` as `h → 0` (`h ≠ 0`).
    Composition of `tendsto_exp_sub_one_div` with `h ↦ h²`. -/
lemma tendsto_exp_sq_sub_one_div_sq (c : ℝ) :
    Tendsto (fun h : ℝ ↦ (Real.exp (c * h^2) - 1) / h^2) (𝓝[≠] 0) (𝓝 c) :=
  (tendsto_exp_sub_one_div c).comp tendsto_sq_nhdsWithin_ne_zero

/-- `(e^{c · h²} − 1) / h → 0` as `h → 0` (`h ≠ 0`).
    Equals `h · ((e^{c·h²} − 1)/h²) → 0 · c = 0`. -/
lemma tendsto_exp_sq_sub_one_div_h (c : ℝ) :
    Tendsto (fun h : ℝ ↦ (Real.exp (c * h^2) - 1) / h) (𝓝[≠] 0) (𝓝 0) := by
  have h_id : Tendsto (fun h : ℝ ↦ h) (𝓝[≠] 0) (𝓝 0) :=
    tendsto_id.mono_left nhdsWithin_le_nhds
  have h_sq := tendsto_exp_sq_sub_one_div_sq c
  have h_mul := h_id.mul h_sq
  -- h_mul : Tendsto (fun h ↦ h * ((exp(c·h²)-1)/h²)) (𝓝[≠] 0) (𝓝 (0 * c))
  have h_target_eq : (0 : ℝ) * c = 0 := zero_mul c
  rw [h_target_eq] at h_mul
  refine h_mul.congr' ?_
  filter_upwards [self_mem_nhdsWithin] with h hh
  have hh_ne : h ≠ 0 := hh
  have h_sq_ne : h^2 ≠ 0 := pow_ne_zero 2 hh_ne
  field_simp

/-- `(e^{σ h} − 1)/h − (e^{-σ h} − 1)/h = (e^{σ h} − e^{-σ h})/h`. -/
private lemma exp_diff_sub_eq (σ h : ℝ) :
    (Real.exp (σ * h) - 1) / h - (Real.exp (-(σ * h)) - 1) / h
      = (Real.exp (σ * h) - Real.exp (-(σ * h))) / h := by
  rw [← sub_div]
  congr 1
  ring

/-- `(e^{σ h} − e^{-σ h}) / h → 2σ` as `h → 0` (`h ≠ 0`). -/
lemma tendsto_sinh_div (σ : ℝ) :
    Tendsto (fun h : ℝ ↦ (Real.exp (σ * h) - Real.exp (-(σ * h))) / h)
      (𝓝[≠] 0) (𝓝 (2 * σ)) := by
  have h_pos : Tendsto (fun h : ℝ ↦ (Real.exp (σ * h) - 1) / h) (𝓝[≠] 0) (𝓝 σ) :=
    tendsto_exp_sub_one_div σ
  have h_neg : Tendsto (fun h : ℝ ↦ (Real.exp (-(σ * h)) - 1) / h) (𝓝[≠] 0) (𝓝 (-σ)) := by
    refine (tendsto_exp_sub_one_div (-σ)).congr' ?_
    filter_upwards with h
    rw [show -σ * h = -(σ * h) from by ring]
  have h_diff := h_pos.sub h_neg
  have h_target : (σ - (-σ) : ℝ) = 2 * σ := by ring
  rw [h_target] at h_diff
  refine h_diff.congr' ?_
  filter_upwards with h
  exact exp_diff_sub_eq σ h

/-! ### CRR probability limit: `p_n → 1/2` -/

/-- **CRR probability limit**: `p_n → 1/2` as `n → ∞`.

The intuition: as `Δt = T/n → 0`, both `e^{rΔt} - e^{-σ√Δt}` and `e^{σ√Δt} - e^{-σ√Δt}`
tend to 0, but they share the leading `σ√Δt` term, so the ratio tends to `1/2`.

Formally: substitute `h_n = √(T/n)`. Then
  `p_n = (e^{r·h_n²} - e^{-σ·h_n}) / (e^{σ·h_n} - e^{-σ·h_n})`.

Multiply numerator and denominator by `1/h_n`:
  `p_n = [(e^{r·h_n²} - 1)/h_n − (e^{-σ·h_n} − 1)/h_n] / [(e^{σ·h_n} − 1)/h_n − (e^{-σ·h_n} − 1)/h_n]`.

As `h_n → 0`:
  - `(e^{r h_n²} − 1)/h_n → 0`
  - `(e^{σ h_n} − 1)/h_n → σ`
  - `(e^{-σ h_n} − 1)/h_n → −σ`

Hence `p_n → (0 − (−σ)) / (σ − (−σ)) = σ / (2σ) = 1/2`. -/
theorem crrProb_tendsto_half {σ T r : ℝ} (hσ : 0 < σ) (hT : 0 < T) :
    Tendsto (fun n : ℕ ↦ crrProb r σ T n) atTop (𝓝 (1/2)) := by
  -- Step 1: define the per-h probability function and prove p(h) → 1/2 as h → 0 (h ≠ 0).
  -- p(h) = (e^{r·h²} - e^{-σh}) / (e^{σh} - e^{-σh})
  --      = [(e^{r·h²}-1)/h - (e^{-σh}-1)/h] / [(e^{σh}-1)/h - (e^{-σh}-1)/h]  (for h ≠ 0)
  -- Numerator → σ, denominator → 2σ, so p(h) → σ/(2σ) = 1/2.
  -- Denominator is nonzero for h ≠ 0 (since σ > 0).
  have h_denom_ne : ∀ h : ℝ, h ≠ 0 → Real.exp (σ * h) - Real.exp (-(σ * h)) ≠ 0 := by
    intro h hh h_eq
    rw [sub_eq_zero, Real.exp_eq_exp] at h_eq
    have h_zero : σ * h = 0 := by linarith
    rcases mul_eq_zero.mp h_zero with hσ0 | hh0
    · exact absurd hσ0 hσ.ne'
    · exact absurd hh0 hh
  -- Limits of numerator and denominator (working with the difference-quotient form):
  have h_neg_div : Tendsto (fun h : ℝ ↦ (Real.exp (-(σ * h)) - 1) / h) (𝓝[≠] 0) (𝓝 (-σ)) := by
    refine (tendsto_exp_sub_one_div (-σ)).congr' ?_
    filter_upwards with h
    rw [show -σ * h = -(σ * h) from by ring]
  have h_num_tendsto : Tendsto
      (fun h : ℝ ↦ (Real.exp (r * h^2) - 1) / h - (Real.exp (-(σ * h)) - 1) / h)
      (𝓝[≠] 0) (𝓝 σ) := by
    have h_sub := (tendsto_exp_sq_sub_one_div_h r).sub h_neg_div
    have h_target : ((0 : ℝ) - (-σ)) = σ := by ring
    rwa [h_target] at h_sub
  have h_denom_tendsto : Tendsto
      (fun h : ℝ ↦ (Real.exp (σ * h) - 1) / h - (Real.exp (-(σ * h)) - 1) / h)
      (𝓝[≠] 0) (𝓝 (2 * σ)) := by
    have h_sub := (tendsto_exp_sub_one_div σ).sub h_neg_div
    have h_target : ((σ - (-σ)) : ℝ) = 2 * σ := by ring
    rwa [h_target] at h_sub
  -- Quotient: p_h → σ / (2σ) = 1/2
  have h_2σ_ne : (2 * σ : ℝ) ≠ 0 := by positivity
  have h_p_tendsto :
      Tendsto (fun h : ℝ ↦
          ((Real.exp (r * h^2) - 1) / h - (Real.exp (-(σ * h)) - 1) / h)
            / ((Real.exp (σ * h) - 1) / h - (Real.exp (-(σ * h)) - 1) / h))
        (𝓝[≠] 0) (𝓝 (σ / (2 * σ))) :=
    h_num_tendsto.div h_denom_tendsto h_2σ_ne
  have h_half : σ / (2 * σ) = 1/2 := by field_simp
  rw [h_half] at h_p_tendsto
  -- Step 2: equate p_h to the closed form p(h) for h ≠ 0
  have h_p_form : ∀ h : ℝ, h ≠ 0 →
      ((Real.exp (r * h^2) - 1) / h - (Real.exp (-(σ * h)) - 1) / h)
        / ((Real.exp (σ * h) - 1) / h - (Real.exp (-(σ * h)) - 1) / h)
      = (Real.exp (r * h^2) - Real.exp (-(σ * h))) /
        (Real.exp (σ * h) - Real.exp (-(σ * h))) := by
    intro h hh
    have hd_ne := h_denom_ne h hh
    have h_num_eq : (Real.exp (r * h^2) - 1) / h - (Real.exp (-(σ * h)) - 1) / h
                  = (Real.exp (r * h^2) - Real.exp (-(σ * h))) / h := by
      rw [← sub_div]
      congr 1; ring
    rw [h_num_eq, exp_diff_sub_eq σ h]
    field_simp
  -- Step 3: compose with h_n = √(T/n) → 0+
  have h_h_n_tendsto : Tendsto (fun n : ℕ ↦ Real.sqrt (crrStep T n)) atTop (𝓝[≠] 0) := by
    refine tendsto_nhdsWithin_iff.mpr ⟨tendsto_sqrt_crrStep_zero T, ?_⟩
    filter_upwards [Filter.eventually_ge_atTop 1] with n hn
    have h_n_pos : (0 : ℝ) < n := by exact_mod_cast hn
    have h_step_pos : 0 < crrStep T n := div_pos hT h_n_pos
    exact (Real.sqrt_pos.mpr h_step_pos).ne'
  have h_comp := h_p_tendsto.comp h_h_n_tendsto
  -- Step 4: Show crrProb r σ T n = (the composed expression at √(T/n)) for n ≥ 1
  refine h_comp.congr' ?_
  filter_upwards [Filter.eventually_ge_atTop 1] with n hn
  have h_n_pos : (0 : ℝ) < n := by exact_mod_cast hn
  have h_step_pos : 0 < crrStep T n := div_pos hT h_n_pos
  have h_sqrt_pos : 0 < Real.sqrt (crrStep T n) := Real.sqrt_pos.mpr h_step_pos
  have h_sqrt_ne : Real.sqrt (crrStep T n) ≠ 0 := h_sqrt_pos.ne'
  have h_sqrt_sq : Real.sqrt (crrStep T n) ^ 2 = crrStep T n :=
    Real.sq_sqrt h_step_pos.le
  -- Show: crrProb r σ T n = [the composition output]
  -- Compose pulls back through `h_p_form` once we identify rT/n with r·(√(T/n))² etc.
  show ((fun h : ℝ ↦
          ((Real.exp (r * h^2) - 1) / h - (Real.exp (-(σ * h)) - 1) / h)
            / ((Real.exp (σ * h) - 1) / h - (Real.exp (-(σ * h)) - 1) / h))
          ∘ fun n : ℕ ↦ Real.sqrt (crrStep T n)) n = crrProb r σ T n
  simp only [Function.comp_apply]
  rw [h_p_form (Real.sqrt (crrStep T n)) h_sqrt_ne]
  unfold crrProb crrUp crrDown crrPerStepRate
  congr 2
  rw [h_sqrt_sq]

/-! ### Variance limit -/

/-- **Variance limit**: `n · σ² · (T/n) · 4 p_n (1 − p_n) → σ² T` as `n → ∞`.

Equivalently, the per-step variance of the log-return matches the BS variance to
leading order. Follows directly from `crrProb_tendsto_half` and limit arithmetic.

The formula simplifies: `n · σ² · (T/n) · 4 p_n (1 − p_n) = 4 σ² T · p_n (1 − p_n)`. -/
theorem crr_variance_limit {σ T r : ℝ} (hσ : 0 < σ) (hT : 0 < T) :
    Tendsto (fun n : ℕ ↦ 4 * σ^2 * T * (crrProb r σ T n) * (1 - crrProb r σ T n))
      atTop (𝓝 (σ^2 * T)) := by
  have h_p := crrProb_tendsto_half (r := r) hσ hT
  have h_1_minus_p : Tendsto (fun n : ℕ ↦ 1 - crrProb r σ T n) atTop (𝓝 (1/2)) := by
    have h_const : Tendsto (fun _ : ℕ ↦ (1 : ℝ)) atTop (𝓝 1) := tendsto_const_nhds
    have := h_const.sub h_p
    have h_target : (1 - 1/2 : ℝ) = 1/2 := by norm_num
    rwa [h_target] at this
  have h_prod := h_p.mul h_1_minus_p
  -- h_prod : Tendsto (p_n * (1 - p_n)) atTop (𝓝 ((1/2) * (1/2))) = (𝓝 (1/4))
  have h_const_mul : Tendsto
      (fun n : ℕ ↦ 4 * σ^2 * T * (crrProb r σ T n * (1 - crrProb r σ T n)))
      atTop (𝓝 (4 * σ^2 * T * (1/2 * (1/2)))) := h_prod.const_mul _
  have h_target_eq : (4 * σ^2 * T * (1/2 * (1/2)) : ℝ) = σ^2 * T := by ring
  rw [h_target_eq] at h_const_mul
  refine h_const_mul.congr' ?_
  filter_upwards with n
  ring

/-! ### Full pricing convergence (done elsewhere)

The `binomialPrice → bs_call_price` convergence is **proved** in
`Binomial/CRRCharFun.lean` (`binomialPrice_call_tendsto_bs`) via a
characteristic-function/Lévy route plus put-call parity — no triangular-array
CLT needed. The drift limit `n · (2 p_n − 1) · σ √Δt → (r − σ²/2) T` is proved
in `Binomial/DriftLimit.lean` (`crr_drift_limit_n`); the variance limit
`crr_variance_limit` is above. Together they pin the matched BS log-return
moments feeding that convergence. -/

end MathFin
