track fees on contract and bets; change fee schedule for cpmm markets; only pay out creator fees at resolution
This commit is contained in:
		
							parent
							
								
									a0833b4764
								
							
						
					
					
						commit
						6b30ed4513
					
				|  | @ -1,3 +1,5 @@ | |||
| import { Fees } from './fees' | ||||
| 
 | ||||
| export type Bet = { | ||||
|   id: string | ||||
|   userId: string | ||||
|  | @ -6,7 +8,7 @@ export type Bet = { | |||
|   amount: number // bet size; negative if SELL bet
 | ||||
|   loanAmount?: number | ||||
|   outcome: string | ||||
|   shares: number // dynamic parimutuel pool weight; negative if SELL bet
 | ||||
|   shares: number // dynamic parimutuel pool weight or fixed ; negative if SELL bet
 | ||||
| 
 | ||||
|   probBefore: number | ||||
|   probAfter: number | ||||
|  | @ -17,6 +19,8 @@ export type Bet = { | |||
|     // TODO: add sale time?
 | ||||
|   } | ||||
| 
 | ||||
|   fees: Fees | ||||
| 
 | ||||
|   isSold?: boolean // true if this BUY bet has been sold
 | ||||
|   isAnte?: boolean | ||||
|   isLiquidityProvision?: boolean | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import * as _ from 'lodash' | ||||
| 
 | ||||
| import { Bet } from './bet' | ||||
| import { deductFixedFees } from './calculate-fixed-payouts' | ||||
| import { Binary, CPMM, FullContract } from './contract' | ||||
| import { CREATOR_FEE } from './fees' | ||||
| import { CREATOR_FEE, Fees, LIQUIDITY_FEE, PLATFORM_FEE } from './fees' | ||||
| 
 | ||||
| export function getCpmmProbability(pool: { [outcome: string]: number }) { | ||||
|   // For binary contracts only.
 | ||||
|  | @ -35,8 +35,6 @@ function calculateCpmmShares( | |||
|   return shares | ||||
| } | ||||
| 
 | ||||
| export const CPMM_LIQUIDITY_FEE = 0.02 | ||||
| 
 | ||||
| export function getCpmmLiquidityFee( | ||||
|   contract: FullContract<CPMM, Binary>, | ||||
|   bet: number, | ||||
|  | @ -44,9 +42,16 @@ export function getCpmmLiquidityFee( | |||
| ) { | ||||
|   const p = getCpmmProbability(contract.pool) | ||||
|   const betP = outcome === 'YES' ? 1 - p : p | ||||
|   const fee = CPMM_LIQUIDITY_FEE * betP * bet | ||||
|   const remainingBet = bet - fee | ||||
|   return { fee, remainingBet } | ||||
| 
 | ||||
|   const liquidityFee = LIQUIDITY_FEE * betP * bet | ||||
|   const platformFee = PLATFORM_FEE * betP * bet | ||||
|   const creatorFee = CREATOR_FEE * betP * bet | ||||
|   const fees: Fees = { liquidityFee, platformFee, creatorFee } | ||||
| 
 | ||||
|   const totalFees = liquidityFee + platformFee + creatorFee | ||||
|   const remainingBet = bet - totalFees | ||||
| 
 | ||||
|   return { remainingBet, fees } | ||||
| } | ||||
| 
 | ||||
| export function calculateCpmmSharesAfterFee( | ||||
|  | @ -66,7 +71,7 @@ export function calculateCpmmPurchase( | |||
|   outcome: string | ||||
| ) { | ||||
|   const { pool } = contract | ||||
|   const { remainingBet } = getCpmmLiquidityFee(contract, bet, outcome) | ||||
|   const { remainingBet, fees } = getCpmmLiquidityFee(contract, bet, outcome) | ||||
| 
 | ||||
|   const shares = calculateCpmmShares(pool, remainingBet, outcome) | ||||
|   const { YES: y, NO: n } = pool | ||||
|  | @ -78,7 +83,7 @@ export function calculateCpmmPurchase( | |||
| 
 | ||||
|   const newPool = { YES: newY, NO: newN } | ||||
| 
 | ||||
|   return { shares, newPool } | ||||
|   return { shares, newPool, fees } | ||||
| } | ||||
| 
 | ||||
| export function calculateCpmmShareValue( | ||||
|  | @ -103,7 +108,7 @@ export function calculateCpmmSale( | |||
| 
 | ||||
|   const rawSaleValue = calculateCpmmShareValue(contract, shares, outcome) | ||||
| 
 | ||||
|   const { fee, remainingBet: saleValue } = getCpmmLiquidityFee( | ||||
|   const { fees, remainingBet: saleValue } = getCpmmLiquidityFee( | ||||
|     contract, | ||||
|     rawSaleValue, | ||||
|     outcome === 'YES' ? 'NO' : 'YES' | ||||
|  | @ -112,6 +117,8 @@ export function calculateCpmmSale( | |||
|   const { pool } = contract | ||||
|   const { YES: y, NO: n } = pool | ||||
| 
 | ||||
|   const { liquidityFee: fee } = fees | ||||
| 
 | ||||
|   const [newY, newN] = | ||||
|     outcome === 'YES' | ||||
|       ? [y + shares - saleValue + fee, n - saleValue + fee] | ||||
|  | @ -119,11 +126,7 @@ export function calculateCpmmSale( | |||
| 
 | ||||
|   const newPool = { YES: newY, NO: newN } | ||||
| 
 | ||||
|   const profit = saleValue - bet.amount | ||||
|   const creatorFee = CREATOR_FEE * Math.max(0, profit) | ||||
|   const saleAmount = deductFixedFees(bet.amount, saleValue) | ||||
| 
 | ||||
|   return { saleValue, newPool, creatorFee, saleAmount } | ||||
|   return { saleValue, newPool, fees } | ||||
| } | ||||
| 
 | ||||
| export function getCpmmProbabilityAfterSale( | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import * as _ from 'lodash' | ||||
| import { Bet } from './bet' | ||||
| import { Binary, DPM, FullContract } from './contract' | ||||
| import { FEES } from './fees' | ||||
| import { DPM_FEES } from './fees' | ||||
| 
 | ||||
| export function getDpmProbability(totalShares: { [outcome: string]: number }) { | ||||
|   // For binary contracts only.
 | ||||
|  | @ -176,7 +176,7 @@ export function calculateStandardDpmPayout( | |||
| 
 | ||||
|   const winnings = (shares / total) * poolTotal | ||||
|   // profit can be negative if using phantom shares
 | ||||
|   return amount + (1 - FEES) * Math.max(0, winnings - amount) | ||||
|   return amount + (1 - DPM_FEES) * Math.max(0, winnings - amount) | ||||
| } | ||||
| 
 | ||||
| export function calculateDpmPayoutAfterCorrectBet( | ||||
|  | @ -268,7 +268,7 @@ export function resolvedDpmPayout(contract: FullContract<DPM, any>, bet: Bet) { | |||
| 
 | ||||
| export const deductDpmFees = (betAmount: number, winnings: number) => { | ||||
|   return winnings > betAmount | ||||
|     ? betAmount + (1 - FEES) * (winnings - betAmount) | ||||
|     ? betAmount + (1 - DPM_FEES) * (winnings - betAmount) | ||||
|     : winnings | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| import { Bet } from './bet' | ||||
| import { getProbability } from './calculate' | ||||
| import { Binary, FixedPayouts, FullContract } from './contract' | ||||
| import { FEES } from './fees' | ||||
| 
 | ||||
| export function calculateFixedPayout( | ||||
|   contract: FullContract<FixedPayouts, Binary>, | ||||
|  | @ -19,9 +18,9 @@ export function calculateFixedCancelPayout(bet: Bet) { | |||
| } | ||||
| 
 | ||||
| export function calculateStandardFixedPayout(bet: Bet, outcome: string) { | ||||
|   const { amount, outcome: betOutcome, shares } = bet | ||||
|   const { outcome: betOutcome, shares } = bet | ||||
|   if (betOutcome !== outcome) return 0 | ||||
|   return deductFixedFees(amount, shares) | ||||
|   return shares | ||||
| } | ||||
| 
 | ||||
| function calculateFixedMktPayout( | ||||
|  | @ -34,17 +33,9 @@ function calculateFixedMktPayout( | |||
|       ? resolutionProbability | ||||
|       : getProbability(contract) | ||||
| 
 | ||||
|   const { outcome, amount, shares } = bet | ||||
|   const { outcome, shares } = bet | ||||
| 
 | ||||
|   const betP = outcome === 'YES' ? p : 1 - p | ||||
|   const winnings = betP * shares | ||||
| 
 | ||||
|   return deductFixedFees(amount, winnings) | ||||
| } | ||||
| 
 | ||||
| export const deductFixedFees = (betAmount: number, winnings: number) => { | ||||
|   return winnings | ||||
|   //   return winnings > betAmount
 | ||||
|   //     ? betAmount + (1 - FEES) * (winnings - betAmount)
 | ||||
|   //     : winnings
 | ||||
|   return betP * shares | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { Bet } from './bet' | ||||
| import { | ||||
|   calculateCpmmShareValue, | ||||
|   calculateCpmmSale, | ||||
|   getCpmmProbability, | ||||
|   getCpmmOutcomeProbabilityAfterBet, | ||||
|   getCpmmProbabilityAfterSale, | ||||
|  | @ -16,10 +16,7 @@ import { | |||
|   getDpmOutcomeProbabilityAfterBet, | ||||
|   getDpmProbabilityAfterSale, | ||||
| } from './calculate-dpm' | ||||
| import { | ||||
|   calculateFixedPayout, | ||||
|   deductFixedFees, | ||||
| } from './calculate-fixed-payouts' | ||||
| import { calculateFixedPayout } from './calculate-fixed-payouts' | ||||
| import { Binary, Contract, CPMM, DPM, FullContract } from './contract' | ||||
| 
 | ||||
| export function getProbability(contract: FullContract<DPM | CPMM, Binary>) { | ||||
|  | @ -72,16 +69,13 @@ export function calculateShares( | |||
| 
 | ||||
| export function calculateSaleAmount(contract: Contract, bet: Bet) { | ||||
|   return contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY' | ||||
|     ? deductFixedFees( | ||||
|         bet.amount, | ||||
|         calculateCpmmShareValue(contract, bet.shares, bet.outcome) | ||||
|       ) | ||||
|     ? calculateCpmmSale(contract, bet) | ||||
|     : calculateDpmSaleAmount(contract, bet) | ||||
| } | ||||
| 
 | ||||
| export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) { | ||||
|   return contract.mechanism === 'cpmm-1' | ||||
|     ? deductFixedFees(bet.amount, bet.shares) | ||||
|     ? bet.shares | ||||
|     : calculateDpmPayoutAfterCorrectBet(contract, bet) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import { Answer } from './answer' | ||||
| import { Fees } from './fees' | ||||
| 
 | ||||
| export type FullContract< | ||||
|   M extends DPM | CPMM, | ||||
|  | @ -30,6 +31,8 @@ export type FullContract< | |||
| 
 | ||||
|   volume24Hours: number | ||||
|   volume7Days: number | ||||
| 
 | ||||
|   collectedFees: Fees | ||||
| } & M & | ||||
|   T | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,19 @@ | |||
| export const PLATFORM_FEE = 0.01 | ||||
| export const CREATOR_FEE = 0.04 | ||||
| export const PLATFORM_FEE = 0.005 | ||||
| export const CREATOR_FEE = 0.02 | ||||
| export const LIQUIDITY_FEE = 0.02 | ||||
| 
 | ||||
| export const FEES = PLATFORM_FEE + CREATOR_FEE | ||||
| export const DPM_PLATFORM_FEE = 2 * PLATFORM_FEE | ||||
| export const DPM_CREATOR_FEE = 2 * CREATOR_FEE | ||||
| export const DPM_FEES = DPM_PLATFORM_FEE + DPM_CREATOR_FEE | ||||
| 
 | ||||
| export type Fees = { | ||||
|   creatorFee: number | ||||
|   platformFee: number | ||||
|   liquidityFee: number | ||||
| } | ||||
| 
 | ||||
| export const noFees: Fees = { | ||||
|   creatorFee: 0, | ||||
|   platformFee: 0, | ||||
|   liquidityFee: 0, | ||||
| } | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ import { | |||
|   Multi, | ||||
| } from './contract' | ||||
| import { User } from './user' | ||||
| import { noFees } from './fees' | ||||
| 
 | ||||
| export const getNewBinaryCpmmBetInfo = ( | ||||
|   user: User, | ||||
|  | @ -25,7 +26,11 @@ export const getNewBinaryCpmmBetInfo = ( | |||
|   loanAmount: number, | ||||
|   newBetId: string | ||||
| ) => { | ||||
|   const { shares, newPool } = calculateCpmmPurchase(contract, amount, outcome) | ||||
|   const { shares, newPool, fees } = calculateCpmmPurchase( | ||||
|     contract, | ||||
|     amount, | ||||
|     outcome | ||||
|   ) | ||||
| 
 | ||||
|   const newBalance = user.balance - (amount - loanAmount) | ||||
| 
 | ||||
|  | @ -39,13 +44,14 @@ export const getNewBinaryCpmmBetInfo = ( | |||
|     amount, | ||||
|     shares, | ||||
|     outcome, | ||||
|     fees, | ||||
|     loanAmount, | ||||
|     probBefore, | ||||
|     probAfter, | ||||
|     createdTime: Date.now(), | ||||
|   } | ||||
| 
 | ||||
|   return { newBet, newPool, newBalance } | ||||
|   return { newBet, newPool, newBalance, fees } | ||||
| } | ||||
| 
 | ||||
| export const getNewBinaryDpmBetInfo = ( | ||||
|  | @ -93,6 +99,7 @@ export const getNewBinaryDpmBetInfo = ( | |||
|     probBefore, | ||||
|     probAfter, | ||||
|     createdTime: Date.now(), | ||||
|     fees: noFees, | ||||
|   } | ||||
| 
 | ||||
|   const newBalance = user.balance - (amount - loanAmount) | ||||
|  | @ -135,6 +142,7 @@ export const getNewMultiBetInfo = ( | |||
|     probBefore, | ||||
|     probAfter, | ||||
|     createdTime: Date.now(), | ||||
|     fees: noFees, | ||||
|   } | ||||
| 
 | ||||
|   const newBalance = user.balance - (amount - loanAmount) | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ export function getNewContract( | |||
|       ? getBinaryCpmmProps(initialProb, ante) // getBinaryDpmProps(initialProb, ante)
 | ||||
|       : getFreeAnswerProps(ante) | ||||
| 
 | ||||
|   const contract = removeUndefinedProps({ | ||||
|   const contract: Contract = removeUndefinedProps({ | ||||
|     id, | ||||
|     slug, | ||||
|     ...propsByOutcomeType, | ||||
|  | @ -57,6 +57,12 @@ export function getNewContract( | |||
| 
 | ||||
|     volume24Hours: 0, | ||||
|     volume7Days: 0, | ||||
| 
 | ||||
|     collectedFees: { | ||||
|       creatorFee: 0, | ||||
|       liquidityFee: 0, | ||||
|       platformFee: 0, | ||||
|     }, | ||||
|   }) | ||||
| 
 | ||||
|   return contract as Contract | ||||
|  |  | |||
|  | @ -3,7 +3,14 @@ import * as _ from 'lodash' | |||
| import { Bet } from './bet' | ||||
| import { deductDpmFees, getDpmProbability } from './calculate-dpm' | ||||
| import { DPM, FreeResponse, FullContract, Multi } from './contract' | ||||
| import { CREATOR_FEE, FEES } from './fees' | ||||
| import { | ||||
|   DPM_CREATOR_FEE, | ||||
|   DPM_FEES, | ||||
|   DPM_PLATFORM_FEE, | ||||
|   Fees, | ||||
|   noFees, | ||||
| } from './fees' | ||||
| import { addObjects } from './util/object' | ||||
| 
 | ||||
| export const getDpmCancelPayouts = ( | ||||
|   contract: FullContract<DPM, any>, | ||||
|  | @ -15,10 +22,12 @@ export const getDpmCancelPayouts = ( | |||
| 
 | ||||
|   const betSum = _.sumBy(bets, (b) => b.amount) | ||||
| 
 | ||||
|   return bets.map((bet) => ({ | ||||
|   const payouts = bets.map((bet) => ({ | ||||
|     userId: bet.userId, | ||||
|     payout: (bet.amount / betSum) * poolTotal, | ||||
|   })) | ||||
| 
 | ||||
|   return [payouts, contract.collectedFees ?? noFees] | ||||
| } | ||||
| 
 | ||||
| export const getDpmStandardPayouts = ( | ||||
|  | @ -36,12 +45,21 @@ export const getDpmStandardPayouts = ( | |||
|     const profit = winnings - amount | ||||
| 
 | ||||
|     // profit can be negative if using phantom shares
 | ||||
|     const payout = amount + (1 - FEES) * Math.max(0, profit) | ||||
|     const payout = amount + (1 - DPM_FEES) * Math.max(0, profit) | ||||
|     return { userId, profit, payout } | ||||
|   }) | ||||
| 
 | ||||
|   const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) | ||||
|   const creatorPayout = CREATOR_FEE * profits | ||||
|   const creatorFee = DPM_CREATOR_FEE * profits | ||||
|   const platformFee = DPM_PLATFORM_FEE * profits | ||||
| 
 | ||||
|   const finalFees: Fees = { | ||||
|     creatorFee, | ||||
|     platformFee, | ||||
|     liquidityFee: 0, | ||||
|   } | ||||
| 
 | ||||
|   const fees = addObjects<Fees>(finalFees, contract.collectedFees ?? {}) | ||||
| 
 | ||||
|   console.log( | ||||
|     'resolved', | ||||
|  | @ -51,12 +69,14 @@ export const getDpmStandardPayouts = ( | |||
|     'profits', | ||||
|     profits, | ||||
|     'creator fee', | ||||
|     creatorPayout | ||||
|     creatorFee | ||||
|   ) | ||||
| 
 | ||||
|   return payouts | ||||
|   const totalPayouts = payouts | ||||
|     .map(({ userId, payout }) => ({ userId, payout })) | ||||
|     .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
 | ||||
|     .concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee
 | ||||
| 
 | ||||
|   return [totalPayouts, fees] | ||||
| } | ||||
| 
 | ||||
| export const getDpmMktPayouts = ( | ||||
|  | @ -84,7 +104,17 @@ export const getDpmMktPayouts = ( | |||
|   }) | ||||
| 
 | ||||
|   const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) | ||||
|   const creatorPayout = CREATOR_FEE * profits | ||||
| 
 | ||||
|   const creatorFee = DPM_CREATOR_FEE * profits | ||||
|   const platformFee = DPM_PLATFORM_FEE * profits | ||||
| 
 | ||||
|   const finalFees: Fees = { | ||||
|     creatorFee, | ||||
|     platformFee, | ||||
|     liquidityFee: 0, | ||||
|   } | ||||
| 
 | ||||
|   const fees = addObjects<Fees>(finalFees, contract.collectedFees ?? {}) | ||||
| 
 | ||||
|   console.log( | ||||
|     'resolved MKT', | ||||
|  | @ -93,13 +123,14 @@ export const getDpmMktPayouts = ( | |||
|     pool, | ||||
|     'profits', | ||||
|     profits, | ||||
|     'creator fee', | ||||
|     creatorPayout | ||||
|     'creator fee' | ||||
|   ) | ||||
| 
 | ||||
|   return payouts | ||||
|   const totalPayouts = payouts | ||||
|     .map(({ userId, payout }) => ({ userId, payout })) | ||||
|     .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
 | ||||
|     .concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee
 | ||||
| 
 | ||||
|   return [totalPayouts, fees] | ||||
| } | ||||
| 
 | ||||
| export const getPayoutsMultiOutcome = ( | ||||
|  | @ -122,12 +153,22 @@ export const getPayoutsMultiOutcome = ( | |||
|     const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal | ||||
|     const profit = winnings - amount | ||||
| 
 | ||||
|     const payout = amount + (1 - FEES) * Math.max(0, profit) | ||||
|     const payout = amount + (1 - DPM_FEES) * Math.max(0, profit) | ||||
|     return { userId, profit, payout } | ||||
|   }) | ||||
| 
 | ||||
|   const profits = _.sumBy(payouts, (po) => po.profit) | ||||
|   const creatorPayout = CREATOR_FEE * profits | ||||
| 
 | ||||
|   const creatorFee = DPM_CREATOR_FEE * profits | ||||
|   const platformFee = DPM_PLATFORM_FEE * profits | ||||
| 
 | ||||
|   const finalFees: Fees = { | ||||
|     creatorFee, | ||||
|     platformFee, | ||||
|     liquidityFee: 0, | ||||
|   } | ||||
| 
 | ||||
|   const fees = addObjects<Fees>(finalFees, contract.collectedFees ?? {}) | ||||
| 
 | ||||
|   console.log( | ||||
|     'resolved', | ||||
|  | @ -137,10 +178,12 @@ export const getPayoutsMultiOutcome = ( | |||
|     'profits', | ||||
|     profits, | ||||
|     'creator fee', | ||||
|     creatorPayout | ||||
|     creatorFee | ||||
|   ) | ||||
| 
 | ||||
|   return payouts | ||||
|   const totalPayouts = payouts | ||||
|     .map(({ userId, payout }) => ({ userId, payout })) | ||||
|     .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
 | ||||
|     .concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee
 | ||||
| 
 | ||||
|   return [totalPayouts, fees] | ||||
| } | ||||
|  |  | |||
|  | @ -2,13 +2,10 @@ import * as _ from 'lodash' | |||
| 
 | ||||
| import { Bet } from './bet' | ||||
| import { getProbability } from './calculate' | ||||
| import { deductFixedFees } from './calculate-fixed-payouts' | ||||
| import { Binary, CPMM, FixedPayouts, FullContract } from './contract' | ||||
| import { CREATOR_FEE } from './fees' | ||||
| import { LiquidityProvision } from './liquidity-provision' | ||||
| 
 | ||||
| export const getFixedCancelPayouts = ( | ||||
|   contract: FullContract<FixedPayouts, Binary>, | ||||
|   bets: Bet[], | ||||
|   liquidities: LiquidityProvision[] | ||||
| ) => { | ||||
|  | @ -34,23 +31,20 @@ export const getStandardFixedPayouts = ( | |||
| ) => { | ||||
|   const winningBets = bets.filter((bet) => bet.outcome === outcome) | ||||
| 
 | ||||
|   const payouts = winningBets.map(({ userId, amount, shares }) => { | ||||
|     const winnings = shares | ||||
|     const profit = winnings - amount | ||||
|     const payout = deductFixedFees(amount, winnings) | ||||
|     return { userId, profit, payout } | ||||
|   }) | ||||
|   const payouts = winningBets.map(({ userId, shares }) => ({ | ||||
|     userId, | ||||
|     payout: shares, | ||||
|   })) | ||||
| 
 | ||||
|   const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) | ||||
|   const creatorPayout = 0 // CREATOR_FEE * profits
 | ||||
|   const creatorPayout = contract.collectedFees.creatorFee | ||||
| 
 | ||||
|   console.log( | ||||
|     'resolved', | ||||
|     outcome, | ||||
|     'pool', | ||||
|     contract.pool, | ||||
|     'profits', | ||||
|     profits, | ||||
|     contract.pool[outcome], | ||||
|     'payouts', | ||||
|     _.sum(payouts), | ||||
|     'creator fee', | ||||
|     creatorPayout | ||||
|   ) | ||||
|  | @ -88,24 +82,20 @@ export const getMktFixedPayouts = ( | |||
|       ? getProbability(contract) | ||||
|       : resolutionProbability | ||||
| 
 | ||||
|   const payouts = bets.map(({ userId, outcome, amount, shares }) => { | ||||
|   const payouts = bets.map(({ userId, outcome, shares }) => { | ||||
|     const betP = outcome === 'YES' ? p : 1 - p | ||||
|     const winnings = betP * shares | ||||
|     const profit = winnings - amount | ||||
|     const payout = deductFixedFees(amount, winnings) | ||||
|     return { userId, profit, payout } | ||||
|     return { userId, payout: betP * shares } | ||||
|   }) | ||||
| 
 | ||||
|   const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) | ||||
|   const creatorPayout = 0 // CREATOR_FEE * profits
 | ||||
|   const creatorPayout = contract.collectedFees.creatorFee | ||||
| 
 | ||||
|   console.log( | ||||
|     'resolved MKT', | ||||
|     'resolved PROB', | ||||
|     p, | ||||
|     'pool', | ||||
|     contract.pool, | ||||
|     'profits', | ||||
|     profits, | ||||
|     p * contract.pool.YES + (1 - p) * contract.pool.NO, | ||||
|     'payouts', | ||||
|     _.sum(payouts), | ||||
|     'creator fee', | ||||
|     creatorPayout | ||||
|   ) | ||||
|  |  | |||
|  | @ -1,7 +1,16 @@ | |||
| import * as _ from 'lodash' | ||||
| 
 | ||||
| import { Bet } from './bet' | ||||
| import { Contract, DPM, FreeResponse, FullContract, Multi } from './contract' | ||||
| import { | ||||
|   Binary, | ||||
|   Contract, | ||||
|   DPM, | ||||
|   FixedPayouts, | ||||
|   FreeResponse, | ||||
|   FullContract, | ||||
|   Multi, | ||||
| } from './contract' | ||||
| import { Fees } from './fees' | ||||
| import { LiquidityProvision } from './liquidity-provision' | ||||
| import { | ||||
|   getDpmCancelPayouts, | ||||
|  | @ -15,57 +24,12 @@ import { | |||
|   getStandardFixedPayouts, | ||||
| } from './payouts-fixed' | ||||
| 
 | ||||
| export const getPayouts = ( | ||||
|   outcome: | ||||
|     | string | ||||
|     | { | ||||
|         [outcome: string]: number | ||||
|       }, | ||||
|   contract: Contract, | ||||
|   allBets: Bet[], | ||||
|   liquidities: LiquidityProvision[], | ||||
|   resolutionProbability?: number | ||||
| ) => { | ||||
|   const bets = allBets.filter((b) => !b.isSold && !b.sale) | ||||
| 
 | ||||
|   if (contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY') { | ||||
|     switch (outcome) { | ||||
|       case 'YES': | ||||
|       case 'NO': | ||||
|         return getStandardFixedPayouts(outcome, contract, bets, liquidities) | ||||
|       case 'MKT': | ||||
|         return getMktFixedPayouts( | ||||
|           contract, | ||||
|           bets, | ||||
|           liquidities, | ||||
|           resolutionProbability | ||||
|         ) | ||||
|       case 'CANCEL': | ||||
|         return getFixedCancelPayouts(contract, allBets, liquidities) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   switch (outcome) { | ||||
|     case 'YES': | ||||
|     case 'NO': | ||||
|       return getDpmStandardPayouts(outcome, contract, bets) | ||||
|     case 'MKT': | ||||
|       return getDpmMktPayouts(contract, bets, resolutionProbability) | ||||
|     case 'CANCEL': | ||||
|       return getDpmCancelPayouts(contract, bets) | ||||
|     default: | ||||
|       // Multi outcome.
 | ||||
|       return getPayoutsMultiOutcome( | ||||
|         outcome as { | ||||
|           [outcome: string]: number | ||||
|         }, | ||||
|         contract as FullContract<DPM, Multi | FreeResponse>, | ||||
|         bets | ||||
|       ) | ||||
|   } | ||||
| export type Payout = { | ||||
|   userId: string | ||||
|   payout: number | ||||
| } | ||||
| 
 | ||||
| export const getLoanPayouts = (bets: Bet[]) => { | ||||
| export const getLoanPayouts = (bets: Bet[]): Payout[] => { | ||||
|   const betsWithLoans = bets.filter((bet) => bet.loanAmount) | ||||
|   const betsByUser = _.groupBy(betsWithLoans, (bet) => bet.userId) | ||||
|   const loansByUser = _.mapValues(betsByUser, (bets) => | ||||
|  | @ -73,3 +37,92 @@ export const getLoanPayouts = (bets: Bet[]) => { | |||
|   ) | ||||
|   return _.toPairs(loansByUser).map(([userId, payout]) => ({ userId, payout })) | ||||
| } | ||||
| 
 | ||||
| export const getPayouts = ( | ||||
|   outcome: string, | ||||
|   resolutions: { | ||||
|     [outcome: string]: number | ||||
|   }, | ||||
|   contract: Contract, | ||||
|   bets: Bet[], | ||||
|   liquidities: LiquidityProvision[], | ||||
|   resolutionProbability?: number | ||||
| ): [Payout[], Fees] => { | ||||
|   if (contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY') { | ||||
|     const payouts = getFixedPayouts( | ||||
|       outcome, | ||||
|       contract, | ||||
|       bets, | ||||
|       liquidities, | ||||
|       resolutionProbability | ||||
|     ) | ||||
|     return [payouts, contract.collectedFees] | ||||
|   } | ||||
| 
 | ||||
|   return getDpmPayouts( | ||||
|     outcome, | ||||
|     resolutions, | ||||
|     contract, | ||||
|     bets, | ||||
|     resolutionProbability | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export const getFixedPayouts = ( | ||||
|   outcome: string, | ||||
|   contract: FullContract<FixedPayouts, Binary>, | ||||
|   bets: Bet[], | ||||
|   liquidities: LiquidityProvision[], | ||||
|   resolutionProbability?: number | ||||
| ): Payout[] => { | ||||
|   switch (outcome) { | ||||
|     case 'YES': | ||||
|     case 'NO': | ||||
|       return getStandardFixedPayouts(outcome, contract, bets, liquidities) | ||||
|     case 'MKT': | ||||
|       return getMktFixedPayouts( | ||||
|         contract, | ||||
|         bets, | ||||
|         liquidities, | ||||
|         resolutionProbability | ||||
|       ) | ||||
|     default: | ||||
|     case 'CANCEL': | ||||
|       return getFixedCancelPayouts(bets, liquidities) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export const getDpmPayouts = ( | ||||
|   outcome: string, | ||||
|   resolutions: { | ||||
|     [outcome: string]: number | ||||
|   }, | ||||
|   contract: Contract, | ||||
|   bets: Bet[], | ||||
|   resolutionProbability?: number | ||||
| ) => { | ||||
|   const openBets = bets.filter((b) => !b.isSold && !b.sale) | ||||
| 
 | ||||
|   switch (outcome) { | ||||
|     case 'YES': | ||||
|     case 'NO': | ||||
|       return getDpmStandardPayouts(outcome, contract, openBets) as [ | ||||
|         Payout[], | ||||
|         Fees | ||||
|       ] | ||||
|     case 'MKT': | ||||
|       return getDpmMktPayouts(contract, openBets, resolutionProbability) as [ | ||||
|         Payout[], | ||||
|         Fees | ||||
|       ] | ||||
|     case 'CANCEL': | ||||
|       return getDpmCancelPayouts(contract, openBets) as [Payout[], Fees] | ||||
|     default: | ||||
|       // Multi outcome.
 | ||||
|       return getPayoutsMultiOutcome( | ||||
|         resolutions, | ||||
|         contract as FullContract<DPM, Multi | FreeResponse>, | ||||
|         openBets | ||||
|       ) as [Payout[], Fees] | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import { | |||
| } from './calculate-dpm' | ||||
| import { calculateCpmmSale, getCpmmProbability } from './calculate-cpmm' | ||||
| import { Binary, DPM, CPMM, FullContract } from './contract' | ||||
| import { CREATOR_FEE } from './fees' | ||||
| import { DPM_CREATOR_FEE, DPM_PLATFORM_FEE, Fees } from './fees' | ||||
| import { User } from './user' | ||||
| 
 | ||||
| export const getSellBetInfo = ( | ||||
|  | @ -33,7 +33,15 @@ export const getSellBetInfo = ( | |||
|   const probAfter = getDpmProbability(newTotalShares) | ||||
| 
 | ||||
|   const profit = adjShareValue - amount | ||||
|   const creatorFee = CREATOR_FEE * Math.max(0, profit) | ||||
| 
 | ||||
|   const creatorFee = DPM_CREATOR_FEE * Math.max(0, profit) | ||||
|   const platformFee = DPM_PLATFORM_FEE * Math.max(0, profit) | ||||
|   const fees: Fees = { | ||||
|     creatorFee, | ||||
|     platformFee, | ||||
|     liquidityFee: 0, | ||||
|   } | ||||
| 
 | ||||
|   const saleAmount = deductDpmFees(amount, adjShareValue) | ||||
| 
 | ||||
|   console.log( | ||||
|  | @ -60,6 +68,7 @@ export const getSellBetInfo = ( | |||
|       amount: saleAmount, | ||||
|       betId, | ||||
|     }, | ||||
|     fees, | ||||
|   } | ||||
| 
 | ||||
|   const newBalance = user.balance + saleAmount - (loanAmount ?? 0) | ||||
|  | @ -70,7 +79,7 @@ export const getSellBetInfo = ( | |||
|     newTotalShares, | ||||
|     newTotalBets, | ||||
|     newBalance, | ||||
|     creatorFee, | ||||
|     fees, | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -83,10 +92,7 @@ export const getCpmmSellBetInfo = ( | |||
|   const { pool } = contract | ||||
|   const { id: betId, amount, shares, outcome } = bet | ||||
| 
 | ||||
|   const { saleValue, newPool, creatorFee, saleAmount } = calculateCpmmSale( | ||||
|     contract, | ||||
|     bet | ||||
|   ) | ||||
|   const { saleValue, newPool, fees } = calculateCpmmSale(contract, bet) | ||||
| 
 | ||||
|   const probBefore = getCpmmProbability(pool) | ||||
|   const probAfter = getCpmmProbability(newPool) | ||||
|  | @ -96,9 +102,9 @@ export const getCpmmSellBetInfo = ( | |||
|     amount, | ||||
|     outcome, | ||||
|     'for M$', | ||||
|     saleAmount, | ||||
|     saleValue, | ||||
|     'creator fee: M$', | ||||
|     creatorFee | ||||
|     fees.creatorFee | ||||
|   ) | ||||
| 
 | ||||
|   const newBet: Bet = { | ||||
|  | @ -112,17 +118,18 @@ export const getCpmmSellBetInfo = ( | |||
|     probAfter, | ||||
|     createdTime: Date.now(), | ||||
|     sale: { | ||||
|       amount: saleAmount, | ||||
|       amount: saleValue, | ||||
|       betId, | ||||
|     }, | ||||
|     fees, | ||||
|   } | ||||
| 
 | ||||
|   const newBalance = user.balance + saleAmount | ||||
|   const newBalance = user.balance + saleValue | ||||
| 
 | ||||
|   return { | ||||
|     newBet, | ||||
|     newPool, | ||||
|     newBalance, | ||||
|     creatorFee, | ||||
|     fees, | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| import * as _ from 'lodash' | ||||
| 
 | ||||
| export const removeUndefinedProps = <T>(obj: T): T => { | ||||
|   let newObj: any = {} | ||||
| 
 | ||||
|  | @ -7,3 +9,17 @@ export const removeUndefinedProps = <T>(obj: T): T => { | |||
| 
 | ||||
|   return newObj | ||||
| } | ||||
| 
 | ||||
| export const addObjects = <T extends { [key: string]: number }>( | ||||
|   obj1: T, | ||||
|   obj2: T | ||||
| ) => { | ||||
|   const keys = _.union(Object.keys(obj1), Object.keys(obj2)) | ||||
|   const newObj = {} as any | ||||
| 
 | ||||
|   for (let key of keys) { | ||||
|     newObj[key] = (obj1[key] ?? 0) + (obj2[key] ?? 0) | ||||
|   } | ||||
| 
 | ||||
|   return newObj as T | ||||
| } | ||||
|  |  | |||
|  | @ -9,9 +9,10 @@ import { | |||
|   getNewMultiBetInfo, | ||||
|   getLoanAmount, | ||||
| } from '../../common/new-bet' | ||||
| import { removeUndefinedProps } from '../../common/util/object' | ||||
| import { addObjects, removeUndefinedProps } from '../../common/util/object' | ||||
| import { Bet } from '../../common/bet' | ||||
| import { redeemShares } from './redeem-shares' | ||||
| import { Fees } from '../../common/fees' | ||||
| 
 | ||||
| export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( | ||||
|   async ( | ||||
|  | @ -48,7 +49,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( | |||
|           return { status: 'error', message: 'Invalid contract' } | ||||
|         const contract = contractSnap.data() as Contract | ||||
| 
 | ||||
|         const { closeTime, outcomeType, mechanism } = contract | ||||
|         const { closeTime, outcomeType, mechanism, collectedFees } = contract | ||||
|         if (closeTime && Date.now() > closeTime) | ||||
|           return { status: 'error', message: 'Trading is closed' } | ||||
| 
 | ||||
|  | @ -73,7 +74,14 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( | |||
|           .collection(`contracts/${contractId}/bets`) | ||||
|           .doc() | ||||
| 
 | ||||
|         const { newBet, newPool, newTotalShares, newTotalBets, newBalance } = | ||||
|         const { | ||||
|           newBet, | ||||
|           newPool, | ||||
|           newTotalShares, | ||||
|           newTotalBets, | ||||
|           newBalance, | ||||
|           fees, | ||||
|         } = | ||||
|           outcomeType === 'BINARY' | ||||
|             ? mechanism === 'dpm-2' | ||||
|               ? getNewBinaryDpmBetInfo( | ||||
|  | @ -109,6 +117,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( | |||
|             pool: newPool, | ||||
|             totalShares: newTotalShares, | ||||
|             totalBets: newTotalBets, | ||||
|             collectedFees: addObjects<Fees>(fees ?? {}, collectedFees ?? {}), | ||||
|           }) | ||||
|         ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import * as _ from 'lodash' | |||
| import { Contract } from '../../common/contract' | ||||
| import { User } from '../../common/user' | ||||
| import { Bet } from '../../common/bet' | ||||
| import { getUser, payUser } from './utils' | ||||
| import { getUser, isProd, payUser } from './utils' | ||||
| import { sendMarketResolutionEmail } from './emails' | ||||
| import { getLoanPayouts, getPayouts } from '../../common/payouts' | ||||
| import { removeUndefinedProps } from '../../common/util/object' | ||||
|  | @ -75,19 +75,6 @@ export const resolveMarket = functions | |||
|         ? Math.min(closeTime, resolutionTime) | ||||
|         : closeTime | ||||
| 
 | ||||
|       await contractDoc.update( | ||||
|         removeUndefinedProps({ | ||||
|           isResolved: true, | ||||
|           resolution: outcome, | ||||
|           resolutionTime, | ||||
|           closeTime: newCloseTime, | ||||
|           resolutionProbability, | ||||
|           resolutions, | ||||
|         }) | ||||
|       ) | ||||
| 
 | ||||
|       console.log('contract ', contractId, 'resolved to:', outcome) | ||||
| 
 | ||||
|       const betsSnap = await firestore | ||||
|         .collection(`contracts/${contractId}/bets`) | ||||
|         .get() | ||||
|  | @ -102,18 +89,33 @@ export const resolveMarket = functions | |||
|         (doc) => doc.data() as LiquidityProvision | ||||
|       ) | ||||
| 
 | ||||
|       const payouts = getPayouts( | ||||
|         resolutions ?? outcome, | ||||
|       const [payouts, collectedFees] = getPayouts( | ||||
|         outcome, | ||||
|         resolutions ?? {}, | ||||
|         contract, | ||||
|         bets, | ||||
|         liquidities, | ||||
|         resolutionProbability | ||||
|       ) | ||||
| 
 | ||||
|       await contractDoc.update( | ||||
|         removeUndefinedProps({ | ||||
|           isResolved: true, | ||||
|           resolution: outcome, | ||||
|           resolutionTime, | ||||
|           closeTime: newCloseTime, | ||||
|           resolutionProbability, | ||||
|           resolutions, | ||||
|           collectedFees, | ||||
|         }) | ||||
|       ) | ||||
| 
 | ||||
|       console.log('contract ', contractId, 'resolved to:', outcome) | ||||
| 
 | ||||
|       const openBets = bets.filter((b) => !b.isSold && !b.sale) | ||||
|       const loanPayouts = getLoanPayouts(openBets) | ||||
| 
 | ||||
|       console.log('payouts:', payouts) | ||||
|       if (!isProd) console.log('payouts:', payouts) | ||||
| 
 | ||||
|       const groups = _.groupBy( | ||||
|         [...payouts, ...loanPayouts], | ||||
|  |  | |||
|  | @ -5,7 +5,8 @@ import { Contract } from '../../common/contract' | |||
| import { User } from '../../common/user' | ||||
| import { Bet } from '../../common/bet' | ||||
| import { getCpmmSellBetInfo, getSellBetInfo } from '../../common/sell-bet' | ||||
| import { removeUndefinedProps } from '../../common/util/object' | ||||
| import { addObjects, removeUndefinedProps } from '../../common/util/object' | ||||
| import { Fees } from '../../common/fees' | ||||
| 
 | ||||
| export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( | ||||
|   async ( | ||||
|  | @ -34,7 +35,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( | |||
|         return { status: 'error', message: 'Invalid contract' } | ||||
|       const contract = contractSnap.data() as Contract | ||||
| 
 | ||||
|       const { closeTime, mechanism } = contract | ||||
|       const { closeTime, mechanism, collectedFees } = contract | ||||
|       if (closeTime && Date.now() > closeTime) | ||||
|         return { status: 'error', message: 'Trading is closed' } | ||||
| 
 | ||||
|  | @ -55,7 +56,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( | |||
|         newTotalShares, | ||||
|         newTotalBets, | ||||
|         newBalance, | ||||
|         creatorFee, | ||||
|         fees, | ||||
|       } = | ||||
|         mechanism === 'dpm-2' | ||||
|           ? getSellBetInfo(user, bet, contract, newBetDoc.id) | ||||
|  | @ -66,20 +67,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( | |||
|               newBetDoc.id | ||||
|             ) as any) | ||||
| 
 | ||||
|       if (contract.creatorId === userId) { | ||||
|         transaction.update(userDoc, { balance: newBalance + creatorFee }) | ||||
|       } else { | ||||
|         const creatorDoc = firestore.doc(`users/${contract.creatorId}`) | ||||
|         const creatorSnap = await transaction.get(creatorDoc) | ||||
| 
 | ||||
|         if (creatorSnap.exists) { | ||||
|           const creator = creatorSnap.data() as User | ||||
|           const creatorNewBalance = creator.balance + creatorFee | ||||
|           transaction.update(creatorDoc, { balance: creatorNewBalance }) | ||||
|         } | ||||
| 
 | ||||
|         transaction.update(userDoc, { balance: newBalance }) | ||||
|       } | ||||
|       transaction.update(userDoc, { balance: newBalance }) | ||||
| 
 | ||||
|       transaction.update(betDoc, { isSold: true }) | ||||
|       transaction.create(newBetDoc, newBet) | ||||
|  | @ -89,6 +77,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall( | |||
|           pool: newPool, | ||||
|           totalShares: newTotalShares, | ||||
|           totalBets: newTotalBets, | ||||
|           collectedFees: addObjects<Fees>(fees ?? {}, collectedFees ?? {}), | ||||
|         }) | ||||
|       ) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user