rename contract properties

This commit is contained in:
mantikoros 2021-12-17 16:15:09 -06:00
parent 022caa4407
commit 756f31b1b7
8 changed files with 127 additions and 115 deletions

View File

@ -43,7 +43,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
.collection(`contracts/${contractId}/bets`)
.doc()
const { newBet, newPot, newDpmWeights, newBalance } = getNewBetInfo(
const { newBet, newPool, newDpmWeights, newBalance } = getNewBetInfo(
user,
outcome,
amount,
@ -52,7 +52,10 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
)
transaction.create(newBetDoc, newBet)
transaction.update(contractDoc, { pot: newPot, dpmWeights: newDpmWeights })
transaction.update(contractDoc, {
pool: newPool,
dpmWeights: newDpmWeights,
})
transaction.update(userDoc, { balance: newBalance })
return { status: 'success' }
@ -69,35 +72,37 @@ const getNewBetInfo = (
contract: Contract,
newBetId: string
) => {
const { YES: yesPot, NO: noPot } = contract.pot
const { YES: yesPool, NO: noPool } = contract.pool
const newPot =
const newPool =
outcome === 'YES'
? { YES: yesPot + amount, NO: noPot }
: { YES: yesPot, NO: noPot + amount }
? { YES: yesPool + amount, NO: noPool }
: { YES: yesPool, NO: noPool + amount }
const dpmWeight =
outcome === 'YES'
? (amount * noPot ** 2) / (yesPot ** 2 + amount * yesPot)
: (amount * yesPot ** 2) / (noPot ** 2 + amount * noPot)
? (amount * noPool ** 2) / (yesPool ** 2 + amount * yesPool)
: (amount * yesPool ** 2) / (noPool ** 2 + amount * noPool)
const { YES: yesWeight, NO: noWeight } = contract.dpmWeights
|| { YES: 0, NO: 0 } // only nesc for old contracts
const { YES: yesWeight, NO: noWeight } = contract.dpmWeights || {
YES: 0,
NO: 0,
} // only nesc for old contracts
const newDpmWeights =
outcome === 'YES'
? { YES: yesWeight + dpmWeight, NO: noWeight }
: { YES: yesWeight, NO: noWeight + dpmWeight }
const probBefore = yesPot ** 2 / (yesPot ** 2 + noPot ** 2)
const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2)
const probAverage =
(amount +
noPot * Math.atan(yesPot / noPot) -
noPot * Math.atan((amount + yesPot) / noPot)) /
noPool * Math.atan(yesPool / noPool) -
noPool * Math.atan((amount + yesPool) / noPool)) /
amount
const probAfter = newPot.YES ** 2 / (newPot.YES ** 2 + newPot.NO ** 2)
const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2)
const newBet: Bet = {
id: newBetId,
@ -114,5 +119,5 @@ const getNewBetInfo = (
const newBalance = user.balance - amount
return { newBet, newPot, newDpmWeights, newBalance }
return { newBet, newPool, newDpmWeights, newBalance }
}

View File

@ -11,90 +11,98 @@ export const CREATOR_FEE = 0.01 // 1%
export const resolveMarket = functions
.runWith({ minInstances: 1 })
.https
.onCall(async (data: {
outcome: string
contractId: string
}, context) => {
const userId = context?.auth?.uid
if (!userId) return { status: 'error', message: 'Not authorized' }
.https.onCall(
async (
data: {
outcome: string
contractId: string
},
context
) => {
const userId = context?.auth?.uid
if (!userId) return { status: 'error', message: 'Not authorized' }
const { outcome, contractId } = data
const { outcome, contractId } = data
if (!['YES', 'NO', 'CANCEL'].includes(outcome))
return { status: 'error', message: 'Invalid outcome' }
if (!['YES', 'NO', 'CANCEL'].includes(outcome))
return { status: 'error', message: 'Invalid outcome' }
const contractDoc = firestore.doc(`contracts/${contractId}`)
const contractSnap = await contractDoc.get()
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as Contract
const contractDoc = firestore.doc(`contracts/${contractId}`)
const contractSnap = await contractDoc.get()
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as Contract
if (contract.creatorId !== userId)
return { status: 'error', message: 'User not creator of contract' }
if (contract.creatorId !== userId)
return { status: 'error', message: 'User not creator of contract' }
if (contract.resolution)
return { status: 'error', message: 'Contract already resolved' }
if (contract.resolution)
return { status: 'error', message: 'Contract already resolved' }
await contractDoc.update({
isResolved: true,
resolution: outcome,
resolutionTime: Date.now()
})
await contractDoc.update({
isResolved: true,
resolution: outcome,
resolutionTime: Date.now(),
})
console.log('contract ', contractId, 'resolved to:', outcome)
console.log('contract ', contractId, 'resolved to:', outcome)
const betsSnap = await firestore.collection(`contracts/${contractId}/bets`).get()
const bets = betsSnap.docs.map(doc => doc.data() as Bet)
const betsSnap = await firestore
.collection(`contracts/${contractId}/bets`)
.get()
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
const payouts = outcome === 'CANCEL'
? bets.map(bet => ({
userId: bet.userId,
payout: bet.amount
}))
const payouts =
outcome === 'CANCEL'
? bets.map((bet) => ({
userId: bet.userId,
payout: bet.amount,
}))
: getPayouts(outcome, contract, bets)
: getPayouts(outcome, contract, bets)
console.log('payouts:', payouts)
console.log('payouts:', payouts)
const groups = _.groupBy(payouts, (payout) => payout.userId)
const userPayouts = _.mapValues(groups, (group) =>
_.sumBy(group, (g) => g.payout)
)
const groups = _.groupBy(payouts, payout => payout.userId)
const userPayouts = _.mapValues(groups, group => _.sumBy(group, g => g.payout))
const payoutPromises = Object.entries(userPayouts).map(payUser)
const payoutPromises = Object
.entries(userPayouts)
.map(payUser)
return await Promise.all(payoutPromises)
.catch(e => ({ status: 'error', message: e }))
.then(() => ({ status: 'success' }))
})
return await Promise.all(payoutPromises)
.catch((e) => ({ status: 'error', message: e }))
.then(() => ({ status: 'success' }))
}
)
const firestore = admin.firestore()
const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => {
const [yesBets, noBets] = _.partition(bets, bet => bet.outcome === 'YES')
const [yesBets, noBets] = _.partition(bets, (bet) => bet.outcome === 'YES')
const [pot, winningBets] = outcome === 'YES'
? [contract.pot.NO - contract.seedAmounts.NO, yesBets]
: [contract.pot.YES - contract.seedAmounts.YES, noBets]
const [pool, winningBets] =
outcome === 'YES'
? [contract.pool.NO - contract.startPool.NO, yesBets]
: [contract.pool.YES - contract.startPool.YES, noBets]
const finalPot = (1 - PLATFORM_FEE - CREATOR_FEE) * pot
const creatorPayout = CREATOR_FEE * pot
console.log('final pot:', finalPot, 'creator fee:', creatorPayout)
const finalPool = (1 - PLATFORM_FEE - CREATOR_FEE) * pool
const creatorPayout = CREATOR_FEE * pool
console.log('final pool:', finalPool, 'creator fee:', creatorPayout)
const sumWeights = _.sumBy(winningBets, bet => bet.dpmWeight)
const sumWeights = _.sumBy(winningBets, (bet) => bet.dpmWeight)
const winnerPayouts = winningBets.map(bet => ({
const winnerPayouts = winningBets.map((bet) => ({
userId: bet.userId,
payout: bet.amount + (bet.dpmWeight / sumWeights * finalPot)
payout: bet.amount + (bet.dpmWeight / sumWeights) * finalPool,
}))
return winnerPayouts
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
return winnerPayouts.concat([
{ userId: contract.creatorId, payout: creatorPayout },
]) // add creator fee
}
const payUser = ([userId, payout]: [string, number]) => {
return firestore.runTransaction(async transaction => {
return firestore.runTransaction(async (transaction) => {
const userDoc = firestore.doc(`users/${userId}`)
const userSnap = await transaction.get(userDoc)
if (!userSnap.exists) return
@ -104,4 +112,3 @@ const payUser = ([userId, payout]: [string, number]) => {
transaction.update(userDoc, { balance: newUserBalance })
})
}

View File

@ -1,4 +1,3 @@
export type Contract = {
id: string // Chosen by creator; must be unique
creatorId: string
@ -6,11 +5,11 @@ export type Contract = {
question: string
description: string // More info about what the contract is about
outcomeType: 'BINARY' // | 'MULTI' | 'interval' | 'date'
// outcomes: ['YES', 'NO']
seedAmounts: { YES: number; NO: number }
pot: { YES: number; NO: number }
startPool: { YES: number; NO: number }
pool: { YES: number; NO: number }
dpmWeights: { YES: number; NO: number }
createdTime: number // Milliseconds since epoch
@ -18,6 +17,6 @@ export type Contract = {
closeTime?: number // When no more trading is allowed
isResolved: boolean
resolutionTime?: 10293849 // When the contract creator resolved the market; 0 if unresolved
resolutionTime?: number // When the contract creator resolved the market
resolution?: 'YES' | 'NO' | 'CANCEL' // Chosen by creator; must be one of outcomes
}

View File

@ -77,13 +77,13 @@ export function BetPanel(props: { contract: Contract; className?: string }) {
const betDisabled = isSubmitting || !betAmount || error
const initialProb = getProbability(contract.pot)
const initialProb = getProbability(contract.pool)
const resultProb = getProbabilityAfterBet(
contract.pot,
contract.pool,
betChoice,
betAmount ?? 0
)
const dpmWeight = getDpmWeight(contract.pot, betAmount ?? 0, betChoice)
const dpmWeight = getDpmWeight(contract.pool, betAmount ?? 0, betChoice)
const estimatedWinnings = Math.floor((betAmount ?? 0) + dpmWeight)
const estimatedReturn = betAmount

View File

@ -7,19 +7,19 @@ import { Contract } from '../lib/firebase/contracts'
export function ContractProbGraph(props: { contract: Contract }) {
const { contract } = props
const { id, seedAmounts, resolutionTime } = contract
const { id, startPool, resolutionTime } = contract
let bets = useBets(id)
if (bets === 'loading') bets = []
const seedProb =
seedAmounts.YES ** 2 / (seedAmounts.YES ** 2 + seedAmounts.NO ** 2)
const startProb =
startPool.YES ** 2 / (startPool.YES ** 2 + startPool.NO ** 2)
const times = [
contract.createdTime,
...bets.map((bet) => bet.createdTime),
].map((time) => new Date(time))
const probs = [seedProb, ...bets.map((bet) => bet.probAfter)]
const probs = [startProb, ...bets.map((bet) => bet.probAfter)]
const latestTime = dayjs(resolutionTime ? resolutionTime : Date.now())

View File

@ -3,35 +3,35 @@ import { Contract } from '../firebase/contracts'
const fees = 0.02
export function getProbability(pot: { YES: number; NO: number }) {
const [yesPot, noPot] = [pot.YES, pot.NO]
const numerator = Math.pow(yesPot, 2)
const denominator = Math.pow(yesPot, 2) + Math.pow(noPot, 2)
export function getProbability(pool: { YES: number; NO: number }) {
const [yesPool, noPool] = [pool.YES, pool.NO]
const numerator = Math.pow(yesPool, 2)
const denominator = Math.pow(yesPool, 2) + Math.pow(noPool, 2)
return numerator / denominator
}
export function getProbabilityAfterBet(
pot: { YES: number; NO: number },
pool: { YES: number; NO: number },
outcome: 'YES' | 'NO',
bet: number
) {
const [YES, NO] = [
pot.YES + (outcome === 'YES' ? bet : 0),
pot.NO + (outcome === 'NO' ? bet : 0),
pool.YES + (outcome === 'YES' ? bet : 0),
pool.NO + (outcome === 'NO' ? bet : 0),
]
return getProbability({ YES, NO })
}
export function getDpmWeight(
pot: { YES: number; NO: number },
pool: { YES: number; NO: number },
bet: number,
betChoice: 'YES' | 'NO'
) {
const [yesPot, noPot] = [pot.YES, pot.NO]
const [yesPool, noPool] = [pool.YES, pool.NO]
return betChoice === 'YES'
? (bet * Math.pow(noPot, 2)) / (Math.pow(yesPot, 2) + bet * yesPot)
: (bet * Math.pow(yesPot, 2)) / (Math.pow(noPot, 2) + bet * noPot)
? (bet * Math.pow(noPool, 2)) / (Math.pow(yesPool, 2) + bet * yesPool)
: (bet * Math.pow(yesPool, 2)) / (Math.pow(noPool, 2) + bet * noPool)
}
export function calculatePayout(
@ -44,18 +44,18 @@ export function calculatePayout(
if (outcome === 'CANCEL') return amount
if (betOutcome !== outcome) return 0
let { dpmWeights, pot, seedAmounts } = contract
let { dpmWeights, pool, startPool } = contract
// Fake data if not set.
if (!dpmWeights) dpmWeights = { YES: 100, NO: 100 }
// Fake data if not set.
if (!pot) pot = { YES: 100, NO: 100 }
if (!pool) pool = { YES: 100, NO: 100 }
const otherOutcome = outcome === 'YES' ? 'NO' : 'YES'
const potSize = pot[otherOutcome] - seedAmounts[otherOutcome]
const poolSize = pool[otherOutcome] - startPool[otherOutcome]
return (1 - fees) * (dpmWeight / dpmWeights[outcome]) * potSize + amount
return (1 - fees) * (dpmWeight / dpmWeights[outcome]) * poolSize + amount
}
export function resolvedPayout(contract: Contract, bet: Bet) {
if (contract.resolution)
@ -64,7 +64,7 @@ export function resolvedPayout(contract: Contract, bet: Bet) {
}
export function currentValue(contract: Contract, bet: Bet) {
const prob = getProbability(contract.pot)
const prob = getProbability(contract.pool)
const yesPayout = calculatePayout(contract, bet, 'YES')
const noPayout = calculatePayout(contract, bet, 'NO')

View File

@ -24,8 +24,9 @@ export type Contract = {
description: string // More info about what the contract is about
outcomeType: 'BINARY' // | 'MULTI' | 'interval' | 'date'
// outcomes: ['YES', 'NO']
seedAmounts: { YES: number; NO: number } // seedBets: [number, number]
pot: { YES: number; NO: number }
startPool: { YES: number; NO: number }
pool: { YES: number; NO: number }
dpmWeights: { YES: number; NO: number }
createdTime: number // Milliseconds since epoch
@ -33,7 +34,7 @@ export type Contract = {
closeTime?: number // When no more trading is allowed
isResolved: boolean
resolutionTime?: number // When the contract creator resolved the market; 0 if unresolved
resolutionTime?: number // When the contract creator resolved the market
resolution?: 'YES' | 'NO' | 'CANCEL' // Chosen by creator; must be one of outcomes
}
@ -45,9 +46,9 @@ export function path(contract: Contract) {
}
export function compute(contract: Contract) {
const { pot, seedAmounts, createdTime, resolutionTime, isResolved } = contract
const volume = pot.YES + pot.NO - seedAmounts.YES - seedAmounts.NO
const prob = pot.YES ** 2 / (pot.YES ** 2 + pot.NO ** 2)
const { pool, startPool, createdTime, resolutionTime, isResolved } = contract
const volume = pool.YES + pool.NO - startPool.YES - startPool.NO
const prob = pool.YES ** 2 / (pool.YES ** 2 + pool.NO ** 2)
const probPercent = Math.round(prob * 100) + '%'
const createdDate = dayjs(createdTime).format('MMM D')
const resolvedDate = isResolved

View File

@ -16,7 +16,7 @@ export async function createContract(
const contractId = preexistingContract ? slug + '-' + randomString() : slug
const { seedYes, seedNo } = calcSeedBets(initialProb)
const { startYes, startNo } = calcStartPool(initialProb)
const contract: Contract = {
id: contractId,
@ -28,8 +28,8 @@ export async function createContract(
question: question.trim(),
description: description.trim(),
seedAmounts: { YES: seedYes, NO: seedNo },
pot: { YES: seedYes, NO: seedNo },
startPool: { YES: startYes, NO: startNo },
pool: { YES: startYes, NO: startNo },
dpmWeights: { YES: 0, NO: 0 },
isResolved: false,
@ -43,15 +43,15 @@ export async function createContract(
return contract
}
export function calcSeedBets(initialProb: number, initialCapital = 100) {
export function calcStartPool(initialProb: number, initialCapital = 100) {
const p = initialProb / 100.0
const seedYes =
const startYes =
p === 0.5
? p * initialCapital
: -(initialCapital * (-p + Math.sqrt((-1 + p) * -p))) / (-1 + 2 * p)
const seedNo = initialCapital - seedYes
const startNo = initialCapital - startYes
return { seedYes, seedNo }
return { startYes, startNo }
}