From 5a6ddd4a35dead6608518d81f35446590663a5ac Mon Sep 17 00:00:00 2001 From: Adam Fraser Date: Tue, 24 Sep 2024 13:22:59 -0400 Subject: [PATCH] Return values from redis without division, perform division in javascript --- .../caches/orderbook-mid-prices-cache.test.ts | 38 +++++++++++++++++-- .../src/caches/orderbook-mid-prices-cache.ts | 30 ++++++++++++--- .../src/scripts/get_market_median_price.lua | 7 ++-- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/indexer/packages/redis/__tests__/caches/orderbook-mid-prices-cache.test.ts b/indexer/packages/redis/__tests__/caches/orderbook-mid-prices-cache.test.ts index 70ed134e67..5dfd662f68 100644 --- a/indexer/packages/redis/__tests__/caches/orderbook-mid-prices-cache.test.ts +++ b/indexer/packages/redis/__tests__/caches/orderbook-mid-prices-cache.test.ts @@ -13,10 +13,6 @@ describe('orderbook-mid-prices-cache', () => { await deleteAllAsync(client); }); - afterEach(async () => { - await deleteAllAsync(client); - }); - describe('setPrice', () => { it('sets a price for a ticker', async () => { await setPrice(client, ticker, '50000'); @@ -102,5 +98,39 @@ describe('orderbook-mid-prices-cache', () => { jest.useRealTimers(); }); + + it('returns the correct median price for small numbers with even number of prices', async () => { + await Promise.all([ + setPrice(client, ticker, '0.00000000002345'), + setPrice(client, ticker, '0.00000000002346'), + ]); + + const midPrice1 = await getMedianPrice(client, ticker); + expect(midPrice1).toEqual('0.000000000023455'); + }); + + it('returns the correct median price for small numbers with odd number of prices', async () => { + await Promise.all([ + setPrice(client, ticker, '0.00000000001'), + setPrice(client, ticker, '0.00000000002'), + setPrice(client, ticker, '0.00000000003'), + setPrice(client, ticker, '0.00000000004'), + setPrice(client, ticker, '0.00000000005'), + ]); + + const midPrice1 = await getMedianPrice(client, ticker); + expect(midPrice1).toEqual('0.00000000003'); + + await deleteAllAsync(client); + + await Promise.all([ + setPrice(client, ticker, '0.00000847007'), + setPrice(client, ticker, '0.00000847006'), + setPrice(client, ticker, '0.00000847008'), + ]); + + const midPrice2 = await getMedianPrice(client, ticker); + expect(midPrice2).toEqual('0.00000847007'); + }); }); }); diff --git a/indexer/packages/redis/src/caches/orderbook-mid-prices-cache.ts b/indexer/packages/redis/src/caches/orderbook-mid-prices-cache.ts index f2857b70e9..ece95a3ca2 100644 --- a/indexer/packages/redis/src/caches/orderbook-mid-prices-cache.ts +++ b/indexer/packages/redis/src/caches/orderbook-mid-prices-cache.ts @@ -1,3 +1,4 @@ +import Big from 'big.js'; import { Callback, RedisClient } from 'redis'; import { @@ -69,7 +70,9 @@ export async function setPrice( /** * Retrieves the median price for a given ticker from the cache. - * Uses a Lua script to calculate the median price from the sorted set in Redis. + * Uses a Lua script to fetch either the middle element (for odd number of prices) + * or the two middle elements (for even number of prices) from a sorted set in Redis. + * If two middle elements are returned, their average is calculated in JavaScript. * @param client The Redis client * @param ticker The ticker symbol * @returns A promise that resolves with the median price as a string, or null if not found @@ -77,13 +80,13 @@ export async function setPrice( export async function getMedianPrice(client: RedisClient, ticker: string): Promise { let evalAsync: ( marketCacheKey: string, - ) => Promise = ( + ) => Promise = ( marketCacheKey, ) => { return new Promise((resolve, reject) => { - const callback: Callback = ( + const callback: Callback = ( err: Error | null, - results: string, + results: string[], ) => { if (err) { return reject(err); @@ -101,7 +104,24 @@ export async function getMedianPrice(client: RedisClient, ticker: string): Promi }; evalAsync = evalAsync.bind(client); - return evalAsync( + const prices = await evalAsync( getOrderbookMidPriceCacheKey(ticker), ); + + if (!prices || prices.length === 0) { + return null; + } + + if (prices.length === 1) { + return Big(prices[0]).toFixed(); + } + + if (prices.length === 2) { + const [price1, price2] = prices.map((price) => { + return Big(price); + }); + return price1.plus(price2).div(2).toFixed(); + } + + return null; } diff --git a/indexer/packages/redis/src/scripts/get_market_median_price.lua b/indexer/packages/redis/src/scripts/get_market_median_price.lua index 281da9bed8..a318296f20 100644 --- a/indexer/packages/redis/src/scripts/get_market_median_price.lua +++ b/indexer/packages/redis/src/scripts/get_market_median_price.lua @@ -14,10 +14,9 @@ local middle = math.floor(#prices / 2) -- Calculate median if #prices % 2 == 0 then - -- If even, return the average of the two middle elements - local median = (tonumber(prices[middle]) + tonumber(prices[middle + 1])) / 2 - return tostring(median) + -- If even, return both prices, division will be handled in Javascript + return {prices[middle], prices[middle + 1]} else -- If odd, return the middle element - return prices[middle + 1] + return {prices[middle + 1]} end