From ac451b34af5ae1cac3a5acc2e9e1eb2f22a65c72 Mon Sep 17 00:00:00 2001 From: dapplion Date: Sun, 4 Aug 2019 20:24:52 +0200 Subject: [PATCH] Better tx distribution - 52 weeks down-sampling --- src/App.tsx | 172 ++++++++++++++++++++++------------- src/PriceSimulationChart.tsx | 1 - src/math.ts | 61 +++++++++++++ 3 files changed, 169 insertions(+), 65 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 759d2ae..1127f9e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,7 +21,10 @@ import { getInitialParams, getPriceR, getRandomDeltas, - getSlippage + getSlippage, + getTxDistribution, + getDeltaR_priceGrowth, + rv_U } from "./math"; import { throttle } from "lodash"; // General styles @@ -42,6 +45,9 @@ const useStyles = makeStyles((theme: Theme) => }, paddingBottom: theme.spacing(9) }, + simulationContainer: { + minHeight: "442px" + }, paper: { width: "100%", height: "100%", @@ -72,6 +78,15 @@ const useStyles = makeStyles((theme: Theme) => paddingRight: "5px", paddingLeft: "5px" }, + boxPlaceholder: { + padding: theme.spacing(3, 3), + display: "flex", + height: "100%", + alignItems: "center", + justifyContent: "center", + color: theme.palette.text.secondary, + opacity: 0.4 + }, header: { backgroundColor: "#0b1216", color: "#f8f8f8", @@ -102,7 +117,7 @@ export default function App() { * to re-render too often */ const setCurveParamsThrottle = useMemo( - () => throttle(setCurveParams, 1000), + () => throttle(setCurveParams, 250), [] ); @@ -124,11 +139,15 @@ export default function App() { const [totalReserve, setTotalReserve] = useState(R0); const [withdrawCount, setWithdrawCount] = useState(0); const [avgSlippage, setAvgSlippage] = useState(0); - const [avgTxSize] = useState(10000); + const [avgTxSize, setAvgTxSize] = useState(0); // Simulation state variables const [simulationActive, setSimulationActive] = useState(false); const [simulationRunning, setSimulationRunning] = useState(false); + useEffect(() => { + setSimulationActive(false); + }, [curveParams]); + // #### TEST: Immediate simulation async function startSimulation() { @@ -157,47 +176,52 @@ export default function App() { const p_t: number[] = [getPriceR({ R: R0, V0, k })]; const wFee_t: number[] = [0]; const slippage_t: number[] = []; + const avgTxSize_t: number[] = []; // Random walk - const numSteps = 100; - const updateEveryNth = 1; - - // Compute the random deltas - const deltaR_t = getRandomDeltas({ numSteps, avgTxSize }); + const numSteps = 52; + // numSteps = 52 take 8ms to run setSimulationRunning(true); - for (let i = 0; i < numSteps; i++) { - const deltaR = deltaR_t[i]; + for (let t = 0; t < numSteps; t++) { + const txsWeek = Math.ceil(t < 5 ? rv_U(0, 5) : rv_U(5, 2 * t)); + const priceGrowth = rv_U(0.99, 1.03); const R = getLast(R_t); + const deltaR = getDeltaR_priceGrowth({ R, k, priceGrowth }); const R_next = R + deltaR; + const txs = getTxDistribution({ sum: deltaR, num: txsWeek }); + // Compute slippage + const slippage = getAvg( + txs.map(txR => getSlippage({ R, deltaR: txR, V0, k })) + ); + const txsWithdraw = txs.filter(tx => tx < 0); + const wFees = -wFee * txsWithdraw.reduce((t, c) => t + c, 0); + const _avgTxSize = + txs.reduce((t, c) => t + Math.abs(c), 0) / txs.length; + R_t.push(R_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); - setWithdrawCount(c => c + 1); - } else { - wFee_t.push(getLast(wFee_t)); - } + slippage_t.push(slippage); + avgTxSize_t.push(_avgTxSize); + wFee_t.push(getLast(wFee_t) + wFees); + setWithdrawCount(c => c + txsWithdraw.length); // Stop the simulation if it's no longer active if (!simulationActive || !canContinueSimulation) break; - - if (i % updateEveryNth === 0) { - setPriceTimeseries(p_t); - setWithdrawFeeTimeseries(wFee_t); - setAvgSlippage(getAvg(slippage_t)); - setTotalReserve(R_next); - - // Make this run non-UI blocking - await pause(5); - } } + + setPriceTimeseries(p_t); + setWithdrawFeeTimeseries(wFee_t); + setAvgSlippage(getAvg(slippage_t)); + setAvgTxSize(getAvg(avgTxSize_t)); + setTotalReserve(getLast(R_t)); + + // Make this run non-UI blocking + await pause(5); + setSimulationRunning(false); } @@ -210,7 +234,9 @@ export default function App() { const resultFields = [ { - label: `Average slippage (avg tx size ${avgTxSize} DAI)`, + label: `Average slippage (avg tx size ${Math.round( + avgTxSize + ).toLocaleString()} DAI)`, value: +(100 * avgSlippage).toFixed(3) + "%" }, { @@ -219,6 +245,10 @@ export default function App() { (+getLast(withdrawFeeTimeseries).toPrecision(3)).toLocaleString() + " DAI" }, + { + label: `Capital collected from initial hatch`, + value: Math.round(d0 * theta).toLocaleString() + " DAI" + }, { label: `Total reserve`, value: (+totalReserve.toPrecision(3)).toLocaleString() + " DAI" @@ -300,44 +330,58 @@ export default function App() { - {simulationActive && ( - - - - - Simulation - Some context about this simulation} - /> - + + {simulationActive ? ( + <> + + + + Simulation + Some context about this simulation} + /> + - - + + + + + + + + + + Results + Explanation of what do this results mean + } + /> + + + + + + + + + ) : ( + + + + + Run a simulation to see results + - - - - - Results - Explanation of what do this results mean} - /> - - - - - - - - - )} + )} + ); diff --git a/src/PriceSimulationChart.tsx b/src/PriceSimulationChart.tsx index b558c9b..3a35bd5 100644 --- a/src/PriceSimulationChart.tsx +++ b/src/PriceSimulationChart.tsx @@ -55,7 +55,6 @@ function PriceSimulationChart({ function ReferenceLabel(props: any) { const { textAnchor, viewBox, text } = props; - console.log(props); return ( (avgTxSize * deltaR) / deltaR_avg); } + +/** + * Get deltaR for a given price growth factor + */ +export function getDeltaR_priceGrowth({ + R, + k, + priceGrowth +}: { + R: number; + k: number; + priceGrowth: number; +}) { + return -R + (priceGrowth * R ** (1 - 1 / k)) ** (k / (-1 + k)); +} + +/** + * Computes a tx distribution using a normal distribution, + * Given a sum of tx value and a number of transactions + * + * Demo: https://codepen.io/anon/pen/mNqJjv?editors=0010#0 + * Very quick: < 10ms for 10000 txs + */ +export function getTxDistribution({ sum, num }: { sum: number; num: number }) { + const mean = sum / num; + const off = mean * 4; + const x: number[] = []; + for (let i = 0; i < num; i++) { + x[i] = randn_bm(mean - off, mean + off); + } + return x; +} + +// Minor utils + +/** + * Random variable uniformly distributed + */ +export function rv_U(min: number, max: number) { + return Math.random() * (max - min) + min; +} + +/** + * Standard Normal variate using Box-Muller transform. + * by https://stackoverflow.com/questions/25582882/javascript-math-random-normal-distribution-gaussian-bell-curve/36481059#36481059 + */ +function randn_bm(min: number, max: number) { + var u = 0, + v = 0; + while (u === 0) u = Math.random(); //Converting [0,1) to (0,1) + while (v === 0) v = Math.random(); + let num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); + + num = num / 10.0 + 0.5; // Translate to 0 -> 1 + if (num > 1 || num < 0) num = randn_bm(min, max); // resample between 0 and 1 if out of range + num *= max - min; // Stretch to fill range + num += min; // offset to min + return num; +}