From e61591622e09fa78d7fb87898a7ff89c3690a3fc Mon Sep 17 00:00:00 2001
From: Austin Chen
Date: Sat, 10 Sep 2022 11:50:03 -0700
Subject: [PATCH 1/7] Feature a few other semi-tournaments
---
web/pages/tournaments/index.tsx | 89 +++++++++++++++++++++++++--------
1 file changed, 69 insertions(+), 20 deletions(-)
diff --git a/web/pages/tournaments/index.tsx b/web/pages/tournaments/index.tsx
index e9645957..7b1d8ceb 100644
--- a/web/pages/tournaments/index.tsx
+++ b/web/pages/tournaments/index.tsx
@@ -99,13 +99,6 @@ const tourneys: Tourney[] = [
endTime: toDate('Jan 6, 2023'),
groupId: 'SxGRqXRpV3RAQKudbcNb',
},
- {
- title: 'SF 2022 Ballot',
- blurb: 'Which ballot initiatives will pass this year in SF and CA?',
- award: '',
- endTime: toDate('Nov 8, 2022'),
- groupId: 'VkWZyS5yxs8XWUJrX9eq',
- },
// {
// title: 'Clearer Thinking Regrant Project',
// blurb: 'Something amazing',
@@ -113,6 +106,27 @@ const tourneys: Tourney[] = [
// endTime: toDate('Sep 22, 2022'),
// groupId: '2VsVVFGhKtIdJnQRAXVb',
// },
+
+ // Tournaments without awards get featured belows
+ {
+ title: 'SF 2022 Ballot',
+ blurb: 'Which ballot initiatives will pass this year in SF and CA?',
+ endTime: toDate('Nov 8, 2022'),
+ groupId: 'VkWZyS5yxs8XWUJrX9eq',
+ },
+
+ {
+ title: '2024 Democratic Nominees',
+ blurb: 'How would different Democratic candidates fare in 2024?',
+ endTime: toDate('Nov 2, 2024'),
+ groupId: 'gFhjgFVrnYeFYfxhoLNn',
+ },
+ {
+ title: 'Private Tech Companies',
+ blurb: 'What will these companies exit for?',
+ endTime: toDate('Dec 31, 2030'),
+ groupId: 'faNUnphw6Eoq7OJBRJds',
+ },
]
type SectionInfo = {
@@ -144,19 +158,22 @@ export default function TournamentPage(props: { sections: SectionInfo[] }) {
description="Win money by betting in forecasting touraments on current events, sports, science, and more"
/>
- {sections.map(({ tourney, slug, numPeople }) => (
-
-
- {tourney.blurb}
-
-
- ))}
+ {sections.map(
+ ({ tourney, slug, numPeople }) =>
+ tourney.award && (
+
+
+ {tourney.blurb}
+
+
+ )
+ )}
{Salem.blurb}
+
+ {/* Title break */}
+
+
+
+
+ Featured Groups
+
+
+
+
+ {sections.map(
+ ({ tourney, slug, numPeople }) =>
+ !tourney.award && (
+
+
+ {tourney.blurb}
+
+
+ )
+ )}
)
From 33bcc1a65ef3728db2d4b087c8bdf6401cea7375 Mon Sep 17 00:00:00 2001
From: Austin Chen
Date: Sat, 10 Sep 2022 12:00:01 -0700
Subject: [PATCH 2/7] Clean up /tournaments styling
---
web/pages/tournaments/index.tsx | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/web/pages/tournaments/index.tsx b/web/pages/tournaments/index.tsx
index 7b1d8ceb..27c51c15 100644
--- a/web/pages/tournaments/index.tsx
+++ b/web/pages/tournaments/index.tsx
@@ -169,7 +169,7 @@ export default function TournamentPage(props: { sections: SectionInfo[] }) {
award={tourney.award}
endTime={tourney.endTime}
/>
- {tourney.blurb}
+ {tourney.blurb}
)
@@ -181,7 +181,7 @@ export default function TournamentPage(props: { sections: SectionInfo[] }) {
award={Salem.award}
endTime={Salem.endTime}
/>
- {Salem.blurb}
+ {Salem.blurb}
@@ -211,11 +211,22 @@ export default function TournamentPage(props: { sections: SectionInfo[] }) {
award={tourney.award}
endTime={tourney.endTime}
/>
- {tourney.blurb}
+ {tourney.blurb}
)
)}
+
+
+ We'd love to sponsor more tournaments and groups. Have an idea? Ping{' '}
+
+ Austin on Discord
+
+ !
+
)
@@ -232,9 +243,7 @@ const SectionHeader = (props: {
return (
-
- {title}
-
+ {title}
{!!award && 🏆 {award}}
{!!ppl && (
From e17234ecce46dade2f7db68bf98b7848af7411ff Mon Sep 17 00:00:00 2001
From: mantikoros
Date: Sat, 10 Sep 2022 17:43:52 -0500
Subject: [PATCH 3/7] typo
---
functions/src/email-templates/creating-market.html | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/functions/src/email-templates/creating-market.html b/functions/src/email-templates/creating-market.html
index a61e8d65..df215bdc 100644
--- a/functions/src/email-templates/creating-market.html
+++ b/functions/src/email-templates/creating-market.html
@@ -186,8 +186,9 @@
font-family: Readex Pro, Arial, Helvetica,
sans-serif;
font-size: 17px;
- ">Did you know you create your own prediction market on Manifold for
+ ">Did you know you can create your own prediction market on Manifold on
any question you care about?
From b39e0f304f7472e0e3e2ae2539b9c0f56d26b430 Mon Sep 17 00:00:00 2001
From: Ian Philips
Date: Sat, 10 Sep 2022 17:07:23 -0600
Subject: [PATCH 4/7] Yes and no buttons on contract page (#868)
* Yes and no buttons on contract page
* Cheating by adding 0.05 to max shares but gives better quickbet UX
---
web/components/bet-button.tsx | 23 ++--
web/components/contract/contract-card.tsx | 8 +-
web/components/contract/contract-overview.tsx | 41 ++++--
.../{quick-bet.tsx => quick-bet-arrows.tsx} | 4 +-
web/components/contract/quick-bet-button.tsx | 128 ++++++++++++++++++
5 files changed, 179 insertions(+), 25 deletions(-)
rename web/components/contract/{quick-bet.tsx => quick-bet-arrows.tsx} (98%)
create mode 100644 web/components/contract/quick-bet-button.tsx
diff --git a/web/components/bet-button.tsx b/web/components/bet-button.tsx
index 0bd3702f..2aadbc78 100644
--- a/web/components/bet-button.tsx
+++ b/web/components/bet-button.tsx
@@ -32,6 +32,17 @@ export default function BetButton(props: {
return (
<>
+ {user && (
+
+ {hasYesShares
+ ? `(${Math.floor(yesShares)} ${
+ isPseudoNumeric ? 'HIGHER' : 'YES'
+ })`
+ : hasNoShares
+ ? `(${Math.floor(noShares)} ${isPseudoNumeric ? 'LOWER' : 'NO'})`
+ : ''}
+
+ )}
{user ? (
)}
-
- {user && (
-
- {hasYesShares
- ? `(${Math.floor(yesShares)} ${
- isPseudoNumeric ? 'HIGHER' : 'YES'
- })`
- : hasNoShares
- ? `(${Math.floor(noShares)} ${isPseudoNumeric ? 'LOWER' : 'NO'})`
- : ''}
-
- )}
diff --git a/web/components/contract/contract-card.tsx b/web/components/contract/contract-card.tsx
index dab92a7a..b021d660 100644
--- a/web/components/contract/contract-card.tsx
+++ b/web/components/contract/contract-card.tsx
@@ -25,7 +25,11 @@ import {
} from 'common/calculate'
import { AvatarDetails, MiscDetails, ShowTime } from './contract-details'
import { getExpectedValue, getValueFromBucket } from 'common/calculate-dpm'
-import { getColor, ProbBar, QuickBet } from './quick-bet'
+import {
+ getColor,
+ ProbBar,
+ QuickBetArrows,
+} from 'web/components/contract/quick-bet-arrows'
import { useContractWithPreload } from 'web/hooks/use-contract'
import { useUser } from 'web/hooks/use-user'
import { track } from '@amplitude/analytics-browser'
@@ -101,7 +105,7 @@ export function ContractCard(props: {
))}
{showQuickBet ? (
-
+
) : (
<>
{outcomeType === 'BINARY' && (
diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx
index 1bfe84de..77a9420c 100644
--- a/web/components/contract/contract-overview.tsx
+++ b/web/components/contract/contract-overview.tsx
@@ -27,21 +27,44 @@ import {
} from 'common/contract'
import { ContractDetails, ExtraMobileContractDetails } from './contract-details'
import { NumericGraph } from './numeric-graph'
+import { QuickBetButtons } from 'web/components/contract/quick-bet-button'
const OverviewQuestion = (props: { text: string }) => (
)
const BetWidget = (props: { contract: CPMMContract }) => {
+ const { contract } = props
const user = useUser()
return (
-
-
- {!user && (
-
- (with play money!)
-
- )}
+
+
+ {contract.outcomeType === 'BINARY' &&
+ user &&
+ QuickBetButtons({
+ contract: contract as CPMMBinaryContract,
+ user: user,
+ side: 'NO',
+ className: 'self-end min-w-[60px]',
+ })}
+
+
+ {contract.outcomeType === 'BINARY' &&
+ user &&
+ QuickBetButtons({
+ contract: contract as CPMMBinaryContract,
+ user: user,
+ side: 'YES',
+ className: 'self-end min-w-[60px]',
+ })}
+
+
+ {!user && (
+
+ (with play money!)
+
+ )}
+
)
}
@@ -85,13 +108,13 @@ const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => {
-
{tradingAllowed(contract) && (
)}
+
)
}
@@ -140,11 +163,11 @@ const PseudoNumericOverview = (props: {
-
{tradingAllowed(contract) && }
+
)
}
diff --git a/web/components/contract/quick-bet.tsx b/web/components/contract/quick-bet-arrows.tsx
similarity index 98%
rename from web/components/contract/quick-bet.tsx
rename to web/components/contract/quick-bet-arrows.tsx
index 7b19306f..678963bf 100644
--- a/web/components/contract/quick-bet.tsx
+++ b/web/components/contract/quick-bet-arrows.tsx
@@ -38,7 +38,7 @@ import { getBinaryProb } from 'common/contract-details'
const BET_SIZE = 10
-export function QuickBet(props: {
+export function QuickBetArrows(props: {
contract: BinaryContract | PseudoNumericContract
user: User
className?: string
@@ -243,7 +243,7 @@ export function ProbBar(props: { contract: Contract; previewProb?: number }) {
)
}
-function quickOutcome(contract: Contract, direction: 'UP' | 'DOWN') {
+export function quickOutcome(contract: Contract, direction: 'UP' | 'DOWN') {
const { outcomeType } = contract
if (outcomeType === 'BINARY' || outcomeType === 'PSEUDO_NUMERIC') {
diff --git a/web/components/contract/quick-bet-button.tsx b/web/components/contract/quick-bet-button.tsx
new file mode 100644
index 00000000..d7d5a3a3
--- /dev/null
+++ b/web/components/contract/quick-bet-button.tsx
@@ -0,0 +1,128 @@
+import {
+ getOutcomeProbability,
+ getProbability,
+ getTopAnswer,
+} from 'common/calculate'
+import { getExpectedValue } from 'common/calculate-dpm'
+import { User } from 'common/user'
+import { Contract, CPMMBinaryContract, NumericContract } from 'common/contract'
+import { formatMoney } from 'common/util/format'
+import toast from 'react-hot-toast'
+import { useUserContractBets } from 'web/hooks/use-user-bets'
+import { placeBet } from 'web/lib/firebase/api'
+import { useSaveBinaryShares } from '../use-save-binary-shares'
+import { sellShares } from 'web/lib/firebase/api'
+import { calculateCpmmSale } from 'common/calculate-cpmm'
+import { track } from 'web/lib/service/analytics'
+import { useUnfilledBets } from 'web/hooks/use-bets'
+import { getBinaryProb } from 'common/contract-details'
+import { quickOutcome } from 'web/components/contract/quick-bet-arrows'
+import { Button } from 'web/components/button'
+
+const BET_SIZE = 10
+
+export function QuickBetButtons(props: {
+ contract: CPMMBinaryContract
+ user: User
+ side: 'YES' | 'NO'
+ className?: string
+}) {
+ const { contract, side, user } = props
+ let sharesSold: number | undefined
+ let sellOutcome: 'YES' | 'NO' | undefined
+ let saleAmount: number | undefined
+
+ const userBets = useUserContractBets(user.id, contract.id)
+ const unfilledBets = useUnfilledBets(contract.id) ?? []
+
+ const { yesShares, noShares } = useSaveBinaryShares(contract, userBets)
+ const oppositeShares = side === 'YES' ? noShares : yesShares
+ if (oppositeShares > 0.01) {
+ sellOutcome = side === 'YES' ? 'NO' : 'YES'
+
+ const prob = getProb(contract)
+ const maxSharesSold =
+ (BET_SIZE + 0.05) / (sellOutcome === 'YES' ? prob : 1 - prob)
+ sharesSold = Math.min(oppositeShares, maxSharesSold)
+
+ const { saleValue } = calculateCpmmSale(
+ contract,
+ sharesSold,
+ sellOutcome,
+ unfilledBets
+ )
+ saleAmount = saleValue
+ }
+
+ async function placeQuickBet() {
+ const betPromise = async () => {
+ if (sharesSold && sellOutcome) {
+ return await sellShares({
+ shares: sharesSold,
+ outcome: sellOutcome,
+ contractId: contract.id,
+ })
+ }
+
+ const outcome = quickOutcome(contract, side === 'YES' ? 'UP' : 'DOWN')
+ return await placeBet({
+ amount: BET_SIZE,
+ outcome,
+ contractId: contract.id,
+ })
+ }
+ const shortQ = contract.question.slice(0, 20)
+ const message =
+ sellOutcome && saleAmount
+ ? `${formatMoney(saleAmount)} sold of "${shortQ}"...`
+ : `${formatMoney(BET_SIZE)} on "${shortQ}"...`
+
+ toast.promise(betPromise(), {
+ loading: message,
+ success: message,
+ error: (err) => `${err.message}`,
+ })
+
+ track('quick bet button', {
+ slug: contract.slug,
+ outcome: side,
+ contractId: contract.id,
+ })
+ }
+
+ return (
+
+ )
+}
+
+// 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) {
+ const { outcomeType, resolution, resolutionProbability } = contract
+ return resolutionProbability
+ ? resolutionProbability
+ : resolution
+ ? 1
+ : outcomeType === 'BINARY'
+ ? getBinaryProb(contract)
+ : outcomeType === 'PSEUDO_NUMERIC'
+ ? getProbability(contract)
+ : outcomeType === 'FREE_RESPONSE' || outcomeType === 'MULTIPLE_CHOICE'
+ ? getOutcomeProbability(contract, getTopAnswer(contract)?.id || '')
+ : outcomeType === 'NUMERIC'
+ ? getNumericScale(contract)
+ : 1 // Should not happen
+}
+
+function getNumericScale(contract: NumericContract) {
+ const { min, max } = contract
+ const ev = getExpectedValue(contract)
+ return (ev - min) / (max - min)
+}
From 9ee71733055bbf7ade9b340f98709fc6cd6151aa Mon Sep 17 00:00:00 2001
From: Ian Philips
Date: Sat, 10 Sep 2022 17:48:35 -0600
Subject: [PATCH 5/7] Put sale value above quick bet button
---
web/components/bet-button.tsx | 17 +------
web/components/contract/contract-overview.tsx | 2 +-
web/components/contract/quick-bet-button.tsx | 48 ++++++++++++++-----
3 files changed, 39 insertions(+), 28 deletions(-)
diff --git a/web/components/bet-button.tsx b/web/components/bet-button.tsx
index 2aadbc78..75d12211 100644
--- a/web/components/bet-button.tsx
+++ b/web/components/bet-button.tsx
@@ -23,26 +23,11 @@ export default function BetButton(props: {
const user = useUser()
const userBets = useUserContractBets(user?.id, contract.id)
- const { yesShares, noShares, hasYesShares, hasNoShares } =
- useSaveBinaryShares(contract, userBets)
-
- const { outcomeType } = contract
- const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
+ const { hasYesShares, hasNoShares } = useSaveBinaryShares(contract, userBets)
return (
<>
- {user && (
-
- {hasYesShares
- ? `(${Math.floor(yesShares)} ${
- isPseudoNumeric ? 'HIGHER' : 'YES'
- })`
- : hasNoShares
- ? `(${Math.floor(noShares)} ${isPseudoNumeric ? 'LOWER' : 'NO'})`
- : ''}
-
- )}
{user ? (