From 5deb0649b739425fe490283d343729fb930d4cd1 Mon Sep 17 00:00:00 2001 From: Maximilian Schneider Date: Tue, 18 Jun 2024 12:08:29 +0100 Subject: [PATCH] use least squares regression for apr calculation --- hooks/useStakeRates.ts | 58 ++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/hooks/useStakeRates.ts b/hooks/useStakeRates.ts index d990cc6..77cc1d5 100644 --- a/hooks/useStakeRates.ts +++ b/hooks/useStakeRates.ts @@ -2,38 +2,33 @@ import { useQuery } from '@tanstack/react-query' import { OHLCVPairItem, fetchOHLCPair } from 'apis/birdeye/helpers' import { SOL_MINT, STAKEABLE_TOKENS_DATA, USDC_MINT } from 'utils/constants' -const avgOHCL = (i: OHLCVPairItem) => (i.c + i.o) * 0.5 +const avgOpenClose = (i: OHLCVPairItem) => (i.c + i.o) * .5; +const sum = (x: number, y: number) => x + y; +const ANNUAL_SECONDS = 60 * 60 * 24 * 365; const calculateRate = (ohlcvs: OHLCVPairItem[]) => { - if (ohlcvs && ohlcvs?.length > 6) { - const startSamples = [ - avgOHCL(ohlcvs[0]), - avgOHCL(ohlcvs[1]), - avgOHCL(ohlcvs[2]), - avgOHCL(ohlcvs[3]), - avgOHCL(ohlcvs[4]), - avgOHCL(ohlcvs[5]), - ] - // 67th percentile of first 6 days - const start = startSamples.sort()[startSamples.length - 3] - const endSamples = [ - avgOHCL(ohlcvs[ohlcvs.length - 1]), - avgOHCL(ohlcvs[ohlcvs.length - 2]), - avgOHCL(ohlcvs[ohlcvs.length - 3]), - avgOHCL(ohlcvs[ohlcvs.length - 4]), - avgOHCL(ohlcvs[ohlcvs.length - 5]), - avgOHCL(ohlcvs[ohlcvs.length - 6]), - ] - // 67th percentile of last 6 days - const end = endSamples.sort()[endSamples.length - 3] - // percentiles cut off 3 samples at the start and 2 at the end - const annualized = 365 / (ohlcvs.length - 5) - return { - rate: (annualized * (end - start)) / start, - start: [start, ...startSamples], - end: [end, ...endSamples], - } + + if (ohlcvs && ohlcvs?.length > 30) { + + // basic least squares regression: + // https://www.ncl.ac.uk/webtemplate/ask-assets/external/maths-resources/statistics/regression-and-correlation/simple-linear-regression.html + const xs = ohlcvs.map(o => o.unixTime); + const ys = ohlcvs.map(avgOpenClose); + const x_sum = xs.reduce(sum, 0); + const y_sum = ys.reduce(sum, 0); + const x_mean = x_sum / xs.length; + const y_mean = y_sum / ys.length; + const S_xy = xs.map((xi, i) => (xi - x_mean) * (ys[i] - y_mean)).reduce(sum, 0); + const S_xx = xs.map((xi) => (xi - x_mean) ** 2).reduce(sum, 0); + const b = S_xy / S_xx; + const a = y_mean - b * x_mean; + + const start = a + b * xs[0]; + const end = a + b * (xs[0] + ANNUAL_SECONDS); + return { rate: (end - start)/start, start, end, a, b, S_xx, S_xy}; + } else { + return { rate: 0.082 }; // fixed rate to avoid outliers } } @@ -41,10 +36,11 @@ const fetchRates = async () => { try { const promises = STAKEABLE_TOKENS_DATA.filter( (token) => token.mint_address !== USDC_MINT, - ).map((t) => { + ).map(async (t) => { const isUsdcBorrow = t.name === 'JLP' || t.name === 'USDC' const quoteMint = isUsdcBorrow ? USDC_MINT : SOL_MINT - return fetchOHLCPair(t.mint_address, quoteMint, '90') + const dailyCandles = await fetchOHLCPair(t.mint_address, quoteMint, '90'); + return dailyCandles; }) const [ jlpPrices,