718 lines
No EOL
20 KiB
TypeScript
718 lines
No EOL
20 KiB
TypeScript
/**
|
||
* Options Pricing Models
|
||
* Implementation of various options pricing models and Greeks calculations
|
||
*/
|
||
|
||
export interface OptionParameters {
|
||
spotPrice: number;
|
||
strikePrice: number;
|
||
timeToExpiry: number; // in years
|
||
riskFreeRate: number;
|
||
volatility: number;
|
||
dividendYield?: number;
|
||
}
|
||
|
||
export interface OptionPricing {
|
||
callPrice: number;
|
||
putPrice: number;
|
||
intrinsicValueCall: number;
|
||
intrinsicValuePut: number;
|
||
timeValueCall: number;
|
||
timeValuePut: number;
|
||
}
|
||
|
||
export interface GreeksCalculation {
|
||
delta: number;
|
||
gamma: number;
|
||
theta: number;
|
||
vega: number;
|
||
rho: number;
|
||
}
|
||
|
||
export interface ImpliedVolatilityResult {
|
||
impliedVolatility: number;
|
||
iterations: number;
|
||
converged: boolean;
|
||
}
|
||
|
||
/**
|
||
* Black-Scholes option pricing model
|
||
*/
|
||
export function blackScholes(params: OptionParameters): OptionPricing {
|
||
const { spotPrice, strikePrice, timeToExpiry, riskFreeRate, volatility, dividendYield = 0 } = params;
|
||
|
||
if (timeToExpiry <= 0) {
|
||
const intrinsicValueCall = Math.max(spotPrice - strikePrice, 0);
|
||
const intrinsicValuePut = Math.max(strikePrice - spotPrice, 0);
|
||
|
||
return {
|
||
callPrice: intrinsicValueCall,
|
||
putPrice: intrinsicValuePut,
|
||
intrinsicValueCall,
|
||
intrinsicValuePut,
|
||
timeValueCall: 0,
|
||
timeValuePut: 0
|
||
};
|
||
}
|
||
|
||
const d1 = (Math.log(spotPrice / strikePrice) + (riskFreeRate - dividendYield + 0.5 * volatility * volatility) * timeToExpiry) /
|
||
(volatility * Math.sqrt(timeToExpiry));
|
||
const d2 = d1 - volatility * Math.sqrt(timeToExpiry);
|
||
|
||
const nd1 = normalCDF(d1);
|
||
const nd2 = normalCDF(d2);
|
||
const nMinusd1 = normalCDF(-d1);
|
||
const nMinusd2 = normalCDF(-d2);
|
||
|
||
const callPrice = spotPrice * Math.exp(-dividendYield * timeToExpiry) * nd1 -
|
||
strikePrice * Math.exp(-riskFreeRate * timeToExpiry) * nd2;
|
||
|
||
const putPrice = strikePrice * Math.exp(-riskFreeRate * timeToExpiry) * nMinusd2 -
|
||
spotPrice * Math.exp(-dividendYield * timeToExpiry) * nMinusd1;
|
||
|
||
const intrinsicValueCall = Math.max(spotPrice - strikePrice, 0);
|
||
const intrinsicValuePut = Math.max(strikePrice - spotPrice, 0);
|
||
|
||
const timeValueCall = callPrice - intrinsicValueCall;
|
||
const timeValuePut = putPrice - intrinsicValuePut;
|
||
|
||
return {
|
||
callPrice,
|
||
putPrice,
|
||
intrinsicValueCall,
|
||
intrinsicValuePut,
|
||
timeValueCall,
|
||
timeValuePut
|
||
};
|
||
}
|
||
|
||
export function impliedVolatility(
|
||
price: number, S: number, K: number, T: number, r: number, isCall = true
|
||
): number {
|
||
// …Newton–Raphson on σ to match blackScholesPrice
|
||
let sigma = 0.2; // Initial guess for volatility
|
||
const tolerance = 1e-6;
|
||
const maxIterations = 100;
|
||
let iteration = 0;
|
||
let priceDiff = 1; // Initialize to a non-zero value
|
||
while (Math.abs(priceDiff) > tolerance && iteration < maxIterations) {
|
||
const params: OptionParameters = {
|
||
spotPrice: S,
|
||
strikePrice: K,
|
||
timeToExpiry: T,
|
||
riskFreeRate: r,
|
||
volatility: sigma
|
||
};
|
||
|
||
const calculatedPrice = isCall ? blackScholes(params).callPrice : blackScholes(params).putPrice;
|
||
priceDiff = calculatedPrice - price;
|
||
|
||
// Calculate Vega
|
||
const greeks = calculateGreeks(params, isCall ? 'call' : 'put');
|
||
const vega = greeks.vega * 100; // Convert from percentage to absolute
|
||
|
||
if (vega === 0) {
|
||
break; // Avoid division by zero
|
||
}
|
||
|
||
sigma -= priceDiff / vega; // Update volatility estimate
|
||
iteration++;
|
||
}
|
||
if (iteration === maxIterations) {
|
||
console.warn('Implied volatility calculation did not converge');
|
||
}
|
||
|
||
if (sigma < 0) {
|
||
console.warn('Calculated implied volatility is negative, returning 0');
|
||
return 0;
|
||
}
|
||
|
||
if (sigma > 10) {
|
||
console.warn('Calculated implied volatility is too high, returning 10');
|
||
return 10; // Cap at a reasonable maximum
|
||
}
|
||
if (isNaN(sigma)) {
|
||
console.warn('Calculated implied volatility is NaN, returning 0');
|
||
return 0;
|
||
}
|
||
return sigma
|
||
}
|
||
|
||
/**
|
||
* Calculate option Greeks using Black-Scholes model
|
||
*/
|
||
export function calculateGreeks(params: OptionParameters, optionType: 'call' | 'put' = 'call'): GreeksCalculation {
|
||
const { spotPrice, strikePrice, timeToExpiry, riskFreeRate, volatility, dividendYield = 0 } = params;
|
||
|
||
if (timeToExpiry <= 0) {
|
||
return {
|
||
delta: optionType === 'call' ? (spotPrice > strikePrice ? 1 : 0) : (spotPrice < strikePrice ? -1 : 0),
|
||
gamma: 0,
|
||
theta: 0,
|
||
vega: 0,
|
||
rho: 0
|
||
};
|
||
}
|
||
|
||
const d1 = (Math.log(spotPrice / strikePrice) + (riskFreeRate - dividendYield + 0.5 * volatility * volatility) * timeToExpiry) /
|
||
(volatility * Math.sqrt(timeToExpiry));
|
||
const d2 = d1 - volatility * Math.sqrt(timeToExpiry);
|
||
|
||
const nd1 = normalCDF(d1);
|
||
const nd2 = normalCDF(d2);
|
||
const npd1 = normalPDF(d1);
|
||
|
||
// Delta
|
||
const callDelta = Math.exp(-dividendYield * timeToExpiry) * nd1;
|
||
const putDelta = Math.exp(-dividendYield * timeToExpiry) * (nd1 - 1);
|
||
const delta = optionType === 'call' ? callDelta : putDelta;
|
||
|
||
// Gamma (same for calls and puts)
|
||
const gamma = Math.exp(-dividendYield * timeToExpiry) * npd1 /
|
||
(spotPrice * volatility * Math.sqrt(timeToExpiry));
|
||
|
||
// Theta
|
||
const term1 = -(spotPrice * npd1 * volatility * Math.exp(-dividendYield * timeToExpiry)) /
|
||
(2 * Math.sqrt(timeToExpiry));
|
||
const term2Call = riskFreeRate * strikePrice * Math.exp(-riskFreeRate * timeToExpiry) * nd2;
|
||
const term2Put = -riskFreeRate * strikePrice * Math.exp(-riskFreeRate * timeToExpiry) * normalCDF(-d2);
|
||
const term3 = dividendYield * spotPrice * Math.exp(-dividendYield * timeToExpiry) *
|
||
(optionType === 'call' ? nd1 : normalCDF(-d1));
|
||
|
||
const theta = optionType === 'call' ?
|
||
(term1 - term2Call + term3) / 365 :
|
||
(term1 + term2Put + term3) / 365;
|
||
|
||
// Vega (same for calls and puts)
|
||
const vega = spotPrice * Math.exp(-dividendYield * timeToExpiry) * npd1 * Math.sqrt(timeToExpiry) / 100;
|
||
|
||
// Rho
|
||
const callRho = strikePrice * timeToExpiry * Math.exp(-riskFreeRate * timeToExpiry) * nd2 / 100;
|
||
const putRho = -strikePrice * timeToExpiry * Math.exp(-riskFreeRate * timeToExpiry) * normalCDF(-d2) / 100;
|
||
const rho = optionType === 'call' ? callRho : putRho;
|
||
|
||
return {
|
||
delta,
|
||
gamma,
|
||
theta,
|
||
vega,
|
||
rho
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Calculate implied volatility using Newton-Raphson method
|
||
*/
|
||
export function calculateImpliedVolatility(
|
||
marketPrice: number,
|
||
spotPrice: number,
|
||
strikePrice: number,
|
||
timeToExpiry: number,
|
||
riskFreeRate: number,
|
||
optionType: 'call' | 'put' = 'call',
|
||
dividendYield: number = 0,
|
||
initialGuess: number = 0.2,
|
||
tolerance: number = 1e-6,
|
||
maxIterations: number = 100
|
||
): ImpliedVolatilityResult {
|
||
let volatility = initialGuess;
|
||
let iterations = 0;
|
||
let converged = false;
|
||
|
||
for (let i = 0; i < maxIterations; i++) {
|
||
iterations = i + 1;
|
||
|
||
const params: OptionParameters = {
|
||
spotPrice,
|
||
strikePrice,
|
||
timeToExpiry,
|
||
riskFreeRate,
|
||
volatility,
|
||
dividendYield
|
||
};
|
||
|
||
const pricing = blackScholes(params);
|
||
const theoreticalPrice = optionType === 'call' ? pricing.callPrice : pricing.putPrice;
|
||
|
||
const priceDiff = theoreticalPrice - marketPrice;
|
||
|
||
if (Math.abs(priceDiff) < tolerance) {
|
||
converged = true;
|
||
break;
|
||
}
|
||
|
||
// Calculate vega for Newton-Raphson
|
||
const greeks = calculateGreeks(params, optionType);
|
||
const vega = greeks.vega * 100; // Convert back from percentage
|
||
|
||
if (Math.abs(vega) < 1e-10) {
|
||
break; // Avoid division by zero
|
||
}
|
||
|
||
volatility = volatility - priceDiff / vega;
|
||
|
||
// Keep volatility within reasonable bounds
|
||
volatility = Math.max(0.001, Math.min(volatility, 10));
|
||
}
|
||
|
||
return {
|
||
impliedVolatility: volatility,
|
||
iterations,
|
||
converged
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Binomial option pricing model
|
||
*/
|
||
export function binomialOptionPricing(
|
||
params: OptionParameters,
|
||
optionType: 'call' | 'put' = 'call',
|
||
americanStyle: boolean = false,
|
||
steps: number = 100
|
||
): OptionPricing {
|
||
const { spotPrice, strikePrice, timeToExpiry, riskFreeRate, volatility, dividendYield = 0 } = params;
|
||
|
||
const dt = timeToExpiry / steps;
|
||
const u = Math.exp(volatility * Math.sqrt(dt));
|
||
const d = 1 / u;
|
||
const p = (Math.exp((riskFreeRate - dividendYield) * dt) - d) / (u - d);
|
||
const discount = Math.exp(-riskFreeRate * dt);
|
||
|
||
// Create price tree
|
||
const stockPrices: number[][] = [];
|
||
for (let i = 0; i <= steps; i++) {
|
||
stockPrices[i] = [];
|
||
for (let j = 0; j <= i; j++) {
|
||
stockPrices[i][j] = spotPrice * Math.pow(u, i - j) * Math.pow(d, j);
|
||
}
|
||
}
|
||
|
||
// Calculate option values at expiration
|
||
const optionValues: number[][] = [];
|
||
for (let i = 0; i <= steps; i++) {
|
||
optionValues[i] = [];
|
||
}
|
||
|
||
for (let j = 0; j <= steps; j++) {
|
||
if (optionType === 'call') {
|
||
optionValues[steps][j] = Math.max(stockPrices[steps][j] - strikePrice, 0);
|
||
} else {
|
||
optionValues[steps][j] = Math.max(strikePrice - stockPrices[steps][j], 0);
|
||
}
|
||
}
|
||
|
||
// Work backwards through the tree
|
||
for (let i = steps - 1; i >= 0; i--) {
|
||
for (let j = 0; j <= i; j++) {
|
||
// European option value
|
||
const holdValue = discount * (p * optionValues[i + 1][j] + (1 - p) * optionValues[i + 1][j + 1]);
|
||
|
||
if (americanStyle) {
|
||
// American option - can exercise early
|
||
const exerciseValue = optionType === 'call' ?
|
||
Math.max(stockPrices[i][j] - strikePrice, 0) :
|
||
Math.max(strikePrice - stockPrices[i][j], 0);
|
||
|
||
optionValues[i][j] = Math.max(holdValue, exerciseValue);
|
||
} else {
|
||
optionValues[i][j] = holdValue;
|
||
}
|
||
}
|
||
}
|
||
|
||
const price = optionValues[0][0];
|
||
const intrinsicValue = optionType === 'call' ?
|
||
Math.max(spotPrice - strikePrice, 0) :
|
||
Math.max(strikePrice - spotPrice, 0);
|
||
const timeValue = price - intrinsicValue;
|
||
|
||
if (optionType === 'call') {
|
||
return {
|
||
callPrice: price,
|
||
putPrice: 0, // Not calculated
|
||
intrinsicValueCall: intrinsicValue,
|
||
intrinsicValuePut: 0,
|
||
timeValueCall: timeValue,
|
||
timeValuePut: 0
|
||
};
|
||
} else {
|
||
return {
|
||
callPrice: 0, // Not calculated
|
||
putPrice: price,
|
||
intrinsicValueCall: 0,
|
||
intrinsicValuePut: intrinsicValue,
|
||
timeValueCall: 0,
|
||
timeValuePut: timeValue
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Monte Carlo option pricing
|
||
*/
|
||
export function monteCarloOptionPricing(
|
||
params: OptionParameters,
|
||
optionType: 'call' | 'put' = 'call',
|
||
numSimulations: number = 100000
|
||
): OptionPricing {
|
||
const { spotPrice, strikePrice, timeToExpiry, riskFreeRate, volatility, dividendYield = 0 } = params;
|
||
|
||
let totalPayoff = 0;
|
||
|
||
for (let i = 0; i < numSimulations; i++) {
|
||
// Generate random price path
|
||
const z = boxMullerTransform();
|
||
const finalPrice = spotPrice * Math.exp(
|
||
(riskFreeRate - dividendYield - 0.5 * volatility * volatility) * timeToExpiry +
|
||
volatility * Math.sqrt(timeToExpiry) * z
|
||
);
|
||
|
||
// Calculate payoff
|
||
const payoff = optionType === 'call' ?
|
||
Math.max(finalPrice - strikePrice, 0) :
|
||
Math.max(strikePrice - finalPrice, 0);
|
||
|
||
totalPayoff += payoff;
|
||
}
|
||
|
||
const averagePayoff = totalPayoff / numSimulations;
|
||
const price = averagePayoff * Math.exp(-riskFreeRate * timeToExpiry);
|
||
|
||
const intrinsicValue = optionType === 'call' ?
|
||
Math.max(spotPrice - strikePrice, 0) :
|
||
Math.max(strikePrice - spotPrice, 0);
|
||
const timeValue = price - intrinsicValue;
|
||
|
||
if (optionType === 'call') {
|
||
return {
|
||
callPrice: price,
|
||
putPrice: 0,
|
||
intrinsicValueCall: intrinsicValue,
|
||
intrinsicValuePut: 0,
|
||
timeValueCall: timeValue,
|
||
timeValuePut: 0
|
||
};
|
||
} else {
|
||
return {
|
||
callPrice: 0,
|
||
putPrice: price,
|
||
intrinsicValueCall: 0,
|
||
intrinsicValuePut: intrinsicValue,
|
||
timeValueCall: 0,
|
||
timeValuePut: timeValue
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Calculate option portfolio risk metrics
|
||
*/
|
||
export function calculateOptionPortfolioRisk(
|
||
positions: Array<{
|
||
optionType: 'call' | 'put';
|
||
quantity: number;
|
||
params: OptionParameters;
|
||
}>
|
||
): {
|
||
totalDelta: number;
|
||
totalGamma: number;
|
||
totalTheta: number;
|
||
totalVega: number;
|
||
totalRho: number;
|
||
portfolioValue: number;
|
||
} {
|
||
let totalDelta = 0;
|
||
let totalGamma = 0;
|
||
let totalTheta = 0;
|
||
let totalVega = 0;
|
||
let totalRho = 0;
|
||
let portfolioValue = 0;
|
||
|
||
for (const position of positions) {
|
||
const greeks = calculateGreeks(position.params, position.optionType);
|
||
const pricing = blackScholes(position.params);
|
||
const optionPrice = position.optionType === 'call' ? pricing.callPrice : pricing.putPrice;
|
||
|
||
totalDelta += greeks.delta * position.quantity;
|
||
totalGamma += greeks.gamma * position.quantity;
|
||
totalTheta += greeks.theta * position.quantity;
|
||
totalVega += greeks.vega * position.quantity;
|
||
totalRho += greeks.rho * position.quantity;
|
||
portfolioValue += optionPrice * position.quantity;
|
||
}
|
||
|
||
return {
|
||
totalDelta,
|
||
totalGamma,
|
||
totalTheta,
|
||
totalVega,
|
||
totalRho,
|
||
portfolioValue
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Volatility surface interpolation
|
||
*/
|
||
export function interpolateVolatilitySurface(
|
||
strikes: number[],
|
||
expiries: number[],
|
||
volatilities: number[][],
|
||
targetStrike: number,
|
||
targetExpiry: number
|
||
): number {
|
||
// Simplified bilinear interpolation
|
||
// In production, use more sophisticated interpolation methods
|
||
|
||
// Find surrounding points
|
||
let strikeIndex = 0;
|
||
let expiryIndex = 0;
|
||
|
||
for (let i = 0; i < strikes.length - 1; i++) {
|
||
if (targetStrike >= strikes[i] && targetStrike <= strikes[i + 1]) {
|
||
strikeIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
for (let i = 0; i < expiries.length - 1; i++) {
|
||
if (targetExpiry >= expiries[i] && targetExpiry <= expiries[i + 1]) {
|
||
expiryIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Bilinear interpolation
|
||
const x1 = strikes[strikeIndex];
|
||
const x2 = strikes[strikeIndex + 1];
|
||
const y1 = expiries[expiryIndex];
|
||
const y2 = expiries[expiryIndex + 1];
|
||
|
||
const q11 = volatilities[expiryIndex][strikeIndex];
|
||
const q12 = volatilities[expiryIndex + 1][strikeIndex];
|
||
const q21 = volatilities[expiryIndex][strikeIndex + 1];
|
||
const q22 = volatilities[expiryIndex + 1][strikeIndex + 1];
|
||
|
||
const wx = (targetStrike - x1) / (x2 - x1);
|
||
const wy = (targetExpiry - y1) / (y2 - y1);
|
||
|
||
return q11 * (1 - wx) * (1 - wy) +
|
||
q21 * wx * (1 - wy) +
|
||
q12 * (1 - wx) * wy +
|
||
q22 * wx * wy;
|
||
}
|
||
|
||
// Helper functions
|
||
|
||
/**
|
||
* Normal cumulative distribution function
|
||
*/
|
||
function normalCDF(x: number): number {
|
||
return 0.5 * (1 + erf(x / Math.sqrt(2)));
|
||
}
|
||
|
||
/**
|
||
* Normal probability density function
|
||
*/
|
||
function normalPDF(x: number): number {
|
||
return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);
|
||
}
|
||
|
||
/**
|
||
* Error function approximation
|
||
*/
|
||
function erf(x: number): number {
|
||
// Abramowitz and Stegun approximation
|
||
const a1 = 0.254829592;
|
||
const a2 = -0.284496736;
|
||
const a3 = 1.421413741;
|
||
const a4 = -1.453152027;
|
||
const a5 = 1.061405429;
|
||
const p = 0.3275911;
|
||
|
||
const sign = x >= 0 ? 1 : -1;
|
||
x = Math.abs(x);
|
||
|
||
const t = 1.0 / (1.0 + p * x);
|
||
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
|
||
|
||
return sign * y;
|
||
}
|
||
|
||
/**
|
||
* Box-Muller transformation for normal random numbers
|
||
*/
|
||
function boxMullerTransform(): number {
|
||
let u1 = Math.random();
|
||
let u2 = Math.random();
|
||
|
||
// Ensure u1 is not zero
|
||
while (u1 === 0) {
|
||
u1 = Math.random();
|
||
}
|
||
|
||
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
||
}
|
||
|
||
/**
|
||
* Prices a straddle option strategy
|
||
*/
|
||
export function straddle(params: OptionParameters): { callPrice: number; putPrice: number; strategyCost: number } {
|
||
const callOption = blackScholes(params);
|
||
const putOption = blackScholes(params);
|
||
const strategyCost = callOption.callPrice + putOption.putPrice;
|
||
|
||
return {
|
||
callPrice: callOption.callPrice,
|
||
putPrice: putOption.putPrice,
|
||
strategyCost: strategyCost
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Prices a strangle option strategy
|
||
*/
|
||
export function strangle(callParams: OptionParameters, putParams: OptionParameters): { callPrice: number; putPrice: number; strategyCost: number } {
|
||
const callOption = blackScholes(callParams);
|
||
const putOption = blackScholes(putParams);
|
||
const strategyCost = callOption.callPrice + putOption.putPrice;
|
||
|
||
return {
|
||
callPrice: callOption.callPrice,
|
||
putPrice: putOption.putPrice,
|
||
strategyCost: strategyCost
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Prices a butterfly option strategy
|
||
*/
|
||
export function butterfly(
|
||
lowerStrikeParams: OptionParameters,
|
||
middleStrikeParams: OptionParameters,
|
||
upperStrikeParams: OptionParameters
|
||
): {
|
||
lowerCallPrice: number;
|
||
middleCallPrice: number;
|
||
upperCallPrice: number;
|
||
strategyCost: number;
|
||
} {
|
||
const lowerCall = blackScholes(lowerStrikeParams);
|
||
const middleCall = blackScholes(middleStrikeParams);
|
||
const upperCall = blackScholes(upperStrikeParams);
|
||
|
||
const strategyCost = lowerCall.callPrice - 2 * middleCall.callPrice + upperCall.callPrice;
|
||
|
||
return {
|
||
lowerCallPrice: lowerCall.callPrice,
|
||
middleCallPrice: middleCall.callPrice,
|
||
upperCallPrice: upperCall.callPrice,
|
||
strategyCost: strategyCost
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Prices a condor option strategy
|
||
*/
|
||
export function condor(
|
||
lowerStrikeParams: OptionParameters,
|
||
middleLowerStrikeParams: OptionParameters,
|
||
middleUpperStrikeParams: OptionParameters,
|
||
upperStrikeParams: OptionParameters
|
||
): {
|
||
lowerCallPrice: number;
|
||
middleLowerCallPrice: number;
|
||
middleUpperCallPrice: number;
|
||
upperCallPrice: number;
|
||
strategyCost: number;
|
||
} {
|
||
const lowerCall = blackScholes(lowerStrikeParams);
|
||
const middleLowerCall = blackScholes(middleLowerStrikeParams);
|
||
const middleUpperCall = blackScholes(middleUpperStrikeParams);
|
||
const upperCall = blackScholes(upperStrikeParams);
|
||
|
||
const strategyCost = lowerCall.callPrice - middleLowerCall.callPrice - middleUpperCall.callPrice + upperCall.callPrice;
|
||
|
||
return {
|
||
lowerCallPrice: lowerCall.callPrice,
|
||
middleLowerCallPrice: middleLowerCall.callPrice,
|
||
middleUpperCallPrice: middleUpperCall.callPrice,
|
||
upperCallPrice: upperCall.callPrice,
|
||
strategyCost: strategyCost
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Calculates combined Greeks for an option strategy
|
||
*/
|
||
export function calculateStrategyGreeks(
|
||
positions: Array<{
|
||
optionType: 'call' | 'put';
|
||
quantity: number;
|
||
params: OptionParameters;
|
||
}>
|
||
): GreeksCalculation {
|
||
let totalDelta = 0;
|
||
let totalGamma = 0;
|
||
let totalTheta = 0;
|
||
let totalVega = 0;
|
||
let totalRho = 0;
|
||
|
||
for (const position of positions) {
|
||
const greeks = calculateGreeks(position.params, position.optionType);
|
||
|
||
totalDelta += greeks.delta * position.quantity;
|
||
totalGamma += greeks.gamma * position.quantity;
|
||
totalTheta += greeks.theta * position.quantity;
|
||
totalVega += greeks.vega * position.quantity;
|
||
totalRho += greeks.rho * position.quantity;
|
||
}
|
||
|
||
return {
|
||
delta: totalDelta,
|
||
gamma: totalGamma,
|
||
theta: totalTheta,
|
||
vega: totalVega,
|
||
rho: totalRho
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Black-Scholes option pricing model with greeks
|
||
*/
|
||
export function blackScholesWithGreeks(params: OptionParameters, optionType: 'call' | 'put' = 'call'): { pricing: OptionPricing; greeks: GreeksCalculation } {
|
||
const pricing = blackScholes(params);
|
||
const greeks = calculateGreeks(params, optionType);
|
||
return { pricing, greeks };
|
||
}
|
||
|
||
/**
|
||
* Calculates the breakeven point for a call option at expiration
|
||
*/
|
||
export function callBreakeven(strikePrice: number, callPrice: number): number {
|
||
return strikePrice + callPrice;
|
||
}
|
||
|
||
/**
|
||
* Calculates the breakeven point for a put option at expiration
|
||
*/
|
||
export function putBreakeven(strikePrice: number, putPrice: number): number {
|
||
return strikePrice - putPrice;
|
||
}
|
||
|
||
/**
|
||
* Estimates the probability of profit for a call option at expiration
|
||
*/
|
||
export function callProbabilityOfProfit(spotPrice: number, strikePrice: number, timeToExpiry: number, riskFreeRate: number, volatility: number): number {
|
||
const d1 = (Math.log(spotPrice / strikePrice) + (riskFreeRate + 0.5 * volatility * volatility) * timeToExpiry) / (volatility * Math.sqrt(timeToExpiry));
|
||
return normalCDF(d1);
|
||
}
|
||
|
||
/**
|
||
* Estimates the probability of profit for a put option at expiration
|
||
*/
|
||
export function putProbabilityOfProfit(spotPrice: number, strikePrice: number, timeToExpiry: number, riskFreeRate: number, volatility: number): number {
|
||
const d1 = (Math.log(spotPrice / strikePrice) + (riskFreeRate + 0.5 * volatility * volatility) * timeToExpiry) / (volatility * Math.sqrt(timeToExpiry));
|
||
return 1 - normalCDF(d1);
|
||
} |