cpmm initial commit: common logic, cloud functions

This commit is contained in:
mantikoros 2022-03-02 16:12:27 -05:00
parent a6657a28fd
commit f830119023
13 changed files with 630 additions and 75 deletions

View File

@ -1,11 +1,50 @@
import { Bet } from './bet' import { Bet } from './bet'
import { getProbability } from './calculate' 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' import { User } from './user'
export const PHANTOM_ANTE = 0.001 export const PHANTOM_ANTE = 0.001
export const MINIMUM_ANTE = 10 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<CPMM, Binary>,
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) => { export const calcStartPool = (initialProbInt: number, ante = 0) => {
const p = initialProbInt / 100.0 const p = initialProbInt / 100.0
const totalAnte = PHANTOM_ANTE + ante const totalAnte = PHANTOM_ANTE + ante
@ -24,7 +63,7 @@ export const calcStartPool = (initialProbInt: number, ante = 0) => {
export function getAnteBets( export function getAnteBets(
creator: User, creator: User,
contract: Contract, contract: FullContract<DPM, Binary>,
yesAnteId: string, yesAnteId: string,
noAnteId: string noAnteId: string
) { ) {
@ -64,7 +103,7 @@ export function getAnteBets(
export function getFreeAnswerAnte( export function getFreeAnswerAnte(
creator: User, creator: User,
contract: Contract, contract: FullContract<DPM, FreeResponse>,
anteBetId: string anteBetId: string
) { ) {
const { totalBets, totalShares } = contract const { totalBets, totalShares } = contract

134
common/calculate-cpmm.ts Normal file
View File

@ -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<CPMM, Binary>,
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<CPMM, Binary>,
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<CPMM, Binary>,
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<CPMM, Binary>,
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<CPMM, Binary>,
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<CPMM, Binary>,
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
}

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash' import * as _ from 'lodash'
import { Bet } from './bet' import { Bet } from './bet'
import { Contract } from './contract' import { Binary, DPM, FullContract } from './contract'
import { FEES } from './fees' import { FEES } from './fees'
export function getProbability(totalShares: { [outcome: string]: number }) { export function getProbability(totalShares: { [outcome: string]: number }) {
@ -86,7 +86,7 @@ export function calculateRawShareValue(
} }
export function calculateMoneyRatio( export function calculateMoneyRatio(
contract: Contract, contract: FullContract<DPM, any>,
bet: Bet, bet: Bet,
shareValue: number shareValue: number
) { ) {
@ -112,7 +112,10 @@ export function calculateMoneyRatio(
return actual / expected return actual / expected
} }
export function calculateShareValue(contract: Contract, bet: Bet) { export function calculateShareValue(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { pool, totalShares } = contract const { pool, totalShares } = contract
const { shares, outcome } = bet const { shares, outcome } = bet
@ -124,20 +127,30 @@ export function calculateShareValue(contract: Contract, bet: Bet) {
return adjShareValue return adjShareValue
} }
export function calculateSaleAmount(contract: Contract, bet: Bet) { export function calculateSaleAmount(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { amount } = bet const { amount } = bet
const winnings = calculateShareValue(contract, bet) const winnings = calculateShareValue(contract, bet)
return deductFees(amount, winnings) return deductFees(amount, winnings)
} }
export function calculatePayout(contract: Contract, bet: Bet, outcome: string) { export function calculatePayout(
contract: FullContract<DPM, any>,
bet: Bet,
outcome: string
) {
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)
return calculateStandardPayout(contract, bet, outcome) return calculateStandardPayout(contract, bet, outcome)
} }
export function calculateCancelPayout(contract: Contract, bet: Bet) { export function calculateCancelPayout(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { totalBets, pool } = contract const { totalBets, pool } = contract
const betTotal = _.sum(Object.values(totalBets)) const betTotal = _.sum(Object.values(totalBets))
const poolTotal = _.sum(Object.values(pool)) const poolTotal = _.sum(Object.values(pool))
@ -146,7 +159,7 @@ export function calculateCancelPayout(contract: Contract, bet: Bet) {
} }
export function calculateStandardPayout( export function calculateStandardPayout(
contract: Contract, contract: FullContract<DPM, any>,
bet: Bet, bet: Bet,
outcome: string outcome: string
) { ) {
@ -166,7 +179,10 @@ export function calculateStandardPayout(
return amount + (1 - FEES) * Math.max(0, winnings - amount) return amount + (1 - FEES) * Math.max(0, winnings - amount)
} }
export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) { export function calculatePayoutAfterCorrectBet(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { totalShares, pool, totalBets } = contract const { totalShares, pool, totalBets } = contract
const { shares, amount, outcome } = bet const { shares, amount, outcome } = bet
@ -193,7 +209,7 @@ export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) {
return calculateStandardPayout(newContract, bet, outcome) return calculateStandardPayout(newContract, bet, outcome)
} }
function calculateMktPayout(contract: Contract, bet: Bet) { function calculateMktPayout(contract: FullContract<DPM, any>, bet: Bet) {
if (contract.outcomeType === 'BINARY') if (contract.outcomeType === 'BINARY')
return calculateBinaryMktPayout(contract, bet) return calculateBinaryMktPayout(contract, bet)
@ -201,7 +217,7 @@ function calculateMktPayout(contract: Contract, bet: Bet) {
const totalPool = _.sum(Object.values(pool)) const totalPool = _.sum(Object.values(pool))
const sharesSquareSum = _.sumBy( const sharesSquareSum = _.sumBy(
Object.values(totalShares), Object.values(totalShares) as number[],
(shares) => shares ** 2 (shares) => shares ** 2
) )
@ -220,7 +236,10 @@ function calculateMktPayout(contract: Contract, bet: Bet) {
return deductFees(amount, winnings) return deductFees(amount, winnings)
} }
function calculateBinaryMktPayout(contract: Contract, bet: Bet) { function calculateBinaryMktPayout(
contract: FullContract<DPM, Binary>,
bet: Bet
) {
const { resolutionProbability, totalShares, phantomShares } = contract const { resolutionProbability, totalShares, phantomShares } = contract
const p = const p =
resolutionProbability !== undefined resolutionProbability !== undefined
@ -241,7 +260,7 @@ function calculateBinaryMktPayout(contract: Contract, bet: Bet) {
return deductFees(amount, winnings) return deductFees(amount, winnings)
} }
export function resolvedPayout(contract: Contract, bet: Bet) { export function resolvedPayout(contract: FullContract<DPM, any>, bet: Bet) {
if (contract.resolution) if (contract.resolution)
return calculatePayout(contract, bet, contract.resolution) return calculatePayout(contract, bet, contract.resolution)
throw new Error('Contract was not resolved') throw new Error('Contract was not resolved')

View File

@ -1,6 +1,9 @@
import { Answer } from './answer' import { Answer } from './answer'
export type Contract = { export type FullContract<
M extends DPM | CPMM,
T extends Binary | Multi | FreeResponse
> = {
id: string id: string
slug: string // auto-generated; must be unique slug: string // auto-generated; must be unique
@ -15,16 +18,6 @@ export type Contract = {
lowercaseTags: string[] lowercaseTags: string[]
visibility: 'public' | 'unlisted' 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 createdTime: number // Milliseconds since epoch
lastUpdatedTime: number // If the question or description was changed lastUpdatedTime: number // If the question or description was changed
closeTime?: number // When no more trading is allowed closeTime?: number // When no more trading is allowed
@ -32,12 +25,48 @@ export type Contract = {
isResolved: boolean isResolved: boolean
resolutionTime?: number // When the contract creator resolved the market resolutionTime?: number // When the contract creator resolved the market
resolution?: string 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 closeEmailsSent?: number
volume24Hours: number volume24Hours: number
volume7Days: number volume7Days: number
} & M &
T
export type Contract = FullContract<DPM | CPMM, Binary | Multi | FreeResponse>
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' export type outcomeType = 'BINARY' | 'MULTI' | 'FREE_RESPONSE'

View File

@ -4,14 +4,60 @@ import {
getProbability, getProbability,
getOutcomeProbability, getOutcomeProbability,
} from './calculate' } 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' import { User } from './user'
export const getNewBinaryBetInfo = ( export const getNewBinaryCpmmBetInfo = (
user: User, user: User,
outcome: 'YES' | 'NO', outcome: 'YES' | 'NO',
amount: number, amount: number,
contract: Contract, contract: FullContract<CPMM, Binary>,
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<DPM, Binary>,
newBetId: string newBetId: string
) => { ) => {
const { YES: yesPool, NO: noPool } = contract.pool const { YES: yesPool, NO: noPool } = contract.pool
@ -61,7 +107,7 @@ export const getNewMultiBetInfo = (
user: User, user: User,
outcome: string, outcome: string,
amount: number, amount: number,
contract: Contract, contract: FullContract<DPM, Multi | FreeResponse>,
newBetId: string newBetId: string
) => { ) => {
const { pool, totalShares, totalBets } = contract const { pool, totalShares, totalBets } = contract

View File

@ -1,5 +1,12 @@
import { calcStartPool } from './antes' import { calcStartPool, calcStartCpmmPool } from './antes'
import { Contract, outcomeType } from './contract' import {
Binary,
Contract,
CPMM,
DPM,
FreeResponse,
outcomeType,
} from './contract'
import { User } from './user' import { User } from './user'
import { parseTags } from './util/parse' import { parseTags } from './util/parse'
import { removeUndefinedProps } from './util/object' import { removeUndefinedProps } from './util/object'
@ -23,13 +30,12 @@ export function getNewContract(
const propsByOutcomeType = const propsByOutcomeType =
outcomeType === 'BINARY' outcomeType === 'BINARY'
? getBinaryProps(initialProb, ante) ? getBinaryCpmmProps(initialProb, ante, creator.id) // getBinaryDpmProps(initialProb, ante)
: getFreeAnswerProps(ante) : getFreeAnswerProps(ante)
const contract: Contract = removeUndefinedProps({ const contract = removeUndefinedProps({
id, id,
slug, slug,
mechanism: 'dpm-2',
outcomeType, outcomeType,
...propsByOutcomeType, ...propsByOutcomeType,
@ -52,28 +58,55 @@ export function getNewContract(
volume7Days: 0, 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 } = const { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo } =
calcStartPool(initialProb, ante) calcStartPool(initialProb, ante)
return { const system: DPM & Binary = {
mechanism: 'dpm-2',
outcomeType: 'BINARY',
phantomShares: { YES: phantomYes, NO: phantomNo }, phantomShares: { YES: phantomYes, NO: phantomNo },
pool: { YES: poolYes, NO: poolNo }, pool: { YES: poolYes, NO: poolNo },
totalShares: { YES: sharesYes, NO: sharesNo }, totalShares: { YES: sharesYes, NO: sharesNo },
totalBets: { YES: poolYes, NO: poolNo }, 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) => { const getFreeAnswerProps = (ante: number) => {
return { const system: DPM & FreeResponse = {
mechanism: 'dpm-2',
outcomeType: 'FREE_RESPONSE',
pool: { '0': ante }, pool: { '0': ante },
totalShares: { '0': ante }, totalShares: { '0': ante },
totalBets: { '0': ante }, totalBets: { '0': ante },
answers: [], answers: [],
} }
return system
} }
const getMultiProps = ( const getMultiProps = (

View File

@ -2,10 +2,21 @@ import * as _ from 'lodash'
import { Bet } from './bet' import { Bet } from './bet'
import { deductFees, getProbability } from './calculate' 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' import { CREATOR_FEE, FEES } from './fees'
export const getCancelPayouts = (contract: Contract, bets: Bet[]) => { export const getDpmCancelPayouts = (
contract: FullContract<DPM, any>,
bets: Bet[]
) => {
const { pool } = contract const { pool } = contract
const poolTotal = _.sum(Object.values(pool)) const poolTotal = _.sum(Object.values(pool))
console.log('resolved N/A, pool M$', poolTotal) 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<CPMM, Binary>,
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<CPMM, Binary>,
outcome: string
) => {
const { creatorId, pool } = contract
return [{ userId: creatorId, payout: pool[outcome] }]
}
export const getStandardPayouts = ( export const getStandardPayouts = (
outcome: string, outcome: string,
contract: Contract, contract: FullContract<DPM, any>,
bets: Bet[] bets: Bet[]
) => { ) => {
const winningBets = bets.filter((bet) => bet.outcome === outcome) const winningBets = bets.filter((bet) => bet.outcome === outcome)
@ -57,7 +118,7 @@ export const getStandardPayouts = (
} }
export const getMktPayouts = ( export const getMktPayouts = (
contract: Contract, contract: FullContract<DPM, any>,
bets: Bet[], bets: Bet[],
resolutionProbability?: number resolutionProbability?: number
) => { ) => {
@ -99,12 +160,71 @@ export const getMktPayouts = (
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
} }
export const getMktFixedPayouts = (
contract: FullContract<CPMM, Binary>,
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<CPMM, Binary>,
p: number
) => {
const { creatorId, pool } = contract
const payout = p * pool.YES + (1 - p) * pool.NO
return [{ userId: creatorId, payout }]
}
export const getPayouts = ( export const getPayouts = (
outcome: string, outcome: string,
contract: Contract, contract: Contract,
bets: Bet[], bets: Bet[],
resolutionProbability?: number 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) { switch (outcome) {
case 'YES': case 'YES':
case 'NO': case 'NO':
@ -112,7 +232,7 @@ export const getPayouts = (
case 'MKT': case 'MKT':
return getMktPayouts(contract, bets, resolutionProbability) return getMktPayouts(contract, bets, resolutionProbability)
case 'CANCEL': case 'CANCEL':
return getCancelPayouts(contract, bets) return getDpmCancelPayouts(contract, bets)
default: default:
// Multi outcome. // Multi outcome.
return getStandardPayouts(outcome, contract, bets) return getStandardPayouts(outcome, contract, bets)
@ -121,7 +241,7 @@ export const getPayouts = (
export const getPayoutsMultiOutcome = ( export const getPayoutsMultiOutcome = (
resolutions: { [outcome: string]: number }, resolutions: { [outcome: string]: number },
contract: Contract, contract: FullContract<DPM, Multi | FreeResponse>,
bets: Bet[] bets: Bet[]
) => { ) => {
const poolTotal = _.sum(Object.values(contract.pool)) const poolTotal = _.sum(Object.values(contract.pool))

View File

@ -1,6 +1,6 @@
import * as _ from 'lodash' import * as _ from 'lodash'
import { Bet } from './bet' import { Bet } from './bet'
import { Contract } from './contract' import { Binary, Contract, FullContract } from './contract'
import { getPayouts } from './payouts' import { getPayouts } from './payouts'
export function scoreCreators(contracts: Contract[], bets: Bet[][]) { export function scoreCreators(contracts: Contract[], bets: Bet[][]) {
@ -23,7 +23,10 @@ export function scoreTraders(contracts: Contract[], bets: Bet[][]) {
return userScores return userScores
} }
export function scoreUsersByContract(contract: Contract, bets: Bet[]) { export function scoreUsersByContract(
contract: FullContract<any, Binary>,
bets: Bet[]
) {
const { resolution, resolutionProbability } = contract const { resolution, resolutionProbability } = contract
const [closedBets, openBets] = _.partition( const [closedBets, openBets] = _.partition(

View File

@ -1,13 +1,18 @@
import { Bet } from './bet' import { Bet } from './bet'
import { calculateShareValue, deductFees, getProbability } from './calculate' 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 { CREATOR_FEE } from './fees'
import { User } from './user' import { User } from './user'
export const getSellBetInfo = ( export const getSellBetInfo = (
user: User, user: User,
bet: Bet, bet: Bet,
contract: Contract, contract: FullContract<DPM, any>,
newBetId: string newBetId: string
) => { ) => {
const { pool, totalShares, totalBets } = contract const { pool, totalShares, totalBets } = contract
@ -68,3 +73,57 @@ export const getSellBetInfo = (
creatorFee, creatorFee,
} }
} }
export const getCpmmSellBetInfo = (
user: User,
bet: Bet,
contract: FullContract<CPMM, Binary>,
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,
}
}

View File

@ -2,12 +2,21 @@ import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin' import * as admin from 'firebase-admin'
import { chargeUser, getUser } from './utils' 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 { slugify } from '../../common/util/slugify'
import { randomString } from '../../common/util/random' import { randomString } from '../../common/util/random'
import { getNewContract } from '../../common/new-contract' import { getNewContract } from '../../common/new-contract'
import { import {
getAnteBets, getAnteBets,
getCpmmAnteBet,
getFreeAnswerAnte, getFreeAnswerAnte,
MINIMUM_ANTE, MINIMUM_ANTE,
} from '../../common/antes' } from '../../common/antes'
@ -89,7 +98,7 @@ export const createContract = functions
await contractRef.create(contract) await contractRef.create(contract)
if (ante) { if (ante) {
if (outcomeType === 'BINARY') { if (outcomeType === 'BINARY' && contract.mechanism === 'dpm-2') {
const yesBetDoc = firestore const yesBetDoc = firestore
.collection(`contracts/${contract.id}/bets`) .collection(`contracts/${contract.id}/bets`)
.doc() .doc()
@ -100,23 +109,51 @@ export const createContract = functions
const { yesBet, noBet } = getAnteBets( const { yesBet, noBet } = getAnteBets(
creator, creator,
contract, contract as FullContract<DPM, Binary>,
yesBetDoc.id, yesBetDoc.id,
noBetDoc.id noBetDoc.id
) )
await yesBetDoc.set(yesBet) await yesBetDoc.set(yesBet)
await noBetDoc.set(noBet) 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<CPMM, Binary>,
betDoc.id,
anteBet,
outcome
)
await betDoc.set(bet)
}
} else if (outcomeType === 'FREE_RESPONSE') { } else if (outcomeType === 'FREE_RESPONSE') {
const noneAnswerDoc = firestore const noneAnswerDoc = firestore
.collection(`contracts/${contract.id}/answers`) .collection(`contracts/${contract.id}/answers`)
.doc('0') .doc('0')
const noneAnswer = getNoneAnswer(contract.id, creator) const noneAnswer = getNoneAnswer(contract.id, creator)
await noneAnswerDoc.set(noneAnswer) await noneAnswerDoc.set(noneAnswer)
const anteBetDoc = firestore const anteBetDoc = firestore
.collection(`contracts/${contract.id}/bets`) .collection(`contracts/${contract.id}/bets`)
.doc() .doc()
const anteBet = getFreeAnswerAnte(creator, contract, anteBetDoc.id)
const anteBet = getFreeAnswerAnte(
creator,
contract as FullContract<DPM, FreeResponse>,
anteBetDoc.id
)
await anteBetDoc.set(anteBet) await anteBetDoc.set(anteBet)
} }
} }

View File

@ -3,7 +3,12 @@ import * as admin from 'firebase-admin'
import { Contract } from '../../common/contract' import { Contract } from '../../common/contract'
import { User } from '../../common/user' 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( export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
async ( async (
@ -42,7 +47,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
return { status: 'error', message: 'Invalid contract' } return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as Contract const contract = contractSnap.data() as Contract
const { closeTime, outcomeType } = contract const { closeTime, outcomeType, mechanism } = contract
if (closeTime && Date.now() > closeTime) if (closeTime && Date.now() > closeTime)
return { status: 'error', message: 'Trading is closed' } 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 } = const { newBet, newPool, newTotalShares, newTotalBets, newBalance } =
outcomeType === 'BINARY' outcomeType === 'BINARY'
? getNewBinaryBetInfo( ? mechanism === 'dpm-2'
? getNewBinaryDpmBetInfo(
user, user,
outcome as 'YES' | 'NO', outcome as 'YES' | 'NO',
amount, amount,
contract, contract,
newBetDoc.id newBetDoc.id
) )
: getNewMultiBetInfo(user, outcome, amount, contract, newBetDoc.id) : (getNewBinaryCpmmBetInfo(
user,
outcome as 'YES' | 'NO',
amount,
contract,
newBetDoc.id
) as any)
: getNewMultiBetInfo(
user,
outcome,
amount,
contract as any,
newBetDoc.id
)
transaction.create(newBetDoc, newBet) transaction.create(newBetDoc, newBet)
transaction.update(contractDoc, {
transaction.update(
contractDoc,
removeUndefinedProps({
pool: newPool, pool: newPool,
totalShares: newTotalShares, totalShares: newTotalShares,
totalBets: newTotalBets, totalBets: newTotalBets,
}) })
)
transaction.update(userDoc, { balance: newBalance }) transaction.update(userDoc, { balance: newBalance })
return { status: 'success', betId: newBetDoc.id } return { status: 'success', betId: newBetDoc.id }

View File

@ -89,7 +89,7 @@ export const resolveMarket = functions
const payouts = const payouts =
outcomeType === 'FREE_RESPONSE' && resolutions outcomeType === 'FREE_RESPONSE' && resolutions
? getPayoutsMultiOutcome(resolutions, contract, openBets) ? getPayoutsMultiOutcome(resolutions, contract as any, openBets)
: getPayouts(outcome, contract, openBets, resolutionProbability) : getPayouts(outcome, contract, openBets, resolutionProbability)
console.log('payouts:', payouts) console.log('payouts:', payouts)

View File

@ -4,7 +4,8 @@ import * as functions from 'firebase-functions'
import { Contract } from '../../common/contract' import { Contract } from '../../common/contract'
import { User } from '../../common/user' import { User } from '../../common/user'
import { Bet } from '../../common/bet' 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( export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
async ( async (
@ -33,7 +34,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
return { status: 'error', message: 'Invalid contract' } return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as Contract const contract = contractSnap.data() as Contract
const { closeTime } = contract const { closeTime, mechanism } = contract
if (closeTime && Date.now() > closeTime) if (closeTime && Date.now() > closeTime)
return { status: 'error', message: 'Trading is closed' } return { status: 'error', message: 'Trading is closed' }
@ -55,7 +56,15 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
newTotalBets, newTotalBets,
newBalance, newBalance,
creatorFee, 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) { if (contract.creatorId === userId) {
transaction.update(userDoc, { balance: newBalance + creatorFee }) 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.update(betDoc, { isSold: true })
transaction.create(newBetDoc, newBet) transaction.create(newBetDoc, newBet)
transaction.update(contractDoc, { transaction.update(
contractDoc,
removeUndefinedProps({
pool: newPool, pool: newPool,
totalShares: newTotalShares, totalShares: newTotalShares,
totalBets: newTotalBets, totalBets: newTotalBets,
}) })
)
return { status: 'success' } return { status: 'success' }
}) })