Store each user's contract bet metrics (#1017)
* Implement most of caching metrics per user per contract * Small group updates refactor * Write contract-metrics subcollection * Fix type error
This commit is contained in:
parent
b8ef272784
commit
f6fd703005
|
@ -1,7 +1,12 @@
|
||||||
import { last, sortBy, sum, sumBy, uniq } from 'lodash'
|
import { Dictionary, groupBy, last, sortBy, sum, sumBy, uniq } from 'lodash'
|
||||||
import { calculatePayout } from './calculate'
|
import { calculatePayout, getContractBetMetrics } from './calculate'
|
||||||
import { Bet, LimitBet } from './bet'
|
import { Bet, LimitBet } from './bet'
|
||||||
import { Contract, CPMMContract, DPMContract } from './contract'
|
import {
|
||||||
|
Contract,
|
||||||
|
CPMMBinaryContract,
|
||||||
|
CPMMContract,
|
||||||
|
DPMContract,
|
||||||
|
} from './contract'
|
||||||
import { PortfolioMetrics, User } from './user'
|
import { PortfolioMetrics, User } from './user'
|
||||||
import { DAY_MS } from './util/time'
|
import { DAY_MS } from './util/time'
|
||||||
import { getBinaryCpmmBetInfo, getNewMultiBetInfo } from './new-bet'
|
import { getBinaryCpmmBetInfo, getNewMultiBetInfo } from './new-bet'
|
||||||
|
@ -35,8 +40,7 @@ export const computeInvestmentValueCustomProb = (
|
||||||
|
|
||||||
const betP = outcome === 'YES' ? p : 1 - p
|
const betP = outcome === 'YES' ? p : 1 - p
|
||||||
|
|
||||||
const payout = betP * shares
|
const value = betP * shares
|
||||||
const value = payout - (bet.loanAmount ?? 0)
|
|
||||||
if (isNaN(value)) return 0
|
if (isNaN(value)) return 0
|
||||||
return value
|
return value
|
||||||
})
|
})
|
||||||
|
@ -246,3 +250,71 @@ export const calculateNewProfit = (
|
||||||
|
|
||||||
return newProfit
|
return newProfit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
contractId: c.id,
|
||||||
|
...current,
|
||||||
|
from: periodMetrics,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
const previousBets = bets.filter((b) => b.createdTime < fromTime)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
const profit = currentBetsValue - previousBetsValue
|
||||||
|
const profitPercent =
|
||||||
|
previousBetsValue === 0 ? 0 : 100 * (profit / previousBetsValue)
|
||||||
|
|
||||||
|
return {
|
||||||
|
profit,
|
||||||
|
profitPercent,
|
||||||
|
prevValue: previousBetsValue,
|
||||||
|
value: currentBetsValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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 { groupBy, isEmpty, keyBy, last, sortBy } from 'lodash'
|
import { groupBy, keyBy, last, sortBy } from 'lodash'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
|
|
||||||
import { getValues, log, logMemory, writeAsync } from './utils'
|
import { getValues, log, logMemory, writeAsync } from './utils'
|
||||||
|
@ -15,6 +15,7 @@ import {
|
||||||
calculateNewPortfolioMetrics,
|
calculateNewPortfolioMetrics,
|
||||||
calculateNewProfit,
|
calculateNewProfit,
|
||||||
calculateProbChanges,
|
calculateProbChanges,
|
||||||
|
calculateMetricsByContract,
|
||||||
computeElasticity,
|
computeElasticity,
|
||||||
computeVolume,
|
computeVolume,
|
||||||
} from '../../common/calculate-metrics'
|
} from '../../common/calculate-metrics'
|
||||||
|
@ -23,6 +24,7 @@ import { Group } from '../../common/group'
|
||||||
import { batchedWaitAll } from '../../common/util/promise'
|
import { batchedWaitAll } from '../../common/util/promise'
|
||||||
import { newEndpointNoAuth } from './api'
|
import { newEndpointNoAuth } from './api'
|
||||||
import { getFunctionUrl } from '../../common/api'
|
import { getFunctionUrl } from '../../common/api'
|
||||||
|
import { filterDefined } from '../../common/util/array'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
export const scheduleUpdateMetrics = functions.pubsub
|
export const scheduleUpdateMetrics = functions.pubsub
|
||||||
|
@ -159,6 +161,12 @@ export async function updateMetricsCore() {
|
||||||
lastPortfolio.investmentValue !== newPortfolio.investmentValue
|
lastPortfolio.investmentValue !== newPortfolio.investmentValue
|
||||||
|
|
||||||
const newProfit = calculateNewProfit(portfolioHistory, newPortfolio)
|
const newProfit = calculateNewProfit(portfolioHistory, newPortfolio)
|
||||||
|
|
||||||
|
const metricsByContract = calculateMetricsByContract(
|
||||||
|
currentBets,
|
||||||
|
contractsById
|
||||||
|
)
|
||||||
|
|
||||||
const contractRatios = userContracts
|
const contractRatios = userContracts
|
||||||
.map((contract) => {
|
.map((contract) => {
|
||||||
if (
|
if (
|
||||||
|
@ -189,6 +197,7 @@ export async function updateMetricsCore() {
|
||||||
newProfit,
|
newProfit,
|
||||||
didPortfolioChange,
|
didPortfolioChange,
|
||||||
newFractionResolvedCorrectly,
|
newFractionResolvedCorrectly,
|
||||||
|
metricsByContract,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -204,63 +213,61 @@ export async function updateMetricsCore() {
|
||||||
const nextLoanByUser = keyBy(userPayouts, (payout) => payout.user.id)
|
const nextLoanByUser = keyBy(userPayouts, (payout) => payout.user.id)
|
||||||
|
|
||||||
const userUpdates = userMetrics.map(
|
const userUpdates = userMetrics.map(
|
||||||
({
|
({ user, newCreatorVolume, newProfit, newFractionResolvedCorrectly }) => {
|
||||||
user,
|
|
||||||
newCreatorVolume,
|
|
||||||
newPortfolio,
|
|
||||||
newProfit,
|
|
||||||
didPortfolioChange,
|
|
||||||
newFractionResolvedCorrectly,
|
|
||||||
}) => {
|
|
||||||
const nextLoanCached = nextLoanByUser[user.id]?.payout ?? 0
|
const nextLoanCached = nextLoanByUser[user.id]?.payout ?? 0
|
||||||
return {
|
return {
|
||||||
fieldUpdates: {
|
doc: firestore.collection('users').doc(user.id),
|
||||||
doc: firestore.collection('users').doc(user.id),
|
fields: {
|
||||||
fields: {
|
creatorVolumeCached: newCreatorVolume,
|
||||||
creatorVolumeCached: newCreatorVolume,
|
profitCached: newProfit,
|
||||||
profitCached: newProfit,
|
nextLoanCached,
|
||||||
nextLoanCached,
|
fractionResolvedCorrectly: newFractionResolvedCorrectly,
|
||||||
fractionResolvedCorrectly: newFractionResolvedCorrectly,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
subcollectionUpdates: {
|
|
||||||
doc: firestore
|
|
||||||
.collection('users')
|
|
||||||
.doc(user.id)
|
|
||||||
.collection('portfolioHistory')
|
|
||||||
.doc(),
|
|
||||||
fields: didPortfolioChange ? newPortfolio : {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await writeAsync(
|
await writeAsync(firestore, userUpdates)
|
||||||
firestore,
|
|
||||||
userUpdates.map((u) => u.fieldUpdates)
|
const portfolioHistoryUpdates = filterDefined(
|
||||||
|
userMetrics.map(({ user, newPortfolio, didPortfolioChange }) => {
|
||||||
|
return didPortfolioChange
|
||||||
|
? {
|
||||||
|
doc: firestore
|
||||||
|
.collection('users')
|
||||||
|
.doc(user.id)
|
||||||
|
.collection('portfolioHistory')
|
||||||
|
.doc(),
|
||||||
|
fields: newPortfolio,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
})
|
||||||
)
|
)
|
||||||
await writeAsync(
|
await writeAsync(firestore, portfolioHistoryUpdates, 'set')
|
||||||
firestore,
|
|
||||||
userUpdates
|
const contractMetricsUpdates = userMetrics.flatMap(
|
||||||
.filter((u) => !isEmpty(u.subcollectionUpdates.fields))
|
({ user, metricsByContract }) => {
|
||||||
.map((u) => u.subcollectionUpdates),
|
const collection = firestore
|
||||||
'set'
|
.collection('users')
|
||||||
|
.doc(user.id)
|
||||||
|
.collection('contract-metrics')
|
||||||
|
return metricsByContract.map((metrics) => ({
|
||||||
|
doc: collection.doc(metrics.contractId),
|
||||||
|
fields: metrics,
|
||||||
|
}))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await writeAsync(firestore, contractMetricsUpdates, 'set')
|
||||||
|
|
||||||
log(`Updated metrics for ${users.length} users.`)
|
log(`Updated metrics for ${users.length} users.`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const groupUpdates = groups.map((group, index) => {
|
const groupUpdates = groups.map((group, index) => {
|
||||||
const groupContractIds = contractsByGroup[index] as GroupContractDoc[]
|
const groupContractIds = contractsByGroup[index] as GroupContractDoc[]
|
||||||
const groupContracts = groupContractIds
|
const groupContracts = filterDefined(
|
||||||
.map((e) => contractsById[e.contractId])
|
groupContractIds.map((e) => contractsById[e.contractId])
|
||||||
.filter((e) => e !== undefined) as Contract[]
|
)
|
||||||
const bets = groupContracts.map((e) => {
|
const bets = groupContracts.map((e) => betsByContract[e.id] ?? [])
|
||||||
if (e != null && e.id in betsByContract) {
|
|
||||||
return betsByContract[e.id] ?? []
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const creatorScores = scoreCreators(groupContracts)
|
const creatorScores = scoreCreators(groupContracts)
|
||||||
const traderScores = scoreTraders(groupContracts, bets)
|
const traderScores = scoreTraders(groupContracts, bets)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user