* cpmm initial commit: common logic, cloud functions

* remove unnecessary property

* contract type

* rename 'calculate.ts' => 'calculate-dpm.ts'

* rename dpm calculations

* use focus hook

* mechanism-agnostic calculations

* bet panel: use new calculations

* use new calculations

* delete markets cloud function

* use correct contract type in scripts / functions

* calculate fixed payouts; bets list calculations

* new bet: use calculateCpmmPurchase

* getOutcomeProbabilityAfterBet

* use deductFixedFees

* fix auto-refactor

* fix antes

* separate logic to payouts-dpm, payouts-fixed

* liquidity provision tracking

* remove comment

* liquidity label

* create liquidity provision even if no ante bet

* liquidity fee

* use all bets for getFixedCancelPayouts

* updateUserBalance: allow negative balances

* store initialProbability in contracts

* turn on liquidity fee; turn off creator fee

* Include time param in tweet url, so image preview is re-fetched

* share redemption

* cpmm ContractBetsTable display

* formatMoney: handle minus zero

* filter out redemption bets

* track fees on contract and bets; change fee schedule for cpmm markets; only pay out creator fees at resolution

* small fixes

* small fixes

* Redeem shares pays back loans first

* Fix initial point on graph

* calculateCpmmPurchase: deduct creator fee

* Filter out redemption bets from feed

* set env to dev for user-testing purposes

* creator fees messaging

* new cfmm: k = y^(1-p) * n^p

* addCpmmLiquidity

* correct price function

* enable fees

* handle overflow

* liquidity provision tracking

* raise fees

* Fix merge error

* fix dpm free response payout for single outcome

* Fix DPM payout calculation

* Remove hardcoding as dev

Co-authored-by: James Grugett <jahooma@gmail.com>
This commit is contained in:
mantikoros 2022-03-15 17:27:51 -05:00 committed by GitHub
parent bd62d8fbcd
commit c183e00d47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 2024 additions and 865 deletions

View File

