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`) .collection(`contracts/${contractId}/bets`)
.doc() .doc()
const { newBet, newPot, newDpmWeights, newBalance } = getNewBetInfo( const { newBet, newPool, newDpmWeights, newBalance } = getNewBetInfo(
user, user,
outcome, outcome,
amount, amount,
@ -52,7 +52,10 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
) )
transaction.create(newBetDoc, newBet) transaction.create(newBetDoc, newBet)
transaction.update(contractDoc, { pot: newPot, dpmWeights: newDpmWeights }) transaction.update(contractDoc, {
pool: newPool,
dpmWeights: newDpmWeights,
})
transaction.update(userDoc, { balance: newBalance }) transaction.update(userDoc, { balance: newBalance })
return { status: 'success' } return { status: 'success' }
@ -69,35 +72,37 @@ const getNewBetInfo = (
contract: Contract, contract: Contract,
newBetId: string newBetId: string
) => { ) => {
const { YES: yesPot, NO: noPot } = contract.pot const { YES: yesPool, NO: noPool } = contract.pool
const newPot = const newPool =
outcome === 'YES' outcome === 'YES'
? { YES: yesPot + amount, NO: noPot } ? { YES: yesPool + amount, NO: noPool }
: { YES: yesPot, NO: noPot + amount } : { YES: yesPool, NO: noPool + amount }
const dpmWeight = const dpmWeight =
outcome === 'YES' outcome === 'YES'
? (amount * noPot ** 2) / (yesPot ** 2 + amount * yesPot) ? (amount * noPool ** 2) / (yesPool ** 2 + amount * yesPool)
: (amount * yesPot ** 2) / (noPot ** 2 + amount * noPot) : (amount * yesPool ** 2) / (noPool ** 2 + amount * noPool)
const { YES: yesWeight, NO: noWeight } = contract.dpmWeights const { YES: yesWeight, NO: noWeight } = contract.dpmWeights || {
|| { YES: 0, NO: 0 } // only nesc for old contracts YES: 0,
NO: 0,
} // only nesc for old contracts
const newDpmWeights = const newDpmWeights =
outcome === 'YES' outcome === 'YES'
? { YES: yesWeight + dpmWeight, NO: noWeight } ? { YES: yesWeight + dpmWeight, NO: noWeight }
: { YES: yesWeight, NO: noWeight + dpmWeight } : { YES: yesWeight, NO: noWeight + dpmWeight }
const probBefore = yesPot ** 2 / (yesPot ** 2 + noPot ** 2) const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2)
const probAverage = const probAverage =
(amount + (amount +
noPot * Math.atan(yesPot / noPot) - noPool * Math.atan(yesPool / noPool) -
noPot * Math.atan((amount + yesPot) / noPot)) / noPool * Math.atan((amount + yesPool) / noPool)) /
amount 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 = { const newBet: Bet = {
id: newBetId, id: newBetId,
@ -114,5 +119,5 @@ const getNewBetInfo = (
const newBalance = user.balance - amount 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 export const resolveMarket = functions
.runWith({ minInstances: 1 }) .runWith({ minInstances: 1 })
.https .https.onCall(
.onCall(async (data: { async (
outcome: string data: {
contractId: string outcome: string
}, context) => { contractId: string
const userId = context?.auth?.uid },
if (!userId) return { status: 'error', message: 'Not authorized' } 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)) if (!['YES', 'NO', 'CANCEL'].includes(outcome))
return { status: 'error', message: 'Invalid outcome' } return { status: 'error', message: 'Invalid outcome' }
const contractDoc = firestore.doc(`contracts/${contractId}`) const contractDoc = firestore.doc(`contracts/${contractId}`)
const contractSnap = await contractDoc.get() const contractSnap = await contractDoc.get()
if (!contractSnap.exists) if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' } return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as Contract const contract = contractSnap.data() as Contract
if (contract.creatorId !== userId) if (contract.creatorId !== userId)
return { status: 'error', message: 'User not creator of contract' } return { status: 'error', message: 'User not creator of contract' }
if (contract.resolution) if (contract.resolution)
return { status: 'error', message: 'Contract already resolved' } return { status: 'error', message: 'Contract already resolved' }
await contractDoc.update({ await contractDoc.update({
isResolved: true, isResolved: true,
resolution: outcome, resolution: outcome,
resolutionTime: Date.now() 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 betsSnap = await firestore
const bets = betsSnap.docs.map(doc => doc.data() as Bet) .collection(`contracts/${contractId}/bets`)
.get()
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
const payouts = outcome === 'CANCEL' const payouts =
? bets.map(bet => ({ outcome === 'CANCEL'
userId: bet.userId, ? bets.map((bet) => ({
payout: bet.amount 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 payoutPromises = Object.entries(userPayouts).map(payUser)
const userPayouts = _.mapValues(groups, group => _.sumBy(group, g => g.payout))
const payoutPromises = Object return await Promise.all(payoutPromises)
.entries(userPayouts) .catch((e) => ({ status: 'error', message: e }))
.map(payUser) .then(() => ({ status: 'success' }))
}
return await Promise.all(payoutPromises) )
.catch(e => ({ status: 'error', message: e }))
.then(() => ({ status: 'success' }))
})
const firestore = admin.firestore() const firestore = admin.firestore()
const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => { 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' const [pool, winningBets] =
? [contract.pot.NO - contract.seedAmounts.NO, yesBets] outcome === 'YES'
: [contract.pot.YES - contract.seedAmounts.YES, noBets] ? [contract.pool.NO - contract.startPool.NO, yesBets]
: [contract.pool.YES - contract.startPool.YES, noBets]
const finalPot = (1 - PLATFORM_FEE - CREATOR_FEE) * pot const finalPool = (1 - PLATFORM_FEE - CREATOR_FEE) * pool
const creatorPayout = CREATOR_FEE * pot const creatorPayout = CREATOR_FEE * pool
console.log('final pot:', finalPot, 'creator fee:', creatorPayout) 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, userId: bet.userId,
payout: bet.amount + (bet.dpmWeight / sumWeights * finalPot) payout: bet.amount + (bet.dpmWeight / sumWeights) * finalPool,
})) }))
return winnerPayouts return winnerPayouts.concat([
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee { userId: contract.creatorId, payout: creatorPayout },
]) // add creator fee
} }
const payUser = ([userId, payout]: [string, number]) => { const payUser = ([userId, payout]: [string, number]) => {
return firestore.runTransaction(async transaction => { return firestore.runTransaction(async (transaction) => {
const userDoc = firestore.doc(`users/${userId}`) const userDoc = firestore.doc(`users/${userId}`)
const userSnap = await transaction.get(userDoc) const userSnap = await transaction.get(userDoc)
if (!userSnap.exists) return if (!userSnap.exists) return
@ -104,4 +112,3 @@ const payUser = ([userId, payout]: [string, number]) => {
transaction.update(userDoc, { balance: newUserBalance }) transaction.update(userDoc, { balance: newUserBalance })
}) })
} }

View File

@ -1,4 +1,3 @@
export type Contract = { export type Contract = {
id: string // Chosen by creator; must be unique id: string // Chosen by creator; must be unique
creatorId: string creatorId: string
@ -6,11 +5,11 @@ export type Contract = {
question: string question: string
description: string // More info about what the contract is about description: string // More info about what the contract is about
outcomeType: 'BINARY' // | 'MULTI' | 'interval' | 'date' outcomeType: 'BINARY' // | 'MULTI' | 'interval' | 'date'
// outcomes: ['YES', 'NO'] // 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 } dpmWeights: { YES: number; NO: number }
createdTime: number // Milliseconds since epoch createdTime: number // Milliseconds since epoch
@ -18,6 +17,6 @@ export type Contract = {
closeTime?: number // When no more trading is allowed closeTime?: number // When no more trading is allowed
isResolved: boolean 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 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 betDisabled = isSubmitting || !betAmount || error
const initialProb = getProbability(contract.pot) const initialProb = getProbability(contract.pool)
const resultProb = getProbabilityAfterBet( const resultProb = getProbabilityAfterBet(
contract.pot, contract.pool,
betChoice, betChoice,
betAmount ?? 0 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 estimatedWinnings = Math.floor((betAmount ?? 0) + dpmWeight)
const estimatedReturn = betAmount const estimatedReturn = betAmount

View File

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

View File

@ -3,35 +3,35 @@ import { Contract } from '../firebase/contracts'
const fees = 0.02 const fees = 0.02
export function getProbability(pot: { YES: number; NO: number }) { export function getProbability(pool: { YES: number; NO: number }) {
const [yesPot, noPot] = [pot.YES, pot.NO] const [yesPool, noPool] = [pool.YES, pool.NO]
const numerator = Math.pow(yesPot, 2) const numerator = Math.pow(yesPool, 2)
const denominator = Math.pow(yesPot, 2) + Math.pow(noPot, 2) const denominator = Math.pow(yesPool, 2) + Math.pow(noPool, 2)
return numerator / denominator return numerator / denominator
} }
export function getProbabilityAfterBet( export function getProbabilityAfterBet(
pot: { YES: number; NO: number }, pool: { YES: number; NO: number },
outcome: 'YES' | 'NO', outcome: 'YES' | 'NO',
bet: number bet: number
) { ) {
const [YES, NO] = [ const [YES, NO] = [
pot.YES + (outcome === 'YES' ? bet : 0), pool.YES + (outcome === 'YES' ? bet : 0),
pot.NO + (outcome === 'NO' ? bet : 0), pool.NO + (outcome === 'NO' ? bet : 0),
] ]
return getProbability({ YES, NO }) return getProbability({ YES, NO })
} }
export function getDpmWeight( export function getDpmWeight(
pot: { YES: number; NO: number }, pool: { YES: number; NO: number },
bet: number, bet: number,
betChoice: 'YES' | 'NO' betChoice: 'YES' | 'NO'
) { ) {
const [yesPot, noPot] = [pot.YES, pot.NO] const [yesPool, noPool] = [pool.YES, pool.NO]
return betChoice === 'YES' return betChoice === 'YES'
? (bet * Math.pow(noPot, 2)) / (Math.pow(yesPot, 2) + bet * yesPot) ? (bet * Math.pow(noPool, 2)) / (Math.pow(yesPool, 2) + bet * yesPool)
: (bet * Math.pow(yesPot, 2)) / (Math.pow(noPot, 2) + bet * noPot) : (bet * Math.pow(yesPool, 2)) / (Math.pow(noPool, 2) + bet * noPool)
} }
export function calculatePayout( export function calculatePayout(
@ -44,18 +44,18 @@ export function calculatePayout(
if (outcome === 'CANCEL') return amount if (outcome === 'CANCEL') return amount
if (betOutcome !== outcome) return 0 if (betOutcome !== outcome) return 0
let { dpmWeights, pot, seedAmounts } = contract let { dpmWeights, pool, startPool } = contract
// Fake data if not set. // Fake data if not set.
if (!dpmWeights) dpmWeights = { YES: 100, NO: 100 } if (!dpmWeights) dpmWeights = { YES: 100, NO: 100 }
// Fake data if not set. // 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 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) { export function resolvedPayout(contract: Contract, bet: Bet) {
if (contract.resolution) if (contract.resolution)
@ -64,7 +64,7 @@ export function resolvedPayout(contract: Contract, bet: Bet) {
} }
export function currentValue(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 yesPayout = calculatePayout(contract, bet, 'YES')
const noPayout = calculatePayout(contract, bet, 'NO') 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 description: string // More info about what the contract is about
outcomeType: 'BINARY' // | 'MULTI' | 'interval' | 'date' outcomeType: 'BINARY' // | 'MULTI' | 'interval' | 'date'
// outcomes: ['YES', 'NO'] // 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 } dpmWeights: { YES: number; NO: number }
createdTime: number // Milliseconds since epoch createdTime: number // Milliseconds since epoch
@ -33,7 +34,7 @@ export type Contract = {
closeTime?: number // When no more trading is allowed closeTime?: number // When no more trading is allowed
isResolved: boolean 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 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) { export function compute(contract: Contract) {
const { pot, seedAmounts, createdTime, resolutionTime, isResolved } = contract const { pool, startPool, createdTime, resolutionTime, isResolved } = contract
const volume = pot.YES + pot.NO - seedAmounts.YES - seedAmounts.NO const volume = pool.YES + pool.NO - startPool.YES - startPool.NO
const prob = pot.YES ** 2 / (pot.YES ** 2 + pot.NO ** 2) const prob = pool.YES ** 2 / (pool.YES ** 2 + pool.NO ** 2)
const probPercent = Math.round(prob * 100) + '%' const probPercent = Math.round(prob * 100) + '%'
const createdDate = dayjs(createdTime).format('MMM D') const createdDate = dayjs(createdTime).format('MMM D')
const resolvedDate = isResolved const resolvedDate = isResolved

View File

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