Better tx distribution - 52 weeks down-sampling

This commit is contained in:
dapplion 2019-08-04 20:24:52 +02:00
parent 2cc32d5764
commit ac451b34af
3 changed files with 169 additions and 65 deletions

View File

@ -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() {
</Grid>
</Grid>
{simulationActive && (
<Grid container spacing={3}>
<Grid item xs={12} sm={12} md={6} lg={8}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Simulation</Typography>
<HelpText
text={<span>Some context about this simulation</span>}
/>
</Box>
<Grid container spacing={3} className={classes.simulationContainer}>
{simulationActive ? (
<>
<Grid item xs={12} sm={12} md={6} lg={8}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Simulation</Typography>
<HelpText
text={<span>Some context about this simulation</span>}
/>
</Box>
<Box className={classes.boxChart}>
<PriceSimulationChart
priceTimeseries={priceTimeseries}
withdrawFeeTimeseries={withdrawFeeTimeseries}
p0={p0}
p1={p1}
/>
<Box className={classes.boxChart}>
<PriceSimulationChart
priceTimeseries={priceTimeseries}
withdrawFeeTimeseries={withdrawFeeTimeseries}
p0={p0}
p1={p1}
/>
</Box>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={4}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Results</Typography>
<HelpText
text={
<span>Explanation of what do this results mean</span>
}
/>
</Box>
<Box className={classes.box}>
<ResultParams resultFields={resultFields} />
</Box>
</Paper>
</Grid>
</>
) : (
<Grid item xs={12}>
<Paper className={classes.paper}>
<Box className={classes.boxPlaceholder}>
<Typography variant="h6">
Run a simulation to see results
</Typography>
</Box>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={4}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Results</Typography>
<HelpText
text={<span>Explanation of what do this results mean</span>}
/>
</Box>
<Box className={classes.box}>
<ResultParams resultFields={resultFields} />
</Box>
</Paper>
</Grid>
</Grid>
)}
)}
</Grid>
</Container>
</>
);

View File

@ -55,7 +55,6 @@ function PriceSimulationChart({
function ReferenceLabel(props: any) {
const { textAnchor, viewBox, text } = props;
console.log(props);
return (
<text
x={viewBox.x + 10}

View File

@ -53,6 +53,8 @@ export function getSlippage({
return Math.abs(realizedPrice - spotPrice) / spotPrice;
}
// Price walk utils
/**
* Generate a random delta given three components:
* 1. Climbing sin
@ -79,3 +81,62 @@ export function getRandomDeltas({
const deltaR_avg = getAvg(deltaR_t);
return deltaR_t.map((deltaR: number) => (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;
}