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 {
getInitialParams,
getPriceR,
getMinPrice,
getS,
vest_tokens,
getMinR,
getSlippage,
getTxDistribution,
getDeltaR_priceGrowth,
rv_U
rv_U,
getMedian,
getSum
} from "./math";
import { throttle } from "lodash";
// General styles
@ -199,7 +205,7 @@ export default function App() {
const {
k, // Invariant power kappa (.)
R0, // Initial reserve (DAI)
// S0, // initial supply of tokens (token)
S0, // initial supply of tokens (token)
V0 // invariant coef
} = getInitialParams({
d0,
@ -210,6 +216,7 @@ export default function App() {
const [priceTimeseries, setPriceTimeseries] = useState([0]);
const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]);
const [floorpriceTimeseries, setFloorpriceTimeseries] = useState([0]);
const [totalReserve, setTotalReserve] = useState(R0);
const [withdrawCount, setWithdrawCount] = useState(0);
const [avgSlippage, setAvgSlippage] = useState(0);
@ -248,48 +255,97 @@ export default function App() {
async function simulateRandomDelta() {
const R_t: number[] = [R0];
const S_t: number[] = [S0];
const p_t: number[] = [getPriceR({ R: R0, V0, k })];
const wFee_t: number[] = [0];
const slippage_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
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
setSimulationRunning(true);
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 txsWeek = rv_U(5, 2 * t + 5);
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 txs = getTxDistribution({ sum: deltaR, num: txsWeek });
const txs = getTxDistribution({
sum: deltaR,
num: txsWeek,
spread: tx_spread
});
// Compute slippage
const slippage = getAvg(
txs.map(txR => getSlippage({ R, deltaR: txR, V0, k }))
const slippage_txs = txs.map(txR =>
getSlippage({ R, deltaR: txR, V0, k })
);
const slippage = getMedian(slippage_txs);
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;
const wFees = -wFee * getSum(txsWithdraw);
// txsWithdraw.reduce((t, c) => t + c, 0);
// 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);
p_t.push(getPriceR({ R: R_next, V0, k }));
slippage_t.push(slippage);
avgTxSize_t.push(_avgTxSize);
wFee_t.push(getLast(wFee_t) + wFees);
H_t.push(H_next);
floorprice_t.push(floorprice_next);
setWithdrawCount(c => c + txsWithdraw.length);
// Stop the simulation if it's no longer active
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);
setWithdrawFeeTimeseries(wFee_t);
setFloorpriceTimeseries(floorprice_t);
setAvgSlippage(getAvg(slippage_t));
setAvgTxSize(getAvg(avgTxSize_t));
setTotalReserve(getLast(R_t));
@ -465,6 +521,7 @@ export default function App() {
<PriceSimulationChart
priceTimeseries={priceTimeseries}
withdrawFeeTimeseries={withdrawFeeTimeseries}
floorpriceTimeseries={floorpriceTimeseries}
p0={p0}
p1={p1}
/>

View File

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

View File

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

View File

@ -23,6 +23,51 @@ export function getInitialParams({
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`
*/
@ -75,17 +120,47 @@ export function getDeltaR_priceGrowth({
* 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 }) {
export function getTxDistribution({
sum,
num,
spread
}: {
sum: number;
num: number;
spread: number;
}) {
const mean = sum / num;
const off = mean * 4;
const off = mean * spread;
const x: number[] = [];
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;
}
// 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
@ -111,3 +186,15 @@ function randn_bm(min: number, max: number) {
num += min; // offset to min
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);
}