liquidity provision tracking

This commit is contained in:
mantikoros 2022-03-07 11:29:58 -06:00
parent 9c5478d3d5
commit 845aefa6a8
13 changed files with 204 additions and 56 deletions

View File

@ -1,23 +1,13 @@
import { Bet } from './bet'
import { getDpmProbability } from './calculate-dpm'
import { getCpmmProbability } from './calculate-cpmm'
import { getCpmmLiquidity, getCpmmProbability } from './calculate-cpmm'
import { Binary, CPMM, DPM, FreeResponse, FullContract } from './contract'
import { User } from './user'
import { LiquidityProvision } from './liquidity-provision'
export const PHANTOM_ANTE = 0.001
export const MINIMUM_ANTE = 10
export const calcStartCpmmPool = (initialProbInt: number, ante: number) => {
const p = initialProbInt / 100.0
const [poolYes, poolNo] =
p >= 0.5 ? [ante * (1 / p - 1), ante] : [ante, ante * (1 / (1 - p) - 1)]
const k = poolYes * poolNo
return { poolYes, poolNo, k }
}
export function getCpmmAnteBet(
creator: User,
contract: FullContract<CPMM, Binary>,
@ -45,20 +35,27 @@ export function getCpmmAnteBet(
return bet
}
export const calcStartPool = (initialProbInt: number, ante = 0) => {
const p = initialProbInt / 100.0
const totalAnte = PHANTOM_ANTE + ante
export function getCpmmInitialLiquidity(
creator: User,
contract: FullContract<CPMM, Binary>,
anteId: string,
amount: number
) {
const { createdTime, pool } = contract
const liquidity = getCpmmLiquidity(pool)
const sharesYes = Math.sqrt(p * totalAnte ** 2)
const sharesNo = Math.sqrt(totalAnte ** 2 - sharesYes ** 2)
const lp: LiquidityProvision = {
id: anteId,
userId: creator.id,
contractId: contract.id,
createdTime,
isAnte: true,
const poolYes = p * ante
const poolNo = (1 - p) * ante
amount: amount,
liquidity,
}
const phantomYes = Math.sqrt(p) * PHANTOM_ANTE
const phantomNo = Math.sqrt(1 - p) * PHANTOM_ANTE
return { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo }
return lp
}
export function getAnteBets(

View File

@ -19,6 +19,7 @@ export type Bet = {
isSold?: boolean // true if this BUY bet has been sold
isAnte?: boolean
isLiquidityProvision?: boolean
createdTime: number
}

View File

@ -24,11 +24,11 @@ export function calculateCpmmShares(
pool: {
[outcome: string]: number
},
k: number,
bet: number,
betChoice: string
) {
const { YES: y, NO: n } = pool
const k = y * n
const numerator = bet ** 2 + bet * (y + n) - k + y * n
const denominator = betChoice === 'YES' ? bet + n : bet + y
const shares = numerator / denominator
@ -40,9 +40,9 @@ export function calculateCpmmPurchase(
bet: number,
outcome: string
) {
const { pool, k } = contract
const { pool } = contract
const shares = calculateCpmmShares(pool, k, bet, outcome)
const shares = calculateCpmmShares(pool, bet, outcome)
const { YES: y, NO: n } = pool
const [newY, newN] =
@ -60,11 +60,11 @@ export function calculateCpmmShareValue(
shares: number,
outcome: string
) {
const { pool, k } = contract
const { pool } = contract
const { YES: y, NO: n } = pool
const poolChange = outcome === 'YES' ? shares + y - n : shares + n - y
const k = y * n
const shareValue = 0.5 * (shares + y + n - Math.sqrt(4 * k + poolChange ** 2))
return shareValue
}
@ -101,3 +101,61 @@ export function getCpmmProbabilityAfterSale(
const { newPool } = calculateCpmmSale(contract, bet)
return getCpmmProbability(newPool)
}
export const calcCpmmInitialPool = (initialProbInt: number, ante: number) => {
const p = initialProbInt / 100.0
const [poolYes, poolNo] =
p >= 0.5 ? [ante * (1 / p - 1), ante] : [ante, ante * (1 / (1 - p) - 1)]
return { poolYes, poolNo }
}
export function getCpmmLiquidity(pool: { [outcome: string]: number }) {
// For binary contracts only.
const { YES, NO } = pool
return Math.sqrt(YES * NO)
}
export function addCpmmLiquidity(
contract: FullContract<CPMM, Binary>,
amount: number
) {
const { YES, NO } = contract.pool
const p = getCpmmProbability({ YES, NO })
const [newYes, newNo] =
p >= 0.5
? [amount * (1 / p - 1), amount]
: [amount, amount * (1 / (1 - p) - 1)]
const betAmount = Math.abs(newYes - newNo)
const betOutcome = p >= 0.5 ? 'YES' : 'NO'
const poolLiquidity = getCpmmLiquidity({ YES, NO })
const newPool = { YES: YES + newYes, NO: NO + newNo }
const resultingLiquidity = getCpmmLiquidity(newPool)
const liquidity = resultingLiquidity - poolLiquidity
return { newPool, liquidity, betAmount, betOutcome }
}
export function removeCpmmLiquidity(
contract: FullContract<CPMM, Binary>,
liquidity: number
) {
const { YES, NO } = contract.pool
const poolLiquidity = getCpmmLiquidity({ YES, NO })
const p = getCpmmProbability({ YES, NO })
const f = liquidity / poolLiquidity
const [payoutYes, payoutNo] = [f * YES, f * NO]
const betAmount = Math.abs(payoutYes - payoutNo)
const betOutcome = p >= 0.5 ? 'NO' : 'YES' // opposite side as adding liquidity
const payout = Math.min(payoutYes, payoutNo)
const newPool = { YES: YES - payoutYes, NO: NO - payoutNo }
return { newPool, payout, betAmount, betOutcome }
}

View File

@ -271,3 +271,23 @@ export const deductDpmFees = (betAmount: number, winnings: number) => {
? betAmount + (1 - FEES) * (winnings - betAmount)
: winnings
}
export const calcDpmInitialPool = (
initialProbInt: number,
ante: number,
phantomAnte: number
) => {
const p = initialProbInt / 100.0
const totalAnte = phantomAnte + ante
const sharesYes = Math.sqrt(p * totalAnte ** 2)
const sharesNo = Math.sqrt(totalAnte ** 2 - sharesYes ** 2)
const poolYes = p * ante
const poolNo = (1 - p) * ante
const phantomYes = Math.sqrt(p) * phantomAnte
const phantomNo = Math.sqrt(1 - p) * phantomAnte
return { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo }
}

View File

@ -28,11 +28,12 @@ export function getProbability(contract: FullContract<DPM | CPMM, Binary>) {
: getDpmProbability(contract.totalShares)
}
// TODO: Deprecate this function
export function getInitialProbability(
contract: FullContract<DPM | CPMM, Binary>
) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbability(contract.liquidity[contract.creatorId])
? getCpmmProbability(contract.pool)
: getDpmProbability(contract.phantomShares ?? contract.totalShares)
}
@ -62,7 +63,7 @@ export function calculateShares(
betChoice: string
) {
return contract.mechanism === 'cpmm-1'
? calculateCpmmShares(contract.pool, contract.k, bet, betChoice)
? calculateCpmmShares(contract.pool, bet, betChoice)
: calculateDpmShares(contract.totalShares, bet, betChoice)
}

View File

@ -46,10 +46,7 @@ export type DPM = {
export type CPMM = {
mechanism: 'cpmm-1'
pool: { [outcome: string]: number }
k: number // liquidity constant
liquidity: { [userId: string]: { [outcome: string]: number } } // track liquidity providers
}
export type FixedPayouts = CPMM

View File

@ -0,0 +1,11 @@
export type LiquidityProvision = {
id: string
userId: string
contractId: string
createdTime: number
isAnte?: boolean
amount: number // M$ quantity
liquidity: number // sqrt(k)
}

View File

@ -1,4 +1,4 @@
import { calcStartPool, calcStartCpmmPool } from './antes'
import { PHANTOM_ANTE } from './antes'
import {
Binary,
Contract,
@ -10,6 +10,8 @@ import {
import { User } from './user'
import { parseTags } from './util/parse'
import { removeUndefinedProps } from './util/object'
import { calcCpmmInitialPool } from './calculate-cpmm'
import { calcDpmInitialPool } from './calculate-dpm'
export function getNewContract(
id: string,
@ -62,7 +64,7 @@ export function getNewContract(
const getBinaryDpmProps = (initialProb: number, ante: number) => {
const { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo } =
calcStartPool(initialProb, ante)
calcDpmInitialPool(initialProb, ante, PHANTOM_ANTE)
const system: DPM & Binary = {
mechanism: 'dpm-2',
@ -81,15 +83,13 @@ const getBinaryCpmmProps = (
ante: number,
userId: string
) => {
const { poolYes, poolNo, k } = calcStartCpmmPool(initialProb, ante)
const { poolYes, poolNo } = calcCpmmInitialPool(initialProb, ante)
const pool = { YES: poolYes, NO: poolNo }
const system: CPMM & Binary = {
mechanism: 'cpmm-1',
outcomeType: 'BINARY',
pool: pool,
k,
liquidity: { [userId]: pool },
}
return system

View File

@ -5,18 +5,32 @@ import { getProbability } from './calculate'
import { deductFixedFees } from './calculate-fixed-payouts'
import { Binary, CPMM, FixedPayouts, FullContract } from './contract'
import { CREATOR_FEE } from './fees'
import { LiquidityProvision } from './liquidity-provision'
export const getFixedCancelPayouts = (bets: Bet[]) => {
return bets.map((bet) => ({
userId: bet.userId,
payout: bet.amount,
export const getFixedCancelPayouts = (
contract: FullContract<FixedPayouts, Binary>,
bets: Bet[],
liquidities: LiquidityProvision[]
) => {
const liquidityPayouts = liquidities.map((lp) => ({
userId: lp.userId,
payout: lp.amount,
}))
return bets
.filter((b) => !b.isAnte && !b.isLiquidityProvision)
.map((bet) => ({
userId: bet.userId,
payout: bet.amount,
}))
.concat(liquidityPayouts)
}
export const getStandardFixedPayouts = (
outcome: string,
contract: FullContract<FixedPayouts, Binary>,
bets: Bet[]
bets: Bet[],
liquidities: LiquidityProvision[]
) => {
const winningBets = bets.filter((bet) => bet.outcome === outcome)
@ -44,20 +58,29 @@ export const getStandardFixedPayouts = (
return payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
.concat(getLiquidityPoolPayouts(contract, outcome))
.concat(getLiquidityPoolPayouts(contract, outcome, liquidities))
}
export const getLiquidityPoolPayouts = (
contract: FullContract<CPMM, Binary>,
outcome: string
outcome: string,
liquidities: LiquidityProvision[]
) => {
const { creatorId, pool } = contract
return [{ userId: creatorId, payout: pool[outcome] }]
const providedLiquidity = _.sumBy(liquidities, (lp) => lp.liquidity)
const { pool } = contract
const finalPool = pool[outcome]
return liquidities.map((lp) => ({
userId: lp.userId,
payout: (lp.liquidity / providedLiquidity) * finalPool,
}))
}
export const getMktFixedPayouts = (
contract: FullContract<FixedPayouts, Binary>,
bets: Bet[],
liquidities: LiquidityProvision[],
resolutionProbability?: number
) => {
const p =
@ -90,14 +113,21 @@ export const getMktFixedPayouts = (
return payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
.concat(getLiquidityPoolProbPayouts(contract, p))
.concat(getLiquidityPoolProbPayouts(contract, p, liquidities))
}
export const getLiquidityPoolProbPayouts = (
contract: FullContract<CPMM, Binary>,
p: number
p: number,
liquidities: LiquidityProvision[]
) => {
const { creatorId, pool } = contract
const payout = p * pool.YES + (1 - p) * pool.NO
return [{ userId: creatorId, payout }]
const providedLiquidity = _.sumBy(liquidities, (lp) => lp.liquidity)
const { pool } = contract
const finalPool = p * pool.YES + (1 - p) * pool.NO
return liquidities.map((lp) => ({
userId: lp.userId,
payout: (lp.liquidity / providedLiquidity) * finalPool,
}))
}

View File

@ -2,6 +2,7 @@ import * as _ from 'lodash'
import { Bet } from './bet'
import { Contract, DPM, FreeResponse, FullContract, Multi } from './contract'
import { LiquidityProvision } from './liquidity-provision'
import {
getDpmCancelPayouts,
getDpmMktPayouts,
@ -22,17 +23,23 @@ export const getPayouts = (
},
contract: Contract,
bets: Bet[],
liquidities: LiquidityProvision[],
resolutionProbability?: number
) => {
if (contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY') {
switch (outcome) {
case 'YES':
case 'NO':
return getStandardFixedPayouts(outcome, contract, bets)
return getStandardFixedPayouts(outcome, contract, bets, liquidities)
case 'MKT':
return getMktFixedPayouts(contract, bets, resolutionProbability)
return getMktFixedPayouts(
contract,
bets,
liquidities,
resolutionProbability
)
case 'CANCEL':
return getFixedCancelPayouts(bets)
return getFixedCancelPayouts(contract, bets, liquidities)
}
}

View File

@ -1,4 +1,5 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { Binary, Contract, FullContract } from './contract'
import { getPayouts } from './payouts'
@ -37,6 +38,7 @@ export function scoreUsersByContract(
resolution ?? 'MKT',
contract,
openBets,
[],
resolutionProbability
)

View File

@ -17,6 +17,7 @@ import { getNewContract } from '../../common/new-contract'
import {
getAnteBets,
getCpmmAnteBet,
getCpmmInitialLiquidity,
getFreeAnswerAnte,
MINIMUM_ANTE,
} from '../../common/antes'
@ -127,7 +128,7 @@ export const createContract = functions
const outcome = y > n ? 'NO' : 'YES' // more in YES pool if prob leans NO
const bet = await getCpmmAnteBet(
const bet = getCpmmAnteBet(
creator,
contract as FullContract<CPMM, Binary>,
betDoc.id,
@ -136,6 +137,19 @@ export const createContract = functions
)
await betDoc.set(bet)
const liquidityDoc = firestore
.collection(`contracts/${contract.id}/liquidity`)
.doc()
const lp = getCpmmInitialLiquidity(
creator,
contract as FullContract<CPMM, Binary>,
liquidityDoc.id,
ante
)
await liquidityDoc.set(lp)
}
} else if (outcomeType === 'FREE_RESPONSE') {
const noneAnswerDoc = firestore

View File

@ -9,6 +9,7 @@ import { getUser, payUser } from './utils'
import { sendMarketResolutionEmail } from './emails'
import { getLoanPayouts, getPayouts } from '../../common/payouts'
import { removeUndefinedProps } from '../../common/util/object'
import { LiquidityProvision } from '../../common/liquidity-provision'
export const resolveMarket = functions
.runWith({ minInstances: 1 })
@ -94,10 +95,19 @@ export const resolveMarket = functions
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
const openBets = bets.filter((b) => !b.isSold && !b.sale)
const liquiditiesSnap = await firestore
.collection(`contracts/${contractId}/liquidity`)
.get()
const liquidities = liquiditiesSnap.docs.map(
(doc) => doc.data() as LiquidityProvision
)
const payouts = getPayouts(
resolutions ?? outcome,
contract,
openBets,
liquidities,
resolutionProbability
)