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 { Bet } from './bet'
|
||||||
import { getProbability } from './calculate'
|
import { getDpmProbability } from './calculate-dpm'
|
||||||
import { Contract } from './contract'
|
import { Binary, CPMM, DPM, FreeResponse, FullContract } from './contract'
|
||||||
import { User } from './user'
|
import { User } from './user'
|
||||||
|
import { LiquidityProvision } from './liquidity-provision'
|
||||||
|
import { noFees } from './fees'
|
||||||
|
|
||||||
export const PHANTOM_ANTE = 0.001
|
export const PHANTOM_ANTE = 0.001
|
||||||
export const MINIMUM_ANTE = 10
|
export const MINIMUM_ANTE = 10
|
||||||
|
|
||||||
export const calcStartPool = (initialProbInt: number, ante = 0) => {
|
export function getCpmmInitialLiquidity(
|
||||||
const p = initialProbInt / 100.0
|
creator: User,
|
||||||
const totalAnte = PHANTOM_ANTE + ante
|
contract: FullContract<CPMM, Binary>,
|
||||||
|
anteId: string,
|
||||||
|
amount: number
|
||||||
|
) {
|
||||||
|
const { createdTime, p } = contract
|
||||||
|
|
||||||
const sharesYes = Math.sqrt(p * totalAnte ** 2)
|
const lp: LiquidityProvision = {
|
||||||
const sharesNo = Math.sqrt(totalAnte ** 2 - sharesYes ** 2)
|
id: anteId,
|
||||||
|
userId: creator.id,
|
||||||
|
contractId: contract.id,
|
||||||
|
createdTime,
|
||||||
|
isAnte: true,
|
||||||
|
|
||||||
const poolYes = p * ante
|
amount: amount,
|
||||||
const poolNo = (1 - p) * ante
|
liquidity: amount,
|
||||||
|
p: p,
|
||||||
|
pool: { YES: 0, NO: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
const phantomYes = Math.sqrt(p) * PHANTOM_ANTE
|
return lp
|
||||||
const phantomNo = Math.sqrt(1 - p) * PHANTOM_ANTE
|
|
||||||
|
|
||||||
return { sharesYes, sharesNo, poolYes, poolNo, phantomYes, phantomNo }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAnteBets(
|
export function getAnteBets(
|
||||||
creator: User,
|
creator: User,
|
||||||
contract: Contract,
|
contract: FullContract<DPM, Binary>,
|
||||||
yesAnteId: string,
|
yesAnteId: string,
|
||||||
noAnteId: string
|
noAnteId: string
|
||||||
) {
|
) {
|
||||||
const p = getProbability(contract.totalShares)
|
const p = getDpmProbability(contract.totalShares)
|
||||||
const ante = contract.totalBets.YES + contract.totalBets.NO
|
const ante = contract.totalBets.YES + contract.totalBets.NO
|
||||||
|
|
||||||
const { createdTime } = contract
|
const { createdTime } = contract
|
||||||
|
@ -44,6 +54,7 @@ export function getAnteBets(
|
||||||
probAfter: p,
|
probAfter: p,
|
||||||
createdTime,
|
createdTime,
|
||||||
isAnte: true,
|
isAnte: true,
|
||||||
|
fees: noFees,
|
||||||
}
|
}
|
||||||
|
|
||||||
const noBet: Bet = {
|
const noBet: Bet = {
|
||||||
|
@ -57,6 +68,7 @@ export function getAnteBets(
|
||||||
probAfter: p,
|
probAfter: p,
|
||||||
createdTime,
|
createdTime,
|
||||||
isAnte: true,
|
isAnte: true,
|
||||||
|
fees: noFees,
|
||||||
}
|
}
|
||||||
|
|
||||||
return { yesBet, noBet }
|
return { yesBet, noBet }
|
||||||
|
@ -64,7 +76,7 @@ export function getAnteBets(
|
||||||
|
|
||||||
export function getFreeAnswerAnte(
|
export function getFreeAnswerAnte(
|
||||||
creator: User,
|
creator: User,
|
||||||
contract: Contract,
|
contract: FullContract<DPM, FreeResponse>,
|
||||||
anteBetId: string
|
anteBetId: string
|
||||||
) {
|
) {
|
||||||
const { totalBets, totalShares } = contract
|
const { totalBets, totalShares } = contract
|
||||||
|
@ -84,6 +96,7 @@ export function getFreeAnswerAnte(
|
||||||
probAfter: 1,
|
probAfter: 1,
|
||||||
createdTime,
|
createdTime,
|
||||||
isAnte: true,
|
isAnte: true,
|
||||||
|
fees: noFees,
|
||||||
}
|
}
|
||||||
|
|
||||||
return anteBet
|
return anteBet
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Fees } from './fees'
|
||||||
|
|
||||||
export type Bet = {
|
export type Bet = {
|
||||||
id: string
|
id: string
|
||||||
userId: string
|
userId: string
|
||||||
|
@ -6,7 +8,7 @@ export type Bet = {
|
||||||
amount: number // bet size; negative if SELL bet
|
amount: number // bet size; negative if SELL bet
|
||||||
loanAmount?: number
|
loanAmount?: number
|
||||||
outcome: string
|
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
|
probBefore: number
|
||||||
probAfter: number
|
probAfter: number
|
||||||
|
@ -17,8 +19,12 @@ export type Bet = {
|
||||||
// TODO: add sale time?
|
// TODO: add sale time?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fees: Fees
|
||||||
|
|
||||||
isSold?: boolean // true if this BUY bet has been sold
|
isSold?: boolean // true if this BUY bet has been sold
|
||||||
isAnte?: boolean
|
isAnte?: boolean
|
||||||
|
isLiquidityProvision?: boolean
|
||||||
|
isRedemption?: boolean
|
||||||
|
|
||||||
createdTime: number
|
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 { Bet } from './bet'
|
||||||
import { Contract } from './contract'
|
import {
|
||||||
import { FEES } from './fees'
|
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 }) {
|
export function getProbability(contract: FullContract<DPM | CPMM, Binary>) {
|
||||||
// For binary contracts only.
|
return contract.mechanism === 'cpmm-1'
|
||||||
return getOutcomeProbability(totalShares, 'YES')
|
? getCpmmProbability(contract.pool, contract.p)
|
||||||
|
: getDpmProbability(contract.totalShares)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOutcomeProbability(
|
export function getInitialProbability(
|
||||||
totalShares: {
|
contract: FullContract<DPM | CPMM, Binary>
|
||||||
[outcome: string]: number
|
|
||||||
},
|
|
||||||
outcome: string
|
|
||||||
) {
|
) {
|
||||||
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
|
return (
|
||||||
const shares = totalShares[outcome] ?? 0
|
contract.initialProbability ??
|
||||||
return shares ** 2 / squareSum
|
(contract.mechanism === 'cpmm-1'
|
||||||
|
? getCpmmProbability(contract.pool, contract.p)
|
||||||
|
: getDpmProbability(contract.phantomShares ?? contract.totalShares))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProbabilityAfterBet(
|
export function getOutcomeProbability(contract: Contract, outcome: string) {
|
||||||
totalShares: {
|
return contract.mechanism === 'cpmm-1'
|
||||||
[outcome: string]: number
|
? getCpmmProbability(contract.pool, contract.p)
|
||||||
},
|
: getDpmOutcomeProbability(contract.totalShares, outcome)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOutcomeProbabilityAfterBet(
|
||||||
|
contract: Contract,
|
||||||
outcome: string,
|
outcome: string,
|
||||||
bet: number
|
bet: number
|
||||||
) {
|
) {
|
||||||
const shares = calculateShares(totalShares, bet, outcome)
|
return contract.mechanism === 'cpmm-1'
|
||||||
|
? getCpmmOutcomeProbabilityAfterBet(
|
||||||
const prevShares = totalShares[outcome] ?? 0
|
contract as FullContract<CPMM, Binary>,
|
||||||
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
|
outcome,
|
||||||
|
bet
|
||||||
return getOutcomeProbability(newTotalShares, outcome)
|
)
|
||||||
}
|
: getDpmOutcomeProbabilityAfterBet(contract.totalShares, outcome, bet)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateShares(
|
export function calculateShares(
|
||||||
totalShares: {
|
contract: Contract,
|
||||||
[outcome: string]: number
|
|
||||||
},
|
|
||||||
bet: number,
|
bet: number,
|
||||||
betChoice: string
|
betChoice: string
|
||||||
) {
|
) {
|
||||||
const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2)
|
return contract.mechanism === 'cpmm-1'
|
||||||
const shares = totalShares[betChoice] ?? 0
|
? calculateCpmmSharesAfterFee(
|
||||||
|
contract as FullContract<CPMM, Binary>,
|
||||||
const c = 2 * bet * Math.sqrt(squareSum)
|
bet,
|
||||||
|
betChoice
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
|
: calculateDpmShares(contract.totalShares, 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateSaleAmount(contract: Contract, bet: Bet) {
|
export function calculateSaleAmount(contract: Contract, bet: Bet) {
|
||||||
const { amount } = bet
|
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
|
||||||
const winnings = calculateShareValue(contract, bet)
|
? calculateCpmmSale(contract, bet).saleValue
|
||||||
return deductFees(amount, winnings)
|
: calculateDpmSaleAmount(contract, bet)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) {
|
export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) {
|
||||||
const { totalShares, pool, totalBets } = contract
|
return contract.mechanism === 'cpmm-1'
|
||||||
const { shares, amount, outcome } = bet
|
? bet.shares
|
||||||
|
: calculateDpmPayoutAfterCorrectBet(contract, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateMktPayout(contract: Contract, bet: Bet) {
|
export function getProbabilityAfterSale(
|
||||||
if (contract.outcomeType === 'BINARY')
|
contract: Contract,
|
||||||
return calculateBinaryMktPayout(contract, bet)
|
outcome: string,
|
||||||
|
shares: number
|
||||||
const { totalShares, pool } = contract
|
) {
|
||||||
|
return contract.mechanism === 'cpmm-1'
|
||||||
const totalPool = _.sum(Object.values(pool))
|
? getCpmmProbabilityAfterSale(
|
||||||
const sharesSquareSum = _.sumBy(
|
contract as FullContract<CPMM, Binary>,
|
||||||
Object.values(totalShares),
|
{ shares, outcome } as Bet
|
||||||
(shares) => shares ** 2
|
|
||||||
)
|
)
|
||||||
|
: getDpmProbabilityAfterSale(contract.totalShares, outcome, shares)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateBinaryMktPayout(contract: Contract, bet: Bet) {
|
export function calculatePayout(contract: Contract, bet: Bet, outcome: string) {
|
||||||
const { resolutionProbability, totalShares, phantomShares } = contract
|
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
|
||||||
const p =
|
? calculateFixedPayout(contract, bet, outcome)
|
||||||
resolutionProbability !== undefined
|
: calculateDpmPayout(contract, bet, outcome)
|
||||||
? 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 resolvedPayout(contract: Contract, bet: Bet) {
|
export function resolvedPayout(contract: Contract, bet: Bet) {
|
||||||
if (contract.resolution)
|
const outcome = contract.resolution
|
||||||
return calculatePayout(contract, bet, contract.resolution)
|
if (!outcome) throw new Error('Contract not resolved')
|
||||||
throw new Error('Contract was not resolved')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deductFees = (betAmount: number, winnings: number) => {
|
return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY'
|
||||||
return winnings > betAmount
|
? calculateFixedPayout(contract, bet, outcome)
|
||||||
? betAmount + (1 - FEES) * (winnings - betAmount)
|
: calculateDpmPayout(contract, bet, outcome)
|
||||||
: winnings
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { Answer } from './answer'
|
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
|
id: string
|
||||||
slug: string // auto-generated; must be unique
|
slug: string // auto-generated; must be unique
|
||||||
|
|
||||||
|
@ -15,16 +19,6 @@ export type Contract = {
|
||||||
lowercaseTags: string[]
|
lowercaseTags: string[]
|
||||||
visibility: 'public' | 'unlisted'
|
visibility: 'public' | 'unlisted'
|
||||||
|
|
||||||
outcomeType: 'BINARY' | 'MULTI' | 'FREE_RESPONSE'
|
|
||||||
multiOutcomes?: string[] // Used for outcomeType 'MULTI'.
|
|
||||||
answers?: Answer[] // Used for outcomeType 'FREE_RESPONSE'.
|
|
||||||
|
|
||||||
mechanism: 'dpm-2'
|
|
||||||
phantomShares?: { [outcome: string]: number }
|
|
||||||
pool: { [outcome: string]: number }
|
|
||||||
totalShares: { [outcome: string]: number }
|
|
||||||
totalBets: { [outcome: string]: number }
|
|
||||||
|
|
||||||
createdTime: number // Milliseconds since epoch
|
createdTime: number // Milliseconds since epoch
|
||||||
lastUpdatedTime: number // If the question or description was changed
|
lastUpdatedTime: number // If the question or description was changed
|
||||||
closeTime?: number // When no more trading is allowed
|
closeTime?: number // When no more trading is allowed
|
||||||
|
@ -32,12 +26,52 @@ export type Contract = {
|
||||||
isResolved: boolean
|
isResolved: boolean
|
||||||
resolutionTime?: number // When the contract creator resolved the market
|
resolutionTime?: number // When the contract creator resolved the market
|
||||||
resolution?: string
|
resolution?: string
|
||||||
resolutionProbability?: number // Used for BINARY markets resolved to MKT
|
|
||||||
resolutions?: { [outcome: string]: number } // Used for outcomeType FREE_RESPONSE resolved to MKT
|
|
||||||
closeEmailsSent?: number
|
closeEmailsSent?: number
|
||||||
|
|
||||||
volume24Hours: number
|
volume24Hours: number
|
||||||
volume7Days: number
|
volume7Days: number
|
||||||
|
|
||||||
|
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'
|
export type outcomeType = 'BINARY' | 'MULTI' | 'FREE_RESPONSE'
|
||||||
|
|
|
@ -1,4 +1,19 @@
|
||||||
export const PLATFORM_FEE = 0.01
|
export const PLATFORM_FEE = 0.02
|
||||||
export const CREATOR_FEE = 0.04
|
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 * as _ from 'lodash'
|
||||||
|
|
||||||
import { Bet, MAX_LOAN_PER_CONTRACT } from './bet'
|
import { Bet, MAX_LOAN_PER_CONTRACT } from './bet'
|
||||||
import {
|
import {
|
||||||
calculateShares,
|
calculateDpmShares,
|
||||||
getProbability,
|
getDpmProbability,
|
||||||
getOutcomeProbability,
|
getDpmOutcomeProbability,
|
||||||
} from './calculate'
|
} from './calculate-dpm'
|
||||||
import { Contract } from './contract'
|
import { calculateCpmmPurchase, getCpmmProbability } from './calculate-cpmm'
|
||||||
|
import {
|
||||||
|
Binary,
|
||||||
|
CPMM,
|
||||||
|
DPM,
|
||||||
|
FreeResponse,
|
||||||
|
FullContract,
|
||||||
|
Multi,
|
||||||
|
} from './contract'
|
||||||
import { User } from './user'
|
import { User } from './user'
|
||||||
|
import { noFees } from './fees'
|
||||||
|
|
||||||
export const getNewBinaryBetInfo = (
|
export const getNewBinaryCpmmBetInfo = (
|
||||||
user: User,
|
user: User,
|
||||||
outcome: 'YES' | 'NO',
|
outcome: 'YES' | 'NO',
|
||||||
amount: number,
|
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,
|
loanAmount: number,
|
||||||
contract: Contract,
|
|
||||||
newBetId: string
|
newBetId: string
|
||||||
) => {
|
) => {
|
||||||
const { YES: yesPool, NO: noPool } = contract.pool
|
const { YES: yesPool, NO: noPool } = contract.pool
|
||||||
|
@ -23,7 +73,7 @@ export const getNewBinaryBetInfo = (
|
||||||
? { YES: yesPool + amount, NO: noPool }
|
? { YES: yesPool + amount, NO: noPool }
|
||||||
: { YES: yesPool, NO: noPool + amount }
|
: { 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
|
const { YES: yesShares, NO: noShares } = contract.totalShares
|
||||||
|
|
||||||
|
@ -39,8 +89,8 @@ export const getNewBinaryBetInfo = (
|
||||||
? { YES: yesBets + amount, NO: noBets }
|
? { YES: yesBets + amount, NO: noBets }
|
||||||
: { YES: yesBets, NO: noBets + amount }
|
: { YES: yesBets, NO: noBets + amount }
|
||||||
|
|
||||||
const probBefore = getProbability(contract.totalShares)
|
const probBefore = getDpmProbability(contract.totalShares)
|
||||||
const probAfter = getProbability(newTotalShares)
|
const probAfter = getDpmProbability(newTotalShares)
|
||||||
|
|
||||||
const newBet: Bet = {
|
const newBet: Bet = {
|
||||||
id: newBetId,
|
id: newBetId,
|
||||||
|
@ -53,6 +103,7 @@ export const getNewBinaryBetInfo = (
|
||||||
probBefore,
|
probBefore,
|
||||||
probAfter,
|
probAfter,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
|
fees: noFees,
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBalance = user.balance - (amount - loanAmount)
|
const newBalance = user.balance - (amount - loanAmount)
|
||||||
|
@ -64,8 +115,8 @@ export const getNewMultiBetInfo = (
|
||||||
user: User,
|
user: User,
|
||||||
outcome: string,
|
outcome: string,
|
||||||
amount: number,
|
amount: number,
|
||||||
|
contract: FullContract<DPM, Multi | FreeResponse>,
|
||||||
loanAmount: number,
|
loanAmount: number,
|
||||||
contract: Contract,
|
|
||||||
newBetId: string
|
newBetId: string
|
||||||
) => {
|
) => {
|
||||||
const { pool, totalShares, totalBets } = contract
|
const { pool, totalShares, totalBets } = contract
|
||||||
|
@ -73,7 +124,7 @@ export const getNewMultiBetInfo = (
|
||||||
const prevOutcomePool = pool[outcome] ?? 0
|
const prevOutcomePool = pool[outcome] ?? 0
|
||||||
const newPool = { ...pool, [outcome]: prevOutcomePool + amount }
|
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 prevShares = totalShares[outcome] ?? 0
|
||||||
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
|
const newTotalShares = { ...totalShares, [outcome]: prevShares + shares }
|
||||||
|
@ -81,8 +132,8 @@ export const getNewMultiBetInfo = (
|
||||||
const prevTotalBets = totalBets[outcome] ?? 0
|
const prevTotalBets = totalBets[outcome] ?? 0
|
||||||
const newTotalBets = { ...totalBets, [outcome]: prevTotalBets + amount }
|
const newTotalBets = { ...totalBets, [outcome]: prevTotalBets + amount }
|
||||||
|
|
||||||
const probBefore = getOutcomeProbability(totalShares, outcome)
|
const probBefore = getDpmOutcomeProbability(totalShares, outcome)
|
||||||
const probAfter = getOutcomeProbability(newTotalShares, outcome)
|
const probAfter = getDpmOutcomeProbability(newTotalShares, outcome)
|
||||||
|
|
||||||
const newBet: Bet = {
|
const newBet: Bet = {
|
||||||
id: newBetId,
|
id: newBetId,
|
||||||
|
@ -95,6 +146,7 @@ export const getNewMultiBetInfo = (
|
||||||
probBefore,
|
probBefore,
|
||||||
probAfter,
|
probAfter,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
|
fees: noFees,
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBalance = user.balance - (amount - loanAmount)
|
const newBalance = user.balance - (amount - loanAmount)
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
import { calcStartPool } from './antes'
|
import { PHANTOM_ANTE } from './antes'
|
||||||
import { Contract, outcomeType } from './contract'
|
import {
|
||||||
|
Binary,
|
||||||
|
Contract,
|
||||||
|
CPMM,
|
||||||
|
DPM,
|
||||||
|
FreeResponse,
|
||||||
|
outcomeType,
|
||||||
|
} from './contract'
|
||||||
import { User } from './user'
|
import { User } from './user'
|
||||||
import { parseTags } from './util/parse'
|
import { parseTags } from './util/parse'
|
||||||
import { removeUndefinedProps } from './util/object'
|
import { removeUndefinedProps } from './util/object'
|
||||||
|
import { calcDpmInitialPool } from './calculate-dpm'
|
||||||
|
|
||||||
export function getNewContract(
|
export function getNewContract(
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -23,14 +31,12 @@ export function getNewContract(
|
||||||
|
|
||||||
const propsByOutcomeType =
|
const propsByOutcomeType =
|
||||||
outcomeType === 'BINARY'
|
outcomeType === 'BINARY'
|
||||||
? getBinaryProps(initialProb, ante)
|
? getBinaryCpmmProps(initialProb, ante) // getBinaryDpmProps(initialProb, ante)
|
||||||
: getFreeAnswerProps(ante)
|
: getFreeAnswerProps(ante)
|
||||||
|
|
||||||
const contract: Contract = removeUndefinedProps({
|
const contract: Contract = removeUndefinedProps({
|
||||||
id,
|
id,
|
||||||
slug,
|
slug,
|
||||||
mechanism: 'dpm-2',
|
|
||||||
outcomeType,
|
|
||||||
...propsByOutcomeType,
|
...propsByOutcomeType,
|
||||||
|
|
||||||
creatorId: creator.id,
|
creatorId: creator.id,
|
||||||
|
@ -50,30 +56,61 @@ export function getNewContract(
|
||||||
|
|
||||||
volume24Hours: 0,
|
volume24Hours: 0,
|
||||||
volume7Days: 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 } =
|
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 },
|
phantomShares: { YES: phantomYes, NO: phantomNo },
|
||||||
pool: { YES: poolYes, NO: poolNo },
|
pool: { YES: poolYes, NO: poolNo },
|
||||||
totalShares: { YES: sharesYes, NO: sharesNo },
|
totalShares: { YES: sharesYes, NO: sharesNo },
|
||||||
totalBets: { YES: poolYes, NO: poolNo },
|
totalBets: { YES: poolYes, NO: poolNo },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return system
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBinaryCpmmProps = (initialProb: number, ante: number) => {
|
||||||
|
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) => {
|
const getFreeAnswerProps = (ante: number) => {
|
||||||
return {
|
const system: DPM & FreeResponse = {
|
||||||
|
mechanism: 'dpm-2',
|
||||||
|
outcomeType: 'FREE_RESPONSE',
|
||||||
pool: { '0': ante },
|
pool: { '0': ante },
|
||||||
totalShares: { '0': ante },
|
totalShares: { '0': ante },
|
||||||
totalBets: { '0': ante },
|
totalBets: { '0': ante },
|
||||||
answers: [],
|
answers: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return system
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMultiProps = (
|
const getMultiProps = (
|
||||||
|
|
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 * as _ from 'lodash'
|
||||||
|
|
||||||
import { Bet } from './bet'
|
import { Bet } from './bet'
|
||||||
import { deductFees, getProbability } from './calculate'
|
import {
|
||||||
import { Contract } from './contract'
|
Binary,
|
||||||
import { CREATOR_FEE, FEES } from './fees'
|
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[]) => {
|
export type Payout = {
|
||||||
const { pool } = contract
|
userId: string
|
||||||
const poolTotal = _.sum(Object.values(pool))
|
payout: number
|
||||||
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 const getStandardPayouts = (
|
export const getLoanPayouts = (bets: Bet[]): Payout[] => {
|
||||||
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[]) => {
|
|
||||||
const betsWithLoans = bets.filter((bet) => bet.loanAmount)
|
const betsWithLoans = bets.filter((bet) => bet.loanAmount)
|
||||||
const betsByUser = _.groupBy(betsWithLoans, (bet) => bet.userId)
|
const betsByUser = _.groupBy(betsWithLoans, (bet) => bet.userId)
|
||||||
const loansByUser = _.mapValues(betsByUser, (bets) =>
|
const loansByUser = _.mapValues(betsByUser, (bets) =>
|
||||||
|
@ -169,3 +37,99 @@ export const getLoanPayouts = (bets: Bet[]) => {
|
||||||
)
|
)
|
||||||
return _.toPairs(loansByUser).map(([userId, payout]) => ({ userId, payout }))
|
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 * as _ from 'lodash'
|
||||||
|
|
||||||
import { Bet } from './bet'
|
import { Bet } from './bet'
|
||||||
import { Contract } from './contract'
|
import { Binary, Contract, FullContract } from './contract'
|
||||||
import { getPayouts } from './payouts'
|
import { getPayouts } from './payouts'
|
||||||
|
|
||||||
export function scoreCreators(contracts: Contract[], bets: Bet[][]) {
|
export function scoreCreators(contracts: Contract[], bets: Bet[][]) {
|
||||||
|
@ -23,17 +24,22 @@ export function scoreTraders(contracts: Contract[], bets: Bet[][]) {
|
||||||
return userScores
|
return userScores
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scoreUsersByContract(contract: Contract, bets: Bet[]) {
|
export function scoreUsersByContract(
|
||||||
|
contract: FullContract<any, Binary>,
|
||||||
|
bets: Bet[]
|
||||||
|
) {
|
||||||
const { resolution, resolutionProbability } = contract
|
const { resolution, resolutionProbability } = contract
|
||||||
|
|
||||||
const [closedBets, openBets] = _.partition(
|
const [closedBets, openBets] = _.partition(
|
||||||
bets,
|
bets,
|
||||||
(bet) => bet.isSold || bet.sale
|
(bet) => bet.isSold || bet.sale
|
||||||
)
|
)
|
||||||
const resolvePayouts = getPayouts(
|
const [resolvePayouts] = getPayouts(
|
||||||
resolution ?? 'MKT',
|
resolution,
|
||||||
|
{},
|
||||||
contract,
|
contract,
|
||||||
openBets,
|
openBets,
|
||||||
|
[],
|
||||||
resolutionProbability
|
resolutionProbability
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import { Bet } from './bet'
|
import { Bet } from './bet'
|
||||||
import { calculateShareValue, deductFees, getProbability } from './calculate'
|
import {
|
||||||
import { Contract } from './contract'
|
getDpmProbability,
|
||||||
import { CREATOR_FEE } from './fees'
|
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'
|
import { User } from './user'
|
||||||
|
|
||||||
export const getSellBetInfo = (
|
export const getSellBetInfo = (
|
||||||
user: User,
|
user: User,
|
||||||
bet: Bet,
|
bet: Bet,
|
||||||
contract: Contract,
|
contract: FullContract<DPM, any>,
|
||||||
newBetId: string
|
newBetId: string
|
||||||
) => {
|
) => {
|
||||||
const { pool, totalShares, totalBets } = contract
|
const { pool, totalShares, totalBets } = contract
|
||||||
const { id: betId, amount, shares, outcome, loanAmount } = bet
|
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 }
|
const newPool = { ...pool, [outcome]: pool[outcome] - adjShareValue }
|
||||||
|
|
||||||
|
@ -24,12 +29,20 @@ export const getSellBetInfo = (
|
||||||
|
|
||||||
const newTotalBets = { ...totalBets, [outcome]: totalBets[outcome] - amount }
|
const newTotalBets = { ...totalBets, [outcome]: totalBets[outcome] - amount }
|
||||||
|
|
||||||
const probBefore = getProbability(totalShares)
|
const probBefore = getDpmProbability(totalShares)
|
||||||
const probAfter = getProbability(newTotalShares)
|
const probAfter = getDpmProbability(newTotalShares)
|
||||||
|
|
||||||
const profit = adjShareValue - amount
|
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(
|
console.log(
|
||||||
'SELL M$',
|
'SELL M$',
|
||||||
|
@ -55,6 +68,7 @@ export const getSellBetInfo = (
|
||||||
amount: saleAmount,
|
amount: saleAmount,
|
||||||
betId,
|
betId,
|
||||||
},
|
},
|
||||||
|
fees,
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBalance = user.balance + saleAmount - (loanAmount ?? 0)
|
const newBalance = user.balance + saleAmount - (loanAmount ?? 0)
|
||||||
|
@ -65,6 +79,57 @@ export const getSellBetInfo = (
|
||||||
newTotalShares,
|
newTotalShares,
|
||||||
newTotalBets,
|
newTotalBets,
|
||||||
newBalance,
|
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) {
|
export function formatMoney(amount: number) {
|
||||||
|
const newAmount = Math.round(amount) === 0 ? 0 : amount // handle -0 case
|
||||||
return (
|
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 => {
|
export const removeUndefinedProps = <T>(obj: T): T => {
|
||||||
let newObj: any = {}
|
let newObj: any = {}
|
||||||
|
|
||||||
|
@ -7,3 +9,17 @@ export const removeUndefinedProps = <T>(obj: T): T => {
|
||||||
|
|
||||||
return newObj
|
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 functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
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 { User } from '../../common/user'
|
||||||
import { getLoanAmount, getNewMultiBetInfo } from '../../common/new-bet'
|
import { getLoanAmount, getNewMultiBetInfo } from '../../common/new-bet'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from '../../common/answer'
|
||||||
|
@ -105,8 +110,8 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
user,
|
user,
|
||||||
answerId,
|
answerId,
|
||||||
amount,
|
amount,
|
||||||
|
contract as FullContract<DPM, FreeResponse>,
|
||||||
loanAmount,
|
loanAmount,
|
||||||
contract,
|
|
||||||
newBetDoc.id
|
newBetDoc.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,12 @@ import * as _ from 'lodash'
|
||||||
|
|
||||||
import { chargeUser, getUser } from './utils'
|
import { chargeUser, getUser } from './utils'
|
||||||
import {
|
import {
|
||||||
|
Binary,
|
||||||
Contract,
|
Contract,
|
||||||
|
CPMM,
|
||||||
|
DPM,
|
||||||
|
FreeResponse,
|
||||||
|
FullContract,
|
||||||
MAX_DESCRIPTION_LENGTH,
|
MAX_DESCRIPTION_LENGTH,
|
||||||
MAX_QUESTION_LENGTH,
|
MAX_QUESTION_LENGTH,
|
||||||
MAX_TAG_LENGTH,
|
MAX_TAG_LENGTH,
|
||||||
|
@ -15,6 +20,7 @@ import { randomString } from '../../common/util/random'
|
||||||
import { getNewContract } from '../../common/new-contract'
|
import { getNewContract } from '../../common/new-contract'
|
||||||
import {
|
import {
|
||||||
getAnteBets,
|
getAnteBets,
|
||||||
|
getCpmmInitialLiquidity,
|
||||||
getFreeAnswerAnte,
|
getFreeAnswerAnte,
|
||||||
MINIMUM_ANTE,
|
MINIMUM_ANTE,
|
||||||
} from '../../common/antes'
|
} from '../../common/antes'
|
||||||
|
@ -105,7 +111,7 @@ export const createContract = functions
|
||||||
await contractRef.create(contract)
|
await contractRef.create(contract)
|
||||||
|
|
||||||
if (ante) {
|
if (ante) {
|
||||||
if (outcomeType === 'BINARY') {
|
if (outcomeType === 'BINARY' && contract.mechanism === 'dpm-2') {
|
||||||
const yesBetDoc = firestore
|
const yesBetDoc = firestore
|
||||||
.collection(`contracts/${contract.id}/bets`)
|
.collection(`contracts/${contract.id}/bets`)
|
||||||
.doc()
|
.doc()
|
||||||
|
@ -116,23 +122,43 @@ export const createContract = functions
|
||||||
|
|
||||||
const { yesBet, noBet } = getAnteBets(
|
const { yesBet, noBet } = getAnteBets(
|
||||||
creator,
|
creator,
|
||||||
contract,
|
contract as FullContract<DPM, Binary>,
|
||||||
yesBetDoc.id,
|
yesBetDoc.id,
|
||||||
noBetDoc.id
|
noBetDoc.id
|
||||||
)
|
)
|
||||||
|
|
||||||
await yesBetDoc.set(yesBet)
|
await yesBetDoc.set(yesBet)
|
||||||
await noBetDoc.set(noBet)
|
await noBetDoc.set(noBet)
|
||||||
|
} else if (outcomeType === 'BINARY') {
|
||||||
|
const 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') {
|
} else if (outcomeType === 'FREE_RESPONSE') {
|
||||||
const noneAnswerDoc = firestore
|
const noneAnswerDoc = firestore
|
||||||
.collection(`contracts/${contract.id}/answers`)
|
.collection(`contracts/${contract.id}/answers`)
|
||||||
.doc('0')
|
.doc('0')
|
||||||
|
|
||||||
const noneAnswer = getNoneAnswer(contract.id, creator)
|
const noneAnswer = getNoneAnswer(contract.id, creator)
|
||||||
await noneAnswerDoc.set(noneAnswer)
|
await noneAnswerDoc.set(noneAnswer)
|
||||||
|
|
||||||
const anteBetDoc = firestore
|
const anteBetDoc = firestore
|
||||||
.collection(`contracts/${contract.id}/bets`)
|
.collection(`contracts/${contract.id}/bets`)
|
||||||
.doc()
|
.doc()
|
||||||
const anteBet = getFreeAnswerAnte(creator, contract, anteBetDoc.id)
|
|
||||||
|
const anteBet = getFreeAnswerAnte(
|
||||||
|
creator,
|
||||||
|
contract as FullContract<DPM, FreeResponse>,
|
||||||
|
anteBetDoc.id
|
||||||
|
)
|
||||||
await anteBetDoc.set(anteBet)
|
await anteBetDoc.set(anteBet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import _ = require('lodash')
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
import { DOMAIN, PROJECT_ID } from '../../common/envs/constants'
|
import { DOMAIN, PROJECT_ID } from '../../common/envs/constants'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from '../../common/answer'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
|
@ -11,34 +12,6 @@ import { formatMoney, formatPercent } from '../../common/util/format'
|
||||||
import { sendTemplateEmail, sendTextEmail } from './send-email'
|
import { sendTemplateEmail, sendTextEmail } from './send-email'
|
||||||
import { getPrivateUser, getUser } from './utils'
|
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 (
|
export const sendMarketResolutionEmail = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
investment: number,
|
investment: number,
|
||||||
|
@ -60,9 +33,12 @@ export const sendMarketResolutionEmail = async (
|
||||||
const user = await getUser(userId)
|
const user = await getUser(userId)
|
||||||
if (!user) return
|
if (!user) return
|
||||||
|
|
||||||
const prob = resolutionProbability ?? getProbability(contract.totalShares)
|
const outcome = toDisplayResolution(
|
||||||
|
contract,
|
||||||
const outcome = toDisplayResolution(resolution, prob, resolutions)
|
resolution,
|
||||||
|
resolutionProbability,
|
||||||
|
resolutions
|
||||||
|
)
|
||||||
|
|
||||||
const subject = `Resolved ${outcome}: ${contract.question}`
|
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 (
|
export const sendWelcomeEmail = async (
|
||||||
user: User,
|
user: User,
|
||||||
privateUser: PrivateUser
|
privateUser: PrivateUser
|
||||||
|
@ -197,7 +208,10 @@ export const sendNewCommentEmail = async (
|
||||||
{ from }
|
{ from }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
betDescription = `${betDescription} of ${toDisplayResolution(outcome)}`
|
betDescription = `${betDescription} of ${toDisplayResolution(
|
||||||
|
contract,
|
||||||
|
outcome
|
||||||
|
)}`
|
||||||
|
|
||||||
await sendTemplateEmail(
|
await sendTemplateEmail(
|
||||||
privateUser.email,
|
privateUser.email,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import * as admin from 'firebase-admin'
|
||||||
admin.initializeApp()
|
admin.initializeApp()
|
||||||
|
|
||||||
// export * from './keep-awake'
|
// export * from './keep-awake'
|
||||||
export * from './markets'
|
|
||||||
export * from './place-bet'
|
export * from './place-bet'
|
||||||
export * from './resolve-market'
|
export * from './resolve-market'
|
||||||
export * from './stripe'
|
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 bet = betSnapshot.data() as Bet
|
||||||
|
|
||||||
const answer =
|
const answer =
|
||||||
contract.answers &&
|
contract.outcomeType === 'FREE_RESPONSE' && contract.answers
|
||||||
contract.answers.find((answer) => answer.id === bet.outcome)
|
? contract.answers.find((answer) => answer.id === bet.outcome)
|
||||||
|
: undefined
|
||||||
|
|
||||||
const comments = await getValues<Comment>(
|
const comments = await getValues<Comment>(
|
||||||
firestore.collection('contracts').doc(contractId).collection('comments')
|
firestore.collection('contracts').doc(contractId).collection('comments')
|
||||||
|
|
|
@ -4,11 +4,15 @@ import * as admin from 'firebase-admin'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import {
|
import {
|
||||||
getLoanAmount,
|
getNewBinaryCpmmBetInfo,
|
||||||
getNewBinaryBetInfo,
|
getNewBinaryDpmBetInfo,
|
||||||
getNewMultiBetInfo,
|
getNewMultiBetInfo,
|
||||||
|
getLoanAmount,
|
||||||
} from '../../common/new-bet'
|
} from '../../common/new-bet'
|
||||||
|
import { addObjects, removeUndefinedProps } from '../../common/util/object'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
|
import { redeemShares } from './redeem-shares'
|
||||||
|
import { Fees } from '../../common/fees'
|
||||||
|
|
||||||
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
@ -31,7 +35,8 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
return { status: 'error', message: 'Invalid outcome' }
|
return { status: 'error', message: 'Invalid outcome' }
|
||||||
|
|
||||||
// run as transaction to prevent race conditions
|
// 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 userDoc = firestore.doc(`users/${userId}`)
|
||||||
const userSnap = await transaction.get(userDoc)
|
const userSnap = await transaction.get(userDoc)
|
||||||
if (!userSnap.exists)
|
if (!userSnap.exists)
|
||||||
|
@ -44,7 +49,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
return { status: 'error', message: 'Invalid contract' }
|
return { status: 'error', message: 'Invalid contract' }
|
||||||
const contract = contractSnap.data() as Contract
|
const contract = contractSnap.data() as Contract
|
||||||
|
|
||||||
const { closeTime, outcomeType } = contract
|
const { closeTime, outcomeType, mechanism, collectedFees } = contract
|
||||||
if (closeTime && Date.now() > closeTime)
|
if (closeTime && Date.now() > closeTime)
|
||||||
return { status: 'error', message: 'Trading is closed' }
|
return { status: 'error', message: 'Trading is closed' }
|
||||||
|
|
||||||
|
@ -69,35 +74,72 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
.collection(`contracts/${contractId}/bets`)
|
.collection(`contracts/${contractId}/bets`)
|
||||||
.doc()
|
.doc()
|
||||||
|
|
||||||
const { newBet, newPool, newTotalShares, newTotalBets, newBalance } =
|
const {
|
||||||
|
newBet,
|
||||||
|
newPool,
|
||||||
|
newTotalShares,
|
||||||
|
newTotalBets,
|
||||||
|
newBalance,
|
||||||
|
newTotalLiquidity,
|
||||||
|
fees,
|
||||||
|
newP,
|
||||||
|
} =
|
||||||
outcomeType === 'BINARY'
|
outcomeType === 'BINARY'
|
||||||
? getNewBinaryBetInfo(
|
? mechanism === 'dpm-2'
|
||||||
|
? getNewBinaryDpmBetInfo(
|
||||||
user,
|
user,
|
||||||
outcome as 'YES' | 'NO',
|
outcome as 'YES' | 'NO',
|
||||||
amount,
|
amount,
|
||||||
loanAmount,
|
|
||||||
contract,
|
contract,
|
||||||
|
loanAmount,
|
||||||
newBetDoc.id
|
newBetDoc.id
|
||||||
)
|
)
|
||||||
|
: (getNewBinaryCpmmBetInfo(
|
||||||
|
user,
|
||||||
|
outcome as 'YES' | 'NO',
|
||||||
|
amount,
|
||||||
|
contract,
|
||||||
|
loanAmount,
|
||||||
|
newBetDoc.id
|
||||||
|
) as any)
|
||||||
: getNewMultiBetInfo(
|
: getNewMultiBetInfo(
|
||||||
user,
|
user,
|
||||||
outcome,
|
outcome,
|
||||||
amount,
|
amount,
|
||||||
|
contract as any,
|
||||||
loanAmount,
|
loanAmount,
|
||||||
contract,
|
|
||||||
newBetDoc.id
|
newBetDoc.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (newP !== undefined && !isFinite(newP)) {
|
||||||
|
return {
|
||||||
|
status: 'error',
|
||||||
|
message: 'Trade rejected due to overflow error.',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
transaction.create(newBetDoc, newBet)
|
transaction.create(newBetDoc, newBet)
|
||||||
transaction.update(contractDoc, {
|
|
||||||
|
transaction.update(
|
||||||
|
contractDoc,
|
||||||
|
removeUndefinedProps({
|
||||||
pool: newPool,
|
pool: newPool,
|
||||||
|
p: newP,
|
||||||
totalShares: newTotalShares,
|
totalShares: newTotalShares,
|
||||||
totalBets: newTotalBets,
|
totalBets: newTotalBets,
|
||||||
|
totalLiquidity: newTotalLiquidity,
|
||||||
|
collectedFees: addObjects<Fees>(fees ?? {}, collectedFees ?? {}),
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
transaction.update(userDoc, { balance: newBalance })
|
transaction.update(userDoc, { balance: newBalance })
|
||||||
|
|
||||||
return { status: 'success', betId: newBetDoc.id }
|
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 { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { getUser, payUser } from './utils'
|
import { getUser, isProd, payUser } from './utils'
|
||||||
import { sendMarketResolutionEmail } from './emails'
|
import { sendMarketResolutionEmail } from './emails'
|
||||||
import {
|
import { getLoanPayouts, getPayouts } from '../../common/payouts'
|
||||||
getLoanPayouts,
|
|
||||||
getPayouts,
|
|
||||||
getPayoutsMultiOutcome,
|
|
||||||
} from '../../common/payouts'
|
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
import { removeUndefinedProps } from '../../common/util/object'
|
||||||
|
import { LiquidityProvision } from '../../common/liquidity-provision'
|
||||||
|
|
||||||
export const resolveMarket = functions
|
export const resolveMarket = functions
|
||||||
.runWith({ minInstances: 1 })
|
.runWith({ minInstances: 1 })
|
||||||
|
@ -78,6 +75,29 @@ export const resolveMarket = functions
|
||||||
? Math.min(closeTime, resolutionTime)
|
? Math.min(closeTime, resolutionTime)
|
||||||
: closeTime
|
: 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(
|
await contractDoc.update(
|
||||||
removeUndefinedProps({
|
removeUndefinedProps({
|
||||||
isResolved: true,
|
isResolved: true,
|
||||||
|
@ -86,26 +106,16 @@ export const resolveMarket = functions
|
||||||
closeTime: newCloseTime,
|
closeTime: newCloseTime,
|
||||||
resolutionProbability,
|
resolutionProbability,
|
||||||
resolutions,
|
resolutions,
|
||||||
|
collectedFees,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('contract ', contractId, 'resolved to:', outcome)
|
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 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)
|
const loanPayouts = getLoanPayouts(openBets)
|
||||||
|
|
||||||
console.log('payouts:', payouts)
|
if (!isProd) console.log('payouts:', payouts)
|
||||||
|
|
||||||
const groups = _.groupBy(
|
const groups = _.groupBy(
|
||||||
[...payouts, ...loanPayouts],
|
[...payouts, ...loanPayouts],
|
||||||
|
|
|
@ -5,13 +5,16 @@ import { initAdmin } from './script-init'
|
||||||
initAdmin('stephen')
|
initAdmin('stephen')
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { getProbability } from '../../../common/calculate'
|
import { getDpmProbability } from '../../../common/calculate-dpm'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function migrateContract(contractRef: DocRef, contract: Contract) {
|
async function migrateContract(
|
||||||
|
contractRef: DocRef,
|
||||||
|
contract: FullContract<DPM, Binary>
|
||||||
|
) {
|
||||||
const bets = await contractRef
|
const bets = await contractRef
|
||||||
.collection('bets')
|
.collection('bets')
|
||||||
.get()
|
.get()
|
||||||
|
@ -19,7 +22,7 @@ async function migrateContract(contractRef: DocRef, contract: Contract) {
|
||||||
|
|
||||||
const lastBet = _.sortBy(bets, (bet) => -bet.createdTime)[0]
|
const lastBet = _.sortBy(bets, (bet) => -bet.createdTime)[0]
|
||||||
if (lastBet) {
|
if (lastBet) {
|
||||||
const probAfter = getProbability(contract.totalShares)
|
const probAfter = getDpmProbability(contract.totalShares)
|
||||||
|
|
||||||
await firestore
|
await firestore
|
||||||
.doc(`contracts/${contract.id}/bets/${lastBet.id}`)
|
.doc(`contracts/${contract.id}/bets/${lastBet.id}`)
|
||||||
|
@ -31,7 +34,9 @@ async function migrateContract(contractRef: DocRef, contract: Contract) {
|
||||||
|
|
||||||
async function migrateContracts() {
|
async function migrateContracts() {
|
||||||
const snapshot = await firestore.collection('contracts').get()
|
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)
|
console.log('Loaded contracts', contracts.length)
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,12 @@ import * as _ from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
initAdmin('stephenDev')
|
initAdmin('stephenDev')
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
|
||||||
import { Bet } from '../../../common/bet'
|
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 { getSellBetInfo } from '../../../common/sell-bet'
|
||||||
import { User } from '../../../common/user'
|
import { User } from '../../../common/user'
|
||||||
|
|
||||||
|
@ -29,7 +32,7 @@ async function recalculateContract(
|
||||||
|
|
||||||
await firestore.runTransaction(async (transaction) => {
|
await firestore.runTransaction(async (transaction) => {
|
||||||
const contractDoc = await transaction.get(contractRef)
|
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 betDocs = await transaction.get(contractRef.collection('bets'))
|
||||||
const bets = _.sortBy(
|
const bets = _.sortBy(
|
||||||
|
@ -126,7 +129,7 @@ async function recalculateContract(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const shares = calculateShares(totalShares, bet.amount, bet.outcome)
|
const shares = calculateDpmShares(totalShares, bet.amount, bet.outcome)
|
||||||
const probBefore = p
|
const probBefore = p
|
||||||
const ind = bet.outcome === 'YES' ? 1 : 0
|
const ind = bet.outcome === 'YES' ? 1 : 0
|
||||||
|
|
||||||
|
@ -145,7 +148,7 @@ async function recalculateContract(
|
||||||
NO: totalBets.NO + (1 - ind) * bet.amount,
|
NO: totalBets.NO + (1 - ind) * bet.amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
p = getProbability(totalShares)
|
p = getDpmProbability(totalShares)
|
||||||
|
|
||||||
const probAfter = p
|
const probAfter = p
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,8 @@ initAdmin('james')
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
import {
|
import { getLoanPayouts, getPayouts } from '../../../common/payouts'
|
||||||
getLoanPayouts,
|
|
||||||
getPayouts,
|
|
||||||
getPayoutsMultiOutcome,
|
|
||||||
} from '../../../common/payouts'
|
|
||||||
import { filterDefined } from '../../../common/util/array'
|
import { filterDefined } from '../../../common/util/array'
|
||||||
import { payUser } from '../utils'
|
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
|
@ -28,12 +23,15 @@ async function checkIfPayOutAgain(contractRef: DocRef, contract: Contract) {
|
||||||
const loanedBets = openBets.filter((bet) => bet.loanAmount)
|
const loanedBets = openBets.filter((bet) => bet.loanAmount)
|
||||||
|
|
||||||
if (loanedBets.length && contract.resolution) {
|
if (loanedBets.length && contract.resolution) {
|
||||||
const { resolution, outcomeType, resolutions, resolutionProbability } =
|
const { resolution, resolutions, resolutionProbability } = contract as any
|
||||||
contract
|
const [payouts] = getPayouts(
|
||||||
const payouts =
|
resolution,
|
||||||
outcomeType === 'FREE_RESPONSE' && resolutions
|
resolutions,
|
||||||
? getPayoutsMultiOutcome(resolutions, contract, openBets)
|
contract,
|
||||||
: getPayouts(resolution, contract, openBets, resolutionProbability)
|
openBets,
|
||||||
|
[],
|
||||||
|
resolutionProbability
|
||||||
|
)
|
||||||
|
|
||||||
const loanPayouts = getLoanPayouts(openBets)
|
const loanPayouts = getLoanPayouts(openBets)
|
||||||
const groups = _.groupBy(
|
const groups = _.groupBy(
|
||||||
|
|
|
@ -4,7 +4,9 @@ import * as functions from 'firebase-functions'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { getSellBetInfo } from '../../common/sell-bet'
|
import { getCpmmSellBetInfo, getSellBetInfo } from '../../common/sell-bet'
|
||||||
|
import { addObjects, removeUndefinedProps } from '../../common/util/object'
|
||||||
|
import { Fees } from '../../common/fees'
|
||||||
|
|
||||||
export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
@ -33,7 +35,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
return { status: 'error', message: 'Invalid contract' }
|
return { status: 'error', message: 'Invalid contract' }
|
||||||
const contract = contractSnap.data() as Contract
|
const contract = contractSnap.data() as Contract
|
||||||
|
|
||||||
const { closeTime } = contract
|
const { closeTime, mechanism, collectedFees } = contract
|
||||||
if (closeTime && Date.now() > closeTime)
|
if (closeTime && Date.now() > closeTime)
|
||||||
return { status: 'error', message: 'Trading is closed' }
|
return { status: 'error', message: 'Trading is closed' }
|
||||||
|
|
||||||
|
@ -54,31 +56,30 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
newTotalShares,
|
newTotalShares,
|
||||||
newTotalBets,
|
newTotalBets,
|
||||||
newBalance,
|
newBalance,
|
||||||
creatorFee,
|
fees,
|
||||||
} = getSellBetInfo(user, bet, contract, newBetDoc.id)
|
} =
|
||||||
|
mechanism === 'dpm-2'
|
||||||
if (contract.creatorId === userId) {
|
? getSellBetInfo(user, bet, contract, newBetDoc.id)
|
||||||
transaction.update(userDoc, { balance: newBalance + creatorFee })
|
: (getCpmmSellBetInfo(
|
||||||
} else {
|
user,
|
||||||
const creatorDoc = firestore.doc(`users/${contract.creatorId}`)
|
bet,
|
||||||
const creatorSnap = await transaction.get(creatorDoc)
|
contract as any,
|
||||||
|
newBetDoc.id
|
||||||
if (creatorSnap.exists) {
|
) as any)
|
||||||
const creator = creatorSnap.data() as User
|
|
||||||
const creatorNewBalance = creator.balance + creatorFee
|
|
||||||
transaction.update(creatorDoc, { balance: creatorNewBalance })
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.update(userDoc, { balance: newBalance })
|
transaction.update(userDoc, { balance: newBalance })
|
||||||
}
|
|
||||||
|
|
||||||
transaction.update(betDoc, { isSold: true })
|
transaction.update(betDoc, { isSold: true })
|
||||||
transaction.create(newBetDoc, newBet)
|
transaction.create(newBetDoc, newBet)
|
||||||
transaction.update(contractDoc, {
|
transaction.update(
|
||||||
|
contractDoc,
|
||||||
|
removeUndefinedProps({
|
||||||
pool: newPool,
|
pool: newPool,
|
||||||
totalShares: newTotalShares,
|
totalShares: newTotalShares,
|
||||||
totalBets: newTotalBets,
|
totalBets: newTotalBets,
|
||||||
|
collectedFees: addObjects<Fees>(fees ?? {}, collectedFees ?? {}),
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
return { status: 'success' }
|
return { status: 'success' }
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { getValues } from './utils'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { calculatePayout } from '../../common/calculate'
|
import { calculateDpmPayout } from '../../common/calculate-dpm'
|
||||||
import { batchedWaitAll } from '../../common/util/promise'
|
import { batchedWaitAll } from '../../common/util/promise'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
@ -53,7 +53,7 @@ const computeInvestmentValue = async (
|
||||||
if (!contract || contract.isResolved) return 0
|
if (!contract || contract.isResolved) return 0
|
||||||
if (bet.sale || bet.isSold) 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)
|
return payout - (bet.loanAmount ?? 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,11 @@ const updateUserBalance = (
|
||||||
|
|
||||||
const newUserBalance = user.balance + delta
|
const newUserBalance = user.balance + delta
|
||||||
|
|
||||||
|
// if (newUserBalance < 0)
|
||||||
|
// throw new Error(
|
||||||
|
// `User (${userId}) balance cannot be negative: ${newUserBalance}`
|
||||||
|
// )
|
||||||
|
|
||||||
if (isDeposit) {
|
if (isDeposit) {
|
||||||
const newTotalDeposits = (user.totalDeposits || 0) + delta
|
const newTotalDeposits = (user.totalDeposits || 0) + delta
|
||||||
transaction.update(userDoc, { totalDeposits: newTotalDeposits })
|
transaction.update(userDoc, { totalDeposits: newTotalDeposits })
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||||
import { XIcon } from '@heroicons/react/solid'
|
import { XIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from '../../../common/answer'
|
||||||
import { Contract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
||||||
import { AmountInput } from '../amount-input'
|
import { AmountInput } from '../amount-input'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { placeBet } from '../../lib/firebase/api-call'
|
import { placeBet } from '../../lib/firebase/api-call'
|
||||||
|
@ -18,17 +18,17 @@ import {
|
||||||
import { InfoTooltip } from '../info-tooltip'
|
import { InfoTooltip } from '../info-tooltip'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from '../../hooks/use-user'
|
||||||
import {
|
import {
|
||||||
getProbabilityAfterBet,
|
getDpmOutcomeProbability,
|
||||||
getOutcomeProbability,
|
calculateDpmShares,
|
||||||
calculateShares,
|
calculateDpmPayoutAfterCorrectBet,
|
||||||
calculatePayoutAfterCorrectBet,
|
getDpmOutcomeProbabilityAfterBet,
|
||||||
} from '../../../common/calculate'
|
} from '../../../common/calculate-dpm'
|
||||||
import { firebaseLogin } from '../../lib/firebase/users'
|
import { firebaseLogin } from '../../lib/firebase/users'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
|
|
||||||
export function AnswerBetPanel(props: {
|
export function AnswerBetPanel(props: {
|
||||||
answer: Answer
|
answer: Answer
|
||||||
contract: Contract
|
contract: FullContract<DPM, FreeResponse>
|
||||||
closePanel: () => void
|
closePanel: () => void
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
|
@ -71,18 +71,22 @@ export function AnswerBetPanel(props: {
|
||||||
|
|
||||||
const betDisabled = isSubmitting || !betAmount || error
|
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,
|
contract.totalShares,
|
||||||
answerId,
|
answerId,
|
||||||
betAmount ?? 0
|
betAmount ?? 0
|
||||||
)
|
)
|
||||||
|
|
||||||
const shares = calculateShares(contract.totalShares, betAmount ?? 0, answerId)
|
const shares = calculateDpmShares(
|
||||||
|
contract.totalShares,
|
||||||
|
betAmount ?? 0,
|
||||||
|
answerId
|
||||||
|
)
|
||||||
|
|
||||||
const currentPayout = betAmount
|
const currentPayout = betAmount
|
||||||
? calculatePayoutAfterCorrectBet(contract, {
|
? calculateDpmPayoutAfterCorrectBet(contract, {
|
||||||
outcome: answerId,
|
outcome: answerId,
|
||||||
amount: betAmount,
|
amount: betAmount,
|
||||||
shares,
|
shares,
|
||||||
|
|
|
@ -3,14 +3,14 @@ import _ from 'lodash'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from '../../../common/answer'
|
||||||
import { Contract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { SiteLink } from '../site-link'
|
import { SiteLink } from '../site-link'
|
||||||
import { BuyButton } from '../yes-no-selector'
|
import { BuyButton } from '../yes-no-selector'
|
||||||
import { formatPercent } from '../../../common/util/format'
|
import { formatPercent } from '../../../common/util/format'
|
||||||
import { getOutcomeProbability } from '../../../common/calculate'
|
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
||||||
import { tradingAllowed } from '../../lib/firebase/contracts'
|
import { tradingAllowed } from '../../lib/firebase/contracts'
|
||||||
import { AnswerBetPanel } from './answer-bet-panel'
|
import { AnswerBetPanel } from './answer-bet-panel'
|
||||||
import { Linkify } from '../linkify'
|
import { Linkify } from '../linkify'
|
||||||
|
@ -19,7 +19,7 @@ import { ContractActivity } from '../feed/contract-activity'
|
||||||
|
|
||||||
export function AnswerItem(props: {
|
export function AnswerItem(props: {
|
||||||
answer: Answer
|
answer: Answer
|
||||||
contract: Contract
|
contract: FullContract<DPM, FreeResponse>
|
||||||
user: User | null | undefined
|
user: User | null | undefined
|
||||||
showChoice: 'radio' | 'checkbox' | undefined
|
showChoice: 'radio' | 'checkbox' | undefined
|
||||||
chosenProb: number | undefined
|
chosenProb: number | undefined
|
||||||
|
@ -41,7 +41,7 @@ export function AnswerItem(props: {
|
||||||
const { username, avatarUrl, name, number, text } = answer
|
const { username, avatarUrl, name, number, text } = answer
|
||||||
const isChosen = chosenProb !== undefined
|
const isChosen = chosenProb !== undefined
|
||||||
|
|
||||||
const prob = getOutcomeProbability(totalShares, answer.id)
|
const prob = getDpmOutcomeProbability(totalShares, answer.id)
|
||||||
const roundedProb = Math.round(prob * 100)
|
const roundedProb = Math.round(prob * 100)
|
||||||
const probPercent = formatPercent(prob)
|
const probPercent = formatPercent(prob)
|
||||||
const wasResolvedTo =
|
const wasResolvedTo =
|
||||||
|
|
|
@ -2,7 +2,7 @@ import clsx from 'clsx'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { resolveMarket } from '../../lib/firebase/api-call'
|
import { resolveMarket } from '../../lib/firebase/api-call'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
|
@ -11,7 +11,7 @@ import { ResolveConfirmationButton } from '../confirmation-button'
|
||||||
import { removeUndefinedProps } from '../../../common/util/object'
|
import { removeUndefinedProps } from '../../../common/util/object'
|
||||||
|
|
||||||
export function AnswerResolvePanel(props: {
|
export function AnswerResolvePanel(props: {
|
||||||
contract: Contract
|
contract: FullContract<DPM, FreeResponse>
|
||||||
resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
|
resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
|
||||||
setResolveOption: (
|
setResolveOption: (
|
||||||
option: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
|
option: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
|
||||||
|
|
|
@ -4,20 +4,28 @@ import dayjs from 'dayjs'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import {
|
||||||
|
Contract,
|
||||||
|
DPM,
|
||||||
|
FreeResponse,
|
||||||
|
FullContract,
|
||||||
|
} from '../../../common/contract'
|
||||||
import { getOutcomeProbability } from '../../../common/calculate'
|
import { getOutcomeProbability } from '../../../common/calculate'
|
||||||
import { useBets } from '../../hooks/use-bets'
|
import { useBets } from '../../hooks/use-bets'
|
||||||
import { useWindowSize } from '../../hooks/use-window-size'
|
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 { 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 bets = (useBets(contract.id) ?? props.bets).filter((bet) => !bet.sale)
|
||||||
|
|
||||||
const { probsByOutcome, sortedOutcomes } = computeProbsByOutcome(
|
const { probsByOutcome, sortedOutcomes } = computeProbsByOutcome(
|
||||||
bets,
|
bets,
|
||||||
totalShares
|
contract
|
||||||
)
|
)
|
||||||
|
|
||||||
const isClosed = !!closeTime && Date.now() > closeTime
|
const isClosed = !!closeTime && Date.now() > closeTime
|
||||||
|
@ -134,10 +142,7 @@ function formatTime(time: number, includeTime: boolean) {
|
||||||
return dayjs(time).format('MMM D')
|
return dayjs(time).format('MMM D')
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeProbsByOutcome = (
|
const computeProbsByOutcome = (bets: Bet[], contract: Contract) => {
|
||||||
bets: Bet[],
|
|
||||||
totalShares: { [outcome: string]: number }
|
|
||||||
) => {
|
|
||||||
const betsByOutcome = _.groupBy(bets, (bet) => bet.outcome)
|
const betsByOutcome = _.groupBy(bets, (bet) => bet.outcome)
|
||||||
const outcomes = Object.keys(betsByOutcome).filter((outcome) => {
|
const outcomes = Object.keys(betsByOutcome).filter((outcome) => {
|
||||||
const maxProb = Math.max(
|
const maxProb = Math.max(
|
||||||
|
@ -148,7 +153,7 @@ const computeProbsByOutcome = (
|
||||||
|
|
||||||
const trackedOutcomes = _.sortBy(
|
const trackedOutcomes = _.sortBy(
|
||||||
outcomes,
|
outcomes,
|
||||||
(outcome) => -1 * getOutcomeProbability(totalShares, outcome)
|
(outcome) => -1 * getOutcomeProbability(contract, outcome)
|
||||||
).slice(0, 5)
|
).slice(0, 5)
|
||||||
|
|
||||||
const probsByOutcome = _.fromPairs(
|
const probsByOutcome = _.fromPairs(
|
||||||
|
|
|
@ -2,18 +2,21 @@ import _ from 'lodash'
|
||||||
import { useLayoutEffect, useState } from 'react'
|
import { useLayoutEffect, useState } from 'react'
|
||||||
|
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from '../../../common/answer'
|
||||||
import { Contract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { formatPercent } from '../../../common/util/format'
|
import { formatPercent } from '../../../common/util/format'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from '../../hooks/use-user'
|
||||||
import { getOutcomeProbability } from '../../../common/calculate'
|
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
||||||
import { useAnswers } from '../../hooks/use-answers'
|
import { useAnswers } from '../../hooks/use-answers'
|
||||||
import { tradingAllowed } from '../../lib/firebase/contracts'
|
import { tradingAllowed } from '../../lib/firebase/contracts'
|
||||||
import { AnswerItem } from './answer-item'
|
import { AnswerItem } from './answer-item'
|
||||||
import { CreateAnswerPanel } from './create-answer-panel'
|
import { CreateAnswerPanel } from './create-answer-panel'
|
||||||
import { AnswerResolvePanel } from './answer-resolve-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 { contract } = props
|
||||||
const { creatorId, resolution, resolutions, totalBets } = contract
|
const { creatorId, resolution, resolutions, totalBets } = contract
|
||||||
|
|
||||||
|
@ -31,7 +34,7 @@ export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
|
||||||
),
|
),
|
||||||
..._.sortBy(
|
..._.sortBy(
|
||||||
otherAnswers,
|
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">
|
<div className="self-end p-4 text-gray-500">
|
||||||
None of the above:{' '}
|
None of the above:{' '}
|
||||||
{formatPercent(getOutcomeProbability(contract.totalShares, '0'))}
|
{formatPercent(getDpmOutcomeProbability(contract.totalShares, '0'))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import _ from 'lodash'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Textarea from 'react-expanding-textarea'
|
import Textarea from 'react-expanding-textarea'
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
||||||
import { AmountInput } from '../amount-input'
|
import { AmountInput } from '../amount-input'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { createAnswer } from '../../lib/firebase/api-call'
|
import { createAnswer } from '../../lib/firebase/api-call'
|
||||||
|
@ -16,14 +16,16 @@ import {
|
||||||
import { InfoTooltip } from '../info-tooltip'
|
import { InfoTooltip } from '../info-tooltip'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from '../../hooks/use-user'
|
||||||
import {
|
import {
|
||||||
getProbabilityAfterBet,
|
calculateDpmShares,
|
||||||
calculateShares,
|
calculateDpmPayoutAfterCorrectBet,
|
||||||
calculatePayoutAfterCorrectBet,
|
getDpmOutcomeProbabilityAfterBet,
|
||||||
} from '../../../common/calculate'
|
} from '../../../common/calculate-dpm'
|
||||||
import { firebaseLogin } from '../../lib/firebase/users'
|
import { firebaseLogin } from '../../lib/firebase/users'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
|
|
||||||
export function CreateAnswerPanel(props: { contract: Contract }) {
|
export function CreateAnswerPanel(props: {
|
||||||
|
contract: FullContract<DPM, FreeResponse>
|
||||||
|
}) {
|
||||||
const { contract } = props
|
const { contract } = props
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const [text, setText] = useState('')
|
const [text, setText] = useState('')
|
||||||
|
@ -53,16 +55,16 @@ export function CreateAnswerPanel(props: { contract: Contract }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultProb = getProbabilityAfterBet(
|
const resultProb = getDpmOutcomeProbabilityAfterBet(
|
||||||
contract.totalShares,
|
contract.totalShares,
|
||||||
'new',
|
'new',
|
||||||
betAmount ?? 0
|
betAmount ?? 0
|
||||||
)
|
)
|
||||||
|
|
||||||
const shares = calculateShares(contract.totalShares, betAmount ?? 0, 'new')
|
const shares = calculateDpmShares(contract.totalShares, betAmount ?? 0, 'new')
|
||||||
|
|
||||||
const currentPayout = betAmount
|
const currentPayout = betAmount
|
||||||
? calculatePayoutAfterCorrectBet(contract, {
|
? calculateDpmPayoutAfterCorrectBet(contract, {
|
||||||
outcome: 'new',
|
outcome: 'new',
|
||||||
amount: betAmount,
|
amount: betAmount,
|
||||||
shares,
|
shares,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { useUser } from '../hooks/use-user'
|
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 { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
|
@ -13,31 +13,22 @@ import {
|
||||||
formatWithCommas,
|
formatWithCommas,
|
||||||
} from '../../common/util/format'
|
} from '../../common/util/format'
|
||||||
import { Title } from './title'
|
import { Title } from './title'
|
||||||
import {
|
|
||||||
getProbability,
|
|
||||||
calculateShares,
|
|
||||||
getProbabilityAfterBet,
|
|
||||||
calculatePayoutAfterCorrectBet,
|
|
||||||
} from '../../common/calculate'
|
|
||||||
import { firebaseLogin } from '../lib/firebase/users'
|
import { firebaseLogin } from '../lib/firebase/users'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { placeBet } from '../lib/firebase/api-call'
|
import { placeBet } from '../lib/firebase/api-call'
|
||||||
import { AmountInput } from './amount-input'
|
import { AmountInput } from './amount-input'
|
||||||
import { InfoTooltip } from './info-tooltip'
|
import { InfoTooltip } from './info-tooltip'
|
||||||
import { OutcomeLabel } from './outcome-label'
|
import { OutcomeLabel } from './outcome-label'
|
||||||
|
import {
|
||||||
// Focus helper from https://stackoverflow.com/a/54159564/1222351
|
calculatePayoutAfterCorrectBet,
|
||||||
function useFocus(): [React.RefObject<HTMLElement>, () => void] {
|
calculateShares,
|
||||||
const htmlElRef = useRef<HTMLElement>(null)
|
getProbability,
|
||||||
const setFocus = () => {
|
getOutcomeProbabilityAfterBet,
|
||||||
htmlElRef.current && htmlElRef.current.focus()
|
} from '../../common/calculate'
|
||||||
}
|
import { useFocus } from '../hooks/use-focus'
|
||||||
|
|
||||||
return [htmlElRef, setFocus]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BetPanel(props: {
|
export function BetPanel(props: {
|
||||||
contract: Contract
|
contract: FullContract<DPM | CPMM, Binary>
|
||||||
className?: string
|
className?: string
|
||||||
title?: string // Set if BetPanel is on a feed modal
|
title?: string // Set if BetPanel is on a feed modal
|
||||||
selected?: 'YES' | 'NO'
|
selected?: 'YES' | 'NO'
|
||||||
|
@ -49,7 +40,6 @@ export function BetPanel(props: {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const { contract, className, title, selected, onBetSuccess } = props
|
const { contract, className, title, selected, onBetSuccess } = props
|
||||||
const { totalShares, phantomShares } = contract
|
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
|
@ -102,20 +92,16 @@ export function BetPanel(props: {
|
||||||
|
|
||||||
const betDisabled = isSubmitting || !betAmount || error
|
const betDisabled = isSubmitting || !betAmount || error
|
||||||
|
|
||||||
const initialProb = getProbability(contract.totalShares)
|
const initialProb = getProbability(contract)
|
||||||
|
|
||||||
const outcomeProb = getProbabilityAfterBet(
|
const outcomeProb = getOutcomeProbabilityAfterBet(
|
||||||
contract.totalShares,
|
contract,
|
||||||
betChoice || 'YES',
|
betChoice || 'YES',
|
||||||
betAmount ?? 0
|
betAmount ?? 0
|
||||||
)
|
)
|
||||||
const resultProb = betChoice === 'NO' ? 1 - outcomeProb : outcomeProb
|
const resultProb = betChoice === 'NO' ? 1 - outcomeProb : outcomeProb
|
||||||
|
|
||||||
const shares = calculateShares(
|
const shares = calculateShares(contract, betAmount ?? 0, betChoice || 'YES')
|
||||||
contract.totalShares,
|
|
||||||
betAmount ?? 0,
|
|
||||||
betChoice || 'YES'
|
|
||||||
)
|
|
||||||
|
|
||||||
const currentPayout = betAmount
|
const currentPayout = betAmount
|
||||||
? calculatePayoutAfterCorrectBet(contract, {
|
? calculatePayoutAfterCorrectBet(contract, {
|
||||||
|
@ -127,11 +113,23 @@ export function BetPanel(props: {
|
||||||
|
|
||||||
const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0
|
const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0
|
||||||
const currentReturnPercent = (currentReturn * 100).toFixed() + '%'
|
const currentReturnPercent = (currentReturn * 100).toFixed() + '%'
|
||||||
|
|
||||||
const panelTitle = title ?? 'Place a trade'
|
const panelTitle = title ?? 'Place a trade'
|
||||||
if (title) {
|
if (title) {
|
||||||
focusAmountInput()
|
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 (
|
return (
|
||||||
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
|
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
|
||||||
<Title
|
<Title
|
||||||
|
@ -172,15 +170,8 @@ export function BetPanel(props: {
|
||||||
<div>
|
<div>
|
||||||
Payout if <OutcomeLabel outcome={betChoice ?? 'YES'} />
|
Payout if <OutcomeLabel outcome={betChoice ?? 'YES'} />
|
||||||
</div>
|
</div>
|
||||||
<InfoTooltip
|
|
||||||
text={`Current payout for ${formatWithCommas(
|
{tooltip && <InfoTooltip text={tooltip} />}
|
||||||
shares
|
|
||||||
)} / ${formatWithCommas(
|
|
||||||
shares +
|
|
||||||
totalShares[betChoice ?? 'YES'] -
|
|
||||||
(phantomShares ? phantomShares[betChoice ?? 'YES'] : 0)
|
|
||||||
)} ${betChoice} shares`}
|
|
||||||
/>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Row className="flex-wrap items-end justify-end gap-2">
|
<Row className="flex-wrap items-end justify-end gap-2">
|
||||||
<span className="whitespace-nowrap">
|
<span className="whitespace-nowrap">
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { Dialog, Transition } from '@headlessui/react'
|
import { Dialog, Transition } from '@headlessui/react'
|
||||||
import { Contract } from '../lib/firebase/contracts'
|
|
||||||
import { BetPanel } from './bet-panel'
|
import { BetPanel } from './bet-panel'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { YesNoSelector } from './yes-no-selector'
|
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.
|
// Inline version of a bet panel. Opens BetPanel in a new modal.
|
||||||
export default function BetRow(props: {
|
export default function BetRow(props: {
|
||||||
contract: Contract
|
contract: FullContract<DPM | CPMM, Binary>
|
||||||
className?: string
|
className?: string
|
||||||
labelClassName?: string
|
labelClassName?: string
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -22,6 +22,12 @@ import {
|
||||||
} from '../lib/firebase/contracts'
|
} from '../lib/firebase/contracts'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { UserLink } from './user-page'
|
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 {
|
import {
|
||||||
calculatePayout,
|
calculatePayout,
|
||||||
calculateSaleAmount,
|
calculateSaleAmount,
|
||||||
|
@ -30,12 +36,6 @@ import {
|
||||||
getProbabilityAfterSale,
|
getProbabilityAfterSale,
|
||||||
resolvedPayout,
|
resolvedPayout,
|
||||||
} from '../../common/calculate'
|
} 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 BetSort = 'newest' | 'profit' | 'settled' | 'value'
|
||||||
type BetFilter = 'open' | 'closed' | 'resolved' | 'all'
|
type BetFilter = 'open' | 'closed' | 'resolved' | 'all'
|
||||||
|
@ -400,7 +400,7 @@ export function MyBetsSummary(props: {
|
||||||
<>
|
<>
|
||||||
Payout at{' '}
|
Payout at{' '}
|
||||||
<span className="text-blue-400">
|
<span className="text-blue-400">
|
||||||
{formatPercent(getProbability(contract.totalShares))}
|
{formatPercent(getProbability(contract))}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
@ -427,28 +427,46 @@ export function ContractBetsTable(props: {
|
||||||
const { contract, bets, className } = props
|
const { contract, bets, className } = props
|
||||||
|
|
||||||
const [sales, buys] = _.partition(bets, (bet) => bet.sale)
|
const [sales, buys] = _.partition(bets, (bet) => bet.sale)
|
||||||
|
|
||||||
const salesDict = _.fromPairs(
|
const salesDict = _.fromPairs(
|
||||||
sales.map((sale) => [sale.sale?.betId ?? '', sale])
|
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 (
|
return (
|
||||||
<div className={clsx('overflow-x-auto', className)}>
|
<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">
|
<table className="table-zebra table-compact table w-full text-gray-500">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="p-2">
|
<tr className="p-2">
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Outcome</th>
|
<th>Outcome</th>
|
||||||
<th>Amount</th>
|
<th>Amount</th>
|
||||||
<th>{isResolved ? <>Payout</> : <>Sale price</>}</th>
|
{!isCPMM && <th>{isResolved ? <>Payout</> : <>Sale price</>}</th>}
|
||||||
{!isResolved && <th>Payout if chosen</th>}
|
{!isCPMM && !isResolved && <th>Payout if chosen</th>}
|
||||||
<th>Probability</th>
|
|
||||||
<th>Shares</th>
|
<th>Shares</th>
|
||||||
|
<th>Probability</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{buys.map((bet) => (
|
{normalBets.map((bet) => (
|
||||||
<BetRow
|
<BetRow
|
||||||
key={bet.id}
|
key={bet.id}
|
||||||
bet={bet}
|
bet={bet}
|
||||||
|
@ -476,9 +494,12 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
|
||||||
loanAmount,
|
loanAmount,
|
||||||
} = bet
|
} = bet
|
||||||
|
|
||||||
const { isResolved, closeTime } = contract
|
const { isResolved, closeTime, mechanism } = contract
|
||||||
|
|
||||||
const isClosed = closeTime && Date.now() > closeTime
|
const isClosed = closeTime && Date.now() > closeTime
|
||||||
|
|
||||||
|
const isCPMM = mechanism === 'cpmm-1'
|
||||||
|
|
||||||
const saleAmount = saleBet?.sale?.amount
|
const saleAmount = saleBet?.sale?.amount
|
||||||
|
|
||||||
const saleDisplay = isAnte ? (
|
const saleDisplay = isAnte ? (
|
||||||
|
@ -501,7 +522,7 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td className="text-neutral">
|
<td className="text-neutral">
|
||||||
{!isResolved && !isClosed && !isSold && !isAnte && (
|
{!isCPMM && !isResolved && !isClosed && !isSold && !isAnte && (
|
||||||
<SellButton contract={contract} bet={bet} />
|
<SellButton contract={contract} bet={bet} />
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
@ -512,12 +533,12 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
|
||||||
{formatMoney(amount)}
|
{formatMoney(amount)}
|
||||||
{loanAmount ? ` (${formatMoney(loanAmount ?? 0)} loan)` : ''}
|
{loanAmount ? ` (${formatMoney(loanAmount ?? 0)} loan)` : ''}
|
||||||
</td>
|
</td>
|
||||||
<td>{saleDisplay}</td>
|
{!isCPMM && <td>{saleDisplay}</td>}
|
||||||
{!isResolved && <td>{payoutIfChosenDisplay}</td>}
|
{!isCPMM && !isResolved && <td>{payoutIfChosenDisplay}</td>}
|
||||||
|
<td>{formatWithCommas(shares)}</td>
|
||||||
<td>
|
<td>
|
||||||
{formatPercent(probBefore)} → {formatPercent(probAfter)}
|
{formatPercent(probBefore)} → {formatPercent(probAfter)}
|
||||||
</td>
|
</td>
|
||||||
<td>{formatWithCommas(shares)}</td>
|
|
||||||
<td>{dayjs(createdTime).format('MMM D, h:mma')}</td>
|
<td>{dayjs(createdTime).format('MMM D, h:mma')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
|
@ -535,15 +556,11 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
const initialProb = getOutcomeProbability(
|
const initialProb = getOutcomeProbability(
|
||||||
contract.totalShares,
|
contract,
|
||||||
outcome === 'NO' ? 'YES' : outcome
|
outcome === 'NO' ? 'YES' : outcome
|
||||||
)
|
)
|
||||||
|
|
||||||
const outcomeProb = getProbabilityAfterSale(
|
const outcomeProb = getProbabilityAfterSale(contract, outcome, shares)
|
||||||
contract.totalShares,
|
|
||||||
outcome,
|
|
||||||
shares
|
|
||||||
)
|
|
||||||
|
|
||||||
const saleAmount = calculateSaleAmount(contract, bet)
|
const saleAmount = calculateSaleAmount(contract, bet)
|
||||||
const profit = saleAmount - bet.amount
|
const profit = saleAmount - bet.amount
|
||||||
|
|
|
@ -125,7 +125,7 @@ function AbbrContractDetails(props: {
|
||||||
}) {
|
}) {
|
||||||
const { contract, showHotVolume, showCloseTime } = props
|
const { contract, showHotVolume, showCloseTime } = props
|
||||||
const { volume24Hours, creatorName, creatorUsername, closeTime } = contract
|
const { volume24Hours, creatorName, creatorUsername, closeTime } = contract
|
||||||
const { truePool } = contractMetrics(contract)
|
const { liquidityLabel } = contractMetrics(contract)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className={clsx('gap-2 text-sm text-gray-500')}>
|
<Col className={clsx('gap-2 text-sm text-gray-500')}>
|
||||||
|
@ -156,7 +156,7 @@ function AbbrContractDetails(props: {
|
||||||
) : (
|
) : (
|
||||||
<Row className="gap-1">
|
<Row className="gap-1">
|
||||||
{/* <DatabaseIcon className="h-5 w-5" /> */}
|
{/* <DatabaseIcon className="h-5 w-5" /> */}
|
||||||
{formatMoney(truePool)} pool
|
{liquidityLabel}
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -170,7 +170,8 @@ export function ContractDetails(props: {
|
||||||
}) {
|
}) {
|
||||||
const { contract, isCreator } = props
|
const { contract, isCreator } = props
|
||||||
const { closeTime, creatorName, creatorUsername } = contract
|
const { closeTime, creatorName, creatorUsername } = contract
|
||||||
const { truePool, createdDate, resolvedDate } = contractMetrics(contract)
|
const { liquidityLabel, createdDate, resolvedDate } =
|
||||||
|
contractMetrics(contract)
|
||||||
|
|
||||||
const tweetText = getTweetText(contract, !!isCreator)
|
const tweetText = getTweetText(contract, !!isCreator)
|
||||||
|
|
||||||
|
@ -224,7 +225,7 @@ export function ContractDetails(props: {
|
||||||
<Row className="items-center gap-1">
|
<Row className="items-center gap-1">
|
||||||
<DatabaseIcon className="h-5 w-5" />
|
<DatabaseIcon className="h-5 w-5" />
|
||||||
|
|
||||||
<div className="whitespace-nowrap">{formatMoney(truePool)} pool</div>
|
<div className="whitespace-nowrap">{liquidityLabel}</div>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<TweetButton className={'self-end'} tweetText={tweetText} />
|
<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
|
// String version of the above, to send to the OpenGraph image generator
|
||||||
export function contractTextDetails(contract: Contract) {
|
export function contractTextDetails(contract: Contract) {
|
||||||
const { closeTime, tags } = contract
|
const { closeTime, tags } = contract
|
||||||
const { truePool, createdDate, resolvedDate } = contractMetrics(contract)
|
const { createdDate, resolvedDate, liquidityLabel } =
|
||||||
|
contractMetrics(contract)
|
||||||
|
|
||||||
const hashtags = tags.map((tag) => `#${tag}`)
|
const hashtags = tags.map((tag) => `#${tag}`)
|
||||||
|
|
||||||
|
@ -247,7 +249,7 @@ export function contractTextDetails(contract: Contract) {
|
||||||
closeTime
|
closeTime
|
||||||
).format('MMM D, h:mma')}`
|
).format('MMM D, h:mma')}`
|
||||||
: '') +
|
: '') +
|
||||||
` • ${formatMoney(truePool)} pool` +
|
` • ${liquidityLabel}` +
|
||||||
(hashtags.length > 0 ? ` • ${hashtags.join(' ')}` : '')
|
(hashtags.length > 0 ? ` • ${hashtags.join(' ')}` : '')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { Fold } from '../../common/fold'
|
||||||
import { FoldTagList } from './tags-list'
|
import { FoldTagList } from './tags-list'
|
||||||
import { ContractActivity } from './feed/contract-activity'
|
import { ContractActivity } from './feed/contract-activity'
|
||||||
import { AnswersGraph } from './answers/answers-graph'
|
import { AnswersGraph } from './answers/answers-graph'
|
||||||
|
import { DPM, FreeResponse, FullContract } from '../../common/contract'
|
||||||
|
|
||||||
export const ContractOverview = (props: {
|
export const ContractOverview = (props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -81,7 +82,10 @@ export const ContractOverview = (props: {
|
||||||
{isBinary ? (
|
{isBinary ? (
|
||||||
<ContractProbGraph contract={contract} bets={bets} />
|
<ContractProbGraph contract={contract} bets={bets} />
|
||||||
) : (
|
) : (
|
||||||
<AnswersGraph contract={contract} bets={bets} />
|
<AnswersGraph
|
||||||
|
contract={contract as FullContract<DPM, FreeResponse>}
|
||||||
|
bets={bets}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -2,27 +2,29 @@ import { DatumValue } from '@nivo/core'
|
||||||
import { ResponsiveLine } from '@nivo/line'
|
import { ResponsiveLine } from '@nivo/line'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Bet } from '../../common/bet'
|
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 { useBetsWithoutAntes } from '../hooks/use-bets'
|
||||||
import { useWindowSize } from '../hooks/use-window-size'
|
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 { contract } = props
|
||||||
const { phantomShares, resolutionTime, closeTime } = contract
|
const { resolutionTime, closeTime } = contract
|
||||||
|
|
||||||
const bets = useBetsWithoutAntes(contract, props.bets)
|
const bets = useBetsWithoutAntes(contract, props.bets).filter(
|
||||||
|
(b) => !b.isRedemption
|
||||||
const startProb = getProbability(
|
|
||||||
phantomShares as { [outcome: string]: number }
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const times = bets
|
const startProb = getInitialProbability(contract)
|
||||||
? [contract.createdTime, ...bets.map((bet) => bet.createdTime)].map(
|
|
||||||
(time) => new Date(time)
|
const times = [
|
||||||
)
|
contract.createdTime,
|
||||||
: []
|
...bets.map((bet) => bet.createdTime),
|
||||||
const probs = bets ? [startProb, ...bets.map((bet) => bet.probAfter)] : []
|
].map((time) => new Date(time))
|
||||||
|
const probs = [startProb, ...bets.map((bet) => bet.probAfter)]
|
||||||
|
|
||||||
const isClosed = !!closeTime && Date.now() > closeTime
|
const isClosed = !!closeTime && Date.now() > closeTime
|
||||||
const latestTime = dayjs(
|
const latestTime = dayjs(
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { Col } from './layout/col'
|
||||||
import { SiteLink } from './site-link'
|
import { SiteLink } from './site-link'
|
||||||
import { ContractCard } from './contract-card'
|
import { ContractCard } from './contract-card'
|
||||||
import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
|
import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
|
||||||
|
import { Answer } from '../../common/answer'
|
||||||
|
|
||||||
export function ContractsGrid(props: {
|
export function ContractsGrid(props: {
|
||||||
contracts: Contract[]
|
contracts: Contract[]
|
||||||
|
@ -217,7 +218,11 @@ export function SearchableGrid(props: {
|
||||||
check(c.creatorName) ||
|
check(c.creatorName) ||
|
||||||
check(c.creatorUsername) ||
|
check(c.creatorUsername) ||
|
||||||
check(c.lowercaseTags.map((tag) => `#${tag}`).join(' ')) ||
|
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') {
|
if (sort === 'newest' || sort === 'all') {
|
||||||
|
|
|
@ -4,7 +4,12 @@ import { Answer } from '../../../common/answer'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { getOutcomeProbability } from '../../../common/calculate'
|
import { getOutcomeProbability } from '../../../common/calculate'
|
||||||
import { Comment } from '../../../common/comment'
|
import { Comment } from '../../../common/comment'
|
||||||
import { Contract } from '../../../common/contract'
|
import {
|
||||||
|
Contract,
|
||||||
|
DPM,
|
||||||
|
FreeResponse,
|
||||||
|
FullContract,
|
||||||
|
} from '../../../common/contract'
|
||||||
import { User } from '../../../common/user'
|
import { User } from '../../../common/user'
|
||||||
import { mapCommentsByBetId } from '../../lib/firebase/comments'
|
import { mapCommentsByBetId } from '../../lib/firebase/comments'
|
||||||
|
|
||||||
|
@ -169,7 +174,7 @@ function groupBets(
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAnswerGroups(
|
function getAnswerGroups(
|
||||||
contract: Contract,
|
contract: FullContract<DPM, FreeResponse>,
|
||||||
bets: Bet[],
|
bets: Bet[],
|
||||||
comments: Comment[],
|
comments: Comment[],
|
||||||
user: User | undefined | null,
|
user: User | undefined | null,
|
||||||
|
@ -181,7 +186,7 @@ function getAnswerGroups(
|
||||||
const { sortByProb, abbreviated } = options
|
const { sortByProb, abbreviated } = options
|
||||||
|
|
||||||
let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
|
let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
|
||||||
(outcome) => getOutcomeProbability(contract.totalShares, outcome) > 0.01
|
(outcome) => getOutcomeProbability(contract, outcome) > 0.01
|
||||||
)
|
)
|
||||||
if (abbreviated) {
|
if (abbreviated) {
|
||||||
const lastComment = _.last(comments)
|
const lastComment = _.last(comments)
|
||||||
|
@ -204,7 +209,7 @@ function getAnswerGroups(
|
||||||
if (sortByProb) {
|
if (sortByProb) {
|
||||||
outcomes = _.sortBy(
|
outcomes = _.sortBy(
|
||||||
outcomes,
|
outcomes,
|
||||||
(outcome) => -1 * getOutcomeProbability(contract.totalShares, outcome)
|
(outcome) => -1 * getOutcomeProbability(contract, outcome)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Sort by recent bet.
|
// Sort by recent bet.
|
||||||
|
@ -266,7 +271,9 @@ export function getAllContractActivityItems(
|
||||||
let answer: Answer | undefined
|
let answer: Answer | undefined
|
||||||
if (filterToOutcome) {
|
if (filterToOutcome) {
|
||||||
bets = bets.filter((bet) => bet.outcome === 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[] =
|
const items: ActivityItem[] =
|
||||||
|
@ -278,10 +285,16 @@ export function getAllContractActivityItems(
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
...(outcomeType === 'FREE_RESPONSE' && !filterToOutcome
|
...(outcomeType === 'FREE_RESPONSE' && !filterToOutcome
|
||||||
? getAnswerGroups(contract, bets, comments, user, {
|
? getAnswerGroups(
|
||||||
|
contract as FullContract<DPM, FreeResponse>,
|
||||||
|
bets,
|
||||||
|
comments,
|
||||||
|
user,
|
||||||
|
{
|
||||||
sortByProb: true,
|
sortByProb: true,
|
||||||
abbreviated,
|
abbreviated,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
: groupBets(bets, comments, contract, user?.id, {
|
: groupBets(bets, comments, contract, user?.id, {
|
||||||
hideOutcome: !!filterToOutcome,
|
hideOutcome: !!filterToOutcome,
|
||||||
abbreviated,
|
abbreviated,
|
||||||
|
@ -317,10 +330,16 @@ export function getRecentContractActivityItems(
|
||||||
|
|
||||||
const items =
|
const items =
|
||||||
contract.outcomeType === 'FREE_RESPONSE'
|
contract.outcomeType === 'FREE_RESPONSE'
|
||||||
? getAnswerGroups(contract, bets, comments, user, {
|
? getAnswerGroups(
|
||||||
|
contract as FullContract<DPM, FreeResponse>,
|
||||||
|
bets,
|
||||||
|
comments,
|
||||||
|
user,
|
||||||
|
{
|
||||||
sortByProb: false,
|
sortByProb: false,
|
||||||
abbreviated: true,
|
abbreviated: true,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
: groupBets(bets, comments, contract, user?.id, {
|
: groupBets(bets, comments, contract, user?.id, {
|
||||||
hideOutcome: false,
|
hideOutcome: false,
|
||||||
abbreviated: true,
|
abbreviated: true,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// From https://tailwindui.com/components/application-ui/lists/feeds
|
// From https://tailwindui.com/components/application-ui/lists/feeds
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
import {
|
import {
|
||||||
BanIcon,
|
BanIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
@ -46,6 +46,7 @@ import { Avatar } from '../avatar'
|
||||||
import { useAdmin } from '../../hooks/use-admin'
|
import { useAdmin } from '../../hooks/use-admin'
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from '../../../common/answer'
|
||||||
import { ActivityItem } from './activity-items'
|
import { ActivityItem } from './activity-items'
|
||||||
|
import { FreeResponse, FullContract } from '../../../common/contract'
|
||||||
|
|
||||||
export function FeedItems(props: {
|
export function FeedItems(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -191,12 +192,6 @@ function FeedBet(props: {
|
||||||
const bought = amount >= 0 ? 'bought' : 'sold'
|
const bought = amount >= 0 ? 'bought' : 'sold'
|
||||||
const money = formatMoney(Math.abs(amount))
|
const money = formatMoney(Math.abs(amount))
|
||||||
|
|
||||||
const answer =
|
|
||||||
!hideOutcome &&
|
|
||||||
(contract.answers?.find((answer: Answer) => answer?.id === outcome) as
|
|
||||||
| Answer
|
|
||||||
| undefined)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
|
@ -222,18 +217,13 @@ function FeedBet(props: {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx('min-w-0 flex-1 pb-1.5', !answer && 'pt-1.5')}>
|
<div className={'min-w-0 flex-1 pb-1.5'}>
|
||||||
{answer && (
|
|
||||||
<div className="text-neutral mb-2" style={{ fontSize: 15 }}>
|
|
||||||
<Linkify text={answer.text} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
<span>
|
<span>
|
||||||
{isSelf ? 'You' : isCreator ? contract.creatorName : 'A trader'}
|
{isSelf ? 'You' : isCreator ? contract.creatorName : 'A trader'}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
{bought} {money}
|
{bought} {money}
|
||||||
{!answer && !hideOutcome && (
|
{!hideOutcome && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
of <OutcomeLabel outcome={outcome} />
|
of <OutcomeLabel outcome={outcome} />
|
||||||
|
@ -425,7 +415,7 @@ export function FeedQuestion(props: {
|
||||||
const { contract, showDescription } = props
|
const { contract, showDescription } = props
|
||||||
const { creatorName, creatorUsername, question, resolution, outcomeType } =
|
const { creatorName, creatorUsername, question, resolution, outcomeType } =
|
||||||
contract
|
contract
|
||||||
const { truePool } = contractMetrics(contract)
|
const { liquidityLabel } = contractMetrics(contract)
|
||||||
const isBinary = outcomeType === 'BINARY'
|
const isBinary = outcomeType === 'BINARY'
|
||||||
|
|
||||||
const closeMessage =
|
const closeMessage =
|
||||||
|
@ -453,7 +443,7 @@ export function FeedQuestion(props: {
|
||||||
asked
|
asked
|
||||||
{/* Currently hidden on mobile; ideally we'd fit this in somewhere. */}
|
{/* Currently hidden on mobile; ideally we'd fit this in somewhere. */}
|
||||||
<span className="float-right hidden text-gray-400 sm:inline">
|
<span className="float-right hidden text-gray-400 sm:inline">
|
||||||
{formatMoney(truePool)} pool
|
{liquidityLabel}
|
||||||
{closeMessage}
|
{closeMessage}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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
|
const { answer } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -677,7 +670,7 @@ function FeedBetGroup(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
function FeedAnswerGroup(props: {
|
function FeedAnswerGroup(props: {
|
||||||
contract: Contract
|
contract: FullContract<any, FreeResponse>
|
||||||
answer: Answer
|
answer: Answer
|
||||||
items: ActivityItem[]
|
items: ActivityItem[]
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { Contract } from '../lib/firebase/contracts'
|
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Title } from './title'
|
import { Title } from './title'
|
||||||
import { User } from '../lib/firebase/users'
|
import { User } from '../lib/firebase/users'
|
||||||
|
@ -10,12 +9,14 @@ import { Spacer } from './layout/spacer'
|
||||||
import { ResolveConfirmationButton } from './confirmation-button'
|
import { ResolveConfirmationButton } from './confirmation-button'
|
||||||
import { resolveMarket } from '../lib/firebase/api-call'
|
import { resolveMarket } from '../lib/firebase/api-call'
|
||||||
import { ProbabilitySelector } from './probability-selector'
|
import { ProbabilitySelector } from './probability-selector'
|
||||||
|
import { DPM_CREATOR_FEE } from '../../common/fees'
|
||||||
import { getProbability } from '../../common/calculate'
|
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: {
|
export function ResolutionPanel(props: {
|
||||||
creator: User
|
creator: User
|
||||||
contract: Contract
|
contract: FullContract<DPM | CPMM, Binary>
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -25,11 +26,16 @@ export function ResolutionPanel(props: {
|
||||||
|
|
||||||
const { contract, className } = 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<
|
const [outcome, setOutcome] = useState<
|
||||||
'YES' | 'NO' | 'MKT' | 'CANCEL' | undefined
|
'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 [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [error, setError] = useState<string | undefined>(undefined)
|
const [error, setError] = useState<string | undefined>(undefined)
|
||||||
|
@ -85,14 +91,14 @@ export function ResolutionPanel(props: {
|
||||||
Winnings will be paid out to YES bettors.
|
Winnings will be paid out to YES bettors.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
You earn {CREATOR_FEE * 100}% of trader profits.
|
You will earn {earnedFees}.
|
||||||
</>
|
</>
|
||||||
) : outcome === 'NO' ? (
|
) : outcome === 'NO' ? (
|
||||||
<>
|
<>
|
||||||
Winnings will be paid out to NO bettors.
|
Winnings will be paid out to NO bettors.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
You earn {CREATOR_FEE * 100}% of trader profits.
|
You will earn {earnedFees}.
|
||||||
</>
|
</>
|
||||||
) : outcome === 'CANCEL' ? (
|
) : outcome === 'CANCEL' ? (
|
||||||
<>The pool will be returned to traders with no fees.</>
|
<>The pool will be returned to traders with no fees.</>
|
||||||
|
@ -103,7 +109,7 @@ export function ResolutionPanel(props: {
|
||||||
probabilityInt={Math.round(prob)}
|
probabilityInt={Math.round(prob)}
|
||||||
setProbabilityInt={setProb}
|
setProbabilityInt={setProb}
|
||||||
/>
|
/>
|
||||||
<div>You earn {CREATOR_FEE * 100}% of trader profits.</div>
|
You will earn {earnedFees}.
|
||||||
</Col>
|
</Col>
|
||||||
) : (
|
) : (
|
||||||
<>Resolving this market will immediately pay out traders.</>
|
<>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 { app } from './init'
|
||||||
import { getValues, listenForValue, listenForValues } from './utils'
|
import { getValues, listenForValue, listenForValues } from './utils'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Binary, Contract, FullContract } from '../../../common/contract'
|
||||||
import { getProbability } from '../../../common/calculate'
|
import { getDpmProbability } from '../../../common/calculate-dpm'
|
||||||
import { createRNG, shuffle } from '../../../common/util/random'
|
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 type { Contract }
|
||||||
|
|
||||||
export function contractPath(contract: Contract) {
|
export function contractPath(contract: Contract) {
|
||||||
|
@ -37,15 +40,25 @@ export function contractMetrics(contract: Contract) {
|
||||||
? dayjs(resolutionTime).format('MMM D')
|
? dayjs(resolutionTime).format('MMM D')
|
||||||
: undefined
|
: 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) {
|
export function getBinaryProbPercent(contract: FullContract<any, Binary>) {
|
||||||
const { totalShares, resolutionProbability } = contract
|
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) + '%'
|
const probPercent = Math.round(prob * 100) + '%'
|
||||||
|
|
||||||
return probPercent
|
return probPercent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,6 @@ function BetsSection(props: {
|
||||||
bets: Bet[]
|
bets: Bet[]
|
||||||
}) {
|
}) {
|
||||||
const { contract, user } = props
|
const { contract, user } = props
|
||||||
const isBinary = contract.outcomeType === 'BINARY'
|
|
||||||
const bets = useBets(contract.id) ?? props.bets
|
const bets = useBets(contract.id) ?? props.bets
|
||||||
|
|
||||||
// Decending creation time.
|
// Decending creation time.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Bet } from '../../../../common/bet'
|
import { Bet } from '../../../../common/bet'
|
||||||
import { getProbability } from '../../../../common/calculate'
|
import { getDpmProbability } from '../../../../common/calculate-dpm'
|
||||||
import { Comment } from '../../../../common/comment'
|
import { Comment } from '../../../../common/comment'
|
||||||
import { Contract } from '../../../../common/contract'
|
import { DPM, FullContract } from '../../../../common/contract'
|
||||||
|
|
||||||
export type LiteMarket = {
|
export type LiteMarket = {
|
||||||
// Unique identifer for this market
|
// Unique identifer for this market
|
||||||
|
@ -56,7 +56,7 @@ export function toLiteMarket({
|
||||||
isResolved,
|
isResolved,
|
||||||
resolution,
|
resolution,
|
||||||
resolutionTime,
|
resolutionTime,
|
||||||
}: Contract): LiteMarket {
|
}: FullContract<DPM, any>): LiteMarket {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
creatorUsername,
|
creatorUsername,
|
||||||
|
@ -72,7 +72,7 @@ export function toLiteMarket({
|
||||||
tags,
|
tags,
|
||||||
url: `https://manifold.markets/${creatorUsername}/${slug}`,
|
url: `https://manifold.markets/${creatorUsername}/${slug}`,
|
||||||
pool: pool.YES + pool.NO,
|
pool: pool.YES + pool.NO,
|
||||||
probability: getProbability(totalShares),
|
probability: getDpmProbability(totalShares),
|
||||||
volume7Days,
|
volume7Days,
|
||||||
volume24Hours,
|
volume24Hours,
|
||||||
isResolved,
|
isResolved,
|
||||||
|
|
|
@ -236,10 +236,9 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
|
|
||||||
<div className="form-control mb-1 items-start">
|
<div className="form-control mb-1 items-start">
|
||||||
<label className="label mb-1 gap-2">
|
<label className="label mb-1 gap-2">
|
||||||
<span>Market ante</span>
|
<span>Market subsidy</span>
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
text={`Subsidize your market to encourage trading. Ante bets are set to match your initial probability.
|
text={`Provide liquidity to encourage traders to participate.`}
|
||||||
You earn ${CREATOR_FEE * 100}% of trader profits.`}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<AmountInput
|
<AmountInput
|
||||||
|
|
|
@ -3,11 +3,12 @@ import dayjs from 'dayjs'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Textarea from 'react-expanding-textarea'
|
import Textarea from 'react-expanding-textarea'
|
||||||
|
|
||||||
import { getProbability } from '../../common/calculate'
|
import { getProbability } from '../../common/calculate'
|
||||||
|
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
|
||||||
import { parseWordsAsTags } from '../../common/util/parse'
|
import { parseWordsAsTags } from '../../common/util/parse'
|
||||||
import { AmountInput } from '../components/amount-input'
|
import { AmountInput } from '../components/amount-input'
|
||||||
import { InfoTooltip } from '../components/info-tooltip'
|
import { InfoTooltip } from '../components/info-tooltip'
|
||||||
|
|
||||||
import { Col } from '../components/layout/col'
|
import { Col } from '../components/layout/col'
|
||||||
import { Row } from '../components/layout/row'
|
import { Row } from '../components/layout/row'
|
||||||
import { Spacer } from '../components/layout/spacer'
|
import { Spacer } from '../components/layout/spacer'
|
||||||
|
@ -16,7 +17,7 @@ import { Page } from '../components/page'
|
||||||
import { Title } from '../components/title'
|
import { Title } from '../components/title'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from '../hooks/use-user'
|
||||||
import { createContract } from '../lib/firebase/api-call'
|
import { createContract } from '../lib/firebase/api-call'
|
||||||
import { Contract, contractPath } from '../lib/firebase/contracts'
|
import { contractPath } from '../lib/firebase/contracts'
|
||||||
|
|
||||||
type Prediction = {
|
type Prediction = {
|
||||||
question: string
|
question: string
|
||||||
|
@ -25,8 +26,8 @@ type Prediction = {
|
||||||
createdUrl?: string
|
createdUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function toPrediction(contract: Contract): Prediction {
|
function toPrediction(contract: FullContract<DPM | CPMM, Binary>): Prediction {
|
||||||
const startProb = getProbability(contract.totalShares)
|
const startProb = getProbability(contract)
|
||||||
return {
|
return {
|
||||||
question: contract.question,
|
question: contract.question,
|
||||||
description: contract.description,
|
description: contract.description,
|
||||||
|
@ -101,7 +102,9 @@ export default function MakePredictions() {
|
||||||
const [description, setDescription] = useState('')
|
const [description, setDescription] = useState('')
|
||||||
const [tags, setTags] = useState('')
|
const [tags, setTags] = useState('')
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
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 [ante, setAnte] = useState<number | undefined>(100)
|
||||||
const [anteError, setAnteError] = useState<string | undefined>()
|
const [anteError, setAnteError] = useState<string | undefined>()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user