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:
mantikoros 2022-01-01 19:03:18 -06:00 committed by GitHub
parent afc6f28a49
commit 5890b74225
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 39 deletions

View File

@ -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 }
} }

View File

@ -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}`)

View 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())

View File

@ -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,
}
} }

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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