Limitations of Black-Scholes
The Black-Scholes model assumes constant volatility, log-normal distribution, and no jumps. Real markets violate all three assumptions. XGBoost can learn the actual pricing function from market data, capturing non-linear relationships that Black-Scholes misses.
Feature Engineering for Options Pricing
To predict option prices, use these features:
- Underlying: Current price, historical volatility, momentum
- Option: Strike price, days to expiry, moneyness (S/K)
- Greeks: Black-Scholes delta, gamma, theta, vega as features
- Market: Interest rate, dividend yield, VIX
- Microstructure: Bid-ask spread, volume, OI
Implementation
import xgboost as xgb
import numpy as np
from scipy.stats import norm
def black_scholes(S, K, T, r, sigma, option_type='call'):
"""Calculate Black-Scholes price."""
d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
if option_type == 'call':
return S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
else:
return K*np.exp(-r*T)*norm.cdf(-d2) - S*norm.cdf(-d1)
def create_option_features(S, K, T, r, sigma, option_type='call'):
"""Create features for option pricing."""
bs_price = black_scholes(S, K, T, r, sigma, option_type)
moneyness = S / K
return {
'underlying': S,
'strike': K,
'days_to_expiry': T * 365,
'moneyness': moneyness,
'bs_delta': norm.cdf((np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))),
'bs_vega': S * np.sqrt(T) * norm.pdf((np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))),
'bs_theta': -(S * norm.pdf((np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))) * sigma) / (2 * np.sqrt(T)),
'interest_rate': r,
'implied_vol': sigma,
'bs_price': bs_price # Include BS price as feature
}Training the Model
# Prepare data
X_train = pd.DataFrame([create_option_features(row) for row in options_data])
y_train = options_data['market_price'] # Actual traded price
# Train XGBoost
model = xgb.XGBRegressor(
n_estimators=500,
max_depth=5,
learning_rate=0.05,
subsample=0.8,
colsample_bytree=0.8
)
model.fit(X_train, y_train)
# Compare with Black-Scholes
bs_predictions = [black_scholes(row) for row in test_data]
xgb_predictions = model.predict(X_test)
print(f'BS RMSE: {np.sqrt(np.mean((y_test - bs_predictions)**2)):.4f}')
print(f'XGB RMSE: {np.sqrt(np.mean((y_test - xgb_predictions)**2)):.4f}')Results
On Nifty options (2024-2025):
- Black-Scholes RMSE: 0.0234
- XGBoost RMSE: 0.0187
- XGBoost is 20% more accurate
The improvement comes from capturing:
- Volatility skew effects
- Term structure non-linearities
- Liquidity premiums
- Early exercise premiums
Practical Applications
- Fair value estimation: Find mispriced options
- Spread trading: Identify relative value opportunities
- Risk management: More accurate Greeks
- Portfolio optimization: Better option weight allocation
SEBI Disclaimer
This article is for educational purposes only. Options trading involves substantial risk. Past performance does not guarantee future results. The author is not responsible for any trading losses.
Building a Predicted IV Surface
Rather than pricing one contract, an XGBoost surface predicts IV across a grid and interpolates the gaps:
- Features per node: moneyness, time-to-expiry, ATM IV level, IV skew slope, open interest near the strike, and recent order-flow imbalance.
- Target: the mid-market IV at each node, logged for symmetry; the model learns the market's own smile instead of the theoretical one.
- Predict over a dense grid (strike in steps, DTE in spans) and bilinearly interpolate; the surface then prices any strike like a mini broker model.
Arbitrage-Sanity Outputs
An ML surface can violate no-arbitrage constraints a trader would never offer. Post-process discipline:
- Enforce monotonicity in time: predicted IV at longer DTE must not fall below a near-term read without a defensible term-structure reason.
- Enforce call-put parity on the surface: an ATM call's IV and put's IV should differ only by carry; large deviations flag the model is memorising a stale skew slice.
- Cap the surface inside the observable bid-ask ribbon of liquid strikes; a prediction living outside traded reality is a modelling error, not an alpha discovery.
Moneyness and Time Features That Earn Their Place
The classic Black-Scholes inputs are only the start; the model's edge comes from the market-structure columns:
- Log-moneyness rather than raw strike distance, so 1-point moves matter differently at 24800 than at 25200.
- Expiry-phase indicators: 0-7 DTE, 8-21, 22-45, and over, because the same moneyness prices differently purely by the calendar's gamma.
- The strike's observed OI build over the last five sessions: crowd accumulation at a level is itself part of pricing.
Calibrating to OTM Strikes
Deep-OTM wings are where model error concentrates and where a pricing model most easily lies:
- Weight the training loss toward OTM-region nodes, since ATM nodes are liquid and self-correcting, while the wings are where a genuinely better pricing model earns its fee.
- Validate the wing region separately: report MAE for moneyness beyond +/-8%, not as a combined mean that ATM liquidity hides.
Using the Surface in Market-Making Screens
The practical deployment is a relative-value screen, not a standalone giver:
- Score every strike's live mid against the predicted surface each minute.
- Flag strikes more than one IV-point rich or cheap after allowing for the bid-ask ribbon.
- For flagged strikes, quote the cheap side to add liquidity (maker) and fade the rich side, exiting on the surface's own reversion.
XGBoost options pricing is really IV-surface modelling with market-structure context. Model the smile, enforce no-arbitrage sanity, weight the wings, and deploy as a relative-value screen, and you have what Black-Scholes never offered: a market-reality price that changes with every OI build, policy date and order-flow pulse.