Options Selling with ML

Options selling (credit spreads, iron condors) benefits from time decay. LightGBM can predict which credit spreads are most likely to expire worthless, optimizing strike selection.

Feature Engineering for Credit Spreads

def create_credit_spread_features(options_chain, underlying_data):
    """Create features for credit spread optimization."""
    features = pd.DataFrame(index=options_chain.index)
    
    # Moneyness
    features['call_moneyness'] = underlying_data['Close'] / options_chain['call_strike']
    features['put_moneyness'] = underlying_data['Close'] / options_chain['put_strike']
    
    # Implied Volatility
    features['call_iv'] = options_chain['call_iv']
    features['put_iv'] = options_chain['put_iv']
    features['iv_skew'] = features['put_iv'] - features['call_iv']
    
    # Time to expiry
    features['dte'] = options_chain['days_to_expiry']
    features['theta_decay'] = options_chain['theta'] / options_chain['call_price']
    
    # Underlying metrics
    features['underlying_volatility'] = underlying_data['Close'].pct_change().rolling(21).std()
    features['vix_rank'] = get_vix_rank()
    
    # Spread width
    features['spread_width'] = options_chain['call_strike'] - options_chain['put_strike']
    features['credit_received'] = options_chain['call_credit'] + options_chain['put_credit']
    
    return features

Predicting Credit Spread Success

import lightgbm as lgb

# Target: 1 if spread expires worthless (profit), 0 if tested
y = (underlying_data['Close'] > options_chain['put_strike']) & \
    (underlying_data['Close'] < options_chain['call_strike'])

# Train model
params = {
    'objective': 'binary',
    'metric': 'auc',
    'num_leaves': 15,
    'learning_rate': 0.05
}

model = lgb.train(params, lgb.Dataset(X_train, label=y_train), num_boost_round=500)

# Predict probability of success
success_prob = model.predict(X_test)

Strike Selection

def select_optimal_strikes(model, options_chain, underlying_data, target_prob=0.70):
    """Select strikes with highest expected value."""
    features = create_credit_spread_features(options_chain, underlying_data)
    
    # Predict success probability for each spread
    success_probs = model.predict(features)
    
    # Calculate expected value
    expected_values = success_probs * options_chain['credit'] - \
                     (1 - success_probs) * options_chain['max_loss']
    
    # Select spread with highest expected value, meeting minimum probability
    valid = success_probs >= target_prob
    if valid.any():
        best_idx = expected_values[valid].idxmax()
        return options_chain.loc[best_idx]
    return None

Walk-Forward Results

Credit spread optimization on Nifty (2022-2025):

  • Success rate: 72% (vs 65% baseline)
  • Average credit: 2.3% higher than baseline
  • Risk-adjusted return: +0.8% annually

Risk Management

  • Maximum loss per trade: 3% of capital
  • Position sizing: Kelly criterion based on success probability
  • Correlation limits: Max 3 concurrent spreads
  • Early management: Close at 50% profit or 200% loss

Expected Value Versus Probability Threshold

Rain-ranking candidates by success probability alone misprices the edge; the correct objective is expected value per rupee of risk:

  • A spread with 85% probability and a credit of 60 on a 400-point width has EV of (0.85*60) - (0.15*340) = -4; it loses money on average.
  • A spread that is 70% probable with credit 150 on the same width has EV of (0.70*150) - (0.30*250) = +30; it wins slowly, consistently.

Use the model's probability inside the EV formula, never in isolation. The visible "72% success" of a high-probability crop is exactly where retail premium sellers lose money, by collecting tiny credits that the rare big test undoes.

Leverage and Margin Budgets

Options selling multiplies notional through margin; the model has no opinion about that, but your capital management must:

  • Cap margin deployment below 60% of account equity, reserving 1.3x the worst-case margin call.
  • Size each spread on its defined loss, not its credit; the margin calculator rewards wide, deep-OUT spreads that the EV model rightly penalises.
  • Monitor margin utilisation daily, because a regime where all your spreads test at once is the leverage event that ends books.

Sharpe Realities of a Spread Book

The honest expectation for ML-assisted premium selling is not a hockey stick equity curve. On Nifty and Bank Nifty data, walk-forward models improve the ratio of winning months and reduce the severity of the worst quarter, but the rare fat loss still dominates:

  • Monthly win rate typically climbs 5-8 percentage points with ML strike selection.
  • Maximum drawdown typically shrinks 20-40% versus a fixed-rule baseline, because the model avoids selling into elevated IV.
  • The absolute Sharpe rarely clears 1.0 for a pure short-vol book; the model's value is drawdown control and correlation awareness, not magic alpha.

Spread Width as a First-Class Feature

Width, moneyness and time interact more than traders give them credit for; push all three into the feature matrix:

  • Width-to-credit ratio predicts whether a spread is a near-sure tiny win or a coin-flip wide-wing, and the model learns the threshold.
  • Moneyness distance at entry predicts how far the underlying can move before the test; combine with a 10-day expected-move feature.
  • Days-to-expiry squared-weighting helps the model separate 10-day dominance from 40-day vega plays.

Regime Rotation: When to Stop Selling

The most profitable output a LightGBM credit-spread system produces is sometimes the word "stop":

  • When the model's average predicted success crosses below 60% for a full week, suspend new entries.
  • Rotate into calendars or long-lived condors during high-IV-rank regimes where credits are rich but tails are closer.
  • When forecast success and realised success diverge for two straight weeks, retrain before trading; the model is working on stale friction.

A credit-spread model earns its cost by telling you which week to not sell premium at all. The spread book that treats "no trade" as a first-class signal compounds faster than the one that fills every Thursday.