Cfmm (#64)
* 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:
parent
bd62d8fbcd
commit
c183e00d47
|
@ -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
|
||||
|
|
|
@ -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
217
common/calculate-cpmm.ts
Normal 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
293
common/calculate-dpm.ts
Normal 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 }
|
||||
}
|
41
common/calculate-fixed-payouts.ts
Normal file
41
common/calculate-fixed-payouts.ts
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
13
common/liquidity-provision.ts
Normal file
13
common/liquidity-provision.ts
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
189
common/payouts-dpm.ts
Normal 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,
|
||||