Mkt resolution: new standard resolution (pay back bets first) (#15)
* new standard resolution; contract.totalBets; MKT resolution * recalculate script * Fix one bug and change script name Co-authored-by: jahooma <jahooma@gmail.com>
This commit is contained in:
		
							parent
							
								
									afc6f28a49
								
							
						
					
					
						commit
						5890b74225
					
				|  | @ -43,18 +43,14 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( | ||||||
|         .collection(`contracts/${contractId}/bets`) |         .collection(`contracts/${contractId}/bets`) | ||||||
|         .doc() |         .doc() | ||||||
| 
 | 
 | ||||||
|       const { newBet, newPool, newTotalShares, newBalance } = getNewBetInfo( |       const { newBet, newPool, newTotalShares, newTotalBets, newBalance } = | ||||||
|         user, |         getNewBetInfo(user, outcome, amount, contract, newBetDoc.id) | ||||||
|         outcome, |  | ||||||
|         amount, |  | ||||||
|         contract, |  | ||||||
|         newBetDoc.id |  | ||||||
|       ) |  | ||||||
| 
 | 
 | ||||||
|       transaction.create(newBetDoc, newBet) |       transaction.create(newBetDoc, newBet) | ||||||
|       transaction.update(contractDoc, { |       transaction.update(contractDoc, { | ||||||
|         pool: newPool, |         pool: newPool, | ||||||
|         totalShares: newTotalShares, |         totalShares: newTotalShares, | ||||||
|  |         totalBets: newTotalBets, | ||||||
|       }) |       }) | ||||||
|       transaction.update(userDoc, { balance: newBalance }) |       transaction.update(userDoc, { balance: newBalance }) | ||||||
| 
 | 
 | ||||||
|  | @ -91,6 +87,13 @@ const getNewBetInfo = ( | ||||||
|       ? { YES: yesShares + shares, NO: noShares } |       ? { YES: yesShares + shares, NO: noShares } | ||||||
|       : { YES: yesShares, NO: noShares + shares } |       : { YES: yesShares, NO: noShares + shares } | ||||||
| 
 | 
 | ||||||
|  |   const { YES: yesBets, NO: noBets } = contract.totalBets | ||||||
|  | 
 | ||||||
|  |   const newTotalBets = | ||||||
|  |     outcome === 'YES' | ||||||
|  |       ? { YES: yesBets + amount, NO: noBets } | ||||||
|  |       : { YES: yesBets, NO: noBets + amount } | ||||||
|  | 
 | ||||||
|   const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2) |   const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2) | ||||||
|   const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2) |   const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2) | ||||||
| 
 | 
 | ||||||
