diff --git a/packages/curve-ui-kit/src/features/candle-chart/CandleChart.tsx b/packages/curve-ui-kit/src/features/candle-chart/CandleChart.tsx index b599e959d..8d6114ef2 100644 --- a/packages/curve-ui-kit/src/features/candle-chart/CandleChart.tsx +++ b/packages/curve-ui-kit/src/features/candle-chart/CandleChart.tsx @@ -1,19 +1,19 @@ -import type { IChartApi, Time, ISeriesApi, LineWidth } from 'lightweight-charts' -import { - createChart, - ColorType, - CrosshairMode, - LineStyle, - AreaSeries, - CandlestickSeries, - LineSeries, -} from 'lightweight-charts' +import type { IChartApi, Time, ISeriesApi, LineWidth, IPriceLine, CustomSeriesWhitespaceData } from 'lightweight-charts' +import { createChart, ColorType, CrosshairMode, LineStyle, CandlestickSeries, LineSeries } from 'lightweight-charts' import lodash from 'lodash' -import { useEffect, useRef, useState, useCallback, useMemo } from 'react' +import { useEffect, useRef, useState, useCallback, useMemo, type RefObject } from 'react' import { styled } from 'styled-components' import { formatNumber } from '@ui-kit/utils/' +import { createLiquidationRangeSeries } from './custom-series/liquidationRangeSeries' +import type { LiquidationRangePoint, LiquidationRangeSeriesOptions } from './custom-series/liquidationRangeSeries' import type { ChartColors } from './hooks/useChartPalette' -import type { LpPriceOhlcDataFormatted, ChartHeight, OraclePriceData, LiquidationRanges } from './types' +import type { + LpPriceOhlcDataFormatted, + ChartHeight, + OraclePriceData, + LiquidationRanges, + LlammaLiquididationRange, +} from './types' import { calculateRobustPriceRange } from './utils' const createPriceFormatter = () => ({ @@ -25,16 +25,57 @@ const createPriceFormatter = () => ({ }), }) -/** - * Shared configuration for liquidation range area series - */ -const SL_RANGE_AREA_SERIES_DEFAULTS = { - lineStyle: 3, - crosshairMarkerVisible: false, - pointMarkersVisible: false, - lineVisible: false, - priceLineStyle: 3, -} as const +type RangeValueAccumulator = { + upper?: number + lower?: number +} + +const normalizeLiquidationRangePoints = (range?: LlammaLiquididationRange | null): LiquidationRangePoint[] => { + if (!range) return [] + + const pointMap = new Map() + + const assignPoint = (time: number, key: keyof RangeValueAccumulator, value: number) => { + const entry = pointMap.get(time) ?? {} + entry[key] = value + pointMap.set(time, entry) + } + + range.price1?.forEach(({ time, value }) => assignPoint(time, 'upper', value)) + range.price2?.forEach(({ time, value }) => assignPoint(time, 'lower', value)) + + const orderedEntries = Array.from(pointMap.entries()) + .filter(([, values]) => typeof values.upper === 'number' && typeof values.lower === 'number') + .sort((a, b) => a[0] - b[0]) + + if (!orderedEntries.length) { + return [] + } + + const fallbackStart = orderedEntries[0][0] as Time + const fallbackEnd = orderedEntries[orderedEntries.length - 1][0] as Time + const rangeStartTime = (range.startTime ?? fallbackStart) as Time + const rangeEndTime = (range.endTime ?? fallbackEnd) as Time + + return orderedEntries.map(([time, values]) => { + const upper = values.upper as number + const lower = values.lower as number + return { + time: time as Time, + upper: Math.max(upper, lower), + lower: Math.min(upper, lower), + rangeStartTime, + rangeEndTime, + } + }) +} + +type LiquidationRangeSeriesApi = ISeriesApi< + 'Custom', + Time, + LiquidationRangePoint | CustomSeriesWhitespaceData