Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
336eb54d72 | ||
|
f761d64228 | ||
|
7d47a5910f | ||
|
8e746ce0cb | ||
|
a204380f57 | ||
|
2373f8e009 | ||
|
6551aa7ea4 | ||
|
370f8eefe4 |
|
@ -6,7 +6,6 @@ import {
|
|||
Contract,
|
||||
contractPath,
|
||||
getBinaryProbPercent,
|
||||
listenForContract,
|
||||
} from 'web/lib/firebase/contracts'
|
||||
import { Col } from '../layout/col'
|
||||
import {
|
||||
|
@ -26,8 +25,7 @@ import {
|
|||
import { getOutcomeProbability, getTopAnswer } from 'common/calculate'
|
||||
import { AvatarDetails, MiscDetails } from './contract-details'
|
||||
import { getExpectedValue, getValueFromBucket } from 'common/calculate-dpm'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { QuickBet, QuickOutcomeView, ProbBar, getColor } from './quick-bet'
|
||||
import { QuickBet, ProbBar, getColor } from './quick-bet'
|
||||
import { useContractWithPreload } from 'web/hooks/use-contract'
|
||||
|
||||
export function ContractCard(props: {
|
||||
|
@ -54,7 +52,7 @@ export function ContractCard(props: {
|
|||
className
|
||||
)}
|
||||
>
|
||||
<Row className={clsx(showQuickBet ? 'divide-x' : '')}>
|
||||
<Row className={clsx(showQuickBet ? '' : '')}>
|
||||
<Col className="relative flex-1 gap-3 pr-1">
|
||||
<div
|
||||
className={clsx(
|
||||
|
@ -92,11 +90,31 @@ export function ContractCard(props: {
|
|||
<QuickBet contract={contract} />
|
||||
) : (
|
||||
<Col className="m-auto pl-2">
|
||||
<QuickOutcomeView contract={contract} />
|
||||
{outcomeType === 'BINARY' && (
|
||||
<BinaryResolutionOrChance
|
||||
className="items-center"
|
||||
contract={contract}
|
||||
/>
|
||||
)}
|
||||
|
||||
{outcomeType === 'NUMERIC' && (
|
||||
<NumericResolutionOrExpectation
|
||||
className="items-center"
|
||||
contract={contract as NumericContract}
|
||||
/>
|
||||
)}
|
||||
|
||||
{outcomeType === 'FREE_RESPONSE' && (
|
||||
<FreeResponseResolutionOrChance
|
||||
className="self-end text-gray-600"
|
||||
contract={contract as FullContract<DPM, FreeResponse>}
|
||||
truncate="long"
|
||||
/>
|
||||
)}
|
||||
<ProbBar contract={contract} />
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
<ProbBar contract={contract} />
|
||||
</Col>
|
||||
</div>
|
||||
)
|
||||
|
@ -106,9 +124,8 @@ export function BinaryResolutionOrChance(props: {
|
|||
contract: FullContract<DPM | CPMM, Binary>
|
||||
large?: boolean
|
||||
className?: string
|
||||
hideText?: boolean
|
||||
}) {
|
||||
const { contract, large, className, hideText } = props
|
||||
const { contract, large, className } = props
|
||||
const { resolution } = contract
|
||||
const textColor = `text-${getColor(contract)}`
|
||||
|
||||
|
@ -129,11 +146,9 @@ export function BinaryResolutionOrChance(props: {
|
|||
) : (
|
||||
<>
|
||||
<div className={textColor}>{getBinaryProbPercent(contract)}</div>
|
||||
{!hideText && (
|
||||
<div className={clsx(textColor, large ? 'text-xl' : 'text-base')}>
|
||||
chance
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Col>
|
||||
|
@ -162,9 +177,8 @@ export function FreeResponseResolutionOrChance(props: {
|
|||
contract: FreeResponseContract
|
||||
truncate: 'short' | 'long' | 'none'
|
||||
className?: string
|
||||
hideText?: boolean
|
||||
}) {
|
||||
const { contract, truncate, className, hideText } = props
|
||||
const { contract, truncate, className } = props
|
||||
const { resolution } = contract
|
||||
|
||||
const topAnswer = getTopAnswer(contract)
|
||||
|
@ -189,7 +203,7 @@ export function FreeResponseResolutionOrChance(props: {
|
|||
<div>
|
||||
{formatPercent(getOutcomeProbability(contract, topAnswer.id))}
|
||||
</div>
|
||||
{!hideText && <div className="text-base">chance</div>}
|
||||
<div className="text-base">chance</div>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
|
@ -201,9 +215,8 @@ export function FreeResponseResolutionOrChance(props: {
|
|||
export function NumericResolutionOrExpectation(props: {
|
||||
contract: NumericContract
|
||||
className?: string
|
||||
hideText?: boolean
|
||||
}) {
|
||||
const { contract, className, hideText } = props
|
||||
const { contract, className } = props
|
||||
const { resolution } = contract
|
||||
const textColor = `text-${getColor(contract)}`
|
||||
|
||||
|
@ -222,9 +235,7 @@ export function NumericResolutionOrExpectation(props: {
|
|||
<div className={clsx('text-3xl', textColor)}>
|
||||
{formatLargeNumber(getExpectedValue(contract))}
|
||||
</div>
|
||||
{!hideText && (
|
||||
<div className={clsx('text-base', textColor)}>expected</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Col>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import clsx from 'clsx'
|
||||
import { getOutcomeProbability, getTopAnswer } from 'common/calculate'
|
||||
import {
|
||||
getOutcomeProbability,
|
||||
getOutcomeProbabilityAfterBet,
|
||||
getTopAnswer,
|
||||
} from 'common/calculate'
|
||||
import { getExpectedValue } from 'common/calculate-dpm'
|
||||
import {
|
||||
Contract,
|
||||
|
@ -8,57 +12,50 @@ import {
|
|||
DPM,
|
||||
Binary,
|
||||
NumericContract,
|
||||
FreeResponse,
|
||||
FreeResponseContract,
|
||||
} from 'common/contract'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import {
|
||||
formatLargeNumber,
|
||||
formatMoney,
|
||||
formatPercent,
|
||||
} from 'common/util/format'
|
||||
import { useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { useUserContractBets } from 'web/hooks/use-user-bets'
|
||||
import { placeBet } from 'web/lib/firebase/api-call'
|
||||
import { getBinaryProb } from 'web/lib/firebase/contracts'
|
||||
import { getBinaryProb, getBinaryProbPercent } from 'web/lib/firebase/contracts'
|
||||
import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon'
|
||||
import TriangleFillIcon from 'web/lib/icons/triangle-fill-icon'
|
||||
import { Col } from '../layout/col'
|
||||
import { OUTCOME_TO_COLOR } from '../outcome-label'
|
||||
import { useSaveShares } from '../use-save-shares'
|
||||
import {
|
||||
BinaryResolutionOrChance,
|
||||
NumericResolutionOrExpectation,
|
||||
FreeResponseResolutionOrChance,
|
||||
} from './contract-card'
|
||||
|
||||
export function QuickBet(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
|
||||
const user = useUser()
|
||||
const userBets = useUserContractBets(user?.id, contract.id)
|
||||
const { yesFloorShares, noFloorShares, yesShares, noShares } = useSaveShares(
|
||||
contract as FullContract<CPMM | DPM, Binary>,
|
||||
userBets
|
||||
)
|
||||
// TODO: For some reason, Floor Shares are inverted for non-BINARY markets
|
||||
const hasUpShares =
|
||||
contract.outcomeType === 'BINARY' ? yesFloorShares : noFloorShares
|
||||
const hasDownShares =
|
||||
contract.outcomeType === 'BINARY' ? noFloorShares : yesFloorShares
|
||||
|
||||
const color = getColor(contract)
|
||||
|
||||
async function placeQuickBet(direction: 'UP' | 'DOWN') {
|
||||
const betPromise = async () => {
|
||||
const outcome = quickOutcome(contract, direction)
|
||||
return await placeBet({
|
||||
amount: 10,
|
||||
outcome,
|
||||
contractId: contract.id,
|
||||
})
|
||||
function previewProb(contract: Contract, amount: number) {
|
||||
if (amount === 0) {
|
||||
return getProb(contract)
|
||||
}
|
||||
try {
|
||||
if (amount > 0) {
|
||||
return getOutcomeProbabilityAfterBet(
|
||||
contract,
|
||||
quickOutcome(contract, 'UP') || '',
|
||||
amount
|
||||
)
|
||||
}
|
||||
if (amount < 0) {
|
||||
return (
|
||||
1 -
|
||||
getOutcomeProbabilityAfterBet(
|
||||
contract,
|
||||
quickOutcome(contract, 'DOWN') || '',
|
||||
-amount
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
return getProb(contract)
|
||||
}
|
||||
const shortQ = contract.question.slice(0, 20)
|
||||
toast.promise(betPromise(), {
|
||||
loading: `${formatMoney(10)} on "${shortQ}"...`,
|
||||
success: `${formatMoney(10)} on "${shortQ}"...`,
|
||||
error: (err) => `${err.message}`,
|
||||
})
|
||||
}
|
||||
|
||||
function quickOutcome(contract: Contract, direction: 'UP' | 'DOWN') {
|
||||
|
@ -78,19 +75,77 @@ export function QuickBet(props: { contract: Contract }) {
|
|||
}
|
||||
}
|
||||
|
||||
export function QuickBet(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
|
||||
const user = useUser()
|
||||
const userBets = useUserContractBets(user?.id, contract.id)
|
||||
const { yesFloorShares, noFloorShares } = useSaveShares(
|
||||
contract as FullContract<CPMM | DPM, Binary>,
|
||||
userBets
|
||||
)
|
||||
// TODO: For some reason, Floor Shares are inverted for non-BINARY markets
|
||||
const hasUpShares =
|
||||
contract.outcomeType === 'BINARY' ? yesFloorShares : noFloorShares
|
||||
const hasDownShares =
|
||||
contract.outcomeType === 'BINARY' ? noFloorShares : yesFloorShares
|
||||
|
||||
const [upHover, setUpHover] = useState(false)
|
||||
const [downHover, setDownHover] = useState(false)
|
||||
|
||||
let previewProb = undefined
|
||||
try {
|
||||
previewProb = upHover
|
||||
? getOutcomeProbabilityAfterBet(
|
||||
contract,
|
||||
quickOutcome(contract, 'UP') || '',
|
||||
10
|
||||
)
|
||||
: downHover
|
||||
? 1 -
|
||||
getOutcomeProbabilityAfterBet(
|
||||
contract,
|
||||
quickOutcome(contract, 'DOWN') || '',
|
||||
10
|
||||
)
|
||||
: undefined
|
||||
} catch (e) {
|
||||
// Catch any errors from hovering on an invalid option
|
||||
}
|
||||
|
||||
const color = getColor(contract, previewProb)
|
||||
|
||||
async function placeQuickBet(direction: 'UP' | 'DOWN') {
|
||||
const betPromise = async () => {
|
||||
const outcome = quickOutcome(contract, direction)
|
||||
return await placeBet({
|
||||
amount: 10,
|
||||
outcome,
|
||||
contractId: contract.id,
|
||||
})
|
||||
}
|
||||
const shortQ = contract.question.slice(0, 20)
|
||||
toast.promise(betPromise(), {
|
||||
loading: `${formatMoney(10)} on "${shortQ}"...`,
|
||||
success: `${formatMoney(10)} on "${shortQ}"...`,
|
||||
error: (err) => `${err.message}`,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Col
|
||||
className={clsx(
|
||||
'relative -my-4 -mr-5 min-w-[6rem] justify-center gap-2 pr-5 pl-3 align-middle',
|
||||
'relative -my-4 -mr-5 min-w-[6rem] justify-center gap-2 pr-5 pl-3 align-middle'
|
||||
// Use this for colored QuickBet panes
|
||||
// `bg-opacity-10 bg-${color}`
|
||||
'bg-gray-50'
|
||||
// upHover || downHover ? `bg-${color} bg-opacity-5` : 'bg-gray-50'
|
||||
)}
|
||||
>
|
||||
{/* Up bet triangle */}
|
||||
<div>
|
||||
<div
|
||||
className="peer absolute top-0 left-0 right-0 h-[50%]"
|
||||
onMouseEnter={() => setUpHover(true)}
|
||||
onMouseLeave={() => setUpHover(false)}
|
||||
onClick={() => placeQuickBet('UP')}
|
||||
></div>
|
||||
<div className="mt-2 text-center text-xs text-transparent peer-hover:text-gray-400">
|
||||
|
@ -109,12 +164,14 @@ export function QuickBet(props: { contract: Contract }) {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<QuickOutcomeView contract={contract} />
|
||||
<QuickOutcomeView contract={contract} previewProb={previewProb} />
|
||||
|
||||
{/* Down bet triangle */}
|
||||
<div>
|
||||
<div
|
||||
className="peer absolute bottom-0 left-0 right-0 h-[50%]"
|
||||
onMouseEnter={() => setDownHover(true)}
|
||||
onMouseLeave={() => setDownHover(false)}
|
||||
onClick={() => placeQuickBet('DOWN')}
|
||||
></div>
|
||||
{hasDownShares > 0 ? (
|
||||
|
@ -135,22 +192,22 @@ export function QuickBet(props: { contract: Contract }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function ProbBar(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
const color = getColor(contract)
|
||||
const prob = getProb(contract)
|
||||
export function ProbBar(props: { contract: Contract; previewProb?: number }) {
|
||||
const { contract, previewProb } = props
|
||||
const color = getColor(contract, previewProb)
|
||||
const prob = previewProb ?? getProb(contract)
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute right-0 top-0 w-2 rounded-tr-md transition-all',
|
||||
'absolute right-0 top-0 w-4 rounded-tr-md transition-all',
|
||||
'bg-gray-200'
|
||||
)}
|
||||
style={{ height: `${100 * (1 - prob)}%` }}
|
||||
></div>
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute right-0 bottom-0 w-2 rounded-br-md transition-all',
|
||||
'absolute right-0 bottom-0 w-4 rounded-br-md transition-all',
|
||||
`bg-${color}`,
|
||||
// If we're showing the full bar, also round the top
|
||||
prob === 1 ? 'rounded-tr-md' : ''
|
||||
|
@ -161,39 +218,112 @@ export function ProbBar(props: { contract: Contract }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function QuickOutcomeView(props: { contract: Contract }) {
|
||||
export function LiquidityBar(props: {
|
||||
contract: Contract
|
||||
previewProb?: number
|
||||
}) {
|
||||
const { contract } = props
|
||||
const { outcomeType } = contract
|
||||
const color = getColor(contract)
|
||||
const up10 = previewProb(contract, 10)
|
||||
const up100 = previewProb(contract, 100)
|
||||
const down10 = previewProb(contract, -10)
|
||||
const down100 = previewProb(contract, -100)
|
||||
console.log('up, down', up10, down10)
|
||||
return (
|
||||
<>
|
||||
{outcomeType === 'BINARY' && (
|
||||
<BinaryResolutionOrChance
|
||||
className="items-center"
|
||||
contract={contract}
|
||||
hideText
|
||||
/>
|
||||
{/* Gray background */}
|
||||
{/* <div
|
||||
className={clsx(
|
||||
'absolute right-0 top-0 h-full w-2 rounded-r-md transition-all',
|
||||
'bg-gray-100 bg-opacity-50'
|
||||
)}
|
||||
></div> */}
|
||||
{/* indigo-200 liquidity range for 100 */}
|
||||
|
||||
{outcomeType === 'NUMERIC' && (
|
||||
<NumericResolutionOrExpectation
|
||||
className="items-center"
|
||||
contract={contract as NumericContract}
|
||||
hideText
|
||||
/>
|
||||
{/* indigo-400 liquidity range for 10 */}
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute -right-1 w-1 transition-all',
|
||||
`bg-blue-300 bg-opacity-100`
|
||||
)}
|
||||
style={{
|
||||
height: `${100 * (up100 - down100)}%`,
|
||||
bottom: `${100 * down100}%`,
|
||||
}}
|
||||
></div>
|
||||
|
||||
{outcomeType === 'FREE_RESPONSE' && (
|
||||
<FreeResponseResolutionOrChance
|
||||
className="self-end text-gray-600"
|
||||
contract={contract as FullContract<DPM, FreeResponse>}
|
||||
truncate="long"
|
||||
hideText
|
||||
/>
|
||||
{/* indigo-400 liquidity range for 10 */}
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute -right-1.5 w-1.5 transition-all',
|
||||
`bg-blue-600 bg-opacity-100`
|
||||
)}
|
||||
style={{
|
||||
height: `${100 * (up10 - down10)}%`,
|
||||
bottom: `${100 * down10}%`,
|
||||
}}
|
||||
></div>
|
||||
|
||||
{/* Try a tailwind gradient for 100 */}
|
||||
{/* <div
|
||||
className="absolute right-0 w-2 bg-gradient-to-b from-red-50 via-red-500 to-red-500 opacity-50"
|
||||
style={{
|
||||
height: `${100 * (up100 - down100)}%`,
|
||||
bottom: `${100 * down100}%`,
|
||||
}}
|
||||
></div> */}
|
||||
|
||||
{/* <div
|
||||
className={clsx(
|
||||
'absolute right-0 bottom-0 w-24 rounded-br-md transition-all',
|
||||
`bg-indigo-500 bg-opacity-20`,
|
||||
// If we're showing the full bar, also round the top
|
||||
prob === 1 ? 'rounded-tr-md' : ''
|
||||
)}
|
||||
style={{ height: `${100 * prob}%` }}
|
||||
></div> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function QuickOutcomeView(props: {
|
||||
contract: Contract
|
||||
previewProb?: number
|
||||
caption?: 'chance' | 'expected'
|
||||
}) {
|
||||
const { contract, previewProb, caption } = props
|
||||
const { outcomeType } = contract
|
||||
// If there's a preview prob, display that instead of the current prob
|
||||
const override =
|
||||
previewProb === undefined ? undefined : formatPercent(previewProb)
|
||||
const textColor = `text-${getColor(contract, previewProb)}`
|
||||
|
||||
let display: string | undefined
|
||||
switch (outcomeType) {
|
||||
case 'BINARY':
|
||||
display = getBinaryProbPercent(contract)
|
||||
break
|
||||
case 'NUMERIC':
|
||||
display = formatLargeNumber(getExpectedValue(contract as NumericContract))
|
||||
break
|
||||
case 'FREE_RESPONSE':
|
||||
const topAnswer = getTopAnswer(contract as FreeResponseContract)
|
||||
display =
|
||||
topAnswer &&
|
||||
formatPercent(getOutcomeProbability(contract, topAnswer.id))
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<Col className={clsx('items-center text-3xl', textColor)}>
|
||||
{override ?? display}
|
||||
{caption && <div className="text-base">{caption}</div>}
|
||||
<ProbBar contract={contract} previewProb={previewProb} />
|
||||
<LiquidityBar contract={contract} previewProb={previewProb} />
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
// Return a number from 0 to 1 for this contract
|
||||
// Resolved contracts are set to 1, for coloring purposes (even if NO)
|
||||
function getProb(contract: Contract) {
|
||||
|
@ -215,7 +345,7 @@ function getNumericScale(contract: NumericContract) {
|
|||
return (ev - min) / (max - min)
|
||||
}
|
||||
|
||||
export function getColor(contract: Contract) {
|
||||
export function getColor(contract: Contract, previewProb?: number) {
|
||||
// TODO: Not sure why eg green-400 doesn't work here; try upgrading Tailwind
|
||||
// TODO: Try injecting a gradient here
|
||||
// return 'primary'
|
||||
|
@ -233,9 +363,6 @@ export function getColor(contract: Contract) {
|
|||
}
|
||||
|
||||
const marketClosed = (contract.closeTime || Infinity) < Date.now()
|
||||
return marketClosed
|
||||
? 'gray-400'
|
||||
: getProb(contract) >= 0.5
|
||||
? 'primary'
|
||||
: 'red-400'
|
||||
const prob = previewProb ?? getProb(contract)
|
||||
return marketClosed ? 'gray-400' : prob >= 0.5 ? 'primary' : 'red-400'
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user