Code
package main
import (
"fmt"
"math"
)
//
// Quick demo of the classic black-scholes-merton model for
// estimating npv of european options. Model assumes that
// the underlying stock does not pay any dividends for the
// lifetime of the option.
//
// For a call, c
// c = S*N(d1) - K*e^(-r(T-t))*N(d2)
//
// For a put, p
// p = K*e^(-r(T-t))*N(-d2) - S*N(-d1)
//
// Where
// d1 = (ln(S/K) + (r + σ²/2)(T-t))/ σ√(T-t)
// And
// d2 = d1 - σ√(T-t)
//
//
const TotalTradingDays = 252.0
type EuropeanOptionParameters struct {
UnderlierPrice float64
StrikePrice float64
Volatility float64
RiskFreeRate float64
DaysTilExpiration float64
}
func main() {
// A european option with parameters-
params := EuropeanOptionParameters{
UnderlierPrice: 42.0,
StrikePrice: 40.0,
RiskFreeRate: 0.1,
Volatility: 0.2,
DaysTilExpiration: 126.0,
}
cv, pv := calculateNPV(params)
// has call value of
fmt.Println("Call Option NPV = ", cv)
// and put value of
fmt.Println("Put Option NPV = ", pv)
}
func calculateNPV(params EuropeanOptionParameters) (callValue float64, putValue float64) {
d1 := (math.Log(params.UnderlierPrice/params.StrikePrice) +
(params.RiskFreeRate+(math.Pow(params.Volatility, 2.0)/2.0))*(params.DaysTilExpiration/TotalTradingDays)) /
(params.Volatility * math.Sqrt(params.DaysTilExpiration/TotalTradingDays))
d2 := (math.Log(params.UnderlierPrice/params.StrikePrice) +
(params.RiskFreeRate-(math.Pow(params.Volatility, 2.0)/2.0))*(params.DaysTilExpiration/TotalTradingDays)) /
(params.Volatility * math.Sqrt(params.DaysTilExpiration/TotalTradingDays))
coeff := params.StrikePrice * math.Exp(-params.RiskFreeRate*(params.DaysTilExpiration/TotalTradingDays))
cv := params.UnderlierPrice*cdf(d1) - coeff*cdf(d2)
pv := coeff*cdf(-d2) - params.UnderlierPrice*cdf(-d1)
return cv, pv
}
// cdf computes the value of the cumulative density function at x.
func cdf(x float64) float64 {
// normal distribution with Mu = 0 and Sigma = 1.
mu := 0.0
sigma := 1.0
return 0.5 * (1 + math.Erf((x-mu)/(sigma*math.Sqrt2)))
}