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 {
|
||||
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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
95
src/math.ts
95
src/math.ts
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue