2022-10-11 22:32:30 +00:00
|
|
|
import { Dictionary, groupBy, last, partition, sum, sumBy, uniq } from 'lodash'
|
2022-10-10 18:05:17 +00:00
|
|
|
import { calculatePayout, getContractBetMetrics } from './calculate'
|
2022-10-06 21:36:16 +00:00
|
|
|
import { Bet, LimitBet } from './bet'
|
2022-10-10 18:05:17 +00:00
|
|
|
import {
|
|
|
|
Contract,
|
|
|
|
CPMMBinaryContract,
|
|
|
|
CPMMContract,
|
|
|
|
DPMContract,
|
|
|
|
} from './contract'
|
2022-09-02 23:45:42 +00:00
|
|
|
import { PortfolioMetrics, User } from './user'
|
|
|
|
import { DAY_MS } from './util/time'
|
2022-10-06 21:36:16 +00:00
|
|
|
import { getBinaryCpmmBetInfo, getNewMultiBetInfo } from './new-bet'
|
|
|
|
import { getCpmmProbability } from './calculate-cpmm'
|
2022-10-10 18:34:02 +00:00
|
|
|
import { removeUndefinedProps } from './util/object'
|
2022-09-02 23:45:42 +00:00
|
|
|
|
|
|
|
const computeInvestmentValue = (
|
|
|
|
bets: Bet[],
|
|
|
|
contractsDict: { [k: string]: Contract }
|
|
|
|
) => {
|
|
|
|
return sumBy(bets, (bet) => {
|
|
|
|
const contract = contractsDict[bet.contractId]
|
|
|
|
if (!contract || contract.isResolved) return 0
|
|
|
|
if (bet.sale || bet.isSold) return 0
|
|
|
|
|
|
|
|
const payout = calculatePayout(contract, bet, 'MKT')
|
|
|
|
const value = payout - (bet.loanAmount ?? 0)
|
|
|
|
if (isNaN(value)) return 0
|
|
|
|
return value
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-09-26 21:49:06 +00:00
|
|
|
export const computeInvestmentValueCustomProb = (
|
|
|
|
bets: Bet[],
|
|
|
|
contract: Contract,
|
|
|
|
p: number
|
|
|
|
) => {
|
|
|
|
return sumBy(bets, (bet) => {
|
|
|
|
if (!contract || contract.isResolved) return 0
|
|
|
|
if (bet.sale || bet.isSold) return 0
|
|
|
|
const { outcome, shares } = bet
|
|
|
|
|
|
|
|
const betP = outcome === 'YES' ? p : 1 - p
|
|
|
|
|
2022-10-10 18:05:17 +00:00
|
|
|
const value = betP * shares
|
2022-09-26 21:49:06 +00:00
|
|
|
if (isNaN(value)) return 0
|
|
|
|
return value
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-10-06 21:36:16 +00:00
|
|
|
export const computeElasticity = (
|
|
|
|
bets: Bet[],
|
|
|
|
contract: Contract,
|
|
|
|
betAmount = 50
|
|
|
|
) => {
|
|
|
|
const { mechanism, outcomeType } = contract
|
|
|
|
return mechanism === 'cpmm-1' &&
|
|
|
|
(outcomeType === 'BINARY' || outcomeType === 'PSEUDO_NUMERIC')
|
|
|
|
? computeBinaryCpmmElasticity(bets, contract, betAmount)
|
|
|
|
: computeDpmElasticity(contract, betAmount)
|
|
|
|
}
|
|
|
|
|
|
|
|
export const computeBinaryCpmmElasticity = (
|
|
|
|
bets: Bet[],
|
|
|
|
contract: CPMMContract,
|
2022-10-06 21:47:52 +00:00
|
|
|
betAmount: number
|
2022-10-06 21:36:16 +00:00
|
|
|
) => {
|
|
|
|
const limitBets = bets
|
|
|
|
.filter(
|
|
|
|
(b) =>
|
2022-10-07 03:16:49 +00:00
|
|
|
!b.isFilled &&
|
|
|
|
!b.isSold &&
|
|
|
|
!b.isRedemption &&
|
|
|
|
!b.sale &&
|
|
|
|
!b.isCancelled &&
|
|
|
|
b.limitProb !== undefined
|
2022-10-06 21:36:16 +00:00
|
|
|
)
|
2022-10-07 03:16:49 +00:00
|
|
|
.sort((a, b) => a.createdTime - b.createdTime) as LimitBet[]
|
|
|
|
|
|
|
|
const userIds = uniq(limitBets.map((b) => b.userId))
|
|
|
|
// Assume all limit orders are good.
|
|
|
|
const userBalances = Object.fromEntries(
|
|
|
|
userIds.map((id) => [id, Number.MAX_SAFE_INTEGER])
|
|
|
|
)
|
2022-10-06 21:36:16 +00:00
|
|
|
|
|
|
|
const { newPool: poolY, newP: pY } = getBinaryCpmmBetInfo(
|
|
|
|
'YES',
|
|
|
|
betAmount,
|
|
|
|
contract,
|
|
|
|
undefined,
|
2022-10-07 03:16:49 +00:00
|
|
|
limitBets,
|
|
|
|
userBalances
|
2022-10-06 21:36:16 +00:00
|
|
|
)
|
|
|
|
const resultYes = getCpmmProbability(poolY, pY)
|
|
|
|
|
|
|
|
const { newPool: poolN, newP: pN } = getBinaryCpmmBetInfo(
|
|
|
|
'NO',
|
|
|
|
betAmount,
|
|
|
|
contract,
|
|
|
|
undefined,
|
2022-10-07 03:16:49 +00:00
|
|
|
limitBets,
|
|
|
|
userBalances
|
2022-10-06 21:36:16 +00:00
|
|
|
)
|
|
|
|
const resultNo = getCpmmProbability(poolN, pN)
|
|
|
|
|
2022-10-08 17:16:38 +00:00
|
|
|
// handle AMM overflow
|
|
|
|
const safeYes = Number.isFinite(resultYes) ? resultYes : 1
|
|
|
|
const safeNo = Number.isFinite(resultNo) ? resultNo : 0
|
|
|
|
|
|
|
|
return safeYes - safeNo
|
2022-10-06 21:36:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-06 21:47:52 +00:00
|
|
|
export const computeDpmElasticity = (
|
|
|
|
contract: DPMContract,
|
|
|
|
betAmount: number
|
|
|
|
) => {
|
|
|
|
return getNewMultiBetInfo('', 2 * betAmount, contract).newBet.probAfter
|
2022-10-06 21:36:16 +00:00
|
|
|
}
|
|
|
|
|
2022-09-02 23:45:42 +00:00
|
|
|
const computeTotalPool = (userContracts: Contract[], startTime = 0) => {
|
|
|
|
const periodFilteredContracts = userContracts.filter(
|
|
|
|
(contract) => contract.createdTime >= startTime
|
|
|
|
)
|
|
|
|
return sum(
|
|
|
|
periodFilteredContracts.map((contract) => sum(Object.values(contract.pool)))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export const computeVolume = (contractBets: Bet[], since: number) => {
|
|
|
|
return sumBy(contractBets, (b) =>
|
|
|
|
b.createdTime > since && !b.isRedemption ? Math.abs(b.amount) : 0
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-09-03 20:06:41 +00:00
|
|
|
const calculateProbChangeSince = (descendingBets: Bet[], since: number) => {
|
|
|
|
const newestBet = descendingBets[0]
|
|
|
|
if (!newestBet) return 0
|
|
|
|
|
|
|
|
const betBeforeSince = descendingBets.find((b) => b.createdTime < since)
|
|
|
|
|
|
|
|
if (!betBeforeSince) {
|
|
|
|
const oldestBet = last(descendingBets) ?? newestBet
|
|
|
|
return newestBet.probAfter - oldestBet.probBefore
|
|
|
|
}
|
|
|
|
|
|
|
|
return newestBet.probAfter - betBeforeSince.probAfter
|
|
|
|
}
|
|
|
|
|
|
|
|
export const calculateProbChanges = (descendingBets: Bet[]) => {
|
|
|
|
const now = Date.now()
|
|
|
|
const yesterday = now - DAY_MS
|
|
|
|
const weekAgo = now - 7 * DAY_MS
|
|
|
|
const monthAgo = now - 30 * DAY_MS
|
|
|
|
|
|
|
|
return {
|
|
|
|
day: calculateProbChangeSince(descendingBets, yesterday),
|
|
|
|
week: calculateProbChangeSince(descendingBets, weekAgo),
|
|
|
|
month: calculateProbChangeSince(descendingBets, monthAgo),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-02 23:45:42 +00:00
|
|
|
export const calculateCreatorVolume = (userContracts: Contract[]) => {
|
|
|
|
const allTimeCreatorVolume = computeTotalPool(userContracts, 0)
|
|
|
|
const monthlyCreatorVolume = computeTotalPool(
|
|
|
|
userContracts,
|
|
|
|
Date.now() - 30 * DAY_MS
|
|
|
|
)
|
|
|
|
const weeklyCreatorVolume = computeTotalPool(
|
|
|
|
userContracts,
|
|
|
|
Date.now() - 7 * DAY_MS
|
|
|
|
)
|
|
|
|
|
|
|
|
const dailyCreatorVolume = computeTotalPool(
|
|
|
|
userContracts,
|
|
|
|
Date.now() - 1 * DAY_MS
|
|
|
|
)
|
|
|
|
|
|
|
|
return {
|
|
|
|
daily: dailyCreatorVolume,
|
|
|
|
weekly: weeklyCreatorVolume,
|
|
|
|
monthly: monthlyCreatorVolume,
|
|
|
|
allTime: allTimeCreatorVolume,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const calculateNewPortfolioMetrics = (
|
|
|
|
user: User,
|
|
|
|
contractsById: { [k: string]: Contract },
|
|
|
|
currentBets: Bet[]
|
|
|
|
) => {
|
|
|
|
const investmentValue = computeInvestmentValue(currentBets, contractsById)
|
|
|
|
const newPortfolio = {
|
|
|
|
investmentValue: investmentValue,
|
|
|
|
balance: user.balance,
|
|
|
|
totalDeposits: user.totalDeposits,
|
|
|
|
timestamp: Date.now(),
|
|
|
|
userId: user.id,
|
|
|
|
}
|
|
|
|
return newPortfolio
|
|
|
|
}
|
|
|
|
|
|
|
|
const calculateProfitForPeriod = (
|
2022-10-10 20:51:51 +00:00
|
|
|
startingPortfolio: PortfolioMetrics | undefined,
|
2022-09-02 23:45:42 +00:00
|
|
|
currentProfit: number
|
|
|
|
) => {
|
|
|
|
if (startingPortfolio === undefined) {
|
|
|
|
return currentProfit
|
|
|
|
}
|
|
|
|
|
2022-09-08 06:36:34 +00:00
|
|
|
const startingProfit = calculatePortfolioProfit(startingPortfolio)
|
2022-09-02 23:45:42 +00:00
|
|
|
|
|
|
|
return currentProfit - startingProfit
|
|
|
|
}
|
|
|
|
|
2022-09-08 06:36:34 +00:00
|
|
|
export const calculatePortfolioProfit = (portfolio: PortfolioMetrics) => {
|
2022-09-02 23:45:42 +00:00
|
|
|
return portfolio.investmentValue + portfolio.balance - portfolio.totalDeposits
|
|
|
|
}
|
|
|
|
|
|
|
|
export const calculateNewProfit = (
|
2022-10-10 20:51:51 +00:00
|
|
|
portfolioHistory: Record<
|
|
|
|
'current' | 'day' | 'week' | 'month',
|
|
|
|
PortfolioMetrics | undefined
|
|
|
|
>,
|
2022-09-02 23:45:42 +00:00
|
|
|
newPortfolio: PortfolioMetrics
|
|
|
|
) => {
|
2022-09-08 06:36:34 +00:00
|
|
|
const allTimeProfit = calculatePortfolioProfit(newPortfolio)
|
2022-09-02 23:45:42 +00:00
|
|
|
|
|
|
|
const newProfit = {
|
2022-10-10 20:51:51 +00:00
|
|
|
daily: calculateProfitForPeriod(portfolioHistory.day, allTimeProfit),
|
|
|
|
weekly: calculateProfitForPeriod(portfolioHistory.week, allTimeProfit),
|
|
|
|
monthly: calculateProfitForPeriod(portfolioHistory.month, allTimeProfit),
|
2022-09-02 23:45:42 +00:00
|
|
|
allTime: allTimeProfit,
|
|
|
|
}
|
|
|
|
|
|
|
|
return newProfit
|
|
|
|
}
|
2022-10-10 18:05:17 +00:00
|
|
|
|
|
|
|
export const calculateMetricsByContract = (
|
|
|
|
bets: Bet[],
|
|
|
|
contractsById: Dictionary<Contract>
|
|
|
|
) => {
|
|
|
|
const betsByContract = groupBy(bets, (bet) => bet.contractId)
|
|
|
|
const unresolvedContracts = Object.keys(betsByContract)
|
|
|
|
.map((cid) => contractsById[cid])
|
|
|
|
.filter((c) => c && !c.isResolved)
|
|
|
|
|
|
|
|
return unresolvedContracts.map((c) => {
|
|
|
|
const bets = betsByContract[c.id] ?? []
|
|
|
|
const current = getContractBetMetrics(c, bets)
|
|
|
|
|
|
|
|
let periodMetrics
|
|
|
|
if (c.mechanism === 'cpmm-1' && c.outcomeType === 'BINARY') {
|
|
|
|
const periods = ['day', 'week', 'month'] as const
|
|
|
|
periodMetrics = Object.fromEntries(
|
|
|
|
periods.map((period) => [
|
|
|
|
period,
|
|
|
|
calculatePeriodProfit(c, bets, period),
|
|
|
|
])
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-10-10 18:34:02 +00:00
|
|
|
return removeUndefinedProps({
|
2022-10-10 18:05:17 +00:00
|
|
|
contractId: c.id,
|
|
|
|
...current,
|
|
|
|
from: periodMetrics,
|
2022-10-10 18:34:02 +00:00
|
|
|
})
|
2022-10-10 18:05:17 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-10-11 22:32:30 +00:00
|
|
|
export type ContractMetrics = ReturnType<
|
|
|
|
typeof calculateMetricsByContract
|
|
|
|
>[number]
|
2022-10-11 05:32:55 +00:00
|
|
|
|
2022-10-10 18:05:17 +00:00
|
|
|
const calculatePeriodProfit = (
|
|
|
|
contract: CPMMBinaryContract,
|
|
|
|
bets: Bet[],
|
|
|
|
period: 'day' | 'week' | 'month'
|
|
|
|
) => {
|
|
|
|
const days = period === 'day' ? 1 : period === 'week' ? 7 : 30
|
|
|
|
const fromTime = Date.now() - days * DAY_MS
|
2022-10-11 22:32:30 +00:00
|
|
|
const [previousBets, recentBets] = partition(
|
|
|
|
bets,
|
|
|
|
(b) => b.createdTime < fromTime
|
|
|
|
)
|
2022-10-10 18:05:17 +00:00
|
|
|
|
|
|
|
const prevProb = contract.prob - contract.probChanges[period]
|
|
|
|
const prob = contract.resolutionProbability
|
|
|
|
? contract.resolutionProbability
|
|
|
|
: contract.prob
|
|
|
|
|
|
|
|
const previousBetsValue = computeInvestmentValueCustomProb(
|
|
|
|
previousBets,
|
|
|
|
contract,
|
|
|
|
prevProb
|
|
|
|
)
|
|
|
|
const currentBetsValue = computeInvestmentValueCustomProb(
|
|
|
|
previousBets,
|
|
|
|
contract,
|
|
|
|
prob
|
|
|
|
)
|
2022-10-11 22:32:30 +00:00
|
|
|
|
|
|
|
const { profit: recentProfit, invested: recentInvested } =
|
|
|
|
getContractBetMetrics(contract, recentBets)
|
|
|
|
|
|
|
|
const profit = currentBetsValue - previousBetsValue + recentProfit
|
|
|
|
const invested = previousBetsValue + recentInvested
|
|
|
|
const profitPercent = invested === 0 ? 0 : 100 * (profit / invested)
|
2022-10-10 18:05:17 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
profit,
|
|
|
|
profitPercent,
|
2022-10-11 22:32:30 +00:00
|
|
|
invested,
|
2022-10-10 18:05:17 +00:00
|
|
|
prevValue: previousBetsValue,
|
|
|
|
value: currentBetsValue,
|
|
|
|
}
|
|
|
|
}
|