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