Skip to content

Commit

Permalink
Fix daily changed profit & optimize scheduled profit periods and hist…
Browse files Browse the repository at this point in the history
…ories
  • Loading branch information
IanPhilips committed Nov 8, 2024
1 parent 314245d commit c6e6e70
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 285 deletions.
81 changes: 81 additions & 0 deletions backend/api/src/get-daily-changed-metrics-and-contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { APIHandler } from './helpers/endpoint'
import { updateUserMetricPeriods } from 'shared/update-user-metric-periods'
import { DAY_MS } from 'common/util/time'
import { getUnresolvedStatsForToken } from 'shared/update-user-portfolio-histories-core'
import { sortBy, uniqBy } from 'lodash'
import { MarketContract } from 'common/contract'

export const getDailyChangedMetricsAndContracts: APIHandler<
'get-daily-changed-metrics-and-contracts'
> = async (props, auth) => {
const { limit } = props
const userId = auth.uid

const since = Date.now() - DAY_MS
// First update the user's metrics
const { metricsByUser, contractsById } = await updateUserMetricPeriods(
[userId],
since,
true
)
const manaStats = getUnresolvedStatsForToken(
'MANA',
metricsByUser[userId],
contractsById
)
const cashStats = getUnresolvedStatsForToken(
'CASH',
metricsByUser[userId],
contractsById
)
const manaMetrics = metricsByUser[userId].filter(
(m) => contractsById[m.contractId]?.token === 'MANA'
)
const cashMetrics = metricsByUser[userId].filter(
(m) => contractsById[m.contractId]?.token === 'CASH'
)
const topManaMetrics = sortBy(
manaMetrics,
(m) => -(m.from?.day.profit ?? 0)
).slice(0, limit / 2)
const bottomManaMetrics = sortBy(
manaMetrics,
(m) => m.from?.day.profit ?? 0
).slice(0, limit / 2)
const topCashMetrics = sortBy(
cashMetrics,
(m) => -(m.from?.day.profit ?? 0)
).slice(0, limit / 2)
const bottomCashMetrics = sortBy(
cashMetrics,
(m) => m.from?.day.profit ?? 0
).slice(0, limit / 2)
const uniqueContractIds = uniqBy(
[
...topManaMetrics,
...bottomManaMetrics,
...topCashMetrics,
...bottomCashMetrics,
],
'contractId'
).map((m) => m.contractId)
const contracts = Object.values(contractsById).filter((c) =>
uniqueContractIds.includes(c.id)
) as MarketContract[]

return {
manaMetrics: uniqBy(
topManaMetrics.concat(bottomManaMetrics),
(m) => m.contractId
),
cashMetrics: uniqBy(
topCashMetrics.concat(bottomCashMetrics),
(m) => m.contractId
),
contracts,
manaProfit: manaStats.dailyProfit,
cashProfit: cashStats.dailyProfit,
manaInvestmentValue: manaStats.value,
cashInvestmentValue: cashStats.value,
}
}
58 changes: 0 additions & 58 deletions backend/api/src/get-user-contract-metrics-with-contracts-direct.ts

This file was deleted.

2 changes: 1 addition & 1 deletion backend/api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ import { refreshAllClients } from './refresh-all-clients'
import { getLeaderboard } from './get-leaderboard'
import { toggleSystemTradingStatus } from './toggle-system-status'
import { completeCashoutRequest } from './gidx/complete-cashout-request'
import { getDailyChangedMetricsAndContracts } from './get-user-contract-metrics-with-contracts-direct'
import { getDailyChangedMetricsAndContracts } from './get-daily-changed-metrics-and-contracts'

// we define the handlers in this object in order to typecheck that every API has a handler
export const handlers: { [k in APIPath]: APIHandler<k> } = {
Expand Down
40 changes: 23 additions & 17 deletions backend/scripts/recalculate-user-contract-metrics-from-bets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,34 @@ import { chunk } from 'lodash'
import { SupabaseDirectClient } from 'shared/supabase/init'
import { updateUserMetricPeriods } from 'shared/update-user-metric-periods'
import { updateUserMetricsWithBets } from 'shared/update-user-metrics-with-bets'
import { updateUserPortfolioHistoriesCore } from 'shared/update-user-portfolio-histories-core'

const chunkSize = 10
const FIX_PERIODS = true
const FIX_PERIODS = false
const UPDATE_PORTFOLIO_HISTORIES = true
if (require.main === module) {
runScript(async ({ pg }) => {
// const allUserIds = [['AJwLWoo3xue32XIiAVrL5SyR1WB2', 0]] as [
// string,
// number
// ][]
if (FIX_PERIODS) {
await fixUserPeriods(pg)
return
}
const allUserIds = [['AJwLWoo3xue32XIiAVrL5SyR1WB2', 0]] as [
string,
number
][]
// const startTime = new Date(0).toISOString()
// const allUserIds = await pg.map(
// `
// select distinct users.id, users.created_time from users
// join contract_bets cb on users.id = cb.user_id
// where users.created_time > $1
// -- and cb.created_time > now () - interval '2 week'
// order by users.created_time
// `,
// [startTime],
// (row) => [row.id, row.created_time]
// )

const startTime = new Date(0).toISOString()
const allUserIds = await pg.map(
`
select distinct users.id, users.created_time from users
join contract_bets cb on users.id = cb.user_id
where users.created_time > $1
-- and cb.created_time > now () - interval '2 week'
order by users.created_time
`,
[startTime],
(row) => [row.id, row.created_time]
)
console.log('Total users:', allUserIds.length)
const chunks = chunk(allUserIds, chunkSize)
let total = 0
Expand All @@ -40,6 +42,10 @@ if (require.main === module) {
)
console.log('last created time:', userIds[userIds.length - 1][1])
}

if (UPDATE_PORTFOLIO_HISTORIES) {
await updateUserPortfolioHistoriesCore(allUserIds.map((u) => u[0]))
}
})
}

