From 845aefa6a8c71f2ef36ad7180a9820bcee28e38d Mon Sep 17 00:00:00 2001 From: mantikoros Date: Mon, 7 Mar 2022 11:29:58 -0600 Subject: [PATCH] liquidity provision tracking --- common/antes.ts | 43 ++++++++++---------- common/bet.ts | 1 + common/calculate-cpmm.ts | 68 +++++++++++++++++++++++++++++--- common/calculate-dpm.ts | 20 ++++++++++ common/calculate.ts | 5 ++- common/contract.ts | 3 -- common/liquidity-provision.ts | 11 ++++++ common/new-contract.ts | 10 ++--- common/payouts-fixed.ts | 58 ++++++++++++++++++++------- common/payouts.ts | 13 ++++-- common/scoring.ts | 2 + functions/src/create-contract.ts | 16 +++++++- functions/src/resolve-market.ts | 10 +++++ 13 files changed, 204 insertions(+), 56 deletions(-) create mode 100644 common/liquidity-provision.ts diff --git a/common/antes.ts b/common/antes.ts index 7bea2740..414f0c60 100644 --- a/common/antes.ts +++ b/common/antes.ts @@ -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, @@ -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, + 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( diff --git a/common/bet.ts b/common/bet.ts index a3e8e714..7b0b4b79 100644 --- a/common/bet.ts +++ b/common/bet.ts @@ -19,6 +19,7 @@ export type Bet = { isSold?: boolean // true if this BUY bet has been sold isAnte?: boolean + isLiquidityProvision?: boolean createdTime: number } diff --git a/common/calculate-cpmm.ts b/common/calculate-cpmm.ts index 94b9a07b..1c4f38f7 100644 --- a/common/calculate-cpmm.ts +++ b/common/calculate-cpmm.ts @@ -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, + 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, + 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 } +} diff --git a/common/calculate-dpm.ts b/common/calculate-dpm.ts index 541fc0c2..76576fc0 100644 --- a/common/calculate-dpm.ts +++ b/common/calculate-dpm.ts @@ -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 } +} diff --git a/common/calculate.ts b/common/calculate.ts index 83bf5091..fe358d32 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -28,11 +28,12 @@ export function getProbability(contract: FullContract) { : getDpmProbability(contract.totalShares) } +// TODO: Deprecate this function export function getInitialProbability( contract: FullContract ) { 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) } diff --git a/common/contract.ts b/common/contract.ts index 21f84b71..5cfd67a2 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -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 diff --git a/common/liquidity-provision.ts b/common/liquidity-provision.ts new file mode 100644 index 00000000..27ba280f --- /dev/null +++ b/common/liquidity-provision.ts @@ -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) +} diff --git a/common/new-contract.ts b/common/new-contract.ts index f27ee2d3..844ce704 100644 --- a/common/new-contract.ts +++ b/common/new-contract.ts @@ -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 diff --git a/common/payouts-fixed.ts b/common/payouts-fixed.ts index 031e8d6c..5263f790 100644 --- a/common/payouts-fixed.ts +++ b/common/payouts-fixed.ts @@ -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, + 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, - 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, - 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, 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, - 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, + })) } diff --git a/common/payouts.ts b/common/payouts.ts index 5b761e48..9cf95a9d 100644 --- a/common/payouts.ts +++ b/common/payouts.ts @@ -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) } } diff --git a/common/scoring.ts b/common/scoring.ts index 78571377..39fce7f9 100644 --- a/common/scoring.ts +++ b/common/scoring.ts @@ -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 ) diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index 0cd18b50..ca3a2f76 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -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, 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, + liquidityDoc.id, + ante + ) + + await liquidityDoc.set(lp) } } else if (outcomeType === 'FREE_RESPONSE') { const noneAnswerDoc = firestore diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts index 0362169c..b256b1b8 100644 --- a/functions/src/resolve-market.ts +++ b/functions/src/resolve-market.ts @@ -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 )