← Back to examples

Dealer Exposures

Calculate gamma, vanna, and charm exposures for market analysis.

Understanding Dealer Exposures

Dealers who sell options to customers accumulate exposure that they must hedge. This hedging activity can amplify or dampen market moves. The calculateGammaVannaCharmExposures function computes these exposures across an entire option chain.

Key Concepts

  • Gamma Exposure (GEX): Shows how much dealers need to buy/sell the underlying as price moves. Negative GEX means dealers buy dips and sell rallies (stabilizing). Positive GEX means dealers sell dips and buy rallies (destabilizing).

  • Vanna Exposure: Sensitivity to changes in implied volatility. Shows how dealer hedging changes when IV moves.

  • Charm Exposure: How delta (and therefore hedging needs) changes as time passes. Important for understanding end-of-day and expiration flows.

Complete Exposure Calculation

Calculate all exposures across an option chain:

import {
  calculateGammaVannaCharmExposures,
  getIVSurfaces,
  OptionChain,
  NormalizedOption
} from "@fullstackcraftllc/floe";

// Build your option chain with market data
const options: NormalizedOption[] = [
  {
    occSymbol: 'SPY251220C00440000',
    underlying: 'SPY',
    strike: 440,
    expiration: '2025-12-20',
    expirationTimestamp: 1766188800000,
    optionType: 'call',
    bid: 15.20,
    ask: 15.40,
    bidSize: 100,
    askSize: 150,
    mark: 15.30,
    last: 15.25,
    volume: 5000,
    openInterest: 10000,
    impliedVolatility: 0.18,
    timestamp: Date.now()
  },
  // ... more options with openInterest data
];

const chain: OptionChain = {
  symbol: 'SPY',
  spot: 450.50,
  riskFreeRate: 0.05,
  dividendYield: 0.02,
  options
};

// First, build IV surfaces
const ivSurfaces = getIVSurfaces('blackscholes', 'totalvariance', chain);

// Then calculate exposures
const exposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);

// Analyze results by expiration
for (const expiry of exposures) {
  console.log(`\nExpiration: ${new Date(expiry.expiration).toDateString()}`);
  console.log(`  Spot: $${expiry.spotPrice}`);
  console.log(`  Total Gamma: ${expiry.totalGammaExposure.toLocaleString()}`);
  console.log(`  Total Vanna: ${expiry.totalVannaExposure.toLocaleString()}`);
  console.log(`  Total Charm: ${expiry.totalCharmExposure.toLocaleString()}`);
  console.log(`  Net Exposure: ${expiry.totalNetExposure.toLocaleString()}`);
  console.log(`  Max Gamma Strike: $${expiry.strikeOfMaxGamma}`);
  console.log(`  Min Gamma Strike: $${expiry.strikeOfMinGamma}`);
}

Strike-Level Analysis

Drill down into individual strike exposures:

// Using exposures from above
for (const expiry of exposures) {
  console.log(`\n=== ${new Date(expiry.expiration).toDateString()} ===`);
  console.log('Strike    | Gamma Exp     | Vanna Exp     | Charm Exp     | Net Exp');
  console.log('-'.repeat(75));
  
  // Sort by strike price
  const sortedStrikes = [...expiry.strikeExposures].sort(
    (a, b) => a.strikePrice - b.strikePrice
  );
  
  for (const strike of sortedStrikes) {
    const gamma = strike.gammaExposure.toLocaleString().padStart(12);
    const vanna = strike.vannaExposure.toLocaleString().padStart(12);
    const charm = strike.charmExposure.toLocaleString().padStart(12);
    const net = strike.netExposure.toLocaleString().padStart(12);
    
    console.log(`$${strike.strikePrice.toString().padStart(6)} | ${gamma} | ${vanna} | ${charm} | ${net}`);
  }
}

Hedging Flow Estimation

Calculate how many shares dealers need to trade:

import { calculateSharesNeededToCover } from "@fullstackcraftllc/floe";

