diff --git a/functions/src/index.ts b/functions/src/index.ts index dfb09c89..ee8da84b 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -2,7 +2,7 @@ import * as admin from 'firebase-admin' admin.initializeApp() -export * from './keep-awake' +// export * from './keep-awake' export * from './place-bet' export * from './resolve-market' export * from './sell-bet' diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts index 9e773848..e6e4d25a 100644 --- a/functions/src/place-bet.ts +++ b/functions/src/place-bet.ts @@ -43,7 +43,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( .collection(`contracts/${contractId}/bets`) .doc() - const { newBet, newPool, newDpmWeights, newBalance } = getNewBetInfo( + const { newBet, newPool, newTotalShares, newBalance } = getNewBetInfo( user, outcome, amount, @@ -54,7 +54,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( transaction.create(newBetDoc, newBet) transaction.update(contractDoc, { pool: newPool, - dpmWeights: newDpmWeights, + totalShares: newTotalShares, }) transaction.update(userDoc, { balance: newBalance }) @@ -79,29 +79,19 @@ const getNewBetInfo = ( ? { YES: yesPool + amount, NO: noPool } : { YES: yesPool, NO: noPool + amount } - const dpmWeight = + const shares = outcome === 'YES' - ? (amount * noPool ** 2) / (yesPool ** 2 + amount * yesPool) - : (amount * yesPool ** 2) / (noPool ** 2 + amount * noPool) + ? amount + (amount * noPool ** 2) / (yesPool ** 2 + amount * yesPool) + : amount + (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: yesShares, NO: noShares } = contract.totalShares - const newDpmWeights = + const newTotalShares = outcome === 'YES' - ? { YES: yesWeight + dpmWeight, NO: noWeight } - : { YES: yesWeight, NO: noWeight + dpmWeight } + ? { YES: yesShares + shares, NO: noShares } + : { YES: yesShares, NO: noShares + shares } const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2) - - const probAverage = - (amount + - noPool * Math.atan(yesPool / noPool) - - noPool * Math.atan((amount + yesPool) / noPool)) / - amount - const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2) const newBet: Bet = { @@ -109,15 +99,14 @@ const getNewBetInfo = ( userId: user.id, contractId: contract.id, amount, - dpmWeight, + shares, outcome, probBefore, - probAverage, probAfter, createdTime: Date.now(), } const newBalance = user.balance - amount - return { newBet, newPool, newDpmWeights, newBalance } + return { newBet, newPool, newTotalShares, newBalance } } diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts index e20ca354..280af022 100644 --- a/functions/src/resolve-market.ts +++ b/functions/src/resolve-market.ts @@ -78,22 +78,27 @@ export const resolveMarket = functions const firestore = admin.firestore() const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => { - const [yesBets, noBets] = _.partition(bets, (bet) => bet.outcome === 'YES') + const openBets = bets.filter((b) => !b.isSold && !b.sale) + const [yesBets, noBets] = _.partition( + openBets, + (bet) => bet.outcome === 'YES' + ) - const [pool, winningBets] = + const startPool = contract.startPool.YES + contract.startPool.NO + const truePool = contract.pool.YES + contract.pool.NO - startPool + + const [totalShares, winningBets] = outcome === 'YES' - ? [contract.pool.NO - contract.startPool.NO, yesBets] - : [contract.pool.YES - contract.startPool.YES, noBets] + ? [contract.totalShares.YES, yesBets] + : [contract.totalShares.NO, noBets] - const finalPool = (1 - PLATFORM_FEE - CREATOR_FEE) * pool - const creatorPayout = CREATOR_FEE * pool + const finalPool = (1 - PLATFORM_FEE - CREATOR_FEE) * truePool + const creatorPayout = CREATOR_FEE * truePool console.log('final pool:', finalPool, 'creator fee:', creatorPayout) - const sumWeights = _.sumBy(winningBets, (bet) => bet.dpmWeight) - const winnerPayouts = winningBets.map((bet) => ({ userId: bet.userId, - payout: bet.amount + (bet.dpmWeight / sumWeights) * finalPool, + payout: (bet.shares / totalShares) * finalPool, })) return winnerPayouts.concat([ diff --git a/functions/src/sell-bet.ts b/functions/src/sell-bet.ts index a4d95d0c..06617e33 100644 --- a/functions/src/sell-bet.ts +++ b/functions/src/sell-bet.ts @@ -35,23 +35,17 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( const betDoc = firestore.doc(`contracts/${contractId}/bets/${betId}`) const betSnap = await transaction.get(betDoc) - if (!betSnap.exists) - return { status: 'error', message: 'Invalid bet' } + if (!betSnap.exists) return { status: 'error', message: 'Invalid bet' } const bet = betSnap.data() as Bet - if (bet.isSold) - return { status: 'error', message: 'Bet already sold' } + if (bet.isSold) return { status: 'error', message: 'Bet already sold' } const newBetDoc = firestore .collection(`contracts/${contractId}/bets`) .doc() - const { newBet, newPool, newDpmWeights, newBalance, creatorFee } = getSellBetInfo( - user, - bet, - contract, - newBetDoc.id - ) + const { newBet, newPool, newTotalShares, newBalance, creatorFee } = + getSellBetInfo(user, bet, contract, newBetDoc.id) const creatorDoc = firestore.doc(`users/${contract.creatorId}`) const creatorSnap = await transaction.get(creatorDoc) @@ -65,7 +59,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( transaction.create(newBetDoc, newBet) transaction.update(contractDoc, { pool: newPool, - dpmWeights: newDpmWeights, + totalShares: newTotalShares, }) transaction.update(userDoc, { balance: newBalance }) @@ -82,69 +76,86 @@ const getSellBetInfo = ( contract: Contract, newBetId: string ) => { - const { id: betId, amount, dpmWeight, outcome } = bet + const { id: betId, amount, shares, outcome } = bet const { YES: yesPool, NO: noPool } = contract.pool - const { YES: yesWeights, NO: noWeights } = contract.dpmWeights + const { YES: yesStart, NO: noStart } = contract.startPool + const { YES: yesShares, NO: noShares } = contract.totalShares - // average implied probability after selling bet position - const p = outcome === 'YES' - ? (amount + - noPool * Math.atan((yesPool - amount) / noPool) - - noPool * Math.atan(yesPool / noPool)) / - amount + const [y, n, s] = [yesPool, noPool, shares] - : yesPool * (Math.atan((amount - noPool) / yesPool) + Math.atan(noPool / yesPool)) / - amount - - - const [sellYesAmount, sellNoAmount] = outcome === 'YES' - ? [ - p * amount, - p * dpmWeight / yesWeights * noPool, - ] - : [ - p * dpmWeight / noWeights * yesPool, - p * amount, - ] - - const newPool = { YES: yesPool - sellYesAmount, NO: noPool - sellNoAmount } - - const newDpmWeights = + const shareValue = outcome === 'YES' - ? { YES: yesWeights - dpmWeight, NO: noWeights } - : { YES: yesWeights, NO: noWeights - dpmWeight } + ? // https://www.wolframalpha.com/input/?i=b+%2B+%28b+n%5E2%29%2F%28y+%28-b+%2B+y%29%29+%3D+c+solve+b + (n ** 2 + + s * y + + y ** 2 - + Math.sqrt( + n ** 4 + (s - y) ** 2 * y ** 2 + 2 * n ** 2 * y * (s + y) + )) / + (2 * y) + : (y ** 2 + + s * n + + n ** 2 - + Math.sqrt( + y ** 4 + (s - n) ** 2 * n ** 2 + 2 * y ** 2 * n * (s + n) + )) / + (2 * n) + + const startPool = yesStart + noStart + const pool = yesPool + noPool - startPool + + const f = outcome === 'YES' ? pool / yesShares : pool / noShares + + const myPool = outcome === 'YES' ? yesPool - yesStart : noPool - noStart + + const adjShareValue = Math.min(Math.min(1, f) * shareValue, myPool) + + const newPool = + outcome === 'YES' + ? { YES: yesPool - adjShareValue, NO: noPool } + : { YES: yesPool, NO: noPool - adjShareValue } + + const newTotalShares = + outcome === 'YES' + ? { YES: yesShares - shares, NO: noShares } + : { YES: yesShares, NO: noShares - shares } const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2) - const probAverage = p const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2) - const keep = 1 - CREATOR_FEE - PLATFORM_FEE + const creatorFee = CREATOR_FEE * adjShareValue + const saleAmount = (1 - CREATOR_FEE - PLATFORM_FEE) * adjShareValue - const [saleAmount, creatorFee] = outcome === 'YES' - ? [{ YES: sellYesAmount, NO: keep * sellNoAmount }, CREATOR_FEE * sellNoAmount] - : [{ YES: keep * sellYesAmount, NO: sellNoAmount }, CREATOR_FEE * sellYesAmount] - - console.log('SELL M$', amount, outcome, 'at', p, 'prob', 'for M$', saleAmount.YES + saleAmount.NO, 'creator fee: M$', creatorFee) + console.log( + 'SELL M$', + amount, + outcome, + 'for M$', + saleAmount, + 'M$/share:', + f, + 'creator fee: M$', + creatorFee + ) const newBet: Bet = { id: newBetId, userId: user.id, contractId: contract.id, - amount: -amount, - dpmWeight: -dpmWeight, + amount: -adjShareValue, + shares: -shares, outcome, probBefore, - probAverage, probAfter, createdTime: Date.now(), sale: { amount: saleAmount, - betId - } + betId, + }, } - const newBalance = user.balance + sellYesAmount + sellNoAmount + const newBalance = user.balance + saleAmount - return { newBet, newPool, newDpmWeights, newBalance, creatorFee } + return { newBet, newPool, newTotalShares, newBalance, creatorFee } } diff --git a/functions/src/types/bet.ts b/functions/src/types/bet.ts index 0c22ffc6..8b540165 100644 --- a/functions/src/types/bet.ts +++ b/functions/src/types/bet.ts @@ -5,18 +5,17 @@ export type Bet = { amount: number // bet size; negative if SELL bet outcome: 'YES' | 'NO' - dpmWeight: number // dynamic parimutuel pool weight; negative if SELL bet + shares: number // dynamic parimutuel pool weight; negative if SELL bet probBefore: number - probAverage: number probAfter: number sale?: { - amount: { YES: number, NO: number } // amount user makes from YES and NO pools from sale + amount: number // amount user makes from sale betId: string // id of bet being sold } - - isSold?: boolean // true if this BUY bet has been sold + + isSold?: boolean // true if this BUY bet has been sold createdTime: number -} \ No newline at end of file +} diff --git a/functions/src/types/contract.ts b/functions/src/types/contract.ts index 8278206b..5f354654 100644 --- a/functions/src/types/contract.ts +++ b/functions/src/types/contract.ts @@ -12,7 +12,7 @@ export type Contract = { startPool: { YES: number; NO: number } pool: { YES: number; NO: number } - dpmWeights: { YES: number; NO: number } + totalShares: { YES: number; NO: number } createdTime: number // Milliseconds since epoch lastUpdatedTime: number // If the question or description was changed diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index f0f34410..4f934290 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -12,7 +12,7 @@ import { formatMoney, formatPercent } from '../lib/util/format' import { Title } from './title' import { getProbability, - getDpmWeight, + calculateShares, getProbabilityAfterBet, } from '../lib/calculation/contract' import { firebaseLogin } from '../lib/firebase/users' @@ -84,9 +84,9 @@ export function BetPanel(props: { contract: Contract; className?: string }) { betChoice, betAmount ?? 0 ) - const dpmWeight = getDpmWeight(contract.pool, betAmount ?? 0, betChoice) + const shares = calculateShares(contract.pool, betAmount ?? 0, betChoice) - const estimatedWinnings = Math.floor((betAmount ?? 0) + dpmWeight) + const estimatedWinnings = Math.floor(shares) const estimatedReturn = betAmount ? (estimatedWinnings - betAmount) / betAmount : 0 diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx index 51cb603e..3a977363 100644 --- a/web/components/bets-list.tsx +++ b/web/components/bets-list.tsx @@ -257,7 +257,16 @@ export function ContractBetsTable(props: { function BetRow(props: { bet: Bet; contract: Contract }) { const { bet, contract } = props - const { amount, outcome, createdTime, probBefore, probAfter, dpmWeight, sale, isSold } = bet + const { + amount, + outcome, + createdTime, + probBefore, + probAfter, + shares, + sale, + isSold, + } = bet const { isResolved } = contract return ( @@ -270,7 +279,7 @@ function BetRow(props: { bet: Bet; contract: Contract }) { {formatPercent(probBefore)} → {formatPercent(probAfter)} - {!isResolved && {formatMoney(amount + dpmWeight)}} + {!isResolved && {formatMoney(shares)}} {formatMoney( isResolved @@ -279,14 +288,19 @@ function BetRow(props: { bet: Bet; contract: Contract }) { )} - {!isResolved && !sale && !isSold && + {!isResolved && !sale && !isSold && ( - + - } + )} ) } diff --git a/web/components/contract-overview.tsx b/web/components/contract-overview.tsx index ad105c9d..aa8dfe6c 100644 --- a/web/components/contract-overview.tsx +++ b/web/components/contract-overview.tsx @@ -90,7 +90,7 @@ export const ContractOverview = (props: { }) => { const { contract, className } = props const { resolution, creatorId } = contract - const { probPercent, volume } = compute(contract) + const { probPercent, truePool } = compute(contract) const user = useUser() const isCreator = user?.id === creatorId @@ -140,7 +140,7 @@ export const ContractOverview = (props: { {/* Show a delete button for contracts without any trading */} - {isCreator && volume === 0 && ( + {isCreator && truePool === 0 && ( <>