rename contract properties
This commit is contained in:
parent
022caa4407
commit
756f31b1b7
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user