Cfmm (#64)
* 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:
parent
bd62d8fbcd
commit
c183e00d47
|
@ -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
|
||||
|
|
|
@ -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
217
common/calculate-cpmm.ts
Normal 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
293
common/calculate-dpm.ts
Normal 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 }
|
||||
}
|
41
common/calculate-fixed-payouts.ts
Normal file
41
common/calculate-fixed-payouts.ts
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
13
common/liquidity-provision.ts
Normal file
13
common/liquidity-provision.ts
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
189
common/payouts-dpm.ts
Normal 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
123
common/payouts-fixed.ts
Normal 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,
|
||||
}))
|
||||
}
|
|
@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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('$', '')
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
|
|
90
functions/src/redeem-shares.ts
Normal file
90
functions/src/redeem-shares.ts
Normal 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()
|
|
@ -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],
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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' }
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
}) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(' ')}` : '')
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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[]
|
||||
}) {
|
||||
|
|
|
@ -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
11
web/hooks/use-focus.ts
Normal 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]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>()
|
||||
|
|
Loading…
Reference in New Issue
Block a user