/**
* Tests for Black-Scholes pricing and Greeks
*/
import { describe, it, expect } from "vitest";
import { priceBlackScholes, greeksBlackScholes } from "./blackScholes";
describe("priceBlackScholes", () => {
it("prices an ATM call correctly", () => {
const result = priceBlackScholes({
spot: 100,
strike: 100,
rate: 0.05,
vol: 0.2,
timeToMaturity: 1,
optionType: "call",
});
// Expected value from standard BS formula ~10.45
expect(result.price).toBeCloseTo(10.45, 1);
expect(result.d1).toBeCloseTo(0.35, 1);
});
it("prices an OTM put correctly", () => {
const result = priceBlackScholes({
spot: 100,
strike: 90,
rate: 0.05,
vol: 0.2,
timeToMaturity: 1,
optionType: "put",
});
// OTM put should be cheaper than ATM
expect(result.price).toBeLessThan(5);
expect(result.price).toBeGreaterThan(0);
});
it("handles dividend yield", () => {
const withDiv = priceBlackScholes({
spot: 100,
strike: 100,
rate: 0.05,
vol: 0.2,
timeToMaturity: 1,
dividendYield: 0.02,
optionType: "call",
});
const withoutDiv = priceBlackScholes({
spot: 100,
strike: 100,
rate: 0.05,
vol: 0.2,
timeToMaturity: 1,
optionType: "call",
});
// Dividend yield reduces call value
expect(withDiv.price).toBeLessThan(withoutDiv.price);
});
it("returns intrinsic value at expiry", () => {
const result = priceBlackScholes({
spot: 110,
strike: 100,
rate: 0.05,
vol: 0.2,
timeToMaturity: 0,
optionType: "call",
});
expect(result.price).toBe(10);
});
it("satisfies put-call parity", () => {
const params = {
spot: 100,
strike: 100,
rate: 0.05,
vol: 0.2,
timeToMaturity: 1,
};
const call = priceBlackScholes({ ...params, optionType: "call" });
const put = priceBlackScholes({ ...params, optionType: "put" });
// C - P = S - K*exp(-rT)
const lhs = call.price - put.price;
const rhs = params.spot - params.strike * Math.exp(-params.rate * params.timeToMaturity);
expect(lhs).toBeCloseTo(rhs, 6);
});
});
describe("greeksBlackScholes", () => {
const baseParams = {
spot: 100,
strike: 100,
rate: 0.05,
vol: 0.2,
timeToMaturity: 0.5,
};
it("computes delta for call", () => {
const greeks = greeksBlackScholes({ ...baseParams, optionType: "call" });
// ATM call delta should be around 0.5-0.6
expect(greeks.delta).toBeGreaterThan(0.5);
expect(greeks.delta).toBeLessThan(0.7);
});
it("computes delta for put", () => {
const greeks = greeksBlackScholes({ ...baseParams, optionType: "put" });
// Put delta is negative
expect(greeks.delta).toBeLessThan(0);
expect(greeks.delta).toBeGreaterThan(-0.5);
});
it("gamma is positive for both call and put", () => {
const callGreeks = greeksBlackScholes({ ...baseParams, optionType: "call" });
const putGreeks = greeksBlackScholes({ ...baseParams, optionType: "put" });
expect(callGreeks.gamma).toBeGreaterThan(0);
expect(putGreeks.gamma).toBeGreaterThan(0);
// Gamma is same for call and put
expect(callGreeks.gamma).toBeCloseTo(putGreeks.gamma, 6);
});
it("theta is negative for long options", () => {
const greeks = greeksBlackScholes({ ...baseParams, optionType: "call" });
// Theta should be negative (time decay)
expect(greeks.theta).toBeLessThan(0);
});
it("vega is positive", () => {
const greeks = greeksBlackScholes({ ...baseParams, optionType: "call" });
expect(greeks.vega).toBeGreaterThan(0);
});
it("call rho is positive, put rho is negative", () => {
const callGreeks = greeksBlackScholes({ ...baseParams, optionType: "call" });
const putGreeks = greeksBlackScholes({ ...baseParams, optionType: "put" });
expect(callGreeks.rho).toBeGreaterThan(0);
expect(putGreeks.rho).toBeLessThan(0);
});
});