From 9c5478d3d5408129190f3565b1b49885ae7d8f33 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 4 Mar 2022 18:31:04 -0500 Subject: [PATCH] separate logic to payouts-dpm, payouts-fixed --- common/payouts-dpm.ts | 146 +++++++++++++++++ common/payouts-fixed.ts | 103 ++++++++++++ common/payouts.ts | 282 ++++---------------------------- functions/src/resolve-market.ts | 16 +- 4 files changed, 284 insertions(+), 263 deletions(-) create mode 100644 common/payouts-dpm.ts create mode 100644 common/payouts-fixed.ts diff --git a/common/payouts-dpm.ts b/common/payouts-dpm.ts new file mode 100644 index 00000000..adca3408 --- /dev/null +++ b/common/payouts-dpm.ts @@ -0,0 +1,146 @@ +import * as _ from 'lodash' + +import { Bet } from './bet' +import { deductDpmFees, getDpmProbability } from './calculate-dpm' +import { DPM, FreeResponse, FullContract, Multi } from './contract' +import { CREATOR_FEE, FEES } from './fees' + +export const getDpmCancelPayouts = ( + contract: FullContract, + bets: Bet[] +) => { + const { pool } = contract + const poolTotal = _.sum(Object.values(pool)) + console.log('resolved N/A, pool M$', poolTotal) + + const betSum = _.sumBy(bets, (b) => b.amount) + + return bets.map((bet) => ({ + userId: bet.userId, + payout: (bet.amount / betSum) * poolTotal, + })) +} + +export const getDpmStandardPayouts = ( + outcome: string, + contract: FullContract, + bets: Bet[] +) => { + const winningBets = bets.filter((bet) => bet.outcome === outcome) + + const poolTotal = _.sum(Object.values(contract.pool)) + const totalShares = _.sumBy(winningBets, (b) => b.shares) + + const payouts = winningBets.map(({ userId, amount, shares }) => { + const winnings = (shares / totalShares) * poolTotal + const profit = winnings - amount + + // profit can be negative if using phantom shares + const payout = amount + (1 - FEES) * Math.max(0, profit) + return { userId, profit, payout } + }) + + const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) + const creatorPayout = CREATOR_FEE * profits + + console.log( + 'resolved', + outcome, + 'pool', + poolTotal, + 'profits', + profits, + 'creator fee', + creatorPayout + ) + + return payouts + .map(({ userId, payout }) => ({ userId, payout })) + .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee +} + +export const getDpmMktPayouts = ( + contract: FullContract, + bets: Bet[], + resolutionProbability?: number +) => { + const p = + resolutionProbability === undefined + ? getDpmProbability(contract.totalShares) + : resolutionProbability + + const weightedShareTotal = _.sumBy(bets, (b) => + b.outcome === 'YES' ? p * b.shares : (1 - p) * b.shares + ) + + const pool = contract.pool.YES + contract.pool.NO + + const payouts = bets.map(({ userId, outcome, amount, shares }) => { + const betP = outcome === 'YES' ? p : 1 - p + const winnings = ((betP * shares) / weightedShareTotal) * pool + const profit = winnings - amount + const payout = deductDpmFees(amount, winnings) + return { userId, profit, payout } + }) + + const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) + const creatorPayout = CREATOR_FEE * profits + + console.log( + 'resolved MKT', + p, + 'pool', + pool, + 'profits', + profits, + 'creator fee', + creatorPayout + ) + + return payouts + .map(({ userId, payout }) => ({ userId, payout })) + .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee +} + +export const getPayoutsMultiOutcome = ( + resolutions: { [outcome: string]: number }, + contract: FullContract, + bets: Bet[] +) => { + const poolTotal = _.sum(Object.values(contract.pool)) + const winningBets = bets.filter((bet) => resolutions[bet.outcome]) + + const betsByOutcome = _.groupBy(winningBets, (bet) => bet.outcome) + const sharesByOutcome = _.mapValues(betsByOutcome, (bets) => + _.sumBy(bets, (bet) => bet.shares) + ) + + const probTotal = _.sum(Object.values(resolutions)) + + const payouts = winningBets.map(({ userId, outcome, amount, shares }) => { + const prob = resolutions[outcome] / probTotal + const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal + const profit = winnings - amount + + const payout = amount + (1 - FEES) * Math.max(0, profit) + return { userId, profit, payout } + }) + + const profits = _.sumBy(payouts, (po) => po.profit) + const creatorPayout = CREATOR_FEE * profits + + console.log( + 'resolved', + resolutions, + 'pool', + poolTotal, + 'profits', + profits, + 'creator fee', + creatorPayout + ) + + return payouts + .map(({ userId, payout }) => ({ userId, payout })) + .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee +} diff --git a/common/payouts-fixed.ts b/common/payouts-fixed.ts new file mode 100644 index 00000000..031e8d6c --- /dev/null +++ b/common/payouts-fixed.ts @@ -0,0 +1,103 @@ +import * as _ from 'lodash' + +import { Bet } from './bet' +import { getProbability } from './calculate' +import { deductFixedFees } from './calculate-fixed-payouts' +import { Binary, CPMM, FixedPayouts, FullContract } from './contract' +import { CREATOR_FEE } from './fees' + +export const getFixedCancelPayouts = (bets: Bet[]) => { + return bets.map((bet) => ({ + userId: bet.userId, + payout: bet.amount, + })) +} + +export const getStandardFixedPayouts = ( + outcome: string, + contract: FullContract, + bets: Bet[] +) => { + const winningBets = bets.filter((bet) => bet.outcome === outcome) + + const payouts = winningBets.map(({ userId, amount, shares }) => { + const winnings = shares + const profit = winnings - amount + const payout = deductFixedFees(amount, winnings) + return { userId, profit, payout } + }) + + const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) + const creatorPayout = CREATOR_FEE * profits + + console.log( + 'resolved', + outcome, + 'pool', + contract.pool, + 'profits', + profits, + 'creator fee', + creatorPayout + ) + + return payouts + .map(({ userId, payout }) => ({ userId, payout })) + .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee + .concat(getLiquidityPoolPayouts(contract, outcome)) +} + +export const getLiquidityPoolPayouts = ( + contract: FullContract, + outcome: string +) => { + const { creatorId, pool } = contract + return [{ userId: creatorId, payout: pool[outcome] }] +} + +export const getMktFixedPayouts = ( + contract: FullContract, + bets: Bet[], + resolutionProbability?: number +) => { + const p = + resolutionProbability === undefined + ? getProbability(contract) + : resolutionProbability + + const payouts = bets.map(({ userId, outcome, amount, shares }) => { + const betP = outcome === 'YES' ? p : 1 - p + const winnings = betP * shares + const profit = winnings - amount + const payout = deductFixedFees(amount, winnings) + return { userId, profit, payout } + }) + + const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) + const creatorPayout = CREATOR_FEE * profits + + console.log( + 'resolved MKT', + p, + 'pool', + contract.pool, + 'profits', + profits, + 'creator fee', + creatorPayout + ) + + return payouts + .map(({ userId, payout }) => ({ userId, payout })) + .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee + .concat(getLiquidityPoolProbPayouts(contract, p)) +} + +export const getLiquidityPoolProbPayouts = ( + contract: FullContract, + p: number +) => { + const { creatorId, pool } = contract + const payout = p * pool.YES + (1 - p) * pool.NO + return [{ userId: creatorId, payout }] +} diff --git a/common/payouts.ts b/common/payouts.ts index 9c9dd62e..5b761e48 100644 --- a/common/payouts.ts +++ b/common/payouts.ts @@ -1,225 +1,36 @@ import * as _ from 'lodash' import { Bet } from './bet' -import { deductDpmFees, getDpmProbability } from './calculate-dpm' +import { Contract, DPM, FreeResponse, FullContract, Multi } from './contract' import { - Binary, - Contract, - CPMM, - DPM, - FreeResponse, - FullContract, - Multi, -} from './contract' -import { CREATOR_FEE, FEES } from './fees' - -export const getDpmCancelPayouts = ( - contract: FullContract, - bets: Bet[] -) => { - const { pool } = contract - const poolTotal = _.sum(Object.values(pool)) - console.log('resolved N/A, pool M$', poolTotal) - - const betSum = _.sumBy(bets, (b) => b.amount) - - return bets.map((bet) => ({ - userId: bet.userId, - payout: (bet.amount / betSum) * poolTotal, - })) -} - -export const getFixedCancelPayouts = (bets: Bet[]) => { - return bets.map((bet) => ({ - userId: bet.userId, - payout: bet.amount, - })) -} - -export const getStandardFixedPayouts = ( - outcome: string, - contract: FullContract, - bets: Bet[] -) => { - const winningBets = bets.filter((bet) => bet.outcome === outcome) - - const payouts = winningBets.map(({ userId, amount, shares }) => { - const winnings = shares - const profit = winnings - amount - - const payout = amount + (1 - FEES) * profit - return { userId, profit, payout } - }) - - const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) - const creatorPayout = CREATOR_FEE * profits - - console.log( - 'resolved', - outcome, - 'pool', - contract.pool, - 'profits', - profits, - 'creator fee', - creatorPayout - ) - - return payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee - .concat(getLiquidityPoolPayouts(contract, outcome)) -} - -export const getLiquidityPoolPayouts = ( - contract: FullContract, - outcome: string -) => { - const { creatorId, pool } = contract - return [{ userId: creatorId, payout: pool[outcome] }] -} - -export const getStandardPayouts = ( - outcome: string, - contract: FullContract, - bets: Bet[] -) => { - const winningBets = bets.filter((bet) => bet.outcome === outcome) - - const poolTotal = _.sum(Object.values(contract.pool)) - const totalShares = _.sumBy(winningBets, (b) => b.shares) - - const payouts = winningBets.map(({ userId, amount, shares }) => { - const winnings = (shares / totalShares) * poolTotal - const profit = winnings - amount - - // profit can be negative if using phantom shares - const payout = amount + (1 - FEES) * Math.max(0, profit) - return { userId, profit, payout } - }) - - const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) - const creatorPayout = CREATOR_FEE * profits - - console.log( - 'resolved', - outcome, - 'pool', - poolTotal, - 'profits', - profits, - 'creator fee', - creatorPayout - ) - - return payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee -} - -export const getMktPayouts = ( - contract: FullContract, - bets: Bet[], - resolutionProbability?: number -) => { - const p = - resolutionProbability === undefined - ? getDpmProbability(contract.totalShares) - : resolutionProbability - - const weightedShareTotal = _.sumBy(bets, (b) => - b.outcome === 'YES' ? p * b.shares : (1 - p) * b.shares - ) - - const pool = contract.pool.YES + contract.pool.NO - - const payouts = bets.map(({ userId, outcome, amount, shares }) => { - const betP = outcome === 'YES' ? p : 1 - p - const winnings = ((betP * shares) / weightedShareTotal) * pool - const profit = winnings - amount - const payout = deductDpmFees(amount, winnings) - return { userId, profit, payout } - }) - - const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) - const creatorPayout = CREATOR_FEE * profits - - console.log( - 'resolved MKT', - p, - 'pool', - pool, - 'profits', - profits, - 'creator fee', - creatorPayout - ) - - return payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee -} - -export const getMktFixedPayouts = ( - contract: FullContract, - bets: Bet[], - resolutionProbability?: number -) => { - const p = - resolutionProbability === undefined - ? getDpmProbability(contract.pool) - : resolutionProbability - - const payouts = bets.map(({ userId, outcome, amount, shares }) => { - const betP = outcome === 'YES' ? p : 1 - p - const winnings = betP * shares - const profit = winnings - amount - const payout = deductDpmFees(amount, winnings) - return { userId, profit, payout } - }) - - const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) - const creatorPayout = CREATOR_FEE * profits - - console.log( - 'resolved MKT', - p, - 'pool', - contract.pool, - 'profits', - profits, - 'creator fee', - creatorPayout - ) - - return payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee - .concat(getLiquidityPoolProbPayouts(contract, p)) -} - -export const getLiquidityPoolProbPayouts = ( - contract: FullContract, - p: number -) => { - const { creatorId, pool } = contract - const payout = p * pool.YES + (1 - p) * pool.NO - return [{ userId: creatorId, payout }] -} + getDpmCancelPayouts, + getDpmMktPayouts, + getDpmStandardPayouts, + getPayoutsMultiOutcome, +} from './payouts-dpm' +import { + getFixedCancelPayouts, + getMktFixedPayouts, + getStandardFixedPayouts, +} from './payouts-fixed' export const getPayouts = ( - outcome: string, + outcome: + | string + | { + [outcome: string]: number + }, contract: Contract, bets: Bet[], resolutionProbability?: number ) => { - if (contract.mechanism === 'cpmm-1') { + if (contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY') { switch (outcome) { case 'YES': case 'NO': - return getStandardFixedPayouts(outcome, contract as any, bets) + return getStandardFixedPayouts(outcome, contract, bets) case 'MKT': - return getMktFixedPayouts(contract as any, bets, resolutionProbability) + return getMktFixedPayouts(contract, bets, resolutionProbability) case 'CANCEL': return getFixedCancelPayouts(bets) } @@ -228,60 +39,23 @@ export const getPayouts = ( switch (outcome) { case 'YES': case 'NO': - return getStandardPayouts(outcome, contract, bets) + return getDpmStandardPayouts(outcome, contract, bets) case 'MKT': - return getMktPayouts(contract, bets, resolutionProbability) + return getDpmMktPayouts(contract, bets, resolutionProbability) case 'CANCEL': return getDpmCancelPayouts(contract, bets) default: // Multi outcome. - return getStandardPayouts(outcome, contract, bets) + return getPayoutsMultiOutcome( + outcome as { + [outcome: string]: number + }, + contract as FullContract, + bets + ) } } -export const getPayoutsMultiOutcome = ( - resolutions: { [outcome: string]: number }, - contract: FullContract, - bets: Bet[] -) => { - const poolTotal = _.sum(Object.values(contract.pool)) - const winningBets = bets.filter((bet) => resolutions[bet.outcome]) - - const betsByOutcome = _.groupBy(winningBets, (bet) => bet.outcome) - const sharesByOutcome = _.mapValues(betsByOutcome, (bets) => - _.sumBy(bets, (bet) => bet.shares) - ) - - const probTotal = _.sum(Object.values(resolutions)) - - const payouts = winningBets.map(({ userId, outcome, amount, shares }) => { - const prob = resolutions[outcome] / probTotal - const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal - const profit = winnings - amount - - const payout = amount + (1 - FEES) * Math.max(0, profit) - return { userId, profit, payout } - }) - - const profits = _.sumBy(payouts, (po) => po.profit) - const creatorPayout = CREATOR_FEE * profits - - console.log( - 'resolved', - resolutions, - 'pool', - poolTotal, - 'profits', - profits, - 'creator fee', - creatorPayout - ) - - return payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee -} - export const getLoanPayouts = (bets: Bet[]) => { const betsWithLoans = bets.filter((bet) => bet.loanAmount) const betsByUser = _.groupBy(betsWithLoans, (bet) => bet.userId) diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts index 9872456d..0362169c 100644 --- a/functions/src/resolve-market.ts +++ b/functions/src/resolve-market.ts @@ -7,11 +7,7 @@ import { User } from '../../common/user' import { Bet } from '../../common/bet' import { getUser, payUser } from './utils' import { sendMarketResolutionEmail } from './emails' -import { - getLoanPayouts, - getPayouts, - getPayoutsMultiOutcome, -} from '../../common/payouts' +import { getLoanPayouts, getPayouts } from '../../common/payouts' import { removeUndefinedProps } from '../../common/util/object' export const resolveMarket = functions @@ -98,10 +94,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 = - outcomeType === 'FREE_RESPONSE' && resolutions - ? getPayoutsMultiOutcome(resolutions, contract as any, openBets) - : getPayouts(outcome, contract, openBets, resolutionProbability) + const payouts = getPayouts( + resolutions ?? outcome, + contract, + openBets, + resolutionProbability + ) const loanPayouts = getLoanPayouts(openBets)