* cpmm initial commit: common logic, cloud functions

* remove unnecessary property

* contract type

* rename 'calculate.ts' => 'calculate-dpm.ts'

* rename dpm calculations

* use focus hook

* mechanism-agnostic calculations

* bet panel: use new calculations

* use new calculations

* delete markets cloud function

* use correct contract type in scripts / functions

* calculate fixed payouts; bets list calculations

* new bet: use calculateCpmmPurchase

* getOutcomeProbabilityAfterBet

* use deductFixedFees

* fix auto-refactor

* fix antes

* separate logic to payouts-dpm, payouts-fixed

* liquidity provision tracking

* remove comment

* liquidity label

* create liquidity provision even if no ante bet

* liquidity fee

* use all bets for getFixedCancelPayouts

* updateUserBalance: allow negative balances

* store initialProbability in contracts

* turn on liquidity fee; turn off creator fee

* Include time param in tweet url, so image preview is re-fetched

* share redemption

* cpmm ContractBetsTable display

* formatMoney: handle minus zero

* filter out redemption bets

* track fees on contract and bets; change fee schedule for cpmm markets; only pay out creator fees at resolution

* small fixes

* small fixes

* Redeem shares pays back loans first

* Fix initial point on graph

* calculateCpmmPurchase: deduct creator fee

* Filter out redemption bets from feed

* set env to dev for user-testing purposes

* creator fees messaging

* new cfmm: k = y^(1-p) * n^p

* addCpmmLiquidity

* correct price function

* enable fees

* handle overflow

* liquidity provision tracking

* raise fees

* Fix merge error

* fix dpm free response payout for single outcome

* Fix DPM payout calculation

* Remove hardcoding as dev

Co-authored-by: James Grugett <jahooma@gmail.com>
This commit is contained in:
mantikoros 2022-03-15 17:27:51 -05:00 committed by GitHub
parent bd62d8fbcd
commit c183e00d47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 2024 additions and 865 deletions

View File

@ -1,34 +1,44 @@
import { Bet } from './bet'
import { getProbability } from './calculate'
import { Contract } from './contract'
import { getDpmProbability } from './calculate-dpm'
import { Binary, CPMM, DPM, FreeResponse, FullContract } from './contract'
import { User } from './user'
import { LiquidityProvision } from './liquidity-provision'
import { noFees } from './fees'
export const PHANTOM_ANTE = 0.001
export const MINIMUM_ANTE = 10
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, p } = contract
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: amount,
p: p,
pool: { YES: 0, NO: 0 },
}
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(
creator: User,
contract: Contract,
contract: FullContract<DPM, Binary>,
yesAnteId: string,
noAnteId: string
) {
const p = getProbability(contract.totalShares)
const p = getDpmProbability(contract.totalShares)
const ante = contract.totalBets.YES + contract.totalBets.NO
const { createdTime } = contract
@ -44,6 +54,7 @@ export function getAnteBets(
probAfter: p,
createdTime,
isAnte: true,
fees: noFees,
}
const noBet: Bet = {
@ -57,6 +68,7 @@ export function getAnteBets(
probAfter: p,
createdTime,
isAnte: true,
fees: noFees,
}
return { yesBet, noBet }
@ -64,7 +76,7 @@ export function getAnteBets(
export function getFreeAnswerAnte(
creator: User,
contract: Contract,
contract: FullContract<DPM, FreeResponse>,
anteBetId: string
) {
const { totalBets, totalShares } = contract
@ -84,6 +96,7 @@ export function getFreeAnswerAnte(
probAfter: 1,
createdTime,
isAnte: true,
fees: noFees,
}
return anteBet

View File

@ -1,3 +1,5 @@
import { Fees } from './fees'
export type Bet = {
id: string
userId: string
@ -6,7 +8,7 @@ export type Bet = {
amount: number // bet size; negative if SELL bet
loanAmount?: number
outcome: string
shares: number // dynamic parimutuel pool weight; negative if SELL bet
shares: number // dynamic parimutuel pool weight or fixed ; negative if SELL bet
probBefore: number
probAfter: number
@ -17,8 +19,12 @@ export type Bet = {
// TODO: add sale time?
}
fees: Fees
isSold?: boolean // true if this BUY bet has been sold
isAnte?: boolean
isLiquidityProvision?: boolean
isRedemption?: boolean
createdTime: number
}

217
common/calculate-cpmm.ts Normal file
View File

@ -0,0 +1,217 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { Binary, CPMM, FullContract } from './contract'
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, noFees, PLATFORM_FEE } from './fees'
export function getCpmmProbability(
pool: { [outcome: string]: number },
p: number
) {
const { YES, NO } = pool
return (p * NO) / ((1 - p) * YES + p * NO)
}
export function getCpmmProbabilityAfterBetBeforeFees(
contract: FullContract<CPMM, Binary>,
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(
contract: FullContract<CPMM, Binary>,
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))
}
export function getCpmmLiquidityFee(
contract: FullContract<CPMM, Binary>,
bet: number,
outcome: string
) {
const prob = getCpmmProbabilityAfterBetBeforeFees(contract, outcome, bet)
const betP = outcome === 'YES' ? 1 - prob : prob
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
return { remainingBet, fees }
}
export function calculateCpmmSharesAfterFee(
contract: FullContract<CPMM, Binary>,
bet: number,
outcome: string
) {
const { pool, p } = contract
const { remainingBet } = getCpmmLiquidityFee(contract, bet, outcome)
return calculateCpmmShares(pool, p, remainingBet, outcome)
}
export function calculateCpmmPurchase(
contract: FullContract<CPMM, Binary>,
bet: number,
outcome: string
) {
const { pool, p } = contract
const { remainingBet, fees } = getCpmmLiquidityFee(contract, bet, outcome)
// const remainingBet = bet
// const fees = noFees
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 }
}
export function calculateCpmmShareValue(
contract: FullContract<CPMM, Binary>,
shares: number,
outcome: string
) {
const { pool } = contract
const { YES: y, NO: n } = pool
// TODO: calculate using new function
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
}
export function calculateCpmmSale(
contract: FullContract<CPMM, Binary>,
bet: Bet
) {
const { shares, outcome } = bet
const rawSaleValue = calculateCpmmShareValue(contract, shares, outcome)
const { fees, remainingBet: saleValue } = getCpmmLiquidityFee(
contract,
rawSaleValue,
outcome === 'YES' ? 'NO' : 'YES'
)
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]
const newPool = { YES: newY, NO: newN }
return { saleValue, newPool, fees }
}
export function getCpmmProbabilityAfterSale(
contract: FullContract<CPMM, Binary>,
bet: Bet
) {
const { newPool } = calculateCpmmSale(contract, bet)
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 }
}
// export function removeCpmmLiquidity(
// contract: FullContract<CPMM, Binary>,
// liquidity: number
// ) {
// const { YES, NO } = contract.pool
// const poolLiquidity = getCpmmLiquidity({ YES, NO })
// const p = getCpmmProbability({ YES, NO }, contract.p)
// 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 }
// }

