Daily profit 💰 (#1023)
* Daily profit client side * Filter out those where profit rounds to 0 * Tabs to spaces
This commit is contained in:
parent
0ec15ff2f8
commit
70b2b14f80
|
@ -266,6 +266,8 @@ export const calculateMetricsByContract = (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ContractMetrics = ReturnType<typeof calculateMetricsByContract>[number]
|
||||||
|
|
||||||
const calculatePeriodProfit = (
|
const calculatePeriodProfit = (
|
||||||
contract: CPMMBinaryContract,
|
contract: CPMMBinaryContract,
|
||||||
bets: Bet[],
|
bets: Bet[],
|
||||||
|
|
|
@ -178,6 +178,8 @@ function getDpmInvested(yourBets: Bet[]) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ContractBetMetrics = ReturnType<typeof getContractBetMetrics>
|
||||||
|
|
||||||
export function getContractBetMetrics(contract: Contract, yourBets: Bet[]) {
|
export function getContractBetMetrics(contract: Contract, yourBets: Bet[]) {
|
||||||
const { resolution } = contract
|
const { resolution } = contract
|
||||||
const isCpmm = contract.mechanism === 'cpmm-1'
|
const isCpmm = contract.mechanism === 'cpmm-1'
|
||||||
|
|
|
@ -44,6 +44,10 @@ service cloud.firestore {
|
||||||
allow read;
|
allow read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match /{somePath=**}/contract-metrics/{contractId} {
|
||||||
|
allow read;
|
||||||
|
}
|
||||||
|
|
||||||
match /{somePath=**}/challenges/{challengeId}{
|
match /{somePath=**}/challenges/{challengeId}{
|
||||||
allow read;
|
allow read;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { formatLargeNumber, formatPercent } from 'common/util/format'
|
import {
|
||||||
|
formatLargeNumber,
|
||||||
|
formatMoney,
|
||||||
|
formatPercent,
|
||||||
|
} from 'common/util/format'
|
||||||
import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts'
|
import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import {
|
import {
|
||||||
|
@ -17,6 +21,7 @@ import {
|
||||||
import {
|
import {
|
||||||
AnswerLabel,
|
AnswerLabel,
|
||||||
BinaryContractOutcomeLabel,
|
BinaryContractOutcomeLabel,
|
||||||
|
BinaryOutcomeLabel,
|
||||||
CancelLabel,
|
CancelLabel,
|
||||||
FreeResponseOutcomeLabel,
|
FreeResponseOutcomeLabel,
|
||||||
} from '../outcome-label'
|
} from '../outcome-label'
|
||||||
|
@ -29,7 +34,7 @@ import { AvatarDetails, MiscDetails, ShowTime } from './contract-details'
|
||||||
import { getExpectedValue, getValueFromBucket } from 'common/calculate-dpm'
|
import { getExpectedValue, getValueFromBucket } from 'common/calculate-dpm'
|
||||||
import { getColor, ProbBar, QuickBet } from './quick-bet'
|
import { getColor, ProbBar, QuickBet } from './quick-bet'
|
||||||
import { useContractWithPreload } from 'web/hooks/use-contract'
|
import { useContractWithPreload } from 'web/hooks/use-contract'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser, useUserContractMetrics } from 'web/hooks/use-user'
|
||||||
import { track } from '@amplitude/analytics-browser'
|
import { track } from '@amplitude/analytics-browser'
|
||||||
import { trackCallback } from 'web/lib/service/analytics'
|
import { trackCallback } from 'web/lib/service/analytics'
|
||||||
import { getMappedValue } from 'common/pseudo-numeric'
|
import { getMappedValue } from 'common/pseudo-numeric'
|
||||||
|
@ -37,6 +42,7 @@ import { Tooltip } from '../tooltip'
|
||||||
import { SiteLink } from '../site-link'
|
import { SiteLink } from '../site-link'
|
||||||
import { ProbChange } from './prob-change-table'
|
import { ProbChange } from './prob-change-table'
|
||||||
import { Card } from '../card'
|
import { Card } from '../card'
|
||||||
|
import { ProfitBadgeMana } from '../profit-badge'
|
||||||
|
|
||||||
export function ContractCard(props: {
|
export function ContractCard(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -390,11 +396,18 @@ export function PseudoNumericResolutionOrExpectation(props: {
|
||||||
export function ContractCardProbChange(props: {
|
export function ContractCardProbChange(props: {
|
||||||
contract: CPMMContract
|
contract: CPMMContract
|
||||||
noLinkAvatar?: boolean
|
noLinkAvatar?: boolean
|
||||||
|
showPosition?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { noLinkAvatar, className } = props
|
const { noLinkAvatar, showPosition, className } = props
|
||||||
const contract = useContractWithPreload(props.contract) as CPMMBinaryContract
|
const contract = useContractWithPreload(props.contract) as CPMMBinaryContract
|
||||||
|
|
||||||
|
const user = useUser()
|
||||||
|
const metrics = useUserContractMetrics(user?.id, contract.id)
|
||||||
|
const dayMetrics = metrics && metrics.from && metrics.from.day
|
||||||
|
const outcome =
|
||||||
|
metrics && metrics.hasShares && metrics.totalShares.YES ? 'YES' : 'NO'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={clsx(className, 'mb-4')}>
|
<Card className={clsx(className, 'mb-4')}>
|
||||||
<AvatarDetails
|
<AvatarDetails
|
||||||
|
@ -411,6 +424,28 @@ export function ContractCardProbChange(props: {
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
<ProbChange className="py-2 pr-4" contract={contract} />
|
<ProbChange className="py-2 pr-4" contract={contract} />
|
||||||
</Row>
|
</Row>
|
||||||
|
{showPosition && metrics && (
|
||||||
|
<Row
|
||||||
|
className={clsx(
|
||||||
|
'items-center justify-between gap-4 pl-6 pr-4 pb-2 text-sm'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Row className="gap-1">
|
||||||
|
<div className="text-gray-500">Position</div>
|
||||||
|
{formatMoney(metrics.payout)}
|
||||||
|
<BinaryOutcomeLabel outcome={outcome} />
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{dayMetrics && (
|
||||||
|
<>
|
||||||
|
<Row className="items-center">
|
||||||
|
<div className="mr-1 text-gray-500">Daily profit</div>
|
||||||
|
<ProfitBadgeMana amount={dayMetrics.profit} />
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ export function ContractsGrid(props: {
|
||||||
<ContractCardProbChange
|
<ContractCardProbChange
|
||||||
key={contract.id}
|
key={contract.id}
|
||||||
contract={contract as CPMMBinaryContract}
|
contract={contract as CPMMBinaryContract}
|
||||||
|
showPosition
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ContractCard
|
<ContractCard
|
||||||
|
|
|
@ -1,11 +1,70 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { CPMMContract } from 'common/contract'
|
|
||||||
import { formatPercent } from 'common/util/format'
|
|
||||||
import { sortBy } from 'lodash'
|
import { sortBy } from 'lodash'
|
||||||
|
import { filterDefined } from 'common/util/array'
|
||||||
|
import { ContractMetrics } from 'common/calculate-metrics'
|
||||||
|
import { CPMMBinaryContract, CPMMContract } from 'common/contract'
|
||||||
|
import { formatPercent } from 'common/util/format'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { LoadingIndicator } from '../loading-indicator'
|
import { LoadingIndicator } from '../loading-indicator'
|
||||||
import { ContractCardProbChange } from './contract-card'
|
import { ContractCardProbChange } from './contract-card'
|
||||||
|
|
||||||
|
export function ProfitChangeTable(props: {
|
||||||
|
contracts: CPMMBinaryContract[]
|
||||||
|
metrics: ContractMetrics[]
|
||||||
|
}) {
|
||||||
|
const { contracts, metrics } = props
|
||||||
|
|
||||||
|
const contractProfit = metrics.map(
|
||||||
|
(m) => [m.contractId, m.from?.day.profit ?? 0] as const
|
||||||
|
)
|
||||||
|
|
||||||
|
const positiveProfit = sortBy(
|
||||||
|
contractProfit.filter(([, profit]) => profit > 0),
|
||||||
|
([, profit]) => profit
|
||||||
|
).reverse()
|
||||||
|
const positive = filterDefined(
|
||||||
|
positiveProfit.map(([contractId]) =>
|
||||||
|
contracts.find((c) => c.id === contractId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const negativeProfit = sortBy(
|
||||||
|
contractProfit.filter(([, profit]) => profit < 0),
|
||||||
|
([, profit]) => profit
|
||||||
|
)
|
||||||
|
const negative = filterDefined(
|
||||||
|
negativeProfit.map(([contractId]) =>
|
||||||
|
contracts.find((c) => c.id === contractId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (positive.length === 0 && negative.length === 0)
|
||||||
|
return <div className="px-4 text-gray-500">None</div>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col className="mb-4 w-full gap-4 rounded-lg md:flex-row">
|
||||||
|
<Col className="flex-1">
|
||||||
|
{positive.map((contract) => (
|
||||||
|
<ContractCardProbChange
|
||||||
|
key={contract.id}
|
||||||
|
contract={contract}
|
||||||
|
showPosition
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
<Col className="flex-1">
|
||||||
|
{negative.map((contract) => (
|
||||||
|
<ContractCardProbChange
|
||||||
|
key={contract.id}
|
||||||
|
contract={contract}
|
||||||
|
showPosition
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function ProbChangeTable(props: {
|
export function ProbChangeTable(props: {
|
||||||
changes: CPMMContract[] | undefined
|
changes: CPMMContract[] | undefined
|
||||||
full?: boolean
|
full?: boolean
|
||||||
|
@ -39,12 +98,20 @@ export function ProbChangeTable(props: {
|
||||||
<Col className="mb-4 w-full gap-4 rounded-lg md:flex-row">
|
<Col className="mb-4 w-full gap-4 rounded-lg md:flex-row">
|
||||||
<Col className="flex-1">
|
<Col className="flex-1">
|
||||||
{filteredPositiveChanges.map((contract) => (
|
{filteredPositiveChanges.map((contract) => (
|
||||||
<ContractCardProbChange key={contract.id} contract={contract} />
|
<ContractCardProbChange
|
||||||
|
key={contract.id}
|
||||||
|
contract={contract}
|
||||||
|
showPosition
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Col>
|
</Col>
|
||||||
<Col className="flex-1">
|
<Col className="flex-1">
|
||||||
{filteredNegativeChanges.map((contract) => (
|
{filteredNegativeChanges.map((contract) => (
|
||||||
<ContractCardProbChange key={contract.id} contract={contract} />
|
<ContractCardProbChange
|
||||||
|
key={contract.id}
|
||||||
|
contract={contract}
|
||||||
|
showPosition
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Col>
|
</Col>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
import { ENV_CONFIG } from 'common/envs/constants'
|
||||||
|
|
||||||
export function ProfitBadge(props: {
|
export function ProfitBadge(props: {
|
||||||
profitPercent: number
|
profitPercent: number
|
||||||
|
@ -26,3 +27,24 @@ export function ProfitBadge(props: {
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ProfitBadgeMana(props: { amount: number; className?: string }) {
|
||||||
|
const { amount, className } = props
|
||||||
|
const colors =
|
||||||
|
amount > 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
|
|
||||||
|
const formatted =
|
||||||
|
ENV_CONFIG.moneyMoniker + (amount > 0 ? '+' : '') + amount.toFixed(0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'ml-1 inline-flex items-center rounded-full px-3 py-0.5 text-sm font-medium',
|
||||||
|
colors,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formatted}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
import { useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
import { useFirestoreDocumentData } from '@react-query-firebase/firestore'
|
import {
|
||||||
import { useQueryClient } from 'react-query'
|
useFirestoreDocumentData,
|
||||||
|
useFirestoreQueryData,
|
||||||
|
} from '@react-query-firebase/firestore'
|
||||||
|
import { useQuery, useQueryClient } from 'react-query'
|
||||||
|
|
||||||
import { doc, DocumentData } from 'firebase/firestore'
|
import { doc, DocumentData } from 'firebase/firestore'
|
||||||
import { getUser, User, users } from 'web/lib/firebase/users'
|
import { getUser, User, users } from 'web/lib/firebase/users'
|
||||||
import { AuthContext } from 'web/components/auth-context'
|
import { AuthContext } from 'web/components/auth-context'
|
||||||
|
import { ContractMetrics } from 'common/calculate-metrics'
|
||||||
|
import { getUserContractMetricsQuery } from 'web/lib/firebase/contract-metrics'
|
||||||
|
import { getContractFromId } from 'web/lib/firebase/contracts'
|
||||||
|
import { buildArray, filterDefined } from 'common/util/array'
|
||||||
|
import { CPMMBinaryContract } from 'common/contract'
|
||||||
|
|
||||||
export const useUser = () => {
|
export const useUser = () => {
|
||||||
const authUser = useContext(AuthContext)
|
const authUser = useContext(AuthContext)
|
||||||
|
@ -38,3 +46,45 @@ export const usePrefetchUsers = (userIds: string[]) => {
|
||||||
queryClient.prefetchQuery(['users', userId], () => getUser(userId))
|
queryClient.prefetchQuery(['users', userId], () => getUser(userId))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useUserContractMetricsByProfit = (
|
||||||
|
userId: string,
|
||||||
|
count: number
|
||||||
|
) => {
|
||||||
|
const positiveResult = useFirestoreQueryData<ContractMetrics>(
|
||||||
|
['contract-metrics-descending', userId, count],
|
||||||
|
getUserContractMetricsQuery(userId, count, 'desc')
|
||||||
|
)
|
||||||
|
const negativeResult = useFirestoreQueryData<ContractMetrics>(
|
||||||
|
['contract-metrics-ascending', userId, count],
|
||||||
|
getUserContractMetricsQuery(userId, count, 'asc')
|
||||||
|
)
|
||||||
|
|
||||||
|
const metrics = buildArray(positiveResult.data, negativeResult.data)
|
||||||
|
const contractIds = metrics.map((m) => m.contractId)
|
||||||
|
|
||||||
|
const contractResult = useQuery(['contracts', contractIds], () =>
|
||||||
|
Promise.all(contractIds.map(getContractFromId))
|
||||||
|
)
|
||||||
|
const contracts = contractResult.data
|
||||||
|
|
||||||
|
if (!positiveResult.data || !negativeResult.data || !contracts)
|
||||||
|
return undefined
|
||||||
|
|
||||||
|
const filteredContracts = filterDefined(contracts) as CPMMBinaryContract[]
|
||||||
|
const filteredMetrics = metrics.filter(
|
||||||
|
(m) => m.from && Math.abs(m.from.day.profit) >= 0.5
|
||||||
|
)
|
||||||
|
return { contracts: filteredContracts, metrics: filteredMetrics }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserContractMetrics = (userId = '_', contractId: string) => {
|
||||||
|
const result = useFirestoreDocumentData<DocumentData, ContractMetrics>(
|
||||||
|
['user-contract-metrics', userId, contractId],
|
||||||
|
doc(users, userId, 'contract-metrics', contractId)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (userId === '_') return undefined
|
||||||
|
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
|
23
web/lib/firebase/contract-metrics.ts
Normal file
23
web/lib/firebase/contract-metrics.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { ContractMetrics } from 'common/calculate-metrics'
|
||||||
|
import {
|
||||||
|
query,
|
||||||
|
limit,
|
||||||
|
Query,
|
||||||
|
collection,
|
||||||
|
orderBy,
|
||||||
|
where,
|
||||||
|
} from 'firebase/firestore'
|
||||||
|
import { db } from './init'
|
||||||
|
|
||||||
|
export function getUserContractMetricsQuery(
|
||||||
|
userId: string,
|
||||||
|
count: number,
|
||||||
|
sort: 'asc' | 'desc'
|
||||||
|
) {
|
||||||
|
return query(
|
||||||
|
collection(db, 'users', userId, 'contract-metrics'),
|
||||||
|
where('from.day.profit', sort === 'desc' ? '>' : '<', 0),
|
||||||
|
orderBy('from.day.profit', sort),
|
||||||
|
limit(count)
|
||||||
|
) as Query<ContractMetrics>
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
import { ProbChangeTable } from 'web/components/contract/prob-change-table'
|
import { ProfitChangeTable } from 'web/components/contract/prob-change-table'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
|
import { Row } from 'web/components/layout/row'
|
||||||
|
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { Title } from 'web/components/title'
|
import { Title } from 'web/components/title'
|
||||||
import { useProbChanges } from 'web/hooks/use-prob-changes'
|
|
||||||
import { useTracking } from 'web/hooks/use-tracking'
|
import { useTracking } from 'web/hooks/use-tracking'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser, useUserContractMetricsByProfit } from 'web/hooks/use-user'
|
||||||
|
import { DailyProfit } from './home'
|
||||||
|
|
||||||
export default function DailyMovers() {
|
export default function DailyMovers() {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
@ -13,7 +15,10 @@ export default function DailyMovers() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<Col className="pm:mx-10 gap-4 sm:px-4 sm:pb-4">
|
<Col className="pm:mx-10 gap-4 sm:px-4 sm:pb-4">
|
||||||
<Title className="mx-4 !mb-0 sm:mx-0" text="Daily movers" />
|
<Row className="mt-4 items-start justify-between sm:mt-0">
|
||||||
|
<Title className="mx-4 !mb-0 !mt-0 sm:mx-0" text="Daily movers" />
|
||||||
|
<DailyProfit user={user} />
|
||||||
|
</Row>
|
||||||
{user && <ProbChangesWrapper userId={user.id} />}
|
{user && <ProbChangesWrapper userId={user.id} />}
|
||||||
</Col>
|
</Col>
|
||||||
</Page>
|
</Page>
|
||||||
|
@ -23,9 +28,9 @@ export default function DailyMovers() {
|
||||||
function ProbChangesWrapper(props: { userId: string }) {
|
function ProbChangesWrapper(props: { userId: string }) {
|
||||||
const { userId } = props
|
const { userId } = props
|
||||||
|
|
||||||
const changes = useProbChanges({ bettorId: userId })?.filter(
|
const data = useUserContractMetricsByProfit(userId, 50)
|
||||||
(c) => Math.abs(c.probChanges.day) >= 0.01
|
|
||||||
)
|
|
||||||
|
|
||||||
return <ProbChangeTable changes={changes} full />
|
if (!data) return <LoadingIndicator />
|
||||||
|
|
||||||
|
return <ProfitChangeTable contracts={data.contracts} metrics={data.metrics} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,19 +19,22 @@ import { useSaveReferral } from 'web/hooks/use-save-referral'
|
||||||
import { Sort } from 'web/components/contract-search'
|
import { Sort } from 'web/components/contract-search'
|
||||||
import { Group } from 'common/group'
|
import { Group } from 'common/group'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
import { SiteLink } from 'web/components/site-link'
|
||||||
import { usePrivateUser, useUser } from 'web/hooks/use-user'
|
import {
|
||||||
|
usePrivateUser,
|
||||||
|
useUser,
|
||||||
|
useUserContractMetricsByProfit,
|
||||||
|
} from 'web/hooks/use-user'
|
||||||
import {
|
import {
|
||||||
useMemberGroupsSubscription,
|
useMemberGroupsSubscription,
|
||||||
useTrendingGroups,
|
useTrendingGroups,
|
||||||
} from 'web/hooks/use-group'
|
} from 'web/hooks/use-group'
|
||||||
import { Button } from 'web/components/button'
|
import { Button } from 'web/components/button'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { ProbChangeTable } from 'web/components/contract/prob-change-table'
|
import { ProfitChangeTable } from 'web/components/contract/prob-change-table'
|
||||||
import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups'
|
import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups'
|
||||||
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
|
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { useProbChanges } from 'web/hooks/use-prob-changes'
|
import { ContractMetrics } from 'common/calculate-metrics'
|
||||||
import { calculatePortfolioProfit } from 'common/calculate-metrics'
|
|
||||||
import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal'
|
import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal'
|
||||||
import { ContractsGrid } from 'web/components/contract/contracts-grid'
|
import { ContractsGrid } from 'web/components/contract/contracts-grid'
|
||||||
import { PillButton } from 'web/components/buttons/pill-button'
|
import { PillButton } from 'web/components/buttons/pill-button'
|
||||||
|
@ -74,7 +77,11 @@ export default function Home() {
|
||||||
}
|
}
|
||||||
}, [user, sections])
|
}, [user, sections])
|
||||||
|
|
||||||
const dailyMovers = useProbChanges({ bettorId: user?.id })
|
const contractMetricsByProfit = useUserContractMetricsByProfit(
|
||||||
|
user?.id ?? '_',
|
||||||
|
3
|
||||||
|
)
|
||||||
|
|
||||||
const trendingContracts = useTrendingContracts(6)
|
const trendingContracts = useTrendingContracts(6)
|
||||||
const newContracts = useNewContracts(6)
|
const newContracts = useNewContracts(6)
|
||||||
const dailyTrendingContracts = useContractsByDailyScoreNotBetOn(user?.id, 6)
|
const dailyTrendingContracts = useContractsByDailyScoreNotBetOn(user?.id, 6)
|
||||||
|
@ -87,7 +94,7 @@ export default function Home() {
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
!user ||
|
!user ||
|
||||||
!dailyMovers ||
|
!contractMetricsByProfit ||
|
||||||
!trendingContracts ||
|
!trendingContracts ||
|
||||||
!newContracts ||
|
!newContracts ||
|
||||||
!dailyTrendingContracts
|
!dailyTrendingContracts
|
||||||
|
@ -118,7 +125,7 @@ export default function Home() {
|
||||||
score: trendingContracts,
|
score: trendingContracts,
|
||||||
newest: newContracts,
|
newest: newContracts,
|
||||||
'daily-trending': dailyTrendingContracts,
|
'daily-trending': dailyTrendingContracts,
|
||||||
'daily-movers': dailyMovers,
|
'daily-movers': contractMetricsByProfit,
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{groups && groupContracts && trendingGroups.length > 0 ? (
|
{groups && groupContracts && trendingGroups.length > 0 ? (
|
||||||
|
@ -184,7 +191,10 @@ export const getHomeItems = (sections: string[]) => {
|
||||||
function renderSections(
|
function renderSections(
|
||||||
sections: { id: string; label: string }[],
|
sections: { id: string; label: string }[],
|
||||||
sectionContracts: {
|
sectionContracts: {
|
||||||
'daily-movers': CPMMBinaryContract[]
|
'daily-movers': {
|
||||||
|
contracts: CPMMBinaryContract[]
|
||||||
|
metrics: ContractMetrics[]
|
||||||
|
}
|
||||||
'daily-trending': CPMMBinaryContract[]
|
'daily-trending': CPMMBinaryContract[]
|
||||||
newest: CPMMBinaryContract[]
|
newest: CPMMBinaryContract[]
|
||||||
score: CPMMBinaryContract[]
|
score: CPMMBinaryContract[]
|
||||||
|
@ -193,13 +203,16 @@ function renderSections(
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{sections.map((s) => {
|
{sections.map((s) => {
|
||||||
const { id, label } = s
|
const { id, label } = s as {
|
||||||
const contracts =
|
id: keyof typeof sectionContracts
|
||||||
sectionContracts[s.id as keyof typeof sectionContracts]
|
label: string
|
||||||
|
|
||||||
if (id === 'daily-movers') {
|
|
||||||
return <DailyMoversSection key={id} contracts={contracts} />
|
|
||||||
}
|
}
|
||||||
|
if (id === 'daily-movers') {
|
||||||
|
return <DailyMoversSection key={id} {...sectionContracts[id]} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const contracts = sectionContracts[id]
|
||||||
|
|
||||||
if (id === 'daily-trending') {
|
if (id === 'daily-trending') {
|
||||||
return (
|
return (
|
||||||
<SearchSection
|
<SearchSection
|
||||||
|
@ -347,58 +360,39 @@ function GroupSection(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DailyMoversSection(props: { contracts: CPMMBinaryContract[] }) {
|
function DailyMoversSection(props: {
|
||||||
const { contracts } = props
|
contracts: CPMMBinaryContract[]
|
||||||
|
metrics: ContractMetrics[]
|
||||||
|
}) {
|
||||||
|
const { contracts, metrics } = props
|
||||||
|
|
||||||
const changes = contracts.filter((c) => Math.abs(c.probChanges.day) >= 0.01)
|
if (contracts.length === 0) {
|
||||||
|
|
||||||
if (changes.length === 0) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="gap-2">
|
<Col className="gap-2">
|
||||||
<SectionHeader label="Daily movers" href="/daily-movers" />
|
<SectionHeader label="Daily movers" href="/daily-movers" />
|
||||||
<ProbChangeTable changes={changes} />
|
<ProfitChangeTable contracts={contracts} metrics={metrics} />
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DailyStats(props: {
|
function DailyStats(props: { user: User | null | undefined }) {
|
||||||
user: User | null | undefined
|
const { user } = props
|
||||||
className?: string
|
|
||||||
}) {
|
|
||||||
const { user, className } = props
|
|
||||||
|
|
||||||
const metrics = usePortfolioHistory(user?.id ?? '', 'daily') ?? []
|
|
||||||
const [first, last] = [metrics[0], metrics[metrics.length - 1]]
|
|
||||||
|
|
||||||
const privateUser = usePrivateUser()
|
const privateUser = usePrivateUser()
|
||||||
const streaks = privateUser?.notificationPreferences?.betting_streaks ?? []
|
const streaks = privateUser?.notificationPreferences?.betting_streaks ?? []
|
||||||
const streaksHidden = streaks.length === 0
|
const streaksHidden = streaks.length === 0
|
||||||
|
|
||||||
let profit = 0
|
|
||||||
let profitPercent = 0
|
|
||||||
if (first && last) {
|
|
||||||
profit = calculatePortfolioProfit(last) - calculatePortfolioProfit(first)
|
|
||||||
profitPercent = profit / first.investmentValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className={'flex-shrink-0 gap-4'}>
|
<Row className={'flex-shrink-0 gap-4'}>
|
||||||
<Col>
|
<DailyProfit user={user} />
|
||||||
<div className="text-gray-500">Daily profit</div>
|
|
||||||
<Row className={clsx(className, 'items-center text-lg')}>
|
|
||||||
<span>{formatMoney(profit)}</span>{' '}
|
|
||||||
<ProfitBadge profitPercent={profitPercent * 100} />
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
{!streaksHidden && (
|
{!streaksHidden && (
|
||||||
<Col>
|
<Col>
|
||||||
<div className="text-gray-500">Streak</div>
|
<div className="text-gray-500">Streak</div>
|
||||||
<Row
|
<Row
|
||||||
className={clsx(
|
className={clsx(
|
||||||
className,
|
|
||||||
'items-center text-lg',
|
'items-center text-lg',
|
||||||
user && !hasCompletedStreakToday(user) && 'grayscale'
|
user && !hasCompletedStreakToday(user) && 'grayscale'
|
||||||
)}
|
)}
|
||||||
|
@ -411,6 +405,39 @@ function DailyStats(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function DailyProfit(props: { user: User | null | undefined }) {
|
||||||
|
const { user } = props
|
||||||
|
|
||||||
|
const contractMetricsByProfit = useUserContractMetricsByProfit(
|
||||||
|
user?.id ?? '_',
|
||||||
|
100
|
||||||
|
)
|
||||||
|
const profit = sum(
|
||||||
|
contractMetricsByProfit?.metrics.map((m) =>
|
||||||
|
m.from ? m.from.day.profit : 0
|
||||||
|
) ?? []
|
||||||
|
)
|
||||||
|
|
||||||
|
const metrics = usePortfolioHistory(user?.id ?? '', 'daily') ?? []
|
||||||
|
const [first, last] = [metrics[0], metrics[metrics.length - 1]]
|
||||||
|
|
||||||
|
let profitPercent = 0
|
||||||
|
if (first && last) {
|
||||||
|
// profit = calculatePortfolioProfit(last) - calculatePortfolioProfit(first)
|
||||||
|
profitPercent = profit / first.investmentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SiteLink className="flex flex-col" href="/daily-movers">
|
||||||
|
<div className="text-gray-500">Daily profit</div>
|
||||||
|
<Row className="items-center text-lg">
|
||||||
|
<span>{formatMoney(profit)}</span>{' '}
|
||||||
|
<ProfitBadge profitPercent={profitPercent * 100} />
|
||||||
|
</Row>
|
||||||
|
</SiteLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function TrendingGroupsSection(props: {
|
export function TrendingGroupsSection(props: {
|
||||||
user: User
|
user: User
|
||||||
myGroups: Group[]
|
myGroups: Group[]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user