cpmm initial commit: common logic, cloud functions
This commit is contained in:
parent
a6657a28fd
commit
f830119023
|
@ -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<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) => {
|
||||
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<DPM, Binary>,
|
||||
yesAnteId: string,
|
||||
noAnteId: string
|
||||
) {
|
||||
|
@ -64,7 +103,7 @@ export function getAnteBets(
|
|||
|
||||
export function getFreeAnswerAnte(
|
||||
creator: User,
|
||||
contract: Contract,
|
||||
contract: FullContract<DPM, FreeResponse>,
|
||||
anteBetId: string
|
||||
) {
|
||||
const { totalBets, totalShares } = contract
|
||||
|
|
134
common/calculate-cpmm.ts
Normal file
134
common/calculate-cpmm.ts
Normal 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
|
||||
}
|
|
@ -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<DPM, any>,
|
||||
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<DPM, any>,
|
||||
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<DPM, any>,
|
||||
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<DPM, any>,
|
||||
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<DPM, any>,
|
||||
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<DPM, any>,
|
||||
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<DPM, any>,
|
||||
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<DPM, any>, 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<DPM, Binary>,
|
||||
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<DPM, any>, bet: Bet) {
|
||||
if (contract.resolution)
|
||||
return calculatePayout(contract, bet, contract.resolution)
|
||||
throw new Error('Contract was not resolved')
|
||||
|
|
|
@ -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<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'
|
||||
|
|
|
@ -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<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
|
||||
) => {
|
||||
const { YES: yesPool, NO: noPool } = contract.pool
|
||||
|
@ -61,7 +107,7 @@ export const getNewMultiBetInfo = (
|
|||
user: User,
|
||||
outcome: string,
|
||||
amount: number,
|
||||
contract: Contract,
|
||||
contract: FullContract<DPM, Multi | FreeResponse>,
|
||||
newBetId: string
|
||||
) => {
|
||||
const { pool, totalShares, totalBets } = contract
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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<DPM, any>,
|
||||
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<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 = (
|
||||
outcome: string,
|
||||
contract: Contract,
|
||||
contract: FullContract<DPM, any>,
|
||||
bets: Bet[]
|
||||
) => {
|
||||
const winningBets = bets.filter((bet) => bet.outcome === outcome)
|
||||
|
@ -57,7 +118,7 @@ export const getStandardPayouts = (
|
|||
}
|
||||
|
||||
export const getMktPayouts = (
|
||||
contract: Contract,
|
||||
contract: FullContract<DPM, any>,
|
||||
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<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 = (
|
||||
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<DPM, Multi | FreeResponse>,
|
||||
bets: Bet[]
|
||||
) => {
|
||||
const poolTotal = _.sum(Object.values(contract.pool))
|
||||
|
|
|
@ -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<any, Binary>,
|
||||
bets: Bet[]
|
||||
) {
|
||||
const { resolution, resolutionProbability } = contract
|
||||
|
||||
const [closedBets, openBets] = _.partition(
|
||||
|
|
|
@ -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<DPM, any>,
|
||||
newBetId: string
|
||||
) => {
|
||||
const { pool, totalShares, totalBets } = contract
|
||||
|
@ -68,3 +73,57 @@ export const getSellBetInfo = (
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<DPM, Binary>,
|
||||
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<CPMM, Binary>,
|
||||
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<DPM, FreeResponse>,
|
||||
anteBetDoc.id
|
||||
)
|
||||
await anteBetDoc.set(anteBet)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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' }
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue
Block a user