Skip to content

Commit

Permalink
Merge pull request #11 from primitivefinance/develop
Browse files Browse the repository at this point in the history
Refactor Replication Math and remove liquidity variables
  • Loading branch information
Alexangelj authored Aug 4, 2021
2 parents 5b897d2 + 0a5f830 commit acc09c4
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 62 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@
"size-limit": [
{
"path": "dist/src.cjs.production.min.js",
"limit": "10 KB"
"limit": "20 KB"
},
{
"path": "dist/src.esm.js",
"limit": "10 KB"
"limit": "20 KB"
}
],
"peerDependencies": {},
"peerDependencies": {},
"devDependencies": {
"@size-limit/preset-small-lib": "^5.0.1",
"husky": "^7.0.1",
Expand Down
4 changes: 2 additions & 2 deletions src/BlackScholes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function getProportionalVol(sigma: number, tau: number): number {
*/
export function calculateD1(strike: number, sigma: number, tau: number, spot: number, rate: number = 0): number {
if (tau <= 0) return 0
return (moneyness(spot, strike) + (rate + Math.pow(sigma, 2) / 2) * tau) / getProportionalVol(sigma, tau)
return (moneyness(strike, spot) + (rate + Math.pow(sigma, 2) / 2) * tau) / getProportionalVol(sigma, tau)
}

/**
Expand Down Expand Up @@ -71,5 +71,5 @@ export function callDelta(strike: number, sigma: number, tau: number, spot: numb
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)
return std_n_cdf(d1) * spot - std_n_cdf(d2) * strike * Math.exp(-tau * rate)
return Math.max(0, std_n_cdf(d1) * spot - std_n_cdf(d2) * strike * Math.exp(-tau * rate))
}
61 changes: 19 additions & 42 deletions src/ReplicationMath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,24 @@ import { getProportionalVol } from './BlackScholes'

/**
* @notice Core math trading function of the AMM to calculate the stable reserve using risky
* @param invariantLast Previous invariant with the same `tau` input as the parameter `tau`
* @param reserveRisky Pool's reserve of risky tokens
* @param liquidity Pool total supply of liquidity (units of replication)
* @param reserveRisky Pool's reserve of risky tokens per unit of liquidity
* @param strike Price point that defines complete stable token composition of the pool
* @param sigma Implied volatility of the pool
* @param tau Time until expiry
* @param invariantLast Previous invariant with the same `tau` input as the parameter `tau`
* @returns Covered Call AMM black-scholes trading function
*/
export function getTradingFunction(
invariantLast: number = 0,
export function getStableGivenRisky(
reserveRisky: number,
liquidity: number,
strike: number,
sigma: number,
tau: number
tau: number,
invariantLast: number = 0
): number {
const K = strike
const vol = getProportionalVol(sigma, tau)
if (vol <= 0) return 0
const reserve: number = reserveRisky / liquidity
const inverseInput: number = 1 - +reserve
const inverseInput: number = 1 - reserveRisky
const phi: number = inverse_std_n_cdf(inverseInput)
const input = phi - vol
const reserveStable = K * std_n_cdf(input) + invariantLast
Expand All @@ -32,27 +29,24 @@ export function getTradingFunction(

/**
* @notice Core math trading function of the AMM to calculate the risky reserve using stable
* @param invariantLast Previous invariant with the same `tau` input as the parameter `tau`
* @param reserveStable Pool's reserve of stable tokens
* @param liquidity Pool total supply of liquidity (units of replication)
* @param reserveStable Pool's reserve of stable tokens per unit of liquidity
* @param strike Price point that defines complete stable token composition of the pool
* @param sigma Implied volatility of the pool
* @param tau Time until expiry
* @param invariantLast Previous invariant with the same `tau` input as the parameter `tau`
* @returns Covered Call AMM black-scholes inverse trading function
*/
export function getInverseTradingFunction(
invariantLast: number = 0,
export function getRiskyGivenStable(
reserveStable: number,
liquidity: number,
strike: number,
sigma: number,
tau: number
tau: number,
invariantLast: number = 0
): number {
const K = strike
const vol = getProportionalVol(sigma, tau)
if (vol <= 0) return 0
const reserve: number = reserveStable / liquidity
const inverseInput: number = (reserve - invariantLast) / K
const inverseInput: number = (reserveStable - invariantLast) / K
const phi: number = inverse_std_n_cdf(inverseInput)
const input = phi + vol
const reserveRisky = 1 - std_n_cdf(input)
Expand All @@ -62,48 +56,36 @@ export function getInverseTradingFunction(
/**
* @param reserveRisky Pool's reserve of risky tokens
* @param reserveStable Pool's reserve of stable tokens
* @param liquidity Pool total supply of liquidity (units of replication)
* @param strike Price point that defines complete stable token composition of the pool
* @param sigma Implied volatility of the pool
* @param tau Time until expiry
* @returns Invariant = Reserve stable - getTradingFunction(...)
* @returns Invariant = Reserve stable - getStableGivenRisky(...)
*/
export function calcInvariant(
reserveRisky: number,
reserveStable: number,
liquidity: number,
strike: number,
sigma: number,
tau: number
): number {
return reserveStable - getTradingFunction(0, reserveRisky, liquidity, strike, sigma, tau)
return reserveStable - getStableGivenRisky(reserveRisky, strike, sigma, tau)
}

/**
* @param reserveRisky Pool's reserve of risky tokens
* @param liquidity Pool total supply of liquidity (units of replication)
* @param strike Price point that defines complete stable token composition of the pool
* @param sigma Implied volatility of the pool
* @param tau Time until expiry
* @returns getTradingFunction(...) * pdf(ppf(1 - risky))^-1
* @returns getStableGivenRisky(...) * pdf(ppf(1 - risky))^-1
*/
export function getSpotPrice(
reserveRisky: number,
liquidity: number,
strike: number,
sigma: number,
tau: number
): number {
return (
getTradingFunction(0, reserveRisky, liquidity, strike, sigma, tau) * quantilePrime(1 - reserveRisky / liquidity)
)
export function getSpotPrice(reserveRisky: number, strike: number, sigma: number, tau: number): number {
return getStableGivenRisky(reserveRisky, strike, sigma, tau) * quantilePrime(1 - reserveRisky)
}

/**
* @notice See https://arxiv.org/pdf/2012.08040.pdf
* @param amountIn Amount of risky token to add to risky reserve
* @param reserveRisky Pool's reserve of risky tokens
* @param liquidity Pool total supply of liquidity (units of replication)
* @param strike Price point that defines complete stable token composition of the pool
* @param sigma Implied volatility of the pool
* @param tau Time until expiry, in years
Expand All @@ -112,16 +94,14 @@ export function getSpotPrice(
export const getMarginalPriceSwapRiskyIn = (
amountIn: number,
reserveRisky: number,
liquidity: number,
strike: number,
sigma: number,
tau: number,
fee: number
) => {
if (!nonNegative(amountIn)) return 0
const gamma = 1 - fee
const risky = reserveRisky / liquidity
const step0 = 1 - risky - gamma * amountIn
const step0 = 1 - reserveRisky - gamma * amountIn
const step1 = sigma * Math.sqrt(tau)
const step2 = quantilePrime(step0)
const step3 = gamma * strike
Expand All @@ -134,7 +114,6 @@ export const getMarginalPriceSwapRiskyIn = (
* @notice See https://arxiv.org/pdf/2012.08040.pdf
* @param amountIn Amount of stable token to add to stable reserve
* @param reserveStable Pool's reserve of stable tokens
* @param liquidity Pool total supply of liquidity (units of replication)
* @param strike Price point that defines complete stable token composition of the pool
* @param sigma Implied volatility of the pool
* @param tau Time until expiry, in years
Expand All @@ -144,16 +123,14 @@ export const getMarginalPriceSwapStableIn = (
amountIn: number,
invariant: number,
reserveStable: number,
liquidity: number,
strike: number,
sigma: number,
tau: number,
fee: number
) => {
if (!nonNegative(amountIn)) return 0
const gamma = 1 - fee
const stable = reserveStable / liquidity
const step0 = (stable + gamma * amountIn - invariant) / strike
const step0 = (reserveStable + gamma * amountIn - invariant) / strike
const step1 = sigma * Math.sqrt(tau)
const step3 = inverse_std_n_cdf(step0)
const step4 = std_n_pdf(step3 + step1)
Expand Down
4 changes: 4 additions & 0 deletions test/blackScholes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ describe('Black Scholes', () => {
it('return 0 if spot and strike are equal', () => {
expect(math.moneyness(10, 10)).toEqual(0)
})

it('return -0.233 if spot and strike are equal', () => {
expect(math.moneyness(2500, 2000)).toBeCloseTo(-0.223)
})
})

describe('getProportionalVol', () => {
Expand Down
60 changes: 60 additions & 0 deletions test/book.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as math from '../src/BlackScholes'

const spot = 2000
const strike = 2500
const tau = 1

const pools = {
[strike]: [
{ iv: '50', size: '45' },
{ iv: '100', size: '355' },
{ iv: '125', size: '322' },
{ iv: '150', size: '200' },
{ iv: '175', size: '50' },
],
}

const poolKeys = Object.keys(pools[strike])

describe('Book', () => {
describe('depth', () => {
it('return 0 if spot and strike are equal', () => {
const premiums = poolKeys.map(key => {
const iv = +pools[strike][key].iv / 100
const premium = math.callPremium(strike, iv, tau, spot)
return premium
})

const totalSize = 100
let size = totalSize
let i = 0
let costs: number[] = []

while (size > 0) {
// get size to take at index
const askSize = +pools[strike][i].size
const premium = premiums[i]
// subtract size from order
if (size > askSize) {
size -= askSize
// add size taken price to avg price
costs.push(askSize * premium)
} else {
// take the remaining size
costs.push(size * premium)
// set the new size to 0
size = 0
}

i++
}

const calcAvgPrice = (costs, totalSize) => {
return costs.reduce((a, b) => a + b) / totalSize
}

calcAvgPrice(costs, totalSize)
//console.log({ avg, costs, premiums })
})
})
})
21 changes: 16 additions & 5 deletions test/cumulativeNormalDistribution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,25 @@ describe('Stats Math Library', () => {
})

describe('inverse standard normal cdf (quantile)', () => {
it('moneyness', () => {
expect(2).toEqual(2)
})
/* it('return nan for out of bounds value: x > 1', () => {
const x = 1.5
expect(math.inverse_std_n_cdf(x)).toEqual(NaN)
}) */
})

describe('quantilePrime', () => {
it('moneyness', () => {
expect(2).toEqual(2)
it('return nan for out of bounds value: x > 1', () => {
const x = 1.5
expect(math.quantilePrime(x)).toEqual(NaN)
})

it('return a number for in bounds number', () => {
const x = 1
expect(math.quantilePrime(x) > 0).toEqual(!NaN)
})
it('return a number for in bounds number', () => {
const x = 0
expect(math.quantilePrime(x) > 0).toEqual(!NaN)
})
})

Expand Down
24 changes: 14 additions & 10 deletions test/replicationMath.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import * as math from '../src/ReplicationMath'

describe('Replication math', () => {
describe('trading function', () => {
describe('getStableGivenRisky', () => {
it('return 0 if strike price and invariant is 0', () => {
expect(math.getTradingFunction(0, 0.5, 1, 0, 1, 1)).toEqual(0)
expect(math.getStableGivenRisky(0.5, 0, 1, 1)).toEqual(0)
})

it('return 0 if risky reserve is 1', () => {
expect(math.getTradingFunction(0, 1, 1, 1, 1, 1)).toEqual(0)
expect(math.getStableGivenRisky(1, 1, 1, 1)).toEqual(0)
})

it('return K if risky reserve is 0', () => {
const K = 1
expect(math.getTradingFunction(0, 0, 1, 1, 1, 1)).toEqual(K)
expect(math.getStableGivenRisky(0, 1, 1, 1)).toEqual(K)
})

it('return 0 if sigma is <= 0', () => {
const sigma = 0
expect(math.getTradingFunction(0, 0.5, 1, 1, sigma, 1)).toEqual(0)
expect(math.getStableGivenRisky(0.5, 1, sigma, 1)).toEqual(0)
})
})

describe('inverse trading function', () => {
describe('getRiskyGivenStable', () => {
it('return 0 if strike price is 0', () => {
expect(2).toEqual(2)
})
Expand All @@ -30,18 +30,22 @@ describe('Replication math', () => {
describe('calculate invariant', () => {
it('return 0 if reserve stable is set to theoretical value based on risky', () => {
const risky = 0.5
const stable = math.getTradingFunction(0, risky, 1, 1, 1, 1)
expect(math.calcInvariant(risky, stable, 1, 1, 1, 1)).toEqual(0)
const stable = math.getStableGivenRisky(risky, 1, 1, 1)
expect(math.calcInvariant(risky, stable, 1, 1, 1)).toEqual(0)
})
})

describe('calculate spot price', () => {
it('shouldnt be nan', () => {
expect(math.getSpotPrice(1, 2, 1, 1, 1) > 0).toBe(!NaN)
expect(math.getSpotPrice(0.999, 1, 1, 1) > 0).toBe(!NaN)
})

it('should be nan if 1 - reserveRisky is 0 or less', () => {
expect(math.getSpotPrice(1, 1, 1, 1)).toBe(NaN)
})

it('should be nan if reserveRisky is greater than 1', () => {
expect(math.getSpotPrice(3, 2, 1, 1, 1)).toBe(NaN)
expect(math.getSpotPrice(3, 2, 1, 1)).toBe(NaN)
})
})

Expand Down

0 comments on commit acc09c4

Please sign in to comment.