Implement most of caching metrics per user per contract

This commit is contained in:
James Grugett 2022-10-10 11:21:49 -05:00
parent 4831c25ce0
commit 0185fe3b0b
2 changed files with 116 additions and 41 deletions

View File

@ -1,7 +1,21 @@
import { last, sortBy, sum, sumBy, uniq } from 'lodash'
import { calculatePayout } from './calculate'
import {
Dictionary,
groupBy,
keyBy,
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 +49,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 +259,67 @@ 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') {
periodMetrics = keyBy(['day', 'week', 'month'] as const, (period) => {
return 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,
}
}

View File

@ -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()
@ -160,6 +162,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 (
@ -190,6 +198,7 @@ export async function updateMetricsCore() {
newProfit,
didPortfolioChange,
newFractionResolvedCorrectly,
metricsByContract,
}
})
@ -205,48 +214,37 @@ 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
.filter((u) => !isEmpty(u.subcollectionUpdates.fields))
.map((u) => u.subcollectionUpdates),
'set'
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, portfolioHistoryUpdates, 'set')
log(`Updated metrics for ${users.length} users.`)
try {