144 lines
6.4 KiB
MQL5
144 lines
6.4 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| BlackScholes.mqh |
|
|
//| MMQ — Muhammad Minhas Qamar |
|
|
//| www.mql5.com/en/articles/23385 |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "MMQ — Muhammad Minhas Qamar"
|
|
#property link "https://www.mql5.com/en/articles/23385"
|
|
#property version "1.00"
|
|
|
|
#ifndef IVSURFACE_BLACKSCHOLES_MQH
|
|
#define IVSURFACE_BLACKSCHOLES_MQH
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Option right, matching MQL5's ENUM_SYMBOL_OPTION_RIGHT so both |
|
|
//| data providers can pass the value straight through. |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_OPT_RIGHT
|
|
{
|
|
OPT_CALL = 0, // right to buy
|
|
OPT_PUT = 1 // right to sell
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Standard normal CDF, via the Abramowitz & Stegun 7.1.26 |
|
|
//| approximation of erf. Accurate to about 1e-7, far tighter than |
|
|
//| option-quote noise. |
|
|
//+------------------------------------------------------------------+
|
|
double NormCDF(const double x)
|
|
{
|
|
double t = 1.0 / (1.0 + 0.2316419 * MathAbs(x));
|
|
double d = 0.3989422804014327 * MathExp(-x * x / 2.0); // 1/sqrt(2pi) * e^(-x^2/2)
|
|
double p = d * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
|
|
return((x > 0.0) ? 1.0 - p : p);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Standard normal PDF. |
|
|
//+------------------------------------------------------------------+
|
|
double NormPDF(const double x)
|
|
{
|
|
return(0.3989422804014327 * MathExp(-x * x / 2.0));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Black-Scholes price of a European option. |
|
|
//| S spot price of the underlying |
|
|
//| K strike price |
|
|
//| r risk-free rate (annual, continuous) |
|
|
//| q dividend / carry yield (annual, continuous) |
|
|
//| sigma volatility (annual) |
|
|
//| T time to expiry in years |
|
|
//+------------------------------------------------------------------+
|
|
double BSPrice(const ENUM_OPT_RIGHT right, const double S, const double K,
|
|
const double r, const double q, const double sigma, const double T)
|
|
{
|
|
if(T <= 0.0 || sigma <= 0.0)
|
|
{
|
|
//--- at/after expiry, value is pure intrinsic
|
|
double intrinsic = (right == OPT_CALL) ? MathMax(S - K, 0.0) : MathMax(K - S, 0.0);
|
|
return(intrinsic);
|
|
}
|
|
double sqrtT = MathSqrt(T);
|
|
double d1 = (MathLog(S / K) + (r - q + 0.5 * sigma * sigma) * T) / (sigma * sqrtT);
|
|
double d2 = d1 - sigma * sqrtT;
|
|
double disc_r = MathExp(-r * T);
|
|
double disc_q = MathExp(-q * T);
|
|
if(right == OPT_CALL)
|
|
return(S * disc_q * NormCDF(d1) - K * disc_r * NormCDF(d2));
|
|
else
|
|
return(K * disc_r * NormCDF(-d2) - S * disc_q * NormCDF(-d1));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Vega (price sensitivity to a 1.00 change in sigma). Used both as |
|
|
//| the Newton step denominator and to reject near-worthless options |
|
|
//| whose IV is numerically meaningless. |
|
|
//+------------------------------------------------------------------+
|
|
double BSVega(const double S, const double K, const double r, const double q,
|
|
const double sigma, const double T)
|
|
{
|
|
if(T <= 0.0 || sigma <= 0.0)
|
|
return(0.0);
|
|
double sqrtT = MathSqrt(T);
|
|
double d1 = (MathLog(S / K) + (r - q + 0.5 * sigma * sigma) * T) / (sigma * sqrtT);
|
|
return(S * MathExp(-q * T) * NormPDF(d1) * sqrtT);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Implied volatility by inverting the Black-Scholes price. Uses |
|
|
//| Newton-Raphson (fast) and falls back to bisection whenever a |
|
|
//| Newton step leaves the bracket, which keeps the solver robust for|
|
|
//| deep in/out-of-the-money quotes where vega is tiny. |
|
|
//| |
|
|
//| Returns the implied volatility, or a negative value if no valid |
|
|
//| IV exists for the given inputs (e.g. price below intrinsic). |
|
|
//+------------------------------------------------------------------+
|
|
double ImpliedVol(const ENUM_OPT_RIGHT right, const double price, const double S,
|
|
const double K, const double r, const double q, const double T)
|
|
{
|
|
if(price <= 0.0 || S <= 0.0 || K <= 0.0 || T <= 0.0)
|
|
return(-1.0);
|
|
|
|
//--- price must sit above intrinsic value or no positive sigma solves it
|
|
double intrinsic = (right == OPT_CALL) ? MathMax(S * MathExp(-q * T) - K * MathExp(-r * T), 0.0)
|
|
: MathMax(K * MathExp(-r * T) - S * MathExp(-q * T), 0.0);
|
|
if(price < intrinsic - 1e-8)
|
|
return(-1.0);
|
|
|
|
double loVol = 1e-4, hiVol = 5.0; // 0.01% .. 500% brackets the search
|
|
double sigma = 0.2; // a sensible starting guess (20%)
|
|
|
|
for(int i = 0; i < 100; i++)
|
|
{
|
|
double model = BSPrice(right, S, K, r, q, sigma, T);
|
|
double diff = model - price;
|
|
if(MathAbs(diff) < 1e-6)
|
|
return(sigma);
|
|
|
|
//--- shrink the bracket using the sign of the error (price rises monotonically with sigma)
|
|
if(diff > 0.0)
|
|
hiVol = sigma;
|
|
else
|
|
loVol = sigma;
|
|
|
|
double vega = BSVega(S, K, r, q, sigma, T);
|
|
double next;
|
|
if(vega > 1e-8)
|
|
next = sigma - diff / vega; // Newton step
|
|
else
|
|
next = 0.5 * (loVol + hiVol); // vega too small: bisect
|
|
|
|
//--- if Newton jumped outside the bracket, fall back to bisection
|
|
if(next <= loVol || next >= hiVol)
|
|
next = 0.5 * (loVol + hiVol);
|
|
|
|
if(MathAbs(next - sigma) < 1e-8)
|
|
return(next);
|
|
sigma = next;
|
|
}
|
|
return(sigma);
|
|
}
|
|
|
|
#endif // IVSURFACE_BLACKSCHOLES_MQH
|
|
//+------------------------------------------------------------------+
|