From 5707a90918bfa21d06bb6f8bc3e2605d15bff8b7 Mon Sep 17 00:00:00 2001 From: case Date: Mon, 2 Sep 2024 13:50:47 -0700 Subject: [PATCH 1/6] First attempt at fixing trade price for multi --- packages/finance/amms/maniswap-v1.1.test.ts | 137 +++++++++++++------- packages/finance/amms/maniswap-v1.1.ts | 13 +- 2 files changed, 97 insertions(+), 53 deletions(-) diff --git a/packages/finance/amms/maniswap-v1.1.test.ts b/packages/finance/amms/maniswap-v1.1.test.ts index 24591412..b873c34c 100644 --- a/packages/finance/amms/maniswap-v1.1.test.ts +++ b/packages/finance/amms/maniswap-v1.1.test.ts @@ -9,55 +9,83 @@ jest.mock('@play-money/markets/lib/getMarketOption', () => ({ getMarketOption: j describe('maniswap-v1.1', () => { describe('trade', () => { - it('should return correct amount for buying YES', async () => { - // Current probability = 0.75 - const amount = await trade({ - amount: new Decimal(50), - targetShare: new Decimal(100), - shares: [new Decimal(100), new Decimal(300)], - isBuy: true, - }) - - expect(amount).toBeCloseToDecimal(64.29) - }) - - it('should return correct amount for buying NO', async () => { - // Current probability = 0.75 - const amount = await trade({ - amount: new Decimal(50), - targetShare: new Decimal(300), - shares: [new Decimal(100), new Decimal(300)], - isBuy: true, - }) - - expect(amount).toBeCloseToDecimal(150) - }) - - // This is the inverse of the test for buying YES - it('should return correct amount for selling YES', async () => { - // Current probability ~= 0.80 - const amount = await trade({ - amount: new Decimal(64.29), - targetShare: new Decimal(85.71), - shares: [new Decimal(85.71), new Decimal(350)], - isBuy: false, - }) + test.each([ + { targetShare: 100, shares: [100, 300], expected: 64.29 }, + { targetShare: 100, shares: [100, 400, 400, 400], expected: 63.04 }, + { targetShare: 100, shares: [100, 400, 400, 400, 400, 400, 400, 400, 400], expected: 63.79 }, + ])( + 'should return $expected for buying high targetShare: $targetShare of shares: $shares', + async ({ targetShare, shares, expected }) => { + // Current probability = 0.75 + const amount = await trade({ + amount: new Decimal(50), + targetShare: new Decimal(targetShare), + shares: shares.map((share) => new Decimal(share)), + isBuy: true, + }) + + expect(amount).toBeCloseToDecimal(expected) + } + ) - expect(amount).toBeCloseToDecimal(50) - }) + test.each([ + { targetShare: 300, shares: [100, 300], expected: 150 }, + { targetShare: 400, shares: [100, 400, 400, 400], expected: 290 }, + { targetShare: 400, shares: [100, 400, 400, 400, 400, 400, 400, 400, 400], expected: 370 }, + ])( + 'should return $expected for buying low targetShare: $targetShare of shares: $shares', + async ({ targetShare, shares, expected }) => { + // Current probability = 0.75 + const amount = await trade({ + amount: new Decimal(50), + targetShare: new Decimal(targetShare), + shares: shares.map((share) => new Decimal(share)), + isBuy: true, + }) + + expect(amount).toBeCloseToDecimal(expected) + } + ) - // This is the inverse of the test for buying NO - it('should return correct amount for selling NO', async () => { - // Current probability ~= 0.57 - const amount = await trade({ - amount: new Decimal(150), - targetShare: new Decimal(200), - shares: [new Decimal(150), new Decimal(200)], - isBuy: false, - }) + // This is the inverse of the test for buying high + test.each([ + { targetShare: 85.71, shares: [85.71, 350], expected: 50 }, + { targetShare: 85.71, shares: [85.71, 400, 400, 400], expected: 49.74 }, + { targetShare: 85.71, shares: [85.71, 400, 400, 400, 400, 400, 400, 400, 400], expected: 49.05 }, + ])( + 'should return $expected for selling high targetShare: $targetShare of shares: $shares', + async ({ targetShare, shares, expected }) => { + // Current probability = 0.75 + const amount = await trade({ + amount: new Decimal(64.29), + targetShare: new Decimal(targetShare), + shares: shares.map((share) => new Decimal(share)), + isBuy: false, + }) + + expect(amount).toBeCloseToDecimal(expected) + } + ) - expect(amount).toBeCloseToDecimal(50) - }) + // This is the inverse of the test for buying low + test.each([ + { targetShare: 200, shares: [150, 200], expected: 50 }, + { targetShare: 200, shares: [150, 200, 450, 450], expected: 64.75 }, + { targetShare: 200, shares: [150, 200, 450, 450, 450, 450, 450, 450, 450], expected: 68.66 }, + ])( + 'should return $expected for selling low targetShare: $targetShare of shares: $shares', + async ({ targetShare, shares, expected }) => { + // Current probability = 0.75 + const amount = await trade({ + amount: new Decimal(150), + targetShare: new Decimal(targetShare), + shares: shares.map((share) => new Decimal(share)), + isBuy: false, + }) + + expect(amount).toBeCloseToDecimal(expected) + } + ) }) describe('quote', () => { @@ -185,8 +213,21 @@ describe('maniswap-v1.1', () => { shares: [new Decimal(200), new Decimal(200), new Decimal(200)], }) - expect(result.probability).toBeCloseToDecimal(0.48) - expect(result.shares).toBeCloseToDecimal(72.22) + expect(result.probability).toBeCloseToDecimal(0.57) + expect(result.shares).toBeCloseToDecimal(116.66) + }) + + // Inverse of previous buy + it('should return correct quote for selling option in multiple choice', async () => { + const result = await quote({ + amount: new Decimal(116.66), + probability: new Decimal(0.01), + targetShare: new Decimal(133.34), + shares: [new Decimal(133.34), new Decimal(250), new Decimal(250)], + }) + + expect(result.probability).toBeCloseToDecimal(0.33) + expect(result.shares).toBeCloseToDecimal(56.8) }) }) diff --git a/packages/finance/amms/maniswap-v1.1.ts b/packages/finance/amms/maniswap-v1.1.ts index 43df31db..978af72c 100644 --- a/packages/finance/amms/maniswap-v1.1.ts +++ b/packages/finance/amms/maniswap-v1.1.ts @@ -12,19 +12,22 @@ function calculateTrade({ targetShare, totalShares, isBuy, + numOptions, }: { amount: Decimal targetShare: Decimal totalShares: Decimal isBuy: boolean + numOptions: number }) { + const avg = totalShares.div(numOptions - 1) if (isBuy) { - return amount.times(amount.add(totalShares)).div(amount.add(totalShares.sub(targetShare))) + return amount.times(amount.add(avg)).div(amount.add(avg.sub(targetShare))) } - return totalShares + return avg .add(amount) - .sub(Decimal.sqrt(totalShares.add(amount).pow(2).sub(totalShares.sub(targetShare).times(4).times(amount)))) + .sub(Decimal.sqrt(avg.add(amount).pow(2).sub(avg.sub(targetShare).times(4).times(amount)))) .times(0.5) } @@ -86,7 +89,7 @@ export function trade({ isBuy: boolean }) { const totalShares = sumShares(shares) - return calculateTrade({ amount, targetShare, totalShares, isBuy }) + return calculateTrade({ amount, targetShare, totalShares, numOptions: shares.length, isBuy }) } export async function quote({ @@ -108,7 +111,7 @@ export async function quote({ let costToHitProbability = calculateProbabilityCost({ probability, targetShare, totalShares, isBuy }) const cost = Decimal.min(costToHitProbability, amount) - const returnedShares = calculateTrade({ amount: cost, targetShare, totalShares, isBuy }) + const returnedShares = calculateTrade({ amount: cost, targetShare, totalShares, isBuy, numOptions: shares.length }) const updatedShares = shares.map((share, i) => i === targetIndex ? share.sub(returnedShares).add(cost) : isBuy ? share.add(cost) : share.sub(returnedShares) From d6b2c711a8ef8332f7632786e1f99647f1af9b06 Mon Sep 17 00:00:00 2001 From: case Date: Mon, 2 Sep 2024 15:47:55 -0700 Subject: [PATCH 2/6] Take 2 --- packages/finance/amms/maniswap-v1.1.test.ts | 28 +++---- packages/finance/amms/maniswap-v1.1.ts | 83 ++++++++++++++++----- 2 files changed, 80 insertions(+), 31 deletions(-) diff --git a/packages/finance/amms/maniswap-v1.1.test.ts b/packages/finance/amms/maniswap-v1.1.test.ts index b873c34c..9d90d4b2 100644 --- a/packages/finance/amms/maniswap-v1.1.test.ts +++ b/packages/finance/amms/maniswap-v1.1.test.ts @@ -11,8 +11,8 @@ describe('maniswap-v1.1', () => { describe('trade', () => { test.each([ { targetShare: 100, shares: [100, 300], expected: 64.29 }, - { targetShare: 100, shares: [100, 400, 400, 400], expected: 63.04 }, - { targetShare: 100, shares: [100, 400, 400, 400, 400, 400, 400, 400, 400], expected: 63.79 }, + { targetShare: 100, shares: [100, 400, 400, 400], expected: 79.76 }, + { targetShare: 100, shares: [100, 400, 400, 400, 400, 400, 400, 400, 400], expected: 111.02 }, ])( 'should return $expected for buying high targetShare: $targetShare of shares: $shares', async ({ targetShare, shares, expected }) => { @@ -30,8 +30,8 @@ describe('maniswap-v1.1', () => { test.each([ { targetShare: 300, shares: [100, 300], expected: 150 }, - { targetShare: 400, shares: [100, 400, 400, 400], expected: 290 }, - { targetShare: 400, shares: [100, 400, 400, 400, 400, 400, 400, 400, 400], expected: 370 }, + { targetShare: 400, shares: [100, 400, 400, 400], expected: 239.3 }, + { targetShare: 400, shares: [100, 400, 400, 400, 400, 400, 400, 400, 400], expected: 333.07 }, ])( 'should return $expected for buying low targetShare: $targetShare of shares: $shares', async ({ targetShare, shares, expected }) => { @@ -50,8 +50,8 @@ describe('maniswap-v1.1', () => { // This is the inverse of the test for buying high test.each([ { targetShare: 85.71, shares: [85.71, 350], expected: 50 }, - { targetShare: 85.71, shares: [85.71, 400, 400, 400], expected: 49.74 }, - { targetShare: 85.71, shares: [85.71, 400, 400, 400, 400, 400, 400, 400, 400], expected: 49.05 }, + { targetShare: 85.71, shares: [85.71, 400, 400, 400], expected: 36.13 }, + { targetShare: 85.71, shares: [85.71, 400, 400, 400, 400, 400, 400, 400, 400], expected: 20.21 }, ])( 'should return $expected for selling high targetShare: $targetShare of shares: $shares', async ({ targetShare, shares, expected }) => { @@ -70,8 +70,8 @@ describe('maniswap-v1.1', () => { // This is the inverse of the test for buying low test.each([ { targetShare: 200, shares: [150, 200], expected: 50 }, - { targetShare: 200, shares: [150, 200, 450, 450], expected: 64.75 }, - { targetShare: 200, shares: [150, 200, 450, 450, 450, 450, 450, 450, 450], expected: 68.66 }, + { targetShare: 200, shares: [150, 200, 450, 450], expected: 36.58 }, + { targetShare: 200, shares: [150, 200, 450, 450, 450, 450, 450, 450, 450], expected: 21.45 }, ])( 'should return $expected for selling low targetShare: $targetShare of shares: $shares', async ({ targetShare, shares, expected }) => { @@ -213,8 +213,8 @@ describe('maniswap-v1.1', () => { shares: [new Decimal(200), new Decimal(200), new Decimal(200)], }) - expect(result.probability).toBeCloseToDecimal(0.57) - expect(result.shares).toBeCloseToDecimal(116.66) + expect(result.probability).toBeCloseToDecimal(0.59) + expect(result.shares).toBeCloseToDecimal(122) }) // Inverse of previous buy @@ -222,12 +222,12 @@ describe('maniswap-v1.1', () => { const result = await quote({ amount: new Decimal(116.66), probability: new Decimal(0.01), - targetShare: new Decimal(133.34), - shares: [new Decimal(133.34), new Decimal(250), new Decimal(250)], + targetShare: new Decimal(128), + shares: [new Decimal(128), new Decimal(250), new Decimal(250)], }) - expect(result.probability).toBeCloseToDecimal(0.33) - expect(result.shares).toBeCloseToDecimal(56.8) + expect(result.probability).toBeCloseToDecimal(0.34) + expect(result.shares).toBeCloseToDecimal(48.2) }) }) diff --git a/packages/finance/amms/maniswap-v1.1.ts b/packages/finance/amms/maniswap-v1.1.ts index 978af72c..843e3ec2 100644 --- a/packages/finance/amms/maniswap-v1.1.ts +++ b/packages/finance/amms/maniswap-v1.1.ts @@ -7,41 +7,88 @@ import Decimal from 'decimal.js' For more information, see https://manifoldmarkets.notion.site/Maniswap-ce406e1e897d417cbd491071ea8a0c39 */ + +function getSellDifference({ + amount, + targetShareIndex, + shares, + amountReturning, +}: { + amount: Decimal + targetShareIndex: number + shares: Array + amountReturning: Decimal +}) { + const sharesProduct = multiplyShares(shares) + return sharesProduct.sub( + multiplyShares( + shares.map((share, i) => + i === targetShareIndex ? share.sub(amountReturning).plus(amount) : share.sub(amountReturning) + ) + ) + ) +} + function calculateTrade({ amount, targetShare, - totalShares, + shares, isBuy, - numOptions, }: { amount: Decimal targetShare: Decimal - totalShares: Decimal + shares: Array isBuy: boolean - numOptions: number }) { - const avg = totalShares.div(numOptions - 1) + const targetShareIndex = findShareIndex(shares, targetShare) + const sharesWithoutTarget = shares.filter((_, i) => i !== targetShareIndex) + if (isBuy) { - return amount.times(amount.add(avg)).div(amount.add(avg.sub(targetShare))) + const sharesProduct = multiplyShares(shares) + const sharesProductAdded = multiplyShares(sharesWithoutTarget.map((share) => share.plus(amount))) + + return targetShare.plus(amount).sub(sharesProduct.div(sharesProductAdded)) } - return avg - .add(amount) - .sub(Decimal.sqrt(avg.add(amount).pow(2).sub(avg.sub(targetShare).times(4).times(amount)))) - .times(0.5) + let lowerBound = new Decimal(0) + let upperBound = amount + let amountReturning = lowerBound.plus(upperBound).div(2) + let difference = getSellDifference({ amount, targetShareIndex, shares, amountReturning }) + + let iterationCount = 0 + + while (upperBound.minus(lowerBound).gt(0.0001)) { + if (iterationCount >= 100) { + throw new Error('Failed to converge. Max iterations reached.') + } + + if (difference.lessThan(0)) { + lowerBound = amountReturning + } else { + upperBound = amountReturning + } + + amountReturning = lowerBound.plus(upperBound).div(2) + difference = getSellDifference({ amount, targetShareIndex, shares, amountReturning }) + + iterationCount++ + } + + return amountReturning } function calculateProbabilityCost({ probability, targetShare, - totalShares, + shares, isBuy, }: { probability: Decimal targetShare: Decimal - totalShares: Decimal + shares: Array isBuy: boolean }): Decimal { + const totalShares = sumShares(shares) const totalOppositeShares = totalShares.sub(targetShare) const factor = isBuy ? probability.neg().div(probability.sub(1)) : probability.sub(1).neg().div(probability) @@ -65,6 +112,10 @@ function sumShares(shares: Array) { return shares.reduce((sum, share) => sum.add(share), new Decimal(0)) } +function multiplyShares(shares: Array) { + return shares.reduce((sum, share) => sum.times(share), new Decimal(1)) +} + export function calculateProbability({ index, shares }: { index: number; shares: Array }): Decimal { const indexShares = shares[index] @@ -88,8 +139,7 @@ export function trade({ shares: Array isBuy: boolean }) { - const totalShares = sumShares(shares) - return calculateTrade({ amount, targetShare, totalShares, numOptions: shares.length, isBuy }) + return calculateTrade({ amount, targetShare, shares, isBuy }) } export async function quote({ @@ -104,14 +154,13 @@ export async function quote({ shares: Array }): Promise<{ probability: Decimal; shares: Decimal; cost: Decimal }> { const targetIndex = findShareIndex(shares, targetShare) - const totalShares = sumShares(shares) const currentProbability = calculateProbability({ index: targetIndex, shares }) const isBuy = currentProbability.lt(probability) - let costToHitProbability = calculateProbabilityCost({ probability, targetShare, totalShares, isBuy }) + let costToHitProbability = calculateProbabilityCost({ probability, targetShare, shares, isBuy }) const cost = Decimal.min(costToHitProbability, amount) - const returnedShares = calculateTrade({ amount: cost, targetShare, totalShares, isBuy, numOptions: shares.length }) + const returnedShares = calculateTrade({ amount: cost, targetShare, shares, isBuy }) const updatedShares = shares.map((share, i) => i === targetIndex ? share.sub(returnedShares).add(cost) : isBuy ? share.add(cost) : share.sub(returnedShares) From 5a7cb5ec8b5bdedccc33c3e6b322cdaaab626741 Mon Sep 17 00:00:00 2001 From: case Date: Mon, 2 Sep 2024 20:58:58 -0700 Subject: [PATCH 3/6] Fix test rounding error --- packages/markets/lib/updateMarketPositionValues.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markets/lib/updateMarketPositionValues.test.ts b/packages/markets/lib/updateMarketPositionValues.test.ts index 51d2ec0b..4e5dec9a 100644 --- a/packages/markets/lib/updateMarketPositionValues.test.ts +++ b/packages/markets/lib/updateMarketPositionValues.test.ts @@ -154,7 +154,7 @@ describe('updateMarketPositionValues', () => { optionId: 'option2', cost: new Decimal(40), quantity: new Decimal(75), - value: new Decimal(9.0147), + value: new Decimal(9.0148), }), ]) From 6c3b8da3f63973c89fe6b0b541f0968d26558650 Mon Sep 17 00:00:00 2001 From: case Date: Mon, 2 Sep 2024 21:17:19 -0700 Subject: [PATCH 4/6] Cleanup Binary Search implementation --- packages/finance/amms/maniswap-v1.1.ts | 110 ++++++++++++------------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/packages/finance/amms/maniswap-v1.1.ts b/packages/finance/amms/maniswap-v1.1.ts index 843e3ec2..fab8a7ba 100644 --- a/packages/finance/amms/maniswap-v1.1.ts +++ b/packages/finance/amms/maniswap-v1.1.ts @@ -8,7 +8,7 @@ import Decimal from 'decimal.js' For more information, see https://manifoldmarkets.notion.site/Maniswap-ce406e1e897d417cbd491071ea8a0c39 */ -function getSellDifference({ +function calculateSellDifference({ amount, targetShareIndex, shares, @@ -29,54 +29,6 @@ function getSellDifference({ ) } -function calculateTrade({ - amount, - targetShare, - shares, - isBuy, -}: { - amount: Decimal - targetShare: Decimal - shares: Array - isBuy: boolean -}) { - const targetShareIndex = findShareIndex(shares, targetShare) - const sharesWithoutTarget = shares.filter((_, i) => i !== targetShareIndex) - - if (isBuy) { - const sharesProduct = multiplyShares(shares) - const sharesProductAdded = multiplyShares(sharesWithoutTarget.map((share) => share.plus(amount))) - - return targetShare.plus(amount).sub(sharesProduct.div(sharesProductAdded)) - } - - let lowerBound = new Decimal(0) - let upperBound = amount - let amountReturning = lowerBound.plus(upperBound).div(2) - let difference = getSellDifference({ amount, targetShareIndex, shares, amountReturning }) - - let iterationCount = 0 - - while (upperBound.minus(lowerBound).gt(0.0001)) { - if (iterationCount >= 100) { - throw new Error('Failed to converge. Max iterations reached.') - } - - if (difference.lessThan(0)) { - lowerBound = amountReturning - } else { - upperBound = amountReturning - } - - amountReturning = lowerBound.plus(upperBound).div(2) - difference = getSellDifference({ amount, targetShareIndex, shares, amountReturning }) - - iterationCount++ - } - - return amountReturning -} - function calculateProbabilityCost({ probability, targetShare, @@ -108,8 +60,8 @@ function findShareIndex(shares: Array, targetShare: Decimal): number { return shares.findIndex((share) => share.eq(targetShare)) } -function sumShares(shares: Array) { - return shares.reduce((sum, share) => sum.add(share), new Decimal(0)) +function sumShares(shares: Array) { + return shares.reduce((sum, share) => sum.add(share), new Decimal(0)) } function multiplyShares(shares: Array) { @@ -118,14 +70,42 @@ function multiplyShares(shares: Array) { export function calculateProbability({ index, shares }: { index: number; shares: Array }): Decimal { const indexShares = shares[index] - - // Calculate the sum of the shares of each index - const sum = shares.reduce((sum, share) => sum.plus(share), new Decimal(0)) + const sum = sumShares(shares) // The probability for the given index is one minus the share count at the index times the number of dimensions divided by the sum of all shares - const probability = new Decimal(1).sub(new Decimal(indexShares).mul(shares.length - 1).div(sum)) + return new Decimal(1).sub(new Decimal(indexShares).mul(shares.length - 1).div(sum)) +} + +function binarySearch( + evaluate: (mid: Decimal) => Decimal, + low: Decimal, + high: Decimal, + tolerance: Decimal = new Decimal('0.0001'), + maxIterations: number = 100 +): Decimal { + let iterationCount = 0 + while (high.minus(low).gt(tolerance)) { + if (iterationCount >= maxIterations) { + throw new Error('Failed to converge. Max iterations reached.') + } + + const mid = low.plus(high).div(2) + const result = evaluate(mid) + + if (result.abs().lte(tolerance)) { + // If we're very close to zero, favor the upper bound + return high + } else if (result.lessThan(0)) { + low = mid + } else { + high = mid + } - return probability + iterationCount++ + } + + // Return the upper bound to ensure largest possible value + return high } export function trade({ @@ -139,7 +119,21 @@ export function trade({ shares: Array isBuy: boolean }) { - return calculateTrade({ amount, targetShare, shares, isBuy }) + const targetShareIndex = findShareIndex(shares, targetShare) + const sharesWithoutTarget = shares.filter((_, i) => i !== targetShareIndex) + + if (isBuy) { + const sharesProduct = multiplyShares(shares) + const sharesProductAdded = multiplyShares(sharesWithoutTarget.map((share) => share.plus(amount))) + + return targetShare.plus(amount).sub(sharesProduct.div(sharesProductAdded)) + } + + return binarySearch( + (amountReturning: Decimal) => calculateSellDifference({ amount, targetShareIndex, shares, amountReturning }), + new Decimal(0), + amount + ) } export async function quote({ @@ -160,7 +154,7 @@ export async function quote({ let costToHitProbability = calculateProbabilityCost({ probability, targetShare, shares, isBuy }) const cost = Decimal.min(costToHitProbability, amount) - const returnedShares = calculateTrade({ amount: cost, targetShare, shares, isBuy }) + const returnedShares = trade({ amount: cost, targetShare, shares, isBuy }) const updatedShares = shares.map((share, i) => i === targetIndex ? share.sub(returnedShares).add(cost) : isBuy ? share.add(cost) : share.sub(returnedShares) From bef7f01b7d82d69b5f1c9012c19e19d87335355b Mon Sep 17 00:00:00 2001 From: case Date: Mon, 2 Sep 2024 21:21:39 -0700 Subject: [PATCH 5/6] Add Comments explaining calcs --- packages/finance/amms/maniswap-v1.1.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/finance/amms/maniswap-v1.1.ts b/packages/finance/amms/maniswap-v1.1.ts index fab8a7ba..b5bbb7e6 100644 --- a/packages/finance/amms/maniswap-v1.1.ts +++ b/packages/finance/amms/maniswap-v1.1.ts @@ -120,15 +120,18 @@ export function trade({ isBuy: boolean }) { const targetShareIndex = findShareIndex(shares, targetShare) - const sharesWithoutTarget = shares.filter((_, i) => i !== targetShareIndex) + // (num shares of buying) + (amount of currency buying) - (product of all share nums)/(product of for each option, sum of option and the amount of currency buying) + // When buying x: returnAmount = x + a - xyz/((y+a)(z+a)) if (isBuy) { const sharesProduct = multiplyShares(shares) + const sharesWithoutTarget = shares.filter((_, i) => i !== targetShareIndex) const sharesProductAdded = multiplyShares(sharesWithoutTarget.map((share) => share.plus(amount))) return targetShare.plus(amount).sub(sharesProduct.div(sharesProductAdded)) } + // When selling n dimensions doesn't have a closed form, so we use binary search to find the amount to sell return binarySearch( (amountReturning: Decimal) => calculateSellDifference({ amount, targetShareIndex, shares, amountReturning }), new Decimal(0), From 0932aee5c4d8fb7ccc586d91c139211e64bbf9d5 Mon Sep 17 00:00:00 2001 From: case Date: Fri, 30 Aug 2024 15:07:54 -0700 Subject: [PATCH 6/6] Calculate Liquidity Prob when adding Liquidity --- packages/finance/amms/maniswap-v1.1.test.ts | 88 ++++++++++++++++++++- packages/finance/amms/maniswap-v1.1.ts | 47 +++-------- 2 files changed, 98 insertions(+), 37 deletions(-) diff --git a/packages/finance/amms/maniswap-v1.1.test.ts b/packages/finance/amms/maniswap-v1.1.test.ts index 9d90d4b2..217ef885 100644 --- a/packages/finance/amms/maniswap-v1.1.test.ts +++ b/packages/finance/amms/maniswap-v1.1.test.ts @@ -247,7 +247,93 @@ describe('maniswap-v1.1', () => { ], }) - expect(result).toEqual([expect.closeToDecimal(50), expect.closeToDecimal(50)]) + expect(result).toEqual([ + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.5) }, + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.5) }, + ]) + }) + + it('should correctly add liquidity to an imbalanced market', async () => { + const result = await addLiquidity({ + amount: new Decimal(50), + options: [ + { + shares: new Decimal(100), + liquidityProbability: new Decimal(0.3), + }, + { + shares: new Decimal(300), + liquidityProbability: new Decimal(0.7), + }, + ], + }) + + expect(result).toEqual([ + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.25) }, + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.75) }, + ]) + }) + + it('should correctly add liquidity to a balanced 4 option market', async () => { + const result = await addLiquidity({ + amount: new Decimal(50), + options: [ + { + shares: new Decimal(1000), + liquidityProbability: new Decimal(0.25), + }, + { + shares: new Decimal(1000), + liquidityProbability: new Decimal(0.25), + }, + { + shares: new Decimal(1000), + liquidityProbability: new Decimal(0.25), + }, + { + shares: new Decimal(1000), + liquidityProbability: new Decimal(0.25), + }, + ], + }) + + expect(result).toEqual([ + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.25) }, + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.25) }, + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.25) }, + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.25) }, + ]) + }) + + it('should correctly add liquidity to an imbalanced 4 option market', async () => { + const result = await addLiquidity({ + amount: new Decimal(50), + options: [ + { + shares: new Decimal(100), + liquidityProbability: new Decimal(0.25), + }, + { + shares: new Decimal(300), + liquidityProbability: new Decimal(0.25), + }, + { + shares: new Decimal(300), + liquidityProbability: new Decimal(0.25), + }, + { + shares: new Decimal(300), + liquidityProbability: new Decimal(0.25), + }, + ], + }) + + expect(result).toEqual([ + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.205) }, + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.265) }, + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.265) }, + { newShares: expect.closeToDecimal(50), liquidityProbability: expect.closeToDecimal(0.265) }, + ]) }) }) diff --git a/packages/finance/amms/maniswap-v1.1.ts b/packages/finance/amms/maniswap-v1.1.ts index b5bbb7e6..4856af0b 100644 --- a/packages/finance/amms/maniswap-v1.1.ts +++ b/packages/finance/amms/maniswap-v1.1.ts @@ -179,40 +179,15 @@ export function addLiquidity({ }: { amount: Decimal options: Array<{ shares: Decimal; liquidityProbability: Decimal }> -}): Array { - // const totalShares = options.reduce((sum, option) => sum.add(option.shares), new Decimal(0)) - // const newShares: Decimal[] = [] - - // for (const option of options) { - // const { shares, liquidityProbability } = option - // const currentProbability = shares.div(totalShares) - - // const p = calculateNewP(currentProbability, liquidityProbability, amount, shares, totalShares) - // const newOptionShares = calculateNewShares(p, amount, shares, totalShares) - - // newShares.push(newOptionShares) - // } - - return [amount, amount] +}): Array<{ newShares: Decimal; liquidityProbability: Decimal }> { + const denominator = options.reduce((sum, option) => { + return sum.add(option.liquidityProbability.mul(option.shares).div(option.shares.add(amount))) + }, new Decimal(0)) + + return options.map(({ shares, liquidityProbability }) => { + return { + newShares: amount, + liquidityProbability: liquidityProbability.mul(shares).div(shares.add(amount)).div(denominator), + } + }) } - -// function calculateNewP( -// currentProbability: Decimal, -// liquidityProbability: Decimal, -// amount: Decimal, -// shares: Decimal, -// totalShares: Decimal -// ): Decimal { -// // This is a simplified approximation. You may need to use numerical methods for more accuracy. -// const weight = amount.div(totalShares.add(amount)) -// return currentProbability.mul(Decimal.sub(1, weight)).add(liquidityProbability.mul(weight)) -// } - -// function calculateNewShares(p: Decimal, amount: Decimal, shares: Decimal, totalShares: Decimal): Decimal { -// const n = totalShares.sub(shares) -// const newShares = Decimal.pow(shares.add(amount), p) -// .mul(Decimal.pow(n.add(amount), Decimal.sub(1, p))) -// .sub(Decimal.pow(shares, p).mul(Decimal.pow(n, Decimal.sub(1, p)))) - -// return newShares -// }