diff --git a/common/antes.ts b/common/antes.ts index d36ef584..287f32d3 100644 --- a/common/antes.ts +++ b/common/antes.ts @@ -1,11 +1,50 @@ import { Bet } from './bet' import { getProbability } from './calculate' -import { Contract } from './contract' +import { getCpmmProbability } from './calculate-cpmm' +import { Binary, CPMM, DPM, FreeResponse, FullContract } from './contract' import { User } from './user' export const PHANTOM_ANTE = 0.001 export const MINIMUM_ANTE = 10 +export const calcStartCpmmPool = (initialProbInt: number, ante: number) => { + const p = initialProbInt / 100.0 + const invP = 1.0 / p - 1 + const otherAnte = ante / invP + + const [poolYes, poolNo] = p >= 0.5 ? [otherAnte, ante] : [ante, otherAnte] + const k = poolYes * poolNo + + return { poolYes, poolNo, k } +} + +export function getCpmmAnteBet( + creator: User, + contract: FullContract, + anteId: string, + amount: number, + outcome: 'YES' | 'NO' +) { + const p = getCpmmProbability(contract.pool) + + const { createdTime } = contract + + const bet: Bet = { + id: anteId, + userId: creator.id, + contractId: contract.id, + amount: amount, + shares: amount, + outcome, + probBefore: p, + probAfter: p, + createdTime, + isAnte: true, + } + + return bet +} + export const calcStartPool = (initialProbInt: number, ante = 0) => { const p = initialProbInt / 100.0 const totalAnte = PHANTOM_ANTE + ante @@ -24,7 +63,7 @@ export const calcStartPool = (initialProbInt: number, ante = 0) => { export function getAnteBets( creator: User, - contract: Contract, + contract: FullContract, yesAnteId: string, noAnteId: string ) { @@ -64,7 +103,7 @@ export function getAnteBets( export function getFreeAnswerAnte( creator: User, - contract: Contract, + contract: FullContract, anteBetId: string ) { const { totalBets, totalShares } = contract diff --git a/common/calculate-cpmm.ts b/common/calculate-cpmm.ts new file mode 100644 index 00000000..9e544a4e --- /dev/null +++ b/common/calculate-cpmm.ts @@ -0,0 +1,134 @@ +import * as _ from 'lodash' +import { Bet } from './bet' +import { Binary, CPMM, FullContract } from './contract' +import { FEES } from './fees' + +export function getCpmmProbability(pool: { [outcome: string]: number }) { + // For binary contracts only. + const { YES, NO } = pool + return NO / (YES + NO) +} + +export function getCpmmProbabilityAfterBet( + contract: FullContract, + outcome: string, + bet: number +) { + const { newPool } = calculateCpmmPurchase(contract, bet, outcome) + return getCpmmProbability(newPool) +} + +export function calculateCpmmShares( + pool: { + [outcome: string]: number + }, + k: number, + bet: number, + betChoice: string +) { + const { YES: y, NO: n } = pool + const numerator = bet ** 2 + bet * (y + n) - k + y * n + const denominator = betChoice === 'YES' ? bet + n : bet + y + const shares = numerator / denominator + return shares +} + +export function calculateCpmmPurchase( + contract: FullContract, + bet: number, + outcome: string +) { + const { pool, k } = contract + + const shares = calculateCpmmShares(pool, k, bet, outcome) + const { YES: y, NO: n } = pool + + const [newY, newN] = + outcome === 'YES' + ? [y - shares + bet, n + bet] + : [y + bet, n - shares + bet] + + const newPool = { YES: newY, NO: newN } + + return { shares, newPool } +} + +export function calculateCpmmShareValue( + contract: FullContract, + shares: number, + outcome: string +) { + const { pool, k } = contract + const { YES: y, NO: n } = pool + + const poolChange = outcome === 'YES' ? shares + y - n : shares + n - y + + const shareValue = 0.5 * (shares + y + n - Math.sqrt(4 * k + poolChange ** 2)) + return shareValue +} + +export function calculateCpmmSale( + contract: FullContract, + bet: Bet +) { + const { shares, outcome } = bet + + const saleValue = calculateCpmmShareValue(contract, shares, outcome) + + const { pool } = contract + const { YES: y, NO: n } = pool + + const [newY, newN] = + outcome === 'YES' + ? [y + shares - saleValue, n - saleValue] + : [y - saleValue, n + shares - saleValue] + + const newPool = { YES: newY, NO: newN } + + return { saleValue, newPool } +} + +export function calculateFixedPayout( + contract: FullContract, + bet: Bet, + outcome: string +) { + if (outcome === 'CANCEL') return calculateFixedCancelPayout(bet) + if (outcome === 'MKT') return calculateFixedMktPayout(contract, bet) + + return calculateStandardFixedPayout(bet, outcome) +} + +export function calculateFixedCancelPayout(bet: Bet) { + return bet.amount +} + +export function calculateStandardFixedPayout(bet: Bet, outcome: string) { + const { amount, outcome: betOutcome, shares } = bet + if (betOutcome !== outcome) return 0 + return deductCpmmFees(amount, shares - amount) +} + +function calculateFixedMktPayout( + contract: FullContract, + bet: Bet +) { + const { resolutionProbability, pool } = contract + const p = + resolutionProbability !== undefined + ? resolutionProbability + : getCpmmProbability(pool) + + const { outcome, amount, shares } = bet + + const betP = outcome === 'YES' ? p : 1 - p + const winnings = betP * shares + + return deductCpmmFees(amount, winnings) +} + +export const deductCpmmFees = (betAmount: number, winnings: number) => { + return winnings > betAmount + ? betAmount + (1 - FEES) * (winnings - betAmount) + : winnings +} diff --git a/common/calculate.ts b/common/calculate.ts index b03868ed..8105109e 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash' import { Bet } from './bet' -import { Contract } from './contract' +import { Binary, DPM, FullContract } from './contract' import { FEES } from './fees' export function getProbability(totalShares: { [outcome: string]: number }) { @@ -86,7 +86,7 @@ export function calculateRawShareValue( } export function calculateMoneyRatio( - contract: Contract, + contract: FullContract, bet: Bet, shareValue: number ) { @@ -112,7 +112,10 @@ export function calculateMoneyRatio( return actual / expected } -export function calculateShareValue(contract: Contract, bet: Bet) { +export function calculateShareValue( + contract: FullContract, + bet: Bet +) { const { pool, totalShares } = contract const { shares, outcome } = bet @@ -124,20 +127,30 @@ export function calculateShareValue(contract: Contract, bet: Bet) { return adjShareValue } -export function calculateSaleAmount(contract: Contract, bet: Bet) { +export function calculateSaleAmount( + contract: FullContract, + bet: Bet +) { const { amount } = bet const winnings = calculateShareValue(contract, bet) return deductFees(amount, winnings) } -export function calculatePayout(contract: Contract, bet: Bet, outcome: string) { +export function calculatePayout( + contract: FullContract, + bet: Bet, + outcome: string +) { if (outcome === 'CANCEL') return calculateCancelPayout(contract, bet) if (outcome === 'MKT') return calculateMktPayout(contract, bet) return calculateStandardPayout(contract, bet, outcome) } -export function calculateCancelPayout(contract: Contract, bet: Bet) { +export function calculateCancelPayout( + contract: FullContract, + bet: Bet +) { const { totalBets, pool } = contract const betTotal = _.sum(Object.values(totalBets)) const poolTotal = _.sum(Object.values(pool)) @@ -146,7 +159,7 @@ export function calculateCancelPayout(contract: Contract, bet: Bet) { } export function calculateStandardPayout( - contract: Contract, + contract: FullContract, bet: Bet, outcome: string ) { @@ -166,7 +179,10 @@ export function calculateStandardPayout( return amount + (1 - FEES) * Math.max(0, winnings - amount) } -export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) { +export function calculatePayoutAfterCorrectBet( + contract: FullContract, + bet: Bet +) { const { totalShares, pool, totalBets } = contract const { shares, amount, outcome } = bet @@ -193,7 +209,7 @@ export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) { return calculateStandardPayout(newContract, bet, outcome) } -function calculateMktPayout(contract: Contract, bet: Bet) { +function calculateMktPayout(contract: FullContract, bet: Bet) { if (contract.outcomeType === 'BINARY') return calculateBinaryMktPayout(contract, bet) @@ -201,7 +217,7 @@ function calculateMktPayout(contract: Contract, bet: Bet) { const totalPool = _.sum(Object.values(pool)) const sharesSquareSum = _.sumBy( - Object.values(totalShares), + Object.values(totalShares) as number[], (shares) => shares ** 2 ) @@ -220,7 +236,10 @@ function calculateMktPayout(contract: Contract, bet: Bet) { return deductFees(amount, winnings) } -function calculateBinaryMktPayout(contract: Contract, bet: Bet) { +function calculateBinaryMktPayout( + contract: FullContract, + bet: Bet +) { const { resolutionProbability, totalShares, phantomShares } = contract const p = resolutionProbability !== undefined @@ -241,7 +260,7 @@ function calculateBinaryMktPayout(contract: Contract, bet: Bet) { return deductFees(amount, winnings) } -export function resolvedPayout(contract: Contract, bet: Bet) { +export function resolvedPayout(contract: FullContract, bet: Bet) { if (contract.resolution) return calculatePayout(contract, bet, contract.resolution) throw new Error('Contract was not resolved') diff --git a/common/contract.ts b/common/contract.ts index 11609329..c39f11d0 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -1,6 +1,9 @@ import { Answer } from './answer' -export type Contract = { +export type FullContract< + M extends DPM | CPMM, + T extends Binary | Multi | FreeResponse +> = { id: string slug: string // auto-generated; must be unique @@ -15,16 +18,6 @@ export type Contract = { lowercaseTags: string[] visibility: 'public' | 'unlisted' - outcomeType: 'BINARY' | 'MULTI' | 'FREE_RESPONSE' - multiOutcomes?: string[] // Used for outcomeType 'MULTI'. - answers?: Answer[] // Used for outcomeType 'FREE_RESPONSE'. - - mechanism: 'dpm-2' - phantomShares?: { [outcome: string]: number } - pool: { [outcome: string]: number } - totalShares: { [outcome: string]: number } - totalBets: { [outcome: string]: number } - createdTime: number // Milliseconds since epoch lastUpdatedTime: number // If the question or description was changed closeTime?: number // When no more trading is allowed @@ -32,12 +25,48 @@ export type Contract = { isResolved: boolean resolutionTime?: number // When the contract creator resolved the market resolution?: string - resolutionProbability?: number // Used for BINARY markets resolved to MKT - resolutions?: { [outcome: string]: number } // Used for outcomeType FREE_RESPONSE resolved to MKT + closeEmailsSent?: number volume24Hours: number volume7Days: number +} & M & + T + +export type Contract = FullContract + +export type DPM = { + mechanism: 'dpm-2' + + pool: { [outcome: string]: number } + phantomShares?: { [outcome: string]: number } + totalShares: { [outcome: string]: number } + totalBets: { [outcome: string]: number } +} + +export type CPMM = { + mechanism: 'cpmm-1' + + pool: { [outcome: string]: number } + k: number // liquidity constant + liquidity: { [userId: string]: { [outcome: string]: number } } // track liquidity providers +} + +export type Binary = { + outcomeType: 'BINARY' + resolutionProbability?: number // Used for BINARY markets resolved to MKT +} + +export type Multi = { + outcomeType: 'MULTI' + multiOutcomes: string[] // Used for outcomeType 'MULTI'. + resolutions?: { [outcome: string]: number } // Used for PROB +} + +export type FreeResponse = { + outcomeType: 'FREE_RESPONSE' + answers: Answer[] // Used for outcomeType 'FREE_RESPONSE'. + resolutions?: { [outcome: string]: number } // Used for PROB } export type outcomeType = 'BINARY' | 'MULTI' | 'FREE_RESPONSE' diff --git a/common/new-bet.ts b/common/new-bet.ts index 29fd421a..4405987a 100644 --- a/common/new-bet.ts +++ b/common/new-bet.ts @@ -4,14 +4,60 @@ import { getProbability, getOutcomeProbability, } from './calculate' -import { Contract } from './contract' +import { calculateCpmmShares, getCpmmProbability } from './calculate-cpmm' +import { + Binary, + CPMM, + DPM, + FreeResponse, + FullContract, + Multi, +} from './contract' import { User } from './user' -export const getNewBinaryBetInfo = ( +export const getNewBinaryCpmmBetInfo = ( user: User, outcome: 'YES' | 'NO', amount: number, - contract: Contract, + contract: FullContract, + newBetId: string +) => { + const { pool, k } = contract + + const shares = calculateCpmmShares(pool, k, amount, outcome) + const { YES: y, NO: n } = pool + + const [newY, newN] = + outcome === 'YES' + ? [y - shares + amount, amount] + : [amount, n - shares + amount] + + const newBalance = user.balance - amount + + const probBefore = getCpmmProbability(pool) + const newPool = { YES: newY, NO: newN } + const probAfter = getCpmmProbability(newPool) + + const newBet: Bet = { + id: newBetId, + userId: user.id, + contractId: contract.id, + amount, + shares, + outcome, + probBefore, + probAfter, + createdTime: Date.now(), + } + + return { newBet, newPool, newBalance } +} + +export const getNewBinaryDpmBetInfo = ( + user: User, + outcome: 'YES' | 'NO', + amount: number, + contract: FullContract, newBetId: string ) => { const { YES: yesPool, NO: noPool } = contract.pool @@ -61,7 +107,7 @@ export const getNewMultiBetInfo = ( user: User, outcome: string, amount: number, - contract: Contract, + contract: FullContract, newBetId: string ) => { const { pool, totalShares, totalBets } = contract diff --git a/common/new-contract.ts b/common/new-contract.ts index cc742f2f..d65c53fd 100644 --- a/common/new-contract.ts +++ b/common/new-contract.ts @@ -1,5 +1,12 @@ -import { calcStartPool } from './antes' -import { Contract, outcomeType } from './contract' +import { calcStartPool, calcStartCpmmPool } from './antes' +import { + Binary, + Contract, + CPMM, + DPM, + FreeResponse, + outcomeType, +} from './contract' import { User } from './user' import { parseTags } from './util/parse' import { removeUndefinedProps } from './util/object' @@ -23,13 +30,12 @@ export function getNewContract( const propsByOutcomeType = outcomeType === 'BINARY' - ? getBinaryProps(initialProb, ante) + ? getBinaryCpmmProps(initialProb, ante, creator.id) // getBinaryDpmProps(initialProb, ante) : getFreeAnswerProps(ante) - const contract: Contract = removeUndefinedProps({ + const contract = removeUndefinedProps({ id, slug, - mechanism: 'dpm-2', outcomeType, ...propsByOutcomeType, @@ -52,28 +58,55 @@ export function getNewContract( volume7Days: 0, }) - return contract + return contract as Contract } -const getBinaryProps = (initialProb: number, ante: number) => { +const getBinaryDpmProps = (initialProb: number, ante: number) => { const { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo } = calcStartPool(initialProb, ante) - return { + const system: DPM & Binary = { + mechanism: 'dpm-2', + outcomeType: 'BINARY', phantomShares: { YES: phantomYes, NO: phantomNo }, pool: { YES: poolYes, NO: poolNo }, totalShares: { YES: sharesYes, NO: sharesNo }, totalBets: { YES: poolYes, NO: poolNo }, } + + return system +} + +const getBinaryCpmmProps = ( + initialProb: number, + ante: number, + userId: string +) => { + const { poolYes, poolNo, k } = calcStartCpmmPool(initialProb, ante) + const pool = { YES: poolYes, NO: poolNo } + + const system: CPMM & Binary = { + mechanism: 'cpmm-1', + outcomeType: 'BINARY', + pool: pool, + k, + liquidity: { [userId]: pool }, + } + + return system } const getFreeAnswerProps = (ante: number) => { - return { + const system: DPM & FreeResponse = { + mechanism: 'dpm-2', + outcomeType: 'FREE_RESPONSE', pool: { '0': ante }, totalShares: { '0': ante }, totalBets: { '0': ante }, answers: [], } + + return system } const getMultiProps = ( diff --git a/common/payouts.ts b/common/payouts.ts index 5c29d6a9..495d734f 100644 --- a/common/payouts.ts +++ b/common/payouts.ts @@ -2,10 +2,21 @@ import * as _ from 'lodash' import { Bet } from './bet' import { deductFees, getProbability } from './calculate' -import { Contract } from './contract' +import { + Binary, + Contract, + CPMM, + DPM, + FreeResponse, + FullContract, + Multi, +} from './contract' import { CREATOR_FEE, FEES } from './fees' -export const getCancelPayouts = (contract: Contract, bets: Bet[]) => { +export const getDpmCancelPayouts = ( + contract: FullContract, + bets: Bet[] +) => { const { pool } = contract const poolTotal = _.sum(Object.values(pool)) console.log('resolved N/A, pool M$', poolTotal) @@ -18,9 +29,59 @@ export const getCancelPayouts = (contract: Contract, bets: Bet[]) => { })) } +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: Contract, + contract: FullContract, bets: Bet[] ) => { const winningBets = bets.filter((bet) => bet.outcome === outcome) @@ -57,7 +118,7 @@ export const getStandardPayouts = ( } export const getMktPayouts = ( - contract: Contract, + contract: FullContract, bets: Bet[], resolutionProbability?: number ) => { @@ -99,12 +160,71 @@ export const getMktPayouts = ( .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee } +export const getMktFixedPayouts = ( + contract: FullContract, + bets: Bet[], + resolutionProbability?: number +) => { + const p = + resolutionProbability === undefined + ? getProbability(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 = deductFees(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 }] +} + export const getPayouts = ( outcome: string, contract: Contract, bets: Bet[], resolutionProbability?: number ) => { + if (contract.mechanism === 'cpmm-1') { + switch (outcome) { + case 'YES': + case 'NO': + return getStandardFixedPayouts(outcome, contract as any, bets) + case 'MKT': + return getMktFixedPayouts(contract as any, bets, resolutionProbability) + case 'CANCEL': + return getFixedCancelPayouts(bets) + } + } + switch (outcome) { case 'YES': case 'NO': @@ -112,7 +232,7 @@ export const getPayouts = ( case 'MKT': return getMktPayouts(contract, bets, resolutionProbability) case 'CANCEL': - return getCancelPayouts(contract, bets) + return getDpmCancelPayouts(contract, bets) default: // Multi outcome. return getStandardPayouts(outcome, contract, bets) @@ -121,7 +241,7 @@ export const getPayouts = ( export const getPayoutsMultiOutcome = ( resolutions: { [outcome: string]: number }, - contract: Contract, + contract: FullContract, bets: Bet[] ) => { const poolTotal = _.sum(Object.values(contract.pool)) diff --git a/common/scoring.ts b/common/scoring.ts index 6940a019..36068652 100644 --- a/common/scoring.ts +++ b/common/scoring.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash' import { Bet } from './bet' -import { Contract } from './contract' +import { Binary, Contract, FullContract } from './contract' import { getPayouts } from './payouts' export function scoreCreators(contracts: Contract[], bets: Bet[][]) { @@ -23,7 +23,10 @@ export function scoreTraders(contracts: Contract[], bets: Bet[][]) { return userScores } -export function scoreUsersByContract(contract: Contract, bets: Bet[]) { +export function scoreUsersByContract( + contract: FullContract, + bets: Bet[] +) { const { resolution, resolutionProbability } = contract const [closedBets, openBets] = _.partition( diff --git a/common/sell-bet.ts b/common/sell-bet.ts index cc824386..778ff000 100644 --- a/common/sell-bet.ts +++ b/common/sell-bet.ts @@ -1,13 +1,18 @@ import { Bet } from './bet' import { calculateShareValue, deductFees, getProbability } from './calculate' -import { Contract } from './contract' +import { + calculateCpmmSale, + calculateCpmmShareValue, + getCpmmProbability, +} from './calculate-cpmm' +import { Binary, DPM, CPMM, FullContract } from './contract' import { CREATOR_FEE } from './fees' import { User } from './user' export const getSellBetInfo = ( user: User, bet: Bet, - contract: Contract, + contract: FullContract, newBetId: string ) => { const { pool, totalShares, totalBets } = contract @@ -68,3 +73,57 @@ export const getSellBetInfo = ( creatorFee, } } + +export const getCpmmSellBetInfo = ( + user: User, + bet: Bet, + contract: FullContract, + newBetId: string +) => { + const { pool } = contract + const { id: betId, amount, shares, outcome } = bet + + const { saleValue, newPool } = calculateCpmmSale(contract, bet) + + const probBefore = getCpmmProbability(pool) + const probAfter = getCpmmProbability(newPool) + + const profit = saleValue - amount + const creatorFee = CREATOR_FEE * Math.max(0, profit) + const saleAmount = deductFees(amount, profit) + + console.log( + 'SELL M$', + amount, + outcome, + 'for M$', + saleAmount, + 'creator fee: M$', + creatorFee + ) + + const newBet: Bet = { + id: newBetId, + userId: user.id, + contractId: contract.id, + amount: -saleValue, + shares: -shares, + outcome, + probBefore, + probAfter, + createdTime: Date.now(), + sale: { + amount: saleAmount, + betId, + }, + } + + const newBalance = user.balance + saleAmount + + return { + newBet, + newPool, + newBalance, + creatorFee, + } +} diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index bb78f134..0cd18b50 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -2,12 +2,21 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' import { chargeUser, getUser } from './utils' -import { Contract, outcomeType } from '../../common/contract' +import { + Binary, + Contract, + CPMM, + DPM, + FreeResponse, + FullContract, + outcomeType, +} from '../../common/contract' import { slugify } from '../../common/util/slugify' import { randomString } from '../../common/util/random' import { getNewContract } from '../../common/new-contract' import { getAnteBets, + getCpmmAnteBet, getFreeAnswerAnte, MINIMUM_ANTE, } from '../../common/antes' @@ -89,7 +98,7 @@ export const createContract = functions await contractRef.create(contract) if (ante) { - if (outcomeType === 'BINARY') { + if (outcomeType === 'BINARY' && contract.mechanism === 'dpm-2') { const yesBetDoc = firestore .collection(`contracts/${contract.id}/bets`) .doc() @@ -100,23 +109,51 @@ export const createContract = functions const { yesBet, noBet } = getAnteBets( creator, - contract, + contract as FullContract, yesBetDoc.id, noBetDoc.id ) + await yesBetDoc.set(yesBet) await noBetDoc.set(noBet) + } else if (outcomeType === 'BINARY') { + const { YES: y, NO: n } = contract.pool + const anteBet = Math.abs(y - n) + + if (anteBet) { + const betDoc = firestore + .collection(`contracts/${contract.id}/bets`) + .doc() + + const outcome = y > n ? 'NO' : 'YES' // more in YES pool if prob leans NO + + const bet = await getCpmmAnteBet( + creator, + contract as FullContract, + betDoc.id, + anteBet, + outcome + ) + + await betDoc.set(bet) + } } else if (outcomeType === 'FREE_RESPONSE') { const noneAnswerDoc = firestore .collection(`contracts/${contract.id}/answers`) .doc('0') + const noneAnswer = getNoneAnswer(contract.id, creator) await noneAnswerDoc.set(noneAnswer) const anteBetDoc = firestore .collection(`contracts/${contract.id}/bets`) .doc() - const anteBet = getFreeAnswerAnte(creator, contract, anteBetDoc.id) + + const anteBet = getFreeAnswerAnte( + creator, + contract as FullContract, + anteBetDoc.id + ) await anteBetDoc.set(anteBet) } } diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts index e37dc12c..c1947def 100644 --- a/functions/src/place-bet.ts +++ b/functions/src/place-bet.ts @@ -3,7 +3,12 @@ import * as admin from 'firebase-admin' import { Contract } from '../../common/contract' import { User } from '../../common/user' -import { getNewBinaryBetInfo, getNewMultiBetInfo } from '../../common/new-bet' +import { + getNewBinaryCpmmBetInfo, + getNewBinaryDpmBetInfo, + getNewMultiBetInfo, +} from '../../common/new-bet' +import { removeUndefinedProps } from '../../common/util/object' export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( async ( @@ -42,7 +47,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( return { status: 'error', message: 'Invalid contract' } const contract = contractSnap.data() as Contract - const { closeTime, outcomeType } = contract + const { closeTime, outcomeType, mechanism } = contract if (closeTime && Date.now() > closeTime) return { status: 'error', message: 'Trading is closed' } @@ -60,21 +65,40 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( const { newBet, newPool, newTotalShares, newTotalBets, newBalance } = outcomeType === 'BINARY' - ? getNewBinaryBetInfo( + ? mechanism === 'dpm-2' + ? getNewBinaryDpmBetInfo( + user, + outcome as 'YES' | 'NO', + amount, + contract, + newBetDoc.id + ) + : (getNewBinaryCpmmBetInfo( + user, + outcome as 'YES' | 'NO', + amount, + contract, + newBetDoc.id + ) as any) + : getNewMultiBetInfo( user, - outcome as 'YES' | 'NO', + outcome, amount, - contract, + contract as any, newBetDoc.id ) - : getNewMultiBetInfo(user, outcome, amount, contract, newBetDoc.id) transaction.create(newBetDoc, newBet) - transaction.update(contractDoc, { - pool: newPool, - totalShares: newTotalShares, - totalBets: newTotalBets, - }) + + transaction.update( + contractDoc, + removeUndefinedProps({ + pool: newPool, + totalShares: newTotalShares, + totalBets: newTotalBets, + }) + ) + transaction.update(userDoc, { balance: newBalance }) return { status: 'success', betId: newBetDoc.id } diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts index 85e67785..824edac3 100644 --- a/functions/src/resolve-market.ts +++ b/functions/src/resolve-market.ts @@ -89,7 +89,7 @@ export const resolveMarket = functions const payouts = outcomeType === 'FREE_RESPONSE' && resolutions - ? getPayoutsMultiOutcome(resolutions, contract, openBets) + ? getPayoutsMultiOutcome(resolutions, contract as any, openBets) : getPayouts(outcome, contract, openBets, resolutionProbability) console.log('payouts:', payouts) diff --git a/functions/src/sell-bet.ts b/functions/src/sell-bet.ts index a5a7b30e..68cb4cd2 100644 --- a/functions/src/sell-bet.ts +++ b/functions/src/sell-bet.ts @@ -4,7 +4,8 @@ import * as functions from 'firebase-functions' import { Contract } from '../../common/contract' import { User } from '../../common/user' import { Bet } from '../../common/bet' -import { getSellBetInfo } from '../../common/sell-bet' +import { getCpmmSellBetInfo, getSellBetInfo } from '../../common/sell-bet' +import { removeUndefinedProps } from '../../common/util/object' export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( async ( @@ -33,7 +34,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( return { status: 'error', message: 'Invalid contract' } const contract = contractSnap.data() as Contract - const { closeTime } = contract + const { closeTime, mechanism } = contract if (closeTime && Date.now() > closeTime) return { status: 'error', message: 'Trading is closed' } @@ -55,7 +56,15 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( newTotalBets, newBalance, creatorFee, - } = getSellBetInfo(user, bet, contract, newBetDoc.id) + } = + mechanism === 'dpm-2' + ? getSellBetInfo(user, bet, contract, newBetDoc.id) + : (getCpmmSellBetInfo( + user, + bet, + contract as any, + newBetDoc.id + ) as any) if (contract.creatorId === userId) { transaction.update(userDoc, { balance: newBalance + creatorFee }) @@ -74,11 +83,14 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( transaction.update(betDoc, { isSold: true }) transaction.create(newBetDoc, newBet) - transaction.update(contractDoc, { - pool: newPool, - totalShares: newTotalShares, - totalBets: newTotalBets, - }) + transaction.update( + contractDoc, + removeUndefinedProps({ + pool: newPool, + totalShares: newTotalShares, + totalBets: newTotalBets, + }) + ) return { status: 'success' } })