From b60d343ad197e811d8d6e84e9974ed91526fca6c Mon Sep 17 00:00:00 2001 From: Brad Smith Date: Thu, 11 Jun 2026 15:00:19 -0500 Subject: [PATCH] docs(features): clarify that gamma * S is intentional normalization Issue #33 flagged that calculate_gamma multiplies by S while calculate_theta and calculate_vega divide by S, and the gamma docstring's "normalized by underlying price" wording reads as if it should divide. On investigation the math is correct and the asymmetry is required: - raw gamma scales as 1/S, so gamma * S is price-scale-invariant - raw theta and vega scale as S, so theta/S and vega/S are price-scale-invariant Verified empirically: gamma*S, theta/S, vega/S/100 are identical for S=100 and S=1000 at equal moneyness/vol/time. Changing gamma to gamma / S would REINTRODUCE price dependence (a real bug), so this is a documentation-only change: reword the docstring and comment to explain why the *S is correct and warn against "fixing" it. No behavior change. Closes #33 Co-Authored-By: Claude Opus 4.8 --- src/features/greeks.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/features/greeks.py b/src/features/greeks.py index e7cf6c5..6957694 100644 --- a/src/features/greeks.py +++ b/src/features/greeks.py @@ -68,7 +68,14 @@ def calculate_gamma(self, S: float, K: float, T: float, r: float, option_type: 'CALL' or 'PUT' Returns: - Gamma value normalized by underlying price + Price-scale-invariant gamma (raw gamma * S). + + Raw Black-Scholes gamma scales as 1/S, so multiplying by S removes + the price-level dependence and yields a feature comparable across + underlyings of any price. This is intentionally the opposite + operation from theta and vega, whose raw values scale as S and are + therefore divided by S to achieve the same normalization. Do not + "fix" this to gamma / S: that would reintroduce price dependence. """ if T <= 0 or sigma <= 0 or S <= 0: return 0.0 @@ -76,7 +83,7 @@ def calculate_gamma(self, S: float, K: float, T: float, r: float, d1 = self._calculate_d1(S, K, T, r, sigma) gamma = norm.pdf(d1) / (S * sigma * math.sqrt(T)) - # Normalize by underlying price to get relative gamma + # Multiply by S so the result is price-scale-invariant (raw gamma ~ 1/S). return float(gamma * S) def calculate_theta(self, S: float, K: float, T: float, r: float,