/**
* Monte Carlo option pricing engine
*/
import { MonteCarloParams, MonteCarloResult } from "./types";
import { randomNormal, mean, stdDev } from "./utils";
/** Maximum paths to return full payoff array (for memory efficiency) */
const MAX_PATHS_FOR_FULL_PAYOFFS = 10000;
/** Z-score for 95% confidence interval */
const Z_95 = 1.96;
/**
* Price a European option using Monte Carlo simulation
*
* Uses Geometric Brownian Motion under risk-neutral measure:
* S_T = S_0 * exp((r - q - 0.5*σ²)*T + σ*√T*Z)
*
* @param params - Monte Carlo parameters
* @returns Price estimate, standard error, confidence interval, and optionally payoffs
*/
export function priceOptionMonteCarlo(params: MonteCarloParams): MonteCarloResult {
const {
spot,
strike,
rate,
vol,
timeToMaturity,
optionType,
paths,
steps = 1,
dividendYield = 0,
} = params;
// Validate inputs
if (paths <= 0) {
throw new Error("paths must be positive");
}
if (timeToMaturity < 0) {
throw new Error("timeToMaturity cannot be negative");
}
// Edge case: expired option
if (timeToMaturity === 0) {
const intrinsic =
optionType === "call"
? Math.max(spot - strike, 0)
: Math.max(strike - spot, 0);
return {
price: intrinsic,
standardError: 0,
confidenceInterval: { lower: intrinsic, upper: intrinsic, level: 0.95 },
payoffs: [intrinsic],
};
}
const dt = timeToMaturity / steps;
const drift = (rate - dividendYield - 0.5 * vol * vol) * dt;
const diffusion = vol * Math.sqrt(dt);
const discountFactor = Math.exp(-rate * timeToMaturity);
const payoffs: number[] = [];
for (let i = 0; i < paths; i++) {
let s = spot;
// Simulate path
for (let j = 0; j < steps; j++) {
const z = randomNormal();
s = s * Math.exp(drift + diffusion * z);
}
// Calculate payoff at maturity
const payoff =
optionType === "call"
? Math.max(s - strike, 0)
: Math.max(strike - s, 0);
payoffs.push(payoff);
}
// Discount payoffs to present value
const discountedPayoffs = payoffs.map((p) => p * discountFactor);
const price = mean(discountedPayoffs);
const standardError = stdDev(discountedPayoffs) / Math.sqrt(paths);
// 95% confidence interval
const margin = Z_95 * standardError;
const confidenceInterval = {
lower: price - margin,
upper: price + margin,
level: 0.95,
};
// Only return full payoffs array if paths <= threshold (memory efficiency)
const result: MonteCarloResult = {
price,
standardError,
confidenceInterval,
};
if (paths <= MAX_PATHS_FOR_FULL_PAYOFFS) {
result.payoffs = payoffs;
}
return result;
}
/**
* Simulate terminal prices for analysis (not option pricing)
*
* @param params - Simulation parameters
* @returns Array of terminal stock prices
*/
export function simulateTerminalPrices(params: {
spot: number;
rate: number;
vol: number;
timeToMaturity: number;
dividendYield?: number;
paths: number;
}): number[] {
const { spot, rate, vol, timeToMaturity, dividendYield = 0, paths } = params;
const drift = (rate - dividendYield - 0.5 * vol * vol) * timeToMaturity;
const diffusion = vol * Math.sqrt(timeToMaturity);
const prices: number[] = [];
for (let i = 0; i < paths; i++) {
const z = randomNormal();
const sT = spot * Math.exp(drift + diffusion * z);
prices.push(sT);
}
return prices;
}