Remove Contract and Bet type params. Use string type for outcomes.

This commit is contained in:
James Grugett 2022-02-16 23:05:45 -06:00
parent 18376f8a3f
commit f161661e7e
17 changed files with 56 additions and 91 deletions

View File

@ -64,7 +64,7 @@ export function getAnteBets(
export function getFreeAnswerAnte( export function getFreeAnswerAnte(
creator: User, creator: User,
contract: Contract<'MULTI'>, contract: Contract,
anteBetId: string anteBetId: string
) { ) {
const { totalBets, totalShares } = contract const { totalBets, totalShares } = contract
@ -73,7 +73,7 @@ export function getFreeAnswerAnte(
const { createdTime } = contract const { createdTime } = contract
const anteBet: Bet<'MULTI'> = { const anteBet: Bet = {
id: anteBetId, id: anteBetId,
userId: creator.id, userId: creator.id,
contractId: contract.id, contractId: contract.id,

View File

@ -1,13 +1,10 @@
export type Bet<outcomeType extends 'BINARY' | 'MULTI' = 'BINARY'> = { export type Bet = {
id: string id: string
userId: string userId: string
contractId: string contractId: string
amount: number // bet size; negative if SELL bet amount: number // bet size; negative if SELL bet
outcome: { outcome: string
BINARY: 'YES' | 'NO'
MULTI: string
}[outcomeType]
shares: number // dynamic parimutuel pool weight; negative if SELL bet shares: number // dynamic parimutuel pool weight; negative if SELL bet
probBefore: number probBefore: number

View File

@ -3,7 +3,8 @@ import { Bet } from './bet'
import { Contract } from './contract' import { Contract } from './contract'
import { FEES } from './fees' import { FEES } from './fees'
export function getProbability(totalShares: { YES: number; NO: number }) { export function getProbability(totalShares: { [outcome: string]: number }) {
// For binary contracts only.
return getOutcomeProbability(totalShares, 'YES') return getOutcomeProbability(totalShares, 'YES')
} }
@ -70,9 +71,9 @@ export function calculateRawShareValue(
return currentValue - postSaleValue return currentValue - postSaleValue
} }
export function calculateMoneyRatio<T extends 'BINARY' | 'MULTI'>( export function calculateMoneyRatio(
contract: Contract<T>, contract: Contract,
bet: Bet<T>, bet: Bet,
shareValue: number shareValue: number
) { ) {
const { totalShares, totalBets, pool } = contract const { totalShares, totalBets, pool } = contract
@ -115,11 +116,7 @@ export function calculateSaleAmount(contract: Contract, bet: Bet) {
return deductFees(amount, winnings) return deductFees(amount, winnings)
} }
export function calculatePayout( export function calculatePayout(contract: Contract, bet: Bet, outcome: string) {
contract: Contract,
bet: Bet,
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
) {
if (outcome === 'CANCEL') return calculateCancelPayout(contract, bet) if (outcome === 'CANCEL') return calculateCancelPayout(contract, bet)
if (outcome === 'MKT') return calculateMktPayout(contract, bet) if (outcome === 'MKT') return calculateMktPayout(contract, bet)
@ -182,7 +179,7 @@ function calculateMktPayout(contract: Contract, bet: Bet) {
if (contract.outcomeType === 'BINARY') if (contract.outcomeType === 'BINARY')
return calculateBinaryMktPayout(contract, bet) return calculateBinaryMktPayout(contract, bet)
const { totalShares, pool } = contract as any as Contract<'MULTI'> const { totalShares, pool } = contract
const totalPool = _.sum(Object.values(pool)) const totalPool = _.sum(Object.values(pool))
const sharesSquareSum = _.sumBy( const sharesSquareSum = _.sumBy(
@ -206,16 +203,17 @@ function calculateMktPayout(contract: Contract, bet: Bet) {
} }
function calculateBinaryMktPayout(contract: Contract, bet: Bet) { function calculateBinaryMktPayout(contract: Contract, bet: Bet) {
const { resolutionProbability, totalShares, phantomShares } = contract
const p = const p =
contract.resolutionProbability !== undefined resolutionProbability !== undefined
? contract.resolutionProbability ? resolutionProbability
: getProbability(contract.totalShares) : getProbability(totalShares)
const pool = contract.pool.YES + contract.pool.NO const pool = contract.pool.YES + contract.pool.NO
const weightedShareTotal = const weightedShareTotal =
p * (contract.totalShares.YES - contract.phantomShares.YES) + p * (totalShares.YES - (phantomShares?.YES ?? 0)) +
(1 - p) * (contract.totalShares.NO - contract.phantomShares.NO) (1 - p) * (totalShares.NO - (phantomShares?.NO ?? 0))
const { outcome, amount, shares } = bet const { outcome, amount, shares } = bet

View File

@ -1,4 +1,4 @@
export type Contract<outcomeType extends 'BINARY' | 'MULTI' = 'BINARY'> = { export type Contract = {
id: string id: string
slug: string // auto-generated; must be unique slug: string // auto-generated; must be unique
@ -13,29 +13,14 @@ export type Contract<outcomeType extends 'BINARY' | 'MULTI' = 'BINARY'> = {
lowercaseTags: string[] lowercaseTags: string[]
visibility: 'public' | 'unlisted' visibility: 'public' | 'unlisted'
outcomeType: outcomeType outcomeType: 'BINARY' | 'MULTI'
outcomes: { outcomes?: 'FREE_ANSWER' | string[]
BINARY: undefined
MULTI: 'FREE_ANSWER' | string[]
}[outcomeType]
mechanism: 'dpm-2' mechanism: 'dpm-2'
phantomShares: { phantomShares?: { [outcome: string]: number }
BINARY: { YES: number; NO: number } pool: { [outcome: string]: number }
MULTI: undefined totalShares: { [outcome: string]: number }
}[outcomeType] totalBets: { [outcome: string]: number }
pool: {
BINARY: { YES: number; NO: number }
MULTI: { [answerId: string]: number }
}[outcomeType]
totalShares: {
BINARY: { YES: number; NO: number }
MULTI: { [answerId: string]: number }
}[outcomeType]
totalBets: {
BINARY: { YES: number; NO: number }
MULTI: { [answerId: string]: number }
}[outcomeType]
createdTime: number // Milliseconds since epoch createdTime: number // Milliseconds since epoch
lastUpdatedTime: number // If the question or description was changed lastUpdatedTime: number // If the question or description was changed
@ -43,14 +28,9 @@ export type Contract<outcomeType extends 'BINARY' | 'MULTI' = 'BINARY'> = {
isResolved: boolean isResolved: boolean
resolutionTime?: number // When the contract creator resolved the market resolutionTime?: number // When the contract creator resolved the market
resolution?: { resolution?: string
BINARY: outcome
MULTI: string
}[outcomeType]
resolutionProbability?: number resolutionProbability?: number
volume24Hours: number volume24Hours: number
volume7Days: number volume7Days: number
} }
export type outcome = 'YES' | 'NO' | 'CANCEL' | 'MKT'

View File

@ -61,7 +61,7 @@ export const getNewMultiBetInfo = (
user: User, user: User,
outcome: string, outcome: string,
amount: number, amount: number,
contract: Contract<'MULTI'>, contract: Contract,
newBetId: string newBetId: string
) => { ) => {
const { pool, totalShares, totalBets } = contract const { pool, totalShares, totalBets } = contract
@ -80,7 +80,7 @@ export const getNewMultiBetInfo = (
const probBefore = getOutcomeProbability(totalShares, outcome) const probBefore = getOutcomeProbability(totalShares, outcome)
const probAfter = getOutcomeProbability(newTotalShares, outcome) const probAfter = getOutcomeProbability(newTotalShares, outcome)
const newBet: Bet<'MULTI'> = { const newBet: Bet = {
id: newBetId, id: newBetId,
userId: user.id, userId: user.id,
contractId: contract.id, contractId: contract.id,

View File

@ -26,7 +26,7 @@ export function getNewContract(
? getBinaryProps(initialProb, ante) ? getBinaryProps(initialProb, ante)
: getFreeAnswerProps(ante) : getFreeAnswerProps(ante)
const contract: Contract<'BINARY' | 'MULTI'> = removeUndefinedProps({ const contract: Contract = removeUndefinedProps({
id, id,
slug, slug,
mechanism: 'dpm-2', mechanism: 'dpm-2',

View File

@ -2,7 +2,7 @@ import * as _ from 'lodash'
import { Bet } from './bet' import { Bet } from './bet'
import { deductFees, getProbability } from './calculate' import { deductFees, getProbability } from './calculate'
import { Contract, outcome } from './contract' import { Contract } from './contract'
import { CREATOR_FEE, FEES } from './fees' import { CREATOR_FEE, FEES } from './fees'
export const getCancelPayouts = (contract: Contract, bets: Bet[]) => { export const getCancelPayouts = (contract: Contract, bets: Bet[]) => {
@ -100,7 +100,7 @@ export const getMktPayouts = (
} }
export const getPayouts = ( export const getPayouts = (
outcome: outcome, outcome: string,
contract: Contract, contract: Contract,
bets: Bet[], bets: Bet[],
resolutionProbability?: number resolutionProbability?: number

View File

@ -42,7 +42,7 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
const contractSnap = await transaction.get(contractDoc) const contractSnap = await transaction.get(contractDoc)
if (!contractSnap.exists) if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' } return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as Contract<'MULTI'> const contract = contractSnap.data() as Contract
if ( if (
contract.outcomeType !== 'MULTI' || contract.outcomeType !== 'MULTI' ||

View File

@ -100,7 +100,7 @@ export const createContract = functions
const { yesBet, noBet } = getAnteBets( const { yesBet, noBet } = getAnteBets(
creator, creator,
contract as Contract<'BINARY'>, contract,
yesBetDoc.id, yesBetDoc.id,
noBetDoc.id noBetDoc.id
) )
@ -116,11 +116,7 @@ export const createContract = functions
const anteBetDoc = firestore const anteBetDoc = firestore
.collection(`contracts/${contract.id}/bets`) .collection(`contracts/${contract.id}/bets`)
.doc() .doc()
const anteBet = getFreeAnswerAnte( const anteBet = getFreeAnswerAnte(creator, contract, anteBetDoc.id)
creator,
contract as Contract<'MULTI'>,
anteBetDoc.id
)
await anteBetDoc.set(anteBet) await anteBetDoc.set(anteBet)
} }
} }

View File

@ -35,10 +35,7 @@ import { useAnswers } from '../hooks/use-answers'
import { ResolveConfirmationButton } from './confirmation-button' import { ResolveConfirmationButton } from './confirmation-button'
import { tradingAllowed } from '../lib/firebase/contracts' import { tradingAllowed } from '../lib/firebase/contracts'
export function AnswersPanel(props: { export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
contract: Contract<'MULTI'>
answers: Answer[]
}) {
const { contract } = props const { contract } = props
const { creatorId, resolution } = contract const { creatorId, resolution } = contract
@ -101,7 +98,7 @@ export function AnswersPanel(props: {
function AnswerItem(props: { function AnswerItem(props: {
answer: Answer answer: Answer
contract: Contract<'MULTI'> contract: Contract
showChoice: boolean showChoice: boolean
isChosen: boolean isChosen: boolean
onChoose: () => void onChoose: () => void
@ -209,7 +206,7 @@ function AnswerItem(props: {
function AnswerBetPanel(props: { function AnswerBetPanel(props: {
answer: Answer answer: Answer
contract: Contract<'MULTI'> contract: Contract
closePanel: () => void closePanel: () => void
}) { }) {
const { answer, contract, closePanel } = props const { answer, contract, closePanel } = props
@ -267,14 +264,11 @@ function AnswerBetPanel(props: {
const shares = calculateShares(contract.totalShares, betAmount ?? 0, answerId) const shares = calculateShares(contract.totalShares, betAmount ?? 0, answerId)
const currentPayout = betAmount const currentPayout = betAmount
? calculatePayoutAfterCorrectBet( ? calculatePayoutAfterCorrectBet(contract, {
contract as any as Contract, outcome: answerId,
{ amount: betAmount,
outcome: answerId, shares,
amount: betAmount, } as Bet)
shares,
} as Bet
)
: 0 : 0
const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0 const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0
@ -351,7 +345,7 @@ function AnswerBetPanel(props: {
) )
} }
function CreateAnswerInput(props: { contract: Contract<'MULTI'> }) { function CreateAnswerInput(props: { contract: Contract }) {
const { contract } = props const { contract } = props
const [text, setText] = useState('') const [text, setText] = useState('')
const [betAmount, setBetAmount] = useState<number | undefined>(10) const [betAmount, setBetAmount] = useState<number | undefined>(10)
@ -426,7 +420,7 @@ function CreateAnswerInput(props: { contract: Contract<'MULTI'> }) {
} }
function AnswerResolvePanel(props: { function AnswerResolvePanel(props: {
contract: Contract<'MULTI'> contract: Contract
resolveOption: 'CHOOSE' | 'NONE' | 'CANCEL' | undefined resolveOption: 'CHOOSE' | 'NONE' | 'CANCEL' | undefined
setResolveOption: (option: 'CHOOSE' | 'NONE' | 'CANCEL' | undefined) => void setResolveOption: (option: 'CHOOSE' | 'NONE' | 'CANCEL' | undefined) => void
answer: string | undefined answer: string | undefined

View File

@ -49,6 +49,7 @@ export function BetPanel(props: {
}, []) }, [])
const { contract, className, title, selected, onBetSuccess } = props const { contract, className, title, selected, onBetSuccess } = props
const { totalShares, phantomShares } = contract
const user = useUser() const user = useUser()
@ -179,8 +180,8 @@ export function BetPanel(props: {
shares shares
)} / ${formatWithCommas( )} / ${formatWithCommas(
shares + shares +
contract.totalShares[betChoice] - totalShares[betChoice] -
contract.phantomShares[betChoice] (phantomShares ? phantomShares[betChoice] : 0)
)} ${betChoice} shares`} )} ${betChoice} shares`}
/> />
</Row> </Row>

View File

@ -31,6 +31,7 @@ import {
import { sellBet } from '../lib/firebase/api-call' import { sellBet } from '../lib/firebase/api-call'
import { ConfirmationButton } from './confirmation-button' import { ConfirmationButton } from './confirmation-button'
import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label' import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label'
import { filterDefined } from '../../common/util/array'
type BetSort = 'newest' | 'profit' type BetSort = 'newest' | 'profit'
@ -49,7 +50,7 @@ export function BetsList(props: { user: User }) {
let disposed = false let disposed = false
Promise.all(contractIds.map((id) => getContractFromId(id))).then( Promise.all(contractIds.map((id) => getContractFromId(id))).then(
(contracts) => { (contracts) => {
if (!disposed) setContracts(contracts.filter(Boolean) as Contract[]) if (!disposed) setContracts(filterDefined(contracts))
} }
) )

View File

@ -21,7 +21,6 @@ import {
contractPath, contractPath,
updateContract, updateContract,
tradingAllowed, tradingAllowed,
getBinaryProbPercent,
} from '../lib/firebase/contracts' } from '../lib/firebase/contracts'
import { useUser } from '../hooks/use-user' import { useUser } from '../hooks/use-user'
import { Linkify } from './linkify' import { Linkify } from './linkify'
@ -38,7 +37,6 @@ import { useBets } from '../hooks/use-bets'
import { Bet } from '../lib/firebase/bets' import { Bet } from '../lib/firebase/bets'
import { Comment, mapCommentsByBetId } from '../lib/firebase/comments' import { Comment, mapCommentsByBetId } from '../lib/firebase/comments'
import { JoinSpans } from './join-spans' import { JoinSpans } from './join-spans'
import { outcome } from '../../common/contract'
import { fromNow } from '../lib/util/time' import { fromNow } from '../lib/util/time'
import BetRow from './bet-row' import BetRow from './bet-row'
import { parseTags } from '../../common/util/parse' import { parseTags } from '../../common/util/parse'
@ -382,7 +380,7 @@ function FeedDescription(props: { contract: Contract }) {
) )
} }
function OutcomeIcon(props: { outcome?: outcome }) { function OutcomeIcon(props: { outcome?: string }) {
const { outcome } = props const { outcome } = props
switch (outcome) { switch (outcome) {
case 'YES': case 'YES':

View File

@ -13,7 +13,9 @@ export function ContractProbGraph(props: { contract: Contract; bets: Bet[] }) {
const bets = useBetsWithoutAntes(contract, props.bets) const bets = useBetsWithoutAntes(contract, props.bets)
const startProb = getProbability(phantomShares) const startProb = getProbability(
phantomShares as { [outcome: string]: number }
)
const times = bets const times = bets
? [contract.createdTime, ...bets.map((bet) => bet.createdTime)].map( ? [contract.createdTime, ...bets.map((bet) => bet.createdTime)].map(

View File

@ -49,7 +49,7 @@ export function getBinaryProbPercent(contract: Contract) {
return probPercent return probPercent
} }
export function tradingAllowed(contract: Contract<'BINARY' | 'MULTI'>) { export function tradingAllowed(contract: Contract) {
return ( return (
!contract.isResolved && !contract.isResolved &&
(!contract.closeTime || contract.closeTime > Date.now()) (!contract.closeTime || contract.closeTime > Date.now())
@ -84,9 +84,7 @@ export async function getContractFromSlug(slug: string) {
const q = query(contractCollection, where('slug', '==', slug)) const q = query(contractCollection, where('slug', '==', slug))
const snapshot = await getDocs(q) const snapshot = await getDocs(q)
return snapshot.empty return snapshot.empty ? undefined : (snapshot.docs[0].data() as Contract)
? undefined
: (snapshot.docs[0].data() as Contract<'BINARY' | 'MULTI'>)
} }
export async function deleteContract(contractId: string) { export async function deleteContract(contractId: string) {

View File

@ -186,7 +186,7 @@ function BetsSection(props: {
) )
} }
const getOpenGraphProps = (contract: Contract<'BINARY'>) => { const getOpenGraphProps = (contract: Contract) => {
const { resolution, question, creatorName, creatorUsername } = contract const { resolution, question, creatorName, creatorUsername } = contract
const probPercent = getBinaryProbPercent(contract) const probPercent = getBinaryProbPercent(contract)

View File

@ -26,7 +26,7 @@ type Prediction = {
} }
function toPrediction(contract: Contract): Prediction { function toPrediction(contract: Contract): Prediction {
const startProb = getProbability(contract.phantomShares) const startProb = getProbability(contract.totalShares)
return { return {
question: contract.question, question: contract.question,
description: contract.description, description: contract.description,