Deduct user balance only on each fill. Store orderAmount of bet. Timestamp of fills.
This commit is contained in:
parent
a2a655063a
commit
34b80074a3
|
@ -1,4 +1,3 @@
|
|||
import { Truthy } from 'lodash'
|
||||
import { Fees } from './fees'
|
||||
|
||||
export type Bet = {
|
||||
|
@ -27,17 +26,10 @@ export type Bet = {
|
|||
isAnte?: boolean
|
||||
isLiquidityProvision?: boolean
|
||||
isRedemption?: boolean
|
||||
|
||||
// A record of each transaction that partially (or fully) fills the bet amount.
|
||||
// I.e. A limit order could be filled by partially matching with several bets.
|
||||
// Non-limit orders can also be filled by matching with multiple limit orders.
|
||||
fills?: {
|
||||
// The id the bet matched against, or null if the bet was matched by
|
||||
// the pool.
|
||||
matchedBetId: string | null
|
||||
amount: number
|
||||
shares: number
|
||||
}[]
|
||||
fills?: fill[]
|
||||
}
|
||||
|
||||
export type NumericBet = Bet & {
|
||||
|
@ -48,10 +40,19 @@ export type NumericBet = Bet & {
|
|||
|
||||
// Binary market limit order.
|
||||
export type LimitBet = Bet & {
|
||||
orderAmount: number // Amount of limit order.
|
||||
limitProb: number // [0, 1]. Bet to this probability.
|
||||
isFilled: boolean // Whether all of the bet amount has been filled.
|
||||
isCancelled: boolean // Whether to prevent any further fills.
|
||||
fills: Truthy<Bet['fills']>
|
||||
fills: fill[]
|
||||
}
|
||||
|
||||
export type fill = {
|
||||
// The id the bet matched against, or null if the bet was matched by the pool.
|
||||
matchedBetId: string | null
|
||||
amount: number
|
||||
shares: number
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export const MAX_LOAN_PER_CONTRACT = 20
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { sortBy, sumBy } from 'lodash'
|
||||
|
||||
import { Bet, LimitBet, MAX_LOAN_PER_CONTRACT, NumericBet } from './bet'
|
||||
import { Bet, fill, LimitBet, MAX_LOAN_PER_CONTRACT, NumericBet } from './bet'
|
||||
import {
|
||||
calculateDpmShares,
|
||||
getDpmProbability,
|
||||
|
@ -62,6 +62,8 @@ const computeFill = (
|
|||
return undefined
|
||||
}
|
||||
|
||||
const timestamp = Date.now()
|
||||
|
||||
if (
|
||||
!matchedBet ||
|
||||
(outcome === 'YES'
|
||||
|
@ -103,18 +105,19 @@ const computeFill = (
|
|||
amount: poolAmount,
|
||||
state: newState,
|
||||
fees,
|
||||
timestamp,
|
||||
},
|
||||
taker: {
|
||||
matchedBetId: null,
|
||||
shares,
|
||||
amount: poolAmount,
|
||||
timestamp,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Fill from matchedBet.
|
||||
const matchRemaining =
|
||||
matchedBet.amount - sumBy(matchedBet.fills, (fill) => fill.amount)
|
||||
const matchRemaining = matchedBet.orderAmount - matchedBet.amount
|
||||
const shares = Math.min(
|
||||
amount /
|
||||
(outcome === 'YES' ? matchedBet.limitProb : 1 - matchedBet.limitProb),
|
||||
|
@ -138,6 +141,7 @@ const computeFill = (
|
|||
shares *
|
||||
(outcome === 'YES' ? 1 - matchedBet.limitProb : matchedBet.limitProb),
|
||||
shares,
|
||||
timestamp,
|
||||
}
|
||||
const taker = {
|
||||
matchedBetId: matchedBet.id,
|
||||
|
@ -145,6 +149,7 @@ const computeFill = (
|
|||
shares *
|
||||
(outcome === 'YES' ? matchedBet.limitProb : 1 - matchedBet.limitProb),
|
||||
shares,
|
||||
timestamp,
|
||||
}
|
||||
return { maker, taker }
|
||||
}
|
||||
|
@ -164,12 +169,13 @@ export const getBinaryCpmmBetInfo = (
|
|||
|
||||
console.log({ outcome, betAmount, limitProb, sortedBets })
|
||||
|
||||
const takers: {
|
||||
matchedBetId: string | null
|
||||
const takers: fill[] = []
|
||||
const makers: {
|
||||
bet: LimitBet
|
||||
amount: number
|
||||
shares: number
|
||||
timestamp: number
|
||||
}[] = []
|
||||
const makers: { bet: LimitBet; amount: number; shares: number }[] = []
|
||||
|
||||
let amount = betAmount
|
||||
let cpmmState = { pool: contract.pool, p: contract.p }
|
||||
|
@ -210,13 +216,14 @@ export const getBinaryCpmmBetInfo = (
|
|||
const isFilled = floatingEqual(betAmount, takerAmount)
|
||||
|
||||
const newBet: CandidateBet = removeUndefinedProps({
|
||||
amount: betAmount,
|
||||
orderAmount: betAmount,
|
||||
amount: takerAmount,
|
||||
shares: takerShares,
|
||||
limitProb,
|
||||
isFilled,
|
||||
isCancelled: false,
|
||||
fills: takers,
|
||||
contractId: contract.id,
|
||||
shares: takerShares,
|
||||
outcome,
|
||||
probBefore,
|
||||
probAfter,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as admin from 'firebase-admin'
|
||||
import { z } from 'zod'
|
||||
import { Query } from 'firebase-admin/firestore'
|
||||
import { sumBy } from 'lodash'
|
||||
import { FieldValue, Query } from 'firebase-admin/firestore'
|
||||
import { groupBy, mapValues, sumBy } from 'lodash'
|
||||
|
||||
import { APIError, newEndpoint, validate } from './api'
|
||||
import { Contract, CPMM_MIN_POOL_QTY } from '../../common/contract'
|
||||
|
@ -74,6 +74,7 @@ export const placebet = newEndpoint({}, async (req, auth) => {
|
|||
bet: LimitBet
|
||||
amount: number
|
||||
shares: number
|
||||
timestamp: number
|
||||
}[]
|
||||
}
|
||||
> => {
|
||||
|
@ -128,29 +129,44 @@ export const placebet = newEndpoint({}, async (req, auth) => {
|
|||
throw new APIError(400, 'Bet too large for current liquidity pool.')
|
||||
}
|
||||
|
||||
const newBalance = user.balance - amount - loanAmount
|
||||
const betDoc = contractDoc.collection('bets').doc()
|
||||
trans.create(betDoc, { id: betDoc.id, userId: user.id, ...newBet })
|
||||
log('Created new bet document.')
|
||||
|
||||
if (makers) {
|
||||
for (const maker of makers) {
|
||||
const { bet, amount, shares } = maker
|
||||
const newFill = { amount, shares, matchedBetId: betDoc.id }
|
||||
const fills = [...bet.fills, newFill]
|
||||
const makersByBet = groupBy(makers, (maker) => maker.bet.id)
|
||||
for (const makers of Object.values(makersByBet)) {
|
||||
const bet = makers[0].bet
|
||||
const newFills = makers.map((maker) => {
|
||||
const { amount, shares, timestamp } = maker
|
||||
return { amount, shares, matchedBetId: betDoc.id, timestamp }
|
||||
})
|
||||
const fills = [...bet.fills, ...newFills]
|
||||
const totalShares = sumBy(fills, 'shares')
|
||||
const isFilled = floatingEqual(sumBy(fills, 'amount'), bet.amount)
|
||||
const totalAmount = sumBy(fills, 'amount')
|
||||
const isFilled = floatingEqual(totalAmount, bet.orderAmount)
|
||||
|
||||
log('Updated a matched limit bet.')
|
||||
trans.update(contractDoc.collection('bets').doc(bet.id), {
|
||||
fills,
|
||||
isFilled,
|
||||
amount: totalAmount,
|
||||
shares: totalShares,
|
||||
})
|
||||
}
|
||||
|
||||
// Deduct balance of makers.
|
||||
const spentByUser = mapValues(
|
||||
groupBy(makers, (maker) => maker.bet.userId),
|
||||
(makers) => sumBy(makers, (maker) => maker.amount)
|
||||
)
|
||||
for (const [userId, spent] of Object.entries(spentByUser)) {
|
||||
const userDoc = firestore.collection('users').doc(userId)
|
||||
trans.update(userDoc, { balance: FieldValue.increment(-spent) })
|
||||
}
|
||||
}
|
||||
|
||||
trans.update(userDoc, { balance: newBalance })
|
||||
trans.update(userDoc, { balance: FieldValue.increment(-amount) })
|
||||
log('Updated user balance.')
|
||||
trans.update(
|
||||
contractDoc,
|
||||
|
|
|
@ -24,10 +24,7 @@ import { sellShares } from 'web/lib/firebase/api-call'
|
|||
import { AmountInput, BuyAmountInput } from './amount-input'
|
||||
import { InfoTooltip } from './info-tooltip'
|
||||
import { BinaryOutcomeLabel, PseudoNumericOutcomeLabel } from './outcome-label'
|
||||
import {
|
||||
calculatePayoutAfterCorrectBet,
|
||||
getProbability,
|
||||
} from 'common/calculate'
|
||||
import { getProbability } from 'common/calculate'
|
||||
import { useFocus } from 'web/hooks/use-focus'
|
||||
import { useUserContractBets } from 'web/hooks/use-user-bets'
|
||||
import {
|
||||
|
|
|
@ -28,7 +28,6 @@ export default function BetRow(props: {
|
|||
contract,
|
||||
userBets
|
||||
)
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -505,7 +505,7 @@ export function ContractBetsTable(props: {
|
|||
const { contract, className, isYourBets } = props
|
||||
|
||||
const bets = sortBy(
|
||||
props.bets.filter((b) => !b.isAnte),
|
||||
props.bets.filter((b) => !b.isAnte && b.amount !== 0),
|
||||
(bet) => bet.createdTime
|
||||
).reverse()
|
||||
|
||||
|
|
|
@ -31,7 +31,9 @@ export function ContractActivity(props: {
|
|||
const comments = updatedComments ?? props.comments
|
||||
|
||||
const updatedBets = useBets(contract.id)
|
||||
const bets = (updatedBets ?? props.bets).filter((bet) => !bet.isRedemption)
|
||||
const bets = (updatedBets ?? props.bets).filter(
|
||||
(bet) => !bet.isRedemption && bet.amount !== 0
|
||||
)
|
||||
const items = getSpecificContractActivityItems(
|
||||
contract,
|
||||
bets,
|
||||
|
|
|
@ -28,7 +28,7 @@ export function LimitBets(props: { bets: LimitBet[]; className?: string }) {
|
|||
|
||||
function LimitBet(props: { bet: LimitBet }) {
|
||||
const { bet } = props
|
||||
const filledAmount = sumBy(bet.fills, (fill) => fill.amount)
|
||||
const { orderAmount, amount, limitProb, outcome } = bet
|
||||
const [isCancelling, setIsCancelling] = useState(false)
|
||||
|
||||
const onCancel = () => {
|
||||
|
@ -40,11 +40,11 @@ function LimitBet(props: { bet: LimitBet }) {
|
|||
<tr>
|
||||
<td>
|
||||
<div className="pl-2">
|
||||
<BinaryOutcomeLabel outcome={bet.outcome as 'YES' | 'NO'} />
|
||||
<BinaryOutcomeLabel outcome={outcome as 'YES' | 'NO'} />
|
||||
</div>
|
||||
</td>
|
||||
<td>{formatMoney(bet.amount - filledAmount)}</td>
|
||||
<td>{formatPercent(bet.limitProb)}</td>
|
||||
<td>{formatMoney(orderAmount - amount)}</td>
|
||||
<td>{formatPercent(limitProb)}</td>
|
||||
<td>
|
||||
{isCancelling ? (
|
||||
<LoadingIndicator />
|
||||
|
|
Loading…
Reference in New Issue
Block a user