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