f533d9bfcb
* Fetch balance of users with open limit orders & cancel orders with insufficient balance
* Fix imports
* Fix bugs
* Fix a bug
* Remove redundant cast
* buttons overlaying content fix (#1005)
* buttons overlaying content fix
* stats: round DAU number
* made set width for portfolio/profit fields (#1006)
* tournaments: included resolved markets
* made delete red, moved button for regular posts (#1008)
* Fix localstorage saved user being overwritten on every page load
* Market page: Show no right panel while user loading
* Don't flash sign in button if user is loading
* election map coloring
* market group modal scroll fix (#1009)
* midterms: posititoning, make mobile friendly
* Un-daisy share buttons (#1010)
* Make embed and challenge buttons non-daisyui
* Allow link Buttons. Change tweet, dupe buttons.
* lint
* don't insert extra lines when upload photos
* Map fixes (#1011)
* usa map: fix sizing
* useSetIframeBackbroundColor
* preload contracts
* seo
* remove hook
* turn off sprig on dev
* Render timestamp only on client to prevent error of server not matching client
* Make sized container have default height so graph doesn't jump
* midterms: use null in static props
* Create common card component (#1012)
* Create common card component
* lint
* add key prop to pills
* redirect to /home after login
* create market: use transaction
* card: reduce border size
* Update groupContracts in db trigger
* Default sort to best
* Save comment sort per user rather than per contract
* Refactor Pinned Items into a reusable component
* Revert "create market: use transaction"
This reverts commit e1f24f24a9
.
* Mark @v with a (Bot) label
* fix padding on daily movers
* fix type errors
* Wrap sprig init in check for window
* unindex date-docs from search engines
* Auto-prettification
* compute elasticity
* change dpm elasticity
* Fix google lighthouse issues (#1013)
* don't hide free response panel on open resolve
* liquidity sort
* Limit order trade log: '/' to 'of'. Remove 'of' in 'of YES'.
* Date doc: Toggle to disable creating a prediction market
* Listen for date doc changes
* Fix merge error
* Don't cancel all a users limit orders if they go negative
Co-authored-by: ingawei <46611122+ingawei@users.noreply.github.com>
Co-authored-by: mantikoros <sgrugett@gmail.com>
Co-authored-by: Sinclair Chen <abc.sinclair@gmail.com>
Co-authored-by: mantikoros <95266179+mantikoros@users.noreply.github.com>
Co-authored-by: Ian Philips <iansphilips@gmail.com>
Co-authored-by: Pico2x <pico2x@gmail.com>
Co-authored-by: Austin Chen <akrolsmir@gmail.com>
Co-authored-by: sipec <sipec@users.noreply.github.com>
316 lines
8.3 KiB
TypeScript
316 lines
8.3 KiB
TypeScript
import { sum, groupBy, mapValues, sumBy } from 'lodash'
|
|
import { LimitBet } from './bet'
|
|
|
|
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, PLATFORM_FEE } from './fees'
|
|
import { LiquidityProvision } from './liquidity-provision'
|
|
import { computeFills } from './new-bet'
|
|
import { binarySearch } from './util/algos'
|
|
import { addObjects } from './util/object'
|
|
|
|
export type CpmmState = {
|
|
pool: { [outcome: string]: number }
|
|
p: number
|
|
}
|
|
|
|
export function getCpmmProbability(
|
|
pool: { [outcome: string]: number },
|
|
p: number
|
|
) {
|
|
const { YES, NO } = pool
|
|
return (p * NO) / ((1 - p) * YES + p * NO)
|
|
}
|
|
|
|
export function getCpmmProbabilityAfterBetBeforeFees(
|
|
state: CpmmState,
|
|
outcome: string,
|
|
bet: number
|
|
) {
|
|
const { pool, p } = state
|
|
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(
|
|
state: CpmmState,
|
|
outcome: string,
|
|
bet: number
|
|
) {
|
|
const { newPool } = calculateCpmmPurchase(state, bet, outcome)
|
|
const p = getCpmmProbability(newPool, state.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 getCpmmFees(state: CpmmState, bet: number, outcome: string) {
|
|
const prob = getCpmmProbabilityAfterBetBeforeFees(state, 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, totalFees, fees }
|
|
}
|
|
|
|
export function calculateCpmmSharesAfterFee(
|
|
state: CpmmState,
|
|
bet: number,
|
|
outcome: string
|
|
) {
|
|
const { pool, p } = state
|
|
const { remainingBet } = getCpmmFees(state, bet, outcome)
|
|
|
|
return calculateCpmmShares(pool, p, remainingBet, outcome)
|
|
}
|
|
|
|
export function calculateCpmmPurchase(
|
|
state: CpmmState,
|
|
bet: number,
|
|
outcome: string
|
|
) {
|
|
const { pool, p } = state
|
|
const { remainingBet, fees } = getCpmmFees(state, bet, outcome)
|
|
|
|
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 }
|
|
}
|
|
|
|
// Note: there might be a closed form solution for this.
|
|
// If so, feel free to switch out this implementation.
|
|
export function calculateCpmmAmountToProb(
|
|
state: CpmmState,
|
|
prob: number,
|
|
outcome: 'YES' | 'NO'
|
|
) {
|
|
if (prob <= 0 || prob >= 1 || isNaN(prob)) return Infinity
|
|
if (outcome === 'NO') prob = 1 - prob
|
|
|
|
// First, find an upper bound that leads to a more extreme probability than prob.
|
|
let maxGuess = 10
|
|
let newProb = 0
|
|
do {
|
|
maxGuess *= 10
|
|
newProb = getCpmmOutcomeProbabilityAfterBet(state, outcome, maxGuess)
|
|
} while (newProb < prob)
|
|
|
|
// Then, binary search for the amount that gets closest to prob.
|
|
const amount = binarySearch(0, maxGuess, (amount) => {
|
|
const newProb = getCpmmOutcomeProbabilityAfterBet(state, outcome, amount)
|
|
return newProb - prob
|
|
})
|
|
|
|
return amount
|
|
}
|
|
|
|
function calculateAmountToBuyShares(
|
|
state: CpmmState,
|
|
shares: number,
|
|
outcome: 'YES' | 'NO',
|
|
unfilledBets: LimitBet[],
|
|
balanceByUserId: { [userId: string]: number }
|
|
) {
|
|
// Search for amount between bounds (0, shares).
|
|
// Min share price is M$0, and max is M$1 each.
|
|
return binarySearch(0, shares, (amount) => {
|
|
const { takers } = computeFills(
|
|
outcome,
|
|
amount,
|
|
state,
|
|
undefined,
|
|
unfilledBets,
|
|
balanceByUserId
|
|
)
|
|
|
|
const totalShares = sumBy(takers, (taker) => taker.shares)
|
|
return totalShares - shares
|
|
})
|
|
}
|
|
|
|
export function calculateCpmmSale(
|
|
state: CpmmState,
|
|
shares: number,
|
|
outcome: 'YES' | 'NO',
|
|
unfilledBets: LimitBet[],
|
|
balanceByUserId: { [userId: string]: number }
|
|
) {
|
|
if (Math.round(shares) < 0) {
|
|
throw new Error('Cannot sell non-positive shares')
|
|
}
|
|
|
|
const oppositeOutcome = outcome === 'YES' ? 'NO' : 'YES'
|
|
const buyAmount = calculateAmountToBuyShares(
|
|
state,
|
|
shares,
|
|
oppositeOutcome,
|
|
unfilledBets,
|
|
balanceByUserId
|
|
)
|
|
|
|
const { cpmmState, makers, takers, totalFees, ordersToCancel } = computeFills(
|
|
oppositeOutcome,
|
|
buyAmount,
|
|
state,
|
|
undefined,
|
|
unfilledBets,
|
|
balanceByUserId
|
|
)
|
|
|
|
// Transform buys of opposite outcome into sells.
|
|
const saleTakers = takers.map((taker) => ({
|
|
...taker,
|
|
// You bought opposite shares, which combine with existing shares, removing them.
|
|
shares: -taker.shares,
|
|
// Opposite shares combine with shares you are selling for M$ of shares.
|
|
// You paid taker.amount for the opposite shares.
|
|
// Take the negative because this is money you gain.
|
|
amount: -(taker.shares - taker.amount),
|
|
isSale: true,
|
|
}))
|
|
|
|
const saleValue = -sumBy(saleTakers, (taker) => taker.amount)
|
|
|
|
return {
|
|
saleValue,
|
|
cpmmState,
|
|
fees: totalFees,
|
|
makers,
|
|
takers: saleTakers,
|
|
ordersToCancel,
|
|
}
|
|
}
|
|
|
|
export function getCpmmProbabilityAfterSale(
|
|
state: CpmmState,
|
|
shares: number,
|
|
outcome: 'YES' | 'NO',
|
|
unfilledBets: LimitBet[],
|
|
balanceByUserId: { [userId: string]: number }
|
|
) {
|
|
const { cpmmState } = calculateCpmmSale(
|
|
state,
|
|
shares,
|
|
outcome,
|
|
unfilledBets,
|
|
balanceByUserId
|
|
)
|
|
return getCpmmProbability(cpmmState.pool, cpmmState.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 }
|
|
}
|
|
|
|
const calculateLiquidityDelta = (p: number) => (l: LiquidityProvision) => {
|
|
const oldLiquidity = getCpmmLiquidity(l.pool, p)
|
|
|
|
const newPool = addObjects(l.pool, { YES: l.amount, NO: l.amount })
|
|
const newLiquidity = getCpmmLiquidity(newPool, p)
|
|
|
|
const liquidity = newLiquidity - oldLiquidity
|
|
return liquidity
|
|
}
|
|
|
|
export function getCpmmLiquidityPoolWeights(
|
|
state: CpmmState,
|
|
liquidities: LiquidityProvision[],
|
|
excludeAntes: boolean
|
|
) {
|
|
const calcLiqudity = calculateLiquidityDelta(state.p)
|
|
const liquidityShares = liquidities.map(calcLiqudity)
|
|
const shareSum = sum(liquidityShares)
|
|
|
|
const weights = liquidityShares.map((shares, i) => ({
|
|
weight: shares / shareSum,
|
|
providerId: liquidities[i].userId,
|
|
}))
|
|
|
|
const includedWeights = excludeAntes
|
|
? weights.filter((_, i) => !liquidities[i].isAnte)
|
|
: weights
|
|
|
|
const userWeights = groupBy(includedWeights, (w) => w.providerId)
|
|
const totalUserWeights = mapValues(userWeights, (userWeight) =>
|
|
sumBy(userWeight, (w) => w.weight)
|
|
)
|
|
return totalUserWeights
|
|
}
|
|
|
|
export function getUserLiquidityShares(
|
|
userId: string,
|
|
state: CpmmState,
|
|
liquidities: LiquidityProvision[],
|
|
excludeAntes: boolean
|
|
) {
|
|
const weights = getCpmmLiquidityPoolWeights(state, liquidities, excludeAntes)
|
|
const userWeight = weights[userId] ?? 0
|
|
|
|
return mapValues(state.pool, (shares) => userWeight * shares)
|
|
}
|