|  | @ -108,5 +111,5 @@ const getNewBetInfo = ( | ||||||
| 
 | 
 | ||||||
|   const newBalance = user.balance - amount |   const newBalance = user.balance - amount | ||||||
| 
 | 
 | ||||||
|   return { newBet, newPool, newTotalShares, newBalance } |   return { newBet, newPool, newTotalShares, newTotalBets, newBalance } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -55,12 +55,19 @@ export const resolveMarket = functions | ||||||
|       const betsSnap = await firestore |       const betsSnap = await firestore | ||||||
|         .collection(`contracts/${contractId}/bets`) |         .collection(`contracts/${contractId}/bets`) | ||||||
|         .get() |         .get() | ||||||
|  | 
 | ||||||
|       const bets = betsSnap.docs.map((doc) => doc.data() as Bet) |       const bets = betsSnap.docs.map((doc) => doc.data() as Bet) | ||||||
|  |       const openBets = bets.filter((b) => !b.isSold && !b.sale) | ||||||
|  | 
 | ||||||
|  |       const startPool = contract.startPool.YES + contract.startPool.NO | ||||||
|  |       const truePool = contract.pool.YES + contract.pool.NO - startPool | ||||||
| 
 | 
 | ||||||
|       const payouts = |       const payouts = | ||||||
|         outcome === 'CANCEL' |         outcome === 'CANCEL' | ||||||
|           ? getCancelPayouts(contract, bets) |           ? getCancelPayouts(truePool, openBets) | ||||||
|           : getPayouts(outcome, contract, bets) |           : outcome === 'MKT' | ||||||
|  |           ? getMktPayouts(truePool, contract, openBets) | ||||||
|  |           : getStandardPayouts(outcome, truePool, contract, openBets) | ||||||
| 
 | 
 | ||||||
|       console.log('payouts:', payouts) |       console.log('payouts:', payouts) | ||||||
| 
 | 
 | ||||||
|  | @ -96,42 +103,51 @@ export const resolveMarket = functions | ||||||
| 
 | 
 | ||||||
| const firestore = admin.firestore() | const firestore = admin.firestore() | ||||||
| 
 | 
 | ||||||
| const getCancelPayouts = (contract: Contract, bets: Bet[]) => { | const getCancelPayouts = (truePool: number, bets: Bet[]) => { | ||||||
|   const startPool = contract.startPool.YES + contract.startPool.NO |   console.log('resolved N/A, pool M$', truePool) | ||||||
|   const truePool = contract.pool.YES + contract.pool.NO - startPool |  | ||||||
| 
 | 
 | ||||||
|   const openBets = bets.filter((b) => !b.isSold && !b.sale) |   const betSum = _.sumBy(bets, (b) => b.amount) | ||||||
| 
 | 
 | ||||||
|   const betSum = _.sumBy(openBets, (b) => b.amount) |   return bets.map((bet) => ({ | ||||||
| 
 |  | ||||||
|   return openBets.map((bet) => ({ |  | ||||||
|     userId: bet.userId, |     userId: bet.userId, | ||||||
|     payout: (bet.amount / betSum) * truePool, |     payout: (bet.amount / betSum) * truePool, | ||||||
|   })) |   })) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => { | const getStandardPayouts = ( | ||||||
|   const openBets = bets.filter((b) => !b.isSold && !b.sale) |   outcome: string, | ||||||
|   const [yesBets, noBets] = _.partition( |   truePool: number, | ||||||
|     openBets, |   contract: Contract, | ||||||
|     (bet) => bet.outcome === 'YES' |   bets: Bet[] | ||||||
|  | ) => { | ||||||
|  |   const [yesBets, noBets] = _.partition(bets, (bet) => bet.outcome === 'YES') | ||||||
|  |   const winningBets = outcome === 'YES' ? yesBets : noBets | ||||||
|  | 
 | ||||||
|  |   const betSum = _.sumBy(winningBets, (b) => b.amount) | ||||||
|  | 
 | ||||||
|  |   if (betSum >= truePool) return getCancelPayouts(truePool, winningBets) | ||||||
|  | 
 | ||||||
|  |   const creatorPayout = CREATOR_FEE * truePool | ||||||
|  |   console.log( | ||||||
|  |     'resolved', | ||||||
|  |     outcome, | ||||||
|  |     'pool: M$', | ||||||
|  |     truePool, | ||||||
|  |     'creator fee: M$', | ||||||
|  |     creatorPayout | ||||||
|   ) |   ) | ||||||
| 
 | 
 | ||||||
|   const startPool = contract.startPool.YES + contract.startPool.NO |   const shareDifferenceSum = _.sumBy(winningBets, (b) => b.shares - b.amount) | ||||||
|   const truePool = contract.pool.YES + contract.pool.NO - startPool |  | ||||||
| 
 | 
 | ||||||
|   const [totalShares, winningBets] = |   const winningsPool = truePool - betSum | ||||||
|     outcome === 'YES' |   const fees = PLATFORM_FEE + CREATOR_FEE | ||||||
|       ? [contract.totalShares.YES, yesBets] |  | ||||||
|       : [contract.totalShares.NO, noBets] |  | ||||||
| 
 |  | ||||||
|   const finalPool = (1 - PLATFORM_FEE - CREATOR_FEE) * truePool |  | ||||||
|   const creatorPayout = CREATOR_FEE * truePool |  | ||||||
|   console.log('final pool:', finalPool, 'creator fee:', creatorPayout) |  | ||||||
| 
 | 
 | ||||||
|   const winnerPayouts = winningBets.map((bet) => ({ |   const winnerPayouts = winningBets.map((bet) => ({ | ||||||
|     userId: bet.userId, |     userId: bet.userId, | ||||||
|     payout: (bet.shares / totalShares) * finalPool, |     payout: | ||||||
|  |       (1 - fees) * | ||||||
|  |       (bet.amount + | ||||||
|  |         ((bet.shares - bet.amount) / shareDifferenceSum) * winningsPool), | ||||||
|   })) |   })) | ||||||
| 
 | 
 | ||||||
|   return winnerPayouts.concat([ |   return winnerPayouts.concat([ | ||||||
|  | @ -139,6 +155,17 @@ const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => { | ||||||
|   ]) // add creator fee
 |   ]) // add creator fee
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const getMktPayouts = (truePool: number, contract: Contract, bets: Bet[]) => { | ||||||
|  |   const p = | ||||||
|  |     contract.pool.YES ** 2 / (contract.pool.YES ** 2 + contract.pool.NO ** 2) | ||||||
|  |   console.log('Resolved MKT at p=', p) | ||||||
|  | 
 | ||||||
|  |   return [ | ||||||
|  |     ...getStandardPayouts('YES', p * truePool, contract, bets), | ||||||
|  |     ...getStandardPayouts('NO', (1 - p) * truePool, contract, bets), | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const payUser = ([userId, payout]: [string, number]) => { | const payUser = ([userId, payout]: [string, number]) => { | ||||||
|   return firestore.runTransaction(async (transaction) => { |   return firestore.runTransaction(async (transaction) => { | ||||||
|     const userDoc = firestore.doc(`users/${userId}`) |     const userDoc = firestore.doc(`users/${userId}`) | ||||||
|  |  | ||||||
							
								
								
									
										62
									
								
								functions/src/scripts/recalculate-contract-totals.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								functions/src/scripts/recalculate-contract-totals.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | ||||||
|  | import * as admin from 'firebase-admin' | ||||||
|  | import * as _ from 'lodash' | ||||||
|  | import { Bet } from '../types/bet' | ||||||
|  | import { Contract } from '../types/contract' | ||||||
|  | 
 | ||||||
|  | type DocRef = admin.firestore.DocumentReference | ||||||
|  | 
 | ||||||
|  | // Generate your own private key, and set the path below:
 | ||||||
|  | // https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
 | ||||||
|  | const serviceAccount = require('../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json') | ||||||
|  | 
 | ||||||
|  | admin.initializeApp({ | ||||||
|  |   credential: admin.credential.cert(serviceAccount), | ||||||
|  | }) | ||||||
|  | const firestore = admin.firestore() | ||||||
|  | 
 | ||||||
|  | async function recalculateContract(contractRef: DocRef, contract: Contract) { | ||||||
|  |   const bets = await contractRef | ||||||
|  |     .collection('bets') | ||||||
|  |     .get() | ||||||
|  |     .then((snap) => snap.docs.map((bet) => bet.data() as Bet)) | ||||||
|  | 
 | ||||||
|  |   const openBets = bets.filter((b) => !b.isSold && !b.sale) | ||||||
|  | 
 | ||||||
|  |   const totalShares = { | ||||||
|  |     YES: _.sumBy(openBets, (bet) => (bet.outcome === 'YES' ? bet.shares : 0)), | ||||||
|  |     NO: _.sumBy(openBets, (bet) => (bet.outcome === 'NO' ? bet.shares : 0)), | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const totalBets = { | ||||||
|  |     YES: _.sumBy(openBets, (bet) => (bet.outcome === 'YES' ? bet.amount : 0)), | ||||||
|  |     NO: _.sumBy(openBets, (bet) => (bet.outcome === 'NO' ? bet.amount : 0)), | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   await contractRef.update({ totalShares, totalBets }) | ||||||
|  | 
 | ||||||
|  |   console.log( | ||||||
|  |     'calculating totals for "', | ||||||
|  |     contract.question, | ||||||
|  |     '" total bets:', | ||||||
|  |     totalBets | ||||||
|  |   ) | ||||||
|  |   console.log() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function recalculateContractTotals() { | ||||||
|  |   console.log('Recalculating contract info') | ||||||
|  | 
 | ||||||
|  |   const snapshot = await firestore.collection('contracts').get() | ||||||
|  |   const contracts = snapshot.docs.map((doc) => doc.data() as Contract) | ||||||
|  | 
 | ||||||
|  |   console.log('Loaded', contracts.length, 'contracts') | ||||||
|  | 
 | ||||||
|  |   for (const contract of contracts) { | ||||||
|  |     const contractRef = firestore.doc(`contracts/${contract.id}`) | ||||||
|  | 
 | ||||||
|  |     await recalculateContract(contractRef, contract) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if (require.main === module) | ||||||
|  |   recalculateContractTotals().then(() => process.exit()) | ||||||
|  | @ -44,8 +44,14 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( | ||||||
|         .collection(`contracts/${contractId}/bets`) |         .collection(`contracts/${contractId}/bets`) | ||||||
|         .doc() |         .doc() | ||||||
| 
 | 
 | ||||||
|       const { newBet, newPool, newTotalShares, newBalance, creatorFee } = |       const { | ||||||
|         getSellBetInfo(user, bet, contract, newBetDoc.id) |         newBet, | ||||||
|  |         newPool, | ||||||
|  |         newTotalShares, | ||||||
|  |         newTotalBets, | ||||||
|  |         newBalance, | ||||||
|  |         creatorFee, | ||||||
|  |       } = getSellBetInfo(user, bet, contract, newBetDoc.id) | ||||||
| 
 | 
 | ||||||
|       const creatorDoc = firestore.doc(`users/${contract.creatorId}`) |       const creatorDoc = firestore.doc(`users/${contract.creatorId}`) | ||||||
|       const creatorSnap = await transaction.get(creatorDoc) |       const creatorSnap = await transaction.get(creatorDoc) | ||||||
|  | @ -60,6 +66,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( | ||||||
|       transaction.update(contractDoc, { |       transaction.update(contractDoc, { | ||||||
|         pool: newPool, |         pool: newPool, | ||||||
|         totalShares: newTotalShares, |         totalShares: newTotalShares, | ||||||
|  |         totalBets: newTotalBets, | ||||||
|       }) |       }) | ||||||
|       transaction.update(userDoc, { balance: newBalance }) |       transaction.update(userDoc, { balance: newBalance }) | ||||||
| 
 | 
 | ||||||
|  | @ -81,6 +88,7 @@ const getSellBetInfo = ( | ||||||
|   const { YES: yesPool, NO: noPool } = contract.pool |   const { YES: yesPool, NO: noPool } = contract.pool | ||||||
|   const { YES: yesStart, NO: noStart } = contract.startPool |   const { YES: yesStart, NO: noStart } = contract.startPool | ||||||
|   const { YES: yesShares, NO: noShares } = contract.totalShares |   const { YES: yesShares, NO: noShares } = contract.totalShares | ||||||
|  |   const { YES: yesBets, NO: noBets } = contract.totalBets | ||||||
| 
 | 
 | ||||||
|   const [y, n, s] = [yesPool, noPool, shares] |   const [y, n, s] = [yesPool, noPool, shares] | ||||||
| 
 | 
 | ||||||
|  | @ -123,6 +131,11 @@ const getSellBetInfo = ( | ||||||
|       ? { YES: yesShares - shares, NO: noShares } |       ? { YES: yesShares - shares, NO: noShares } | ||||||
|       : { YES: yesShares, NO: noShares - shares } |       : { YES: yesShares, NO: noShares - shares } | ||||||
| 
 | 
 | ||||||
|  |   const newTotalBets = | ||||||
|  |     outcome === 'YES' | ||||||
|  |       ? { YES: yesBets - amount, NO: noBets } | ||||||
|  |       : { YES: yesBets, NO: noBets - amount } | ||||||
|  | 
 | ||||||
|   const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2) |   const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2) | ||||||
| 
 | 
 | ||||||
|   const creatorFee = CREATOR_FEE * adjShareValue |   const creatorFee = CREATOR_FEE * adjShareValue | ||||||
|  | @ -158,5 +171,12 @@ const getSellBetInfo = ( | ||||||
| 
 | 
 | ||||||
|   const newBalance = user.balance + saleAmount |   const newBalance = user.balance + saleAmount | ||||||
| 
 | 
 | ||||||
|   return { newBet, newPool, newTotalShares, newBalance, creatorFee } |   return { | ||||||
|  |     newBet, | ||||||
|  |     newPool, | ||||||
|  |     newTotalShares, | ||||||
|  |     newTotalBets, | ||||||
|  |     newBalance, | ||||||
|  |     creatorFee, | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ export type Contract = { | ||||||
|   startPool: { YES: number; NO: number } |   startPool: { YES: number; NO: number } | ||||||
|   pool: { YES: number; NO: number } |   pool: { YES: number; NO: number } | ||||||
|   totalShares: { YES: number; NO: number } |   totalShares: { YES: number; NO: number } | ||||||
|  |   totalBets: { YES: number; NO: number } | ||||||
| 
 | 
 | ||||||
|   createdTime: number // Milliseconds since epoch
 |   createdTime: number // Milliseconds since epoch
 | ||||||
|   lastUpdatedTime: number // If the question or description was changed
 |   lastUpdatedTime: number // If the question or description was changed
 | ||||||
|  |  | ||||||
|  | @ -44,14 +44,20 @@ export function calculatePayout( | ||||||
|   if (outcome === 'CANCEL') return amount |   if (outcome === 'CANCEL') return amount | ||||||
|   if (betOutcome !== outcome) return 0 |   if (betOutcome !== outcome) return 0 | ||||||
| 
 | 
 | ||||||
|   const { totalShares } = contract |   const { totalShares, totalBets } = contract | ||||||
| 
 | 
 | ||||||
|   if (totalShares[outcome] === 0) return 0 |   if (totalShares[outcome] === 0) return 0 | ||||||
| 
 | 
 | ||||||
|   const startPool = contract.startPool.YES + contract.startPool.NO |   const startPool = contract.startPool.YES + contract.startPool.NO | ||||||
|   const pool = contract.pool.YES + contract.pool.NO - startPool |   const truePool = contract.pool.YES + contract.pool.NO - startPool | ||||||
| 
 | 
 | ||||||
|   return (1 - fees) * (shares / totalShares[outcome]) * pool |   if (totalBets[outcome] >= truePool) | ||||||
|  |     return (amount / totalBets[outcome]) * truePool | ||||||
|  | 
 | ||||||
|  |   const total = totalShares[outcome] - totalBets[outcome] | ||||||
|  |   const winningsPool = truePool - totalBets[outcome] | ||||||
|  | 
 | ||||||
|  |   return (1 - fees) * (amount + ((shares - amount) / total) * winningsPool) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function resolvedPayout(contract: Contract, bet: Bet) { | export function resolvedPayout(contract: Contract, bet: Bet) { | ||||||
|  |  | ||||||
|  | @ -31,6 +31,7 @@ export type Contract = { | ||||||
|   startPool: { YES: number; NO: number } |   startPool: { YES: number; NO: number } | ||||||
|   pool: { YES: number; NO: number } |   pool: { YES: number; NO: number } | ||||||
|   totalShares: { YES: number; NO: number } |   totalShares: { YES: number; NO: number } | ||||||
|  |   totalBets: { YES: number; NO: number } | ||||||
| 
 | 
 | ||||||
|   createdTime: number // Milliseconds since epoch
 |   createdTime: number // Milliseconds since epoch
 | ||||||
|   lastUpdatedTime: number // If the question or description was changed
 |   lastUpdatedTime: number // If the question or description was changed
 | ||||||
|  |  | ||||||
|  | @ -38,6 +38,7 @@ export async function createContract( | ||||||
|     startPool: { YES: startYes, NO: startNo }, |     startPool: { YES: startYes, NO: startNo }, | ||||||
|     pool: { YES: startYes, NO: startNo }, |     pool: { YES: startYes, NO: startNo }, | ||||||
|     totalShares: { YES: 0, NO: 0 }, |     totalShares: { YES: 0, NO: 0 }, | ||||||
|  |     totalBets: { YES: 0, NO: 0 }, | ||||||
|     isResolved: false, |     isResolved: false, | ||||||
| 
 | 
 | ||||||
|     // TODO: Set create time to Firestore timestamp
 |     // TODO: Set create time to Firestore timestamp
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user