Loan backend: Add loanAmount field to Bet, manage loans up to max loan amount per market -- buy, sell, and resolve.
This commit is contained in:
parent
c372a0af9d
commit
659f848bec
|
@ -4,6 +4,7 @@ export type Bet = {
|
|||
contractId: string
|
||||
|
||||
amount: number // bet size; negative if SELL bet
|
||||
loanAmount?: number
|
||||
outcome: string
|
||||
shares: number // dynamic parimutuel pool weight; negative if SELL bet
|
||||
|
||||
|
@ -21,3 +22,5 @@ export type Bet = {
|
|||
|
||||
createdTime: number
|
||||
}
|
||||
|
||||
export const MAX_LOAN_PER_CONTRACT = 20
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Bet } from './bet'
|
||||
import * as _ from 'lodash'
|
||||
import { Bet, MAX_LOAN_PER_CONTRACT } from './bet'
|
||||
import {
|
||||
calculateShares,
|
||||
getProbability,
|
||||
|
@ -11,6 +12,7 @@ export const getNewBinaryBetInfo = (
|
|||
user: User,
|
||||
outcome: 'YES' | 'NO',
|
||||
amount: number,
|
||||
loanAmount: number,
|
||||
contract: Contract,
|
||||
newBetId: string
|
||||
) => {
|
||||
|
@ -45,6 +47,7 @@ export const getNewBinaryBetInfo = (
|
|||
userId: user.id,
|
||||
contractId: contract.id,
|
||||
amount,
|
||||
loanAmount,
|
||||
shares,
|
||||
outcome,
|
||||
probBefore,
|
||||
|
@ -52,7 +55,7 @@ export const getNewBinaryBetInfo = (
|
|||
createdTime: Date.now(),
|
||||
}
|
||||
|
||||
const newBalance = user.balance - amount
|
||||
const newBalance = user.balance - (amount - loanAmount)
|
||||
|
||||
return { newBet, newPool, newTotalShares, newTotalBets, newBalance }
|
||||
}
|
||||
|
@ -61,6 +64,7 @@ export const getNewMultiBetInfo = (
|
|||
user: User,
|
||||
outcome: string,
|
||||
amount: number,
|
||||
loanAmount: number,
|
||||
contract: Contract,
|
||||
newBetId: string
|
||||
) => {
|
||||
|
@ -85,6 +89,7 @@ export const getNewMultiBetInfo = (
|
|||
userId: user.id,
|
||||
contractId: contract.id,
|
||||
amount,
|
||||
loanAmount,
|
||||
shares,
|
||||
outcome,
|
||||
probBefore,
|
||||
|
@ -92,7 +97,16 @@ export const getNewMultiBetInfo = (
|
|||
createdTime: Date.now(),
|
||||
}
|
||||
|
||||
const newBalance = user.balance - amount
|
||||
const newBalance = user.balance - (amount - loanAmount)
|
||||
|
||||
return { newBet, newPool, newTotalShares, newTotalBets, newBalance }
|
||||
}
|
||||
|
||||
export const getLoanAmount = (yourBets: Bet[], newBetAmount: number) => {
|
||||
const prevLoanAmount = _.sumBy(yourBets, (bet) => bet.loanAmount ?? 0)
|
||||
const loanAmount = Math.min(
|
||||
newBetAmount,
|
||||
MAX_LOAN_PER_CONTRACT - prevLoanAmount
|
||||
)
|
||||
return loanAmount
|
||||
}
|
||||
|
|
|
@ -161,3 +161,12 @@ export const getPayoutsMultiOutcome = (
|
|||
.map(({ userId, payout }) => ({ userId, payout }))
|
||||
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
|
||||
}
|
||||
|
||||
export const getLoanPayouts = (bets: Bet[]) => {
|
||||
const betsWithLoans = bets.filter((bet) => bet.loanAmount)
|
||||
const betsByUser = _.groupBy(betsWithLoans, (bet) => bet.userId)
|
||||
const loansByUser = _.mapValues(betsByUser, (bets) =>
|
||||
_.sumBy(bets, (bet) => -(bet.loanAmount ?? 0))
|
||||
)
|
||||
return _.toPairs(loansByUser).map(([userId, payout]) => ({ userId, payout }))
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export const getSellBetInfo = (
|
|||
newBetId: string
|
||||
) => {
|
||||
const { pool, totalShares, totalBets } = contract
|
||||
const { id: betId, amount, shares, outcome } = bet
|
||||
const { id: betId, amount, shares, outcome, loanAmount } = bet
|
||||
|
||||
const adjShareValue = calculateShareValue(contract, bet)
|
||||
|
||||
|
@ -57,7 +57,7 @@ export const getSellBetInfo = (
|
|||
},
|
||||
}
|
||||
|
||||
const newBalance = user.balance + saleAmount
|
||||
const newBalance = user.balance + saleAmount - (loanAmount ?? 0)
|
||||
|
||||
return {
|
||||
newBet,
|
||||
|
|
|
@ -3,10 +3,11 @@ import * as admin from 'firebase-admin'
|
|||
|
||||
import { Contract } from '../../common/contract'
|
||||
import { User } from '../../common/user'
|
||||
import { getNewMultiBetInfo } from '../../common/new-bet'
|
||||
import { getLoanAmount, getNewMultiBetInfo } from '../../common/new-bet'
|
||||
import { Answer } from '../../common/answer'
|
||||
import { getContract, getValues } from './utils'
|
||||
import { sendNewAnswerEmail } from './emails'
|
||||
import { Bet } from '../../common/bet'
|
||||
|
||||
export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||
async (
|
||||
|
@ -55,6 +56,11 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
|||
if (closeTime && Date.now() > closeTime)
|
||||
return { status: 'error', message: 'Trading is closed' }
|
||||
|
||||
const yourBetsSnap = await transaction.get(
|
||||
contractDoc.collection('bets').where('userId', '==', userId)
|
||||
)
|
||||
const yourBets = yourBetsSnap.docs.map((doc) => doc.data() as Bet)
|
||||
|
||||
const [lastAnswer] = await getValues<Answer>(
|
||||
firestore
|
||||
.collection(`contracts/${contractId}/answers`)
|
||||
|
@ -92,8 +98,17 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
|||
.collection(`contracts/${contractId}/bets`)
|
||||
.doc()
|
||||
|
||||
const loanAmount = getLoanAmount(yourBets, amount)
|
||||
|
||||
const { newBet, newPool, newTotalShares, newTotalBets, newBalance } =
|
||||
getNewMultiBetInfo(user, answerId, amount, contract, newBetDoc.id)
|
||||
getNewMultiBetInfo(
|
||||
user,
|
||||
answerId,
|
||||
amount,
|
||||
loanAmount,
|
||||
contract,
|
||||
newBetDoc.id
|
||||
)
|
||||
|
||||
transaction.create(newBetDoc, newBet)
|
||||
transaction.update(contractDoc, {
|
||||
|
|
|
@ -3,7 +3,13 @@ import * as admin from 'firebase-admin'
|
|||
|
||||
import { Contract } from '../../common/contract'
|
||||
import { User } from '../../common/user'
|
||||
import { getNewBinaryBetInfo, getNewMultiBetInfo } from '../../common/new-bet'
|
||||
import {
|
||||
getLoanAmount,
|
||||
getNewBinaryBetInfo,
|
||||
getNewMultiBetInfo,
|
||||
} from '../../common/new-bet'
|
||||
import { Bet } from '../../common/bet'
|
||||
import { getValues } from './utils'
|
||||
|
||||
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||
async (
|
||||
|
@ -46,6 +52,11 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
|||
if (closeTime && Date.now() > closeTime)
|
||||
return { status: 'error', message: 'Trading is closed' }
|
||||
|
||||
const yourBetsSnap = await transaction.get(
|
||||
contractDoc.collection('bets').where('userId', '==', userId)
|
||||
)
|
||||
const yourBets = yourBetsSnap.docs.map((doc) => doc.data() as Bet)
|
||||
|
||||
if (outcomeType === 'FREE_RESPONSE') {
|
||||
const answerSnap = await transaction.get(
|
||||
contractDoc.collection('answers').doc(outcome)
|
||||
|
@ -58,16 +69,26 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
|||
.collection(`contracts/${contractId}/bets`)
|
||||
.doc()
|
||||
|
||||
const loanAmount = getLoanAmount(yourBets, amount)
|
||||
|
||||
const { newBet, newPool, newTotalShares, newTotalBets, newBalance } =
|
||||
outcomeType === 'BINARY'
|
||||
? getNewBinaryBetInfo(
|
||||
user,
|
||||
outcome as 'YES' | 'NO',
|
||||
amount,
|
||||
loanAmount,
|
||||
contract,
|
||||
newBetDoc.id
|
||||
)
|
||||
: getNewMultiBetInfo(
|
||||
user,
|
||||
outcome,
|
||||
amount,
|
||||
loanAmount,
|
||||
contract,
|
||||
newBetDoc.id
|
||||
)
|
||||
: getNewMultiBetInfo(user, outcome, amount, contract, newBetDoc.id)
|
||||
|
||||
transaction.create(newBetDoc, newBet)
|
||||
transaction.update(contractDoc, {
|
||||
|
|
|
@ -7,7 +7,11 @@ import { User } from '../../common/user'
|
|||
import { Bet } from '../../common/bet'
|
||||
import { getUser, payUser } from './utils'
|
||||
import { sendMarketResolutionEmail } from './emails'
|
||||
import { getPayouts, getPayoutsMultiOutcome } from '../../common/payouts'
|
||||
import {
|
||||
getLoanPayouts,
|
||||
getPayouts,
|
||||
getPayoutsMultiOutcome,
|
||||
} from '../../common/payouts'
|
||||
import { removeUndefinedProps } from '../../common/util/object'
|
||||
|
||||
export const resolveMarket = functions
|
||||
|
@ -99,9 +103,14 @@ export const resolveMarket = functions
|
|||
? getPayoutsMultiOutcome(resolutions, contract, openBets)
|
||||
: getPayouts(outcome, contract, openBets, resolutionProbability)
|
||||
|
||||
const loanPayouts = getLoanPayouts(openBets)
|
||||
|
||||
console.log('payouts:', payouts)
|
||||
|
||||
const groups = _.groupBy(payouts, (payout) => payout.userId)
|
||||
const groups = _.groupBy(
|
||||
[...payouts, ...loanPayouts],
|
||||
(payout) => payout.userId
|
||||
)
|
||||
const userPayouts = _.mapValues(groups, (group) =>
|
||||
_.sumBy(group, (g) => g.payout)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user