diff --git a/web/components/bet-summary.tsx b/web/components/bet-summary.tsx new file mode 100644 index 00000000..afeeb3fd --- /dev/null +++ b/web/components/bet-summary.tsx @@ -0,0 +1,120 @@ +import { sumBy } from 'lodash' +import clsx from 'clsx' + +import { Bet } from 'web/lib/firebase/bets' +import { + formatMoney, + formatWithCommas, +} from 'common/util/format' +import { Col } from './layout/col' +import { + Contract, +} from 'web/lib/firebase/contracts' +import { Row } from './layout/row' +import { YesLabel, NoLabel } from './outcome-label' +import { + calculatePayout, + getContractBetMetrics, + getProbability, +} from 'common/calculate' +import { floatingEqual } from 'common/util/math' +import { InfoTooltip } from './info-tooltip' +import { ProfitBadge } from './profit-badge' + +export function BetsSummary(props: { + contract: Contract + bets: Bet[] + isYourBets: boolean + className?: string +}) { + const { contract, className } = props + const { resolution, outcomeType } = contract + const isBinary = outcomeType === 'BINARY' + + const bets = props.bets.filter((b) => !b.isAnte) + const { profitPercent, payout, profit, totalShares } = getContractBetMetrics( + contract, + bets + ) + + const excludeSales = bets.filter((b) => !b.isSold && !b.sale) + const yesWinnings = sumBy(excludeSales, (bet) => + calculatePayout(contract, bet, 'YES') + ) + const noWinnings = sumBy(excludeSales, (bet) => + calculatePayout(contract, bet, 'NO') + ) + + const prob = isBinary ? getProbability(contract) : 0 + const expectation = prob * yesWinnings + (1 - prob) * noWinnings + + if ( + isBinary && + floatingEqual(totalShares.YES ?? 0, 0) && + floatingEqual(totalShares.NO ?? 0, 0) + ) + return <> + + return ( + + + {resolution ? ( + +
Payout
+
+ {formatMoney(payout)}{' '} + +
+ + ) : isBinary ? ( + +
+ Position{' '} + +
+
+ {yesWinnings > 0 ? ( + <> + {formatWithCommas(yesWinnings)} + + ) : ( + <> + {formatWithCommas(noWinnings)} + + )} +
+ + ) : ( + +
+ Expected value {''} + +
+
{formatMoney(payout)}
+ + )} + + {isBinary && ( + +
+ Expected value{' '} + +
+
{formatMoney(expectation)}
+ + )} + + +
+ Profit{' '} + +
+
+ {formatMoney(profit)} +
+ +
+ + + ) +} diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx index 4e2f60e0..bd29f8ab 100644 --- a/web/components/bets-list.tsx +++ b/web/components/bets-list.tsx @@ -22,7 +22,7 @@ import { import { Row } from './layout/row' import { sellBet } from 'web/lib/firebase/api' import { ConfirmationButton } from './confirmation-button' -import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label' +import { OutcomeLabel } from './outcome-label' import { LoadingIndicator } from './loading-indicator' import { SiteLink } from './site-link' import { @@ -33,7 +33,6 @@ import { getContractBetMetrics, resolvedPayout, getContractBetNullMetrics, - getProbability, } from 'common/calculate' import { NumericContract } from 'common/contract' import { formatNumericProbability } from 'common/pseudo-numeric' @@ -41,12 +40,12 @@ import { useUser } from 'web/hooks/use-user' import { useUserBets } from 'web/hooks/use-user-bets' import { useUnfilledBets } from 'web/hooks/use-bets' import { LimitBet } from 'common/bet' -import { floatingEqual } from 'common/util/math' import { Pagination } from './pagination' import { LimitOrderTable } from './limit-bets' import { UserLink } from 'web/components/user-link' import { useUserBetContracts } from 'web/hooks/use-contracts' -import { InfoTooltip } from './info-tooltip' +import { BetsSummary } from './bet-summary' +import { ProfitBadge } from './profit-badge' type BetSort = 'newest' | 'profit' | 'closeTime' | 'value' type BetFilter = 'open' | 'limit_bet' | 'sold' | 'closed' | 'resolved' | 'all' @@ -374,104 +373,6 @@ function ContractBets(props: { ) } -export function BetsSummary(props: { - contract: Contract - bets: Bet[] - isYourBets: boolean - className?: string -}) { - const { contract, className } = props - const { resolution, outcomeType } = contract - const isBinary = outcomeType === 'BINARY' - - const bets = props.bets.filter((b) => !b.isAnte) - const { profitPercent, payout, profit, totalShares } = getContractBetMetrics( - contract, - bets - ) - - const excludeSales = bets.filter((b) => !b.isSold && !b.sale) - const yesWinnings = sumBy(excludeSales, (bet) => - calculatePayout(contract, bet, 'YES') - ) - const noWinnings = sumBy(excludeSales, (bet) => - calculatePayout(contract, bet, 'NO') - ) - - const prob = isBinary ? getProbability(contract) : 0 - const expectation = prob * yesWinnings + (1 - prob) * noWinnings - - if ( - isBinary && - floatingEqual(totalShares.YES ?? 0, 0) && - floatingEqual(totalShares.NO ?? 0, 0) - ) - return <> - - return ( - - - {resolution ? ( - -
Payout
-
- {formatMoney(payout)}{' '} - -
- - ) : isBinary ? ( - -
- Position{' '} - -
-
- {yesWinnings > 0 ? ( - <> - {formatWithCommas(yesWinnings)} - - ) : ( - <> - {formatWithCommas(noWinnings)} - - )} -
- - ) : ( - -
- Expected value {''} - -
-
{formatMoney(payout)}
- - )} - - {isBinary && ( - -
- Expected value{' '} - -
-
{formatMoney(expectation)}
- - )} - - -
- Profit{' '} - -
-
- {formatMoney(profit)} -
- -
- - - ) -} - export function ContractBetsTable(props: { contract: Contract bets: Bet[] @@ -733,30 +634,3 @@ function SellButton(props: { ) } - -export function ProfitBadge(props: { - profitPercent: number - round?: boolean - className?: string -}) { - const { profitPercent, round, className } = props - if (!profitPercent) return null - const colors = - profitPercent > 0 - ? 'bg-green-100 text-green-800' - : 'bg-red-100 text-red-800' - - return ( - - {(profitPercent > 0 ? '+' : '') + - profitPercent.toFixed(round ? 0 : 1) + - '%'} - - ) -} diff --git a/web/components/profit-badge.tsx b/web/components/profit-badge.tsx new file mode 100644 index 00000000..f82159e6 --- /dev/null +++ b/web/components/profit-badge.tsx @@ -0,0 +1,28 @@ +import clsx from 'clsx' + +export function ProfitBadge(props: { + profitPercent: number + round?: boolean + className?: string +}) { + const { profitPercent, round, className } = props + if (!profitPercent) return null + const colors = + profitPercent > 0 + ? 'bg-green-100 text-green-800' + : 'bg-red-100 text-red-800' + + return ( + + {(profitPercent > 0 ? '+' : '') + + profitPercent.toFixed(round ? 0 : 1) + + '%'} + + ) +} diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index be6e4b1b..125468d3 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -1,5 +1,6 @@ import React, { memo, useEffect, useMemo, useState } from 'react' import { ArrowLeftIcon } from '@heroicons/react/outline' +import dayjs from 'dayjs' import { useContractWithPreload } from 'web/hooks/use-contract' import { ContractOverview } from 'web/components/contract/contract-overview' @@ -44,9 +45,7 @@ import { useAdmin } from 'web/hooks/use-admin' import { BetSignUpPrompt } from 'web/components/sign-up-prompt' import { PlayMoneyDisclaimer } from 'web/components/play-money-disclaimer' import BetButton from 'web/components/bet-button' - -import dayjs from 'dayjs' -import { BetsSummary } from 'web/components/bets-list' +import { BetsSummary } from 'web/components/bet-summary' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { diff --git a/web/pages/home/index.tsx b/web/pages/home/index.tsx index f13fc200..d9f83351 100644 --- a/web/pages/home/index.tsx +++ b/web/pages/home/index.tsx @@ -33,7 +33,6 @@ import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups' import { usePortfolioHistory } from 'web/hooks/use-portfolio-history' import { formatMoney } from 'common/util/format' import { useProbChanges } from 'web/hooks/use-prob-changes' -import { ProfitBadge } from 'web/components/bets-list' import { calculatePortfolioProfit } from 'common/calculate-metrics' import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal' import { ContractsGrid } from 'web/components/contract/contracts-grid' @@ -45,6 +44,7 @@ import { usePrefetch } from 'web/hooks/use-prefetch' import { Title } from 'web/components/title' import { CPMMBinaryContract } from 'common/contract' import { useContractsByDailyScoreGroups } from 'web/hooks/use-contracts' +import { ProfitBadge } from 'web/components/profit-badge' export default function Home() { const user = useUser()