Gamma-Exposure-MQL5/Include/GEX/BlackScholes.mqh
2026-07-07 19:24:01 +00:00

162 lines
7.3 KiB
MQL5

//+------------------------------------------------------------------+
//| BlackScholes.mqh |
//| MMQ — Muhammad Minhas Qamar |
//| www.mql5.com/en/articles/23410 |
//+------------------------------------------------------------------+
#property copyright "MMQ — Muhammad Minhas Qamar"
#property link "https://www.mql5.com/en/articles/23410"
#property version "1.00"
#ifndef GEX_BLACKSCHOLES_MQH
#define GEX_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);
}
//+------------------------------------------------------------------+
//| Gamma: the rate of change of delta per 1.0 move in the spot, and |
//| the Greek this whole tool is built on. Both a call and a put on |
//| the same strike/expiry share the same gamma, so the sign that |
//| turns it into dealer exposure is applied later, not here. |
//| Same expiry/volatility guard as the pricer: gamma is undefined |
//| at or past expiry, so we return zero there. |
//+------------------------------------------------------------------+
double BSGamma(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 || S <= 0.0)
return(0.0);
double sqrtT = MathSqrt(T);
double d1 = (MathLog(S / K) + (r - q + 0.5 * sigma * sigma) * T) / (sigma * sqrtT);
return(MathExp(-q * T) * NormPDF(d1) / (S * sigma * 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 // GEX_BLACKSCHOLES_MQH
//+------------------------------------------------------------------+