From b9248499c98b8c09e2c4772409c3890b46aacbe9 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Mon, 14 Feb 2022 14:14:02 -0600 Subject: [PATCH] Answer bet panel --- common/calculate-multi.ts | 27 ---- common/calculate.ts | 50 ++++--- common/new-bet.ts | 13 +- web/components/answers-panel.tsx | 215 +++++++++++++++++++++++++++---- 4 files changed, 231 insertions(+), 74 deletions(-) delete mode 100644 common/calculate-multi.ts diff --git a/common/calculate-multi.ts b/common/calculate-multi.ts deleted file mode 100644 index 5ff23cdb..00000000 --- a/common/calculate-multi.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as _ from 'lodash' - -export function getMultiProbability( - totalShares: { - [answerId: string]: number - }, - answerId: string -) { - const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2) - const shares = totalShares[answerId] ?? 0 - return shares ** 2 / squareSum -} - -export function calculateMultiShares( - totalShares: { - [answerId: string]: number - }, - bet: number, - betChoice: string -) { - const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2) - const shares = totalShares[betChoice] ?? 0 - - const c = 2 * bet * Math.sqrt(squareSum) - - return Math.sqrt(bet ** 2 + shares ** 2 + c) - shares -} diff --git a/common/calculate.ts b/common/calculate.ts index fbbfd66f..eeec5251 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -1,39 +1,51 @@ +import _ from 'lodash' import { Bet } from './bet' import { Contract } from './contract' import { FEES } from './fees' export function getProbability(totalShares: { YES: number; NO: number }) { - const { YES: y, NO: n } = totalShares - return y ** 2 / (y ** 2 + n ** 2) + return getOutcomeProbability(totalShares, 'YES') +} + +export function getOutcomeProbability( + totalShares: { + [outcome: string]: number + }, + outcome: string +) { + const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2) + const shares = totalShares[outcome] ?? 0 + return shares ** 2 / squareSum } export function getProbabilityAfterBet( - totalShares: { YES: number; NO: number }, - outcome: 'YES' | 'NO', + totalShares: { + [outcome: string]: number + }, + outcome: string, bet: number ) { const shares = calculateShares(totalShares, bet, outcome) - const [YES, NO] = - outcome === 'YES' - ? [totalShares.YES + shares, totalShares.NO] - : [totalShares.YES, totalShares.NO + shares] + const prevShares = totalShares[outcome] ?? 0 + const newTotalShares = { ...totalShares, outcome: prevShares + shares } - return getProbability({ YES, NO }) + return getOutcomeProbability(newTotalShares, outcome) } export function calculateShares( - totalShares: { YES: number; NO: number }, + totalShares: { + [outcome: string]: number + }, bet: number, - betChoice: 'YES' | 'NO' + betChoice: string ) { - const [yesShares, noShares] = [totalShares.YES, totalShares.NO] + const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2) + const shares = totalShares[betChoice] ?? 0 - const c = 2 * bet * Math.sqrt(yesShares ** 2 + noShares ** 2) + const c = 2 * bet * Math.sqrt(squareSum) - return betChoice === 'YES' - ? Math.sqrt(bet ** 2 + yesShares ** 2 + c) - yesShares - : Math.sqrt(bet ** 2 + noShares ** 2 + c) - noShares + return Math.sqrt(bet ** 2 + shares ** 2 + c) - shares } export function calculateEstimatedWinnings( @@ -128,15 +140,15 @@ export function calculateCancelPayout(contract: Contract, bet: Bet) { export function calculateStandardPayout( contract: Contract, bet: Bet, - outcome: 'YES' | 'NO' + outcome: string ) { const { amount, outcome: betOutcome, shares } = bet if (betOutcome !== outcome) return 0 const { totalShares, totalBets, phantomShares } = contract - if (totalShares[outcome] === 0) return 0 + if (!totalShares[outcome]) return 0 - const truePool = contract.pool.YES + contract.pool.NO + const truePool = _.sum(Object.values(totalShares)) if (totalBets[outcome] >= truePool) return (amount / totalBets[outcome]) * truePool diff --git a/common/new-bet.ts b/common/new-bet.ts index 5b23c84e..67f1e90a 100644 --- a/common/new-bet.ts +++ b/common/new-bet.ts @@ -1,6 +1,9 @@ import { Bet } from './bet' -import { calculateShares, getProbability } from './calculate' -import { calculateMultiShares, getMultiProbability } from './calculate-multi' +import { + calculateShares, + getProbability, + getOutcomeProbability, +} from './calculate' import { Contract } from './contract' import { User } from './user' @@ -66,7 +69,7 @@ export const getNewMultiBetInfo = ( const prevOutcomePool = pool[outcome] ?? 0 const newPool = { ...pool, outcome: prevOutcomePool + amount } - const shares = calculateMultiShares(contract.totalShares, amount, outcome) + const shares = calculateShares(contract.totalShares, amount, outcome) const prevShares = totalShares[outcome] ?? 0 const newTotalShares = { ...totalShares, outcome: prevShares + shares } @@ -74,8 +77,8 @@ export const getNewMultiBetInfo = ( const prevTotalBets = totalBets[outcome] ?? 0 const newTotalBets = { ...totalBets, outcome: prevTotalBets + amount } - const probBefore = getMultiProbability(totalShares, outcome) - const probAfter = getMultiProbability(newTotalShares, outcome) + const probBefore = getOutcomeProbability(totalShares, outcome) + const probAfter = getOutcomeProbability(newTotalShares, outcome) const newBet: Bet<'MULTI'> = { id: newBetId, diff --git a/web/components/answers-panel.tsx b/web/components/answers-panel.tsx index 3745bced..771be5be 100644 --- a/web/components/answers-panel.tsx +++ b/web/components/answers-panel.tsx @@ -1,18 +1,32 @@ import clsx from 'clsx' -import { useState } from 'react' +import { useEffect, useRef, useState } from 'react' import Textarea from 'react-expanding-textarea' import { Answer } from '../../common/answer' import { Contract } from '../../common/contract' import { AmountInput } from './amount-input' import { Col } from './layout/col' -import { createAnswer } from '../lib/firebase/api-call' +import { createAnswer, placeBet } from '../lib/firebase/api-call' import { Row } from './layout/row' import { Avatar } from './avatar' import { SiteLink } from './site-link' import { DateTimeTooltip } from './datetime-tooltip' import dayjs from 'dayjs' import { BuyButton } from './yes-no-selector' +import { Spacer } from './layout/spacer' +import { + formatMoney, + formatPercent, + formatWithCommas, +} from '../../common/util/format' +import { InfoTooltip } from './info-tooltip' +import { useUser } from '../hooks/use-user' +import { + getProbabilityAfterBet, + getOutcomeProbability, + calculateShares, +} from '../../common/calculate' +import { firebaseLogin } from '../lib/firebase/users' export function AnswersPanel(props: { contract: Contract<'MULTI'> @@ -36,33 +50,188 @@ function AnswerItem(props: { answer: Answer; contract: Contract<'MULTI'> }) { const createdDate = dayjs(createdTime).format('MMM D') + const [isBetting, setIsBetting] = useState(false) + return ( - - -
{answer.text}
+ + + +
{answer.text}
- - - - -
{name}
-
-
+ + + + +
{name}
+
+
-
+
-
- - {createdDate} - -
-
+
+ + {createdDate} + +
+
+ + + { + setIsBetting(true) + }} + /> - {}} - /> + {isBetting && } + + ) +} + +function AnswerBetPanel(props: { + answer: Answer + contract: Contract<'MULTI'> +}) { + const { answer, contract } = props + const { id: answerId } = answer + + const user = useUser() + const [betAmount, setBetAmount] = useState(undefined) + + const [error, setError] = useState() + const [isSubmitting, setIsSubmitting] = useState(false) + const [wasSubmitted, setWasSubmitted] = useState(false) + + const inputRef = useRef(null) + useEffect(() => { + inputRef.current && inputRef.current.focus() + }, []) + + function onBetChange(newAmount: number | undefined) { + setWasSubmitted(false) + setBetAmount(newAmount) + } + + async function submitBet() { + if (!user || !betAmount) return + + if (user.balance < betAmount) { + setError('Insufficient balance') + return + } + + setError(undefined) + setIsSubmitting(true) + + const result = await placeBet({ + amount: betAmount, + outcome: answerId, + contractId: contract.id, + }).then((r) => r.data as any) + + console.log('placed bet. Result:', result) + + if (result?.status === 'success') { + setIsSubmitting(false) + setWasSubmitted(true) + setBetAmount(undefined) + } else { + setError(result?.error || 'Error placing bet') + setIsSubmitting(false) + } + } + + const betDisabled = isSubmitting || !betAmount || error + + const initialProb = getOutcomeProbability(contract.totalShares, answer.id) + + const resultProb = getProbabilityAfterBet( + contract.totalShares, + answerId, + betAmount ?? 0 + ) + + const shares = calculateShares(contract.totalShares, betAmount ?? 0, answerId) + + const currentPayout = betAmount + ? 0 + : // calculatePayoutAfterCorrectBet(contract, { + // outcome: answerId, + // amount: betAmount, + // shares, + // } as Bet) + 0 + + const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0 + const currentReturnPercent = (currentReturn * 100).toFixed() + '%' + + return ( + + +
Amount
+ + + + +
+ Implied probability +
+ +
{formatPercent(initialProb)}
+
+
{formatPercent(resultProb)}
+
+ + + + + Potential payout + + +
+ {formatMoney(currentPayout)} +   (+{currentReturnPercent}) +
+ + + + {user ? ( + + ) : ( + + )} + + {wasSubmitted &&
Trade submitted!
} + ) }