From b495225ee99683551bcb7b58befa427001ab2499 Mon Sep 17 00:00:00 2001 From: aforaleka Date: Mon, 14 Oct 2024 13:38:04 -0400 Subject: [PATCH 1/3] change tv datafeed to hook for reset cache fix --- src/hooks/tradingView/useTradingView.ts | 60 +---- src/hooks/useTradingViewDatafeed.ts | 287 +++++++++++++++++++++++ src/lib/tradingView/dydxfeed/index.ts | 269 --------------------- src/views/charts/TradingView/TvChart.tsx | 50 +++- 4 files changed, 344 insertions(+), 322 deletions(-) create mode 100644 src/hooks/useTradingViewDatafeed.ts delete mode 100644 src/lib/tradingView/dydxfeed/index.ts diff --git a/src/hooks/tradingView/useTradingView.ts b/src/hooks/tradingView/useTradingView.ts index 4dadee7e8..77fde76b0 100644 --- a/src/hooks/tradingView/useTradingView.ts +++ b/src/hooks/tradingView/useTradingView.ts @@ -1,8 +1,8 @@ -import React, { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { Dispatch, SetStateAction, useCallback, useEffect, useMemo } from 'react'; -import BigNumber from 'bignumber.js'; import isEmpty from 'lodash/isEmpty'; import { + type IBasicDataFeed, LanguageCode, ResolutionString, TradingTerminalWidgetOptions, @@ -15,21 +15,16 @@ import { STRING_KEYS, SUPPORTED_LOCALE_BASE_TAGS } from '@/constants/localizatio import { tooltipStrings } from '@/constants/tooltips'; import type { TvWidget } from '@/constants/tvchart'; -import { store } from '@/state/_store'; import { getSelectedNetwork } from '@/state/appSelectors'; import { useAppDispatch, useAppSelector } from '@/state/appTypes'; import { getAppColorMode, getAppTheme } from '@/state/configsSelectors'; import { getSelectedLocale } from '@/state/localizationSelectors'; -import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors'; +import { getCurrentMarketId } from '@/state/perpetualsSelectors'; import { updateChartConfig } from '@/state/tradingView'; import { getTvChartConfig } from '@/state/tradingViewSelectors'; -import { getDydxDatafeed } from '@/lib/tradingView/dydxfeed'; import { getSavedResolution, getWidgetOptions, getWidgetOverrides } from '@/lib/tradingView/utils'; -import { orEmptyObj } from '@/lib/typeUtils'; -import { useDydxClient } from '../useDydxClient'; -import { useLocaleSeparators } from '../useLocaleSeparators'; import { useAllStatsigGateValues } from '../useStatsig'; import { useStringGetter } from '../useStringGetter'; import { useURLConfigs } from '../useURLConfigs'; @@ -50,6 +45,8 @@ export const useTradingView = ({ buySellMarksToggleOn, setBuySellMarksToggleOn, setIsChartReady, + tickSizeDecimals, + datafeed, }: { tvWidgetRef: React.MutableRefObject; orderLineToggleRef: React.MutableRefObject; @@ -62,14 +59,14 @@ export const useTradingView = ({ buySellMarksToggleOn: boolean; setBuySellMarksToggleOn: Dispatch>; setIsChartReady: React.Dispatch>; + tickSizeDecimals?: number; + datafeed: IBasicDataFeed; }) => { const stringGetter = useStringGetter(); const urlConfigs = useURLConfigs(); const featureFlags = useAllStatsigGateValues(); const dispatch = useAppDispatch(); - const { group, decimal } = useLocaleSeparators(); - const appTheme = useAppSelector(getAppTheme); const appColorMode = useAppSelector(getAppColorMode); @@ -77,8 +74,6 @@ export const useTradingView = ({ const selectedLocale = useAppSelector(getSelectedLocale); const selectedNetwork = useAppSelector(getSelectedNetwork); - const { getCandlesForDatafeed, getMarketTickSize } = useDydxClient(); - const savedTvChartConfig = useAppSelector(getTvChartConfig); const savedResolution = useMemo( @@ -86,17 +81,6 @@ export const useTradingView = ({ [savedTvChartConfig] ); - const [tickSizeDecimalsIndexer, setTickSizeDecimalsIndexer] = useState<{ - [marketId: string]: number | undefined; - }>({}); - const { tickSizeDecimals: tickSizeDecimalsAbacus } = orEmptyObj( - useAppSelector(getCurrentMarketConfig) - ); - const tickSizeDecimals = - (marketId - ? tickSizeDecimalsIndexer[marketId] ?? tickSizeDecimalsAbacus - : tickSizeDecimalsAbacus) ?? undefined; - const initializeToggle = useCallback( ({ toggleRef, @@ -124,42 +108,17 @@ export const useTradingView = ({ [] ); - useEffect(() => { - // we only need tick size from current market for the price scale settings - // if markets haven't been loaded via abacus, get the current market info from indexer - (async () => { - if (marketId && tickSizeDecimals === undefined) { - const marketTickSize = await getMarketTickSize(marketId); - setTickSizeDecimalsIndexer((prev) => ({ - ...prev, - [marketId]: BigNumber(marketTickSize).decimalPlaces() ?? undefined, - })); - } - })(); - }, [marketId, tickSizeDecimals, getMarketTickSize]); - const tradingViewLimitOrder = useTradingViewLimitOrder(marketId, tickSizeDecimals); useEffect(() => { - if (marketId && tickSizeDecimals !== undefined) { + if (marketId) { const widgetOptions = getWidgetOptions(); const widgetOverrides = getWidgetOverrides({ appTheme, appColorMode }); - const initialPriceScale = BigNumber(10) - .exponentiatedBy(tickSizeDecimals ?? 2) - .toNumber(); const options: TradingTerminalWidgetOptions = { ...widgetOptions, ...widgetOverrides, - datafeed: getDydxDatafeed( - store, - getCandlesForDatafeed, - initialPriceScale, - orderbookCandlesToggleOn, - { decimal, group }, - selectedLocale, - stringGetter - ), + datafeed, interval: (savedResolution ?? DEFAULT_RESOLUTION) as ResolutionString, locale: SUPPORTED_LOCALE_BASE_TAGS[selectedLocale] as LanguageCode, symbol: marketId, @@ -249,6 +208,7 @@ export const useTradingView = ({ selectedLocale, selectedNetwork, !!marketId, + datafeed, tickSizeDecimals !== undefined, orderLineToggleRef, orderbookCandlesToggleRef, diff --git a/src/hooks/useTradingViewDatafeed.ts b/src/hooks/useTradingViewDatafeed.ts new file mode 100644 index 000000000..075ce0c61 --- /dev/null +++ b/src/hooks/useTradingViewDatafeed.ts @@ -0,0 +1,287 @@ +import { useMemo, useRef } from 'react'; + +import BigNumber from 'bignumber.js'; +import { groupBy } from 'lodash'; +import { DateTime } from 'luxon'; +import type { + DatafeedConfiguration, + ErrorCallback, + GetMarksCallback, + HistoryCallback, + LibrarySymbolInfo, + Mark, + OnReadyCallback, + ResolutionString, + ResolveCallback, + SearchSymbolsCallback, + SubscribeBarsCallback, + Timezone, +} from 'public/tradingview/charting_library'; +import { useDispatch } from 'react-redux'; + +import { Candle, RESOLUTION_MAP } from '@/constants/candles'; +import { DEFAULT_MARKETID } from '@/constants/markets'; +import { DisplayUnit } from '@/constants/trade'; + +import { useDydxClient } from '@/hooks/useDydxClient'; + +import { Themes } from '@/styles/themes'; + +import { store } from '@/state/_store'; +import { getMarketFills } from '@/state/accountSelectors'; +import { useAppSelector } from '@/state/appTypes'; +import { getAppColorMode, getAppTheme } from '@/state/configsSelectors'; +import { getSelectedLocale } from '@/state/localizationSelectors'; +import { setCandles } from '@/state/perpetuals'; +import { getMarketData, getPerpetualBarsForPriceChart } from '@/state/perpetualsSelectors'; + +import { objectKeys } from '@/lib/objectHelpers'; +import { log } from '@/lib/telemetry'; +import { lastBarsCache } from '@/lib/tradingView/dydxfeed/cache'; +import { subscribeOnStream, unsubscribeFromStream } from '@/lib/tradingView/dydxfeed/streaming'; +import { getMarkForOrderFills } from '@/lib/tradingView/dydxfeed/utils'; +import { getHistorySlice, getSymbol, mapCandle } from '@/lib/tradingView/utils'; + +import { useLocaleSeparators } from './useLocaleSeparators'; +import { useStringGetter } from './useStringGetter'; + +const timezone = DateTime.local().get('zoneName') as unknown as Timezone; + +const configurationData: DatafeedConfiguration = { + supported_resolutions: objectKeys(RESOLUTION_MAP), + supports_marks: true, + exchanges: [ + { + value: 'dYdX', // `exchange` argument for the `searchSymbols` method, if a user selects this exchange + name: 'dYdX', // filter name + desc: 'dYdX v4 exchange', // full exchange name displayed in the filter popup + }, + ], + symbols_types: [ + { + name: 'crypto', + value: 'crypto', // `symbolType` argument for the `searchSymbols` method, if a user selects this symbol type + }, + ], +}; + +type Props = { + orderbookCandlesToggleOn: boolean; + tickSizeDecimals?: number; +}; + +export const useTradingViewDatafeed = ({ tickSizeDecimals, orderbookCandlesToggleOn }: Props) => { + const resetCacheRef = useRef<() => void | undefined>(); + + const dispatch = useDispatch(); + const stringGetter = useStringGetter(); + + const { group, decimal } = useLocaleSeparators(); + const appTheme = useAppSelector(getAppTheme); + const appColorMode = useAppSelector(getAppColorMode); + const selectedLocale = useAppSelector(getSelectedLocale); + + const { getCandlesForDatafeed } = useDydxClient(); + + return useMemo( + () => ({ + resetCache: () => { + resetCacheRef.current?.(); + }, + datafeed: { + onReady: (callback: OnReadyCallback) => { + setTimeout(() => callback(configurationData), 0); + }, + + searchSymbols: ( + userInput: string, + exchange: string, + symbolType: string, + onResultReadyCallback: SearchSymbolsCallback + ) => { + onResultReadyCallback([]); + }, + + resolveSymbol: async (symbolName: string, onSymbolResolvedCallback: ResolveCallback) => { + const symbolItem = getSymbol(symbolName || DEFAULT_MARKETID); + const initialPriceScale = BigNumber(10) + .exponentiatedBy(tickSizeDecimals ?? 2) + .toNumber(); + const pricescale = tickSizeDecimals ? 10 ** tickSizeDecimals : initialPriceScale ?? 100; + + const symbolInfo: LibrarySymbolInfo = { + ticker: symbolItem.symbol, + name: symbolItem.full_name, + description: symbolItem.description, + type: symbolItem.type, + exchange: 'dYdX', + listed_exchange: 'dYdX', + has_intraday: true, + has_daily: true, + + minmov: 1, + pricescale, + + session: '24x7', + intraday_multipliers: ['1', '5', '15', '30', '60', '240'], + supported_resolutions: configurationData.supported_resolutions!, + data_status: 'streaming', + timezone, + format: 'price', + }; + + setTimeout(() => onSymbolResolvedCallback(symbolInfo), 0); + }, + + getMarks: async ( + symbolInfo: LibrarySymbolInfo, + fromSeconds: number, + toSeconds: number, + onDataCallback: GetMarksCallback, + resolution: ResolutionString + ) => { + const [fromMs, toMs] = [fromSeconds * 1000, toSeconds * 1000]; + + const market = getMarketData(store.getState(), symbolInfo.ticker!); + if (!market) return; + + const fills = getMarketFills(store.getState())[symbolInfo.ticker!] ?? []; + + const inRangeFills = fills.filter( + (fill) => fill.createdAtMilliseconds >= fromMs && fill.createdAtMilliseconds <= toMs + ); + const fillsByOrderId = groupBy(inRangeFills, 'orderId'); + const marks = Object.entries(fillsByOrderId).map(([orderId, orderFills]) => + getMarkForOrderFills( + store, + orderFills, + orderId, + fromMs, + resolution, + stringGetter, + { group, decimal }, + selectedLocale, + Themes[appTheme][appColorMode] + ) + ); + onDataCallback(marks); + }, + + getBars: async ( + symbolInfo: LibrarySymbolInfo, + resolution: ResolutionString, + periodParams: { + countBack: number; + from: number; + to: number; + firstDataRequest: boolean; + }, + onHistoryCallback: HistoryCallback, + onErrorCallback: ErrorCallback + ) => { + if (!symbolInfo) return; + + const { countBack, from, to, firstDataRequest } = periodParams; + const fromMs = from * 1000; + let toMs = to * 1000; + + // Add 1ms to the toMs to ensure that today's candle is included + if (firstDataRequest && resolution === '1D') { + toMs += 1; + } + + try { + const currentMarketBars = getPerpetualBarsForPriceChart(orderbookCandlesToggleOn)( + store.getState(), + symbolInfo.ticker!, + resolution + ); + + // Retrieve candles in the store that are between fromMs and toMs + const cachedBars = getHistorySlice({ + bars: currentMarketBars, + fromMs, + toMs, + firstDataRequest, + orderbookCandlesToggleOn, + }); + + let fetchedCandles: Candle[] | undefined; + + // If there are not enough candles in the store, retrieve more from the API + if (cachedBars.length < countBack) { + const earliestCachedBarTime = cachedBars?.[cachedBars.length - 1]?.time; + + fetchedCandles = await getCandlesForDatafeed({ + marketId: symbolInfo.ticker!, + resolution, + fromMs, + toMs: earliestCachedBarTime || toMs, + }); + + dispatch( + setCandles({ candles: fetchedCandles, marketId: symbolInfo.ticker!, resolution }) + ); + } + + const volumeUnit = store.getState().configs.displayUnit; + + const bars = [ + ...cachedBars, + ...(fetchedCandles?.map(mapCandle(orderbookCandlesToggleOn)) ?? []), + ] + .map((bar) => ({ + ...bar, + volume: volumeUnit === DisplayUnit.Fiat ? bar.usdVolume : bar.assetVolume, + })) + .reverse(); + + if (bars.length === 0) { + onHistoryCallback([], { + noData: true, + }); + + return; + } + + if (firstDataRequest) { + lastBarsCache.set(`${symbolInfo.ticker}/${RESOLUTION_MAP[resolution]}`, { + ...bars[bars.length - 1], + }); + } + + onHistoryCallback(bars, { + noData: false, + }); + } catch (error) { + log('tradingView/dydxfeed/getBars', error); + onErrorCallback(error); + } + }, + + subscribeBars: ( + symbolInfo: LibrarySymbolInfo, + resolution: ResolutionString, + onTick: SubscribeBarsCallback, + listenerGuid: string, + onResetCacheNeededCallback: () => void + ) => { + resetCacheRef.current = onResetCacheNeededCallback; + subscribeOnStream({ + symbolInfo, + resolution, + onRealtimeCallback: onTick, + listenerGuid, + onResetCacheNeededCallback, + lastBar: lastBarsCache.get(`${symbolInfo.ticker}/${RESOLUTION_MAP[resolution]}`), + }); + }, + + unsubscribeBars: (subscriberUID: string) => { + unsubscribeFromStream(subscriberUID); + }, + }, + }), + [tickSizeDecimals !== undefined, orderbookCandlesToggleOn] + ); +}; diff --git a/src/lib/tradingView/dydxfeed/index.ts b/src/lib/tradingView/dydxfeed/index.ts deleted file mode 100644 index 2d610b493..000000000 --- a/src/lib/tradingView/dydxfeed/index.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { groupBy } from 'lodash'; -import { DateTime } from 'luxon'; -import type { - DatafeedConfiguration, - ErrorCallback, - GetMarksCallback, - HistoryCallback, - IBasicDataFeed, - LibrarySymbolInfo, - Mark, - OnReadyCallback, - ResolutionString, - ResolveCallback, - SearchSymbolsCallback, - SubscribeBarsCallback, - Timezone, -} from 'public/tradingview/charting_library'; - -import { Candle, RESOLUTION_MAP } from '@/constants/candles'; -import { StringGetterFunction, SupportedLocales } from '@/constants/localization'; -import { DEFAULT_MARKETID } from '@/constants/markets'; -import { DisplayUnit } from '@/constants/trade'; - -import { useDydxClient } from '@/hooks/useDydxClient'; - -import { Themes } from '@/styles/themes'; - -import { type RootStore } from '@/state/_store'; -import { getMarketFills } from '@/state/accountSelectors'; -import { getAppColorMode, getAppTheme } from '@/state/configsSelectors'; -import { setCandles } from '@/state/perpetuals'; -import { - getMarketConfig, - getMarketData, - getPerpetualBarsForPriceChart, -} from '@/state/perpetualsSelectors'; - -import { objectKeys } from '@/lib/objectHelpers'; - -import { log } from '../../telemetry'; -import { getHistorySlice, getSymbol, mapCandle } from '../utils'; -import { lastBarsCache } from './cache'; -import { subscribeOnStream, unsubscribeFromStream } from './streaming'; -import { getMarkForOrderFills } from './utils'; - -const timezone = DateTime.local().get('zoneName') as unknown as Timezone; - -const configurationData: DatafeedConfiguration = { - supported_resolutions: objectKeys(RESOLUTION_MAP), - supports_marks: true, - exchanges: [ - { - value: 'dYdX', // `exchange` argument for the `searchSymbols` method, if a user selects this exchange - name: 'dYdX', // filter name - desc: 'dYdX v4 exchange', // full exchange name displayed in the filter popup - }, - ], - symbols_types: [ - { - name: 'crypto', - value: 'crypto', // `symbolType` argument for the `searchSymbols` method, if a user selects this symbol type - }, - ], -}; - -export const getDydxDatafeed = ( - store: RootStore, - getCandlesForDatafeed: ReturnType['getCandlesForDatafeed'], - initialPriceScale: number | null, - orderbookCandlesToggleOn: boolean, - localeSeparators: { - group?: string; - decimal?: string; - }, - selectedLocale: SupportedLocales, - stringGetter: StringGetterFunction -): IBasicDataFeed => ({ - onReady: (callback: OnReadyCallback) => { - setTimeout(() => callback(configurationData), 0); - }, - - searchSymbols: ( - userInput: string, - exchange: string, - symbolType: string, - onResultReadyCallback: SearchSymbolsCallback - ) => { - onResultReadyCallback([]); - }, - - resolveSymbol: async (symbolName: string, onSymbolResolvedCallback: ResolveCallback) => { - const symbolItem = getSymbol(symbolName || DEFAULT_MARKETID); - const { tickSizeDecimals } = getMarketConfig(store.getState(), symbolItem.symbol) ?? {}; - - const pricescale = tickSizeDecimals ? 10 ** tickSizeDecimals : initialPriceScale ?? 100; - - const symbolInfo: LibrarySymbolInfo = { - ticker: symbolItem.symbol, - name: symbolItem.full_name, - description: symbolItem.description, - type: symbolItem.type, - exchange: 'dYdX', - listed_exchange: 'dYdX', - has_intraday: true, - has_daily: true, - - minmov: 1, - pricescale, - - session: '24x7', - intraday_multipliers: ['1', '5', '15', '30', '60', '240'], - supported_resolutions: configurationData.supported_resolutions!, - data_status: 'streaming', - timezone, - format: 'price', - }; - - setTimeout(() => onSymbolResolvedCallback(symbolInfo), 0); - }, - - getMarks: async ( - symbolInfo: LibrarySymbolInfo, - fromSeconds: number, - toSeconds: number, - onDataCallback: GetMarksCallback, - resolution: ResolutionString - ) => { - const theme = getAppTheme(store.getState()); - const colorMode = getAppColorMode(store.getState()); - - const [fromMs, toMs] = [fromSeconds * 1000, toSeconds * 1000]; - const market = getMarketData(store.getState(), symbolInfo.ticker!); - if (!market) return; - - const fills = getMarketFills(store.getState())[symbolInfo.ticker!] ?? []; - const inRangeFills = fills.filter( - (fill) => fill.createdAtMilliseconds >= fromMs && fill.createdAtMilliseconds <= toMs - ); - const fillsByOrderId = groupBy(inRangeFills, 'orderId'); - const marks = Object.entries(fillsByOrderId).map(([orderId, orderFills]) => - getMarkForOrderFills( - store, - orderFills, - orderId, - fromMs, - resolution, - stringGetter, - localeSeparators, - selectedLocale, - Themes[theme][colorMode] - ) - ); - onDataCallback(marks); - }, - - getBars: async ( - symbolInfo: LibrarySymbolInfo, - resolution: ResolutionString, - periodParams: { - countBack: number; - from: number; - to: number; - firstDataRequest: boolean; - }, - onHistoryCallback: HistoryCallback, - onErrorCallback: ErrorCallback - ) => { - if (!symbolInfo) return; - - const { countBack, from, to, firstDataRequest } = periodParams; - const fromMs = from * 1000; - let toMs = to * 1000; - - // Add 1ms to the toMs to ensure that today's candle is included - if (firstDataRequest && resolution === '1D') { - toMs += 1; - } - - try { - const currentMarketBars = getPerpetualBarsForPriceChart(orderbookCandlesToggleOn)( - store.getState(), - symbolInfo.ticker!, - resolution - ); - - // Retrieve candles in the store that are between fromMs and toMs - const cachedBars = getHistorySlice({ - bars: currentMarketBars, - fromMs, - toMs, - firstDataRequest, - orderbookCandlesToggleOn, - }); - - let fetchedCandles: Candle[] | undefined; - - // If there are not enough candles in the store, retrieve more from the API - if (cachedBars.length < countBack) { - const earliestCachedBarTime = cachedBars?.[cachedBars.length - 1]?.time; - - fetchedCandles = await getCandlesForDatafeed({ - marketId: symbolInfo.ticker!, - resolution, - fromMs, - toMs: earliestCachedBarTime || toMs, - }); - - store.dispatch( - setCandles({ candles: fetchedCandles, marketId: symbolInfo.ticker!, resolution }) - ); - } - - const volumeUnit = store.getState().configs.displayUnit; - - const bars = [ - ...cachedBars, - ...(fetchedCandles?.map(mapCandle(orderbookCandlesToggleOn)) ?? []), - ] - .map((bar) => ({ - ...bar, - volume: volumeUnit === DisplayUnit.Fiat ? bar.usdVolume : bar.assetVolume, - })) - .reverse(); - - if (bars.length === 0) { - onHistoryCallback([], { - noData: true, - }); - - return; - } - - if (firstDataRequest) { - lastBarsCache.set(`${symbolInfo.ticker}/${RESOLUTION_MAP[resolution]}`, { - ...bars[bars.length - 1], - }); - } - - onHistoryCallback(bars, { - noData: false, - }); - } catch (error) { - log('tradingView/dydxfeed/getBars', error); - onErrorCallback(error); - } - }, - - subscribeBars: ( - symbolInfo: LibrarySymbolInfo, - resolution: ResolutionString, - onTick: SubscribeBarsCallback, - listenerGuid: string, - onResetCacheNeededCallback: Function - ) => { - onResetCacheNeededCallback(); - subscribeOnStream({ - symbolInfo, - resolution, - onRealtimeCallback: onTick, - listenerGuid, - onResetCacheNeededCallback, - lastBar: lastBarsCache.get(`${symbolInfo.ticker}/${RESOLUTION_MAP[resolution]}`), - }); - }, - - unsubscribeBars: (subscriberUID: string) => { - unsubscribeFromStream(subscriberUID); - }, -}); diff --git a/src/views/charts/TradingView/TvChart.tsx b/src/views/charts/TradingView/TvChart.tsx index 864c79a81..70125e222 100644 --- a/src/views/charts/TradingView/TvChart.tsx +++ b/src/views/charts/TradingView/TvChart.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from 'react'; +import BigNumber from 'bignumber.js'; import type { ResolutionString } from 'public/tradingview/charting_library'; import { DEFAULT_MARKETID } from '@/constants/markets'; @@ -12,17 +13,24 @@ import { useOrderbookCandles } from '@/hooks/tradingView/useOrderbookCandles'; import { useTradingView } from '@/hooks/tradingView/useTradingView'; import { useTradingViewTheme } from '@/hooks/tradingView/useTradingViewTheme'; import { useTradingViewToggles } from '@/hooks/tradingView/useTradingViewToggles'; +import { useDydxClient } from '@/hooks/useDydxClient'; import usePrevious from '@/hooks/usePrevious'; +import { useTradingViewDatafeed } from '@/hooks/useTradingViewDatafeed'; import { useAppSelector } from '@/state/appTypes'; import { getSelectedDisplayUnit } from '@/state/configsSelectors'; -import { getCurrentMarketId } from '@/state/perpetualsSelectors'; +import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors'; + +import { orEmptyObj } from '@/lib/typeUtils'; import { BaseTvChart } from './BaseTvChart'; export const TvChart = () => { const [isChartReady, setIsChartReady] = useState(false); const currentMarketId: string = useAppSelector(getCurrentMarketId) ?? DEFAULT_MARKETID; + const { tickSizeDecimals: tickSizeDecimalsAbacus } = orEmptyObj( + useAppSelector(getCurrentMarketConfig) + ); const tvWidgetRef = useRef(null); const tvWidget = tvWidgetRef.current; @@ -36,6 +44,31 @@ export const TvChart = () => { const buySellMarksToggleRef = useRef(null); const buySellMarksToggle = buySellMarksToggleRef.current; + const [tickSizeDecimalsIndexer, setTickSizeDecimalsIndexer] = useState<{ + [marketId: string]: number | undefined; + }>({}); + + const tickSizeDecimals = + (currentMarketId + ? tickSizeDecimalsIndexer[currentMarketId] ?? tickSizeDecimalsAbacus + : tickSizeDecimalsAbacus) ?? undefined; + + const { getMarketTickSize } = useDydxClient(); + + useEffect(() => { + // we only need tick size from current market for the price scale settings + // if markets haven't been loaded via abacus, get the current market info from indexer + (async () => { + if (currentMarketId && tickSizeDecimals === undefined) { + const marketTickSize = await getMarketTickSize(currentMarketId); + setTickSizeDecimalsIndexer((prev) => ({ + ...prev, + [currentMarketId]: BigNumber(marketTickSize).decimalPlaces() ?? undefined, + })); + } + })(); + }, [currentMarketId, getMarketTickSize, tickSizeDecimals]); + const { orderLinesToggleOn, setOrderLinesToggleOn, @@ -44,6 +77,12 @@ export const TvChart = () => { setBuySellMarksToggleOn, buySellMarksToggleOn, } = useTradingViewToggles(); + + const { datafeed, resetCache } = useTradingViewDatafeed({ + orderbookCandlesToggleOn, + tickSizeDecimals, + }); + const { savedResolution } = useTradingView({ tvWidgetRef, orderLineToggleRef, @@ -56,6 +95,8 @@ export const TvChart = () => { buySellMarksToggleOn, setBuySellMarksToggleOn, setIsChartReady, + tickSizeDecimals, + datafeed, }); useChartMarketAndResolution({ currentMarketId, @@ -92,9 +133,12 @@ export const TvChart = () => { // Only reset data if displayUnit has actually changed if (prevDisplayUnit !== displayUnit) { const chart = tvWidget.activeChart?.(); - chart?.resetData(); + if (chart) { + chart.resetData(); + resetCache(); + } } - }, [displayUnit, tvWidget, isChartReady, prevDisplayUnit]); + }, [displayUnit, tvWidget, isChartReady, prevDisplayUnit, resetCache]); return ; }; From 3f9058c12f1aaf97c221ca0f52e675de657acbff Mon Sep 17 00:00:00 2001 From: aforaleka Date: Mon, 14 Oct 2024 16:03:22 -0400 Subject: [PATCH 2/3] update location --- src/hooks/{ => tradingView}/useTradingViewDatafeed.ts | 0 src/views/charts/TradingView/TvChart.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/hooks/{ => tradingView}/useTradingViewDatafeed.ts (100%) diff --git a/src/hooks/useTradingViewDatafeed.ts b/src/hooks/tradingView/useTradingViewDatafeed.ts similarity index 100% rename from src/hooks/useTradingViewDatafeed.ts rename to src/hooks/tradingView/useTradingViewDatafeed.ts diff --git a/src/views/charts/TradingView/TvChart.tsx b/src/views/charts/TradingView/TvChart.tsx index 70125e222..3f221ff0a 100644 --- a/src/views/charts/TradingView/TvChart.tsx +++ b/src/views/charts/TradingView/TvChart.tsx @@ -11,11 +11,11 @@ import { useChartLines } from '@/hooks/tradingView/useChartLines'; import { useChartMarketAndResolution } from '@/hooks/tradingView/useChartMarketAndResolution'; import { useOrderbookCandles } from '@/hooks/tradingView/useOrderbookCandles'; import { useTradingView } from '@/hooks/tradingView/useTradingView'; +import { useTradingViewDatafeed } from '@/hooks/tradingView/useTradingViewDatafeed'; import { useTradingViewTheme } from '@/hooks/tradingView/useTradingViewTheme'; import { useTradingViewToggles } from '@/hooks/tradingView/useTradingViewToggles'; import { useDydxClient } from '@/hooks/useDydxClient'; import usePrevious from '@/hooks/usePrevious'; -import { useTradingViewDatafeed } from '@/hooks/useTradingViewDatafeed'; import { useAppSelector } from '@/state/appTypes'; import { getSelectedDisplayUnit } from '@/state/configsSelectors'; From c58c44ff713adb18bb55356510d80aabbef19db4 Mon Sep 17 00:00:00 2001 From: aforaleka Date: Mon, 14 Oct 2024 16:12:25 -0400 Subject: [PATCH 3/3] update imports --- src/hooks/tradingView/useTradingViewDatafeed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/tradingView/useTradingViewDatafeed.ts b/src/hooks/tradingView/useTradingViewDatafeed.ts index 075ce0c61..ce8aaa7c5 100644 --- a/src/hooks/tradingView/useTradingViewDatafeed.ts +++ b/src/hooks/tradingView/useTradingViewDatafeed.ts @@ -42,8 +42,8 @@ import { subscribeOnStream, unsubscribeFromStream } from '@/lib/tradingView/dydx import { getMarkForOrderFills } from '@/lib/tradingView/dydxfeed/utils'; import { getHistorySlice, getSymbol, mapCandle } from '@/lib/tradingView/utils'; -import { useLocaleSeparators } from './useLocaleSeparators'; -import { useStringGetter } from './useStringGetter'; +import { useLocaleSeparators } from '../useLocaleSeparators'; +import { useStringGetter } from '../useStringGetter'; const timezone = DateTime.local().get('zoneName') as unknown as Timezone;