293
common/calculate-dpm.ts Normal file
View File

@ -0,0 +1,293 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { Binary, DPM, FullContract } from './contract'
import { DPM_FEES } from './fees'
export function getDpmProbability(totalShares: { [outcome: string]: number }) {
// For binary contracts only.
return getDpmOutcomeProbability(totalShares, 'YES')
}
export function getDpmOutcomeProbability(
totalShares: {
[outcome: string]: number
},
outcome: string
) {
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
const shares = totalShares[outcome] ?? 0
return shares ** 2 / squareSum
}
export function getDpmOutcomeProbabilityAfterBet(
totalShares: {
[outcome: string]: number
},
outcome: string,
bet: number
) {
const shares = calculateDpmShares(totalShares, bet, outcome)
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
return getDpmOutcomeProbability(newTotalShares, outcome)
}
export function getDpmProbabilityAfterSale(
totalShares: {
[outcome: string]: number
},
outcome: string,
shares: number
) {
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares - shares }
const predictionOutcome = outcome === 'NO' ? 'YES' : outcome
return getDpmOutcomeProbability(newTotalShares, predictionOutcome)
}
export function calculateDpmShares(
totalShares: {
[outcome: string]: number
},
bet: number,
betChoice: string
) {
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
const shares = totalShares[betChoice] ?? 0
const c = 2 * bet * Math.sqrt(squareSum)
return Math.sqrt(bet ** 2 + shares ** 2 + c) - shares
}
export function calculateDpmRawShareValue(
totalShares: {
[outcome: string]: number
},
shares: number,
betChoice: string
) {
const currentValue = Math.sqrt(
_.sumBy(Object.values(totalShares), (shares) => shares ** 2)
)
const postSaleValue = Math.sqrt(
_.sumBy(Object.keys(totalShares), (outcome) =>
outcome === betChoice
? Math.max(0, totalShares[outcome] - shares) ** 2
: totalShares[outcome] ** 2
)
)
return currentValue - postSaleValue
}
export function calculateDpmMoneyRatio(
contract: FullContract<DPM, any>,
bet: Bet,
shareValue: number
) {
const { totalShares, totalBets, pool } = contract
const { outcome, amount } = bet
const p = getDpmOutcomeProbability(totalShares, outcome)
const actual = _.sum(Object.values(pool)) - shareValue
const betAmount = p * amount
const expected =
_.sumBy(
Object.keys(totalBets),
(outcome) =>
getDpmOutcomeProbability(totalShares, outcome) *
(totalBets as { [outcome: string]: number })[outcome]
) - betAmount
if (actual <= 0 || expected <= 0) return 0
return actual / expected
}
export function calculateDpmShareValue(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { pool, totalShares } = contract
const { shares, outcome } = bet
const shareValue = calculateDpmRawShareValue(totalShares, shares, outcome)
const f = calculateDpmMoneyRatio(contract, bet, shareValue)
const myPool = pool[outcome]
const adjShareValue = Math.min(Math.min(1, f) * shareValue, myPool)
return adjShareValue
}
export function calculateDpmSaleAmount(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { amount } = bet
const winnings = calculateDpmShareValue(contract, bet)
return deductDpmFees(amount, winnings)
}
export function calculateDpmPayout(
contract: FullContract<DPM, any>,
bet: Bet,
outcome: string
) {
if (outcome === 'CANCEL') return calculateDpmCancelPayout(contract, bet)
if (outcome === 'MKT') return calculateMktDpmPayout(contract, bet)
return calculateStandardDpmPayout(contract, bet, outcome)
}
export function calculateDpmCancelPayout(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { totalBets, pool } = contract
const betTotal = _.sum(Object.values(totalBets))
const poolTotal = _.sum(Object.values(pool))
return (bet.amount / betTotal) * poolTotal
}
export function calculateStandardDpmPayout(
contract: FullContract<DPM, any>,
bet: Bet,
outcome: string
) {
const { amount, outcome: betOutcome, shares } = bet
if (betOutcome !== outcome) return 0
const { totalShares, phantomShares, pool } = contract
if (!totalShares[outcome]) return 0
const poolTotal = _.sum(Object.values(pool))
const total =
totalShares[outcome] - (phantomShares ? phantomShares[outcome] : 0)
const winnings = (shares / total) * poolTotal
// profit can be negative if using phantom shares
return amount + (1 - DPM_FEES) * Math.max(0, winnings - amount)
}
export function calculateDpmPayoutAfterCorrectBet(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { totalShares, pool, totalBets } = contract
const { shares, amount, outcome } = bet
const prevShares = totalShares[outcome] ?? 0
const prevPool = pool[outcome] ?? 0
const prevTotalBet = totalBets[outcome] ?? 0
const newContract = {
...contract,
totalShares: {
...totalShares,
[outcome]: prevShares + shares,
},
pool: {
...pool,
[outcome]: prevPool + amount,
},
totalBets: {
...totalBets,
[outcome]: prevTotalBet + amount,
},
}
return calculateStandardDpmPayout(newContract, bet, outcome)
}
function calculateMktDpmPayout(contract: FullContract<DPM, any>, bet: Bet) {
if (contract.outcomeType === 'BINARY')
return calculateBinaryMktDpmPayout(contract, bet)
const { totalShares, pool } = contract
const totalPool = _.sum(Object.values(pool))
const sharesSquareSum = _.sumBy(
Object.values(totalShares) as number[],
(shares) => shares ** 2
)
const weightedShareTotal = _.sumBy(Object.keys(totalShares), (outcome) => {
// Avoid O(n^2) by reusing sharesSquareSum for prob.
const shares = totalShares[outcome]
const prob = shares ** 2 / sharesSquareSum
return prob * shares
})
const { outcome, amount, shares } = bet
const betP = getDpmOutcomeProbability(totalShares, outcome)
const winnings = ((betP * shares) / weightedShareTotal) * totalPool
return deductDpmFees(amount, winnings)
}
function calculateBinaryMktDpmPayout(
contract: FullContract<DPM, Binary>,
bet: Bet
) {
const { resolutionProbability, totalShares, phantomShares } = contract
const p =
resolutionProbability !== undefined
? resolutionProbability
: getDpmProbability(totalShares)
const pool = contract.pool.YES + contract.pool.NO
const weightedShareTotal =
p * (totalShares.YES - (phantomShares?.YES ?? 0)) +
(1 - p) * (totalShares.NO - (phantomShares?.NO ?? 0))
const { outcome, amount, shares } = bet
const betP = outcome === 'YES' ? p : 1 - p
const winnings = ((betP * shares) / weightedShareTotal) * pool
return deductDpmFees(amount, winnings)
}
export function resolvedDpmPayout(contract: FullContract<DPM, any>, bet: Bet) {
if (contract.resolution)
return calculateDpmPayout(contract, bet, contract.resolution)
throw new Error('Contract was not resolved')
}
export const deductDpmFees = (betAmount: number, winnings: number) => {
return winnings > betAmount
? betAmount + (1 - DPM_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

@ -0,0 +1,41 @@
import { Bet } from './bet'
import { getProbability } from './calculate'
import { Binary, FixedPayouts, FullContract } from './contract'
export function calculateFixedPayout(
contract: FullContract<FixedPayouts, Binary>,
bet: Bet,
outcome: string
) {
if (outcome === 'CANCEL') return calculateFixedCancelPayout(bet)
if (outcome === 'MKT') return calculateFixedMktPayout(contract, bet)
return calculateStandardFixedPayout(bet, outcome)
}
export function calculateFixedCancelPayout(bet: Bet) {
return bet.amount
}
export function calculateStandardFixedPayout(bet: Bet, outcome: string) {
const { outcome: betOutcome, shares } = bet
if (betOutcome !== outcome) return 0
return shares
}
function calculateFixedMktPayout(
contract: FullContract<FixedPayouts, Binary>,
bet: Bet
) {
const { resolutionProbability } = contract
const p =
resolutionProbability !== undefined
? resolutionProbability
: getProbability(contract)
const { outcome, shares } = bet
const betP = outcome === 'YES' ? p : 1 - p
return betP * shares
}

View File

@ -1,254 +1,111 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { Contract } from './contract'
import { FEES } from './fees'
import {
calculateCpmmSale,
getCpmmProbability,
getCpmmOutcomeProbabilityAfterBet,
getCpmmProbabilityAfterSale,
calculateCpmmSharesAfterFee,
} from './calculate-cpmm'
import {
calculateDpmPayout,
calculateDpmPayoutAfterCorrectBet,
calculateDpmSaleAmount,
calculateDpmShares,
getDpmOutcomeProbability,
getDpmProbability,
getDpmOutcomeProbabilityAfterBet,
getDpmProbabilityAfterSale,
} from './calculate-dpm'
import { calculateFixedPayout } from './calculate-fixed-payouts'
import { Binary, Contract, CPMM, DPM, FullContract } from './contract'
export function getProbability(totalShares: { [outcome: string]: number }) {
// For binary contracts only.
return getOutcomeProbability(totalShares, 'YES')
export function getProbability(contract: FullContract<DPM | CPMM, Binary>) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbability(contract.pool, contract.p)
: getDpmProbability(contract.totalShares)
}
export function getOutcomeProbability(
totalShares: {
[outcome: string]: number
},
outcome: string
export function getInitialProbability(
contract: FullContract<DPM | CPMM, Binary>
) {
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
const shares = totalShares[outcome] ?? 0
return shares ** 2 / squareSum
return (
contract.initialProbability ??
(contract.mechanism === 'cpmm-1'
? getCpmmProbability(contract.pool, contract.p)
: getDpmProbability(contract.phantomShares ?? contract.totalShares))
)
}
export function getProbabilityAfterBet(
totalShares: {
[outcome: string]: number
},
export function getOutcomeProbability(contract: Contract, outcome: string) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbability(contract.pool, contract.p)
: getDpmOutcomeProbability(contract.totalShares, outcome)
}
export function getOutcomeProbabilityAfterBet(
contract: Contract,
outcome: string,
bet: number
) {
const shares = calculateShares(totalShares, bet, outcome)
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
return getOutcomeProbability(newTotalShares, outcome)
}
export function getProbabilityAfterSale(
totalShares: {
[outcome: string]: number
},
outcome: string,
shares: number
) {
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares - shares }
const predictionOutcome = outcome === 'NO' ? 'YES' : outcome
return getOutcomeProbability(newTotalShares, predictionOutcome)
return contract.mechanism === 'cpmm-1'
? getCpmmOutcomeProbabilityAfterBet(
contract as FullContract<CPMM, Binary>,
outcome,
bet
)
: getDpmOutcomeProbabilityAfterBet(contract.totalShares, outcome, bet)
}
export function calculateShares(
totalShares: {
[outcome: string]: number
},
contract: Contract,
bet: number,
betChoice: string
) {
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
const shares = totalShares[betChoice] ?? 0
const c = 2 * bet * Math.sqrt(squareSum)
return Math.sqrt(bet ** 2 + shares ** 2 + c) - shares
}
export function calculateRawShareValue(
totalShares: {
[outcome: string]: number
},
shares: number,
betChoice: string
) {
const currentValue = Math.sqrt(
_.sumBy(Object.values(totalShares), (shares) => shares ** 2)
)
const postSaleValue = Math.sqrt(
_.sumBy(Object.keys(totalShares), (outcome) =>
outcome === betChoice
? Math.max(0, totalShares[outcome] - shares) ** 2
: totalShares[outcome] ** 2
)
)
return currentValue - postSaleValue
}
export function calculateMoneyRatio(
contract: Contract,
bet: Bet,
shareValue: number
) {
const { totalShares, totalBets, pool } = contract
const { outcome, amount } = bet
const p = getOutcomeProbability(totalShares, outcome)
const actual = _.sum(Object.values(pool)) - shareValue
const betAmount = p * amount
const expected =
_.sumBy(
Object.keys(totalBets),
(outcome) =>
getOutcomeProbability(totalShares, outcome) *
(totalBets as { [outcome: string]: number })[outcome]
) - betAmount
if (actual <= 0 || expected <= 0) return 0
return actual / expected
}
export function calculateShareValue(contract: Contract, bet: Bet) {
const { pool, totalShares } = contract
const { shares, outcome } = bet
const shareValue = calculateRawShareValue(totalShares, shares, outcome)
const f = calculateMoneyRatio(contract, bet, shareValue)
const myPool = pool[outcome]
const adjShareValue = Math.min(Math.min(1, f) * shareValue, myPool)
return adjShareValue
return contract.mechanism === 'cpmm-1'
? calculateCpmmSharesAfterFee(
contract as FullContract<CPMM, Binary>,
bet,
betChoice
)
: calculateDpmShares(contract.totalShares, bet, betChoice)
}
export function calculateSaleAmount(contract: Contract, bet: Bet) {
const { amount } = bet
const winnings = calculateShareValue(contract, bet)
return deductFees(amount, winnings)
}
export function calculatePayout(contract: Contract, bet: Bet, outcome: string) {
if (outcome === 'CANCEL') return calculateCancelPayout(contract, bet)
if (outcome === 'MKT') return calculateMktPayout(contract, bet)
return calculateStandardPayout(contract, bet, outcome)
}
export function calculateCancelPayout(contract: Contract, bet: Bet) {
const { totalBets, pool } = contract
const betTotal = _.sum(Object.values(totalBets))
const poolTotal = _.sum(Object.values(pool))
return (bet.amount / betTotal) * poolTotal
}
export function calculateStandardPayout(
contract: Contract,
bet: Bet,
outcome: string
) {
const { amount, outcome: betOutcome, shares } = bet
if (betOutcome !== outcome) return 0
const { totalShares, phantomShares, pool } = contract
if (!totalShares[outcome]) return 0
const poolTotal = _.sum(Object.values(pool))
const total =
totalShares[outcome] - (phantomShares ? phantomShares[outcome] : 0)
const winnings = (shares / total) * poolTotal
// profit can be negative if using phantom shares
return amount + (1 - FEES) * Math.max(0, winnings - amount)
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
? calculateCpmmSale(contract, bet).saleValue
: calculateDpmSaleAmount(contract, bet)
}
export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) {
const { totalShares, pool, totalBets } = contract
const { shares, amount, outcome } = bet
const prevShares = totalShares[outcome] ?? 0
const prevPool = pool[outcome] ?? 0
const prevTotalBet = totalBets[outcome] ?? 0
const newContract = {
...contract,
totalShares: {
...totalShares,
[outcome]: prevShares + shares,
},
pool: {
...pool,
[outcome]: prevPool + amount,
},
totalBets: {
...totalBets,
[outcome]: prevTotalBet + amount,
},
}
return calculateStandardPayout(newContract, bet, outcome)
return contract.mechanism === 'cpmm-1'
? bet.shares
: calculateDpmPayoutAfterCorrectBet(contract, bet)
}
function calculateMktPayout(contract: Contract, bet: Bet) {
if (contract.outcomeType === 'BINARY')
return calculateBinaryMktPayout(contract, bet)
const { totalShares, pool } = contract
const totalPool = _.sum(Object.values(pool))
const sharesSquareSum = _.sumBy(
Object.values(totalShares),
(shares) => shares ** 2
)
const weightedShareTotal = _.sumBy(Object.keys(totalShares), (outcome) => {
// Avoid O(n^2) by reusing sharesSquareSum for prob.
const shares = totalShares[outcome]
const prob = shares ** 2 / sharesSquareSum
return prob * shares
})
const { outcome, amount, shares } = bet
const betP = getOutcomeProbability(totalShares, outcome)
const winnings = ((betP * shares) / weightedShareTotal) * totalPool
return deductFees(amount, winnings)
export function getProbabilityAfterSale(
contract: Contract,
outcome: string,
shares: number
) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbabilityAfterSale(
contract as FullContract<CPMM, Binary>,
{ shares, outcome } as Bet
)
: getDpmProbabilityAfterSale(contract.totalShares, outcome, shares)
}
function calculateBinaryMktPayout(contract: Contract, bet: Bet) {
const { resolutionProbability, totalShares, phantomShares } = contract
const p =
resolutionProbability !== undefined
? resolutionProbability
: getProbability(totalShares)
const pool = contract.pool.YES + contract.pool.NO
const weightedShareTotal =
p * (totalShares.YES - (phantomShares?.YES ?? 0)) +
(1 - p) * (totalShares.NO - (phantomShares?.NO ?? 0))
const { outcome, amount, shares } = bet
const betP = outcome === 'YES' ? p : 1 - p
const winnings = ((betP * shares) / weightedShareTotal) * pool
return deductFees(amount, winnings)
export function calculatePayout(contract: Contract, bet: Bet, outcome: string) {
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
? calculateFixedPayout(contract, bet, outcome)
: calculateDpmPayout(contract, bet, outcome)
}
export function resolvedPayout(contract: Contract, bet: Bet) {
if (contract.resolution)
return calculatePayout(contract, bet, contract.resolution)
throw new Error('Contract was not resolved')
}
const outcome = contract.resolution
if (!outcome) throw new Error('Contract not resolved')
export const deductFees = (betAmount: number, winnings: number) => {
return winnings > betAmount
? betAmount + (1 - FEES) * (winnings - betAmount)
: winnings
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
? calculateFixedPayout(contract, bet, outcome)
: calculateDpmPayout(contract, bet, outcome)
}

View File

@ -1,6 +1,10 @@
import { Answer } from './answer'
import { Fees } from './fees'
export type Contract = {
export type FullContract<
M extends DPM | CPMM,
T extends Binary | Multi | FreeResponse
> = {
id: string
slug: string // auto-generated; must be unique
@ -15,16 +19,6 @@ export type Contract = {
lowercaseTags: string[]
visibility: 'public' | 'unlisted'
outcomeType: 'BINARY' | 'MULTI' | 'FREE_RESPONSE'
multiOutcomes?: string[] // Used for outcomeType 'MULTI'.
answers?: Answer[] // Used for outcomeType 'FREE_RESPONSE'.
mechanism: 'dpm-2'
phantomShares?: { [outcome: string]: number }
pool: { [outcome: string]: number }
totalShares: { [outcome: string]: number }
totalBets: { [outcome: string]: number }
createdTime: number // Milliseconds since epoch
lastUpdatedTime: number // If the question or description was changed
closeTime?: number // When no more trading is allowed
@ -32,12 +26,52 @@ export type Contract = {
isResolved: boolean
resolutionTime?: number // When the contract creator resolved the market
resolution?: string
resolutionProbability?: number // Used for BINARY markets resolved to MKT
resolutions?: { [outcome: string]: number } // Used for outcomeType FREE_RESPONSE resolved to MKT
closeEmailsSent?: number
volume24Hours: number
volume7Days: number
collectedFees: Fees
} & M &
T
export type Contract = FullContract<DPM | CPMM, Binary | Multi | FreeResponse>
export type DPM = {
mechanism: 'dpm-2'
pool: { [outcome: string]: number }
phantomShares?: { [outcome: string]: number }
totalShares: { [outcome: string]: number }
totalBets: { [outcome: string]: number }
}
export type CPMM = {
mechanism: 'cpmm-1'
pool: { [outcome: string]: number }
p: number // probability constant in y^p * n^(1-p) = k
totalLiquidity: number // in M$
}
export type FixedPayouts = CPMM
export type Binary = {
outcomeType: 'BINARY'
initialProbability: number
resolutionProbability?: number // Used for BINARY markets resolved to MKT
}
export type Multi = {
outcomeType: 'MULTI'
multiOutcomes: string[] // Used for outcomeType 'MULTI'.
resolutions?: { [outcome: string]: number } // Used for PROB
}
export type FreeResponse = {
outcomeType: 'FREE_RESPONSE'
answers: Answer[] // Used for outcomeType 'FREE_RESPONSE'.
resolutions?: { [outcome: string]: number } // Used for PROB
}
export type outcomeType = 'BINARY' | 'MULTI' | 'FREE_RESPONSE'

View File

@ -1,4 +1,19 @@
export const PLATFORM_FEE = 0.01
export const CREATOR_FEE = 0.04
export const PLATFORM_FEE = 0.02
export const CREATOR_FEE = 0.08
export const LIQUIDITY_FEE = 0.08
export const FEES = PLATFORM_FEE + CREATOR_FEE
export const DPM_PLATFORM_FEE = 0.01
export const DPM_CREATOR_FEE = 0.04
export const DPM_FEES = DPM_PLATFORM_FEE + DPM_CREATOR_FEE
export type Fees = {
creatorFee: number
platformFee: number
liquidityFee: number
}
export const noFees: Fees = {
creatorFee: 0,
platformFee: 0,
liquidityFee: 0,
}

View File

@ -0,0 +1,13 @@
export type LiquidityProvision = {
id: string
userId: string
contractId: string
createdTime: number
isAnte?: boolean
amount: number // M$ quantity
pool: { [outcome: string]: number } // pool shares before provision
liquidity: number // change in constant k after provision
p: number // p constant after provision
}

View File

@ -1,19 +1,69 @@
import * as _ from 'lodash'
import { Bet, MAX_LOAN_PER_CONTRACT } from './bet'
import {
calculateShares,
getProbability,
getOutcomeProbability,
} from './calculate'
import { Contract } from './contract'
calculateDpmShares,
getDpmProbability,
getDpmOutcomeProbability,
} from './calculate-dpm'
import { calculateCpmmPurchase, getCpmmProbability } from './calculate-cpmm'
import {
Binary,
CPMM,
DPM,
FreeResponse,
FullContract,
Multi,
} from './contract'
import { User } from './user'
import { noFees } from './fees'
export const getNewBinaryBetInfo = (
export const getNewBinaryCpmmBetInfo = (
user: User,
outcome: 'YES' | 'NO',
amount: number,
contract: FullContract<CPMM, Binary>,
loanAmount: number,
newBetId: string
) => {
const { shares, newPool, newP, fees } = calculateCpmmPurchase(
contract,
amount,
outcome
)
const newBalance = user.balance - (amount - loanAmount)
const { pool, p, totalLiquidity } = contract
const probBefore = getCpmmProbability(pool, p)
const probAfter = getCpmmProbability(newPool, newP)
const newBet: Bet = {
id: newBetId,
userId: user.id,
contractId: contract.id,
amount,
shares,
outcome,
fees,
loanAmount,
probBefore,
probAfter,
createdTime: Date.now(),
}
const { liquidityFee } = fees
const newTotalLiquidity = (totalLiquidity ?? 0) + liquidityFee
return { newBet, newPool, newP, newBalance, newTotalLiquidity, fees }
}
export const getNewBinaryDpmBetInfo = (
user: User,
outcome: 'YES' | 'NO',
amount: number,
contract: FullContract<DPM, Binary>,
loanAmount: number,
contract: Contract,
newBetId: string
) => {
const { YES: yesPool, NO: noPool } = contract.pool
@ -23,7 +73,7 @@ export const getNewBinaryBetInfo = (
? { YES: yesPool + amount, NO: noPool }
: { YES: yesPool, NO: noPool + amount }
const shares = calculateShares(contract.totalShares, amount, outcome)
const shares = calculateDpmShares(contract.totalShares, amount, outcome)
const { YES: yesShares, NO: noShares } = contract.totalShares
@ -39,8 +89,8 @@ export const getNewBinaryBetInfo = (
? { YES: yesBets + amount, NO: noBets }
: { YES: yesBets, NO: noBets + amount }
const probBefore = getProbability(contract.totalShares)
const probAfter = getProbability(newTotalShares)
const probBefore = getDpmProbability(contract.totalShares)
const probAfter = getDpmProbability(newTotalShares)
const newBet: Bet = {
id: newBetId,
@ -53,6 +103,7 @@ export const getNewBinaryBetInfo = (
probBefore,
probAfter,
createdTime: Date.now(),
fees: noFees,
}
const newBalance = user.balance - (amount - loanAmount)
@ -64,8 +115,8 @@ export const getNewMultiBetInfo = (
user: User,
outcome: string,
amount: number,
contract: FullContract<DPM, Multi | FreeResponse>,
loanAmount: number,
contract: Contract,
newBetId: string
) => {
const { pool, totalShares, totalBets } = contract
@ -73,7 +124,7 @@ export const getNewMultiBetInfo = (
const prevOutcomePool = pool[outcome] ?? 0
const newPool = { ...pool, [outcome]: prevOutcomePool + amount }
const shares = calculateShares(contract.totalShares, amount, outcome)
const shares = calculateDpmShares(contract.totalShares, amount, outcome)
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
@ -81,8 +132,8 @@ export const getNewMultiBetInfo = (
const prevTotalBets = totalBets[outcome] ?? 0
const newTotalBets = { ...totalBets, [outcome]: prevTotalBets + amount }
const probBefore = getOutcomeProbability(totalShares, outcome)
const probAfter = getOutcomeProbability(newTotalShares, outcome)
const probBefore = getDpmOutcomeProbability(totalShares, outcome)
const probAfter = getDpmOutcomeProbability(newTotalShares, outcome)
const newBet: Bet = {
id: newBetId,
@ -95,6 +146,7 @@ export const getNewMultiBetInfo = (
probBefore,
probAfter,
createdTime: Date.now(),
fees: noFees,
}
const newBalance = user.balance - (amount - loanAmount)

View File

@ -1,8 +1,16 @@
import { calcStartPool } from './antes'
import { Contract, outcomeType } from './contract'
import { PHANTOM_ANTE } from './antes'
import {
Binary,
Contract,
CPMM,
DPM,
FreeResponse,
outcomeType,
} from './contract'
import { User } from './user'
import { parseTags } from './util/parse'
import { removeUndefinedProps } from './util/object'
import { calcDpmInitialPool } from './calculate-dpm'
export function getNewContract(
id: string,
@ -23,14 +31,12 @@ export function getNewContract(
const propsByOutcomeType =
outcomeType === 'BINARY'
? getBinaryProps(initialProb, ante)
? getBinaryCpmmProps(initialProb, ante) // getBinaryDpmProps(initialProb, ante)
: getFreeAnswerProps(ante)
const contract: Contract = removeUndefinedProps({
id,
slug,
mechanism: 'dpm-2',
outcomeType,
...propsByOutcomeType,
creatorId: creator.id,
@ -50,30 +56,61 @@ export function getNewContract(
volume24Hours: 0,
volume7Days: 0,
collectedFees: {
creatorFee: 0,
liquidityFee: 0,
platformFee: 0,
},
})
return contract
return contract as Contract
}
const getBinaryProps = (initialProb: number, ante: number) => {
const getBinaryDpmProps = (initialProb: number, ante: number) => {
const { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo } =
calcStartPool(initialProb, ante)
calcDpmInitialPool(initialProb, ante, PHANTOM_ANTE)
return {
const system: DPM & Binary = {
mechanism: 'dpm-2',
outcomeType: 'BINARY',
initialProbability: initialProb / 100,
phantomShares: { YES: phantomYes, NO: phantomNo },
pool: { YES: poolYes, NO: poolNo },
totalShares: { YES: sharesYes, NO: sharesNo },
totalBets: { YES: poolYes, NO: poolNo },
}
return system
}
const getBinaryCpmmProps = (initialProb: number, ante: number) => {
const pool = { YES: ante, NO: ante }
const p = initialProb / 100
const system: CPMM & Binary = {
mechanism: 'cpmm-1',
outcomeType: 'BINARY',
totalLiquidity: ante,
initialProbability: p,
p,
pool: pool,
}
return system
}
const getFreeAnswerProps = (ante: number) => {
return {
const system: DPM & FreeResponse = {
mechanism: 'dpm-2',
outcomeType: 'FREE_RESPONSE',
pool: { '0': ante },
totalShares: { '0': ante },
totalBets: { '0': ante },
answers: [],
}
return system
}
const getMultiProps = (

189
common/payouts-dpm.ts Normal file
View File

@ -0,0 +1,189 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { deductDpmFees, getDpmProbability } from './calculate-dpm'
import { DPM, FreeResponse, FullContract, Multi } from './contract'
import {
DPM_CREATOR_FEE,
DPM_FEES,
DPM_PLATFORM_FEE,
Fees,
noFees,
} from './fees'
import { addObjects } from './util/object'
export const getDpmCancelPayouts = (
contract: FullContract<DPM, any>,
bets: Bet[]
) => {
const { pool } = contract
const poolTotal = _.sum(Object.values(pool))
console.log('resolved N/A, pool M$', poolTotal)
const betSum = _.sumBy(bets, (b) => b.amount)
const payouts = bets.map((bet) => ({
userId: bet.userId,
payout: (bet.amount / betSum) * poolTotal,
}))
return [payouts, contract.collectedFees ?? noFees]
}
export const getDpmStandardPayouts = (
outcome: string,
contract: FullContract<DPM, any>,
bets: Bet[]
) => {
const winningBets = bets.filter((bet) => bet.outcome === outcome)
const poolTotal = _.sum(Object.values(contract.pool))
const totalShares = _.sumBy(winningBets, (b) => b.shares)
const payouts = winningBets.map(({ userId, amount, shares }) => {
const winnings = (shares / totalShares) * poolTotal
const profit = winnings - amount
// profit can be negative if using phantom shares
const payout = amount + (1 - DPM_FEES) * Math.max(0, profit)
return { userId, profit, payout }
})
const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit))
const creatorFee = DPM_CREATOR_FEE * profits
const platformFee = DPM_PLATFORM_FEE * profits
const finalFees: Fees = {
creatorFee,
platformFee,