Resolve prob (#41)
* rename MKT to PROB; show resolved probability; move format utilities to common * ProbabilitySelector * resolve to PROB * prob and mkt labels
This commit is contained in:
parent
855256816e
commit
f06ca8305c
|
@ -174,7 +174,10 @@ export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) {
|
|||
}
|
||||
|
||||
function calculateMktPayout(contract: Contract, bet: Bet) {
|
||||
const p = getProbability(contract.totalShares)
|
||||
const p =
|
||||
contract.resolutionProbability !== undefined
|
||||
? contract.resolutionProbability
|
||||
: getProbability(contract.totalShares)
|
||||
|
||||
const weightedTotal =
|
||||
p * contract.totalBets.YES + (1 - p) * contract.totalBets.NO
|
||||
|
|
|
@ -27,6 +27,7 @@ export type Contract = {
|
|||
isResolved: boolean
|
||||
resolutionTime?: number // When the contract creator resolved the market
|
||||
resolution?: outcome // Chosen by creator; must be one of outcomes
|
||||
resolutionProbability?: number
|
||||
|
||||
volume24Hours: number
|
||||
volume7Days: number
|
||||
|
|
|
@ -59,8 +59,16 @@ export const getStandardPayouts = (
|
|||
]) // add creator fee
|
||||
}
|
||||
|
||||
export const getMktPayouts = (contract: Contract, bets: Bet[]) => {
|
||||
const p = getProbability(contract.totalShares)
|
||||
export const getMktPayouts = (
|
||||
contract: Contract,
|
||||
bets: Bet[],
|
||||
resolutionProbability?: number
|
||||
) => {
|
||||
const p =
|
||||
resolutionProbability === undefined
|
||||
? getProbability(contract.totalShares)
|
||||
: resolutionProbability
|
||||
|
||||
const poolTotal = contract.pool.YES + contract.pool.NO
|
||||
console.log('Resolved MKT at p=', p, 'pool: $M', poolTotal)
|
||||
|
||||
|
@ -116,14 +124,15 @@ export const getMktPayouts = (contract: Contract, bets: Bet[]) => {
|
|||
export const getPayouts = (
|
||||
outcome: outcome,
|
||||
contract: Contract,
|
||||
bets: Bet[]
|
||||
bets: Bet[],
|
||||
resolutionProbability?: number
|
||||
) => {
|
||||
switch (outcome) {
|
||||
case 'YES':
|
||||
case 'NO':
|
||||
return getStandardPayouts(outcome, contract, bets)
|
||||
case 'MKT':
|
||||
return getMktPayouts(contract, bets)
|
||||
return getMktPayouts(contract, bets, resolutionProbability)
|
||||
case 'CANCEL':
|
||||
return getCancelPayouts(contract, bets)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { getProbability } from '../../common/calculate'
|
||||
import { Contract } from '../../common/contract'
|
||||
import { User } from '../../common/user'
|
||||
import { formatPercent } from '../../common/util/format'
|
||||
import { sendTemplateEmail } from './send-email'
|
||||
import { getPrivateUser, getUser } from './utils'
|
||||
|
||||
|
@ -18,7 +20,8 @@ export const sendMarketResolutionEmail = async (
|
|||
payout: number,
|
||||
creator: User,
|
||||
contract: Contract,
|
||||
resolution: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
||||
resolution: 'YES' | 'NO' | 'CANCEL' | 'MKT',
|
||||
resolutionProbability?: number
|
||||
) => {
|
||||
const privateUser = await getPrivateUser(userId)
|
||||
if (
|
||||
|
@ -31,6 +34,14 @@ export const sendMarketResolutionEmail = async (
|
|||
const user = await getUser(userId)
|
||||
if (!user) return
|
||||
|
||||
const prob = resolutionProbability ?? getProbability(contract.totalShares)
|
||||
|
||||
const toDisplayResolution = {
|
||||
YES: 'YES',
|
||||
NO: 'NO',
|
||||
CANCEL: 'N/A',
|
||||
MKT: formatPercent(prob),
|
||||
}
|
||||
const outcome = toDisplayResolution[resolution]
|
||||
|
||||
const subject = `Resolved ${outcome}: ${contract.question}`
|
||||
|
@ -56,5 +67,3 @@ export const sendMarketResolutionEmail = async (
|
|||
templateData
|
||||
)
|
||||
}
|
||||
|
||||
const toDisplayResolution = { YES: 'YES', NO: 'NO', CANCEL: 'N/A', MKT: 'MKT' }
|
||||
|
|
|
@ -16,17 +16,24 @@ export const resolveMarket = functions
|
|||
data: {
|
||||
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
||||
contractId: string
|
||||
probabilityInt?: number
|
||||
},
|
||||
context
|
||||
) => {
|
||||
const userId = context?.auth?.uid
|
||||
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||
|
||||
const { outcome, contractId } = data
|
||||
const { outcome, contractId, probabilityInt } = data
|
||||
|
||||
if (!['YES', 'NO', 'MKT', 'CANCEL'].includes(outcome))
|
||||
return { status: 'error', message: 'Invalid outcome' }
|
||||
|
||||
if (
|
||||
probabilityInt !== undefined &&
|
||||
(probabilityInt < 1 || probabilityInt > 99 || !isFinite(probabilityInt))
|
||||
)
|
||||
return { status: 'error', message: 'Invalid probability' }
|
||||
|
||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||
const contractSnap = await contractDoc.get()
|
||||
if (!contractSnap.exists)
|
||||
|
@ -42,10 +49,16 @@ export const resolveMarket = functions
|
|||
const creator = await getUser(contract.creatorId)
|
||||
if (!creator) return { status: 'error', message: 'Creator not found' }
|
||||
|
||||
const resolutionProbability =
|
||||
probabilityInt !== undefined ? probabilityInt / 100 : undefined
|
||||
|
||||
await contractDoc.update({
|
||||
isResolved: true,
|
||||
resolution: outcome,
|
||||
resolutionTime: Date.now(),
|
||||
...(resolutionProbability === undefined
|
||||
? {}
|
||||
: { resolutionProbability }),
|
||||
})
|
||||
|
||||
console.log('contract ', contractId, 'resolved to:', outcome)
|
||||
|
@ -57,7 +70,12 @@ export const resolveMarket = functions
|
|||
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
|
||||
const openBets = bets.filter((b) => !b.isSold && !b.sale)
|
||||
|
||||
const payouts = getPayouts(outcome, contract, openBets)
|
||||
const payouts = getPayouts(
|
||||
outcome,
|
||||
contract,
|
||||
openBets,
|
||||
resolutionProbability
|
||||
)
|
||||
|
||||
console.log('payouts:', payouts)
|
||||
|
||||
|
@ -79,7 +97,8 @@ export const resolveMarket = functions
|
|||
userPayouts,
|
||||
creator,
|
||||
contract,
|
||||
outcome
|
||||
outcome,
|
||||
resolutionProbability
|
||||
)
|
||||
|
||||
return result
|
||||
|
@ -91,7 +110,8 @@ const sendResolutionEmails = async (
|
|||
userPayouts: { [userId: string]: number },
|
||||
creator: User,
|
||||
contract: Contract,
|
||||
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
||||
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT',
|
||||
resolutionProbability?: number
|
||||
) => {
|
||||
const nonWinners = _.difference(
|
||||
_.uniq(openBets.map(({ userId }) => userId)),
|
||||
|
@ -103,7 +123,14 @@ const sendResolutionEmails = async (
|
|||
]
|
||||
await Promise.all(
|
||||
emailPayouts.map(([userId, payout]) =>
|
||||
sendMarketResolutionEmail(userId, payout, creator, contract, outcome)
|
||||
sendMarketResolutionEmail(
|
||||
userId,
|
||||
payout,
|
||||
creator,
|
||||
contract,
|
||||
outcome,
|
||||
resolutionProbability
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import clsx from 'clsx'
|
||||
import { useUser } from '../hooks/use-user'
|
||||
import { formatMoney } from '../lib/util/format'
|
||||
import { formatMoney } from '../../common/util/format'
|
||||
import { AddFundsButton } from './add-funds-button'
|
||||
import { Col } from './layout/col'
|
||||
import { Row } from './layout/row'
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
formatMoney,
|
||||
formatPercent,
|
||||
formatWithCommas,
|
||||
} from '../lib/util/format'
|
||||
} from '../../common/util/format'
|
||||
import { Title } from './title'
|
||||
import {
|
||||
getProbability,
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
formatMoney,
|
||||
formatPercent,
|
||||
formatWithCommas,
|
||||
} from '../lib/util/format'
|
||||
} from '../../common/util/format'
|
||||
import { Col } from './layout/col'
|
||||
import { Spacer } from './layout/spacer'
|
||||
import {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import clsx from 'clsx'
|
||||
import Link from 'next/link'
|
||||
import { Row } from '../components/layout/row'
|
||||
import { formatMoney } from '../lib/util/format'
|
||||
import { formatMoney } from '../../common/util/format'
|
||||
import { UserLink } from './user-page'
|
||||
import {
|
||||
Contract,
|
||||
|
@ -74,7 +74,7 @@ export function ResolutionOrChance(props: {
|
|||
const resolutionText = {
|
||||
YES: 'YES',
|
||||
NO: 'NO',
|
||||
MKT: 'MKT',
|
||||
MKT: probPercent,
|
||||
CANCEL: 'N/A',
|
||||
'': '',
|
||||
}[resolution || '']
|
||||
|
|
|
@ -27,7 +27,7 @@ import { Linkify } from './linkify'
|
|||
import { Row } from './layout/row'
|
||||
import { createComment } from '../lib/firebase/comments'
|
||||
import { useComments } from '../hooks/use-comments'
|
||||
import { formatMoney } from '../lib/util/format'
|
||||
import { formatMoney } from '../../common/util/format'
|
||||
import { ResolutionOrChance } from './contract-card'
|
||||
import { SiteLink } from './site-link'
|
||||
import { Col } from './layout/col'
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useState } from 'react'
|
|||
import { parseWordsAsTags } from '../../common/util/parse'
|
||||
import { createFold } from '../lib/firebase/api-call'
|
||||
import { foldPath } from '../lib/firebase/folds'
|
||||
import { toCamelCase } from '../lib/util/format'
|
||||
import { toCamelCase } from '../../common/util/format'
|
||||
import { ConfirmationButton } from './confirmation-button'
|
||||
import { Col } from './layout/col'
|
||||
import { Spacer } from './layout/spacer'
|
||||
|
|
|
@ -6,7 +6,7 @@ import { PencilIcon } from '@heroicons/react/outline'
|
|||
import { Fold } from '../../common/fold'
|
||||
import { parseWordsAsTags } from '../../common/util/parse'
|
||||
import { deleteFold, updateFold } from '../lib/firebase/folds'
|
||||
import { toCamelCase } from '../lib/util/format'
|
||||
import { toCamelCase } from '../../common/util/format'
|
||||
import { Spacer } from './layout/spacer'
|
||||
import { TagsList } from './tags-list'
|
||||
import { useRouter } from 'next/router'
|
||||
|
|
|
@ -5,7 +5,7 @@ export function OutcomeLabel(props: {
|
|||
|
||||
if (outcome === 'YES') return <YesLabel />
|
||||
if (outcome === 'NO') return <NoLabel />
|
||||
if (outcome === 'MKT') return <MarketLabel />
|
||||
if (outcome === 'MKT') return <ProbLabel />
|
||||
return <CancelLabel />
|
||||
}
|
||||
|
||||
|
@ -24,3 +24,7 @@ export function CancelLabel() {
|
|||
export function MarketLabel() {
|
||||
return <span className="text-blue-400">MKT</span>
|
||||
}
|
||||
|
||||
export function ProbLabel() {
|
||||
return <span className="text-blue-400">PROB</span>
|
||||
}
|
||||
|
|
36
web/components/probability-selector.tsx
Normal file
36
web/components/probability-selector.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Row } from './layout/row'
|
||||
|
||||
export function ProbabilitySelector(props: {
|
||||
probabilityInt: number
|
||||
setProbabilityInt: (p: number) => void
|
||||
isSubmitting?: boolean
|
||||
}) {
|
||||
const { probabilityInt, setProbabilityInt, isSubmitting } = props
|
||||
|
||||
return (
|
||||
<Row className="items-center gap-2">
|
||||
<label className="input-group input-group-lg w-fit text-lg">
|
||||
<input
|
||||
type="number"
|
||||
value={probabilityInt}
|
||||
className="input input-bordered input-md text-lg"
|
||||
disabled={isSubmitting}
|
||||
min={1}
|
||||
max={99}
|
||||
onChange={(e) =>
|
||||
setProbabilityInt(parseInt(e.target.value.substring(0, 2)))
|
||||
}
|
||||
/>
|
||||
<span>%</span>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
className="range range-primary"
|
||||
min={1}
|
||||
max={99}
|
||||
value={probabilityInt}
|
||||
onChange={(e) => setProbabilityInt(parseInt(e.target.value))}
|
||||
/>
|
||||
</Row>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { firebaseLogout, User } from '../lib/firebase/users'
|
||||
import { formatMoney } from '../lib/util/format'
|
||||
import { formatMoney } from '../../common/util/format'
|
||||
import { Avatar } from './avatar'
|
||||
import { Col } from './layout/col'
|
||||
import { MenuButton } from './menu'
|
||||
|
|
|
@ -9,6 +9,8 @@ import { YesNoCancelSelector } from './yes-no-selector'
|
|||
import { Spacer } from './layout/spacer'
|
||||
import { ConfirmationButton as ConfirmationButton } from './confirmation-button'
|
||||
import { resolveMarket } from '../lib/firebase/api-call'
|
||||
import { ProbabilitySelector } from './probability-selector'
|
||||
import { getProbability } from '../../common/calculate'
|
||||
|
||||
export function ResolutionPanel(props: {
|
||||
creator: User
|
||||
|
@ -26,6 +28,8 @@ export function ResolutionPanel(props: {
|
|||
'YES' | 'NO' | 'MKT' | 'CANCEL' | undefined
|
||||
>()
|
||||
|
||||
const [prob, setProb] = useState(getProbability(contract.totalShares) * 100)
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [error, setError] = useState<string | undefined>(undefined)
|
||||
|
||||
|
@ -35,6 +39,7 @@ export function ResolutionPanel(props: {
|
|||
const result = await resolveMarket({
|
||||
outcome,
|
||||
contractId: contract.id,
|
||||
probabilityInt: prob,
|
||||
}).then((r) => r.data as any)
|
||||
|
||||
console.log('resolved', outcome, 'result:', result)
|
||||
|
@ -82,8 +87,8 @@ export function ResolutionPanel(props: {
|
|||
<>The pool will be returned to traders with no fees.</>
|
||||
) : outcome === 'MKT' ? (
|
||||
<>
|
||||
Traders will be paid out at the current implied probability. You
|
||||
earn 1% of the pool.
|
||||
Traders will be paid out at the probability you specify. You earn 1%
|
||||
of the pool.
|
||||
</>
|
||||
) : (
|
||||
<>Resolving this market will immediately pay out traders.</>
|
||||
|
@ -113,7 +118,20 @@ export function ResolutionPanel(props: {
|
|||
}}
|
||||
onSubmit={resolve}
|
||||
>
|
||||
{outcome === 'MKT' ? (
|
||||
<>
|
||||
<p className="mb-4">
|
||||
What probability would you like to resolve the market to?
|
||||
</p>
|
||||
|
||||
<ProbabilitySelector
|
||||
probabilityInt={Math.round(prob)}
|
||||
setProbabilityInt={setProb}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p>Are you sure you want to resolve this market?</p>
|
||||
)}
|
||||
</ConfirmationButton>
|
||||
</Col>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import { formatMoney } from '../lib/util/format'
|
||||
import { formatMoney } from '../../common/util/format'
|
||||
import { Col } from './layout/col'
|
||||
import { Row } from './layout/row'
|
||||
|
||||
|
@ -78,7 +78,7 @@ export function YesNoCancelSelector(props: {
|
|||
onClick={() => onSelect('MKT')}
|
||||
className={clsx(btnClassName, 'btn-sm')}
|
||||
>
|
||||
MKT
|
||||
PROB
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
|
|
@ -35,10 +35,11 @@ export function contractMetrics(contract: Contract) {
|
|||
createdTime,
|
||||
resolutionTime,
|
||||
isResolved,
|
||||
resolutionProbability,
|
||||
} = contract
|
||||
|
||||
const truePool = pool.YES + pool.NO
|
||||
const prob = getProbability(totalShares)
|
||||
const prob = resolutionProbability ?? getProbability(totalShares)
|
||||
const probPercent = Math.round(prob * 100) + '%'
|
||||
|
||||
const startProb = getProbability(phantomShares)
|
||||
|
|
|
@ -27,13 +27,18 @@ export function scoreTraders(contracts: Contract[], bets: Bet[][]) {
|
|||
}
|
||||
|
||||
function scoreUsersByContract(contract: Contract, bets: Bet[]) {
|
||||
const { resolution } = contract
|
||||
const { resolution, resolutionProbability } = contract
|
||||
|
||||
const [closedBets, openBets] = _.partition(
|
||||
bets,
|
||||
(bet) => bet.isSold || bet.sale
|
||||
)
|
||||
const resolvePayouts = getPayouts(resolution ?? 'MKT', contract, openBets)
|
||||
const resolvePayouts = getPayouts(
|
||||
resolution ?? 'MKT',
|
||||
contract,
|
||||
openBets,
|
||||
resolutionProbability
|
||||
)
|
||||
|
||||
const salePayouts = closedBets.map((bet) => {
|
||||
const { userId, sale } = bet
|
||||
|
|
|
@ -15,6 +15,7 @@ import { InfoTooltip } from '../components/info-tooltip'
|
|||
import { CREATOR_FEE } from '../../common/fees'
|
||||
import { Page } from '../components/page'
|
||||
import { Title } from '../components/title'
|
||||
import { ProbabilitySelector } from '../components/probability-selector'
|
||||
|
||||
export default function Create() {
|
||||
const [question, setQuestion] = useState('')
|
||||
|
@ -127,30 +128,11 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
<label className="label">
|
||||
<span className="mb-1">Initial probability</span>
|
||||
</label>
|
||||
<Row className="items-center gap-2">
|
||||
<label className="input-group input-group-lg w-fit text-lg">
|
||||
<input
|
||||
type="number"
|
||||
value={initialProb}
|
||||
className="input input-bordered input-md text-lg"
|
||||
disabled={isSubmitting}
|
||||
min={1}
|
||||
max={99}
|
||||
onChange={(e) =>
|
||||
setInitialProb(parseInt(e.target.value.substring(0, 2)))
|
||||
}
|
||||
|
||||
<ProbabilitySelector
|
||||
probabilityInt={initialProb}
|
||||
setProbabilityInt={setInitialProb}
|
||||
/>
|
||||
<span>%</span>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
className="range range-primary"
|
||||
min={1}
|
||||
max={99}
|
||||
value={initialProb}
|
||||
onChange={(e) => setInitialProb(parseInt(e.target.value))}
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<Spacer h={4} />
|
||||
|
|
|
@ -28,7 +28,7 @@ import { useRouter } from 'next/router'
|
|||
import clsx from 'clsx'
|
||||
import { scoreCreators, scoreTraders } from '../../../lib/firebase/scoring'
|
||||
import { Leaderboard } from '../../../components/leaderboard'
|
||||
import { formatMoney, toCamelCase } from '../../../lib/util/format'
|
||||
import { formatMoney, toCamelCase } from '../../../../common/util/format'
|
||||
import { EditFoldButton } from '../../../components/edit-fold-button'
|
||||
import Custom404 from '../../404'
|
||||
import { FollowFoldButton } from '../../../components/follow-fold-button'
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
import { Col } from '../components/layout/col'
|
||||
import { Leaderboard } from '../components/leaderboard'
|
||||
import { Page } from '../components/page'
|
||||
import { getTopCreators, getTopTraders, User } from '../lib/firebase/users'
|
||||
import { formatMoney } from '../lib/util/format'
|
||||
import { formatMoney } from '../../common/util/format'
|
||||
|
||||
export async function getStaticProps() {
|
||||
const [topTraders, topCreators] = await Promise.all([
|
||||
|
|
Loading…
Reference in New Issue
Block a user