From 2265769b9895b08a1095231c9840d7e44e8482cd Mon Sep 17 00:00:00 2001 From: Nick Adamson Date: Thu, 14 Dec 2023 09:44:11 -0800 Subject: [PATCH] chore: rename variable; export optionsgreeks --- src/utils/index.ts | 2 + src/utils/vol/index.ts | 43 +++-- src/utils/vol/vol.test.ts | 320 ++++++++++++++++++++++---------------- 3 files changed, 211 insertions(+), 154 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index ff7f3114..3dac6376 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,4 @@ export * from './siwe'; export * from './timestamps'; +export { Brent, OptionsGreeks, TypeOfOption } from './vol'; +export type { Market, OptionData, Underlying } from './vol'; diff --git a/src/utils/vol/index.ts b/src/utils/vol/index.ts index 80316023..f59d89af 100644 --- a/src/utils/vol/index.ts +++ b/src/utils/vol/index.ts @@ -20,13 +20,13 @@ interface Underlying { } // Enumeration for option type -enum OptionType { +enum TypeOfOption { Call = 'call', // The option is a call Put = 'put', // The option is a put } interface OptionData { - type: OptionType; + type: TypeOfOption; T: number; // Time of expiration in years, expressed as a decimal K: number; // Strike price of the option } @@ -192,7 +192,7 @@ class OptionsGreeks { * @returns The fair value of the option. */ public static blackScholesMerton( - type: OptionType, + type: TypeOfOption, st: number, K: number, r: number, @@ -209,7 +209,7 @@ class OptionsGreeks { const d1 = this.d1(st, K, r, q, sigma, tau); const d2 = this.d2(d1, sigma, tau); - if (type === OptionType.Call) { + if (type === TypeOfOption.Call) { // Calculate call option price return ( st * exp(-q * tau) * this.phi(d1) - K * exp(-r * tau) * this.phi(d2) @@ -234,7 +234,7 @@ class OptionsGreeks { * @returns The implied volatility as a decimal. */ public static sigma( - type: OptionType, + type: TypeOfOption, marketPrice: number, st: number, K: number, @@ -285,7 +285,7 @@ class OptionsGreeks { * Delta is also used for hedging strategies, where a position can be delta-hedged by taking positions in the underlying asset. */ public static delta( - type: OptionType, + type: TypeOfOption, st: number, K: number, r: number, @@ -295,7 +295,7 @@ class OptionsGreeks { ): number { const d1 = this.d1(st, K, r, q, sigma, tau); - if (type === OptionType.Call) { + if (type === TypeOfOption.Call) { // Delta for a Call option return exp(-q * tau) * this.phi(d1); } @@ -348,7 +348,7 @@ class OptionsGreeks { * @returns The theta value of the option. Theta is typically negative since options lose value as time passes. */ public static theta( - type: OptionType, + type: TypeOfOption, st: number, K: number, r: number, @@ -362,11 +362,10 @@ class OptionsGreeks { const pdfD1 = exp(-pow(d1, 2) / 2) / sqrt(2 * pi); // Calculate the first part of the theta formula which is common between call and put - const thetaCommon = - (st * exp(-q * tau) * pdfD1 * sigma) / (2 * sqrt(tau)); + const thetaCommon = (st * exp(-q * tau) * pdfD1 * sigma) / (2 * sqrt(tau)); // Depending on the option type calculate the rest of the theta value - if (type === OptionType.Call) { + if (type === TypeOfOption.Call) { // Call option theta formula const secondTerm = -q * st * exp(-q * tau) * this.phi(d1); const thirdTerm = r * K * exp(-r * tau) * this.phi(d2); @@ -375,7 +374,7 @@ class OptionsGreeks { // Put option theta formula const secondTerm = -q * st * exp(-q * tau) * this.phi(-d1); const thirdTerm = r * K * exp(-r * tau) * this.phi(-d2); - return (-thetaCommon + secondTerm + thirdTerm); + return -thetaCommon + secondTerm + thirdTerm; } /** @@ -396,7 +395,7 @@ class OptionsGreeks { * where a shift in interest rates could have a more pronounced effect on the option's value. */ public static rho( - type: OptionType, + type: TypeOfOption, st: number, K: number, r: number, @@ -408,7 +407,7 @@ class OptionsGreeks { const d1 = this.d1(st, K, r, q, sigma, tau); const d2 = this.d2(d1, sigma, tau); - if (type === OptionType.Call) { + if (type === TypeOfOption.Call) { // Rho for a Call option return K * tau * exp(-r * tau) * this.phi(d2) * 0.01; } @@ -433,7 +432,7 @@ class OptionsGreeks { * with a decrease in dividend yield, while a negative epsilon indicates the price decreases as the dividend yield rises. */ public static epsilon( - type: OptionType, + type: TypeOfOption, st: number, K: number, r: number, @@ -443,7 +442,7 @@ class OptionsGreeks { ): number { const d1 = this.d1(st, K, r, q, sigma, tau); - if (type === OptionType.Call) { + if (type === TypeOfOption.Call) { // Epsilon for a Call option return -st * tau * exp(-q * tau) * this.phi(d1); } @@ -468,7 +467,7 @@ class OptionsGreeks { * in the price of the underlying asset. It is similar to Delta but expressed in percentage terms. */ public static lambda( - type: OptionType, + type: TypeOfOption, st: number, K: number, r: number, @@ -578,7 +577,7 @@ class OptionsGreeks { * without trading. */ public static charm( - type: OptionType, + type: TypeOfOption, st: number, K: number, r: number, @@ -597,7 +596,7 @@ class OptionsGreeks { ((2 * (r - q) * tau - d2 * sigma * sqrt(tau)) / (2 * tau * sigma * sqrt(tau))); - if (type === OptionType.Call) { + if (type === TypeOfOption.Call) { // Charm for a Call option return commonTerm; } @@ -842,7 +841,7 @@ class OptionsGreeks { * @returns The dual delta of the option. */ public static dualDelta( - type: OptionType, + type: TypeOfOption, st: number, K: number, r: number, @@ -852,7 +851,7 @@ class OptionsGreeks { ): number { const d2 = this.d2(this.d1(st, K, r, q, sigma, tau), sigma, tau); - if (type === OptionType.Call) { + if (type === TypeOfOption.Call) { // Dual Delta for a Call option return -exp(-r * tau) * this.phi(d2); } @@ -888,5 +887,5 @@ class OptionsGreeks { } } -export { OptionsGreeks, OptionType, Brent }; +export { OptionsGreeks, TypeOfOption, Brent }; export type { Market, Underlying, OptionData }; diff --git a/src/utils/vol/vol.test.ts b/src/utils/vol/vol.test.ts index 85402e74..4a5c49ac 100644 --- a/src/utils/vol/vol.test.ts +++ b/src/utils/vol/vol.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { pi, sin, pow } from 'mathjs/number'; -import { Brent, OptionsGreeks, OptionType } from './index'; +import { Brent, OptionsGreeks, TypeOfOption } from './index'; describe('Brent root-finding algorithm', () => { it('finds the correct root for the Wikipedia example function', () => { @@ -44,138 +44,194 @@ describe('Brent root-finding algorithm', () => { }); describe('Options Greeks BSM and IV convergence', () => { - it('should calculate the correct price for a European put option using Black-Scholes-Merton model', () => { - const st = 100; // Spot price of the underlying asset - const K = 95; // Strike price of the option - const q = 0.05; // Dividend yield - const t = 0.5; // Time to expiration - const r = 0.1; // Risk-free interest rate - const sigma = 0.2; // Volatility of the asset - - const calculatedPutPrice = OptionsGreeks.blackScholesMerton( - OptionType.Put, - st, - K, - r, - q, - sigma, - t - ); - - // assertEquals in QUnit is analogous to toBeCloseTo in vitest with a precision of 4 decimal places - expect(calculatedPutPrice).toBeCloseTo(2.4648, 4); - }); - - it('should calculate the correct implied volatility', () => { - const S = 100; // Spot price of the underlying asset - const K = 100; // Strike price of the option - const sigma = 0.2; // Volatility of the asset - const r = 0.01; // Risk-free interest rate - const type = OptionType.Call; // 'c' for call option - const t = 0.5; // Time to expiration - const q = 0; // Dividend yield - - const expectedPrice = 5.87602423383; - const expectedIv = 0.2; - - // Calculate the option price using Black-Scholes-Merton model - const price = OptionsGreeks.blackScholesMerton(type, S, K, r, q, sigma, t); - - // Calculate the implied volatility using the provided sigma function - const impliedVol = OptionsGreeks.sigma(type, price, S, K, r, q, t); - - // Check if the calculated price and implied volatility are as expected - expect(price).toBeCloseTo(expectedPrice, 5); // Precision to 5 decimal places - expect(impliedVol).toBeCloseTo(expectedIv, 5); // Precision to 5 decimal places - }); + it('should calculate the correct price for a European put option using Black-Scholes-Merton model', () => { + const st = 100; // Spot price of the underlying asset + const K = 95; // Strike price of the option + const q = 0.05; // Dividend yield + const t = 0.5; // Time to expiration + const r = 0.1; // Risk-free interest rate + const sigma = 0.2; // Volatility of the asset + + const calculatedPutPrice = OptionsGreeks.blackScholesMerton( + TypeOfOption.Put, + st, + K, + r, + q, + sigma, + t, + ); + + // assertEquals in QUnit is analogous to toBeCloseTo in vitest with a precision of 4 decimal places + expect(calculatedPutPrice).toBeCloseTo(2.4648, 4); + }); + + it('should calculate the correct implied volatility', () => { + const S = 100; // Spot price of the underlying asset + const K = 100; // Strike price of the option + const sigma = 0.2; // Volatility of the asset + const r = 0.01; // Risk-free interest rate + const type = TypeOfOption.Call; // 'c' for call option + const t = 0.5; // Time to expiration + const q = 0; // Dividend yield + + const expectedPrice = 5.87602423383; + const expectedIv = 0.2; + + // Calculate the option price using Black-Scholes-Merton model + const price = OptionsGreeks.blackScholesMerton(type, S, K, r, q, sigma, t); + + // Calculate the implied volatility using the provided sigma function + const impliedVol = OptionsGreeks.sigma(type, price, S, K, r, q, t); + + // Check if the calculated price and implied volatility are as expected + expect(price).toBeCloseTo(expectedPrice, 5); // Precision to 5 decimal places + expect(impliedVol).toBeCloseTo(expectedIv, 5); // Precision to 5 decimal places + }); }); describe('OptionsGreeks - Greeks', () => { - const S = 49; // Underlying asset price - const K = 50; // Strike price - const r = 0.05; // Risk-free interest rate - const t = 0.3846; // Time to expiration in years - const sigma = 0.2; // Volatility - const q = 0; // Dividend yield - const accuracy = 1e-12; // Tolerance for equality - - it('calculates the correct delta', () => { - const deltaCall = OptionsGreeks.delta(OptionType.Call, S, K, r, q, sigma, t); - expect(deltaCall).toBeCloseTo(0.521601633972, accuracy); - }); - - it('calculates the correct theta', () => { - const thetaCallAnnual = OptionsGreeks.theta(OptionType.Call, S, K, r, q, sigma, t); - expect(thetaCallAnnual).toBeCloseTo(-4.30538996455, accuracy); - - const thetaPutAnnual = OptionsGreeks.theta(OptionType.Put, S, K, r, q, sigma, t); - expect(thetaPutAnnual).toBeCloseTo(-1.8530056722, accuracy); - }); - - it('calculates the correct gamma', () => { - const gammaCall = OptionsGreeks.gamma(S, K, r, q, sigma, t); - expect(gammaCall).toBeCloseTo(0.0655453772525, accuracy); - }); - - it('calculates the correct vega', () => { - const vegaCall = OptionsGreeks.vega(S, K, r, q, sigma, t); - expect(vegaCall).toBeCloseTo(0.121052427542, accuracy); - }); - - it('calculates the correct rho', () => { - const rhoCall = OptionsGreeks.rho(OptionType.Call, S, K, r, q, sigma, t); - expect(rhoCall).toBeCloseTo(0.089065740988, accuracy); - }); - - it('calculates the correct vanna (sensitivity of delta to volatility)', () => { - const _vannaValue = OptionsGreeks.vanna(S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct charm (rate of change of delta over time)', () => { - const _charmCall = OptionsGreeks.charm(OptionType.Call, S, K, r, q, sigma, t); - const _charmPut = OptionsGreeks.charm(OptionType.Put, S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct vomma (sensitivity of vega to volatility)', () => { - const _vommaValue = OptionsGreeks.vomma(S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct veta (rate of change of vega over time)', () => { - const _vetaValue = OptionsGreeks.veta(S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct speed (rate of change of gamma with respect to underlying price)', () => { - const _speedValue = OptionsGreeks.speed(S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct zomma (rate of change of gamma with respect to volatility)', () => { - const _zommaValue = OptionsGreeks.zomma(S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct color (rate of change of gamma with respect to time)', () => { - const _colorValue = OptionsGreeks.color(S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct ultima (rate of change of vomma with respect to volatility)', () => { - const _ultimaValue = OptionsGreeks.ultima(S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct dual delta (sensitivity of option value to strike price)', () => { - const _dualDeltaCall = OptionsGreeks.dualDelta(OptionType.Call, S, K, r, q, sigma, t); - const _dualDeltaPut = OptionsGreeks.dualDelta(OptionType.Put, S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); - - it('calculates the correct dual gamma (sensitivity of dual delta to strike price)', () => { - const _dualGammaValue = OptionsGreeks.dualGamma(S, K, r, q, sigma, t); - // TODO(Include assertions based on expected value) - }); + const S = 49; // Underlying asset price + const K = 50; // Strike price + const r = 0.05; // Risk-free interest rate + const t = 0.3846; // Time to expiration in years + const sigma = 0.2; // Volatility + const q = 0; // Dividend yield + const accuracy = 1e-12; // Tolerance for equality + + it('calculates the correct delta', () => { + const deltaCall = OptionsGreeks.delta( + TypeOfOption.Call, + S, + K, + r, + q, + sigma, + t, + ); + expect(deltaCall).toBeCloseTo(0.521601633972, accuracy); + }); + + it('calculates the correct theta', () => { + const thetaCallAnnual = OptionsGreeks.theta( + TypeOfOption.Call, + S, + K, + r, + q, + sigma, + t, + ); + expect(thetaCallAnnual).toBeCloseTo(-4.30538996455, accuracy); + + const thetaPutAnnual = OptionsGreeks.theta( + TypeOfOption.Put, + S, + K, + r, + q, + sigma, + t, + ); + expect(thetaPutAnnual).toBeCloseTo(-1.8530056722, accuracy); + }); + + it('calculates the correct gamma', () => { + const gammaCall = OptionsGreeks.gamma(S, K, r, q, sigma, t); + expect(gammaCall).toBeCloseTo(0.0655453772525, accuracy); + }); + + it('calculates the correct vega', () => { + const vegaCall = OptionsGreeks.vega(S, K, r, q, sigma, t); + expect(vegaCall).toBeCloseTo(0.121052427542, accuracy); + }); + + it('calculates the correct rho', () => { + const rhoCall = OptionsGreeks.rho(TypeOfOption.Call, S, K, r, q, sigma, t); + expect(rhoCall).toBeCloseTo(0.089065740988, accuracy); + }); + + it('calculates the correct vanna (sensitivity of delta to volatility)', () => { + const _vannaValue = OptionsGreeks.vanna(S, K, r, q, sigma, t); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct charm (rate of change of delta over time)', () => { + const _charmCall = OptionsGreeks.charm( + TypeOfOption.Call, + S, + K, + r, + q, + sigma, + t, + ); + const _charmPut = OptionsGreeks.charm( + TypeOfOption.Put, + S, + K, + r, + q, + sigma, + t, + ); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct vomma (sensitivity of vega to volatility)', () => { + const _vommaValue = OptionsGreeks.vomma(S, K, r, q, sigma, t); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct veta (rate of change of vega over time)', () => { + const _vetaValue = OptionsGreeks.veta(S, K, r, q, sigma, t); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct speed (rate of change of gamma with respect to underlying price)', () => { + const _speedValue = OptionsGreeks.speed(S, K, r, q, sigma, t); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct zomma (rate of change of gamma with respect to volatility)', () => { + const _zommaValue = OptionsGreeks.zomma(S, K, r, q, sigma, t); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct color (rate of change of gamma with respect to time)', () => { + const _colorValue = OptionsGreeks.color(S, K, r, q, sigma, t); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct ultima (rate of change of vomma with respect to volatility)', () => { + const _ultimaValue = OptionsGreeks.ultima(S, K, r, q, sigma, t); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct dual delta (sensitivity of option value to strike price)', () => { + const _dualDeltaCall = OptionsGreeks.dualDelta( + TypeOfOption.Call, + S, + K, + r, + q, + sigma, + t, + ); + const _dualDeltaPut = OptionsGreeks.dualDelta( + TypeOfOption.Put, + S, + K, + r, + q, + sigma, + t, + ); + // TODO(Include assertions based on expected value) + }); + + it('calculates the correct dual gamma (sensitivity of dual delta to strike price)', () => { + const _dualGammaValue = OptionsGreeks.dualGamma(S, K, r, q, sigma, t); + // TODO(Include assertions based on expected value) + }); });