From e834cf255713717d82a440cc6f9c9448d25edf49 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Mon, 16 May 2022 17:25:47 -0400 Subject: [PATCH] numeric bets: store shares, bet amounts across buckets in each bet object --- common/antes.ts | 46 ++++++++------ common/bet.ts | 5 ++ common/calculate.ts | 27 --------- common/new-bet.ts | 48 ++++++++------- common/payouts-dpm.ts | 60 ++++++++++++++++++- common/payouts.ts | 6 +- functions/src/create-contract.ts | 25 ++++---- functions/src/place-bet.ts | 2 +- web/components/contract/contract-overview.tsx | 5 ++ web/components/numeric-bet-panel.tsx | 5 +- 10 files changed, 141 insertions(+), 88 deletions(-) diff --git a/common/antes.ts b/common/antes.ts index 5ff92ce4..6ffc08b4 100644 --- a/common/antes.ts +++ b/common/antes.ts @@ -1,4 +1,4 @@ -import { Bet } from './bet' +import { Bet, NumericBet } from './bet' import { getDpmProbability } from './calculate-dpm' import { Binary, @@ -115,30 +115,40 @@ export function getFreeAnswerAnte( return anteBet } -export function getNumericAntes( +export function getNumericAnte( creator: User, contract: FullContract, - ante: number + ante: number, + newBetId: string ) { const { bucketCount, createdTime } = contract const betAnte = ante / bucketCount const betShares = Math.sqrt(ante ** 2 / bucketCount) - return _.range(0, bucketCount).map((i) => { - const anteBet: Omit = { - userId: creator.id, - contractId: contract.id, - amount: betAnte, - shares: betShares, - outcome: i.toString(), - probBefore: 0, - probAfter: 1 / bucketCount, - createdTime, - isAnte: true, - fees: noFees, - } + const allOutcomeShares = Object.fromEntries( + _.range(0, bucketCount).map((_, i) => [i, betShares]) + ) - return anteBet - }) + const allBetAmounts = Object.fromEntries( + _.range(0, bucketCount).map((_, i) => [i, betAnte]) + ) + + const anteBet: NumericBet = { + id: newBetId, + userId: creator.id, + contractId: contract.id, + amount: betAnte, + allBetAmounts, + outcome: '0', + shares: betShares, + allOutcomeShares, + probBefore: 0, + probAfter: 1 / bucketCount, + createdTime, + isAnte: true, + fees: noFees, + } + + return anteBet } diff --git a/common/bet.ts b/common/bet.ts index 3ef71ed6..11942b42 100644 --- a/common/bet.ts +++ b/common/bet.ts @@ -29,4 +29,9 @@ export type Bet = { createdTime: number } +export type NumericBet = Bet & { + allOutcomeShares: { [outcome: string]: number } + allBetAmounts: { [outcome: string]: number } +} + export const MAX_LOAN_PER_CONTRACT = 20 diff --git a/common/calculate.ts b/common/calculate.ts index 4c11a660..06820edc 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -161,7 +161,6 @@ export function getContractBetMetrics(contract: Contract, yourBets: Bet[]) { return { invested: Math.max(0, currentInvested), - currentInvested, payout, netPayout, profit, @@ -190,29 +189,3 @@ export function getTopAnswer(contract: FreeResponseContract) { ) return top?.answer } - -export function hasUserHitManaLimit( - contract: FreeResponseContract, - bets: Bet[], - amount: number -) { - const { manaLimitPerUser } = contract - if (manaLimitPerUser) { - const contractMetrics = getContractBetMetrics(contract, bets) - const currentInvested = contractMetrics.currentInvested - console.log('user current invested amount', currentInvested) - console.log('mana limit:', manaLimitPerUser) - - if (currentInvested + amount > manaLimitPerUser) { - const manaAllowed = manaLimitPerUser - currentInvested - return { - status: 'error', - message: `Market bet cap is M$${manaLimitPerUser}, you've M$${manaAllowed} left`, - } - } - } - return { - status: 'success', - message: '', - } -} diff --git a/common/new-bet.ts b/common/new-bet.ts index 33acd2dc..163290bb 100644 --- a/common/new-bet.ts +++ b/common/new-bet.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash' -import { Bet, MAX_LOAN_PER_CONTRACT } from './bet' +import { Bet, MAX_LOAN_PER_CONTRACT, NumericBet } from './bet' import { calculateDpmShares, getDpmProbability, @@ -162,42 +162,48 @@ export const getNumericBetsInfo = ( user: User, outcome: string, amount: number, - contract: NumericContract + contract: NumericContract, + newBetId: string ) => { const { pool, totalShares, totalBets } = contract const bets = getNumericBets(contract, outcome, amount) - const newPool = addObjects(pool, Object.fromEntries(bets)) + const allBetAmounts = Object.fromEntries(bets) + + const newPool = addObjects(pool, allBetAmounts) const { shares, totalShares: newTotalShares } = calculateNumericDpmShares( contract.totalShares, bets ) - const newTotalBets = addObjects(totalBets, Object.fromEntries(bets)) + const newTotalBets = addObjects(totalBets, allBetAmounts) - const newBets = bets.map(([outcome, amount], i) => { - const probBefore = getDpmOutcomeProbability(totalShares, outcome) - const probAfter = getDpmOutcomeProbability(newTotalShares, outcome) + const allOutcomeShares = Object.fromEntries( + bets.map(([outcome], i) => [outcome, shares[i]]) + ) - const newBet: Omit = { - userId: user.id, - contractId: contract.id, - amount, - shares: shares[i], - outcome, - probBefore, - probAfter, - createdTime: Date.now(), - fees: noFees, - } + const probBefore = getDpmOutcomeProbability(totalShares, outcome) + const probAfter = getDpmOutcomeProbability(newTotalShares, outcome) - return newBet - }) + const newBet: NumericBet = { + id: newBetId, + userId: user.id, + contractId: contract.id, + amount, + allBetAmounts, + shares: shares.find((s, i) => bets[i][0] === outcome) ?? 0, + allOutcomeShares, + outcome, + probBefore, + probAfter, + createdTime: Date.now(), + fees: noFees, + } const newBalance = user.balance - amount - return { newBets, newPool, newTotalShares, newTotalBets, newBalance } + return { newBet, newPool, newTotalShares, newTotalBets, newBalance } } export const getLoanAmount = (yourBets: Bet[], newBetAmount: number) => { diff --git a/common/payouts-dpm.ts b/common/payouts-dpm.ts index 64c7f90e..50e0b14c 100644 --- a/common/payouts-dpm.ts +++ b/common/payouts-dpm.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash' -import { Bet } from './bet' +import { Bet, NumericBet } from './bet' import { deductDpmFees, getDpmProbability } from './calculate-dpm' import { DPM, FreeResponse, FullContract, Multi } from './contract' import { @@ -88,6 +88,64 @@ export const getDpmStandardPayouts = ( } } +export const getNumericDpmPayouts = ( + outcome: string, + contract: FullContract, + bets: NumericBet[] +) => { + const totalShares = _.sumBy(bets, (bet) => bet.allOutcomeShares[outcome] ?? 0) + const winningBets = bets.filter((bet) => !!bet.allOutcomeShares[outcome]) + + const poolTotal = _.sum(Object.values(contract.pool)) + + const payouts = winningBets.map( + ({ userId, allBetAmounts, allOutcomeShares }) => { + const shares = allOutcomeShares[outcome] ?? 0 + const winnings = (shares / totalShares) * poolTotal + + const amount = allBetAmounts[outcome] ?? 0 + 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, + liquidityFee: 0, + } + + const collectedFees = addObjects( + finalFees, + contract.collectedFees ?? {} + ) + + console.log( + 'resolved numeric bucket: ', + outcome, + 'pool', + poolTotal, + 'profits', + profits, + 'creator fee', + creatorFee + ) + + return { + payouts: payouts.map(({ userId, payout }) => ({ userId, payout })), + creatorPayout: creatorFee, + liquidityPayouts: [], + collectedFees, + } +} + export const getDpmMktPayouts = ( contract: FullContract, bets: Bet[], diff --git a/common/payouts.ts b/common/payouts.ts index 33f58120..aa1bbeb8 100644 --- a/common/payouts.ts +++ b/common/payouts.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash' -import { Bet } from './bet' +import { Bet, NumericBet } from './bet' import { Binary, Contract, @@ -16,6 +16,7 @@ import { getDpmCancelPayouts, getDpmMktPayouts, getDpmStandardPayouts, + getNumericDpmPayouts, getPayoutsMultiOutcome, } from './payouts-dpm' import { @@ -131,6 +132,9 @@ export const getDpmPayouts = ( return getDpmCancelPayouts(contract, openBets) default: + if (contract.outcomeType === 'NUMERIC') + return getNumericDpmPayouts(outcome, contract, openBets as NumericBet[]) + // Outcome is a free response answer id. return getDpmStandardPayouts(outcome, contract, openBets) } diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index 379428fa..209333c1 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -24,7 +24,7 @@ import { getAnteBets, getCpmmInitialLiquidity, getFreeAnswerAnte, - getNumericAntes, + getNumericAnte, HOUSE_LIQUIDITY_PROVIDER_ID, MINIMUM_ANTE, } from 'common/antes' @@ -207,26 +207,21 @@ export const createContract = functions contract as FullContract, anteBetDoc.id ) + await anteBetDoc.set(anteBet) } else if (outcomeType === 'NUMERIC') { - const antes = getNumericAntes( + const anteBetDoc = firestore + .collection(`contracts/${contract.id}/bets`) + .doc() + + const anteBet = getNumericAnte( creator, contract as FullContract, - ante + ante, + anteBetDoc.id ) - await Promise.all( - antes.map(async (ante) => { - const anteBetDoc = firestore - .collection(`contracts/${contract.id}/bets`) - .doc() - - await anteBetDoc.set({ - id: anteBetDoc.id, - ...ante, - }) - }) - ) + await anteBetDoc.set(anteBet) } } diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts index 1a850c74..c6772f3d 100644 --- a/functions/src/place-bet.ts +++ b/functions/src/place-bet.ts @@ -113,7 +113,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( newBetDoc.id ) as any) : outcomeType === 'NUMERIC' && mechanism === 'dpm-2' - ? getNumericBetsInfo(user, outcome, amount, contract) + ? getNumericBetsInfo(user, outcome, amount, contract, newBetDoc.id) : getNewMultiBetInfo( user, outcome, diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx index 05ce22b2..5b2ce6b7 100644 --- a/web/components/contract/contract-overview.tsx +++ b/web/components/contract/contract-overview.tsx @@ -10,6 +10,7 @@ import clsx from 'clsx' import { FreeResponseResolutionOrChance, BinaryResolutionOrChance, + NumericResolution, } from './contract-card' import { Bet } from 'common/bet' import { Comment } from 'common/comment' @@ -72,6 +73,10 @@ export const ContractOverview = (props: { ) )} + {outcomeType === 'NUMERIC' && resolution && ( + + )} +