@ -1,34 +1,44 @@
import { Bet } from './bet'
import { getProbability } from './calculate'
import { Contract } from './contract'
import { getDpmProbability } from './calculate-dpm'
import { Binary, CPMM, DPM, FreeResponse, FullContract } from './contract'
import { User } from './user'
import { LiquidityProvision } from './liquidity-provision'
import { noFees } from './fees'
export const PHANTOM_ANTE = 0.001
export const MINIMUM_ANTE = 10
export const calcStartPool = (initialProbInt: number, ante = 0) => {
const p = initialProbInt / 100.0
const totalAnte = PHANTOM_ANTE + ante
export function getCpmmInitialLiquidity(
creator: User,
contract: FullContract<CPMM, Binary>,
anteId: string,
amount: number
) {
const { createdTime, p } = contract
const sharesYes = Math.sqrt(p * totalAnte ** 2)
const sharesNo = Math.sqrt(totalAnte ** 2 - sharesYes ** 2)
const lp: LiquidityProvision = {
id: anteId,
userId: creator.id,
contractId: contract.id,
createdTime,
isAnte: true,
const poolYes = p * ante
const poolNo = (1 - p) * ante
amount: amount,
liquidity: amount,
p: p,
pool: { YES: 0, NO: 0 },
}
const phantomYes = Math.sqrt(p) * PHANTOM_ANTE
const phantomNo = Math.sqrt(1 - p) * PHANTOM_ANTE
return { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo }
return lp
}
export function getAnteBets(
creator: User,
contract: Contract,
contract: FullContract<DPM, Binary>,
yesAnteId: string,
noAnteId: string
) {
const p = getProbability(contract.totalShares)
const p = getDpmProbability(contract.totalShares)
const ante = contract.totalBets.YES + contract.totalBets.NO
const { createdTime } = contract
@ -44,6 +54,7 @@ export function getAnteBets(
probAfter: p,
createdTime,
isAnte: true,
fees: noFees,
}
const noBet: Bet = {
@ -57,6 +68,7 @@ export function getAnteBets(
probAfter: p,
createdTime,
isAnte: true,
fees: noFees,
}
return { yesBet, noBet }
@ -64,7 +76,7 @@ export function getAnteBets(
export function getFreeAnswerAnte(
creator: User,
contract: Contract,
contract: FullContract<DPM, FreeResponse>,
anteBetId: string
) {
const { totalBets, totalShares } = contract
@ -84,6 +96,7 @@ export function getFreeAnswerAnte(
probAfter: 1,
createdTime,
isAnte: true,
fees: noFees,
}
return anteBet

View File

@ -1,3 +1,5 @@
import { Fees } from './fees'
export type Bet = {
id: string
userId: string
@ -6,7 +8,7 @@ export type Bet = {
amount: number // bet size; negative if SELL bet
loanAmount?: number
outcome: string
shares: number // dynamic parimutuel pool weight; negative if SELL bet
shares: number // dynamic parimutuel pool weight or fixed ; negative if SELL bet
probBefore: number
probAfter: number
@ -17,8 +19,12 @@ export type Bet = {
// TODO: add sale time?
}
fees: Fees
isSold?: boolean // true if this BUY bet has been sold
isAnte?: boolean
isLiquidityProvision?: boolean
isRedemption?: boolean
createdTime: number
}

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

@ -0,0 +1,217 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { Binary, CPMM, FullContract } from './contract'
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, noFees, PLATFORM_FEE } from './fees'
export function getCpmmProbability(
pool: { [outcome: string]: number },
p: number
) {
const { YES, NO } = pool
return (p * NO) / ((1 - p) * YES + p * NO)
}
export function getCpmmProbabilityAfterBetBeforeFees(
contract: FullContract<CPMM, Binary>,
outcome: string,
bet: number
) {
const { pool, p } = contract
const shares = calculateCpmmShares(pool, p, bet, outcome)
const { YES: y, NO: n } = pool
const [newY, newN] =
outcome === 'YES'
? [y - shares + bet, n + bet]
: [y + bet, n - shares + bet]
return getCpmmProbability({ YES: newY, NO: newN }, p)
}
export function getCpmmOutcomeProbabilityAfterBet(
contract: FullContract<CPMM, Binary>,
outcome: string,
bet: number
) {
const { newPool } = calculateCpmmPurchase(contract, bet, outcome)
const p = getCpmmProbability(newPool, contract.p)
return outcome === 'NO' ? 1 - p : p
}
// before liquidity fee
function calculateCpmmShares(
pool: {
[outcome: string]: number
},
p: number,
bet: number,
betChoice: string
) {
const { YES: y, NO: n } = pool
const k = y ** p * n ** (1 - p)
return betChoice === 'YES'
? // https://www.wolframalpha.com/input?i=%28y%2Bb-s%29%5E%28p%29*%28n%2Bb%29%5E%281-p%29+%3D+k%2C+solve+s
y + bet - (k * (bet + n) ** (p - 1)) ** (1 / p)
: n + bet - (k * (bet + y) ** -p) ** (1 / (1 - p))
}
export function getCpmmLiquidityFee(
contract: FullContract<CPMM, Binary>,
bet: number,
outcome: string
) {
const prob = getCpmmProbabilityAfterBetBeforeFees(contract, outcome, bet)
const betP = outcome === 'YES' ? 1 - prob : prob
const liquidityFee = LIQUIDITY_FEE * betP * bet
const platformFee = PLATFORM_FEE * betP * bet
const creatorFee = CREATOR_FEE * betP * bet
const fees: Fees = { liquidityFee, platformFee, creatorFee }
const totalFees = liquidityFee + platformFee + creatorFee
const remainingBet = bet - totalFees
return { remainingBet, fees }
}
export function calculateCpmmSharesAfterFee(
contract: FullContract<CPMM, Binary>,
bet: number,
outcome: string
) {
const { pool, p } = contract
const { remainingBet } = getCpmmLiquidityFee(contract, bet, outcome)
return calculateCpmmShares(pool, p, remainingBet, outcome)
}
export function calculateCpmmPurchase(
contract: FullContract<CPMM, Binary>,
bet: number,
outcome: string
) {
const { pool, p } = contract
const { remainingBet, fees } = getCpmmLiquidityFee(contract, bet, outcome)
// const remainingBet = bet
// const fees = noFees
const shares = calculateCpmmShares(pool, p, remainingBet, outcome)
const { YES: y, NO: n } = pool
const { liquidityFee: fee } = fees
const [newY, newN] =
outcome === 'YES'
? [y - shares + remainingBet + fee, n + remainingBet + fee]
: [y + remainingBet + fee, n - shares + remainingBet + fee]
const postBetPool = { YES: newY, NO: newN }
const { newPool, newP } = addCpmmLiquidity(postBetPool, p, fee)
return { shares, newPool, newP, fees }
}
export function calculateCpmmShareValue(
contract: FullContract<CPMM, Binary>,
shares: number,
outcome: string
) {
const { pool } = contract
const { YES: y, NO: n } = pool
// TODO: calculate using new function
const poolChange = outcome === 'YES' ? shares + y - n : shares + n - y
const k = y * n
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 rawSaleValue = calculateCpmmShareValue(contract, shares, outcome)
const { fees, remainingBet: saleValue } = getCpmmLiquidityFee(
contract,
rawSaleValue,
outcome === 'YES' ? 'NO' : 'YES'
)
const { pool } = contract
const { YES: y, NO: n } = pool
const { liquidityFee: fee } = fees
const [newY, newN] =
outcome === 'YES'
? [y + shares - saleValue + fee, n - saleValue + fee]
: [y - saleValue + fee, n + shares - saleValue + fee]
const newPool = { YES: newY, NO: newN }
return { saleValue, newPool, fees }
}
export function getCpmmProbabilityAfterSale(
contract: FullContract<CPMM, Binary>,
bet: Bet
) {
const { newPool } = calculateCpmmSale(contract, bet)
return getCpmmProbability(newPool, contract.p)
}
export function getCpmmLiquidity(
pool: { [outcome: string]: number },
p: number
) {
const { YES, NO } = pool
return YES ** p * NO ** (1 - p)
}
export function addCpmmLiquidity(
pool: { [outcome: string]: number },
p: number,
amount: number
) {
const prob = getCpmmProbability(pool, p)
//https://www.wolframalpha.com/input?i=p%28n%2Bb%29%2F%28%281-p%29%28y%2Bb%29%2Bp%28n%2Bb%29%29%3Dq%2C+solve+p
const { YES: y, NO: n } = pool
const numerator = prob * (amount + y)
const denominator = amount - n * (prob - 1) + prob * y
const newP = numerator / denominator
const newPool = { YES: y + amount, NO: n + amount }
const oldLiquidity = getCpmmLiquidity(pool, newP)
const newLiquidity = getCpmmLiquidity(newPool, newP)
const liquidity = newLiquidity - oldLiquidity
return { newPool, liquidity, newP }
}
// export function removeCpmmLiquidity(
// contract: FullContract<CPMM, Binary>,
// liquidity: number
// ) {
// const { YES, NO } = contract.pool
// const poolLiquidity = getCpmmLiquidity({ YES, NO })
// const p = getCpmmProbability({ YES, NO }, contract.p)
// const f = liquidity / poolLiquidity
// const [payoutYes, payoutNo] = [f * YES, f * NO]
// const betAmount = Math.abs(payoutYes - payoutNo)
// const betOutcome = p >= 0.5 ? 'NO' : 'YES' // opposite side as adding liquidity
// const payout = Math.min(payoutYes, payoutNo)
// const newPool = { YES: YES - payoutYes, NO: NO - payoutNo }
// return { newPool, payout, betAmount, betOutcome }
// }

293
common/calculate-dpm.ts Normal file
View File

@ -0,0 +1,293 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { Binary, DPM, FullContract } from './contract'
import { DPM_FEES } from './fees'
export function getDpmProbability(totalShares: { [outcome: string]: number }) {
// For binary contracts only.
return getDpmOutcomeProbability(totalShares, 'YES')
}
export function getDpmOutcomeProbability(
totalShares: {
[outcome: string]: number
},
outcome: string
) {
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
const shares = totalShares[outcome] ?? 0
return shares ** 2 / squareSum
}
export function getDpmOutcomeProbabilityAfterBet(
totalShares: {
[outcome: string]: number
},
outcome: string,
bet: number
) {
const shares = calculateDpmShares(totalShares, bet, outcome)
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
return getDpmOutcomeProbability(newTotalShares, outcome)
}
export function getDpmProbabilityAfterSale(
totalShares: {
[outcome: string]: number
},
outcome: string,
shares: number
) {
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares - shares }
const predictionOutcome = outcome === 'NO' ? 'YES' : outcome
return getDpmOutcomeProbability(newTotalShares, predictionOutcome)
}
export function calculateDpmShares(
totalShares: {
[outcome: string]: number
},
bet: number,
betChoice: string
) {
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
const shares = totalShares[betChoice] ?? 0
const c = 2 * bet * Math.sqrt(squareSum)
return Math.sqrt(bet ** 2 + shares ** 2 + c) - shares
}
export function calculateDpmRawShareValue(
totalShares: {
[outcome: string]: number
},
shares: number,
betChoice: string
) {
const currentValue = Math.sqrt(
_.sumBy(Object.values(totalShares), (shares) => shares ** 2)
)
const postSaleValue = Math.sqrt(
_.sumBy(Object.keys(totalShares), (outcome) =>
outcome === betChoice
? Math.max(0, totalShares[outcome] - shares) ** 2
: totalShares[outcome] ** 2
)
)
return currentValue - postSaleValue
}
export function calculateDpmMoneyRatio(
contract: FullContract<DPM, any>,
bet: Bet,
shareValue: number
) {
const { totalShares, totalBets, pool } = contract
const { outcome, amount } = bet
const p = getDpmOutcomeProbability(totalShares, outcome)
const actual = _.sum(Object.values(pool)) - shareValue
const betAmount = p * amount
const expected =
_.sumBy(
Object.keys(totalBets),
(outcome) =>
getDpmOutcomeProbability(totalShares, outcome) *
(totalBets as { [outcome: string]: number })[outcome]
) - betAmount
if (actual <= 0 || expected <= 0) return 0
return actual / expected
}
export function calculateDpmShareValue(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { pool, totalShares } = contract
const { shares, outcome } = bet
const shareValue = calculateDpmRawShareValue(totalShares, shares, outcome)
const f = calculateDpmMoneyRatio(contract, bet, shareValue)
const myPool = pool[outcome]
const adjShareValue = Math.min(Math.min(1, f) * shareValue, myPool)
return adjShareValue
}
export function calculateDpmSaleAmount(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { amount } = bet
const winnings = calculateDpmShareValue(contract, bet)
return deductDpmFees(amount, winnings)
}
export function calculateDpmPayout(
contract: FullContract<DPM, any>,
bet: Bet,
outcome: string
) {
if (outcome === 'CANCEL') return calculateDpmCancelPayout(contract, bet)
if (outcome === 'MKT') return calculateMktDpmPayout(contract, bet)
return calculateStandardDpmPayout(contract, bet, outcome)
}
export function calculateDpmCancelPayout(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { totalBets, pool } = contract
const betTotal = _.sum(Object.values(totalBets))
const poolTotal = _.sum(Object.values(pool))
return (bet.amount / betTotal) * poolTotal
}
export function calculateStandardDpmPayout(
contract: FullContract<DPM, any>,
bet: Bet,
outcome: string
) {
const { amount, outcome: betOutcome, shares } = bet
if (betOutcome !== outcome) return 0
const { totalShares, phantomShares, pool } = contract
if (!totalShares[outcome]) return 0
const poolTotal = _.sum(Object.values(pool))
const total =
totalShares[outcome] - (phantomShares ? phantomShares[outcome] : 0)
const winnings = (shares / total) * poolTotal
// profit can be negative if using phantom shares
return amount + (1 - DPM_FEES) * Math.max(0, winnings - amount)
}
export function calculateDpmPayoutAfterCorrectBet(
contract: FullContract<DPM, any>,
bet: Bet
) {
const { totalShares, pool, totalBets } = contract
const { shares, amount, outcome } = bet
const prevShares = totalShares[outcome] ?? 0
const prevPool = pool[outcome] ?? 0
const prevTotalBet = totalBets[outcome] ?? 0
const newContract = {
...contract,
totalShares: {
...totalShares,
[outcome]: prevShares + shares,
},
pool: {
...pool,
[outcome]: prevPool + amount,
},
totalBets: {
...totalBets,
[outcome]: prevTotalBet + amount,
},
}
return calculateStandardDpmPayout(newContract, bet, outcome)
}
function calculateMktDpmPayout(contract: FullContract<DPM, any>, bet: Bet) {
if (contract.outcomeType === 'BINARY')
return calculateBinaryMktDpmPayout(contract, bet)
const { totalShares, pool } = contract
const totalPool = _.sum(Object.values(pool))
const sharesSquareSum = _.sumBy(
Object.values(totalShares) as number[],
(shares) => shares ** 2
)
const weightedShareTotal = _.sumBy(Object.keys(totalShares), (outcome) => {
// Avoid O(n^2) by reusing sharesSquareSum for prob.
const shares = totalShares[outcome]
const prob = shares ** 2 / sharesSquareSum
return prob * shares
})
const { outcome, amount, shares } = bet
const betP = getDpmOutcomeProbability(totalShares, outcome)
const winnings = ((betP * shares) / weightedShareTotal) * totalPool
return deductDpmFees(amount, winnings)
}
function calculateBinaryMktDpmPayout(
contract: FullContract<DPM, Binary>,
bet: Bet
) {
const { resolutionProbability, totalShares, phantomShares } = contract
const p =
resolutionProbability !== undefined
? resolutionProbability
: getDpmProbability(totalShares)
const pool = contract.pool.YES + contract.pool.NO
const weightedShareTotal =
p * (totalShares.YES - (phantomShares?.YES ?? 0)) +
(1 - p) * (totalShares.NO - (phantomShares?.NO ?? 0))
const { outcome, amount, shares } = bet
const betP = outcome === 'YES' ? p : 1 - p
const winnings = ((betP * shares) / weightedShareTotal) * pool
return deductDpmFees(amount, winnings)
}
export function resolvedDpmPayout(contract: FullContract<DPM, any>, bet: Bet) {
if (contract.resolution)
return calculateDpmPayout(contract, bet, contract.resolution)
throw new Error('Contract was not resolved')
}
export const deductDpmFees = (betAmount: number, winnings: number) => {
return winnings > betAmount
? betAmount + (1 - DPM_FEES) * (winnings - betAmount)
: winnings
}
export const calcDpmInitialPool = (
initialProbInt: number,
ante: number,
phantomAnte: number
) => {
const p = initialProbInt / 100.0
const totalAnte = phantomAnte + ante
const sharesYes = Math.sqrt(p * totalAnte ** 2)
const sharesNo = Math.sqrt(totalAnte ** 2 - sharesYes ** 2)
const poolYes = p * ante
const poolNo = (1 - p) * ante
const phantomYes = Math.sqrt(p) * phantomAnte
const phantomNo = Math.sqrt(1 - p) * phantomAnte
return { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo }
}

View File

@ -0,0 +1,41 @@
import { Bet } from './bet'
import { getProbability } from './calculate'
import { Binary, FixedPayouts, FullContract } from './contract'
export function calculateFixedPayout(
contract: FullContract<FixedPayouts, 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 { outcome: betOutcome, shares } = bet
if (betOutcome !== outcome) return 0
return shares
}
function calculateFixedMktPayout(
contract: FullContract<FixedPayouts, Binary>,
bet: Bet
) {
const { resolutionProbability } = contract
const p =
resolutionProbability !== undefined
? resolutionProbability
: getProbability(contract)
const { outcome, shares } = bet
const betP = outcome === 'YES' ? p : 1 - p
return betP * shares
}

View File

@ -1,254 +1,111 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { Contract } from './contract'
import { FEES } from './fees'
import {
calculateCpmmSale,
getCpmmProbability,
getCpmmOutcomeProbabilityAfterBet,
getCpmmProbabilityAfterSale,
calculateCpmmSharesAfterFee,
} from './calculate-cpmm'
import {
calculateDpmPayout,
calculateDpmPayoutAfterCorrectBet,
calculateDpmSaleAmount,
calculateDpmShares,
getDpmOutcomeProbability,
getDpmProbability,
getDpmOutcomeProbabilityAfterBet,
getDpmProbabilityAfterSale,
} from './calculate-dpm'
import { calculateFixedPayout } from './calculate-fixed-payouts'
import { Binary, Contract, CPMM, DPM, FullContract } from './contract'
export function getProbability(totalShares: { [outcome: string]: number }) {
// For binary contracts only.
return getOutcomeProbability(totalShares, 'YES')
export function getProbability(contract: FullContract<DPM | CPMM, Binary>) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbability(contract.pool, contract.p)
: getDpmProbability(contract.totalShares)
}
export function getOutcomeProbability(
totalShares: {
[outcome: string]: number
},
outcome: string
export function getInitialProbability(
contract: FullContract<DPM | CPMM, Binary>
) {
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
const shares = totalShares[outcome] ?? 0
return shares ** 2 / squareSum
return (
contract.initialProbability ??
(contract.mechanism === 'cpmm-1'
? getCpmmProbability(contract.pool, contract.p)
: getDpmProbability(contract.phantomShares ?? contract.totalShares))
)
}
export function getProbabilityAfterBet(
totalShares: {
[outcome: string]: number
},
export function getOutcomeProbability(contract: Contract, outcome: string) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbability(contract.pool, contract.p)
: getDpmOutcomeProbability(contract.totalShares, outcome)
}
export function getOutcomeProbabilityAfterBet(
contract: Contract,
outcome: string,
bet: number
) {
const shares = calculateShares(totalShares, bet, outcome)
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
return getOutcomeProbability(newTotalShares, outcome)
}
export function getProbabilityAfterSale(
totalShares: {
[outcome: string]: number
},
outcome: string,
shares: number
) {
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares - shares }
const predictionOutcome = outcome === 'NO' ? 'YES' : outcome
return getOutcomeProbability(newTotalShares, predictionOutcome)
return contract.mechanism === 'cpmm-1'
? getCpmmOutcomeProbabilityAfterBet(
contract as FullContract<CPMM, Binary>,
outcome,
bet
)
: getDpmOutcomeProbabilityAfterBet(contract.totalShares, outcome, bet)
}
export function calculateShares(
totalShares: {
[outcome: string]: number
},
contract: Contract,
bet: number,
betChoice: string
) {
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
const shares = totalShares[betChoice] ?? 0
const c = 2 * bet * Math.sqrt(squareSum)
return Math.sqrt(bet ** 2 + shares ** 2 + c) - shares
}
export function calculateRawShareValue(
totalShares: {
[outcome: string]: number
},
shares: number,
betChoice: string
) {
const currentValue = Math.sqrt(
_.sumBy(Object.values(totalShares), (shares) => shares ** 2)
return contract.mechanism === 'cpmm-1'
? calculateCpmmSharesAfterFee(
contract as FullContract<CPMM, Binary>,
bet,
betChoice
)
const postSaleValue = Math.sqrt(
_.sumBy(Object.keys(totalShares), (outcome) =>
outcome === betChoice
? Math.max(0, totalShares[outcome] - shares) ** 2
: totalShares[outcome] ** 2
)
)
return currentValue - postSaleValue
}
export function calculateMoneyRatio(
contract: Contract,
bet: Bet,
shareValue: number
) {
const { totalShares, totalBets, pool } = contract
const { outcome, amount } = bet
const p = getOutcomeProbability(totalShares, outcome)
const actual = _.sum(Object.values(pool)) - shareValue
const betAmount = p * amount
const expected =
_.sumBy(
Object.keys(totalBets),
(outcome) =>
getOutcomeProbability(totalShares, outcome) *
(totalBets as { [outcome: string]: number })[outcome]
) - betAmount
if (actual <= 0 || expected <= 0) return 0
return actual / expected
}
export function calculateShareValue(contract: Contract, bet: Bet) {
const { pool, totalShares } = contract
const { shares, outcome } = bet
const shareValue = calculateRawShareValue(totalShares, shares, outcome)
const f = calculateMoneyRatio(contract, bet, shareValue)
const myPool = pool[outcome]
const adjShareValue = Math.min(Math.min(1, f) * shareValue, myPool)
return adjShareValue
: calculateDpmShares(contract.totalShares, bet, betChoice)
}
export function calculateSaleAmount(contract: Contract, bet: Bet) {
const { amount } = bet
const winnings = calculateShareValue(contract, bet)
return deductFees(amount, winnings)
}
export function calculatePayout(contract: Contract, 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) {
const { totalBets, pool } = contract
const betTotal = _.sum(Object.values(totalBets))
const poolTotal = _.sum(Object.values(pool))
return (bet.amount / betTotal) * poolTotal
}
export function calculateStandardPayout(
contract: Contract,
bet: Bet,
outcome: string
) {
const { amount, outcome: betOutcome, shares } = bet
if (betOutcome !== outcome) return 0
const { totalShares, phantomShares, pool } = contract
if (!totalShares[outcome]) return 0
const poolTotal = _.sum(Object.values(pool))
const total =
totalShares[outcome] - (phantomShares ? phantomShares[outcome] : 0)
const winnings = (shares / total) * poolTotal
// profit can be negative if using phantom shares
return amount + (1 - FEES) * Math.max(0, winnings - amount)
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
? calculateCpmmSale(contract, bet).saleValue
: calculateDpmSaleAmount(contract, bet)
}
export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) {
const { totalShares, pool, totalBets } = contract
const { shares, amount, outcome } = bet
const prevShares = totalShares[outcome] ?? 0
const prevPool = pool[outcome] ?? 0
const prevTotalBet = totalBets[outcome] ?? 0
const newContract = {
...contract,
totalShares: {
...totalShares,
[outcome]: prevShares + shares,
},
pool: {
...pool,
[outcome]: prevPool + amount,
},
totalBets: {
...totalBets,
[outcome]: prevTotalBet + amount,
},
}
return calculateStandardPayout(newContract, bet, outcome)
return contract.mechanism === 'cpmm-1'
? bet.shares
: calculateDpmPayoutAfterCorrectBet(contract, bet)
}
function calculateMktPayout(contract: Contract, bet: Bet) {
if (contract.outcomeType === 'BINARY')
return calculateBinaryMktPayout(contract, bet)
const { totalShares, pool } = contract
const totalPool = _.sum(Object.values(pool))
const sharesSquareSum = _.sumBy(
Object.values(totalShares),
(shares) => shares ** 2
export function getProbabilityAfterSale(
contract: Contract,
outcome: string,
shares: number
) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbabilityAfterSale(
contract as FullContract<CPMM, Binary>,
{ shares, outcome } as Bet
)
const weightedShareTotal = _.sumBy(Object.keys(totalShares), (outcome) => {
// Avoid O(n^2) by reusing sharesSquareSum for prob.
const shares = totalShares[outcome]
const prob = shares ** 2 / sharesSquareSum
return prob * shares
})
const { outcome, amount, shares } = bet
const betP = getOutcomeProbability(totalShares, outcome)
const winnings = ((betP * shares) / weightedShareTotal) * totalPool
return deductFees(amount, winnings)
: getDpmProbabilityAfterSale(contract.totalShares, outcome, shares)
}
function calculateBinaryMktPayout(contract: Contract, bet: Bet) {
const { resolutionProbability, totalShares, phantomShares } = contract
const p =
resolutionProbability !== undefined
? resolutionProbability
: getProbability(totalShares)
const pool = contract.pool.YES + contract.pool.NO
const weightedShareTotal =
p * (totalShares.YES - (phantomShares?.YES ?? 0)) +
(1 - p) * (totalShares.NO - (phantomShares?.NO ?? 0))
const { outcome, amount, shares } = bet
const betP = outcome === 'YES' ? p : 1 - p
const winnings = ((betP * shares) / weightedShareTotal) * pool
return deductFees(amount, winnings)
export function calculatePayout(contract: Contract, bet: Bet, outcome: string) {
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
? calculateFixedPayout(contract, bet, outcome)
: calculateDpmPayout(contract, bet, outcome)
}
export function resolvedPayout(contract: Contract, bet: Bet) {
if (contract.resolution)
return calculatePayout(contract, bet, contract.resolution)
throw new Error('Contract was not resolved')
}
const outcome = contract.resolution
if (!outcome) throw new Error('Contract not resolved')
export const deductFees = (betAmount: number, winnings: number) => {
return winnings > betAmount
? betAmount + (1 - FEES) * (winnings - betAmount)
: winnings
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
? calculateFixedPayout(contract, bet, outcome)
: calculateDpmPayout(contract, bet, outcome)
}

View File

@ -1,6 +1,10 @@
import { Answer } from './answer'
import { Fees } from './fees'
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 +19,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 +26,52 @@ 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
collectedFees: Fees
} & 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 }
p: number // probability constant in y^p * n^(1-p) = k
totalLiquidity: number // in M$
}
export type FixedPayouts = CPMM
export type Binary = {
outcomeType: 'BINARY'
initialProbability: number
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'

View File

@ -1,4 +1,19 @@
export const PLATFORM_FEE = 0.01
export const CREATOR_FEE = 0.04
export const PLATFORM_FEE = 0.02
export const CREATOR_FEE = 0.08
export const LIQUIDITY_FEE = 0.08
export const FEES = PLATFORM_FEE + CREATOR_FEE
export const DPM_PLATFORM_FEE = 0.01
export const DPM_CREATOR_FEE = 0.04
export const DPM_FEES = DPM_PLATFORM_FEE + DPM_CREATOR_FEE
export type Fees = {
creatorFee: number
platformFee: number
liquidityFee: number
}
export const noFees: Fees = {
creatorFee: 0,
platformFee: 0,
liquidityFee: 0,
}

View File

@ -0,0 +1,13 @@
export type LiquidityProvision = {
id: string
userId: string
contractId: string
createdTime: number
isAnte?: boolean
amount: number // M$ quantity
pool: { [outcome: string]: number } // pool shares before provision
liquidity: number // change in constant k after provision
p: number // p constant after provision
}

View File

@ -1,19 +1,69 @@
import * as _ from 'lodash'
import { Bet, MAX_LOAN_PER_CONTRACT } from './bet'
import {
calculateShares,
getProbability,
getOutcomeProbability,
} from './calculate'
import { Contract } from './contract'
calculateDpmShares,
getDpmProbability,
getDpmOutcomeProbability,
} from './calculate-dpm'
import { calculateCpmmPurchase, getCpmmProbability } from './calculate-cpmm'
import {
Binary,
CPMM,
DPM,
FreeResponse,
FullContract,
Multi,
} from './contract'
import { User } from './user'
import { noFees } from './fees'
export const getNewBinaryBetInfo = (
export const getNewBinaryCpmmBetInfo = (
user: User,
outcome: 'YES' | 'NO',
amount: number,
contract: FullContract<CPMM, Binary>,
loanAmount: number,
newBetId: string
) => {
const { shares, newPool, newP, fees } = calculateCpmmPurchase(
contract,
amount,
outcome
)
const newBalance = user.balance - (amount - loanAmount)
const { pool, p, totalLiquidity } = contract
const probBefore = getCpmmProbability(pool, p)
const probAfter = getCpmmProbability(newPool, newP)
const newBet: Bet = {
id: newBetId,
userId: user.id,
contractId: contract.id,
amount,
shares,
outcome,
fees,
loanAmount,
probBefore,
probAfter,
createdTime: Date.now(),
}
const { liquidityFee } = fees
const newTotalLiquidity = (totalLiquidity ?? 0) + liquidityFee
return { newBet, newPool, newP, newBalance, newTotalLiquidity, fees }
}
export const getNewBinaryDpmBetInfo = (
user: User,
outcome: 'YES' | 'NO',
amount: number,
contract: FullContract<DPM, Binary>,
loanAmount: number,
contract: Contract,
newBetId: string
) => {
const { YES: yesPool, NO: noPool } = contract.pool
@ -23,7 +73,7 @@ export const getNewBinaryBetInfo = (
? { YES: yesPool + amount, NO: noPool }
: { YES: yesPool, NO: noPool + amount }
const shares = calculateShares(contract.totalShares, amount, outcome)
const shares = calculateDpmShares(contract.totalShares, amount, outcome)
const { YES: yesShares, NO: noShares } = contract.totalShares
@ -39,8 +89,8 @@ export const getNewBinaryBetInfo = (
? { YES: yesBets + amount, NO: noBets }
: { YES: yesBets, NO: noBets + amount }
const probBefore = getProbability(contract.totalShares)
const probAfter = getProbability(newTotalShares)
const probBefore = getDpmProbability(contract.totalShares)
const probAfter = getDpmProbability(newTotalShares)
const newBet: Bet = {
id: newBetId,
@ -53,6 +103,7 @@ export const getNewBinaryBetInfo = (
probBefore,
probAfter,
createdTime: Date.now(),
fees: noFees,
}
const newBalance = user.balance - (amount - loanAmount)
@ -64,8 +115,8 @@ export const getNewMultiBetInfo = (
user: User,
outcome: string,
amount: number,
contract: FullContract<DPM, Multi | FreeResponse>,
loanAmount: number,
contract: Contract,
newBetId: string
) => {
const { pool, totalShares, totalBets } = contract
@ -73,7 +124,7 @@ export const getNewMultiBetInfo = (
const prevOutcomePool = pool[outcome] ?? 0
const newPool = { ...pool, [outcome]: prevOutcomePool + amount }
const shares = calculateShares(contract.totalShares, amount, outcome)
const shares = calculateDpmShares(contract.totalShares, amount, outcome)
const prevShares = totalShares[outcome] ?? 0
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
@ -81,8 +132,8 @@ export const getNewMultiBetInfo = (
const prevTotalBets = totalBets[outcome] ?? 0
const newTotalBets = { ...totalBets, [outcome]: prevTotalBets + amount }
const probBefore = getOutcomeProbability(totalShares, outcome)
const probAfter = getOutcomeProbability(newTotalShares, outcome)
const probBefore = getDpmOutcomeProbability(totalShares, outcome)
const probAfter = getDpmOutcomeProbability(newTotalShares, outcome)
const newBet: Bet = {
id: newBetId,
@ -95,6 +146,7 @@ export const getNewMultiBetInfo = (
probBefore,
probAfter,
createdTime: Date.now(),
fees: noFees,
}
const newBalance = user.balance - (amount - loanAmount)

View File

@ -1,8 +1,16 @@
import { calcStartPool } from './antes'
import { Contract, outcomeType } from './contract'
import { PHANTOM_ANTE } 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'
import { calcDpmInitialPool } from './calculate-dpm'
export function getNewContract(
id: string,
@ -23,14 +31,12 @@ export function getNewContract(
const propsByOutcomeType =
outcomeType === 'BINARY'
? getBinaryProps(initialProb, ante)
? getBinaryCpmmProps(initialProb, ante) // getBinaryDpmProps(initialProb, ante)
: getFreeAnswerProps(ante)
const contract: Contract = removeUndefinedProps({
id,
slug,
mechanism: 'dpm-2',
outcomeType,
...propsByOutcomeType,
creatorId: creator.id,
@ -50,30 +56,61 @@ export function getNewContract(
volume24Hours: 0,
volume7Days: 0,
collectedFees: {
creatorFee: 0,
liquidityFee: 0,
platformFee: 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)
calcDpmInitialPool(initialProb, ante, PHANTOM_ANTE)
return {
const system: DPM & Binary = {
mechanism: 'dpm-2',
outcomeType: 'BINARY',
initialProbability: initialProb / 100,
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) => {
const pool = { YES: ante, NO: ante }
const p = initialProb / 100
const system: CPMM & Binary = {
mechanism: 'cpmm-1',
outcomeType: 'BINARY',
totalLiquidity: ante,
initialProbability: p,
p,
pool: 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 = (

189
common/payouts-dpm.ts Normal file
View File

@ -0,0 +1,189 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { deductDpmFees, getDpmProbability } from './calculate-dpm'
import { DPM, FreeResponse, FullContract, Multi } from './contract'
import {
DPM_CREATOR_FEE,
DPM_FEES,
DPM_PLATFORM_FEE,
Fees,
noFees,
} from './fees'
import { addObjects } from './util/object'
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)
const betSum = _.sumBy(bets, (b) => b.amount)
const payouts = bets.map((bet) => ({
userId: bet.userId,
payout: (bet.amount / betSum) * poolTotal,
}))
return [payouts, contract.collectedFees ?? noFees]
}
export const getDpmStandardPayouts = (
outcome: string,
contract: FullContract<DPM, any>,
bets: Bet[]
) => {
const winningBets = bets.filter((bet) => bet.outcome === outcome)
const poolTotal = _.sum(Object.values(contract.pool))
const totalShares = _.sumBy(winningBets, (b) => b.shares)
const payouts = winningBets.map(({ userId, amount, shares }) => {
const winnings = (shares / totalShares) * poolTotal
const profit = winnings - amount
// profit can be negative if using phantom shares
const payout = amount + (1 - DPM_FEES) * Math.max(0, profit)
return { userId, profit, payout }
})
const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit))
const creatorFee = DPM_CREATOR_FEE * profits
const platformFee = DPM_PLATFORM_FEE * profits
const finalFees: Fees = {
creatorFee,
platformFee,
liquidityFee: 0,
}
const fees = addObjects<Fees>(finalFees, contract.collectedFees ?? {})
console.log(
'resolved',
outcome,
'pool',
poolTotal,
'profits',
profits,
'creator fee',
creatorFee
)
const totalPayouts = payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee
return [totalPayouts, fees]
}
export const getDpmMktPayouts = (
contract: FullContract<DPM, any>,
bets: Bet[],
resolutionProbability?: number
) => {
const p =
resolutionProbability === undefined
? getDpmProbability(contract.totalShares)
: resolutionProbability
const weightedShareTotal = _.sumBy(bets, (b) =>
b.outcome === 'YES' ? p * b.shares : (1 - p) * b.shares
)
const pool = contract.pool.YES + contract.pool.NO
const payouts = bets.map(({ userId, outcome, amount, shares }) => {
const betP = outcome === 'YES' ? p : 1 - p
const winnings = ((betP * shares) / weightedShareTotal) * pool
const profit = winnings - amount
const payout = deductDpmFees(amount, winnings)
return { userId, profit, payout }
})
const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit))
const creatorFee = DPM_CREATOR_FEE * profits
const platformFee = DPM_PLATFORM_FEE * profits
const finalFees: Fees = {
creatorFee,
platformFee,
liquidityFee: 0,
}
const fees = addObjects<Fees>(finalFees, contract.collectedFees ?? {})
console.log(
'resolved MKT',
p,
'pool',
pool,
'profits',
profits,
'creator fee'
)
const totalPayouts = payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee
return [totalPayouts, fees]
}
export const getPayoutsMultiOutcome = (
resolutions: { [outcome: string]: number },
contract: FullContract<DPM, Multi | FreeResponse>,
bets: Bet[]
) => {
const poolTotal = _.sum(Object.values(contract.pool))
const winningBets = bets.filter((bet) => resolutions[bet.outcome])
const betsByOutcome = _.groupBy(winningBets, (bet) => bet.outcome)
const sharesByOutcome = _.mapValues(betsByOutcome, (bets) =>
_.sumBy(bets, (bet) => bet.shares)
)
const probTotal = _.sum(Object.values(resolutions))
const payouts = winningBets.map(({ userId, outcome, amount, shares }) => {
const prob = resolutions[outcome] / probTotal
const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal
const profit = winnings - amount
const payout = amount + (1 - DPM_FEES) * Math.max(0, profit)
return { userId, profit, payout }
})
const profits = _.sumBy(payouts, (po) => po.profit)
const creatorFee = DPM_CREATOR_FEE * profits
const platformFee = DPM_PLATFORM_FEE * profits
const finalFees: Fees = {
creatorFee,
platformFee,
liquidityFee: 0,
}
const fees = addObjects<Fees>(finalFees, contract.collectedFees ?? noFees)
console.log(
'resolved',
resolutions,
'pool',
poolTotal,
'profits',
profits,
'creator fee',
creatorFee
)
const totalPayouts = payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee
return [totalPayouts, fees]
}

123
common/payouts-fixed.ts Normal file
View File

@ -0,0 +1,123 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { getProbability } from './calculate'
import { Binary, CPMM, FixedPayouts, FullContract } from './contract'
import { LiquidityProvision } from './liquidity-provision'
export const getFixedCancelPayouts = (
bets: Bet[],
liquidities: LiquidityProvision[]
) => {
const liquidityPayouts = liquidities.map((lp) => ({
userId: lp.userId,
payout: lp.amount,
}))
return bets
.filter((b) => !b.isAnte && !b.isLiquidityProvision)
.map((bet) => ({
userId: bet.userId,
payout: bet.amount,
}))
.concat(liquidityPayouts)
}
export const getStandardFixedPayouts = (
outcome: string,
contract: FullContract<FixedPayouts, Binary>,
bets: Bet[],
liquidities: LiquidityProvision[]
) => {
const winningBets = bets.filter((bet) => bet.outcome === outcome)
const payouts = winningBets.map(({ userId, shares }) => ({
userId,
payout: shares,
}))
const creatorPayout = contract.collectedFees.creatorFee
console.log(
'resolved',
outcome,
'pool',
contract.pool[outcome],
'payouts',
_.sum(payouts),
'creator fee',
creatorPayout
)
return payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
.concat(getLiquidityPoolPayouts(contract, outcome, liquidities))
}
export const getLiquidityPoolPayouts = (
contract: FullContract<CPMM, Binary>,
outcome: string,
liquidities: LiquidityProvision[]
) => {
const providedLiquidity = _.sumBy(liquidities, (lp) => lp.liquidity)
const { pool } = contract
const finalPool = pool[outcome]
return liquidities.map((lp) => ({
userId: lp.userId,
payout: (lp.liquidity / providedLiquidity) * finalPool,
}))
}
export const getMktFixedPayouts = (
contract: FullContract<FixedPayouts, Binary>,
bets: Bet[],
liquidities: LiquidityProvision[],
resolutionProbability?: number
) => {
const p =
resolutionProbability === undefined
? getProbability(contract)
: resolutionProbability
const payouts = bets.map(({ userId, outcome, shares }) => {
const betP = outcome === 'YES' ? p : 1 - p
return { userId, payout: betP * shares }
})
const creatorPayout = contract.collectedFees.creatorFee
console.log(
'resolved PROB',
p,
'pool',
p * contract.pool.YES + (1 - p) * contract.pool.NO,
'payouts',
_.sum(payouts),
'creator fee',
creatorPayout
)
return payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
.concat(getLiquidityPoolProbPayouts(contract, p, liquidities))
}
export const getLiquidityPoolProbPayouts = (
contract: FullContract<CPMM, Binary>,
p: number,
liquidities: LiquidityProvision[]
) => {
const providedLiquidity = _.sumBy(liquidities, (lp) => lp.liquidity)
const { pool } = contract
const finalPool = p * pool.YES + (1 - p) * pool.NO
return liquidities.map((lp) => ({
userId: lp.userId,
payout: (lp.liquidity / providedLiquidity) * finalPool,
}))
}

View File

@ -1,167 +1,35 @@
import * as _ from 'lodash'
import { Bet } from './bet'
import { deductFees, getProbability } from './calculate'
import { Contract } from './contract'
import { CREATOR_FEE, FEES } from './fees'
import {
Binary,
Contract,
DPM,
FixedPayouts,
FreeResponse,
FullContract,
Multi,
} from './contract'
import { Fees } from './fees'
import { LiquidityProvision } from './liquidity-provision'
import {
getDpmCancelPayouts,
getDpmMktPayouts,
getDpmStandardPayouts,
getPayoutsMultiOutcome,
} from './payouts-dpm'
import {
getFixedCancelPayouts,
getMktFixedPayouts,
getStandardFixedPayouts,
} from './payouts-fixed'
export const getCancelPayouts = (contract: Contract, bets: Bet[]) => {
const { pool } = contract
const poolTotal = _.sum(Object.values(pool))
console.log('resolved N/A, pool M$', poolTotal)
const betSum = _.sumBy(bets, (b) => b.amount)
return bets.map((bet) => ({
userId: bet.userId,
payout: (bet.amount / betSum) * poolTotal,
}))
export type Payout = {
userId: string
payout: number
}
export const getStandardPayouts = (
outcome: string,
contract: Contract,
bets: Bet[]
) => {
const winningBets = bets.filter((bet) => bet.outcome === outcome)
const poolTotal = _.sum(Object.values(contract.pool))
const totalShares = _.sumBy(winningBets, (b) => b.shares)
const payouts = winningBets.map(({ userId, amount, shares }) => {
const winnings = (shares / totalShares) * poolTotal
const profit = winnings - amount
// profit can be negative if using phantom shares
const payout = amount + (1 - FEES) * Math.max(0, profit)
return { userId, profit, payout }
})
const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit))
const creatorPayout = CREATOR_FEE * profits
console.log(
'resolved',
outcome,
'pool',
poolTotal,
'profits',
profits,
'creator fee',
creatorPayout
)
return payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
}
export const getMktPayouts = (
contract: Contract,
bets: Bet[],
resolutionProbability?: number
) => {
const p =
resolutionProbability === undefined
? getProbability(contract.totalShares)
: resolutionProbability
const weightedShareTotal = _.sumBy(bets, (b) =>
b.outcome === 'YES' ? p * b.shares : (1 - p) * b.shares
)
const pool = contract.pool.YES + contract.pool.NO
const payouts = bets.map(({ userId, outcome, amount, shares }) => {
const betP = outcome === 'YES' ? p : 1 - p
const winnings = ((betP * shares) / weightedShareTotal) * pool
const profit = winnings - amount
const payout = 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',
pool,
'profits',
profits,
'creator fee',
creatorPayout
)
return payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
}
export const getPayouts = (
outcome: string,
contract: Contract,
bets: Bet[],
resolutionProbability?: number
) => {
switch (outcome) {
case 'YES':
case 'NO':
return getStandardPayouts(outcome, contract, bets)
case 'MKT':
return getMktPayouts(contract, bets, resolutionProbability)
case 'CANCEL':
return getCancelPayouts(contract, bets)
default:
// Multi outcome.
return getStandardPayouts(outcome, contract, bets)
}
}
export const getPayoutsMultiOutcome = (
resolutions: { [outcome: string]: number },
contract: Contract,
bets: Bet[]
) => {
const poolTotal = _.sum(Object.values(contract.pool))
const winningBets = bets.filter((bet) => resolutions[bet.outcome])
const betsByOutcome = _.groupBy(winningBets, (bet) => bet.outcome)
const sharesByOutcome = _.mapValues(betsByOutcome, (bets) =>
_.sumBy(bets, (bet) => bet.shares)
)
const probTotal = _.sum(Object.values(resolutions))
const payouts = winningBets.map(({ userId, outcome, amount, shares }) => {
const prob = resolutions[outcome] / probTotal
const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal
const profit = winnings - amount
const payout = deductFees(amount, winnings)
return { userId, profit, payout }
})
const profits = _.sumBy(payouts, (po) => po.profit)
const creatorPayout = CREATOR_FEE * profits
console.log(
'resolved',
resolutions,
'pool',
poolTotal,
'profits',
profits,
'creator fee',
creatorPayout
)
return payouts
.map(({ userId, payout }) => ({ userId, payout }))
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
}
export const getLoanPayouts = (bets: Bet[]) => {
export const getLoanPayouts = (bets: Bet[]): Payout[] => {
const betsWithLoans = bets.filter((bet) => bet.loanAmount)
const betsByUser = _.groupBy(betsWithLoans, (bet) => bet.userId)
const loansByUser = _.mapValues(betsByUser, (bets) =>
@ -169,3 +37,99 @@ export const getLoanPayouts = (bets: Bet[]) => {
)
return _.toPairs(loansByUser).map(([userId, payout]) => ({ userId, payout }))
}
export const getPayouts = (
outcome: string,
resolutions: {
[outcome: string]: number
},
contract: Contract,
bets: Bet[],
liquidities: LiquidityProvision[],
resolutionProbability?: number
): [Payout[], Fees] => {
if (contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY') {
const payouts = getFixedPayouts(
outcome,
contract,
bets,
liquidities,
resolutionProbability
)
return [payouts, contract.collectedFees]
}
return getDpmPayouts(
outcome,
resolutions,
contract,
bets,
resolutionProbability
)
}
export const getFixedPayouts = (
outcome: string,
contract: FullContract<FixedPayouts, Binary>,
bets: Bet[],
liquidities: LiquidityProvision[],
resolutionProbability?: number
): Payout[] => {
switch (outcome) {
case 'YES':
case 'NO':
return getStandardFixedPayouts(outcome, contract, bets, liquidities)
case 'MKT':
return getMktFixedPayouts(
contract,
bets,
liquidities,
resolutionProbability
)
default:
case 'CANCEL':
return getFixedCancelPayouts(bets, liquidities)
}
}
export const getDpmPayouts = (
outcome: string,
resolutions: {
[outcome: string]: number
},
contract: Contract,
bets: Bet[],
resolutionProbability?: number
) => {
const openBets = bets.filter((b) => !b.isSold && !b.sale)
switch (outcome) {
case 'YES':
case 'NO':
return getDpmStandardPayouts(outcome, contract, openBets) as [
Payout[],
Fees
]
case 'MKT':
return contract.outcomeType === 'FREE_RESPONSE'
? (getPayoutsMultiOutcome(
resolutions,
contract as FullContract<DPM, Multi | FreeResponse>,
openBets
) as [Payout[], Fees])
: (getDpmMktPayouts(contract, openBets, resolutionProbability) as [
Payout[],
Fees
])
case 'CANCEL':
return getDpmCancelPayouts(contract, openBets) as [Payout[], Fees]
default:
// Outcome is a free response answer id.
return getDpmStandardPayouts(outcome, contract, openBets) as [
Payout[],
Fees
]
}
}

View File

@ -1,6 +1,7 @@
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,17 +24,22 @@ 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(
bets,
(bet) => bet.isSold || bet.sale
)
const resolvePayouts = getPayouts(
resolution ?? 'MKT',
const [resolvePayouts] = getPayouts(
resolution,
{},
contract,
openBets,
[],
resolutionProbability
)

View File

@ -1,19 +1,24 @@
import { Bet } from './bet'
import { calculateShareValue, deductFees, getProbability } from './calculate'
import { Contract } from './contract'
import { CREATOR_FEE } from './fees'
import {
getDpmProbability,
calculateDpmShareValue,
deductDpmFees,
} from './calculate-dpm'
import { calculateCpmmSale, getCpmmProbability } from './calculate-cpmm'
import { Binary, DPM, CPMM, FullContract } from './contract'
import { DPM_CREATOR_FEE, DPM_PLATFORM_FEE, Fees } 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
const { id: betId, amount, shares, outcome, loanAmount } = bet
const adjShareValue = calculateShareValue(contract, bet)
const adjShareValue = calculateDpmShareValue(contract, bet)
const newPool = { ...pool, [outcome]: pool[outcome] - adjShareValue }
@ -24,12 +29,20 @@ export const getSellBetInfo = (
const newTotalBets = { ...totalBets, [outcome]: totalBets[outcome] - amount }
const probBefore = getProbability(totalShares)
const probAfter = getProbability(newTotalShares)
const probBefore = getDpmProbability(totalShares)
const probAfter = getDpmProbability(newTotalShares)
const profit = adjShareValue - amount
const creatorFee = CREATOR_FEE * Math.max(0, profit)
const saleAmount = deductFees(amount, adjShareValue)
const creatorFee = DPM_CREATOR_FEE * Math.max(0, profit)
const platformFee = DPM_PLATFORM_FEE * Math.max(0, profit)
const fees: Fees = {
creatorFee,
platformFee,
liquidityFee: 0,
}
const saleAmount = deductDpmFees(amount, adjShareValue)
console.log(
'SELL M$',
@ -55,6 +68,7 @@ export const getSellBetInfo = (
amount: saleAmount,
betId,
},
fees,
}
const newBalance = user.balance + saleAmount - (loanAmount ?? 0)
@ -65,6 +79,57 @@ export const getSellBetInfo = (
newTotalShares,
newTotalBets,
newBalance,
creatorFee,
fees,
}
}
export const getCpmmSellBetInfo = (
user: User,
bet: Bet,
contract: FullContract<CPMM, Binary>,
newBetId: string
) => {
const { pool, p } = contract
const { id: betId, amount, shares, outcome } = bet
const { saleValue, newPool, fees } = calculateCpmmSale(contract, bet)
const probBefore = getCpmmProbability(pool, p)
const probAfter = getCpmmProbability(newPool, p)
console.log(
'SELL M$',
amount,
outcome,
'for M$',
saleValue,
'creator fee: M$',
fees.creatorFee
)
const newBet: Bet = {
id: newBetId,
userId: user.id,
contractId: contract.id,
amount: -saleValue,
shares: -shares,
outcome,
probBefore,
probAfter,
createdTime: Date.now(),
sale: {
amount: saleValue,
betId,
},
fees,
}
const newBalance = user.balance + saleValue
return {
newBet,
newPool,
newBalance,
fees,
}
}

View File

@ -8,8 +8,9 @@ const formatter = new Intl.NumberFormat('en-US', {
})
export function formatMoney(amount: number) {
const newAmount = Math.round(amount) === 0 ? 0 : amount // handle -0 case
return (
ENV_CONFIG.moneyMoniker + ' ' + formatter.format(amount).replace('$', '')
ENV_CONFIG.moneyMoniker + ' ' + formatter.format(newAmount).replace('$', '')
)
}

View File

@ -1,3 +1,5 @@
import * as _ from 'lodash'
export const removeUndefinedProps = <T>(obj: T): T => {
let newObj: any = {}
@ -7,3 +9,17 @@ export const removeUndefinedProps = <T>(obj: T): T => {
return newObj
}
export const addObjects = <T extends { [key: string]: number }>(
obj1: T,
obj2: T
) => {
const keys = _.union(Object.keys(obj1), Object.keys(obj2))
const newObj = {} as any
for (let key of keys) {
newObj[key] = (obj1[key] ?? 0) + (obj2[key] ?? 0)
}
return newObj as T
}

View File

@ -1,7 +1,12 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { Contract } from '../../common/contract'
import {
Contract,
DPM,
FreeResponse,
FullContract,
} from '../../common/contract'
import { User } from '../../common/user'
import { getLoanAmount, getNewMultiBetInfo } from '../../common/new-bet'
import { Answer } from '../../common/answer'
@ -105,8 +110,8 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
user,
answerId,
amount,
contract as FullContract<DPM, FreeResponse>,
loanAmount,
contract,
newBetDoc.id
)

View File

@ -4,7 +4,12 @@ import * as _ from 'lodash'
import { chargeUser, getUser } from './utils'
import {
Binary,
Contract,
CPMM,
DPM,
FreeResponse,
FullContract,
MAX_DESCRIPTION_LENGTH,
MAX_QUESTION_LENGTH,
MAX_TAG_LENGTH,
@ -15,6 +20,7 @@ import { randomString } from '../../common/util/random'
import { getNewContract } from '../../common/new-contract'
import {
getAnteBets,
getCpmmInitialLiquidity,
getFreeAnswerAnte,
MINIMUM_ANTE,
} from '../../common/antes'
@ -105,7 +111,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()
@ -116,23 +122,43 @@ 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 liquidityDoc = firestore
.collection(`contracts/${contract.id}/liquidity`)
.doc()
const lp = getCpmmInitialLiquidity(
creator,
contract as FullContract<CPMM, Binary>,
liquidityDoc.id,
ante
)
await liquidityDoc.set(lp)
} 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)
}
}

View File

@ -1,4 +1,5 @@
import _ = require('lodash')
import * as _ from 'lodash'
import { DOMAIN, PROJECT_ID } from '../../common/envs/constants'
import { Answer } from '../../common/answer'
import { Bet } from '../../common/bet'
@ -11,34 +12,6 @@ import { formatMoney, formatPercent } from '../../common/util/format'
import { sendTemplateEmail, sendTextEmail } from './send-email'
import { getPrivateUser, getUser } from './utils'
type market_resolved_template = {
userId: string
name: string
creatorName: string
question: string
outcome: string
investment: string
payout: string
url: string
}
const toDisplayResolution = (
outcome: string,
prob?: number,
resolutions?: { [outcome: string]: number }
) => {
if (outcome === 'MKT' && resolutions) return 'MULTI'
const display = {
YES: 'YES',
NO: 'NO',
CANCEL: 'N/A',
MKT: formatPercent(prob ?? 0),
}[outcome]
return display === undefined ? `#${outcome}` : display
}
export const sendMarketResolutionEmail = async (
userId: string,
investment: number,
@ -60,9 +33,12 @@ export const sendMarketResolutionEmail = async (
const user = await getUser(userId)
if (!user) return
const prob = resolutionProbability ?? getProbability(contract.totalShares)
const outcome = toDisplayResolution(resolution, prob, resolutions)
const outcome = toDisplayResolution(
contract,
resolution,
resolutionProbability,
resolutions
)
const subject = `Resolved ${outcome}: ${contract.question}`
@ -89,6 +65,41 @@ export const sendMarketResolutionEmail = async (
)
}
type market_resolved_template = {
userId: string
name: string
creatorName: string
question: string
outcome: string
investment: string
payout: string
url: string
}
const toDisplayResolution = (
contract: Contract,
resolution: string,
resolutionProbability?: number,
resolutions?: { [outcome: string]: number }
) => {
if (contract.outcomeType === 'BINARY') {
const prob = resolutionProbability ?? getProbability(contract)
const display = {
YES: 'YES',
NO: 'NO',
CANCEL: 'N/A',
MKT: formatPercent(prob ?? 0),
}[resolution]
return display || resolution
}
if (resolution === 'MKT' && resolutions) return 'MULTI'
return `#${resolution}`
}
export const sendWelcomeEmail = async (
user: User,
privateUser: PrivateUser
@ -197,7 +208,10 @@ export const sendNewCommentEmail = async (
{ from }
)
} else {
betDescription = `${betDescription} of ${toDisplayResolution(outcome)}`
betDescription = `${betDescription} of ${toDisplayResolution(
contract,
outcome
)}`
await sendTemplateEmail(
privateUser.email,

View File

@ -3,7 +3,6 @@ import * as admin from 'firebase-admin'
admin.initializeApp()
// export * from './keep-awake'
export * from './markets'
export * from './place-bet'
export * from './resolve-market'
export * from './stripe'

View File

@ -1,61 +0,0 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { getValues } from './utils'
import { Contract } from '../../common/contract'
import { getProbability } from '../../common/calculate'
const cache = { lastUpdated: 0, data: '' }
export const markets = functions
.runWith({ minInstances: 1 })
.https.onRequest(async (req, res) => {
const contracts: Contract[] = await getValues(
firestore.collection('contracts').orderBy('volume24Hours', 'desc')
)
if (!cache.data || Date.now() - cache.lastUpdated > 120 * 1000) {
const contractsInfo = contracts.map(getContractInfo)
cache.data = JSON.stringify(contractsInfo)
cache.lastUpdated = Date.now()
}
res.status(200).send(cache.data)
})
const getContractInfo = ({
id,
creatorUsername,
creatorName,
createdTime,
closeTime,
question,
description,
slug,
pool,
totalShares,
volume7Days,
volume24Hours,
isResolved,
resolution,
}: Contract) => {
return {
id,
creatorUsername,
creatorName,
createdTime,
closeTime,
question,
description,
url: `https://manifold.markets/${creatorUsername}/${slug}`,
pool: pool.YES + pool.NO,
probability: getProbability(totalShares),
volume7Days,
volume24Hours,
isResolved,
resolution,
}
}
const firestore = admin.firestore()

View File

@ -33,8 +33,9 @@ export const onCreateComment = functions.firestore
const bet = betSnapshot.data() as Bet
const answer =
contract.answers &&
contract.answers.find((answer) => answer.id === bet.outcome)
contract.outcomeType === 'FREE_RESPONSE' && contract.answers
? contract.answers.find((answer) => answer.id === bet.outcome)
: undefined
const comments = await getValues<Comment>(
firestore.collection('contracts').doc(contractId).collection('comments')

View File

@ -4,11 +4,15 @@ import * as admin from 'firebase-admin'
import { Contract } from '../../common/contract'
import { User } from '../../common/user'
import {
getLoanAmount,
getNewBinaryBetInfo,
getNewBinaryCpmmBetInfo,
getNewBinaryDpmBetInfo,
getNewMultiBetInfo,
getLoanAmount,
} from '../../common/new-bet'
import { addObjects, removeUndefinedProps } from '../../common/util/object'
import { Bet } from '../../common/bet'
import { redeemShares } from './redeem-shares'
import { Fees } from '../../common/fees'
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
async (
@ -31,7 +35,8 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
return { status: 'error', message: 'Invalid outcome' }
// run as transaction to prevent race conditions
return await firestore.runTransaction(async (transaction) => {
return await firestore
.runTransaction(async (transaction) => {
const userDoc = firestore.doc(`users/${userId}`)
const userSnap = await transaction.get(userDoc)
if (!userSnap.exists)
@ -44,7 +49,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, collectedFees } = contract
if (closeTime && Date.now() > closeTime)
return { status: 'error', message: 'Trading is closed' }
@ -69,35 +74,72 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
.collection(`contracts/${contractId}/bets`)
.doc()
const { newBet, newPool, newTotalShares, newTotalBets, newBalance } =
const {
newBet,
newPool,
newTotalShares,
newTotalBets,
newBalance,
newTotalLiquidity,
fees,
newP,
} =
outcomeType === 'BINARY'
? getNewBinaryBetInfo(
? mechanism === 'dpm-2'
? getNewBinaryDpmBetInfo(
user,
outcome as 'YES' | 'NO',
amount,
loanAmount,
contract,
loanAmount,
newBetDoc.id
)
: (getNewBinaryCpmmBetInfo(
user,
outcome as 'YES' | 'NO',
amount,
contract,
loanAmount,
newBetDoc.id
) as any)
: getNewMultiBetInfo(
user,
outcome,
amount,
contract as any,
loanAmount,
contract,
newBetDoc.id
)
if (newP !== undefined && !isFinite(newP)) {
return {
status: 'error',
message: 'Trade rejected due to overflow error.',
}
}
transaction.create(newBetDoc, newBet)
transaction.update(contractDoc, {
transaction.update(
contractDoc,
removeUndefinedProps({
pool: newPool,
p: newP,
totalShares: newTotalShares,
totalBets: newTotalBets,
totalLiquidity: newTotalLiquidity,
collectedFees: addObjects<Fees>(fees ?? {}, collectedFees ?? {}),
})
)
transaction.update(userDoc, { balance: newBalance })
return { status: 'success', betId: newBetDoc.id }
})
.then(async (result) => {
await redeemShares(userId, contractId)
return result
})
}
)

View File

@ -0,0 +1,90 @@
import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { Bet } from '../../common/bet'
import { getProbability } from '../../common/calculate'
import { Binary, CPMM, FullContract } from '../../common/contract'
import { noFees } from '../../common/fees'
import { User } from '../../common/user'
export const redeemShares = async (userId: string, contractId: string) => {
return await firestore.runTransaction(async (transaction) => {
const contractDoc = firestore.doc(`contracts/${contractId}`)
const contractSnap = await transaction.get(contractDoc)
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as FullContract<CPMM, Binary>
if (contract.outcomeType !== 'BINARY' || contract.mechanism !== 'cpmm-1')
return { status: 'success' }
const betsSnap = await transaction.get(
firestore
.collection(`contracts/${contract.id}/bets`)
.where('userId', '==', userId)
)
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
const [yesBets, noBets] = _.partition(bets, (b) => b.outcome === 'YES')
const yesShares = _.sumBy(yesBets, (b) => b.shares)
const noShares = _.sumBy(noBets, (b) => b.shares)
const amount = Math.min(yesShares, noShares)
if (amount <= 0) return
const prevLoanAmount = _.sumBy(bets, (bet) => bet.loanAmount ?? 0)
const loanPaid = Math.min(prevLoanAmount, amount)
const netAmount = amount - loanPaid
const p = getProbability(contract)
const createdTime = Date.now()
const yesDoc = firestore.collection(`contracts/${contract.id}/bets`).doc()
const yesBet: Bet = {
id: yesDoc.id,
userId: userId,
contractId: contract.id,
amount: p * -amount,
shares: -amount,
loanAmount: loanPaid ? -loanPaid / 2 : 0,
outcome: 'YES',
probBefore: p,
probAfter: p,
createdTime,
isRedemption: true,
fees: noFees,
}
const noDoc = firestore.collection(`contracts/${contract.id}/bets`).doc()
const noBet: Bet = {
id: noDoc.id,
userId: userId,
contractId: contract.id,
amount: (1 - p) * -amount,
shares: -amount,
loanAmount: loanPaid ? -loanPaid / 2 : 0,
outcome: 'NO',
probBefore: p,
probAfter: p,
createdTime,
isRedemption: true,
fees: noFees,
}
const userDoc = firestore.doc(`users/${userId}`)
const userSnap = await transaction.get(userDoc)
if (!userSnap.exists) return { status: 'error', message: 'User not found' }
const user = userSnap.data() as User
const newBalance = user.balance + netAmount
transaction.update(userDoc, { balance: newBalance })
transaction.create(yesDoc, yesBet)
transaction.create(noDoc, noBet)
return { status: 'success' }
})
}
const firestore = admin.firestore()

View File

@ -5,14 +5,11 @@ import * as _ from 'lodash'
import { Contract } from '../../common/contract'
import { User } from '../../common/user'
import { Bet } from '../../common/bet'
import { getUser, payUser } from './utils'
import { getUser, isProd, payUser } from './utils'
import { sendMarketResolutionEmail } from './emails'
import {
getLoanPayouts,
getPayouts,
getPayoutsMultiOutcome,
} from '../../common/payouts'
import { getLoanPayouts, getPayouts } from '../../common/payouts'
import { removeUndefinedProps } from '../../common/util/object'
import { LiquidityProvision } from '../../common/liquidity-provision'
export const resolveMarket = functions
.runWith({ minInstances: 1 })
@ -78,6 +75,29 @@ export const resolveMarket = functions
? Math.min(closeTime, resolutionTime)
: closeTime
const betsSnap = await firestore
.collection(`contracts/${contractId}/bets`)
.get()
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
const liquiditiesSnap = await firestore
.collection(`contracts/${contractId}/liquidity`)
.get()
const liquidities = liquiditiesSnap.docs.map(
(doc) => doc.data() as LiquidityProvision
)
const [payouts, collectedFees] = getPayouts(
outcome,
resolutions ?? {},
contract,
bets,
liquidities,
resolutionProbability
)
await contractDoc.update(
removeUndefinedProps({
isResolved: true,
@ -86,26 +106,16 @@ export const resolveMarket = functions
closeTime: newCloseTime,
resolutionProbability,
resolutions,
collectedFees,
})
)
console.log('contract ', contractId, 'resolved to:', outcome)
const betsSnap = await firestore
.collection(`contracts/${contractId}/bets`)
.get()
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
const openBets = bets.filter((b) => !b.isSold && !b.sale)
const payouts =
outcomeType === 'FREE_RESPONSE' && resolutions
? getPayoutsMultiOutcome(resolutions, contract, openBets)
: getPayouts(outcome, contract, openBets, resolutionProbability)
const loanPayouts = getLoanPayouts(openBets)
console.log('payouts:', payouts)
if (!isProd) console.log('payouts:', payouts)
const groups = _.groupBy(
[...payouts, ...loanPayouts],

View File

@ -5,13 +5,16 @@ import { initAdmin } from './script-init'
initAdmin('stephen')
import { Bet } from '../../../common/bet'
import { getProbability } from '../../../common/calculate'
import { Contract } from '../../../common/contract'
import { getDpmProbability } from '../../../common/calculate-dpm'
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
type DocRef = admin.firestore.DocumentReference
const firestore = admin.firestore()
async function migrateContract(contractRef: DocRef, contract: Contract) {
async function migrateContract(
contractRef: DocRef,
contract: FullContract<DPM, Binary>
) {
const bets = await contractRef
.collection('bets')
.get()
@ -19,7 +22,7 @@ async function migrateContract(contractRef: DocRef, contract: Contract) {
const lastBet = _.sortBy(bets, (bet) => -bet.createdTime)[0]
if (lastBet) {
const probAfter = getProbability(contract.totalShares)
const probAfter = getDpmProbability(contract.totalShares)
await firestore
.doc(`contracts/${contract.id}/bets/${lastBet.id}`)
@ -31,7 +34,9 @@ async function migrateContract(contractRef: DocRef, contract: Contract) {
async function migrateContracts() {
const snapshot = await firestore.collection('contracts').get()
const contracts = snapshot.docs.map((doc) => doc.data() as Contract)
const contracts = snapshot.docs.map(
(doc) => doc.data() as FullContract<DPM, Binary>
)
console.log('Loaded contracts', contracts.length)

View File

@ -4,9 +4,12 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin('stephenDev')
import { Contract } from '../../../common/contract'
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
import { Bet } from '../../../common/bet'
import { calculateShares, getProbability } from '../../../common/calculate'
import {
calculateDpmShares,
getDpmProbability,
} from '../../../common/calculate-dpm'
import { getSellBetInfo } from '../../../common/sell-bet'
import { User } from '../../../common/user'
@ -29,7 +32,7 @@ async function recalculateContract(
await firestore.runTransaction(async (transaction) => {
const contractDoc = await transaction.get(contractRef)
const contract = contractDoc.data() as Contract
const contract = contractDoc.data() as FullContract<DPM, Binary>
const betDocs = await transaction.get(contractRef.collection('bets'))
const bets = _.sortBy(
@ -126,7 +129,7 @@ async function recalculateContract(
continue
}
const shares = calculateShares(totalShares, bet.amount, bet.outcome)
const shares = calculateDpmShares(totalShares, bet.amount, bet.outcome)
const probBefore = p
const ind = bet.outcome === 'YES' ? 1 : 0
@ -145,7 +148,7 @@ async function recalculateContract(
NO: totalBets.NO + (1 - ind) * bet.amount,
}
p = getProbability(totalShares)
p = getDpmProbability(totalShares)
const probAfter = p

View File

@ -6,13 +6,8 @@ initAdmin('james')
import { Bet } from '../../../common/bet'
import { Contract } from '../../../common/contract'
import {
getLoanPayouts,
getPayouts,
getPayoutsMultiOutcome,
} from '../../../common/payouts'
import { getLoanPayouts, getPayouts } from '../../../common/payouts'
import { filterDefined } from '../../../common/util/array'
import { payUser } from '../utils'
type DocRef = admin.firestore.DocumentReference
@ -28,12 +23,15 @@ async function checkIfPayOutAgain(contractRef: DocRef, contract: Contract) {
const loanedBets = openBets.filter((bet) => bet.loanAmount)
if (loanedBets.length && contract.resolution) {
const { resolution, outcomeType, resolutions, resolutionProbability } =
contract
const payouts =
outcomeType === 'FREE_RESPONSE' && resolutions
? getPayoutsMultiOutcome(resolutions, contract, openBets)
: getPayouts(resolution, contract, openBets, resolutionProbability)
const { resolution, resolutions, resolutionProbability } = contract as any
const [payouts] = getPayouts(
resolution,
resolutions,
contract,
openBets,
[],
resolutionProbability
)
const loanPayouts = getLoanPayouts(openBets)
const groups = _.groupBy(

View File

@ -4,7 +4,9 @@ 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 { addObjects, removeUndefinedProps } from '../../common/util/object'
import { Fees } from '../../common/fees'
export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
async (
@ -33,7 +35,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, collectedFees } = contract
if (closeTime && Date.now() > closeTime)
return { status: 'error', message: 'Trading is closed' }
@ -54,31 +56,30 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
newTotalShares,
newTotalBets,
newBalance,
creatorFee,
} = getSellBetInfo(user, bet, contract, newBetDoc.id)
if (contract.creatorId === userId) {
transaction.update(userDoc, { balance: newBalance + creatorFee })
} else {
const creatorDoc = firestore.doc(`users/${contract.creatorId}`)
const creatorSnap = await transaction.get(creatorDoc)
if (creatorSnap.exists) {
const creator = creatorSnap.data() as User
const creatorNewBalance = creator.balance + creatorFee
transaction.update(creatorDoc, { balance: creatorNewBalance })
}
fees,
} =
mechanism === 'dpm-2'
? getSellBetInfo(user, bet, contract, newBetDoc.id)
: (getCpmmSellBetInfo(
user,
bet,
contract as any,
newBetDoc.id
) as any)
transaction.update(userDoc, { balance: newBalance })
}
transaction.update(betDoc, { isSold: true })
transaction.create(newBetDoc, newBet)
transaction.update(contractDoc, {
transaction.update(
contractDoc,
removeUndefinedProps({
pool: newPool,
totalShares: newTotalShares,
totalBets: newTotalBets,
collectedFees: addObjects<Fees>(fees ?? {}, collectedFees ?? {}),
})
)
return { status: 'success' }
})

View File

@ -6,7 +6,7 @@ import { getValues } from './utils'
import { Contract } from '../../common/contract'
import { Bet } from '../../common/bet'
import { User } from '../../common/user'
import { calculatePayout } from '../../common/calculate'
import { calculateDpmPayout } from '../../common/calculate-dpm'
import { batchedWaitAll } from '../../common/util/promise'
const firestore = admin.firestore()
@ -53,7 +53,7 @@ const computeInvestmentValue = async (
if (!contract || contract.isResolved) return 0
if (bet.sale || bet.isSold) return 0
const payout = calculatePayout(contract, bet, 'MKT')
const payout = calculateDpmPayout(contract, bet, 'MKT')
return payout - (bet.loanAmount ?? 0)
})
}

View File

@ -53,6 +53,11 @@ const updateUserBalance = (
const newUserBalance = user.balance + delta
// if (newUserBalance < 0)
// throw new Error(
// `User (${userId}) balance cannot be negative: ${newUserBalance}`
// )
if (isDeposit) {
const newTotalDeposits = (user.totalDeposits || 0) + delta
transaction.update(userDoc, { totalDeposits: newTotalDeposits })

View File

@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react'
import { XIcon } from '@heroicons/react/solid'
import { Answer } from '../../../common/answer'
import { Contract } from '../../../common/contract'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
import { AmountInput } from '../amount-input'
import { Col } from '../layout/col'
import { placeBet } from '../../lib/firebase/api-call'
@ -18,17 +18,17 @@ import {
import { InfoTooltip } from '../info-tooltip'
import { useUser } from '../../hooks/use-user'
import {
getProbabilityAfterBet,
getOutcomeProbability,
calculateShares,
calculatePayoutAfterCorrectBet,
} from '../../../common/calculate'
getDpmOutcomeProbability,
calculateDpmShares,
calculateDpmPayoutAfterCorrectBet,
getDpmOutcomeProbabilityAfterBet,
} from '../../../common/calculate-dpm'
import { firebaseLogin } from '../../lib/firebase/users'
import { Bet } from '../../../common/bet'
export function AnswerBetPanel(props: {
answer: Answer
contract: Contract
contract: FullContract<DPM, FreeResponse>
closePanel: () => void
className?: string
}) {
@ -71,18 +71,22 @@ export function AnswerBetPanel(props: {
const betDisabled = isSubmitting || !betAmount || error
const initialProb = getOutcomeProbability(contract.totalShares, answer.id)
const initialProb = getDpmOutcomeProbability(contract.totalShares, answer.id)
const resultProb = getProbabilityAfterBet(
const resultProb = getDpmOutcomeProbabilityAfterBet(
contract.totalShares,
answerId,
betAmount ?? 0
)
const shares = calculateShares(contract.totalShares, betAmount ?? 0, answerId)
const shares = calculateDpmShares(
contract.totalShares,
betAmount ?? 0,
answerId
)
const currentPayout = betAmount
? calculatePayoutAfterCorrectBet(contract, {
? calculateDpmPayoutAfterCorrectBet(contract, {
outcome: answerId,
amount: betAmount,
shares,

View File

@ -3,14 +3,14 @@ import _ from 'lodash'
import { useState } from 'react'
import { Answer } from '../../../common/answer'
import { Contract } from '../../../common/contract'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
import { Col } from '../layout/col'
import { Row } from '../layout/row'
import { Avatar } from '../avatar'
import { SiteLink } from '../site-link'
import { BuyButton } from '../yes-no-selector'
import { formatPercent } from '../../../common/util/format'
import { getOutcomeProbability } from '../../../common/calculate'
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
import { tradingAllowed } from '../../lib/firebase/contracts'
import { AnswerBetPanel } from './answer-bet-panel'
import { Linkify } from '../linkify'
@ -19,7 +19,7 @@ import { ContractActivity } from '../feed/contract-activity'
export function AnswerItem(props: {
answer: Answer
contract: Contract
contract: FullContract<DPM, FreeResponse>
user: User | null | undefined
showChoice: 'radio' | 'checkbox' | undefined
chosenProb: number | undefined
@ -41,7 +41,7 @@ export function AnswerItem(props: {
const { username, avatarUrl, name, number, text } = answer
const isChosen = chosenProb !== undefined
const prob = getOutcomeProbability(totalShares, answer.id)
const prob = getDpmOutcomeProbability(totalShares, answer.id)
const roundedProb = Math.round(prob * 100)
const probPercent = formatPercent(prob)
const wasResolvedTo =

View File

@ -2,7 +2,7 @@ import clsx from 'clsx'
import _ from 'lodash'
import { useState } from 'react'
import { Contract } from '../../../common/contract'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
import { Col } from '../layout/col'
import { resolveMarket } from '../../lib/firebase/api-call'
import { Row } from '../layout/row'
@ -11,7 +11,7 @@ import { ResolveConfirmationButton } from '../confirmation-button'
import { removeUndefinedProps } from '../../../common/util/object'
export function AnswerResolvePanel(props: {
contract: Contract
contract: FullContract<DPM, FreeResponse>
resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
setResolveOption: (
option: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined

View File

@ -4,20 +4,28 @@ import dayjs from 'dayjs'
import _ from 'lodash'
import { Bet } from '../../../common/bet'
import { Contract } from '../../../common/contract'
import {
Contract,
DPM,
FreeResponse,
FullContract,
} from '../../../common/contract'
import { getOutcomeProbability } from '../../../common/calculate'
import { useBets } from '../../hooks/use-bets'
import { useWindowSize } from '../../hooks/use-window-size'
export function AnswersGraph(props: { contract: Contract; bets: Bet[] }) {
export function AnswersGraph(props: {
contract: FullContract<DPM, FreeResponse>
bets: Bet[]
}) {
const { contract } = props
const { resolutionTime, closeTime, answers, totalShares } = contract
const { resolutionTime, closeTime, answers } = contract
const bets = (useBets(contract.id) ?? props.bets).filter((bet) => !bet.sale)
const { probsByOutcome, sortedOutcomes } = computeProbsByOutcome(
bets,
totalShares
contract
)
const isClosed = !!closeTime && Date.now() > closeTime
@ -134,10 +142,7 @@ function formatTime(time: number, includeTime: boolean) {
return dayjs(time).format('MMM D')
}
const computeProbsByOutcome = (
bets: Bet[],
totalShares: { [outcome: string]: number }
) => {
const computeProbsByOutcome = (bets: Bet[], contract: Contract) => {
const betsByOutcome = _.groupBy(bets, (bet) => bet.outcome)
const outcomes = Object.keys(betsByOutcome).filter((outcome) => {
const maxProb = Math.max(
@ -148,7 +153,7 @@ const computeProbsByOutcome = (
const trackedOutcomes = _.sortBy(
outcomes,
(outcome) => -1 * getOutcomeProbability(totalShares, outcome)
(outcome) => -1 * getOutcomeProbability(contract, outcome)
).slice(0, 5)
const probsByOutcome = _.fromPairs(

View File

@ -2,18 +2,21 @@ import _ from 'lodash'
import { useLayoutEffect, useState } from 'react'
import { Answer } from '../../../common/answer'
import { Contract } from '../../../common/contract'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
import { Col } from '../layout/col'
import { formatPercent } from '../../../common/util/format'
import { useUser } from '../../hooks/use-user'
import { getOutcomeProbability } from '../../../common/calculate'
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
import { useAnswers } from '../../hooks/use-answers'
import { tradingAllowed } from '../../lib/firebase/contracts'
import { AnswerItem } from './answer-item'
import { CreateAnswerPanel } from './create-answer-panel'
import { AnswerResolvePanel } from './answer-resolve-panel'
export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
export function AnswersPanel(props: {
contract: FullContract<DPM, FreeResponse>
answers: Answer[]
}) {
const { contract } = props
const { creatorId, resolution, resolutions, totalBets } = contract
@ -31,7 +34,7 @@ export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
),
..._.sortBy(
otherAnswers,
(answer) => -1 * getOutcomeProbability(contract.totalShares, answer.id)
(answer) => -1 * getDpmOutcomeProbability(contract.totalShares, answer.id)
),
]
@ -100,7 +103,7 @@ export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
) : (
<div className="self-end p-4 text-gray-500">
None of the above:{' '}
{formatPercent(getOutcomeProbability(contract.totalShares, '0'))}
{formatPercent(getDpmOutcomeProbability(contract.totalShares, '0'))}
</div>
)}

View File

@ -3,7 +3,7 @@ import _ from 'lodash'
import { useState } from 'react'
import Textarea from 'react-expanding-textarea'
import { Contract } from '../../../common/contract'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
import { AmountInput } from '../amount-input'
import { Col } from '../layout/col'
import { createAnswer } from '../../lib/firebase/api-call'
@ -16,14 +16,16 @@ import {
import { InfoTooltip } from '../info-tooltip'
import { useUser } from '../../hooks/use-user'
import {
getProbabilityAfterBet,
calculateShares,
calculatePayoutAfterCorrectBet,
} from '../../../common/calculate'
calculateDpmShares,
calculateDpmPayoutAfterCorrectBet,
getDpmOutcomeProbabilityAfterBet,
} from '../../../common/calculate-dpm'
import { firebaseLogin } from '../../lib/firebase/users'
import { Bet } from '../../../common/bet'
export function CreateAnswerPanel(props: { contract: Contract }) {
export function CreateAnswerPanel(props: {
contract: FullContract<DPM, FreeResponse>
}) {
const { contract } = props
const user = useUser()
const [text, setText] = useState('')
@ -53,16 +55,16 @@ export function CreateAnswerPanel(props: { contract: Contract }) {
}
}
const resultProb = getProbabilityAfterBet(
const resultProb = getDpmOutcomeProbabilityAfterBet(
contract.totalShares,
'new',
betAmount ?? 0
)
const shares = calculateShares(contract.totalShares, betAmount ?? 0, 'new')
const shares = calculateDpmShares(contract.totalShares, betAmount ?? 0, 'new')
const currentPayout = betAmount
? calculatePayoutAfterCorrectBet(contract, {
? calculateDpmPayoutAfterCorrectBet(contract, {
outcome: 'new',
amount: betAmount,
shares,

View File

@ -1,8 +1,8 @@
import clsx from 'clsx'
import React, { useEffect, useRef, useState } from 'react'
import React, { useEffect, useState } from 'react'
import { useUser } from '../hooks/use-user'
import { Contract } from '../../common/contract'
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
import { Col } from './layout/col'
import { Row } from './layout/row'
import { Spacer } from './layout/spacer'
@ -13,31 +13,22 @@ import {
formatWithCommas,
} from '../../common/util/format'
import { Title } from './title'
import {
getProbability,
calculateShares,
getProbabilityAfterBet,
calculatePayoutAfterCorrectBet,
} from '../../common/calculate'
import { firebaseLogin } from '../lib/firebase/users'
import { Bet } from '../../common/bet'
import { placeBet } from '../lib/firebase/api-call'
import { AmountInput } from './amount-input'
import { InfoTooltip } from './info-tooltip'
import { OutcomeLabel } from './outcome-label'
// Focus helper from https://stackoverflow.com/a/54159564/1222351
function useFocus(): [React.RefObject<HTMLElement>, () => void] {
const htmlElRef = useRef<HTMLElement>(null)
const setFocus = () => {
htmlElRef.current && htmlElRef.current.focus()
}
return [htmlElRef, setFocus]
}
import {
calculatePayoutAfterCorrectBet,
calculateShares,
getProbability,
getOutcomeProbabilityAfterBet,
} from '../../common/calculate'
import { useFocus } from '../hooks/use-focus'
export function BetPanel(props: {
contract: Contract
contract: FullContract<DPM | CPMM, Binary>
className?: string
title?: string // Set if BetPanel is on a feed modal
selected?: 'YES' | 'NO'
@ -49,7 +40,6 @@ export function BetPanel(props: {
}, [])
const { contract, className, title, selected, onBetSuccess } = props
const { totalShares, phantomShares } = contract
const user = useUser()
@ -102,20 +92,16 @@ export function BetPanel(props: {
const betDisabled = isSubmitting || !betAmount || error
const initialProb = getProbability(contract.totalShares)
const initialProb = getProbability(contract)
const outcomeProb = getProbabilityAfterBet(
contract.totalShares,
const outcomeProb = getOutcomeProbabilityAfterBet(
contract,
betChoice || 'YES',
betAmount ?? 0
)
const resultProb = betChoice === 'NO' ? 1 - outcomeProb : outcomeProb
const shares = calculateShares(
contract.totalShares,
betAmount ?? 0,
betChoice || 'YES'
)
const shares = calculateShares(contract, betAmount ?? 0, betChoice || 'YES')
const currentPayout = betAmount
? calculatePayoutAfterCorrectBet(contract, {
@ -127,11 +113,23 @@ export function BetPanel(props: {
const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0
const currentReturnPercent = (currentReturn * 100).toFixed() + '%'
const panelTitle = title ?? 'Place a trade'
if (title) {
focusAmountInput()
}
const tooltip =
contract.mechanism === 'dpm-2'
? `Current payout for ${formatWithCommas(shares)} / ${formatWithCommas(
shares +
contract.totalShares[betChoice ?? 'YES'] -
(contract.phantomShares
? contract.phantomShares[betChoice ?? 'YES']
: 0)
)} ${betChoice} shares`
: undefined
return (
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
<Title
@ -172,15 +170,8 @@ export function BetPanel(props: {
<div>
Payout if <OutcomeLabel outcome={betChoice ?? 'YES'} />
</div>
<InfoTooltip
text={`Current payout for ${formatWithCommas(
shares
)} / ${formatWithCommas(
shares +
totalShares[betChoice ?? 'YES'] -
(phantomShares ? phantomShares[betChoice ?? 'YES'] : 0)
)} ${betChoice} shares`}
/>
{tooltip && <InfoTooltip text={tooltip} />}
</Row>
<Row className="flex-wrap items-end justify-end gap-2">
<span className="whitespace-nowrap">

View File

@ -1,14 +1,15 @@
import clsx from 'clsx'
import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { Contract } from '../lib/firebase/contracts'
import { BetPanel } from './bet-panel'
import { Row } from './layout/row'
import { YesNoSelector } from './yes-no-selector'
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
// Inline version of a bet panel. Opens BetPanel in a new modal.
export default function BetRow(props: {
contract: Contract
contract: FullContract<DPM | CPMM, Binary>
className?: string
labelClassName?: string
}) {

View File

@ -22,6 +22,12 @@ import {
} from '../lib/firebase/contracts'
import { Row } from './layout/row'
import { UserLink } from './user-page'
import { sellBet } from '../lib/firebase/api-call'
import { ConfirmationButton } from './confirmation-button'
import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label'
import { filterDefined } from '../../common/util/array'
import { LoadingIndicator } from './loading-indicator'
import { SiteLink } from './site-link'
import {
calculatePayout,
calculateSaleAmount,
@ -30,12 +36,6 @@ import {
getProbabilityAfterSale,
resolvedPayout,
} from '../../common/calculate'
import { sellBet } from '../lib/firebase/api-call'
import { ConfirmationButton } from './confirmation-button'
import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label'
import { filterDefined } from '../../common/util/array'
import { LoadingIndicator } from './loading-indicator'
import { SiteLink } from './site-link'
type BetSort = 'newest' | 'profit' | 'settled' | 'value'
type BetFilter = 'open' | 'closed' | 'resolved' | 'all'
@ -400,7 +400,7 @@ export function MyBetsSummary(props: {
<>
Payout at{' '}
<span className="text-blue-400">
{formatPercent(getProbability(contract.totalShares))}
{formatPercent(getProbability(contract))}
</span>
</>
) : (
@ -427,28 +427,46 @@ export function ContractBetsTable(props: {
const { contract, bets, className } = props
const [sales, buys] = _.partition(bets, (bet) => bet.sale)
const salesDict = _.fromPairs(
sales.map((sale) => [sale.sale?.betId ?? '', sale])
)
const { isResolved } = contract
const [redemptions, normalBets] = _.partition(buys, (b) => b.isRedemption)
const amountRedeemed = Math.floor(
-0.5 * _.sumBy(redemptions, (b) => b.shares)
)
const { isResolved, mechanism } = contract
const isCPMM = mechanism === 'cpmm-1'
return (
<div className={clsx('overflow-x-auto', className)}>
{amountRedeemed > 0 && (
<>
<div className="text-gray-500 text-sm pl-2">
{amountRedeemed} YES shares and {amountRedeemed} NO shares
automatically redeemed for {formatMoney(amountRedeemed)}.
</div>
<Spacer h={4} />
</>
)}
<table className="table-zebra table-compact table w-full text-gray-500">
<thead>
<tr className="p-2">
<th></th>
<th>Outcome</th>
<th>Amount</th>
<th>{isResolved ? <>Payout</> : <>Sale price</>}</th>
{!isResolved && <th>Payout if chosen</th>}
<th>Probability</th>
{!isCPMM && <th>{isResolved ? <>Payout</> : <>Sale price</>}</th>}
{!isCPMM && !isResolved && <th>Payout if chosen</th>}
<th>Shares</th>
<th>Probability</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{buys.map((bet) => (
{normalBets.map((bet) => (
<BetRow
key={bet.id}
bet={bet}
@ -476,9 +494,12 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
loanAmount,
} = bet
const { isResolved, closeTime } = contract
const { isResolved, closeTime, mechanism } = contract
const isClosed = closeTime && Date.now() > closeTime
const isCPMM = mechanism === 'cpmm-1'
const saleAmount = saleBet?.sale?.amount
const saleDisplay = isAnte ? (
@ -501,7 +522,7 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
return (
<tr>
<td className="text-neutral">
{!isResolved && !isClosed && !isSold && !isAnte && (
{!isCPMM && !isResolved && !isClosed && !isSold && !isAnte && (
<SellButton contract={contract} bet={bet} />
)}
</td>
@ -512,12 +533,12 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
{formatMoney(amount)}
{loanAmount ? ` (${formatMoney(loanAmount ?? 0)} loan)` : ''}
</td>
<td>{saleDisplay}</td>
{!isResolved && <td>{payoutIfChosenDisplay}</td>}
{!isCPMM && <td>{saleDisplay}</td>}
{!isCPMM && !isResolved && <td>{payoutIfChosenDisplay}</td>}
<td>{formatWithCommas(shares)}</td>
<td>
{formatPercent(probBefore)} {formatPercent(probAfter)}
</td>
<td>{formatWithCommas(shares)}</td>
<td>{dayjs(createdTime).format('MMM D, h:mma')}</td>
</tr>
)
@ -535,15 +556,11 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
const [isSubmitting, setIsSubmitting] = useState(false)
const initialProb = getOutcomeProbability(
contract.totalShares,
contract,
outcome === 'NO' ? 'YES' : outcome
)
const outcomeProb = getProbabilityAfterSale(
contract.totalShares,
outcome,
shares
)
const outcomeProb = getProbabilityAfterSale(contract, outcome, shares)
const saleAmount = calculateSaleAmount(contract, bet)
const profit = saleAmount - bet.amount

View File

@ -125,7 +125,7 @@ function AbbrContractDetails(props: {
}) {
const { contract, showHotVolume, showCloseTime } = props
const { volume24Hours, creatorName, creatorUsername, closeTime } = contract
const { truePool } = contractMetrics(contract)
const { liquidityLabel } = contractMetrics(contract)
return (
<Col className={clsx('gap-2 text-sm text-gray-500')}>
@ -156,7 +156,7 @@ function AbbrContractDetails(props: {
) : (
<Row className="gap-1">
{/* <DatabaseIcon className="h-5 w-5" /> */}
{formatMoney(truePool)} pool
{liquidityLabel}
</Row>
)}
</Row>
@ -170,7 +170,8 @@ export function ContractDetails(props: {
}) {
const { contract, isCreator } = props
const { closeTime, creatorName, creatorUsername } = contract
const { truePool, createdDate, resolvedDate } = contractMetrics(contract)
const { liquidityLabel, createdDate, resolvedDate } =
contractMetrics(contract)
const tweetText = getTweetText(contract, !!isCreator)
@ -224,7 +225,7 @@ export function ContractDetails(props: {
<Row className="items-center gap-1">
<DatabaseIcon className="h-5 w-5" />
<div className="whitespace-nowrap">{formatMoney(truePool)} pool</div>
<div className="whitespace-nowrap">{liquidityLabel}</div>
</Row>
<TweetButton className={'self-end'} tweetText={tweetText} />
@ -236,7 +237,8 @@ export function ContractDetails(props: {
// String version of the above, to send to the OpenGraph image generator
export function contractTextDetails(contract: Contract) {
const { closeTime, tags } = contract
const { truePool, createdDate, resolvedDate } = contractMetrics(contract)
const { createdDate, resolvedDate, liquidityLabel } =
contractMetrics(contract)
const hashtags = tags.map((tag) => `#${tag}`)
@ -247,7 +249,7 @@ export function contractTextDetails(contract: Contract) {
closeTime
).format('MMM D, h:mma')}`
: '') +
`${formatMoney(truePool)} pool` +
`${liquidityLabel}` +
(hashtags.length > 0 ? `${hashtags.join(' ')}` : '')
)
}

View File

@ -20,6 +20,7 @@ import { Fold } from '../../common/fold'
import { FoldTagList } from './tags-list'
import { ContractActivity } from './feed/contract-activity'
import { AnswersGraph } from './answers/answers-graph'
import { DPM, FreeResponse, FullContract } from '../../common/contract'
export const ContractOverview = (props: {
contract: Contract
@ -81,7 +82,10 @@ export const ContractOverview = (props: {
{isBinary ? (
<ContractProbGraph contract={contract} bets={bets} />
) : (
<AnswersGraph contract={contract} bets={bets} />
<AnswersGraph
contract={contract as FullContract<DPM, FreeResponse>}
bets={bets}
/>
)}
{children}

View File

@ -2,27 +2,29 @@ import { DatumValue } from '@nivo/core'
import { ResponsiveLine } from '@nivo/line'
import dayjs from 'dayjs'
import { Bet } from '../../common/bet'
import { getProbability } from '../../common/calculate'
import { getInitialProbability } from '../../common/calculate'
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
import { useBetsWithoutAntes } from '../hooks/use-bets'
import { useWindowSize } from '../hooks/use-window-size'
import { Contract } from '../lib/firebase/contracts'
export function ContractProbGraph(props: { contract: Contract; bets: Bet[] }) {
export function ContractProbGraph(props: {
contract: FullContract<DPM | CPMM, Binary>
bets: Bet[]
}) {
const { contract } = props
const { phantomShares, resolutionTime, closeTime } = contract
const { resolutionTime, closeTime } = contract
const bets = useBetsWithoutAntes(contract, props.bets)
const startProb = getProbability(
phantomShares as { [outcome: string]: number }
const bets = useBetsWithoutAntes(contract, props.bets).filter(
(b) => !b.isRedemption
)
const times = bets
? [contract.createdTime, ...bets.map((bet) => bet.createdTime)].map(
(time) => new Date(time)
)
: []
const probs = bets ? [startProb, ...bets.map((bet) => bet.probAfter)] : []
const startProb = getInitialProbability(contract)
const times = [
contract.createdTime,
...bets.map((bet) => bet.createdTime),
].map((time) => new Date(time))
const probs = [startProb, ...bets.map((bet) => bet.probAfter)]
const isClosed = !!closeTime && Date.now() > closeTime
const latestTime = dayjs(

View File

@ -13,6 +13,7 @@ import { Col } from './layout/col'
import { SiteLink } from './site-link'
import { ContractCard } from './contract-card'
import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
import { Answer } from '../../common/answer'
export function ContractsGrid(props: {
contracts: Contract[]
@ -217,7 +218,11 @@ export function SearchableGrid(props: {
check(c.creatorName) ||
check(c.creatorUsername) ||
check(c.lowercaseTags.map((tag) => `#${tag}`).join(' ')) ||
check((c.answers ?? []).map((answer) => answer.text).join(' '))
check(
((c as any).answers ?? [])
.map((answer: Answer) => answer.text)
.join(' ')
)
)
if (sort === 'newest' || sort === 'all') {

View File

@ -4,7 +4,12 @@ import { Answer } from '../../../common/answer'
import { Bet } from '../../../common/bet'
import { getOutcomeProbability } from '../../../common/calculate'
import { Comment } from '../../../common/comment'
import { Contract } from '../../../common/contract'
import {
Contract,
DPM,
FreeResponse,
FullContract,
} from '../../../common/contract'
import { User } from '../../../common/user'
import { mapCommentsByBetId } from '../../lib/firebase/comments'
@ -169,7 +174,7 @@ function groupBets(
}
function getAnswerGroups(
contract: Contract,
contract: FullContract<DPM, FreeResponse>,
bets: Bet[],
comments: Comment[],
user: User | undefined | null,
@ -181,7 +186,7 @@ function getAnswerGroups(
const { sortByProb, abbreviated } = options
let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
(outcome) => getOutcomeProbability(contract.totalShares, outcome) > 0.01
(outcome) => getOutcomeProbability(contract, outcome) > 0.01
)
if (abbreviated) {
const lastComment = _.last(comments)
@ -204,7 +209,7 @@ function getAnswerGroups(
if (sortByProb) {
outcomes = _.sortBy(
outcomes,
(outcome) => -1 * getOutcomeProbability(contract.totalShares, outcome)
(outcome) => -1 * getOutcomeProbability(contract, outcome)
)
} else {
// Sort by recent bet.
@ -266,7 +271,9 @@ export function getAllContractActivityItems(
let answer: Answer | undefined
if (filterToOutcome) {
bets = bets.filter((bet) => bet.outcome === filterToOutcome)
answer = contract.answers?.find((answer) => answer.id === filterToOutcome)
answer = (contract as FullContract<DPM, FreeResponse>).answers?.find(
(answer) => answer.id === filterToOutcome
)
}
const items: ActivityItem[] =
@ -278,10 +285,16 @@ export function getAllContractActivityItems(
items.push(
...(outcomeType === 'FREE_RESPONSE' && !filterToOutcome
? getAnswerGroups(contract, bets, comments, user, {
? getAnswerGroups(
contract as FullContract<DPM, FreeResponse>,
bets,
comments,
user,
{
sortByProb: true,
abbreviated,
})
}
)
: groupBets(bets, comments, contract, user?.id, {
hideOutcome: !!filterToOutcome,
abbreviated,
@ -317,10 +330,16 @@ export function getRecentContractActivityItems(
const items =
contract.outcomeType === 'FREE_RESPONSE'
? getAnswerGroups(contract, bets, comments, user, {
? getAnswerGroups(
contract as FullContract<DPM, FreeResponse>,
bets,
comments,
user,
{
sortByProb: false,
abbreviated: true,
})
}
)
: groupBets(bets, comments, contract, user?.id, {
hideOutcome: false,
abbreviated: true,

View File

@ -1,6 +1,6 @@
// From https://tailwindui.com/components/application-ui/lists/feeds
import { Fragment, useState } from 'react'
import _ from 'lodash'
import * as _ from 'lodash'
import {
BanIcon,
CheckIcon,
@ -46,6 +46,7 @@ import { Avatar } from '../avatar'
import { useAdmin } from '../../hooks/use-admin'
import { Answer } from '../../../common/answer'
import { ActivityItem } from './activity-items'
import { FreeResponse, FullContract } from '../../../common/contract'
export function FeedItems(props: {
contract: Contract
@ -191,12 +192,6 @@ function FeedBet(props: {
const bought = amount >= 0 ? 'bought' : 'sold'
const money = formatMoney(Math.abs(amount))
const answer =
!hideOutcome &&
(contract.answers?.find((answer: Answer) => answer?.id === outcome) as
| Answer
| undefined)
return (
<>
<div>
@ -222,18 +217,13 @@ function FeedBet(props: {
</div>
)}
</div>
<div className={clsx('min-w-0 flex-1 pb-1.5', !answer && 'pt-1.5')}>
{answer && (
<div className="text-neutral mb-2" style={{ fontSize: 15 }}>
<Linkify text={answer.text} />
</div>
)}
<div className={'min-w-0 flex-1 pb-1.5'}>
<div className="text-sm text-gray-500">
<span>
{isSelf ? 'You' : isCreator ? contract.creatorName : 'A trader'}
</span>{' '}
{bought} {money}
{!answer && !hideOutcome && (
{!hideOutcome && (
<>
{' '}
of <OutcomeLabel outcome={outcome} />
@ -425,7 +415,7 @@ export function FeedQuestion(props: {
const { contract, showDescription } = props
const { creatorName, creatorUsername, question, resolution, outcomeType } =
contract
const { truePool } = contractMetrics(contract)
const { liquidityLabel } = contractMetrics(contract)
const isBinary = outcomeType === 'BINARY'
const closeMessage =
@ -453,7 +443,7 @@ export function FeedQuestion(props: {
asked
{/* Currently hidden on mobile; ideally we'd fit this in somewhere. */}
<span className="float-right hidden text-gray-400 sm:inline">
{formatMoney(truePool)} pool
{liquidityLabel}
{closeMessage}
</span>
</div>
@ -517,7 +507,10 @@ function FeedDescription(props: { contract: Contract }) {
)
}
function FeedCreateAnswer(props: { contract: Contract; answer: Answer }) {
function FeedCreateAnswer(props: {
contract: FullContract<any, FreeResponse>
answer: Answer
}) {
const { answer } = props
return (
@ -677,7 +670,7 @@ function FeedBetGroup(props: {
}
function FeedAnswerGroup(props: {
contract: Contract
contract: FullContract<any, FreeResponse>
answer: Answer
items: ActivityItem[]
}) {

View File

@ -1,7 +1,6 @@
import clsx from 'clsx'
import React, { useEffect, useState } from 'react'
import { Contract } from '../lib/firebase/contracts'
import { Col } from './layout/col'
import { Title } from './title'
import { User } from '../lib/firebase/users'
@ -10,12 +9,14 @@ import { Spacer } from './layout/spacer'
import { ResolveConfirmationButton } from './confirmation-button'
import { resolveMarket } from '../lib/firebase/api-call'
import { ProbabilitySelector } from './probability-selector'
import { DPM_CREATOR_FEE } from '../../common/fees'
import { getProbability } from '../../common/calculate'
import { CREATOR_FEE } from '../../common/fees'
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
import { formatMoney } from '../../common/util/format'
export function ResolutionPanel(props: {
creator: User
contract: Contract
contract: FullContract<DPM | CPMM, Binary>
className?: string
}) {
useEffect(() => {
@ -25,11 +26,16 @@ export function ResolutionPanel(props: {
const { contract, className } = props
const earnedFees =
contract.mechanism === 'dpm-2'
? `${DPM_CREATOR_FEE * 100}% of trader profits`
: `${formatMoney(contract.collectedFees.creatorFee)} in fees`
const [outcome, setOutcome] = useState<
'YES' | 'NO' | 'MKT' | 'CANCEL' | undefined
>()
const [prob, setProb] = useState(getProbability(contract.totalShares) * 100)
const [prob, setProb] = useState(getProbability(contract) * 100)
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState<string | undefined>(undefined)
@ -85,14 +91,14 @@ export function ResolutionPanel(props: {
Winnings will be paid out to YES bettors.
<br />
<br />
You earn {CREATOR_FEE * 100}% of trader profits.
You will earn {earnedFees}.
</>
) : outcome === 'NO' ? (
<>
Winnings will be paid out to NO bettors.
<br />
<br />
You earn {CREATOR_FEE * 100}% of trader profits.
You will earn {earnedFees}.
</>
) : outcome === 'CANCEL' ? (
<>The pool will be returned to traders with no fees.</>
@ -103,7 +109,7 @@ export function ResolutionPanel(props: {
probabilityInt={Math.round(prob)}
setProbabilityInt={setProb}
/>
<div>You earn {CREATOR_FEE * 100}% of trader profits.</div>
You will earn {earnedFees}.
</Col>
) : (
<>Resolving this market will immediately pay out traders.</>

11
web/hooks/use-focus.ts Normal file
View File

@ -0,0 +1,11 @@
import { useRef } from 'react'
// Focus helper from https://stackoverflow.com/a/54159564/1222351
export function useFocus(): [React.RefObject<HTMLElement>, () => void] {
const htmlElRef = useRef<HTMLElement>(null)
const setFocus = () => {
htmlElRef.current && htmlElRef.current.focus()
}
return [htmlElRef, setFocus]
}

View File

@ -17,9 +17,12 @@ import _ from 'lodash'
import { app } from './init'
import { getValues, listenForValue, listenForValues } from './utils'
import { Contract } from '../../../common/contract'
import { getProbability } from '../../../common/calculate'
import { Binary, Contract, FullContract } from '../../../common/contract'
import { getDpmProbability } from '../../../common/calculate-dpm'
import { createRNG, shuffle } from '../../../common/util/random'
import { getCpmmProbability } from '../../../common/calculate-cpmm'
import { formatMoney } from '../../../common/util/format'
import { getCpmmLiquidity } from '../../../common/calculate-cpmm'
export type { Contract }
export function contractPath(contract: Contract) {
@ -37,15 +40,25 @@ export function contractMetrics(contract: Contract) {
? dayjs(resolutionTime).format('MMM D')
: undefined
return { truePool, createdDate, resolvedDate }
const liquidityLabel =
contract.mechanism === 'dpm-2'
? `${formatMoney(truePool)} pool`
: `${formatMoney(
contract.totalLiquidity ?? getCpmmLiquidity(pool, contract.p)
)} liquidity`
return { truePool, liquidityLabel, createdDate, resolvedDate }
}
export function getBinaryProbPercent(contract: Contract) {
const { totalShares, resolutionProbability } = contract
export function getBinaryProbPercent(contract: FullContract<any, Binary>) {
const { totalShares, pool, p, resolutionProbability, mechanism } = contract
const prob =
resolutionProbability ?? mechanism === 'cpmm-1'
? getCpmmProbability(pool, p)
: getDpmProbability(totalShares)
const prob = resolutionProbability ?? getProbability(totalShares)
const probPercent = Math.round(prob * 100) + '%'
return probPercent
}

View File

@ -175,7 +175,6 @@ function BetsSection(props: {
bets: Bet[]
}) {
const { contract, user } = props
const isBinary = contract.outcomeType === 'BINARY'
const bets = useBets(contract.id) ?? props.bets
// Decending creation time.

View File

@ -1,7 +1,7 @@
import { Bet } from '../../../../common/bet'
import { getProbability } from '../../../../common/calculate'
import { getDpmProbability } from '../../../../common/calculate-dpm'
import { Comment } from '../../../../common/comment'
import { Contract } from '../../../../common/contract'
import { DPM, FullContract } from '../../../../common/contract'
export type LiteMarket = {
// Unique identifer for this market
@ -56,7 +56,7 @@ export function toLiteMarket({
isResolved,
resolution,
resolutionTime,
}: Contract): LiteMarket {
}: FullContract<DPM, any>): LiteMarket {
return {
id,
creatorUsername,
@ -72,7 +72,7 @@ export function toLiteMarket({
tags,
url: `https://manifold.markets/${creatorUsername}/${slug}`,
pool: pool.YES + pool.NO,
probability: getProbability(totalShares),
probability: getDpmProbability(totalShares),
volume7Days,
volume24Hours,
isResolved,

View File

@ -236,10 +236,9 @@ export function NewContract(props: { question: string; tag?: string }) {
<div className="form-control mb-1 items-start">
<label className="label mb-1 gap-2">
<span>Market ante</span>
<span>Market subsidy</span>
<InfoTooltip
text={`Subsidize your market to encourage trading. Ante bets are set to match your initial probability.
You earn ${CREATOR_FEE * 100}% of trader profits.`}
text={`Provide liquidity to encourage traders to participate.`}
/>
</label>
<AmountInput

View File

@ -3,11 +3,12 @@ import dayjs from 'dayjs'
import Link from 'next/link'
import { useState } from 'react'
import Textarea from 'react-expanding-textarea'
import { getProbability } from '../../common/calculate'
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
import { parseWordsAsTags } from '../../common/util/parse'
import { AmountInput } from '../components/amount-input'
import { InfoTooltip } from '../components/info-tooltip'
import { Col } from '../components/layout/col'
import { Row } from '../components/layout/row'
import { Spacer } from '../components/layout/spacer'
@ -16,7 +17,7 @@ import { Page } from '../components/page'
import { Title } from '../components/title'
import { useUser } from '../hooks/use-user'
import { createContract } from '../lib/firebase/api-call'
import { Contract, contractPath } from '../lib/firebase/contracts'
import { contractPath } from '../lib/firebase/contracts'
type Prediction = {
question: string
@ -25,8 +26,8 @@ type Prediction = {
createdUrl?: string
}
function toPrediction(contract: Contract): Prediction {
const startProb = getProbability(contract.totalShares)
function toPrediction(contract: FullContract<DPM | CPMM, Binary>): Prediction {
const startProb = getProbability(contract)
return {
question: contract.question,
description: contract.description,
@ -101,7 +102,9 @@ export default function MakePredictions() {
const [description, setDescription] = useState('')
const [tags, setTags] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [createdContracts, setCreatedContracts] = useState<Contract[]>([])
const [createdContracts, setCreatedContracts] = useState<
FullContract<DPM | CPMM, Binary>[]
>([])
const [ante, setAnte] = useState<number | undefined>(100)
const [anteError, setAnteError] = useState<string | undefined>()