From d8aaf4219e4c84e0a7d463e477112a4a2b1a7854 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Mon, 27 Jun 2022 19:14:23 -0500 Subject: [PATCH] pseudo numeric market layout, quick betting --- common/numeric.ts | 10 +++ web/components/bet-panel.tsx | 18 +++-- web/components/bet-row.tsx | 5 +- web/components/contract/contract-card.tsx | 42 ++++++++++- web/components/contract/contract-overview.tsx | 13 ++++ .../contract/contract-prob-graph.tsx | 9 ++- web/components/contract/quick-bet.tsx | 74 +++++++++++-------- web/components/numeric-resolution-panel.tsx | 4 +- web/components/sell-button.tsx | 12 ++- web/components/sell-modal.tsx | 4 +- web/components/sell-row.tsx | 4 +- web/pages/[username]/[contractSlug].tsx | 7 +- 12 files changed, 147 insertions(+), 55 deletions(-) create mode 100644 common/numeric.ts diff --git a/common/numeric.ts b/common/numeric.ts new file mode 100644 index 00000000..d5fd931c --- /dev/null +++ b/common/numeric.ts @@ -0,0 +1,10 @@ +import { PseudoNumericContract } from './contract' + +export function formatNumericProbability( + p: number, + contract: PseudoNumericContract +) { + const { min, max } = contract + const value = p * (max - min) + min + return Math.round(value).toString() +} diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index db793b59..65ec8018 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -25,7 +25,7 @@ import { APIError, placeBet } from 'web/lib/firebase/api-call' import { sellShares } from 'web/lib/firebase/api-call' import { AmountInput, BuyAmountInput } from './amount-input' import { InfoTooltip } from './info-tooltip' -import { BinaryOutcomeLabel } from './outcome-label' +import { BinaryOutcomeLabel, PseudoNumericOutcomeLabel } from './outcome-label' import { calculatePayoutAfterCorrectBet, calculateShares, @@ -46,7 +46,7 @@ import { isIOS } from 'web/lib/util/device' import { track } from 'web/lib/service/analytics' export function BetPanel(props: { - contract: BinaryContract + contract: BinaryContract | PseudoNumericContract className?: string }) { const { contract, className } = props @@ -85,7 +85,7 @@ export function BetPanel(props: { } export function BetPanelSwitcher(props: { - contract: BinaryContract + contract: BinaryContract | PseudoNumericContract className?: string title?: string // Set if BetPanel is on a feed modal selected?: 'YES' | 'NO' @@ -93,7 +93,8 @@ export function BetPanelSwitcher(props: { }) { const { contract, className, title, selected, onBetSuccess } = props - const { mechanism } = contract + const { mechanism, outcomeType } = contract + const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC' const user = useUser() const userBets = useUserContractBets(user?.id, contract.id) @@ -126,7 +127,12 @@ export function BetPanelSwitcher(props: {
You have {formatWithCommas(floorShares)}{' '} - shares + {isPseudoNumeric ? ( + + ) : ( + + )}{' '} + shares
{tradeType === 'BUY' && ( @@ -405,7 +411,7 @@ function BuyPanel(props: { } export function SellPanel(props: { - contract: CPMMBinaryContract + contract: CPMMBinaryContract | PseudoNumericContract userBets: Bet[] shares: number sharesOutcome: 'YES' | 'NO' diff --git a/web/components/bet-row.tsx b/web/components/bet-row.tsx index 9621f7a9..ae5e0b00 100644 --- a/web/components/bet-row.tsx +++ b/web/components/bet-row.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx' import { BetPanelSwitcher } from './bet-panel' import { YesNoSelector } from './yes-no-selector' -import { BinaryContract } from 'common/contract' +import { BinaryContract, PseudoNumericContract } from 'common/contract' import { Modal } from './layout/modal' import { SellButton } from './sell-button' import { useUser } from 'web/hooks/use-user' @@ -12,7 +12,7 @@ import { useSaveShares } from './use-save-shares' // Inline version of a bet panel. Opens BetPanel in a new modal. export default function BetRow(props: { - contract: BinaryContract + contract: BinaryContract | PseudoNumericContract className?: string btnClassName?: string betPanelClassName?: string @@ -32,6 +32,7 @@ export default function BetRow(props: { return ( <> { diff --git a/web/components/contract/contract-card.tsx b/web/components/contract/contract-card.tsx index 87239465..5422174b 100644 --- a/web/components/contract/contract-card.tsx +++ b/web/components/contract/contract-card.tsx @@ -9,6 +9,7 @@ import { BinaryContract, FreeResponseContract, NumericContract, + PseudoNumericContract, } from 'common/contract' import { AnswerLabel, @@ -16,7 +17,11 @@ import { CancelLabel, FreeResponseOutcomeLabel, } from '../outcome-label' -import { getOutcomeProbability, getTopAnswer } from 'common/calculate' +import { + getOutcomeProbability, + getProbability, + getTopAnswer, +} from 'common/calculate' import { AvatarDetails, MiscDetails, ShowTime } from './contract-details' import { getExpectedValue, getValueFromBucket } from 'common/calculate-dpm' import { QuickBet, ProbBar, getColor } from './quick-bet' @@ -24,6 +29,7 @@ import { useContractWithPreload } from 'web/hooks/use-contract' import { useUser } from 'web/hooks/use-user' import { track } from '@amplitude/analytics-browser' import { trackCallback } from 'web/lib/service/analytics' +import { formatNumericProbability } from 'common/numeric' export function ContractCard(props: { contract: Contract @@ -284,3 +290,37 @@ export function NumericResolutionOrExpectation(props: { ) } + +export function PseudoNumericResolutionOrExpectation(props: { + contract: PseudoNumericContract + className?: string +}) { + const { contract, className } = props + const { resolution, resolutionProbability } = contract + const textColor = `text-blue-400` + + return ( + + {resolution ? ( + <> +
Resolved
+ + {resolution === 'CANCEL' ? ( + + ) : ( +
+ {formatNumericProbability(resolutionProbability ?? 0, contract)} +
+ )} + + ) : ( + <> +
+ {formatNumericProbability(getProbability(contract), contract)} +
+
expected
+ + )} + + ) +} diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx index f2795d08..897bef04 100644 --- a/web/components/contract/contract-overview.tsx +++ b/web/components/contract/contract-overview.tsx @@ -11,6 +11,7 @@ import { FreeResponseResolutionOrChance, BinaryResolutionOrChance, NumericResolutionOrExpectation, + PseudoNumericResolutionOrExpectation, } from './contract-card' import { Bet } from 'common/bet' import BetRow from '../bet-row' @@ -50,6 +51,13 @@ export const ContractOverview = (props: { /> )} + {isPseudoNumeric && ( + + )} + {outcomeType === 'NUMERIC' && ( + {tradingAllowed(contract) && } +
+ ) : isPseudoNumeric ? ( + + {tradingAllowed(contract) && } ) : ( diff --git a/web/components/contract/contract-prob-graph.tsx b/web/components/contract/contract-prob-graph.tsx index b5f253a1..9b3932dc 100644 --- a/web/components/contract/contract-prob-graph.tsx +++ b/web/components/contract/contract-prob-graph.tsx @@ -81,13 +81,16 @@ export const ContractProbGraph = memo(function ContractProbGraph(props: { } } - const data = [{ id: 'Yes', data: points, color: '#11b981' }] + const isBinary = contract.outcomeType === 'BINARY' + + const data = [ + { id: 'Yes', data: points, color: isBinary ? '#11b981' : '#5fa5f9' }, + ] const multiYear = !dayjs(startDate).isSame(latestTime, 'year') const lessThanAWeek = dayjs(startDate).add(8, 'day').isAfter(latestTime) - const formatter = - contract.outcomeType === 'BINARY' ? formatPercent : formatNumeric + const formatter = isBinary ? formatPercent : formatNumeric return (
) : ( )} @@ -189,7 +171,7 @@ export function QuickBet(props: { contract: Contract; user: User }) { {/* Down bet triangle */} - {contract.outcomeType !== 'BINARY' ? ( + {outcomeType !== 'BINARY' && outcomeType !== 'PSEUDO_NUMERIC' ? (
{ diff --git a/web/components/sell-button.tsx b/web/components/sell-button.tsx index 2b3734a5..51c88442 100644 --- a/web/components/sell-button.tsx +++ b/web/components/sell-button.tsx @@ -1,4 +1,4 @@ -import { BinaryContract } from 'common/contract' +import { BinaryContract, PseudoNumericContract } from 'common/contract' import { User } from 'common/user' import { useUserContractBets } from 'web/hooks/use-user-bets' import { useState } from 'react' @@ -7,7 +7,7 @@ import clsx from 'clsx' import { SellSharesModal } from './sell-modal' export function SellButton(props: { - contract: BinaryContract + contract: BinaryContract | PseudoNumericContract user: User | null | undefined sharesOutcome: 'YES' | 'NO' | undefined shares: number @@ -16,7 +16,8 @@ export function SellButton(props: { const { contract, user, sharesOutcome, shares, panelClassName } = props const userBets = useUserContractBets(user?.id, contract.id) const [showSellModal, setShowSellModal] = useState(false) - const { mechanism } = contract + const { mechanism, outcomeType } = contract + const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC' if (sharesOutcome && user && mechanism === 'cpmm-1') { return ( @@ -32,7 +33,10 @@ export function SellButton(props: { )} onClick={() => setShowSellModal(true)} > - {'Sell ' + sharesOutcome} + Sell{' '} + {isPseudoNumeric + ? { YES: 'HIGH', NO: 'LOW' }[sharesOutcome] + : sharesOutcome}
{'(' + Math.floor(shares) + ' shares)'} diff --git a/web/components/sell-modal.tsx b/web/components/sell-modal.tsx index f5a1af67..63cf79b2 100644 --- a/web/components/sell-modal.tsx +++ b/web/components/sell-modal.tsx @@ -1,4 +1,4 @@ -import { CPMMBinaryContract } from 'common/contract' +import { CPMMBinaryContract, PseudoNumericContract } from 'common/contract' import { Bet } from 'common/bet' import { User } from 'common/user' import { Modal } from './layout/modal' @@ -11,7 +11,7 @@ import clsx from 'clsx' export function SellSharesModal(props: { className?: string - contract: CPMMBinaryContract + contract: CPMMBinaryContract | PseudoNumericContract userBets: Bet[] shares: number sharesOutcome: 'YES' | 'NO' diff --git a/web/components/sell-row.tsx b/web/components/sell-row.tsx index 4fe2536f..a8cb2851 100644 --- a/web/components/sell-row.tsx +++ b/web/components/sell-row.tsx @@ -1,4 +1,4 @@ -import { BinaryContract } from 'common/contract' +import { BinaryContract, PseudoNumericContract } from 'common/contract' import { User } from 'common/user' import { useState } from 'react' import { Col } from './layout/col' @@ -10,7 +10,7 @@ import { useSaveShares } from './use-save-shares' import { SellSharesModal } from './sell-modal' export function SellRow(props: { - contract: BinaryContract + contract: BinaryContract | PseudoNumericContract user: User | null | undefined className?: string }) { diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index dab67582..f3de69a9 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -142,11 +142,12 @@ export function ContractPageContent( const { creatorId, isResolved, question, outcomeType } = contract const isCreator = user?.id === creatorId - const isBinary = outcomeType === 'BINARY' || outcomeType === 'PSEUDO_NUMERIC' + const isBinary = outcomeType === 'BINARY' + const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC' const isNumeric = outcomeType === 'NUMERIC' const allowTrade = tradingAllowed(contract) const allowResolve = !isResolved && isCreator && !!user - const hasSidePanel = (isBinary || isNumeric) && (allowTrade || allowResolve) + const hasSidePanel = (isBinary || isNumeric || isPseudoNumeric) && (allowTrade || allowResolve) const ogCardProps = getOpenGraphProps(contract) @@ -159,7 +160,7 @@ export function ContractPageContent( ))} {allowResolve && - (isNumeric ? ( + (isNumeric || isPseudoNumeric ? ( ) : (