Expand Down
52 changes: 24 additions & 28 deletions backend/shared/src/update-user-metric-periods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
SupabaseDirectClient,
} from 'shared/supabase/init'
import { contractColumnsToSelect, isProd, log } from 'shared/utils'
import { chunk, groupBy, sortBy, sumBy, uniq } from 'lodash'
import { CPMMMultiContract } from 'common/contract'
import { chunk, groupBy, sortBy, sumBy, uniq, uniqBy } from 'lodash'
import { Contract, CPMMMultiContract } from 'common/contract'
import {
calculateMetricsByContractAndAnswer,
isEmptyMetric,
Expand All @@ -20,7 +20,8 @@ import { convertAnswer, convertContract } from 'common/supabase/contracts'
const CHUNK_SIZE = isProd() ? 400 : 10
export async function updateUserMetricPeriods(
userIds?: string[],
since?: number
since?: number,
skipUpdates?: boolean
) {
log('Starting user period metrics update')
const useSince = since !== undefined
Expand Down Expand Up @@ -76,13 +77,8 @@ export async function updateUserMetricPeriods(

log(`Loaded ${allActiveUserIds.length} active users.`)
const chunks = chunk(allActiveUserIds, CHUNK_SIZE)
const statsByUser: Record<
string,
{
profit: number
value: number
}
> = {}
const metricsByUser: Record<string, ContractMetric[]> = {}
const contractsById: Record<string, Contract> = {}
for (const activeUserIds of chunks) {
log('Loading bets for', activeUserIds)
const metricRelevantBets = await getUnresolvedOrRecentlyResolvedBets(
Expand All @@ -98,6 +94,7 @@ export async function updateUserMetricPeriods(
)

const allBets = Object.values(metricRelevantBets).flat()
const contractIds = uniq(allBets.map((b) => b.contractId))
log('Loading contracts, answers, users, and current contract metrics...')
// We could cache the contracts and answers to query for less data
const results = await pg.multi(
Expand All @@ -109,9 +106,9 @@ export async function updateUserMetricPeriods(
select id, data from user_contract_metrics
where user_id in ($2:list)
and contract_id in ($1:list);
and contract_id in ($3:list);
`,
[uniq(allBets.map((b) => b.contractId)), activeUserIds]
[contractIds.filter((c) => !contractsById[c]), activeUserIds, contractIds]
)
const contracts = results[0].map(convertContract)
const answers = results[1].map(convertAnswer)
Expand All @@ -126,13 +123,14 @@ export async function updateUserMetricPeriods(
and ${currentContractMetrics.length} contract metrics.`
)

const contractsById = Object.fromEntries(contracts.map((c) => [c.id, c]))

for (const [contractId, answers] of Object.entries(answersByContractId)) {
// Denormalize answers onto the contract.
// eslint-disable-next-line no-extra-semi
;(contractsById[contractId] as CPMMMultiContract).answers = answers
}
contracts.forEach((c) => {
const answers = answersByContractId[c.id] ?? []
if (c.mechanism === 'cpmm-multi-1') {
// eslint-disable-next-line no-extra-semi
;(c as CPMMMultiContract).answers = answers
}
contractsById[c.id] = c
})

const currentMetricsByUserId = groupBy(
currentContractMetrics,
Expand All @@ -155,13 +153,11 @@ export async function updateUserMetricPeriods(
userId,
answersByContractId
).flat()
const nullFreshMetrics = freshMetrics.filter((m) => !m.answerId)
statsByUser[userId] = {
profit: sumBy(nullFreshMetrics, (m) => m.from?.day?.profit ?? 0),
value: sumBy(nullFreshMetrics, (m) => m.payout ?? 0),
}

const currentMetricsForUser = currentMetricsByUserId[userId] ?? []
metricsByUser[userId] = uniqBy(
[...freshMetrics, ...currentMetricsForUser],
(m) => m.contractId + m.answerId
)
contractMetricUpdates.push(
...filterDefined(
freshMetrics.map((freshMetric) => {
Expand Down Expand Up @@ -200,8 +196,8 @@ export async function updateUserMetricPeriods(
}
log(`Computed ${contractMetricUpdates.length} metric updates.`)

log('Writing updates')
if (contractMetricUpdates.length > 0) {
if (contractMetricUpdates.length > 0 && !skipUpdates) {
log('Writing updates')
await bulkUpdateData(pg, 'user_contract_metrics', contractMetricUpdates)
.catch((e) => log.error('Error upserting contract metrics', e))
.then(() =>
Expand All @@ -214,7 +210,7 @@ export async function updateUserMetricPeriods(
}
}
log('Finished updating user period metrics')
return statsByUser
return { metricsByUser, contractsById }
}

const getUnresolvedOrRecentlyResolvedBets = async (
Expand Down
Loading

0 comments on commit c6e6e70

Please sign in to comment.