numeric bets: store shares, bet amounts across buckets in each bet object

This commit is contained in:
mantikoros 2022-05-16 17:25:47 -04:00
parent 93bc27bc52
commit e834cf2557
10 changed files with 141 additions and 88 deletions

View File

@ -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<DPM, Numeric>,
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<Bet, 'id'> = {
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
}

View File

@ -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

View File

@ -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: '',
}
}

View File

@ -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<Bet, 'id'> = {
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) => {

View File

@ -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<DPM, any>,
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<Fees>(
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<DPM, any>,
bets: Bet[],

View File

@ -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)
}

View File

@ -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<DPM, FreeResponse>,
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<DPM, Numeric>,
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)
}
}

View File

@ -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,

View File

@ -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 && (
<NumericResolution contract={contract} />
)}
<ContractDetails
contract={contract}
bets={bets}

View File

@ -4,12 +4,9 @@ import { useState } from 'react'
import { Bet } from '../../common/bet'
import {
getOutcomeProbabilityAfterBet,
calculateShares,
calculatePayoutAfterCorrectBet,
getOutcomeProbability,
} from '../../common/calculate'
import { getNumericBets } from '../../common/calculate-dpm'
import { NumericContract } from '../../common/contract'
import { formatPercent, formatMoney } from '../../common/util/format'
import { useUser } from '../hooks/use-user'
@ -96,7 +93,7 @@ function NumericBuyPanel(props: {
const betDisabled = isSubmitting || !betAmount || !bucketChoice || error
const { newBet, newPool, newTotalShares, newTotalBets } = getNumericBetsInfo(
{ id: 'dummy', balance: 0 } as User,
{ id: 'dummy', balance: 0 } as User, // a little hackish
bucketChoice ?? 'NaN',
betAmount ?? 0,
contract,