From 54f05746b5bd1019954fdab948c6ad5e0843ba49 Mon Sep 17 00:00:00 2001 From: Filip Hallqvist Date: Wed, 15 Oct 2025 16:25:12 +0200 Subject: [PATCH] feat: make price formatting feed-exponent aware --- .../src/components/LivePrices/index.tsx | 55 ++++++++++++++---- .../src/components/PriceFeed/Chart/chart.tsx | 13 ++++- .../src/hooks/use-live-price-data.tsx | 1 + .../insights/src/hooks/use-price-formatter.ts | 56 +++++++++---------- 4 files changed, 85 insertions(+), 40 deletions(-) diff --git a/apps/insights/src/components/LivePrices/index.tsx b/apps/insights/src/components/LivePrices/index.tsx index a590679047..fc5248172c 100644 --- a/apps/insights/src/components/LivePrices/index.tsx +++ b/apps/insights/src/components/LivePrices/index.tsx @@ -43,9 +43,17 @@ const LiveAggregatePrice = ({ if (current === undefined) { return ; } else if (current.status === PriceStatus.Trading) { - return ; + return ( + + ); } else { - return ; + return ( + + ); } }; @@ -58,20 +66,28 @@ const LiveComponentPrice = ({ publisherKey: string; cluster: Cluster; }) => { - const { prev, current } = useLivePriceComponent( + const { prev, current, exponent } = useLivePriceComponent( cluster, feedKey, publisherKey, ); - return ; + return ( + + ); }; const Price = ({ prev, current, + exponent, }: { prev?: number | undefined; current?: number | undefined; + exponent?: number | undefined; }) => current === undefined ? ( @@ -80,7 +96,7 @@ const Price = ({ className={styles.price} data-direction={prev ? getChangeDirection(prev, current) : "flat"} > - + ); @@ -114,6 +130,7 @@ const LiveAggregateConfidence = ({ ? current.confidence : current.previousConfidence) } + exponent={current?.exponent} /> ); }; @@ -128,24 +145,42 @@ const LiveComponentConfidence = ({ cluster: Cluster; }) => { const { current } = useLivePriceComponent(cluster, feedKey, publisherKey); - return ; + const { current: priceData } = useLivePriceData(cluster, feedKey); + return ( + + ); }; -const Confidence = ({ confidence }: { confidence?: number | undefined }) => ( +const Confidence = ({ + confidence, + exponent, +}: { + confidence?: number | undefined; + exponent?: number | undefined; +}) => ( {confidence === undefined ? ( ) : ( - + )} ); -const FormattedPriceValue = ({ n }: { n: number }) => { - const formatter = usePriceFormatter(); +const FormattedPriceValue = ({ + n, + exponent, +}: { + n: number; + exponent?: number | undefined; +}) => { + const formatter = usePriceFormatter(exponent); return useMemo(() => formatter.format(n), [n, formatter]); }; diff --git a/apps/insights/src/components/PriceFeed/Chart/chart.tsx b/apps/insights/src/components/PriceFeed/Chart/chart.tsx index e807c99d0a..d0fba58d89 100644 --- a/apps/insights/src/components/PriceFeed/Chart/chart.tsx +++ b/apps/insights/src/components/PriceFeed/Chart/chart.tsx @@ -69,7 +69,6 @@ const useChartElem = (symbol: string, feedId: string) => { const chartContainerRef = useRef(null); const chartRef = useRef(undefined); const isBackfilling = useRef(false); - const priceFormatter = usePriceFormatter(); const abortControllerRef = useRef(undefined); // Lightweight charts has [a // bug](https://github.com/tradingview/lightweight-charts/issues/1649) where @@ -79,6 +78,7 @@ const useChartElem = (symbol: string, feedId: string) => { const whitespaceData = useRef>(new Set()); const { current: livePriceData } = useLivePriceData(Cluster.Pythnet, feedId); + const priceFormatter = usePriceFormatter(livePriceData?.exponent); const didResetVisibleRange = useRef(false); const didLoadInitialData = useRef(false); @@ -370,6 +370,17 @@ const useChartElem = (symbol: string, feedId: string) => { }); }, [quickSelectWindow, resolution, fetchHistoricalData]); + // Update the chart's price formatter when the exponent becomes available + useEffect(() => { + if (chartRef.current && livePriceData?.exponent !== undefined) { + chartRef.current.chart.applyOptions({ + localization: { + priceFormatter: priceFormatter.format, + }, + }); + } + }, [livePriceData?.exponent, priceFormatter]); + return { chartRef, chartContainerRef }; }; diff --git a/apps/insights/src/hooks/use-live-price-data.tsx b/apps/insights/src/hooks/use-live-price-data.tsx index 9a57a5262c..2ebbb2169f 100644 --- a/apps/insights/src/hooks/use-live-price-data.tsx +++ b/apps/insights/src/hooks/use-live-price-data.tsx @@ -72,6 +72,7 @@ export const useLivePriceComponent = ( prev: prev?.priceComponents.find((component) => component.publisher.equals(publisherKey), ), + exponent: current?.exponent, }; }; diff --git a/apps/insights/src/hooks/use-price-formatter.ts b/apps/insights/src/hooks/use-price-formatter.ts index f51bbdd446..e7c8054bec 100644 --- a/apps/insights/src/hooks/use-price-formatter.ts +++ b/apps/insights/src/hooks/use-price-formatter.ts @@ -1,40 +1,38 @@ import { useCallback, useMemo } from "react"; import { useNumberFormatter } from "react-aria"; -export const usePriceFormatter = () => { +export const usePriceFormatter = (exponent?: number) => { + // Calculate the number of decimal places based on the exponent + // The exponent represents the power of 10, so -8 means 8 decimal places + const decimals = exponent === undefined ? undefined : Math.abs(exponent); + const bigNumberFormatter = useNumberFormatter({ maximumFractionDigits: 2 }); const smallNumberFormatter = useNumberFormatter({ maximumSignificantDigits: 6, }); + const exponentBasedFormatter = useNumberFormatter({ + minimumFractionDigits: decimals, + maximumFractionDigits: decimals, + }); + const format = useCallback( - (n: number) => - n >= 1000 - ? bigNumberFormatter.format(n) - : formatToSubscriptNumber(smallNumberFormatter.format(n)), - [bigNumberFormatter, smallNumberFormatter], + (n: number) => { + // If we have an exponent, use exponent-based formatting + if (decimals !== undefined) { + return exponentBasedFormatter.format(n); + } + // Otherwise, fall back to the old behavior + if (n >= 1000) { + return bigNumberFormatter.format(n); + } + return smallNumberFormatter.format(n); + }, + [ + bigNumberFormatter, + smallNumberFormatter, + exponentBasedFormatter, + decimals, + ], ); return useMemo(() => ({ format }), [format]); }; - -const formatToSubscriptNumber = (numString: string) => { - const parts = numString.split("."); - - const [integerPart, decimalPart] = parts; - if (integerPart && decimalPart) { - const zerosCount = - decimalPart.length - decimalPart.replace(/^0+/, "").length; - - return zerosCount < 5 - ? numString - : integerPart + - "." + - "0" + - (zerosCount > 9 - ? String.fromCodePoint(0x20_80 + Math.floor(zerosCount / 10)) - : "") + - String.fromCodePoint(0x20_80 + (zerosCount % 10)) + - decimalPart.replace(/^0+/, ""); - } else { - return numString; - } -};