Vega
${formatNum(pricingResults.vega)}
Calculated Option Greeks
| Greek | Call Value | Put Value |
| Delta (Δ) | ${formatNum(pricingResults.callDelta)} | ${formatNum(pricingResults.putDelta)} |
| Gamma (Γ) | ${formatNum(pricingResults.gamma)} | ${formatNum(pricingResults.gamma)} |
| Theta (Θ) | ${formatNum(pricingResults.callTheta)} | ${formatNum(pricingResults.putTheta)} |
| Vega (ν) | ${formatNum(pricingResults.vega)} | ${formatNum(pricingResults.vega)} |
| Rho (ρ) | ${formatNum(pricingResults.callRho)} | ${formatNum(pricingResults.putRho)} |
`;
};
const renderInputsTab = () => {
return `
`;
};
// --- DATA & EVENT HANDLERS ---
window.updateInput = (key, value) => {
optionInputs[key] = (key === 'expirationDate') ? value : parseFloat(value) || 0;
};
const handleCalculation = () => {
pricingResults = blackScholes(
optionInputs.stockPrice,
optionInputs.strikePrice,
optionInputs.riskFreeRate / 100,
optionInputs.volatility / 100,
getTimeToExpiration()
);
switchTab(0);
};
const getTimeToExpiration = () => {
const today = new Date();
const expDate = new Date(optionInputs.expirationDate);
// Set both to UTC midnight to avoid timezone issues affecting day count
today.setUTCHours(0, 0, 0, 0);
expDate.setUTCHours(0, 0, 0, 0);
const diffTime = expDate.getTime() - today.getTime();
const diffDays = Math.max(0, diffTime / (1000 * 60 * 60 * 24));
return diffDays / 365.0;
};
// --- BLACK-SCHOLES CALCULATION LOGIC ---
function CND(x) { // Cumulative Normal Distribution
let a1 = 0.319381530, a2 = -0.356563782, a3 = 1.781477937, a4 = -1.821255978, a5 = 1.330274429;
let L = Math.abs(x);
let k = 1.0 / (1.0 + 0.2316419 * L);
let w = 1.0 - 1.0 / Math.sqrt(2 * Math.PI) * Math.exp(-L * L / 2) * (a1 * k + a2 * k * k + a3 * Math.pow(k, 3) + a4 * Math.pow(k, 4) + a5 * Math.pow(k, 5));
return x < 0 ? 1.0 - w : w;
}
function PDF(x) { // Probability Density Function
return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);
}
function blackScholes(S, K, r, sigma, T) {
if (T <= 0) T = 0.000001; // Avoid division by zero for expired options
let d1 = (Math.log(S / K) + (r + sigma * sigma / 2.0) * T) / (sigma * Math.sqrt(T));
let d2 = d1 - sigma * Math.sqrt(T);
let callPrice = S * CND(d1) - K * Math.exp(-r * T) * CND(d2);
let putPrice = K * Math.exp(-r * T) * CND(-d2) - S * CND(-d1);
// Greeks
let callDelta = CND(d1);
let putDelta = callDelta - 1;
let gamma = PDF(d1) / (S * sigma * Math.sqrt(T));
let vega = S * PDF(d1) * Math.sqrt(T) / 100; // per 1% change
let callTheta = (- (S * PDF(d1) * sigma) / (2 * Math.sqrt(T)) - r * K * Math.exp(-r * T) * CND(d2)) / 365; // per day
let putTheta = (- (S * PDF(d1) * sigma) / (2 * Math.sqrt(T)) + r * K * Math.exp(-r * T) * CND(-d2)) / 365; // per day
let callRho = (K * T * Math.exp(-r * T) * CND(d2)) / 100; // per 1% change
let putRho = (-K * T * Math.exp(-r * T) * CND(-d2)) / 100; // per 1% change
return { callPrice, putPrice, callDelta, putDelta, gamma, vega, callTheta, putTheta, callRho, putRho };
}
// --- CHART & PDF ---
const renderChart = () => {
if (payoffChart) payoffChart.destroy();
const ctx = document.getElementById('payoffChart')?.getContext('2d');
if (!ctx || !pricingResults) return;
const K = optionInputs.strikePrice;
const callP = pricingResults.callPrice;
const putP = pricingResults.putPrice;
const labels = [];
const callPayoff = [];
const putPayoff = [];
for (let i = -0.4; i <= 0.4; i += 0.05) {
let stockPriceAtExp = K * (1 + i);
labels.push(stockPriceAtExp.toFixed(2));
callPayoff.push(Math.max(0, stockPriceAtExp - K) - callP);
putPayoff.push(Math.max(0, K - stockPriceAtExp) - putP);
}
payoffChart = new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [
{ label: 'Call Option P/L', data: callPayoff, borderColor: '#16a34a', backgroundColor: 'rgba(22, 163, 74, 0.1)', fill: true, tension: 0.1 },
{ label: 'Put Option P/L', data: putPayoff, borderColor: '#dc2626', backgroundColor: 'rgba(220, 38, 38, 0.1)', fill: true, tension: 0.1 }
]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: { title: { display: true, text: 'Profit/Loss at Expiration' } },
scales: { x: { title: { display: true, text: 'Stock Price at Expiration ($)' } }, y: { title: { display: true, text: 'Profit / Loss ($)' } } }
}
});
};
window.downloadPDF = () => {
if (!pricingResults) return;
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFontSize(20);
doc.text("Stock Option Pricing Report", doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' });
doc.setFontSize(14);
doc.text("Input Parameters", 14, 35);
doc.autoTable({
startY: 40,
body: [
['Stock Price', `$${optionInputs.stockPrice.toFixed(2)}`],
['Strike Price', `$${optionInputs.strikePrice.toFixed(2)}`],
['Expiration Date', optionInputs.expirationDate],
['Volatility', `${optionInputs.volatility.toFixed(2)}%`],
['Risk-Free Rate', `${optionInputs.riskFreeRate.toFixed(2)}%`],
],
theme: 'grid',
styles: { cellPadding: 2 },
headStyles: { fillColor: false, textColor: 0 },
columnStyles: { 0: { fontStyle: 'bold' } },
});
const finalY1 = doc.lastAutoTable.finalY + 15;
doc.setFontSize(14);
doc.text("Pricing & Greeks", 14, finalY1);
const head = [['Metric', 'Call Value', 'Put Value']];
const body = [
['Price', `$${pricingResults.callPrice.toFixed(2)}`, `$${pricingResults.putPrice.toFixed(2)}`],
['Delta (Δ)', pricingResults.callDelta.toFixed(4), pricingResults.putDelta.toFixed(4)],
['Gamma (Γ)', pricingResults.gamma.toFixed(4), pricingResults.gamma.toFixed(4)],
['Theta (Θ)', pricingResults.callTheta.toFixed(4), pricingResults.putTheta.toFixed(4)],
['Vega (ν)', pricingResults.vega.toFixed(4), pricingResults.vega.toFixed(4)],
['Rho (ρ)', pricingResults.callRho.toFixed(4), pricingResults.putRho.toFixed(4)],
];
doc.autoTable({ startY: finalY1 + 5, head: head, body: body, theme: 'striped', headStyles: { fillColor: [55, 48, 163] } });
doc.save('Option-Pricing-Report.pdf');
};
// --- INITIALIZATION ---
DOM.nextBtn.addEventListener('click', () => switchTab(currentTab + 1));
DOM.prevBtn.addEventListener('click', () => switchTab(currentTab - 1));
DOM.tabButtons.forEach((btn, i) => btn.addEventListener('click', () => switchTab(i)));
renderCurrentTab();
updateNavButtons();
});