diff --git a/common/calculate-swap3.ts b/common/calculate-swap3.ts index a2f957a8..a40fe54a 100644 --- a/common/calculate-swap3.ts +++ b/common/calculate-swap3.ts @@ -89,6 +89,7 @@ export function calculateLPCost( deltaL: number ) { // TODO: this is subtly wrong, because of rounding between curTick and sqrtPrice + // Also below in buyYES const upperTick = Math.min(maxTick, Math.max(minTick, curTick)) const costN = toRatio(upperTick) ** 0.5 - toRatio(minTick) ** 0.5 @@ -101,6 +102,62 @@ export function calculateLPCost( } } +// Returns a preview of the new pool + number of YES shares purchased. +// Does NOT modify the pool +// Hm, logic is pretty complicated. Let's see if we can simplify this. +export function buyYes( + pool: Swap3Pool, + amount: number // In M$ +) { + const tickStates = sortedTickStates(pool) + let tick = pool.tick + let stateIndex = 0 + let amountLeft = amount + let yesPurchased = 0 + while (amountLeft > 0) { + // Find the current tick state + while (tick >= tickStates[stateIndex + 1].tick) { + stateIndex++ + if (stateIndex > tickStates.length - 2) { + // We've reached the end of the tick states... + throw new Error('Ran out of tick states') + } + } + const state = tickStates[stateIndex] + const nextState = tickStates[stateIndex + 1] + + // Copied from above; TODO extract to common function + const noCost = toRatio(nextState.tick) ** 0.5 - toRatio(tick) ** 0.5 + const yesCost = + 1 / toRatio(tick) ** 0.5 - 1 / toRatio(nextState.tick) ** 0.5 + + if (noCost * state.liquidityGross <= amountLeft) { + // We can fully purchase up until the next tick state + amountLeft -= noCost * state.liquidityGross + yesPurchased += yesCost * state.liquidityGross + tick = nextState.tick + } else { + // Buy as much as we can at the current tick state. Derivation: + // noCostLeft = toRatio(upTick) ** 0.5 - toRatio(tick) ** 0.5 + // (noCostLeft + toRatio(tick) ** 0.5) ** 2 = toRatio(upTick) + // TODO check flooring done here + const noCostLeft = amountLeft / state.liquidityGross + const finalTick = fromRatio((noCostLeft + toRatio(tick) ** 0.5) ** 2) + const yesCostLeft = + 1 / toRatio(tick) ** 0.5 - 1 / toRatio(finalTick) ** 0.5 + + amountLeft = 0 + yesPurchased += yesCostLeft * state.liquidityGross + tick = finalTick + } + } + + return { + newPoolTick: tick, + yesPurchased, + } +} + // Currently, this mutates the pool. Should it return a new object instead? export function addPosition( pool: Swap3Pool, @@ -165,5 +222,9 @@ export function toProb(tick: number) { // Returns the tick for a given probability from 0 to 1 export function fromProb(prob: number) { const ratio = prob / (1 - prob) + return fromRatio(ratio) +} + +function fromRatio(ratio: number) { return Math.floor(Math.log(ratio) / Math.log(1.0001)) } diff --git a/web/pages/swap.tsx b/web/pages/swap.tsx index edeb6c48..6e5aeb7b 100644 --- a/web/pages/swap.tsx +++ b/web/pages/swap.tsx @@ -1,5 +1,6 @@ import { addPosition, + buyYes, calculateLPCost, fromProb, getSwap3Probability, @@ -136,11 +137,14 @@ export default function Swap() { tickStates: [], } INIT_POOL = addPosition(INIT_POOL, -(2 ** 23), 2 ** 20, 100) + INIT_POOL = addPosition(INIT_POOL, fromProb(0.32), fromProb(0.35), 100) INIT_POOL = grossLiquidity(INIT_POOL) + const [pool, setPool] = useState(INIT_POOL) const [minTick, setMinTick] = useState(0) const [maxTick, setMaxTick] = useState(0) + const [buyAmount, setBuyAmount] = useState(0) const { requiredN, requiredY } = calculateLPCost( pool.tick, @@ -149,6 +153,8 @@ export default function Swap() { 100 // deltaL ) + const { newPoolTick, yesPurchased } = buyYes(pool, buyAmount) + return (