diff --git a/src/App.tsx b/src/App.tsx index 71301e1..bcc6162 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,12 @@ import PriceSimulationChart from "./PriceSimulationChart"; import HelpText from "./HelpText"; // Utils import { getLast, getAvg, pause } from "./utils"; +import { + getInitialParams, + getPriceR, + getRandomDeltas, + getSlippage +} from "./math"; import { throttle } from "lodash"; // General styles import "./app.css"; @@ -84,7 +90,7 @@ export default function App() { const [curveParams, setCurveParams] = useState({ d0: 1e6, // Initial raise, d0 (DAI) theta: 0.35, // fraction allocated to reserve (.) - p0: 0.1, // Hatch sale Price p0 (DAI / token) + p0: 0.1, // Hatch sale price p0 (DAI / token) returnF: 3, // Return factor (.) wFee: 0.05 // friction coefficient (.) }); @@ -101,10 +107,17 @@ export default function App() { ); // Simulation results - const k = returnF / (1 - theta); // Invariant power kappa (.) - const R0 = (1 - theta / 100) * d0; // Initial reserve (DAI) - const S0 = d0 / p0; // initial supply of tokens (token) - const V0 = S0 ** k / R0; // invariant coef + const { + k, // Invariant power kappa (.) + R0, // Initial reserve (DAI) + S0, // initial supply of tokens (token) + V0 // invariant coef + } = getInitialParams({ + d0, + theta, + p0, + returnF + }); const [priceTimeseries, setPriceTimeseries] = useState([0]); const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]); @@ -141,8 +154,7 @@ export default function App() { let canContinueSimulation = true; async function simulateRandomDelta() { const R_t: number[] = [R0]; - const S_t: number[] = [S0]; - const p_t: number[] = [R0 / S0]; + const p_t: number[] = [getPriceR({ R: R0, V0, k })]; const wFee_t: number[] = [0]; const slippage_t: number[] = []; @@ -151,37 +163,20 @@ export default function App() { const updateEveryNth = 1; // Compute the random deltas - const deltaR_t: number[] = []; - for (let i = 0; i < numSteps; i++) { - const rand = 1 - 2 * Math.random(); - const sin = Math.sin((1 / 20) * (i / numSteps)); - const ascending = Math.sin((Math.PI / 1) * (i / numSteps)); - const deltaR = 1e5 * rand + 1e5 * sin + 2e4 * ascending; - deltaR_t.push(deltaR); - } - - // Normalize random deltas with the average transaction size - const deltaR_avg = getAvg(deltaR_t); - const deltaR_t_normalized = deltaR_t.map( - (deltaR: number) => (avgTxSize * deltaR) / deltaR_avg - ); + const deltaR_t = getRandomDeltas({ numSteps, avgTxSize }); setSimulationRunning(true); for (let i = 0; i < numSteps; i++) { - const deltaR = deltaR_t_normalized[i]; + const deltaR = deltaR_t[i]; - // Protect against too strong negative deltas const R = getLast(R_t); - const S = getLast(S_t); - const p = getLast(p_t); const R_next = R + deltaR; - const deltaS = (V0 * (R + deltaR)) ** (1 / k) - S; - const S_next = S + deltaS; R_t.push(R_next); - S_t.push(S_next); - p_t.push(R_next / S_next); + p_t.push(getPriceR({ R: R_next, V0, k })); + slippage_t.push(getSlippage({ R, deltaR, V0, k })); + // Consider withdraw fees on sales only if (deltaR < 0) { wFee_t.push(getLast(wFee_t) - deltaR * wFee); @@ -190,12 +185,6 @@ export default function App() { wFee_t.push(getLast(wFee_t)); } - const p_next = getLast(p_t); - // const realizedPrice = deltaR / deltaS; - const spotPrice = p; - const slippage = Math.abs(p_next - spotPrice) / spotPrice / 2; - slippage_t.push(slippage); - // Stop the simulation if it's no longer active if (!simulationActive || !canContinueSimulation) break; diff --git a/src/app.css b/src/app.css index 9474f9b..c3ae5bf 100644 --- a/src/app.css +++ b/src/app.css @@ -1,4 +1,5 @@ #root { background-color: #152128; min-height: 100vh; + margin: 0; } diff --git a/src/math.ts b/src/math.ts new file mode 100644 index 0000000..335d0b9 --- /dev/null +++ b/src/math.ts @@ -0,0 +1,81 @@ +import { getAvg } from "./utils"; + +/** + * Computes the initial params given the "user friendly" params: + * - Initial raise, `d0` (DAI) + * - fraction allocated to reserve, `theta` + * - Hatch sale price, `p0` (DAI / token) + * - Return factor, `returnF` + */ +export function getInitialParams({ + d0, + theta, + p0, + returnF +}: { + d0: number; + theta: number; + p0: number; + returnF: number; +}) { + const k = returnF / (1 - theta); // Invariant power kappa (.) + const R0 = (1 - theta) * d0; // Initial reserve (DAI) + const S0 = d0 / p0; // initial supply of tokens (token) + const V0 = S0 ** k / R0; // invariant coef + return { k, R0, S0, V0 }; +} + +/** + * Computes the price at a specific reserve `R` + */ +export function getPriceR({ R, V0, k }: { R: number; V0: number; k: number }) { + return (k * R ** ((k - 1) / k)) / V0 ** (1 / k); +} + +/** + * Compute slippage at a point `R`, given a `deltaR` + */ +export function getSlippage({ + R, + deltaR, + V0, + k +}: { + R: number; + deltaR: number; + V0: number; + k: number; +}) { + const S = (V0 * R) ** (1 / k); + const deltaS = (V0 * (R + deltaR)) ** (1 / k) - S; + const realizedPrice = deltaR / deltaS; + const spotPrice = getPriceR({ R, V0, k }); + return Math.abs(realizedPrice - spotPrice) / spotPrice; +} + +/** + * Generate a random delta given three components: + * 1. Climbing sin + * 2. Oscilating sin + * 3. Random component + */ +export function getRandomDeltas({ + numSteps, + avgTxSize +}: { + numSteps: number; + avgTxSize: number; +}) { + const deltaR_t: number[] = []; + for (let i = 0; i < numSteps; i++) { + const rand = 1 - 2 * Math.random(); + const sin = Math.sin((1 / 20) * (i / numSteps)); + const ascending = Math.sin((Math.PI / 1) * (i / numSteps)); + const deltaR = 1e5 * rand + 1e5 * sin + 2e4 * ascending; + deltaR_t.push(deltaR); + } + + // Normalize random deltas with the average transaction size + const deltaR_avg = getAvg(deltaR_t); + return deltaR_t.map((deltaR: number) => (avgTxSize * deltaR) / deltaR_avg); +}