Answers graph (#65)
* In progress * Calculate correct graph probabilites
This commit is contained in:
		
							parent
							
								
									f17c4ac40f
								
							
						
					
					
						commit
						1bb5d3b8cd
					
				
							
								
								
									
										177
									
								
								web/components/answers/answers-graph.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								web/components/answers/answers-graph.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,177 @@ | |||
| import { DatumValue } from '@nivo/core' | ||||
| import { ResponsiveLine } from '@nivo/line' | ||||
| import dayjs from 'dayjs' | ||||
| import _ from 'lodash' | ||||
| 
 | ||||
| import { Bet } from '../../../common/bet' | ||||
| import { Contract } from '../../../common/contract' | ||||
| import { getOutcomeProbability } from '../../../common/calculate' | ||||
| import { useBets } from '../../hooks/use-bets' | ||||
| import { useWindowSize } from '../../hooks/use-window-size' | ||||
| 
 | ||||
| export function AnswersGraph(props: { contract: Contract; bets: Bet[] }) { | ||||
|   const { contract } = props | ||||
|   const { resolutionTime, closeTime, answers, totalShares } = contract | ||||
| 
 | ||||
|   const bets = (useBets(contract.id) ?? props.bets).filter((bet) => !bet.sale) | ||||
| 
 | ||||
|   const { probsByOutcome, sortedOutcomes } = computeProbsByOutcome( | ||||
|     bets, | ||||
|     totalShares | ||||
|   ) | ||||
| 
 | ||||
|   const isClosed = !!closeTime && Date.now() > closeTime | ||||
|   const latestTime = dayjs( | ||||
|     resolutionTime && isClosed | ||||
|       ? Math.min(resolutionTime, closeTime) | ||||
|       : isClosed | ||||
|       ? closeTime | ||||
|       : resolutionTime ?? Date.now() | ||||
|   ) | ||||
| 
 | ||||
|   const { width } = useWindowSize() | ||||
| 
 | ||||
|   const labelLength = !width || width > 800 ? 75 : 20 | ||||
| 
 | ||||
|   const colors = ['#2a81e3', '#c72ae3', '#b91111', '#f3ad28', '#11b981'] | ||||
| 
 | ||||
|   const maxProb = _.max( | ||||
|     Object.values(probsByOutcome).map((probs) => Math.max(...probs)) | ||||
|   ) | ||||
|   const yMax = Math.min(100, Math.max(50, (maxProb ?? 0) * 100 * 1.2)) | ||||
| 
 | ||||
|   const times = _.sortBy([ | ||||
|     contract.createdTime, | ||||
|     ...bets.map((bet) => bet.createdTime), | ||||
|   ]) | ||||
|   const dateTimes = times.map((time) => new Date(time)) | ||||
| 
 | ||||
|   const data = sortedOutcomes.map((outcome, i) => { | ||||
|     const probs = probsByOutcome[outcome] | ||||
| 
 | ||||
|     if (resolutionTime || isClosed) { | ||||
|       dateTimes.push(latestTime.toDate()) | ||||
|       probs.push(probs[probs.length - 1]) | ||||
|     } else { | ||||
|       // Add a fake datapoint in future so the line continues horizontally
 | ||||
|       // to the right.
 | ||||
|       dateTimes.push(latestTime.add(1, 'month').toDate()) | ||||
|       probs.push(probs[probs.length - 1]) | ||||
|     } | ||||
| 
 | ||||
|     const points = probs.map((prob, i) => ({ | ||||
|       x: dateTimes[i], | ||||
|       y: Math.round(prob * 100), | ||||
|     })) | ||||
| 
 | ||||
|     const answer = | ||||
|       answers?.find((answer) => answer.id === outcome)?.text ?? 'None' | ||||
|     const answerText = | ||||
|       answer.slice(0, labelLength) + (answer.length > labelLength ? '...' : '') | ||||
|     const id = `#${outcome}: ${answerText}` | ||||
| 
 | ||||
|     return { id, data: points, color: colors[i] } | ||||
|   }) | ||||
| 
 | ||||
|   data.reverse() | ||||
| 
 | ||||
|   const yTickValues = [0, 25, 50, 75, 100] | ||||
| 
 | ||||
|   const numXTickValues = !width || width < 800 ? 2 : 5 | ||||
|   const hoursAgo = latestTime.subtract(5, 'hours') | ||||
|   const startDate = dayjs(contract.createdTime).isBefore(hoursAgo) | ||||
|     ? new Date(contract.createdTime) | ||||
|     : hoursAgo.toDate() | ||||
| 
 | ||||
|   const lessThanAWeek = dayjs(startDate).add(1, 'week').isAfter(latestTime) | ||||
| 
 | ||||
|   return ( | ||||
|     <div | ||||
|       className="w-full overflow-hidden" | ||||
|       style={{ height: !width || width >= 800 ? 350 : 225 }} | ||||
|     > | ||||
|       <ResponsiveLine | ||||
|         data={data} | ||||
|         yScale={{ min: 0, max: yMax, type: 'linear' }} | ||||
|         yFormat={formatPercent} | ||||
|         gridYValues={yTickValues} | ||||
|         axisLeft={{ | ||||
|           tickValues: yTickValues, | ||||
|           format: formatPercent, | ||||
|         }} | ||||
|         xScale={{ | ||||
|           type: 'time', | ||||
|           min: startDate, | ||||
|           max: latestTime.toDate(), | ||||
|         }} | ||||
|         xFormat={(d) => formatTime(+d.valueOf(), lessThanAWeek)} | ||||
|         axisBottom={{ | ||||
|           tickValues: numXTickValues, | ||||
|           format: (time) => formatTime(+time, lessThanAWeek), | ||||
|         }} | ||||
|         colors={{ datum: 'color' }} | ||||
|         pointSize={0} | ||||
|         enableSlices="x" | ||||
|         enableGridX={!!width && width >= 800} | ||||
|         enableArea | ||||
|         margin={{ top: 20, right: 28, bottom: 22, left: 40 }} | ||||
|       /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| function formatPercent(y: DatumValue) { | ||||
|   return `${Math.round(+y.toString())}%` | ||||
| } | ||||
| 
 | ||||
| function formatTime(time: number, includeTime: boolean) { | ||||
|   const d = dayjs(time) | ||||
| 
 | ||||
|   if (d.isSame(Date.now(), 'day')) return d.format('ha') | ||||
| 
 | ||||
|   if (includeTime) return dayjs(time).format('MMM D, ha') | ||||
| 
 | ||||
|   return dayjs(time).format('MMM D') | ||||
| } | ||||
| 
 | ||||
| const computeProbsByOutcome = ( | ||||
|   bets: Bet[], | ||||
|   totalShares: { [outcome: string]: number } | ||||
| ) => { | ||||
|   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 | ||||
|   }) | ||||
| 
 | ||||
|   const trackedOutcomes = _.sortBy( | ||||
|     outcomes, | ||||
|     (outcome) => -1 * getOutcomeProbability(totalShares, outcome) | ||||
|   ).slice(0, 5) | ||||
| 
 | ||||
|   const probsByOutcome = _.fromPairs( | ||||
|     trackedOutcomes.map((outcome) => [outcome, [0]]) | ||||
|   ) | ||||
|   const sharesByOutcome = _.fromPairs( | ||||
|     Object.keys(betsByOutcome).map((outcome) => [outcome, 0]) | ||||
|   ) | ||||
| 
 | ||||
|   for (const bet of bets) { | ||||
|     const { outcome, shares } = bet | ||||
|     sharesByOutcome[outcome] += shares | ||||
| 
 | ||||
|     const sharesSquared = _.sumBy( | ||||
|       Object.values(sharesByOutcome).map((shares) => shares ** 2) | ||||
|     ) | ||||
| 
 | ||||
|     for (const outcome of trackedOutcomes) { | ||||
|       probsByOutcome[outcome].push( | ||||
|         sharesByOutcome[outcome] ** 2 / sharesSquared | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return { probsByOutcome, sortedOutcomes: trackedOutcomes } | ||||
| } | ||||
|  | @ -19,6 +19,7 @@ import BetRow from './bet-row' | |||
| import { Fold } from '../../common/fold' | ||||
| import { FoldTagList } from './tags-list' | ||||
| import { ContractActivity } from './feed/contract-activity' | ||||
| import { AnswersGraph } from './answers/answers-graph' | ||||
| 
 | ||||
| export const ContractOverview = (props: { | ||||
|   contract: Contract | ||||
|  | @ -77,7 +78,11 @@ export const ContractOverview = (props: { | |||
| 
 | ||||
|       <Spacer h={4} /> | ||||
| 
 | ||||
|       {isBinary && <ContractProbGraph contract={contract} bets={bets} />} | ||||
|       {isBinary ? ( | ||||
|         <ContractProbGraph contract={contract} bets={bets} /> | ||||
|       ) : ( | ||||
|         <AnswersGraph contract={contract} bets={bets} /> | ||||
|       )} | ||||
| 
 | ||||
|       {children} | ||||
| 
 | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ export function ContractProbGraph(props: { contract: Contract; bets: Bet[] }) { | |||
| 
 | ||||
|   return ( | ||||
|     <div | ||||
|       className="w-full" | ||||
|       className="w-full overflow-hidden" | ||||
|       style={{ height: !width || width >= 800 ? 400 : 250 }} | ||||
|     > | ||||
|       <ResponsiveLine | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user