// Sum up net exposure across all expirations
const totalNetExposure = exposures.reduce(
  (sum, exp) => sum + exp.totalNetExposure,
  0
);

// SPY has approximately 900M shares outstanding
const sharesOutstanding = 900_000_000;
const spot = 450.50;

const coverage = calculateSharesNeededToCover(
  sharesOutstanding,
  totalNetExposure,
  spot
);

console.log('\n=== Dealer Hedging Analysis ===');
console.log(`Total Net Exposure: ${totalNetExposure.toLocaleString()}`);
console.log(`Dealers need to: ${coverage.actionToCover}`);
console.log(`Shares to trade: ${coverage.sharesToCover.toLocaleString()}`);
console.log(`Implied price move: ${coverage.impliedMoveToCover.toFixed(2)}%`);
console.log(`Resulting price: $${coverage.resultingSpotToCover.toFixed(2)}`);

Finding Key Levels

Identify important gamma levels for trading:

// Find the "gamma wall" - strike with highest absolute gamma
let maxGammaStrike = 0;
let maxGamma = 0;

for (const expiry of exposures) {
  for (const strike of expiry.strikeExposures) {
    if (Math.abs(strike.gammaExposure) > maxGamma) {
      maxGamma = Math.abs(strike.gammaExposure);
      maxGammaStrike = strike.strikePrice;
    }
  }
}

console.log(`\nGamma Wall: $${maxGammaStrike}`);
console.log(`Gamma at wall: ${maxGamma.toLocaleString()}`);

// Find zero gamma level (flip point)
const nearestExpiry = exposures[0];
if (nearestExpiry) {
  const sortedByStrike = [...nearestExpiry.strikeExposures].sort(
    (a, b) => a.strikePrice - b.strikePrice
  );
  
  // Find where gamma crosses zero
  for (let i = 1; i < sortedByStrike.length; i++) {
    const prev = sortedByStrike[i - 1];
    const curr = sortedByStrike[i];
    
    if (prev.gammaExposure < 0 && curr.gammaExposure >= 0) {
      console.log(`Zero Gamma Level: ~$${(prev.strikePrice + curr.strikePrice) / 2}`);
      break;
    }
  }
}

Real-World Integration

Combine with FloeClient for live exposure tracking:

import {
  FloeClient,
  Broker,
  calculateGammaVannaCharmExposures,
  getIVSurfaces,
  generateOCCSymbolsAroundSpot
} from "@fullstackcraftllc/floe";

async function trackLiveExposures() {
  const client = new FloeClient({ verbose: false });
  await client.connect(Broker.TRADIER, process.env.TRADIER_TOKEN!);
  
  // Generate symbols for SPY options
  const symbols = generateOCCSymbolsAroundSpot('SPY', '2025-12-20', 450, {
    strikesAbove: 20,
    strikesBelow: 20,
    strikeIncrementInDollars: 1
  });
  
  const optionData = new Map();
  
  client.on('optionUpdate', (option) => {
    optionData.set(option.occSymbol, option);
  });
  
  client.subscribeToOptions(symbols);
  await client.fetchOpenInterest();
  
  // Wait for data to populate
  await new Promise(resolve => setTimeout(resolve, 5000));
  
  // Build chain from live data
  const chain = {
    symbol: 'SPY',
    spot: 450.50,  // Get from ticker update
    riskFreeRate: 0.05,
    dividendYield: 0.02,
    options: Array.from(optionData.values())
  };
  
  // Calculate exposures
  const surfaces = getIVSurfaces('blackscholes', 'totalvariance', chain);
  const exposures = calculateGammaVannaCharmExposures(chain, surfaces);
  
  // Output analysis
  console.log('Live Exposure Analysis:');
  for (const exp of exposures) {
    console.log(`${new Date(exp.expiration).toDateString()}: Net ${exp.totalNetExposure.toLocaleString()}`);
  }
  
  client.disconnect();
}

trackLiveExposures();