@gnosis.pm/pm-js
Version:
A javascript library for building applications on top of Gnosis, the Ethereum prediction market platform
192 lines (176 loc) • 9.37 kB
JavaScript
import { Decimal, normalizeWeb3Args } from './utils'
/**
* Estimates the cost of buying specified number of outcome tokens from LMSR market. Unfunded markets will be handled as a special case, returning the outcomeTokenCount passed plus its corresponding fee fee.
* @param {number[]|string[]|BigNumber[]} opts.netOutcomeTokensSold - Amounts of net outcome tokens that have been sold. Negative amount means more have been bought than sold.
* @param {number|string|BigNumber} opts.funding - The amount of funding market has
* @param {number|string|BigNumber} opts.outcomeTokenIndex - The index of the outcome
* @param {number|string|BigNumber} opts.outcomeTokenCount - The number of outcome tokens to buy
* @param {number|string|BigNumber} opts.feeFactor - The fee factor. Specifying 1,000,000 corresponds to 100%, 50,000 corresponds to 5%, etc.
* @returns {Decimal} The cost of the outcome tokens in event collateral tokens
* @alias Gnosis.calcLMSRCost
*/
export function calcLMSRCost () {
let [[netOutcomeTokensSold, funding, outcomeTokenIndex, outcomeTokenCount, feeFactor]] =
normalizeWeb3Args(Array.from(arguments), {
methodName: 'calcLMSRCost',
functionInputs: [
{ name: 'netOutcomeTokensSold', type: 'int256[]' },
{ name: 'funding', type: 'uint256' },
{ name: 'outcomeTokenIndex', type: 'uint8' },
{ name: 'outcomeTokenCount', type: 'uint256' },
{ name: 'feeFactor', type: 'uint24' },
],
defaults: {
feeFactor: 0,
},
})
outcomeTokenCount = new Decimal(outcomeTokenCount.toString())
let baseCost
if(funding == 0) {
baseCost = outcomeTokenCount
} else {
const b = new Decimal(funding.toString()).dividedBy(new Decimal(netOutcomeTokensSold.length).ln())
baseCost = b.times(
netOutcomeTokensSold.reduce((acc, numShares, i) =>
acc.plus(
new Decimal(numShares.toString())
.plus(i === outcomeTokenIndex ? outcomeTokenCount : 0)
.dividedBy(b)
.exp()),
new Decimal(0)).ln()
.minus(netOutcomeTokensSold.reduce((acc, numShares) =>
acc.plus(
new Decimal(numShares.toString())
.dividedBy(b)
.exp()),
new Decimal(0)).ln()
))
}
return baseCost.times(new Decimal(1).plus(new Decimal(feeFactor).dividedBy(1e6)))
.times(1+1e-9).ceil() // TODO: Standardize this 1e-9 and 1e9 in isClose of tests
// This is necessary because of rounding errors due to
// series truncation in solidity implementation.
}
/**
* Estimates profit from selling specified number of outcome tokens to LMSR market.Unfunded markets will be handled as a special case, returning no profit.
* @param {number[]|string[]|BigNumber[]} opts.netOutcomeTokensSold - Amounts of net outcome tokens that have been sold by the market already. Negative amount means more have been sold to the market than sold by the market.
* @param {number|string|BigNumber} opts.funding - The amount of funding market has
* @param {number|string|BigNumber} opts.outcomeTokenIndex - The index of the outcome
* @param {number|string|BigNumber} opts.outcomeTokenCount - The number of outcome tokens to sell
* @param {number|string|BigNumber} opts.feeFactor - The fee factor. Specifying 1,000,000 corresponds to 100%, 50,000 corresponds to 5%, etc.
* @returns {Decimal} The profit from selling outcome tokens in event collateral tokens
* @alias Gnosis.calcLMSRProfit
*/
export function calcLMSRProfit () {
let [[netOutcomeTokensSold, funding, outcomeTokenIndex, outcomeTokenCount, feeFactor]] =
normalizeWeb3Args(Array.from(arguments), {
methodName: 'calcLMSRProfit',
functionInputs: [
{ name: 'netOutcomeTokensSold', type: 'int256[]' },
{ name: 'funding', type: 'uint256' },
{ name: 'outcomeTokenIndex', type: 'uint8' },
{ name: 'outcomeTokenCount', type: 'uint256' },
{ name: 'feeFactor', type: 'uint24' },
],
defaults: {
feeFactor: 0,
},
})
if(funding == 0) {
return new Decimal(0)
}
outcomeTokenCount = new Decimal(outcomeTokenCount.toString())
let b = new Decimal(funding.toString()).dividedBy(new Decimal(netOutcomeTokensSold.length).ln())
return b.times(
netOutcomeTokensSold.reduce((acc, numShares) =>
acc.plus(
new Decimal(numShares.toString())
.dividedBy(b)
.exp()),
new Decimal(0)).ln()
.minus(netOutcomeTokensSold.reduce((acc, numShares, i) =>
acc.plus(
new Decimal(numShares.toString())
.minus(i === outcomeTokenIndex ? outcomeTokenCount : 0)
.dividedBy(b)
.exp()),
new Decimal(0)).ln()
)).times(new Decimal(1).minus(new Decimal(feeFactor).dividedBy(1e6)))
.dividedBy(1+1e-9).floor() // TODO: Standardize this 1e-9 and 1e9 in isClose of tests
// This is necessary because of rounding errors due to
// series truncation in solidity implementation.
}
/**
* Estimates the number of outcome tokens which can be purchased by specified amount of collateral.
* @param {Number[]|string[]|BigNumber[]} opts.netOutcomeTokensSold - Amounts of net outcome tokens that have been sold. Negative amount means more have been bought than sold.
* @param {number|string|BigNumber} opts.funding - The amount of funding market has
* @param {number|string|BigNumber} opts.outcomeTokenIndex - The index of the outcome
* @param {number|string|BigNumber} opts.cost - The amount of collateral for buying tokens
* @returns {Decimal} The number of outcome tokens that can be bought
* @alias Gnosis.calcLMSROutcomeTokenCount
*/
export function calcLMSROutcomeTokenCount () {
// decimal.js making this reaaally messy :/
let [[netOutcomeTokensSold, funding, outcomeTokenIndex, cost, feeFactor]] =
normalizeWeb3Args(Array.from(arguments), {
methodName: 'calcLMSROutcomeTokenCount',
functionInputs: [
{ name: 'netOutcomeTokensSold', type: 'int256[]' },
{ name: 'funding', type: 'uint256'},
{ name: 'outcomeTokenIndex', type: 'uint8'},
{ name: 'cost', type: 'uint256'},
{ name: 'feeFactor', type: 'uint24' },
],
defaults: {
feeFactor: 0,
},
})
cost = new Decimal(cost.toString())
let b = new Decimal(funding.toString()).dividedBy(new Decimal(netOutcomeTokensSold.length).ln())
return b.times(
netOutcomeTokensSold.reduce((acc, numShares) =>
acc.plus(
new Decimal(numShares.toString())
.plus(cost.dividedBy(new Decimal(1).plus(new Decimal(feeFactor).dividedBy(1e6))))
.dividedBy(b)
.exp()),
new Decimal(0))
.minus(netOutcomeTokensSold.reduce((acc, numShares, i) =>
i === outcomeTokenIndex ? acc
: acc.plus(
new Decimal(numShares.toString())
.dividedBy(b)
.exp()),
new Decimal(0)))
.ln()).minus(netOutcomeTokensSold[outcomeTokenIndex]).floor()
}
/**
* Estimates the marginal price of outcome token. Unfunded markets with no outcome tokens sold will be handled as a special case, returning (1 / number of outcomes sold).
* @param {Number[]|string[]|BigNumber[]} opts.netOutcomeTokensSold - Amounts of net outcome tokens that have been sold. Negative amount means more have been bought than sold.
* @param {number|string|BigNumber} opts.funding - The amount of funding market has
* @param {number|string|BigNumber} opts.outcomeTokenIndex - The index of the outcome
* @returns {Decimal} The marginal price of outcome tokens. Will differ from actual price, which varies with quantity being moved.
* @alias Gnosis.calcLMSRMarginalPrice
*/
export function calcLMSRMarginalPrice() {
let [[netOutcomeTokensSold, funding, outcomeTokenIndex]] =
normalizeWeb3Args(Array.from(arguments), {
methodName: 'calcLMSRMarginalPrice',
functionInputs: [
{ name: 'netOutcomeTokensSold', type: 'int256[]' },
{ name: 'funding', type: 'uint256'},
{ name: 'outcomeTokenIndex', type: 'uint8'},
]
})
if(funding == 0 && netOutcomeTokensSold.every(quantity => quantity == 0)) {
return new Decimal(1).div(netOutcomeTokensSold.length)
}
const b = Decimal(funding.valueOf()).div(Decimal.ln(netOutcomeTokensSold.length))
const expOffset = Decimal.max(...netOutcomeTokensSold).div(b)
return Decimal(netOutcomeTokensSold[outcomeTokenIndex].valueOf()).div(b).sub(expOffset).exp().div(
netOutcomeTokensSold.reduce(
(acc, tokensSold) => acc.add(Decimal(tokensSold.valueOf()).div(b).sub(expOffset).exp()),
Decimal(0)
)
)
}