Implement zargham math review and vesting period floor price

This commit is contained in:
dapplion 2019-08-10 16:35:12 +02:00
parent 28215fef56
commit 9bc519be2d
4 changed files with 188 additions and 24 deletions

View File

@ -20,10 +20,16 @@ import { getLast, getAvg, pause } from "./utils";
import { import {
getInitialParams, getInitialParams,
getPriceR, getPriceR,
getMinPrice,
getS,
vest_tokens,
getMinR,
getSlippage, getSlippage,
getTxDistribution, getTxDistribution,
getDeltaR_priceGrowth, getDeltaR_priceGrowth,
rv_U rv_U,
getMedian,
getSum
} from "./math"; } from "./math";
import { throttle } from "lodash"; import { throttle } from "lodash";
// General styles // General styles
@ -199,7 +205,7 @@ export default function App() {
const { const {
k, // Invariant power kappa (.) k, // Invariant power kappa (.)
R0, // Initial reserve (DAI) R0, // Initial reserve (DAI)
// S0, // initial supply of tokens (token) S0, // initial supply of tokens (token)
V0 // invariant coef V0 // invariant coef
} = getInitialParams({ } = getInitialParams({
d0, d0,
@ -210,6 +216,7 @@ export default function App() {
const [priceTimeseries, setPriceTimeseries] = useState([0]); const [priceTimeseries, setPriceTimeseries] = useState([0]);
const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]); const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]);
const [floorpriceTimeseries, setFloorpriceTimeseries] = useState([0]);
const [totalReserve, setTotalReserve] = useState(R0); const [totalReserve, setTotalReserve] = useState(R0);
const [withdrawCount, setWithdrawCount] = useState(0); const [withdrawCount, setWithdrawCount] = useState(0);
const [avgSlippage, setAvgSlippage] = useState(0); const [avgSlippage, setAvgSlippage] = useState(0);
@ -248,48 +255,97 @@ export default function App() {
async function simulateRandomDelta() { async function simulateRandomDelta() {
const R_t: number[] = [R0]; const R_t: number[] = [R0];
const S_t: number[] = [S0];
const p_t: number[] = [getPriceR({ R: R0, V0, k })]; const p_t: number[] = [getPriceR({ R: R0, V0, k })];
const wFee_t: number[] = [0]; const wFee_t: number[] = [0];
const slippage_t: number[] = []; const slippage_t: number[] = [];
const avgTxSize_t: number[] = []; const avgTxSize_t: number[] = [];
// hatchers tokens = S0[section added by Z]
const H_t: number[] = [S0]; // total hatcher tokens not vested
const floorprice_t: number[] = []; // initially the price is the floor as all tokens are hatcher tokens
// Random walk // Random walk
const numSteps = 52; const numSteps = 52;
const u_min = 0.97;
const u_max = 1.04;
const tx_spread = 10;
// vesting(should this be exposed in the app ?)
const cliff = 8; // weeks before vesting starts ~2 months
const halflife = 52; // 26 weeks, half life is ~6 months
// percentage of the hatch tokens which vest per week(since that is our timescale in the sim)
// numSteps = 52 take 8ms to run // numSteps = 52 take 8ms to run
setSimulationRunning(true); setSimulationRunning(true);
for (let t = 0; t < numSteps; t++) { for (let t = 0; t < numSteps; t++) {
const txsWeek = Math.ceil(t < 5 ? rv_U(0, 5) : rv_U(5, 2 * t)); const txsWeek = rv_U(5, 2 * t + 5);
const priceGrowth = rv_U(0.99, 1.03);
const R = getLast(R_t); const R = getLast(R_t);
const deltaR = getDeltaR_priceGrowth({ R, k, priceGrowth }); const S = getLast(S_t);
const H = getLast(H_t);
// enforce the effects of the unvested tokens not being burnable
let u_lower;
if (H === S) {
u_lower = 1;
} else {
const R_ratio = getMinR({ S, H, V0, k }) / R;
u_lower = Math.max(1 - R_ratio, u_min);
}
const priceGrowth = rv_U(u_lower, u_max);
const deltaR = getDeltaR_priceGrowth({ R, k, priceGrowth });
const R_next = R + deltaR; const R_next = R + deltaR;
const txs = getTxDistribution({ sum: deltaR, num: txsWeek }); const txs = getTxDistribution({
sum: deltaR,
num: txsWeek,
spread: tx_spread
});
// Compute slippage // Compute slippage
const slippage = getAvg( const slippage_txs = txs.map(txR =>
txs.map(txR => getSlippage({ R, deltaR: txR, V0, k })) getSlippage({ R, deltaR: txR, V0, k })
); );
const slippage = getMedian(slippage_txs);
const txsWithdraw = txs.filter(tx => tx < 0); const txsWithdraw = txs.filter(tx => tx < 0);
const wFees = -wFee * txsWithdraw.reduce((t, c) => t + c, 0); const wFees = -wFee * getSum(txsWithdraw);
const _avgTxSize = // txsWithdraw.reduce((t, c) => t + c, 0);
txs.reduce((t, c) => t + Math.abs(c), 0) / txs.length;
// Vest
const delta_H = vest_tokens({ week: t, H, halflife, cliff });
const H_next = H - delta_H;
// find floor price
const S_next = getS({ R, V0, k });
const floorprice_next = getMinPrice({
S: S_next,
H: S0 - H_next,
V0,
k
});
const _avgTxSize = getMedian(txsWithdraw);
R_t.push(R_next); R_t.push(R_next);
p_t.push(getPriceR({ R: R_next, V0, k })); p_t.push(getPriceR({ R: R_next, V0, k }));
slippage_t.push(slippage); slippage_t.push(slippage);
avgTxSize_t.push(_avgTxSize); avgTxSize_t.push(_avgTxSize);
wFee_t.push(getLast(wFee_t) + wFees); wFee_t.push(getLast(wFee_t) + wFees);
H_t.push(H_next);
floorprice_t.push(floorprice_next);
setWithdrawCount(c => c + txsWithdraw.length); setWithdrawCount(c => c + txsWithdraw.length);
// Stop the simulation if it's no longer active // Stop the simulation if it's no longer active
if (!simulationActive || !canContinueSimulation) break; if (!simulationActive || !canContinueSimulation) break;
} }
// floorprice_t is missing one data point
floorprice_t[floorprice_t.length] = floorprice_t[floorprice_t.length - 1];
setPriceTimeseries(p_t); setPriceTimeseries(p_t);
setWithdrawFeeTimeseries(wFee_t); setWithdrawFeeTimeseries(wFee_t);
setFloorpriceTimeseries(floorprice_t);
setAvgSlippage(getAvg(slippage_t)); setAvgSlippage(getAvg(slippage_t));
setAvgTxSize(getAvg(avgTxSize_t)); setAvgTxSize(getAvg(avgTxSize_t));
setTotalReserve(getLast(R_t)); setTotalReserve(getLast(R_t));
@ -465,6 +521,7 @@ export default function App() {
<PriceSimulationChart <PriceSimulationChart
priceTimeseries={priceTimeseries} priceTimeseries={priceTimeseries}
withdrawFeeTimeseries={withdrawFeeTimeseries} withdrawFeeTimeseries={withdrawFeeTimeseries}
floorpriceTimeseries={floorpriceTimeseries}
p0={p0} p0={p0}
p1={p1} p1={p1}
/> />

View File

@ -17,6 +17,7 @@ import { linspace } from "./utils";
const keyHorizontal = "x"; const keyHorizontal = "x";
const keyVerticalLeft = "Price (DAI/token)"; const keyVerticalLeft = "Price (DAI/token)";
const keyVerticalRight = "Total exit tributes (DAI)"; const keyVerticalRight = "Total exit tributes (DAI)";
const keyVerticalLeft2 = "Floor price (DAI/token)";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -32,11 +33,13 @@ const useStyles = makeStyles((theme: Theme) =>
function PriceSimulationChart({ function PriceSimulationChart({
priceTimeseries, priceTimeseries,
withdrawFeeTimeseries, withdrawFeeTimeseries,
floorpriceTimeseries,
p0, p0,
p1 p1
}: { }: {
priceTimeseries: number[]; priceTimeseries: number[];
withdrawFeeTimeseries: number[]; withdrawFeeTimeseries: number[];
floorpriceTimeseries: number[];
p0: number; p0: number;
p1: number; p1: number;
}) { }) {
@ -51,6 +54,7 @@ function PriceSimulationChart({
data.push({ data.push({
[keyHorizontal]: t, [keyHorizontal]: t,
[keyVerticalLeft]: priceTimeseries[t] || 0, [keyVerticalLeft]: priceTimeseries[t] || 0,
[keyVerticalLeft2]: floorpriceTimeseries[t] || 0,
[keyVerticalRight]: withdrawFeeTimeseries[t] || 0 [keyVerticalRight]: withdrawFeeTimeseries[t] || 0
}); });
} }
@ -70,8 +74,8 @@ function PriceSimulationChart({
const { textAnchor, viewBox, text } = props; const { textAnchor, viewBox, text } = props;
return ( return (
<text <text
x={viewBox.x + 10} x={viewBox.x + 8}
y={viewBox.y - 10} y={viewBox.y + 17}
fill={theme.palette.text.secondary} fill={theme.palette.text.secondary}
textAnchor={textAnchor} textAnchor={textAnchor}
> >
@ -83,10 +87,12 @@ function PriceSimulationChart({
function CustomTooltip({ active, payload, label }: any) { function CustomTooltip({ active, payload, label }: any) {
if (active) { if (active) {
const price = payload[0].value; const price = payload[0].value;
const exit = payload[1].value; const floor = payload[1].value;
const exit = payload[2].value;
const weekNum = label; const weekNum = label;
const toolTipData: string[][] = [ const toolTipData: string[][] = [
["Price", price.toFixed(2), "DAI/tk"], ["Price", price.toFixed(2), "DAI/tk"],
["Floor", floor.toFixed(2), "DAI/tk"],
["Exit t.", formatter(exit), "DAI"], ["Exit t.", formatter(exit), "DAI"],
["Week", weekNum, ""] ["Week", weekNum, ""]
]; ];
@ -148,12 +154,10 @@ function PriceSimulationChart({
{/* Capital collected from withdraw fees - AXIS */} {/* Capital collected from withdraw fees - AXIS */}
<YAxis <YAxis
yAxisId="right" yAxisId="right"
// domain={[ domain={[0, +(2 * withdrawFeeTimeseries.slice(-1)[0]).toPrecision(1)]}
// Math.floor(Math.min(...withdrawFeeTimeseries)),
// Math.ceil(Math.max(...withdrawFeeTimeseries))
// ]}
orientation="right" orientation="right"
tick={{ fill: theme.palette.text.secondary }} tick={{ fill: theme.palette.text.secondary }}
tickFormatter={formatter}
stroke={theme.palette.text.secondary} stroke={theme.palette.text.secondary}
/> />
@ -166,7 +170,20 @@ function PriceSimulationChart({
dataKey={keyVerticalLeft} dataKey={keyVerticalLeft}
stroke={theme.palette.primary.main} stroke={theme.palette.primary.main}
fill={theme.palette.primary.main} fill={theme.palette.primary.main}
fillOpacity={0.3}
strokeWidth={2}
/> />
<Area
isAnimationActive={false}
yAxisId="left"
type="monotone"
dataKey={keyVerticalLeft2}
stroke={"#adcd2e"}
fill={"#adcd2e"}
fillOpacity={0.05}
strokeWidth={2}
/>
<ReferenceLine <ReferenceLine
y={p0} y={p0}
yAxisId="left" yAxisId="left"
@ -188,9 +205,10 @@ function PriceSimulationChart({
yAxisId="right" yAxisId="right"
type="monotone" type="monotone"
dataKey={keyVerticalRight} dataKey={keyVerticalRight}
stroke={theme.palette.secondary.dark} stroke={"#0085ff"}
fill={theme.palette.secondary.dark} fill={theme.palette.secondary.dark}
fillOpacity="0.8" fillOpacity={0.5}
strokeWidth={2}
/> />
{/* <ReferenceLine {/* <ReferenceLine

View File

@ -186,6 +186,8 @@ function SupplyVsDemandChart({
dataKey={keyVertical} dataKey={keyVertical}
stroke={theme.palette.primary.main} stroke={theme.palette.primary.main}
fill={theme.palette.primary.main} fill={theme.palette.primary.main}
fillOpacity={0.3}
strokeWidth={2}
/> />
<ReferenceLine <ReferenceLine
x={R0_round} x={R0_round}

View File

@ -23,6 +23,51 @@ export function getInitialParams({
return { k, R0, S0, V0 }; return { k, R0, S0, V0 };
} }
function getR({ S, V0, k }: { S: number; V0: number; k: number }) {
return S ** k / V0;
}
export function getS({ R, V0, k }: { R: number; V0: number; k: number }) {
return (V0 * R) ** (1 / k);
}
// compute the reserve if all that supply is burned
export function getMinR({
S,
H,
V0,
k
}: {
S: number;
H: number;
V0: number;
k: number;
}) {
return getR({ S: S - H, V0, k });
}
// compute the price if all that supply is burned
export function getMinPrice({
S,
H,
V0,
k
}: {
S: number;
H: number;
V0: number;
k: number;
}) {
if (S === H) {
const myR = getR({ S, V0, k });
const myP = getPriceR({ R: myR, V0, k }); // numerical precision make complex numbers just suppress it
return Math.abs(myP);
} else {
const minR = getMinR({ S, H, V0, k });
return getPriceR({ R: minR, V0, k });
}
}
/** /**
* Computes the price at a specific reserve `R` * Computes the price at a specific reserve `R`
*/ */
@ -75,17 +120,47 @@ export function getDeltaR_priceGrowth({
* Demo: https://codepen.io/anon/pen/mNqJjv?editors=0010#0 * Demo: https://codepen.io/anon/pen/mNqJjv?editors=0010#0
* Very quick: < 10ms for 10000 txs * Very quick: < 10ms for 10000 txs
*/ */
export function getTxDistribution({ sum, num }: { sum: number; num: number }) { export function getTxDistribution({
sum,
num,
spread
}: {
sum: number;
num: number;
spread: number;
}) {
const mean = sum / num; const mean = sum / num;
const off = mean * 4; const off = mean * spread;
const x: number[] = []; const x: number[] = [];
for (let i = 0; i < num; i++) { for (let i = 0; i < num; i++) {
x[i] = randn_bm(mean - off, mean + off); x.push(randn_bm(mean - off, mean + off));
} }
return x; return x;
} }
// Minor utils export function vest_tokens({
week,
H, // unvested_hatch_tokens
halflife,
cliff
}: {
week: number;
H: number;
halflife: number;
cliff: number;
}) {
// check cliff
if (week < cliff) {
return 0;
} else {
// rate of release given half - life
const vest_fraction = 0.5 ** (1 / halflife);
// number of tokens that vest in this week
return H * (1 - vest_fraction);
}
}
// Statistics utils
/** /**
* Random variable uniformly distributed * Random variable uniformly distributed
@ -111,3 +186,15 @@ function randn_bm(min: number, max: number) {
num += min; // offset to min num += min; // offset to min
return num; return num;
} }
// Array utils
export function getMedian(arr: number[]) {
const mid = Math.floor(arr.length / 2);
const nums = [...arr].sort((a, b) => a - b);
return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
}
export function getSum(arr: number[]) {
return arr.reduce((a, b) => a + b, 0);
}