Implement zargham math review and vesting period floor price
This commit is contained in:
parent
28215fef56
commit
9bc519be2d
79
src/App.tsx
79
src/App.tsx
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
95
src/math.ts
95
src/math.ts
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue