Arte SheetsLearn. Try. Understand.

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 RoleComputed Value
CIP Forward Rate F_0S_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 / PutDiscounted Foreign Spot vs Domestic StrikeCall = 0.0421 | Put = 0.0313
Put-Call Parity Spread C - PS_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.

Further reading