Financial Modeling
Garman-Kohlhagen FX Currency Option Pricing & Covered Interest Parity
Price European FX calls and puts under Garman and Kohlhagen (1983) by discounting foreign currency spot at r_f and domestic strike at r_d, verified via FX put-call parity.
Why naive Black-Scholes misprices currency options by ignoring the foreign deposit yield r_f
Holding one unit of foreign currency earns the foreign risk-free deposit rate r_f continuously, behaving identically to a continuous dividend yield in equity options. Mark Garman and Steven Kohlhagen (1983) incorporated both domestic rate r_d and foreign rate r_f into spot FX option pricing (domestic units per 1 foreign unit): covered interest parity sets forward rate F_0 = S_0 * EXP((r_d - r_f) * T), while call value is C = S_0 * EXP(-r_f * T) * N(d_1) - K * EXP(-r_d * T) * N(d_2).
Always cross-check FX option sheets with exact Garman-Kohlhagen Put-Call Parity: C - P = S_0 * EXP(-r_f * T) - K * EXP(-r_d * T). At S_0 = K = 1.1000 with r_d = 5%, r_f = 3%, sigma = 12%, and T = 0.5 years, the positive interest differential (r_d > r_f) lifts the forward to 1.1111 and makes the ATM spot call (0.0421) worth 0.0108 more than the ATM spot put (0.0313).
| FX Option Quantity (EUR/USD 1.10, T=0.5y) | Formula Role | Computed Value |
|---|---|---|
| CIP Forward Rate F_0 | S_0 * EXP((r_d - r_f) * T) | 1.1111 |
| Garman-Kohlhagen d_1 / d_2 | (LN(S/K) + (r_d - r_f + 0.5*sig^2)*T) / (sig*SQRT(T)) | 0.1603 / 0.0754 |
| European FX Call / Put | Discounted Foreign Spot vs Domestic Strike | Call = 0.0421 | Put = 0.0313 |
| Put-Call Parity Spread C - P | S_0*EXP(-r_f*T) - K*EXP(-r_d*T) | +0.0108 |
Covered Interest Parity Forward & Garman-Kohlhagen Call Formula
Evaluate pv_for = S_0 * EXP(-r_f * T) and pv_dom = K * EXP(-r_d * T) inside LET so both d_1 = (LN(pv_for / pv_dom) + 0.5 * vol_t^2) / vol_t and Call pricing stay algebraically locked to FX forward parity.
=LET(pv_for, B2 * EXP(-B5 * B7), pv_dom, B3 * EXP(-B4 * B7), vol_t, B6 * SQRT(B7), IF(vol_t <= 0, ROUND(MAX(0, pv_for - pv_dom), 4), LET(d_one, (LN(pv_for / pv_dom) + 0.5 * (vol_t ^ 2)) / vol_t, d_two, d_one - vol_t, az_one, ABS(d_one), kt_one, 1 / (1 + 0.2316419 * az_one), tp_one, (EXP(-0.5 * az_one * az_one) / SQRT(2 * PI())) * kt_one * (0.31938153 + kt_one * (-0.356563782 + kt_one * (1.781477937 + kt_one * (-1.821255978 + 1.330274429 * kt_one)))), nd_one, IF(d_one >= 0, 1 - tp_one, tp_one), az_two, ABS(d_two), kt_two, 1 / (1 + 0.2316419 * az_two), tp_two, (EXP(-0.5 * az_two * az_two) / SQRT(2 * PI())) * kt_two * (0.31938153 + kt_two * (-0.356563782 + kt_two * (1.781477937 + kt_two * (-1.821255978 + 1.330274429 * kt_two)))), nd_two, IF(d_two >= 0, 1 - tp_two, tp_two), ROUND(pv_for * nd_one - pv_dom * nd_two, 4))))Garman-Kohlhagen Put Value via Exact FX Put-Call Parity
Recover European FX Put P = C - S_0 * EXP(-r_f * T) + K * EXP(-r_d * T) directly from Call E4 and verify symmetric pricing when r_d = r_f = 0.04.
=ROUND(E4 - B2 * EXP(-B5 * B7) + B3 * EXP(-B4 * B7), 4)Try the example
Use the original values or change them to test your understanding.