Mkt resolution 2: Enable MKT resolution (#16)
* new standard resolution; contract.totalBets; MKT resolution * recalculate script * enable resolve MKT * different approach to resolve MKT * comment out init * Count payouts for bets with exluded sales Co-authored-by: jahooma <jahooma@gmail.com>
This commit is contained in:
parent
a9e8b4c1e7
commit
907acec601
|
@ -26,7 +26,7 @@ export const resolveMarket = functions
|
||||||
|
|
||||||
const { outcome, contractId } = data
|
const { outcome, contractId } = data
|
||||||
|
|
||||||
if (!['YES', 'NO', 'CANCEL'].includes(outcome))
|
if (!['YES', 'NO', 'MKT', 'CANCEL'].includes(outcome))
|
||||||
return { status: 'error', message: 'Invalid outcome' }
|
return { status: 'error', message: 'Invalid outcome' }
|
||||||
|
|
||||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||||
|
@ -155,7 +155,6 @@ const getStandardPayouts = (
|
||||||
const shareDifferenceSum = _.sumBy(winningBets, (b) => b.shares - b.amount)
|
const shareDifferenceSum = _.sumBy(winningBets, (b) => b.shares - b.amount)
|
||||||
|
|
||||||
const winningsPool = truePool - betSum
|
const winningsPool = truePool - betSum
|
||||||
const fees = PLATFORM_FEE + CREATOR_FEE
|
|
||||||
|
|
||||||
const winnerPayouts = winningBets.map((bet) => ({
|
const winnerPayouts = winningBets.map((bet) => ({
|
||||||
userId: bet.userId,
|
userId: bet.userId,
|
||||||
|
@ -173,11 +172,53 @@ const getStandardPayouts = (
|
||||||
const getMktPayouts = (truePool: number, contract: Contract, bets: Bet[]) => {
|
const getMktPayouts = (truePool: number, contract: Contract, bets: Bet[]) => {
|
||||||
const p =
|
const p =
|
||||||
contract.pool.YES ** 2 / (contract.pool.YES ** 2 + contract.pool.NO ** 2)
|
contract.pool.YES ** 2 / (contract.pool.YES ** 2 + contract.pool.NO ** 2)
|
||||||
console.log('Resolved MKT at p=', p)
|
console.log('Resolved MKT at p=', p, 'pool: $M', truePool)
|
||||||
|
|
||||||
|
const [yesBets, noBets] = _.partition(bets, (bet) => bet.outcome === 'YES')
|
||||||
|
|
||||||
|
const weightedBetTotal =
|
||||||
|
p * _.sumBy(yesBets, (b) => b.amount) +
|
||||||
|
(1 - p) * _.sumBy(noBets, (b) => b.amount)
|
||||||
|
|
||||||
|
if (weightedBetTotal >= truePool) {
|
||||||
|
return bets.map((bet) => ({
|
||||||
|
userId: bet.userId,
|
||||||
|
payout:
|
||||||
|
(((bet.outcome === 'YES' ? p : 1 - p) * bet.amount) /
|
||||||
|
weightedBetTotal) *
|
||||||
|
truePool,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const winningsPool = truePool - weightedBetTotal
|
||||||
|
|
||||||
|
const weightedShareTotal =
|
||||||
|
p * _.sumBy(yesBets, (b) => b.shares - b.amount) +
|
||||||
|
(1 - p) * _.sumBy(noBets, (b) => b.shares - b.amount)
|
||||||
|
|
||||||
|
const yesPayouts = yesBets.map((bet) => ({
|
||||||
|
userId: bet.userId,
|
||||||
|
payout:
|
||||||
|
(1 - fees) *
|
||||||
|
(p * bet.amount +
|
||||||
|
((p * (bet.shares - bet.amount)) / weightedShareTotal) * winningsPool),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const noPayouts = noBets.map((bet) => ({
|
||||||
|
userId: bet.userId,
|
||||||
|
payout:
|
||||||
|
(1 - fees) *
|
||||||
|
((1 - p) * bet.amount +
|
||||||
|
(((1 - p) * (bet.shares - bet.amount)) / weightedShareTotal) *
|
||||||
|
winningsPool),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const creatorPayout = CREATOR_FEE * truePool
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...getStandardPayouts('YES', p * truePool, contract, bets),
|
...yesPayouts,
|
||||||
...getStandardPayouts('NO', (1 - p) * truePool, contract, bets),
|
...noPayouts,
|
||||||
|
{ userId: contract.creatorId, payout: creatorPayout },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,3 +233,5 @@ const payUser = ([userId, payout]: [string, number]) => {
|
||||||
transaction.update(userDoc, { balance: newUserBalance })
|
transaction.update(userDoc, { balance: newUserBalance })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fees = PLATFORM_FEE + CREATOR_FEE
|
||||||
|
|
61
functions/src/scripts/recalculate.ts
Normal file
61
functions/src/scripts/recalculate.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
import * as _ from 'lodash'
|
||||||
|
import { Bet } from '../types/bet'
|
||||||
|
import { Contract } from '../types/contract'
|
||||||
|
|
||||||
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
|
// Generate your own private key, and set the path below:
|
||||||
|
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
||||||
|
const serviceAccount = require('../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json')
|
||||||
|
|
||||||
|
admin.initializeApp({
|
||||||
|
credential: admin.credential.cert(serviceAccount),
|
||||||
|
})
|
||||||
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
async function recalculateContract(contractRef: DocRef, contract: Contract) {
|
||||||
|
const bets = await contractRef
|
||||||
|
.collection('bets')
|
||||||
|
.get()
|
||||||
|
.then((snap) => snap.docs.map((bet) => bet.data() as Bet))
|
||||||
|
|
||||||
|
const openBets = bets.filter((b) => !b.isSold && !b.sale)
|
||||||
|
|
||||||
|
const totalShares = {
|
||||||
|
YES: _.sumBy(openBets, (bet) => (bet.outcome === 'YES' ? bet.shares : 0)),
|
||||||
|
NO: _.sumBy(openBets, (bet) => (bet.outcome === 'NO' ? bet.shares : 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalBets = {
|
||||||
|
YES: _.sumBy(openBets, (bet) => (bet.outcome === 'YES' ? bet.amount : 0)),
|
||||||
|
NO: _.sumBy(openBets, (bet) => (bet.outcome === 'NO' ? bet.amount : 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
await contractRef.update({ totalShares, totalBets })
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'calculating totals for "',
|
||||||
|
contract.question,
|
||||||
|
'" total bets:',
|
||||||
|
totalBets
|
||||||
|
)
|
||||||
|
console.log()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function migrateContracts() {
|
||||||
|
console.log('Recalculating contract info')
|
||||||
|
|
||||||
|
const snapshot = await firestore.collection('contracts').get()
|
||||||
|
const contracts = snapshot.docs.map((doc) => doc.data() as Contract)
|
||||||
|
|
||||||
|
console.log('Loaded', contracts.length, 'contracts')
|
||||||
|
|
||||||
|
for (const contract of contracts) {
|
||||||
|
const contractRef = firestore.doc(`contracts/${contract.id}`)
|
||||||
|
|
||||||
|
await recalculateContract(contractRef, contract)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) migrateContracts().then(() => process.exit())
|
|
@ -191,7 +191,7 @@ export function MyBetsSummary(props: {
|
||||||
const betsTotal = _.sumBy(excludeSales, (bet) => bet.amount)
|
const betsTotal = _.sumBy(excludeSales, (bet) => bet.amount)
|
||||||
|
|
||||||
const betsPayout = resolution
|
const betsPayout = resolution
|
||||||
? _.sumBy(bets, (bet) => resolvedPayout(contract, bet))
|
? _.sumBy(excludeSales, (bet) => resolvedPayout(contract, bet))
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
const yesWinnings = _.sumBy(excludeSales, (bet) =>
|
const yesWinnings = _.sumBy(excludeSales, (bet) =>
|
||||||
|
@ -357,11 +357,12 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function OutcomeLabel(props: { outcome: 'YES' | 'NO' | 'CANCEL' }) {
|
function OutcomeLabel(props: { outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT' }) {
|
||||||
const { outcome } = props
|
const { outcome } = props
|
||||||
|
|
||||||
if (outcome === 'YES') return <YesLabel />
|
if (outcome === 'YES') return <YesLabel />
|
||||||
if (outcome === 'NO') return <NoLabel />
|
if (outcome === 'NO') return <NoLabel />
|
||||||
|
if (outcome === 'MKT') return <MarketLabel />
|
||||||
return <CancelLabel />
|
return <CancelLabel />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,3 +377,7 @@ function NoLabel() {
|
||||||
function CancelLabel() {
|
function CancelLabel() {
|
||||||
return <span className="text-yellow-400">N/A</span>
|
return <span className="text-yellow-400">N/A</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MarketLabel() {
|
||||||
|
return <span className="text-blue-400">MKT</span>
|
||||||
|
}
|
||||||
|
|
|
@ -98,6 +98,7 @@ export const ContractOverview = (props: {
|
||||||
const resolutionColor = {
|
const resolutionColor = {
|
||||||
YES: 'text-primary',
|
YES: 'text-primary',
|
||||||
NO: 'text-red-400',
|
NO: 'text-red-400',
|
||||||
|
MKT: 'text-blue-400',
|
||||||
CANCEL: 'text-yellow-400',
|
CANCEL: 'text-yellow-400',
|
||||||
'': '', // Empty if unresolved
|
'': '', // Empty if unresolved
|
||||||
}[contract.resolution || '']
|
}[contract.resolution || '']
|
||||||
|
|
|
@ -44,6 +44,7 @@ function ContractCard(props: { contract: Contract }) {
|
||||||
const resolutionColor = {
|
const resolutionColor = {
|
||||||
YES: 'text-primary',
|
YES: 'text-primary',
|
||||||
NO: 'text-red-400',
|
NO: 'text-red-400',
|
||||||
|
MKT: 'text-blue-400',
|
||||||
CANCEL: 'text-yellow-400',
|
CANCEL: 'text-yellow-400',
|
||||||
'': '', // Empty if unresolved
|
'': '', // Empty if unresolved
|
||||||
}[contract.resolution || '']
|
}[contract.resolution || '']
|
||||||
|
@ -51,6 +52,7 @@ function ContractCard(props: { contract: Contract }) {
|
||||||
const resolutionText = {
|
const resolutionText = {
|
||||||
YES: 'YES',
|
YES: 'YES',
|
||||||
NO: 'NO',
|
NO: 'NO',
|
||||||
|
MKT: 'MKT',
|
||||||
CANCEL: 'N/A',
|
CANCEL: 'N/A',
|
||||||
'': '',
|
'': '',
|
||||||
}[contract.resolution || '']
|
}[contract.resolution || '']
|
||||||
|
|
|
@ -20,7 +20,9 @@ export function ResolutionPanel(props: {
|
||||||
}) {
|
}) {
|
||||||
const { contract, className } = props
|
const { contract, className } = props
|
||||||
|
|
||||||
const [outcome, setOutcome] = useState<'YES' | 'NO' | 'CANCEL' | undefined>()
|
const [outcome, setOutcome] = useState<
|
||||||
|
'YES' | 'NO' | 'MKT' | 'CANCEL' | undefined
|
||||||
|
>()
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [error, setError] = useState<string | undefined>(undefined)
|
const [error, setError] = useState<string | undefined>(undefined)
|
||||||
|
@ -48,6 +50,8 @@ export function ResolutionPanel(props: {
|
||||||
? 'bg-red-400 hover:bg-red-500'
|
? 'bg-red-400 hover:bg-red-500'
|
||||||
: outcome === 'CANCEL'
|
: outcome === 'CANCEL'
|
||||||
? 'bg-yellow-400 hover:bg-yellow-500'
|
? 'bg-yellow-400 hover:bg-yellow-500'
|
||||||
|
: outcome === 'MKT'
|
||||||
|
? 'bg-blue-400 hover:bg-blue-500'
|
||||||
: 'btn-disabled'
|
: 'btn-disabled'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -74,6 +78,11 @@ export function ResolutionPanel(props: {
|
||||||
<>Winnings will be paid out to NO bettors. You earn 1% of the pool.</>
|
<>Winnings will be paid out to NO bettors. You earn 1% of the pool.</>
|
||||||
) : outcome === 'CANCEL' ? (
|
) : outcome === 'CANCEL' ? (
|
||||||
<>The pool will be returned to traders with no fees.</>
|
<>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.
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>Resolving this market will immediately pay out traders.</>
|
<>Resolving this market will immediately pay out traders.</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
|
||||||
export function YesNoSelector(props: {
|
export function YesNoSelector(props: {
|
||||||
|
@ -29,48 +30,60 @@ export function YesNoSelector(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function YesNoCancelSelector(props: {
|
export function YesNoCancelSelector(props: {
|
||||||
selected: 'YES' | 'NO' | 'CANCEL' | undefined
|
selected: 'YES' | 'NO' | 'MKT' | 'CANCEL' | undefined
|
||||||
onSelect: (selected: 'YES' | 'NO' | 'CANCEL') => void
|
onSelect: (selected: 'YES' | 'NO' | 'MKT' | 'CANCEL') => void
|
||||||
className?: string
|
className?: string
|
||||||
btnClassName?: string
|
btnClassName?: string
|
||||||
}) {
|
}) {
|
||||||
const { selected, onSelect, className } = props
|
const { selected, onSelect, className } = props
|
||||||
|
|
||||||
const btnClassName = clsx('px-6', props.btnClassName)
|
const btnClassName = clsx('px-6 flex-1', props.btnClassName)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className={clsx('space-x-3', className)}>
|
<Col>
|
||||||
<Button
|
<Row className={clsx('space-x-3 w-full', className)}>
|
||||||
color={selected === 'YES' ? 'green' : 'gray'}
|
<Button
|
||||||
onClick={() => onSelect('YES')}
|
color={selected === 'YES' ? 'green' : 'gray'}
|
||||||
className={btnClassName}
|
onClick={() => onSelect('YES')}
|
||||||
>
|
className={btnClassName}
|
||||||
YES
|
>
|
||||||
</Button>
|
YES
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color={selected === 'NO' ? 'red' : 'gray'}
|
color={selected === 'NO' ? 'red' : 'gray'}
|
||||||
onClick={() => onSelect('NO')}
|
onClick={() => onSelect('NO')}
|
||||||
className={btnClassName}
|
className={btnClassName}
|
||||||
>
|
>
|
||||||
NO
|
NO
|
||||||
</Button>
|
</Button>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Button
|
<Row className={clsx('space-x-3 w-full', className)}>
|
||||||
color={selected === 'CANCEL' ? 'yellow' : 'gray'}
|
<Button
|
||||||
onClick={() => onSelect('CANCEL')}
|
color={selected === 'MKT' ? 'blue' : 'gray'}
|
||||||
className={btnClassName}
|
onClick={() => onSelect('MKT')}
|
||||||
>
|
className={clsx(btnClassName, 'btn-sm')}
|
||||||
N/A
|
>
|
||||||
</Button>
|
MKT
|
||||||
</Row>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color={selected === 'CANCEL' ? 'yellow' : 'gray'}
|
||||||
|
onClick={() => onSelect('CANCEL')}
|
||||||
|
className={clsx(btnClassName, 'btn-sm')}
|
||||||
|
>
|
||||||
|
N/A
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Button(props: {
|
function Button(props: {
|
||||||
className?: string
|
className?: string
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
color: 'green' | 'red' | 'yellow' | 'gray'
|
color: 'green' | 'red' | 'blue' | 'yellow' | 'gray'
|
||||||
children?: any
|
children?: any
|
||||||
}) {
|
}) {
|
||||||
const { className, onClick, children, color } = props
|
const { className, onClick, children, color } = props
|
||||||
|
@ -83,6 +96,7 @@ function Button(props: {
|
||||||
color === 'green' && 'btn-primary',
|
color === 'green' && 'btn-primary',
|
||||||
color === 'red' && 'bg-red-400 hover:bg-red-500',
|
color === 'red' && 'bg-red-400 hover:bg-red-500',
|
||||||
color === 'yellow' && 'bg-yellow-400 hover:bg-yellow-500',
|
color === 'yellow' && 'bg-yellow-400 hover:bg-yellow-500',
|
||||||
|
color === 'blue' && 'bg-blue-400 hover:bg-blue-500',
|
||||||
color === 'gray' && 'text-gray-700 bg-gray-300 hover:bg-gray-400',
|
color === 'gray' && 'text-gray-700 bg-gray-300 hover:bg-gray-400',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -37,11 +37,13 @@ export function calculateShares(
|
||||||
export function calculatePayout(
|
export function calculatePayout(
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
bet: Bet,
|
bet: Bet,
|
||||||
outcome: 'YES' | 'NO' | 'CANCEL'
|
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
||||||
) {
|
) {
|
||||||
const { amount, outcome: betOutcome, shares } = bet
|
const { amount, outcome: betOutcome, shares } = bet
|
||||||
|
|
||||||
if (outcome === 'CANCEL') return amount
|
if (outcome === 'CANCEL') return amount
|
||||||
|
if (outcome === 'MKT') return calculateMktPayout(contract, bet)
|
||||||
|
|
||||||
if (betOutcome !== outcome) return 0
|
if (betOutcome !== outcome) return 0
|
||||||
|
|
||||||
const { totalShares, totalBets } = contract
|
const { totalShares, totalBets } = contract
|
||||||
|
@ -60,6 +62,34 @@ export function calculatePayout(
|
||||||
return (1 - fees) * (amount + ((shares - amount) / total) * winningsPool)
|
return (1 - fees) * (amount + ((shares - amount) / total) * winningsPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculateMktPayout(contract: Contract, bet: Bet) {
|
||||||
|
const p =
|
||||||
|
contract.pool.YES ** 2 / (contract.pool.YES ** 2 + contract.pool.NO ** 2)
|
||||||
|
const weightedTotal =
|
||||||
|
p * contract.totalBets.YES + (1 - p) * contract.totalBets.NO
|
||||||
|
|
||||||
|
const startPool = contract.startPool.YES + contract.startPool.NO
|
||||||
|
const truePool = contract.pool.YES + contract.pool.NO - startPool
|
||||||
|
|
||||||
|
const betP = bet.outcome === 'YES' ? p : 1 - p
|
||||||
|
|
||||||
|
if (weightedTotal >= truePool) {
|
||||||
|
return ((betP * bet.amount) / weightedTotal) * truePool
|
||||||
|
}
|
||||||
|
|
||||||
|
const winningsPool = truePool - weightedTotal
|
||||||
|
|
||||||
|
const weightedShareTotal =
|
||||||
|
p * (contract.totalShares.YES - contract.totalBets.YES) +
|
||||||
|
(1 - p) * (contract.totalShares.NO - contract.totalBets.NO)
|
||||||
|
|
||||||
|
return (
|
||||||
|
(1 - fees) *
|
||||||
|
(betP * bet.amount +
|
||||||
|
((betP * (bet.shares - bet.amount)) / weightedShareTotal) * winningsPool)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function resolvedPayout(contract: Contract, bet: Bet) {
|
export function resolvedPayout(contract: Contract, bet: Bet) {
|
||||||
if (contract.resolution)
|
if (contract.resolution)
|
||||||
return calculatePayout(contract, bet, contract.resolution)
|
return calculatePayout(contract, bet, contract.resolution)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user