diff --git a/src/BlackScholes.ts b/src/BlackScholes.ts index 268659d..ca2881f 100644 --- a/src/BlackScholes.ts +++ b/src/BlackScholes.ts @@ -1,3 +1,4 @@ +import { getCDFSolidity } from '.' import { std_n_cdf } from './CumulativeNormalDistribution' /** @@ -10,7 +11,7 @@ export function moneyness(strike: number, spot: number): number { } /** - * @notice Calculates a common expression + * @notice Calculates a common equation * @param sigma Volatility as a float * @param tau Time until expiry, in years, as a float * @returns volatilty * sqrt(tau) @@ -60,6 +61,23 @@ export function callDelta(strike: number, sigma: number, tau: number, spot: numb return delta } +/** + * @notice D1 = (Log(spot / strike) + sigma^2 / 2 * tau) / (sigma * sqrt(tau)) + * D2 = D1 - sigma * sqrt(tau) + * @returns D1 and D2, auxiliary variables of the black-scholes formula + */ +export function getD1AndD2( + strike: number, + sigma: number, + tau: number, + spot: number, + rate: number = 0 +): { d1: number; d2: number } { + const d1 = calculateD1(strike, sigma, tau, spot, rate) + const d2 = d1 - getProportionalVol(sigma, tau) + return { d1, d2 } +} + /** * @param strike Strike price of option, as a float * @param sigma Implied volatility of option, as a float @@ -69,7 +87,24 @@ export function callDelta(strike: number, sigma: number, tau: number, spot: numb * @returns Black-Scholes price of call option with parameters */ export function callPremium(strike: number, sigma: number, tau: number, spot: number, rate: number = 0): number { - const d1 = calculateD1(strike, sigma, tau, spot, rate) - const d2 = d1 - getProportionalVol(sigma, tau) + const { d1, d2 } = getD1AndD2(strike, sigma, tau, spot, rate ?? rate) return Math.max(0, std_n_cdf(d1) * spot - std_n_cdf(d2) * strike * Math.exp(-tau * rate)) } + +/** + * @notice Uses solidity CDF approximation + * @dev Use to determine the theoretical theta which can accrue to a position + * @returns Premium of a call option using the solidity CDF approximation formula + */ +export function callPremiumApproximation( + strike: number, + sigma: number, + tau: number, + spot: number, + rate: number = 0 +): number { + const { d1, d2 } = getD1AndD2(strike, sigma, tau, spot, rate ?? rate) + const cdfD1 = getCDFSolidity(d1) + const cdfD2 = getCDFSolidity(d2) + return Math.max(0, cdfD1 * spot - cdfD2 * strike * Math.exp(-tau * rate)) +} diff --git a/test/blackScholes.test.ts b/test/blackScholes.test.ts index de3bf22..fc12005 100644 --- a/test/blackScholes.test.ts +++ b/test/blackScholes.test.ts @@ -25,23 +25,23 @@ describe('Black Scholes', () => { }) describe('calculateD1', () => { - it('return 0 if tau is 0', () => { - const tau = 0 - expect(math.calculateD1(1, 1, tau, 1)).toEqual(0) + it('return 0.5 if tau is 1', () => { + const tau = 1 + expect(math.calculateD1(1, 1, tau, 1)).toEqual(0.5) }) }) describe('calculateD2', () => { - it('return 0 if tau is 0', () => { - const tau = 0 - expect(math.calculateD2(1, 1, tau, 1)).toEqual(0) + it('return -0.5 if tau is 1', () => { + const tau = 1 + expect(math.calculateD2(1, 1, tau, 1)).toEqual(-0.5) }) }) describe('callDelta', () => { - it('return 0.5 if tau is 0', () => { - const tau = 0 - expect(math.callDelta(1, 1, tau, 1)).toBeCloseTo(0.5) + it('return 0.6914624612740131036377 if tau is 1', () => { + const tau = 1 + expect(math.callDelta(1, 1, tau, 1)).toBeCloseTo(0.6914624612740131036377) }) }) @@ -50,5 +50,36 @@ describe('Black Scholes', () => { const tau = 0 expect(math.callPremium(1, 1, tau, 1)).toEqual(0) }) + + it('return 0.3829249225480262072754 if tau is 1', () => { + const tau = 1 + expect(math.callPremium(1, 1, tau, 1)).toBeCloseTo(0.3829249225480262072754) + }) + }) + + describe('approximations', () => { + it('call premium using solidity CDF approximation for 0 tau', () => { + const tau = 0 + expect(math.callPremiumApproximation(1, 1, tau, 1)).toBeCloseTo(math.callPremium(1, 1, tau, 1), 6) + }) + + it('call premium using solidity CDF approximation for 1 year tau', () => { + const tau = 1 + expect(math.callPremiumApproximation(1, 1, tau, 1)).toBeCloseTo(math.callPremium(1, 1, tau, 1), 6) + }) + + it('premium for ITM', () => { + const tau = 1 + const spot = 2 + const strike = 1 + expect(math.callPremiumApproximation(strike, 1, tau, spot)).toBeCloseTo(math.callPremium(strike, 1, tau, spot), 6) + }) + + it('premium for OTM', () => { + const tau = 1 + const spot = 1 + const strike = 2 + expect(math.callPremiumApproximation(strike, 1, tau, spot)).toBeCloseTo(math.callPremium(strike, 1, tau, spot), 6) + }) }) })