From 5890b74225ca405fc7d01361170f82666dd8484b Mon Sep 17 00:00:00 2001 From: mantikoros <95266179+mantikoros@users.noreply.github.com> Date: Sat, 1 Jan 2022 19:03:18 -0600 Subject: [PATCH] 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 --- functions/src/place-bet.ts | 19 +++-- functions/src/resolve-market.ts | 77 +++++++++++++------ .../scripts/recalculate-contract-totals.ts | 62 +++++++++++++++ functions/src/sell-bet.ts | 26 ++++++- functions/src/types/contract.ts | 1 + web/lib/calculate.ts | 12 ++- web/lib/firebase/contracts.ts | 1 + web/lib/service/create-contract.ts | 1 + 8 files changed, 160 insertions(+), 39 deletions(-) create mode 100644 functions/src/scripts/recalculate-contract-totals.ts diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts index e6e4d25a..d26c07de 100644 --- a/functions/src/place-bet.ts +++ b/functions/src/place-bet.ts @@ -43,18 +43,14 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( .collection(`contracts/${contractId}/bets`) .doc() - const { newBet, newPool, newTotalShares, newBalance } = getNewBetInfo( - user, - outcome, - amount, - contract, - newBetDoc.id - ) + const { newBet, newPool, newTotalShares, newTotalBets, newBalance } = + getNewBetInfo(user, outcome, amount, contract, newBetDoc.id) transaction.create(newBetDoc, newBet) transaction.update(contractDoc, { pool: newPool, totalShares: newTotalShares, + totalBets: newTotalBets, }) transaction.update(userDoc, { balance: newBalance }) @@ -91,6 +87,13 @@ const getNewBetInfo = ( ? { YES: yesShares + shares, NO: noShares } : { 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 probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2) @@ -108,5 +111,5 @@ const getNewBetInfo = ( const newBalance = user.balance - amount - return { newBet, newPool, newTotalShares, newBalance } + return { newBet, newPool, newTotalShares, newTotalBets, newBalance } } diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts index 0ab9f4cb..656c73b5 100644 --- a/functions/src/resolve-market.ts +++ b/functions/src/resolve-market.ts @@ -55,12 +55,19 @@ export const resolveMarket = functions const betsSnap = await firestore .collection(`contracts/${contractId}/bets`) .get() + 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 = outcome === 'CANCEL' - ? getCancelPayouts(contract, bets) - : getPayouts(outcome, contract, bets) + ? getCancelPayouts(truePool, openBets) + : outcome === 'MKT' + ? getMktPayouts(truePool, contract, openBets) + : getStandardPayouts(outcome, truePool, contract, openBets) console.log('payouts:', payouts) @@ -96,42 +103,51 @@ export const resolveMarket = functions const firestore = admin.firestore() -const getCancelPayouts = (contract: Contract, bets: Bet[]) => { - const startPool = contract.startPool.YES + contract.startPool.NO - const truePool = contract.pool.YES + contract.pool.NO - startPool +const getCancelPayouts = (truePool: number, bets: Bet[]) => { + console.log('resolved N/A, pool M$', truePool) - const openBets = bets.filter((b) => !b.isSold && !b.sale) + const betSum = _.sumBy(bets, (b) => b.amount) - const betSum = _.sumBy(openBets, (b) => b.amount) - - return openBets.map((bet) => ({ + return bets.map((bet) => ({ userId: bet.userId, payout: (bet.amount / betSum) * truePool, })) } -const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => { - const openBets = bets.filter((b) => !b.isSold && !b.sale) - const [yesBets, noBets] = _.partition( - openBets, - (bet) => bet.outcome === 'YES' +const getStandardPayouts = ( + outcome: string, + truePool: number, + contract: Contract, + 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 truePool = contract.pool.YES + contract.pool.NO - startPool + const shareDifferenceSum = _.sumBy(winningBets, (b) => b.shares - b.amount) - const [totalShares, winningBets] = - outcome === 'YES' - ? [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 winningsPool = truePool - betSum + const fees = PLATFORM_FEE + CREATOR_FEE const winnerPayouts = winningBets.map((bet) => ({ userId: bet.userId, - payout: (bet.shares / totalShares) * finalPool, + payout: + (1 - fees) * + (bet.amount + + ((bet.shares - bet.amount) / shareDifferenceSum) * winningsPool), })) return winnerPayouts.concat([ @@ -139,6 +155,17 @@ const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => { ]) // 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]) => { return firestore.runTransaction(async (transaction) => { const userDoc = firestore.doc(`users/${userId}`) diff --git a/functions/src/scripts/recalculate-contract-totals.ts b/functions/src/scripts/recalculate-contract-totals.ts new file mode 100644 index 00000000..d1b33bb4 --- /dev/null +++ b/functions/src/scripts/recalculate-contract-totals.ts @@ -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()) diff --git a/functions/src/sell-bet.ts b/functions/src/sell-bet.ts index ba60a5e3..3d37d3c3 100644 --- a/functions/src/sell-bet.ts +++ b/functions/src/sell-bet.ts @@ -44,8 +44,14 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( .collection(`contracts/${contractId}/bets`) .doc() - const { newBet, newPool, newTotalShares, newBalance, creatorFee } = - getSellBetInfo(user, bet, contract, newBetDoc.id) + const { + newBet, + newPool, + newTotalShares, + newTotalBets, + newBalance, + creatorFee, + } = getSellBetInfo(user, bet, contract, newBetDoc.id) const creatorDoc = firestore.doc(`users/${contract.creatorId}`) const creatorSnap = await transaction.get(creatorDoc) @@ -60,6 +66,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( transaction.update(contractDoc, { pool: newPool, totalShares: newTotalShares, + totalBets: newTotalBets, }) transaction.update(userDoc, { balance: newBalance }) @@ -81,6 +88,7 @@ const getSellBetInfo = ( const { YES: yesPool, NO: noPool } = contract.pool const { YES: yesStart, NO: noStart } = contract.startPool const { YES: yesShares, NO: noShares } = contract.totalShares + const { YES: yesBets, NO: noBets } = contract.totalBets const [y, n, s] = [yesPool, noPool, shares] @@ -123,6 +131,11 @@ const getSellBetInfo = ( ? { YES: yesShares - shares, NO: noShares } : { 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 creatorFee = CREATOR_FEE * adjShareValue @@ -158,5 +171,12 @@ const getSellBetInfo = ( const newBalance = user.balance + saleAmount - return { newBet, newPool, newTotalShares, newBalance, creatorFee } + return { + newBet, + newPool, + newTotalShares, + newTotalBets, + newBalance, + creatorFee, + } } diff --git a/functions/src/types/contract.ts b/functions/src/types/contract.ts index 5f354654..2cb86ac2 100644 --- a/functions/src/types/contract.ts +++ b/functions/src/types/contract.ts @@ -13,6 +13,7 @@ export type Contract = { startPool: { YES: number; NO: number } pool: { YES: number; NO: number } totalShares: { YES: number; NO: number } + totalBets: { YES: number; NO: number } createdTime: number // Milliseconds since epoch lastUpdatedTime: number // If the question or description was changed diff --git a/web/lib/calculate.ts b/web/lib/calculate.ts index b195a65a..642473df 100644 --- a/web/lib/calculate.ts +++ b/web/lib/calculate.ts @@ -44,14 +44,20 @@ export function calculatePayout( if (outcome === 'CANCEL') return amount if (betOutcome !== outcome) return 0 - const { totalShares } = contract + const { totalShares, totalBets } = contract if (totalShares[outcome] === 0) return 0 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) { diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index b8ea93e3..1db5d00e 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -31,6 +31,7 @@ export type Contract = { startPool: { YES: number; NO: number } pool: { YES: number; NO: number } totalShares: { YES: number; NO: number } + totalBets: { YES: number; NO: number } createdTime: number // Milliseconds since epoch lastUpdatedTime: number // If the question or description was changed diff --git a/web/lib/service/create-contract.ts b/web/lib/service/create-contract.ts index eba7b0ff..966115b0 100644 --- a/web/lib/service/create-contract.ts +++ b/web/lib/service/create-contract.ts @@ -38,6 +38,7 @@ export async function createContract( startPool: { YES: startYes, NO: startNo }, pool: { YES: startYes, NO: startNo }, totalShares: { YES: 0, NO: 0 }, + totalBets: { YES: 0, NO: 0 }, isResolved: false, // TODO: Set create time to Firestore timestamp