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
|
contractId: string
|
||||||
|
|
||||||
amount: number // bet size; negative if SELL bet
|
amount: number // bet size; negative if SELL bet
|
||||||
|
loanAmount?: number
|
||||||
outcome: string
|
outcome: string
|
||||||
shares: number // dynamic parimutuel pool weight; negative if SELL bet
|
shares: number // dynamic parimutuel pool weight; negative if SELL bet
|
||||||
|
|
||||||
|
@ -21,3 +22,5 @@ export type Bet = {
|
||||||
|
|
||||||
createdTime: number
|
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 {
|
import {
|
||||||
calculateShares,
|
calculateShares,
|
||||||
getProbability,
|
getProbability,
|
||||||
|
@ -11,6 +12,7 @@ export const getNewBinaryBetInfo = (
|
||||||
user: User,
|
user: User,
|
||||||
outcome: 'YES' | 'NO',
|
outcome: 'YES' | 'NO',
|
||||||
amount: number,
|
amount: number,
|
||||||
|
loanAmount: number,
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
newBetId: string
|
newBetId: string
|
||||||
) => {
|
) => {
|
||||||
|
@ -45,6 +47,7 @@ export const getNewBinaryBetInfo = (
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
amount,
|
amount,
|
||||||
|
loanAmount,
|
||||||
shares,
|
shares,
|
||||||
outcome,
|
outcome,
|
||||||
probBefore,
|
probBefore,
|
||||||
|
@ -52,7 +55,7 @@ export const getNewBinaryBetInfo = (
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBalance = user.balance - amount
|
const newBalance = user.balance - (amount - loanAmount)
|
||||||
|
|
||||||
return { newBet, newPool, newTotalShares, newTotalBets, newBalance }
|
return { newBet, newPool, newTotalShares, newTotalBets, newBalance }
|
||||||
}
|
}
|
||||||
|
@ -61,6 +64,7 @@ export const getNewMultiBetInfo = (
|
||||||
user: User,
|
user: User,
|
||||||
outcome: string,
|
outcome: string,
|
||||||
amount: number,
|
amount: number,
|
||||||
|
loanAmount: number,
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
newBetId: string
|
newBetId: string
|
||||||
) => {
|
) => {
|
||||||
|
@ -85,6 +89,7 @@ export const getNewMultiBetInfo = (
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
amount,
|
amount,
|
||||||
|
loanAmount,
|
||||||
shares,
|
shares,
|
||||||
outcome,
|
outcome,
|
||||||
probBefore,
|
probBefore,
|
||||||
|
@ -92,7 +97,16 @@ export const getNewMultiBetInfo = (
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBalance = user.balance - amount
|
const newBalance = user.balance - (amount - loanAmount)
|
||||||
|
|
||||||
return { newBet, newPool, newTotalShares, newTotalBets, newBalance }
|
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 }))
|
.map(({ userId, payout }) => ({ userId, payout }))
|
||||||
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
|
.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
|
newBetId: string
|
||||||
) => {
|
) => {
|
||||||
const { pool, totalShares, totalBets } = contract
|
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)
|
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 {
|
return {
|
||||||
newBet,
|
newBet,
|
||||||
|
|
|
@ -3,10 +3,11 @@ import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { getNewMultiBetInfo } from '../../common/new-bet'
|
import { getLoanAmount, getNewMultiBetInfo } from '../../common/new-bet'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from '../../common/answer'
|
||||||
import { getContract, getValues } from './utils'
|
import { getContract, getValues } from './utils'
|
||||||
import { sendNewAnswerEmail } from './emails'
|
import { sendNewAnswerEmail } from './emails'
|
||||||
|
import { Bet } from '../../common/bet'
|
||||||
|
|
||||||
export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
@ -55,6 +56,11 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
if (closeTime && Date.now() > closeTime)
|
if (closeTime && Date.now() > closeTime)
|
||||||
return { status: 'error', message: 'Trading is closed' }
|
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>(
|
const [lastAnswer] = await getValues<Answer>(
|
||||||
firestore
|
firestore
|
||||||
.collection(`contracts/${contractId}/answers`)
|
.collection(`contracts/${contractId}/answers`)
|
||||||
|
@ -92,8 +98,17 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
.collection(`contracts/${contractId}/bets`)
|
.collection(`contracts/${contractId}/bets`)
|
||||||
.doc()
|
.doc()
|
||||||
|
|
||||||
|
const loanAmount = getLoanAmount(yourBets, amount)
|
||||||
|
|
||||||
const { newBet, newPool, newTotalShares, newTotalBets, newBalance } =
|
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.create(newBetDoc, newBet)
|
||||||
transaction.update(contractDoc, {
|
transaction.update(contractDoc, {
|
||||||
|
|
|
@ -3,7 +3,13 @@ import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
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(
|
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
async (
|
async (
|
||||||
|
@ -46,6 +52,11 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
if (closeTime && Date.now() > closeTime)
|
if (closeTime && Date.now() > closeTime)
|
||||||
return { status: 'error', message: 'Trading is closed' }
|
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') {
|
if (outcomeType === 'FREE_RESPONSE') {
|
||||||
const answerSnap = await transaction.get(
|
const answerSnap = await transaction.get(
|
||||||
contractDoc.collection('answers').doc(outcome)
|
contractDoc.collection('answers').doc(outcome)
|
||||||
|
@ -58,16 +69,26 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
.collection(`contracts/${contractId}/bets`)
|
.collection(`contracts/${contractId}/bets`)
|
||||||
.doc()
|
.doc()
|
||||||
|
|
||||||
|
const loanAmount = getLoanAmount(yourBets, amount)
|
||||||
|
|
||||||
const { newBet, newPool, newTotalShares, newTotalBets, newBalance } =
|
const { newBet, newPool, newTotalShares, newTotalBets, newBalance } =
|
||||||
outcomeType === 'BINARY'
|
outcomeType === 'BINARY'
|
||||||
? getNewBinaryBetInfo(
|
? getNewBinaryBetInfo(
|
||||||
user,
|
user,
|
||||||
outcome as 'YES' | 'NO',
|
outcome as 'YES' | 'NO',
|
||||||
amount,
|
amount,
|
||||||
|
loanAmount,
|
||||||
|
contract,
|
||||||
|
newBetDoc.id
|
||||||
|
)
|
||||||
|
: getNewMultiBetInfo(
|
||||||
|
user,
|
||||||
|
outcome,
|
||||||
|
amount,
|
||||||
|
loanAmount,
|
||||||
contract,
|
contract,
|
||||||
newBetDoc.id
|
newBetDoc.id
|
||||||
)
|
)
|
||||||
: getNewMultiBetInfo(user, outcome, amount, contract, newBetDoc.id)
|
|
||||||
|
|
||||||
transaction.create(newBetDoc, newBet)
|
transaction.create(newBetDoc, newBet)
|
||||||
transaction.update(contractDoc, {
|
transaction.update(contractDoc, {
|
||||||
|
|
|
@ -7,7 +7,11 @@ import { User } from '../../common/user'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { getUser, payUser } from './utils'
|
import { getUser, payUser } from './utils'
|
||||||
import { sendMarketResolutionEmail } from './emails'
|
import { sendMarketResolutionEmail } from './emails'
|
||||||
import { getPayouts, getPayoutsMultiOutcome } from '../../common/payouts'
|
import {
|
||||||
|
getLoanPayouts,
|
||||||
|
getPayouts,
|
||||||
|
getPayoutsMultiOutcome,
|
||||||
|
} from '../../common/payouts'
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
import { removeUndefinedProps } from '../../common/util/object'
|
||||||
|
|
||||||
export const resolveMarket = functions
|
export const resolveMarket = functions
|
||||||
|
@ -99,9 +103,14 @@ export const resolveMarket = functions
|
||||||
? getPayoutsMultiOutcome(resolutions, contract, openBets)
|
? getPayoutsMultiOutcome(resolutions, contract, openBets)
|
||||||
: getPayouts(outcome, contract, openBets, resolutionProbability)
|
: getPayouts(outcome, contract, openBets, resolutionProbability)
|
||||||
|
|
||||||
|
const loanPayouts = getLoanPayouts(openBets)
|
||||||
|
|
||||||
console.log('payouts:', payouts)
|
console.log('payouts:', payouts)
|
||||||
|
|
||||||
const groups = _.groupBy(payouts, (payout) => payout.userId)
|
const groups = _.groupBy(
|
||||||
|
[...payouts, ...loanPayouts],
|
||||||
|
(payout) => payout.userId
|
||||||
|
)
|
||||||
const userPayouts = _.mapValues(groups, (group) =>
|
const userPayouts = _.mapValues(groups, (group) =>
|
||||||
_.sumBy(group, (g) => g.payout)
|
_.sumBy(group, (g) => g.payout)
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user