2022-07-10 02:39:26 +00:00
|
|
|
import { sum, groupBy, mapValues, sumBy, zip } from 'lodash'
|
2022-03-15 22:27:51 +00:00
|
|
|
|
2022-06-01 02:42:35 +00:00
|
|
|
import { CPMMContract } from './contract'
|
2022-07-08 16:34:16 +00:00
|
|
|
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, PLATFORM_FEE } from './fees'
|
2022-04-30 16:24:24 +00:00
|
|
|
import { LiquidityProvision } from './liquidity-provision'
|
|
|
|
import { addObjects } from './util/object'
|
2022-03-15 22:27:51 +00:00
|
|
|
|
|
|
|
export function getCpmmProbability(
|
|
|
|
pool: { [outcome: string]: number },
|
|
|
|
p: number
|
|
|
|
) {
|
|
|
|
const { YES, NO } = pool
|
|
|
|
return (p * NO) / ((1 - p) * YES + p * NO)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getCpmmProbabilityAfterBetBeforeFees(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-03-15 22:27:51 +00:00
|
|
|
outcome: string,
|
|
|
|
bet: number
|
|
|
|
) {
|
|
|
|
const { pool, p } = contract
|
|
|
|
const shares = calculateCpmmShares(pool, p, bet, outcome)
|
|
|
|
const { YES: y, NO: n } = pool
|
|
|
|
|
|
|
|
const [newY, newN] =
|
|
|
|
outcome === 'YES'
|
|
|
|
? [y - shares + bet, n + bet]
|
|
|
|
: [y + bet, n - shares + bet]
|
|
|
|
|
|
|
|
return getCpmmProbability({ YES: newY, NO: newN }, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getCpmmOutcomeProbabilityAfterBet(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-03-15 22:27:51 +00:00
|
|
|
outcome: string,
|
|
|
|
bet: number
|
|
|
|
) {
|
|
|
|
const { newPool } = calculateCpmmPurchase(contract, bet, outcome)
|
|
|
|
const p = getCpmmProbability(newPool, contract.p)
|
|
|
|
return outcome === 'NO' ? 1 - p : p
|
|
|
|
}
|
|
|
|
|
|
|
|
// before liquidity fee
|
|
|
|
function calculateCpmmShares(
|
|
|
|
pool: {
|
|
|
|
[outcome: string]: number
|
|
|
|
},
|
|
|
|
p: number,
|
|
|
|
bet: number,
|
|
|
|
betChoice: string
|
|
|
|
) {
|
|
|
|
const { YES: y, NO: n } = pool
|
|
|
|
const k = y ** p * n ** (1 - p)
|
|
|
|
|
|
|
|
return betChoice === 'YES'
|
|
|
|
? // https://www.wolframalpha.com/input?i=%28y%2Bb-s%29%5E%28p%29*%28n%2Bb%29%5E%281-p%29+%3D+k%2C+solve+s
|
|
|
|
y + bet - (k * (bet + n) ** (p - 1)) ** (1 / p)
|
|
|
|
: n + bet - (k * (bet + y) ** -p) ** (1 / (1 - p))
|
|
|
|
}
|
|
|
|
|
2022-07-08 16:34:16 +00:00
|
|
|
export function getCpmmFees(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-03-15 22:27:51 +00:00
|
|
|
bet: number,
|
|
|
|
outcome: string
|
|
|
|
) {
|
2022-05-24 20:46:41 +00:00
|
|
|
const prob = getCpmmProbabilityAfterBetBeforeFees(contract, outcome, bet)
|
|
|
|
const betP = outcome === 'YES' ? 1 - prob : prob
|
2022-03-15 22:27:51 +00:00
|
|
|
|
|
|
|
const liquidityFee = LIQUIDITY_FEE * betP * bet
|
|
|
|
const platformFee = PLATFORM_FEE * betP * bet
|
|
|
|
const creatorFee = CREATOR_FEE * betP * bet
|
|
|
|
const fees: Fees = { liquidityFee, platformFee, creatorFee }
|
|
|
|
|
|
|
|
const totalFees = liquidityFee + platformFee + creatorFee
|
|
|
|
const remainingBet = bet - totalFees
|
|
|
|
|
2022-05-09 21:33:14 +00:00
|
|
|
return { remainingBet, totalFees, fees }
|
2022-03-15 22:27:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function calculateCpmmSharesAfterFee(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-03-15 22:27:51 +00:00
|
|
|
bet: number,
|
|
|
|
outcome: string
|
|
|
|
) {
|
|
|
|
const { pool, p } = contract
|
2022-07-08 16:34:16 +00:00
|
|
|
const { remainingBet } = getCpmmFees(contract, bet, outcome)
|
2022-03-15 22:27:51 +00:00
|
|
|
|
|
|
|
return calculateCpmmShares(pool, p, remainingBet, outcome)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function calculateCpmmPurchase(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-03-15 22:27:51 +00:00
|
|
|
bet: number,
|
|
|
|
outcome: string
|
|
|
|
) {
|
|
|
|
const { pool, p } = contract
|
2022-07-08 16:34:16 +00:00
|
|
|
const { remainingBet, fees } = getCpmmFees(contract, bet, outcome)
|
2022-03-15 22:27:51 +00:00
|
|
|
|
|
|
|
const shares = calculateCpmmShares(pool, p, remainingBet, outcome)
|
|
|
|
const { YES: y, NO: n } = pool
|
|
|
|
|
|
|
|
const { liquidityFee: fee } = fees
|
|
|
|
|
|
|
|
const [newY, newN] =
|
|
|
|
outcome === 'YES'
|
|
|
|
? [y - shares + remainingBet + fee, n + remainingBet + fee]
|
|
|
|
: [y + remainingBet + fee, n - shares + remainingBet + fee]
|
|
|
|
|
|
|
|
const postBetPool = { YES: newY, NO: newN }
|
|
|
|
|
|
|
|
const { newPool, newP } = addCpmmLiquidity(postBetPool, p, fee)
|
|
|
|
|
|
|
|
return { shares, newPool, newP, fees }
|
|
|
|
}
|
|
|
|
|
2022-03-29 19:56:56 +00:00
|
|
|
function computeK(y: number, n: number, p: number) {
|
|
|
|
return y ** p * n ** (1 - p)
|
|
|
|
}
|
|
|
|
|
|
|
|
function sellSharesK(
|
|
|
|
y: number,
|
|
|
|
n: number,
|
|
|
|
p: number,
|
|
|
|
s: number,
|
|
|
|
outcome: 'YES' | 'NO',
|
|
|
|
b: number
|
|
|
|
) {
|
|
|
|
return outcome === 'YES'
|
|
|
|
? computeK(y - b + s, n - b, p)
|
|
|
|
: computeK(y - b, n - b + s, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
function calculateCpmmShareValue(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-03-15 22:27:51 +00:00
|
|
|
shares: number,
|
2022-03-29 19:56:56 +00:00
|
|
|
outcome: 'YES' | 'NO'
|
2022-03-15 22:27:51 +00:00
|
|
|
) {
|
2022-03-29 19:56:56 +00:00
|
|
|
const { pool, p } = contract
|
2022-03-15 22:27:51 +00:00
|
|
|
|
2022-03-30 03:12:21 +00:00
|
|
|
// Find bet amount that preserves k after selling shares.
|
2022-03-29 19:56:56 +00:00
|
|
|
const k = computeK(pool.YES, pool.NO, p)
|
2022-03-30 03:12:21 +00:00
|
|
|
const otherPool = outcome === 'YES' ? pool.NO : pool.YES
|
2022-03-29 19:56:56 +00:00
|
|
|
|
2022-03-30 03:12:21 +00:00
|
|
|
// Constrain the max sale value to the lessor of 1. shares and 2. the other pool.
|
|
|
|
// This is because 1. the max value per share is M$ 1,
|
|
|
|
// and 2. The other pool cannot go negative and the sale value is subtracted from it.
|
|
|
|
// (Without this, there are multiple solutions for the same k.)
|
|
|
|
let highAmount = Math.min(shares, otherPool)
|
2022-04-05 21:00:26 +00:00
|
|
|
let lowAmount = 0
|
2022-03-29 19:56:56 +00:00
|
|
|
let mid = 0
|
|
|
|
let kGuess = 0
|
2022-04-05 21:00:26 +00:00
|
|
|
while (true) {
|
2022-03-29 19:56:56 +00:00
|
|
|
mid = lowAmount + (highAmount - lowAmount) / 2
|
2022-04-05 21:00:26 +00:00
|
|
|
|
|
|
|
// Break once we've reached max precision.
|
2022-04-05 20:16:51 +00:00
|
|
|
if (mid === lowAmount || mid === highAmount) break
|
|
|
|
|
2022-03-29 19:56:56 +00:00
|
|
|
kGuess = sellSharesK(pool.YES, pool.NO, p, shares, outcome, mid)
|
|
|
|
if (kGuess < k) {
|
|
|
|
highAmount = mid
|
|
|
|
} else {
|
|
|
|
lowAmount = mid
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mid
|
2022-03-15 22:27:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function calculateCpmmSale(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-03-30 02:30:04 +00:00
|
|
|
shares: number,
|
|
|
|
outcome: string
|
2022-03-15 22:27:51 +00:00
|
|
|
) {
|
2022-05-02 17:58:59 +00:00
|
|
|
if (Math.round(shares) < 0) {
|
2022-03-30 02:30:04 +00:00
|
|
|
throw new Error('Cannot sell non-positive shares')
|
|
|
|
}
|
2022-03-15 22:27:51 +00:00
|
|
|
|
2022-07-08 16:34:16 +00:00
|
|
|
const rawSaleValue = calculateCpmmShareValue(
|
2022-03-29 19:56:56 +00:00
|
|
|
contract,
|
2022-03-30 02:30:04 +00:00
|
|
|
shares,
|
2022-03-29 19:56:56 +00:00
|
|
|
outcome as 'YES' | 'NO'
|
|
|
|
)
|
2022-03-15 22:27:51 +00:00
|
|
|
|
2022-07-08 16:34:16 +00:00
|
|
|
const { fees, remainingBet: saleValue } = getCpmmFees(
|
|
|
|
contract,
|
|
|
|
rawSaleValue,
|
|
|
|
outcome === 'YES' ? 'NO' : 'YES'
|
|
|
|
)
|
2022-03-15 22:27:51 +00:00
|
|
|
|
|
|
|
const { pool } = contract
|
|
|
|
const { YES: y, NO: n } = pool
|
|
|
|
|
|
|
|
const { liquidityFee: fee } = fees
|
|
|
|
|
|
|
|
const [newY, newN] =
|
|
|
|
outcome === 'YES'
|
|
|
|
? [y + shares - saleValue + fee, n - saleValue + fee]
|
|
|
|
: [y - saleValue + fee, n + shares - saleValue + fee]
|
|
|
|
|
2022-03-30 02:30:04 +00:00
|
|
|
if (newY < 0 || newN < 0) {
|
|
|
|
console.log('calculateCpmmSale', {
|
|
|
|
newY,
|
|
|
|
newN,
|
|
|
|
y,
|
|
|
|
n,
|
|
|
|
shares,
|
|
|
|
saleValue,
|
|
|
|
fee,
|
|
|
|
outcome,
|
|
|
|
})
|
|
|
|
throw new Error('Cannot sell more than in pool')
|
|
|
|
}
|
|
|
|
|
2022-03-29 19:56:56 +00:00
|
|
|
const postBetPool = { YES: newY, NO: newN }
|
|
|
|
|
|
|
|
const { newPool, newP } = addCpmmLiquidity(postBetPool, contract.p, fee)
|
2022-03-15 22:27:51 +00:00
|
|
|
|
2022-03-29 19:56:56 +00:00
|
|
|
return { saleValue, newPool, newP, fees }
|
2022-03-15 22:27:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getCpmmProbabilityAfterSale(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-03-30 02:30:04 +00:00
|
|
|
shares: number,
|
|
|
|
outcome: 'YES' | 'NO'
|
2022-03-15 22:27:51 +00:00
|
|
|
) {
|
2022-03-30 02:30:04 +00:00
|
|
|
const { newPool } = calculateCpmmSale(contract, shares, outcome)
|
2022-03-15 22:27:51 +00:00
|
|
|
return getCpmmProbability(newPool, contract.p)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getCpmmLiquidity(
|
|
|
|
pool: { [outcome: string]: number },
|
|
|
|
p: number
|
|
|
|
) {
|
|
|
|
const { YES, NO } = pool
|
|
|
|
return YES ** p * NO ** (1 - p)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function addCpmmLiquidity(
|
|
|
|
pool: { [outcome: string]: number },
|
|
|
|
p: number,
|
|
|
|
amount: number
|
|
|
|
) {
|
|
|
|
const prob = getCpmmProbability(pool, p)
|
|
|
|
|
|
|
|
//https://www.wolframalpha.com/input?i=p%28n%2Bb%29%2F%28%281-p%29%28y%2Bb%29%2Bp%28n%2Bb%29%29%3Dq%2C+solve+p
|
|
|
|
const { YES: y, NO: n } = pool
|
|
|
|
const numerator = prob * (amount + y)
|
|
|
|
const denominator = amount - n * (prob - 1) + prob * y
|
|
|
|
const newP = numerator / denominator
|
|
|
|
|
|
|
|
const newPool = { YES: y + amount, NO: n + amount }
|
|
|
|
|
|
|
|
const oldLiquidity = getCpmmLiquidity(pool, newP)
|
|
|
|
const newLiquidity = getCpmmLiquidity(newPool, newP)
|
|
|
|
const liquidity = newLiquidity - oldLiquidity
|
|
|
|
|
|
|
|
return { newPool, liquidity, newP }
|
|
|
|
}
|
|
|
|
|
2022-06-08 18:00:49 +00:00
|
|
|
const calculateLiquidityDelta = (p: number) => (l: LiquidityProvision) => {
|
|
|
|
const oldLiquidity = getCpmmLiquidity(l.pool, p)
|
|
|
|
|
|
|
|
const newPool = addObjects(l.pool, { YES: l.amount, NO: l.amount })
|
|
|
|
const newLiquidity = getCpmmLiquidity(newPool, p)
|
|
|
|
|
|
|
|
const liquidity = newLiquidity - oldLiquidity
|
|
|
|
return liquidity
|
|
|
|
}
|
|
|
|
|
2022-04-30 16:24:24 +00:00
|
|
|
export function getCpmmLiquidityPoolWeights(
|
2022-06-01 02:42:35 +00:00
|
|
|
contract: CPMMContract,
|
2022-07-09 17:53:50 +00:00
|
|
|
liquidities: LiquidityProvision[],
|
|
|
|
excludeAntes: boolean
|
2022-04-30 16:24:24 +00:00
|
|
|
) {
|
2022-06-08 18:00:49 +00:00
|
|
|
const calcLiqudity = calculateLiquidityDelta(contract.p)
|
2022-07-09 17:53:50 +00:00
|
|
|
const liquidityShares = liquidities.map(calcLiqudity)
|
|
|
|
const shareSum = sum(liquidityShares)
|
2022-04-30 16:24:24 +00:00
|
|
|
|
2022-07-10 02:39:26 +00:00
|
|
|
const weights = liquidityShares.map((shares, i) => ({
|
|
|
|
weight: shares / shareSum,
|
2022-07-09 17:53:50 +00:00
|
|
|
providerId: liquidities[i].userId,
|
2022-04-30 16:24:24 +00:00
|
|
|
}))
|
|
|
|
|
2022-07-10 02:39:26 +00:00
|
|
|
const includedWeights = excludeAntes
|
|
|
|
? weights.filter((_, i) => !liquidities[i].isAnte)
|
|
|
|
: weights
|
|
|
|
|
|
|
|
const userWeights = groupBy(includedWeights, (w) => w.providerId)
|
2022-05-22 08:36:05 +00:00
|
|
|
const totalUserWeights = mapValues(userWeights, (userWeight) =>
|
|
|
|
sumBy(userWeight, (w) => w.weight)
|
2022-04-30 16:24:24 +00:00
|
|
|
)
|
|
|
|
return totalUserWeights
|
|
|
|
}
|
|
|
|
|
2022-06-08 18:00:49 +00:00
|
|
|
export function getUserLiquidityShares(
|
|
|
|
userId: string,
|
|
|
|
contract: CPMMContract,
|
2022-07-09 17:53:50 +00:00
|
|
|
liquidities: LiquidityProvision[],
|
|
|
|
excludeAntes: boolean
|
2022-06-08 18:00:49 +00:00
|
|
|
) {
|
2022-07-09 17:53:50 +00:00
|
|
|
const weights = getCpmmLiquidityPoolWeights(
|
|
|
|
contract,
|
|
|
|
liquidities,
|
|
|
|
excludeAntes
|
|
|
|
)
|
2022-06-08 18:00:49 +00:00
|
|
|
const userWeight = weights[userId] ?? 0
|
2022-03-15 22:27:51 +00:00
|
|
|
|
2022-06-08 18:00:49 +00:00
|
|
|
return mapValues(contract.pool, (shares) => userWeight * shares)
|
|
|
|
}
|