diff --git a/common/contract.ts b/common/contract.ts index 35566c0b..77568f49 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -29,6 +29,7 @@ export type FullContract< closeEmailsSent?: number + volume: number volume24Hours: number volume7Days: number diff --git a/common/new-contract.ts b/common/new-contract.ts index 3b3278c2..ffd27e3f 100644 --- a/common/new-contract.ts +++ b/common/new-contract.ts @@ -34,6 +34,8 @@ export function getNewContract( ? getBinaryCpmmProps(initialProb, ante) // getBinaryDpmProps(initialProb, ante) : getFreeAnswerProps(ante) + const volume = outcomeType === 'BINARY' ? 0 : ante + const contract: Contract = removeUndefinedProps({ id, slug, @@ -54,6 +56,7 @@ export function getNewContract( lastUpdatedTime: Date.now(), closeTime, + volume, volume24Hours: 0, volume7Days: 0, diff --git a/common/util/array.ts b/common/util/array.ts index fba342aa..d81edba1 100644 --- a/common/util/array.ts +++ b/common/util/array.ts @@ -1,3 +1,3 @@ export function filterDefined(array: (T | null | undefined)[]) { - return array.filter((item) => item) as T[] + return array.filter((item) => item !== null && item !== undefined) as T[] } diff --git a/common/util/format.ts b/common/util/format.ts index 7f352cf2..05a8f702 100644 --- a/common/util/format.ts +++ b/common/util/format.ts @@ -18,20 +18,10 @@ export function formatWithCommas(amount: number) { return formatter.format(amount).replace('$', '') } -const decimalPlaces = (x: number) => Math.ceil(-Math.log10(x)) - 2 - -export function formatPercent(decimalPercent: number) { - const displayedFigs = - (decimalPercent >= 0.02 && decimalPercent <= 0.98) || - decimalPercent <= 0 || - decimalPercent >= 1 - ? 0 - : Math.max( - decimalPlaces(decimalPercent), - decimalPlaces(1 - decimalPercent) - ) - - return (decimalPercent * 100).toFixed(displayedFigs) + '%' +export function formatPercent(zeroToOne: number) { + // Show 1 decimal place if <2% or >98%, giving more resolution on the tails + const decimalPlaces = zeroToOne < 0.02 || zeroToOne > 0.98 ? 1 : 0 + return (zeroToOne * 100).toFixed(decimalPlaces) + '%' } export function toCamelCase(words: string) { diff --git a/common/util/types.ts b/common/util/types.ts new file mode 100644 index 00000000..865cb8f3 --- /dev/null +++ b/common/util/types.ts @@ -0,0 +1,5 @@ +export type FirstArgument = T extends (arg1: infer U, ...args: any[]) => any + ? U + : any + +export type Truthy = Exclude diff --git a/functions/src/create-answer.ts b/functions/src/create-answer.ts index 17d085f5..fbde2666 100644 --- a/functions/src/create-answer.ts +++ b/functions/src/create-answer.ts @@ -57,7 +57,7 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall( message: 'Requires a free response contract', } - const { closeTime } = contract + const { closeTime, volume } = contract if (closeTime && Date.now() > closeTime) return { status: 'error', message: 'Trading is closed' } @@ -121,6 +121,7 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall( totalShares: newTotalShares, totalBets: newTotalBets, answers: [...(contract.answers ?? []), answer], + volume: volume + amount, }) if (!isFinite(newBalance)) { diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts index 1a79287d..68789096 100644 --- a/functions/src/place-bet.ts +++ b/functions/src/place-bet.ts @@ -49,7 +49,8 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( return { status: 'error', message: 'Invalid contract' } const contract = contractSnap.data() as Contract - const { closeTime, outcomeType, mechanism, collectedFees } = contract + const { closeTime, outcomeType, mechanism, collectedFees, volume } = + contract if (closeTime && Date.now() > closeTime) return { status: 'error', message: 'Trading is closed' } @@ -129,6 +130,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( totalBets: newTotalBets, totalLiquidity: newTotalLiquidity, collectedFees: addObjects(fees ?? {}, collectedFees ?? {}), + volume: volume + Math.abs(amount), }) ) diff --git a/functions/src/sell-bet.ts b/functions/src/sell-bet.ts index a5bb2af9..4b31cfde 100644 --- a/functions/src/sell-bet.ts +++ b/functions/src/sell-bet.ts @@ -35,7 +35,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( return { status: 'error', message: 'Invalid contract' } const contract = contractSnap.data() as Contract - const { closeTime, mechanism, collectedFees } = contract + const { closeTime, mechanism, collectedFees, volume } = contract if (closeTime && Date.now() > closeTime) return { status: 'error', message: 'Trading is closed' } @@ -81,6 +81,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( totalShares: newTotalShares, totalBets: newTotalBets, collectedFees: addObjects(fees ?? {}, collectedFees ?? {}), + volume: volume + bet.amount, }) ) diff --git a/web/components/analytics/charts.tsx b/web/components/analytics/charts.tsx index 3a315d38..4bf8d52b 100644 --- a/web/components/analytics/charts.tsx +++ b/web/components/analytics/charts.tsx @@ -21,9 +21,11 @@ export function DailyCountChart(props: { })) const data = [{ id: 'Count', data: points, color: '#11b981' }] + const bottomAxisTicks = width && width < 600 ? 6 : undefined + return (
= 800) ? 400 : 250 }} > dayjs(date).format('MMM DD'), }} colors={{ datum: 'color' }} - pointSize={width && width >= 800 ? 10 : 0} + pointSize={0} + pointBorderWidth={1} + pointBorderColor="#fff" + enableSlices="x" + enableGridX={!!width && width >= 800} + enableArea + margin={{ top: 20, right: 28, bottom: 22, left: 40 }} + /> +
+ ) +} + +export function DailyPercentChart(props: { + startDate: number + dailyPercent: number[] + small?: boolean +}) { + const { dailyPercent, startDate, small } = props + const { width } = useWindowSize() + + const dates = dailyPercent.map((_, i) => + dayjs(startDate).add(i, 'day').toDate() + ) + + const points = _.zip(dates, dailyPercent).map(([date, betCount]) => ({ + x: date, + y: betCount, + })) + const data = [{ id: 'Percent', data: points, color: '#11b981' }] + + const bottomAxisTicks = width && width < 600 ? 6 : undefined + + return ( +
= 800) ? 400 : 250 }} + > + `${value}%`, + }} + axisBottom={{ + tickValues: bottomAxisTicks, + format: (date) => dayjs(date).format('MMM DD'), + }} + colors={{ datum: 'color' }} + pointSize={0} pointBorderWidth={1} pointBorderColor="#fff" enableSlices="x" diff --git a/web/components/answers/answer-bet-panel.tsx b/web/components/answers/answer-bet-panel.tsx index 076884d3..37a20946 100644 --- a/web/components/answers/answer-bet-panel.tsx +++ b/web/components/answers/answer-bet-panel.tsx @@ -63,7 +63,7 @@ export function AnswerBetPanel(props: { if (result?.status === 'success') { setIsSubmitting(false) - closePanel() + setBetAmount(undefined) } else { setError(result?.error || 'Error placing bet') setIsSubmitting(false) @@ -134,9 +134,11 @@ export function AnswerBetPanel(props: { - + -
Payout if chosen
+
+ Estimated
payout if chosen +
bets: Bet[] + height?: number }) { - const { contract } = props + const { contract, height } = props const { createdTime, resolutionTime, closeTime, answers } = contract const bets = useBets(contract.id) ?? props.bets @@ -86,7 +87,7 @@ export function AnswersGraph(props: { return (
= 800 ? 350 : 225 }} + style={{ height: height ?? (!width || width >= 800 ? 350 : 225) }} > ) => { + const { totalBets } = contract + const betsByOutcome = _.groupBy(bets, (bet) => bet.outcome) const outcomes = Object.keys(betsByOutcome).filter((outcome) => { const maxProb = Math.max( ...betsByOutcome[outcome].map((bet) => bet.probAfter) ) - return outcome !== '0' && maxProb > 0.05 + return outcome !== '0' && maxProb > 0.02 && totalBets[outcome] > 0.000000001 }) const trackedOutcomes = _.sortBy( diff --git a/web/components/answers/create-answer-panel.tsx b/web/components/answers/create-answer-panel.tsx index 49f680a3..4e7c179d 100644 --- a/web/components/answers/create-answer-panel.tsx +++ b/web/components/answers/create-answer-panel.tsx @@ -81,7 +81,7 @@ export function CreateAnswerPanel(props: {