From a77765b1e99b1ef744198d622d7702c1bbbc8e9d Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Tue, 12 Sep 2023 09:56:43 -0700 Subject: [PATCH] feat(trading): store study sizes, reduce candles gap (#4708) --- apps/trading/pages/styles.css | 1 + libs/candles-chart/src/lib/candles-chart.tsx | 31 ++++++++-- .../src/lib/use-candles-chart-settings.ts | 61 +++++++++++++++---- package.json | 2 +- yarn.lock | 8 +-- 5 files changed, 80 insertions(+), 23 deletions(-) diff --git a/apps/trading/pages/styles.css b/apps/trading/pages/styles.css index e9e1eb368..22d9a6efc 100644 --- a/apps/trading/pages/styles.css +++ b/apps/trading/pages/styles.css @@ -82,6 +82,7 @@ html [data-theme='light'] { --pennant-color-volume-sell: theme(colors.market.red.DEFAULT); + /* reduce space between candles */ --pennant-candlestick-inner-padding: 0.1; } diff --git a/libs/candles-chart/src/lib/candles-chart.tsx b/libs/candles-chart/src/lib/candles-chart.tsx index c05e79399..c0397f681 100644 --- a/libs/candles-chart/src/lib/candles-chart.tsx +++ b/libs/candles-chart/src/lib/candles-chart.tsx @@ -3,6 +3,7 @@ import { CandlestickChart } from 'pennant'; import { VegaDataSource } from './data-source'; import { useApolloClient } from '@apollo/client'; import { useMemo } from 'react'; +import debounce from 'lodash/debounce'; import AutoSizer from 'react-virtualized-auto-sizer'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { useThemeSwitcher } from '@vegaprotocol/react-helpers'; @@ -22,8 +23,25 @@ export const CandlesChartContainer = ({ const { pubKey } = useVegaWallet(); const { theme } = useThemeSwitcher(); - const { interval, chartType, overlays, studies, merge } = - useCandlesChartSettings(); + const { + interval, + chartType, + overlays, + studies, + studySizes, + setStudies, + setStudySizes, + setOverlays, + } = useCandlesChartSettings(); + + const handlePaneChange = useMemo( + () => + debounce((sizes: number[]) => { + // first number is main pain, which is greedy so we don't store it + setStudySizes(sizes.filter((_, i) => i !== 0)); + }, 300), + [setStudySizes] + ); const dataSource = useMemo(() => { return new VegaDataSource(client, marketId, pubKey); @@ -45,15 +63,16 @@ export const CandlesChartContainer = ({ initialNumCandlesToDisplay: Math.floor( width * CANDLES_TO_WIDTH_FACTOR ), + studySize: 150, // default size + studySizes, }} interval={interval} theme={theme} onOptionsChanged={(options) => { - merge({ - overlays: options.overlays, - studies: options.studies, - }); + setStudies(options.studies); + setOverlays(options.overlays); }} + onPaneChanged={handlePaneChange} /> )} diff --git a/libs/candles-chart/src/lib/use-candles-chart-settings.ts b/libs/candles-chart/src/lib/use-candles-chart-settings.ts index cce6fb5b5..c43ab4732 100644 --- a/libs/candles-chart/src/lib/use-candles-chart-settings.ts +++ b/libs/candles-chart/src/lib/use-candles-chart-settings.ts @@ -5,36 +5,45 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; +type StudySizes = { [S in Study]?: number }; + interface StoredSettings { interval: Interval; type: ChartType; overlays: Overlay[]; studies: Study[]; + studySizes: StudySizes; } +export const STUDY_SIZE = 100; +const STUDY_ORDER: Study[] = [ + Study.FORCE_INDEX, + Study.RELATIVE_STRENGTH_INDEX, + Study.ELDAR_RAY, + Study.MACD, + Study.VOLUME, +]; + const DEFAULT_CHART_SETTINGS = { interval: Interval.I15M, type: ChartType.CANDLE, overlays: [Overlay.MOVING_AVERAGE], studies: [Study.MACD, Study.VOLUME], + studySizes: {}, }; export const useCandlesChartSettingsStore = create< StoredSettings & { - merge: (settings: Partial) => void; setType: (type: ChartType) => void; setInterval: (interval: Interval) => void; - setOverlays: (overlays: Overlay[]) => void; - setStudies: (studies: Study[]) => void; + setOverlays: (overlays?: Overlay[]) => void; + setStudies: (studies?: Study[]) => void; + setStudySizes: (sizes: number[]) => void; } >()( persist( immer((set) => ({ ...DEFAULT_CHART_SETTINGS, - merge: (settings: Partial) => - set((state) => { - Object.assign(state, settings); - }), setType: (type) => set((state) => { state.type = type; @@ -43,14 +52,35 @@ export const useCandlesChartSettingsStore = create< set((state) => { state.interval = interval; }), - setOverlays: (overlays) => + setOverlays: (overlays) => { + if (!overlays) return; + set((state) => { state.overlays = overlays; - }), - setStudies: (studies) => + }); + }, + setStudies: (studies) => { + if (!studies) return; + + // Make sure studies are always returned in the same order + studies.sort((a, b) => { + return STUDY_ORDER.indexOf(a) - STUDY_ORDER.indexOf(b); + }); + set((state) => { state.studies = studies; - }), + }); + }, + setStudySizes: (sizes) => { + set((state) => { + // for every study find the corresonding size and update + // the size record for that study + state.studies.forEach((s, i) => { + const size = sizes[i]; + state.studySizes[s] = size; + }); + }); + }, })), { name: 'vega_candles_chart_store', @@ -85,15 +115,22 @@ export const useCandlesChartSettings = () => { [Study.VOLUME] ); + // find the study size + const studySizes = studies.map((s) => { + const size = settings.studySizes[s] || STUDY_SIZE; + return size; + }); + return { interval, chartType, overlays, studies, + studySizes, setInterval: settings.setInterval, setType: settings.setType, setStudies: settings.setStudies, setOverlays: settings.setOverlays, - merge: settings.merge, + setStudySizes: settings.setStudySizes, }; }; diff --git a/package.json b/package.json index 9cee95fdb..0938cdf05 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "jsondiffpatch": "^0.4.1", "lodash": "^4.17.21", "next": "13.3.0", - "pennant": "1.11.1", + "pennant": "1.12.0", "react": "18.2.0", "react-copy-to-clipboard": "^5.0.4", "react-dom": "18.2.0", diff --git a/yarn.lock b/yarn.lock index 8b074a557..9c0a29346 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20253,10 +20253,10 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -pennant@1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.11.1.tgz#f47bceaade01db215eeba666cd755840d16fe9ed" - integrity sha512-U26OxjxETWLJAvCFj20oH0y5gzyBfJ5oD4O3dYg1aWnH4giUYDPWuUDFnzlMJ+yXolyYECccXm6S1BkoVcVzDg== +pennant@1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.12.0.tgz#e12707d5f1aac554d81bad060637e608335e0b50" + integrity sha512-xosg5erRf+Ke9iORdqyv+SOGcD3uJX1dgf990q1DvHuzz36w2txCZsfnvcXhRO++HYVKS9sU/YLPRLJgolFGtA== dependencies: "@babel/runtime" "^7.13.10" "@d3fc/d3fc-technical-indicator" "^8.0.1"