import clsx from 'clsx' import React, { useEffect, useState } from 'react' import { partition, sum, sumBy } from 'lodash' import { useUser } from 'web/hooks/use-user' import { CPMMBinaryContract, PseudoNumericContract } from 'common/contract' import { Col } from './layout/col' import { Row } from './layout/row' import { Spacer } from './layout/spacer' import { formatMoney, formatMoneyWithDecimals, formatPercent, formatWithCommas, } from 'common/util/format' import { getBinaryCpmmBetInfo } from 'common/new-bet' import { User } from 'web/lib/firebase/users' import { Bet, LimitBet } from 'common/bet' import { APIError, placeBet } from 'web/lib/firebase/api' import { sellShares } from 'web/lib/firebase/api' import { AmountInput, BuyAmountInput } from './amount-input' import { InfoTooltip } from './info-tooltip' import { BinaryOutcomeLabel } from './outcome-label' import { getProbability } from 'common/calculate' import { useFocus } from 'web/hooks/use-focus' import { useUserContractBets } from 'web/hooks/use-user-bets' import { calculateCpmmSale, getCpmmProbability } from 'common/calculate-cpmm' import { getFormattedMappedValue, getPseudoProbability, } from 'common/pseudo-numeric' import { SellRow } from './sell-row' import { useSaveBinaryShares } from './use-save-binary-shares' import { SignUpPrompt } from './sign-up-prompt' import { isIOS } from 'web/lib/util/device' import { ProbabilityInput } from './probability-input' import { track } from 'web/lib/service/analytics' import { removeUndefinedProps } from 'common/util/object' import { useUnfilledBets } from 'web/hooks/use-bets' import { LimitBets } from './limit-bets' import { BucketInput } from './bucket-input' import { PillButton } from './buttons/pill-button' import { YesNoSelector } from './yes-no-selector' export function BetPanel(props: { contract: CPMMBinaryContract | PseudoNumericContract className?: string }) { const { contract, className } = props const user = useUser() const userBets = useUserContractBets(user?.id, contract.id) const unfilledBets = useUnfilledBets(contract.id) ?? [] const { sharesOutcome } = useSaveBinaryShares(contract, userBets) const [isLimitOrder, setIsLimitOrder] = useState(false) return ( {unfilledBets.length > 0 && ( )} ) } export function SimpleBetPanel(props: { contract: CPMMBinaryContract | PseudoNumericContract className?: string selected?: 'YES' | 'NO' hasShares?: boolean onBetSuccess?: () => void }) { const { contract, className, selected, hasShares, onBetSuccess } = props const user = useUser() const [isLimitOrder, setIsLimitOrder] = useState(false) const unfilledBets = useUnfilledBets(contract.id) ?? [] return ( {unfilledBets.length > 0 && ( )} ) } function BuyPanel(props: { contract: CPMMBinaryContract | PseudoNumericContract user: User | null | undefined unfilledBets: Bet[] isLimitOrder?: boolean selected?: 'YES' | 'NO' onBuySuccess?: () => void }) { const { contract, user, unfilledBets, isLimitOrder, selected, onBuySuccess } = props const initialProb = getProbability(contract) const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC' const [betChoice, setBetChoice] = useState<'YES' | 'NO' | undefined>(selected) const [betAmount, setBetAmount] = useState(undefined) const [limitProb, setLimitProb] = useState( Math.round(100 * initialProb) ) const [error, setError] = useState() const [isSubmitting, setIsSubmitting] = useState(false) const [wasSubmitted, setWasSubmitted] = useState(false) const [inputRef, focusAmountInput] = useFocus() useEffect(() => { if (selected) { if (isIOS()) window.scrollTo(0, window.scrollY + 200) focusAmountInput() } }, [selected, focusAmountInput]) function onBetChoice(choice: 'YES' | 'NO') { setBetChoice(choice) setWasSubmitted(false) focusAmountInput() } function onBetChange(newAmount: number | undefined) { setWasSubmitted(false) setBetAmount(newAmount) if (!betChoice) { setBetChoice('YES') } } async function submitBet() { if (!user || !betAmount) return if (isLimitOrder && limitProb === undefined) return const limitProbScaled = isLimitOrder && limitProb !== undefined ? limitProb / 100 : undefined setError(undefined) setIsSubmitting(true) placeBet( removeUndefinedProps({ amount: betAmount, outcome: betChoice, contractId: contract.id, limitProb: limitProbScaled, }) ) .then((r) => { console.log('placed bet. Result:', r) setIsSubmitting(false) setWasSubmitted(true) setBetAmount(undefined) if (onBuySuccess) onBuySuccess() }) .catch((e) => { if (e instanceof APIError) { setError(e.toString()) } else { console.error(e) setError('Error placing bet') } setIsSubmitting(false) }) track('bet', { location: 'bet panel', outcomeType: contract.outcomeType, slug: contract.slug, contractId: contract.id, amount: betAmount, outcome: betChoice, isLimitOrder, limitProb: limitProbScaled, }) } const betDisabled = isSubmitting || !betAmount || error const limitProbFrac = (limitProb ?? 0) / 100 const { newPool, newP, newBet } = getBinaryCpmmBetInfo( betChoice ?? 'YES', betAmount ?? 0, contract, isLimitOrder ? limitProbFrac : undefined, unfilledBets as LimitBet[] ) const resultProb = getCpmmProbability(newPool, newP) const probStayedSame = formatPercent(resultProb) === formatPercent(initialProb) const remainingMatched = isLimitOrder ? ((newBet.orderAmount ?? 0) - newBet.amount) / (betChoice === 'YES' ? limitProbFrac : 1 - limitProbFrac) : 0 const currentPayout = newBet.shares + remainingMatched const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0 const currentReturnPercent = formatPercent(currentReturn) const totalFees = sum(Object.values(newBet.fees)) const format = getFormattedMappedValue(contract) return ( <> {isLimitOrder && ( <>

Place a limit order to add to the order book rather than trade directly. Your orders will be filled over time if others trade with them at the prices you set.


)}
{isPseudoNumeric ? 'Direction' : 'Outcome'}
onBetChoice(choice)} isPseudoNumeric={isPseudoNumeric} />
Amount
{isLimitOrder && ( <>

test

Limit {isPseudoNumeric ? 'value' : 'probability'} {isPseudoNumeric ? ( setLimitProb( value === undefined ? undefined : 100 * getPseudoProbability( value, contract.min, contract.max, contract.isLogScale ) ) } isSubmitting={isSubmitting} /> ) : ( )} )} {!isLimitOrder && (
{isPseudoNumeric ? 'Estimated value' : 'Probability'}
{probStayedSame ? (
{format(initialProb)}
) : (
{format(initialProb)} {format(resultProb)}
)}
)}
{isPseudoNumeric ? ( 'Max payout' ) : ( <> Payout if )}
{formatMoney(currentPayout)} (+{currentReturnPercent})
{user && ( )} {wasSubmitted && (
{isLimitOrder ? 'Order' : 'Bet'} submitted!
)} ) } function QuickOrLimitBet(props: { isLimitOrder: boolean setIsLimitOrder: (isLimitOrder: boolean) => void }) { const { isLimitOrder, setIsLimitOrder } = props return (
Bet
{ setIsLimitOrder(false) track('select quick order') }} > Quick { setIsLimitOrder(true) track('select limit order') }} > Limit
) } export function SellPanel(props: { contract: CPMMBinaryContract | PseudoNumericContract userBets: Bet[] shares: number sharesOutcome: 'YES' | 'NO' user: User onSellSuccess?: () => void }) { const { contract, shares, sharesOutcome, userBets, user, onSellSuccess } = props const [amount, setAmount] = useState(shares) const [error, setError] = useState() const [isSubmitting, setIsSubmitting] = useState(false) const [wasSubmitted, setWasSubmitted] = useState(false) const unfilledBets = useUnfilledBets(contract.id) ?? [] const betDisabled = isSubmitting || !amount || error // Sell all shares if remaining shares would be < 1 const sellQuantity = amount === Math.floor(shares) ? shares : amount async function submitSell() { if (!user || !amount) return setError(undefined) setIsSubmitting(true) await sellShares({ shares: sellQuantity, outcome: sharesOutcome, contractId: contract.id, }) .then((r) => { console.log('Sold shares. Result:', r) setIsSubmitting(false) setWasSubmitted(true) setAmount(undefined) if (onSellSuccess) onSellSuccess() }) .catch((e) => { if (e instanceof APIError) { setError(e.toString()) } else { console.error(e) setError('Error selling') } setIsSubmitting(false) }) track('sell shares', { outcomeType: contract.outcomeType, slug: contract.slug, contractId: contract.id, shares: sellQuantity, outcome: sharesOutcome, }) } const initialProb = getProbability(contract) const { cpmmState, saleValue } = calculateCpmmSale( contract, sellQuantity ?? 0, sharesOutcome, unfilledBets ) const resultProb = getCpmmProbability(cpmmState.pool, cpmmState.p) const openUserBets = userBets.filter((bet) => !bet.isSold && !bet.sale) const [yesBets, noBets] = partition( openUserBets, (bet) => bet.outcome === 'YES' ) const [yesShares, noShares] = [ sumBy(yesBets, (bet) => bet.shares), sumBy(noBets, (bet) => bet.shares), ] const ownedShares = Math.round(yesShares) || Math.round(noShares) const onAmountChange = (amount: number | undefined) => { setAmount(amount) // Check for errors. if (amount !== undefined) { if (amount > ownedShares) { setError(`Maximum ${formatWithCommas(Math.floor(ownedShares))} shares`) } else { setError(undefined) } } } const { outcomeType } = contract const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC' const format = getFormattedMappedValue(contract) return ( <> Sale proceeds {formatMoney(saleValue)}
{isPseudoNumeric ? 'Estimated value' : 'Probability'}
{format(initialProb)} {format(resultProb)}
{wasSubmitted &&
Sell submitted!
} ) }