numeric bets: store shares, bet amounts across buckets in each bet object
This commit is contained in:
parent
93bc27bc52
commit
e834cf2557
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: '',
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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[],
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue
Block a user