technical indc
This commit is contained in:
parent
72844038db
commit
2ec44f1742
|
@ -23,7 +23,7 @@ function Modal({
|
||||||
className="relative z-50 overflow-y-auto"
|
className="relative z-50 overflow-y-auto"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`fixed inset-0 backdrop-blur-sm backdrop-brightness-75 ${
|
className={`fixed inset-0 bg-black opacity-50 ${
|
||||||
disableOutsideClose ? 'pointer-events-none' : ''
|
disableOutsideClose ? 'pointer-events-none' : ''
|
||||||
}`}
|
}`}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|
|
@ -16,7 +16,11 @@ import { useWallet } from '@solana/wallet-adapter-react'
|
||||||
import TradeOnboardingTour from '@components/tours/TradeOnboardingTour'
|
import TradeOnboardingTour from '@components/tours/TradeOnboardingTour'
|
||||||
import FavoriteMarketsBar from './FavoriteMarketsBar'
|
import FavoriteMarketsBar from './FavoriteMarketsBar'
|
||||||
|
|
||||||
const TradingViewChart = dynamic(() => import('./TradingViewChart'), {
|
//const TradingViewChart = dynamic(() => import('./TradingViewChart'), {
|
||||||
|
// ssr: false,
|
||||||
|
//})
|
||||||
|
|
||||||
|
const TradingViewChartKline = dynamic(() => import('./TradingViewChartKline'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -179,7 +183,7 @@ const TradeAdvancedPage = () => {
|
||||||
className="h-full border border-x-0 border-th-bkg-3"
|
className="h-full border border-x-0 border-th-bkg-3"
|
||||||
>
|
>
|
||||||
<div className={`relative h-full overflow-auto`}>
|
<div className={`relative h-full overflow-auto`}>
|
||||||
<TradingViewChart />
|
<TradingViewChartKline />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div key="balances">
|
<div key="balances">
|
||||||
|
|
|
@ -1,182 +1,194 @@
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useMemo, useState } from 'react'
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
|
import {
|
||||||
|
widget,
|
||||||
|
ChartingLibraryWidgetOptions,
|
||||||
|
IChartingLibraryWidget,
|
||||||
|
ResolutionString,
|
||||||
|
} from '@public/charting_library'
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
import { CHART_DATA_FEED } from 'utils/constants'
|
import { useViewport } from 'hooks/useViewport'
|
||||||
import { init, dispose } from 'klinecharts'
|
import { CHART_DATA_FEED, DEFAULT_MARKET_NAME } from 'utils/constants'
|
||||||
import axios from 'axios'
|
import { breakpoints } from 'utils/theme'
|
||||||
|
import { COLORS } from 'styles/colors'
|
||||||
|
|
||||||
const ONE_HOUR_MINS = 60
|
export interface ChartContainerProps {
|
||||||
|
container: ChartingLibraryWidgetOptions['container']
|
||||||
const RES_NAME_TO_RES_VAL: {
|
symbol: ChartingLibraryWidgetOptions['symbol']
|
||||||
[key: string]: {
|
interval: ChartingLibraryWidgetOptions['interval']
|
||||||
val: string
|
datafeedUrl: string
|
||||||
seconds: number
|
libraryPath: ChartingLibraryWidgetOptions['library_path']
|
||||||
}
|
chartsStorageUrl: ChartingLibraryWidgetOptions['charts_storage_url']
|
||||||
} = {
|
chartsStorageApiVersion: ChartingLibraryWidgetOptions['charts_storage_api_version']
|
||||||
'1m': { val: '1', seconds: 60 },
|
clientId: ChartingLibraryWidgetOptions['client_id']
|
||||||
'5m': { val: '5', seconds: 5 * 60 },
|
userId: ChartingLibraryWidgetOptions['user_id']
|
||||||
'30m': {
|
fullscreen: ChartingLibraryWidgetOptions['fullscreen']
|
||||||
val: `${ONE_HOUR_MINS / 2}`,
|
autosize: ChartingLibraryWidgetOptions['autosize']
|
||||||
seconds: (ONE_HOUR_MINS / 2) * 60,
|
studiesOverrides: ChartingLibraryWidgetOptions['studies_overrides']
|
||||||
},
|
theme: string
|
||||||
'1H': { val: `${ONE_HOUR_MINS}`, seconds: ONE_HOUR_MINS * 60 },
|
|
||||||
'2H': { val: `${2 * ONE_HOUR_MINS}`, seconds: ONE_HOUR_MINS * 2 * 60 },
|
|
||||||
'4H': { val: `${4 * ONE_HOUR_MINS}`, seconds: ONE_HOUR_MINS * 4 * 60 },
|
|
||||||
'1D': { val: '1D', seconds: 24 * ONE_HOUR_MINS * 60 },
|
|
||||||
}
|
|
||||||
|
|
||||||
type BASE_CHART_QUERY = {
|
|
||||||
resolution: string
|
|
||||||
symbol: string
|
|
||||||
to: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type CHART_QUERY = BASE_CHART_QUERY & {
|
|
||||||
from: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type HISTORY = {
|
|
||||||
c: string[]
|
|
||||||
h: string[]
|
|
||||||
l: string[]
|
|
||||||
o: string[]
|
|
||||||
t: number[]
|
|
||||||
v: string[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TradingViewChart = () => {
|
const TradingViewChart = () => {
|
||||||
//const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const { width } = useViewport()
|
||||||
|
|
||||||
|
const [chartReady, setChartReady] = useState(false)
|
||||||
const selectedMarketName = mangoStore((s) => s.selectedMarket.current?.name)
|
const selectedMarketName = mangoStore((s) => s.selectedMarket.current?.name)
|
||||||
const [resolution, setResultion] = useState(RES_NAME_TO_RES_VAL['1H'])
|
const isMobile = width ? width < breakpoints.sm : false
|
||||||
const [chart, setChart] = useState<klinecharts.Chart | null>(null)
|
|
||||||
const [baseChartQuery, setQuery] = useState<BASE_CHART_QUERY | null>(null)
|
const defaultProps = useMemo(
|
||||||
const clearTimerRef = useRef<NodeJS.Timeout | null>(null)
|
() => ({
|
||||||
const fetchData = async (baseQuery: BASE_CHART_QUERY, from: number) => {
|
symbol: DEFAULT_MARKET_NAME,
|
||||||
const query: CHART_QUERY = {
|
interval: '60' as ResolutionString,
|
||||||
...baseQuery,
|
theme: 'Dark',
|
||||||
from,
|
container: 'tv_chart_container',
|
||||||
}
|
datafeedUrl: CHART_DATA_FEED,
|
||||||
const response = await axios.get(`${CHART_DATA_FEED}/history`, {
|
libraryPath: '/charting_library/',
|
||||||
params: query,
|
fullscreen: false,
|
||||||
})
|
autosize: true,
|
||||||
const newData = response.data as HISTORY
|
studiesOverrides: {
|
||||||
const dataSize = newData.t.length
|
'volume.volume.color.0': COLORS.DOWN[theme],
|
||||||
const dataList = []
|
'volume.volume.color.1': COLORS.UP[theme],
|
||||||
for (let i = 0; i < dataSize; i++) {
|
'volume.precision': 4,
|
||||||
const kLineModel = {
|
},
|
||||||
open: parseFloat(newData.o[i]),
|
}),
|
||||||
low: parseFloat(newData.l[i]),
|
[theme]
|
||||||
high: parseFloat(newData.h[i]),
|
)
|
||||||
close: parseFloat(newData.c[i]),
|
|
||||||
volume: parseFloat(newData.v[i]),
|
const tvWidgetRef = useRef<IChartingLibraryWidget | null>(null)
|
||||||
timestamp: newData.t[i] * 1000,
|
|
||||||
}
|
let chartStyleOverrides = {
|
||||||
dataList.push(kLineModel)
|
'paneProperties.background': 'rgba(0,0,0,0)',
|
||||||
}
|
'paneProperties.backgroundType': 'solid',
|
||||||
return dataList
|
'paneProperties.legendProperties.showBackground': false,
|
||||||
|
'paneProperties.vertGridProperties.color': 'rgba(0,0,0,0)',
|
||||||
|
'paneProperties.horzGridProperties.color': 'rgba(0,0,0,0)',
|
||||||
|
'paneProperties.legendProperties.showStudyTitles': false,
|
||||||
|
'scalesProperties.showStudyLastValue': false,
|
||||||
|
'scalesProperties.fontSize': 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateData(
|
const mainSeriesProperties = [
|
||||||
kLineChart: klinecharts.Chart,
|
'candleStyle',
|
||||||
baseQuery: BASE_CHART_QUERY
|
'hollowCandleStyle',
|
||||||
) {
|
'haStyle',
|
||||||
if (clearTimerRef.current) {
|
'barStyle',
|
||||||
clearInterval(clearTimerRef.current)
|
]
|
||||||
}
|
|
||||||
clearTimerRef.current = setTimeout(async () => {
|
mainSeriesProperties.forEach((prop) => {
|
||||||
if (kLineChart) {
|
chartStyleOverrides = {
|
||||||
const from = baseQuery.to - resolution.seconds
|
...chartStyleOverrides,
|
||||||
const newData = (await fetchData(baseQuery!, from))[0]
|
[`mainSeriesProperties.${prop}.barColorsOnPrevClose`]: true,
|
||||||
newData.timestamp += 10000
|
[`mainSeriesProperties.${prop}.drawWick`]: true,
|
||||||
kLineChart.updateData(newData)
|
[`mainSeriesProperties.${prop}.drawBorder`]: true,
|
||||||
updateData(kLineChart, baseQuery)
|
[`mainSeriesProperties.${prop}.upColor`]: COLORS.UP[theme],
|
||||||
}
|
[`mainSeriesProperties.${prop}.downColor`]: COLORS.DOWN[theme],
|
||||||
}, 10000)
|
[`mainSeriesProperties.${prop}.borderColor`]: COLORS.UP[theme],
|
||||||
|
[`mainSeriesProperties.${prop}.borderUpColor`]: COLORS.UP[theme],
|
||||||
|
[`mainSeriesProperties.${prop}.borderDownColor`]: COLORS.DOWN[theme],
|
||||||
|
[`mainSeriesProperties.${prop}.wickUpColor`]: COLORS.UP[theme],
|
||||||
|
[`mainSeriesProperties.${prop}.wickDownColor`]: COLORS.DOWN[theme],
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchFreshData = async () => {
|
if (tvWidgetRef.current && chartReady && selectedMarketName) {
|
||||||
const from = Math.floor(Date.now() / 1000) - 60 * 60 * 24 * 365
|
tvWidgetRef.current.setSymbol(
|
||||||
const data = await fetchData(baseChartQuery!, from)
|
selectedMarketName!,
|
||||||
chart?.applyNewData(data)
|
tvWidgetRef.current.activeChart().resolution(),
|
||||||
updateData(chart!, baseChartQuery!)
|
() => {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (chart && baseChartQuery) {
|
)
|
||||||
fetchFreshData()
|
|
||||||
}
|
}
|
||||||
}, [baseChartQuery])
|
}, [selectedMarketName, chartReady])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedMarketName && resolution) {
|
if (window) {
|
||||||
setQuery({
|
const widgetOptions: ChartingLibraryWidgetOptions = {
|
||||||
resolution: resolution.val,
|
// debug: true,
|
||||||
symbol: selectedMarketName,
|
symbol: defaultProps.symbol,
|
||||||
to: Math.floor(Date.now() / 1000),
|
// BEWARE: no trailing slash is expected in feed URL
|
||||||
})
|
// tslint:disable-next-line:no-any
|
||||||
}
|
datafeed: new (window as any).Datafeeds.UDFCompatibleDatafeed(
|
||||||
}, [selectedMarketName, resolution])
|
defaultProps.datafeedUrl
|
||||||
|
),
|
||||||
|
interval:
|
||||||
|
defaultProps.interval as ChartingLibraryWidgetOptions['interval'],
|
||||||
|
container:
|
||||||
|
defaultProps.container as ChartingLibraryWidgetOptions['container'],
|
||||||
|
library_path: defaultProps.libraryPath as string,
|
||||||
|
locale: 'en',
|
||||||
|
enabled_features: ['hide_left_toolbar_by_default'],
|
||||||
|
disabled_features: [
|
||||||
|
'use_localstorage_for_settings',
|
||||||
|
'timeframes_toolbar',
|
||||||
|
isMobile ? 'left_toolbar' : '',
|
||||||
|
'show_logo_on_all_charts',
|
||||||
|
'caption_buttons_text_if_possible',
|
||||||
|
'header_settings',
|
||||||
|
// 'header_chart_type',
|
||||||
|
'header_compare',
|
||||||
|
'compare_symbol',
|
||||||
|
'header_screenshot',
|
||||||
|
// 'header_widget_dom_node',
|
||||||
|
// 'header_widget',
|
||||||
|
'header_saveload',
|
||||||
|
'header_undo_redo',
|
||||||
|
'header_interval_dialog_button',
|
||||||
|
'show_interval_dialog_on_key_press',
|
||||||
|
'header_symbol_search',
|
||||||
|
'popup_hints',
|
||||||
|
],
|
||||||
|
fullscreen: defaultProps.fullscreen,
|
||||||
|
autosize: defaultProps.autosize,
|
||||||
|
studies_overrides: defaultProps.studiesOverrides,
|
||||||
|
theme:
|
||||||
|
theme === 'Light' || theme === 'Banana' || theme === 'Lychee'
|
||||||
|
? 'Light'
|
||||||
|
: 'Dark',
|
||||||
|
custom_css_url: '/styles/tradingview.css',
|
||||||
|
loading_screen: {
|
||||||
|
backgroundColor:
|
||||||
|
theme === 'Dark'
|
||||||
|
? COLORS.BKG1.Dark
|
||||||
|
: theme === 'Light'
|
||||||
|
? COLORS.BKG1.Light
|
||||||
|
: theme === 'Mango Classic'
|
||||||
|
? COLORS.BKG1['Mango Classic']
|
||||||
|
: theme === 'Medium'
|
||||||
|
? COLORS.BKG1.Medium
|
||||||
|
: theme === 'Avocado'
|
||||||
|
? COLORS.BKG1.Avocado
|
||||||
|
: theme === 'Blueberry'
|
||||||
|
? COLORS.BKG1.Blueberry
|
||||||
|
: theme === 'Banana'
|
||||||
|
? COLORS.BKG1.Banana
|
||||||
|
: theme === 'Lychee'
|
||||||
|
? COLORS.BKG1.Lychee
|
||||||
|
: theme === 'Olive'
|
||||||
|
? COLORS.BKG1.Olive
|
||||||
|
: COLORS.BKG1['High Contrast'],
|
||||||
|
},
|
||||||
|
overrides: {
|
||||||
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
|
||||||
useEffect(() => {
|
...chartStyleOverrides,
|
||||||
const initKline = async () => {
|
},
|
||||||
const style = getComputedStyle(document.body)
|
}
|
||||||
const gridColor = style.getPropertyValue('--bkg-3')
|
|
||||||
const kLineChart = init('update-k-line')
|
|
||||||
|
|
||||||
kLineChart.setStyleOptions({
|
const tvWidget = new widget(widgetOptions)
|
||||||
grid: {
|
tvWidgetRef.current = tvWidget
|
||||||
show: true,
|
|
||||||
horizontal: {
|
tvWidgetRef.current.onChartReady(function () {
|
||||||
color: gridColor,
|
setChartReady(true)
|
||||||
},
|
|
||||||
vertical: {
|
|
||||||
color: gridColor,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
candle: {
|
|
||||||
tooltip: {
|
|
||||||
labels: ['T: ', 'O: ', 'C: ', 'H: ', 'L: ', 'V: '],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
axisLine: {
|
|
||||||
show: true,
|
|
||||||
color: gridColor,
|
|
||||||
size: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
axisLine: {
|
|
||||||
show: true,
|
|
||||||
color: gridColor,
|
|
||||||
size: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
setChart(kLineChart)
|
//eslint-disable-next-line
|
||||||
}
|
}
|
||||||
initKline()
|
}, [theme, isMobile, defaultProps])
|
||||||
return () => {
|
|
||||||
dispose('update-k-line')
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div id={defaultProps.container as string} className="tradingview-chart" />
|
||||||
<div className="flex">
|
|
||||||
{Object.keys(RES_NAME_TO_RES_VAL).map((key) => (
|
|
||||||
<div
|
|
||||||
className="cursor-pointer py-1 px-2"
|
|
||||||
key={key}
|
|
||||||
onClick={() => setResultion(RES_NAME_TO_RES_VAL[key])}
|
|
||||||
>
|
|
||||||
{key}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{ height: 'calc(100% - 30px)' }}
|
|
||||||
id="update-k-line"
|
|
||||||
className="k-line-chart"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,324 @@
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import { CHART_DATA_FEED } from 'utils/constants'
|
||||||
|
import klinecharts, { init, dispose } from 'klinecharts'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useViewport } from 'hooks/useViewport'
|
||||||
|
import usePrevious from '@components/shared/usePrevious'
|
||||||
|
import Modal from '@components/shared/Modal'
|
||||||
|
import Switch from '@components/forms/Switch'
|
||||||
|
|
||||||
|
const ONE_HOUR_MINS = 60
|
||||||
|
const ONE_MINUTE_SECONDS = 60
|
||||||
|
const ONE_HOUR_SECONDS = ONE_HOUR_MINS * ONE_MINUTE_SECONDS
|
||||||
|
const ONE_DAY_SECONDS = ONE_HOUR_SECONDS * 24
|
||||||
|
|
||||||
|
type BASE_CHART_QUERY = {
|
||||||
|
resolution: string
|
||||||
|
symbol: string
|
||||||
|
to: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type CHART_QUERY = BASE_CHART_QUERY & {
|
||||||
|
from: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type HISTORY = {
|
||||||
|
c: string[]
|
||||||
|
h: string[]
|
||||||
|
l: string[]
|
||||||
|
o: string[]
|
||||||
|
t: number[]
|
||||||
|
v: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
//Translate values that api accepts to chart seconds
|
||||||
|
const RES_NAME_TO_RES_VAL: {
|
||||||
|
[key: string]: {
|
||||||
|
val: string
|
||||||
|
seconds: number
|
||||||
|
}
|
||||||
|
} = {
|
||||||
|
'1m': { val: '1', seconds: ONE_MINUTE_SECONDS },
|
||||||
|
'5m': { val: '5', seconds: 5 * ONE_MINUTE_SECONDS },
|
||||||
|
'30m': {
|
||||||
|
val: `${ONE_HOUR_MINS / 2}`,
|
||||||
|
seconds: (ONE_HOUR_MINS / 2) * ONE_MINUTE_SECONDS,
|
||||||
|
},
|
||||||
|
'1H': { val: `${ONE_HOUR_MINS}`, seconds: ONE_HOUR_SECONDS },
|
||||||
|
'2H': { val: `${2 * ONE_HOUR_MINS}`, seconds: ONE_HOUR_SECONDS * 2 },
|
||||||
|
'4H': { val: `${4 * ONE_HOUR_MINS}`, seconds: ONE_HOUR_SECONDS * 4 },
|
||||||
|
'1D': { val: '1D', seconds: 24 * ONE_HOUR_SECONDS },
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainTechnicalIndicatorTypes = [
|
||||||
|
'MA',
|
||||||
|
'EMA',
|
||||||
|
'SAR',
|
||||||
|
'BOLL',
|
||||||
|
'SMA',
|
||||||
|
'BBI',
|
||||||
|
'TRIX',
|
||||||
|
]
|
||||||
|
const subTechnicalIndicatorTypes = [
|
||||||
|
'VOL',
|
||||||
|
'MACD',
|
||||||
|
'RSI',
|
||||||
|
'KDJ',
|
||||||
|
'OBV',
|
||||||
|
'CCI',
|
||||||
|
'WR',
|
||||||
|
'DMI',
|
||||||
|
'MTM',
|
||||||
|
'EMV',
|
||||||
|
]
|
||||||
|
|
||||||
|
const TradingViewChartKline = () => {
|
||||||
|
const { width } = useViewport()
|
||||||
|
const prevWidth = usePrevious(width)
|
||||||
|
const selectedMarketName = mangoStore((s) => s.selectedMarket.current?.name)
|
||||||
|
const [isTechnicalModalOpen, setIsTechnicalModalOpen] = useState(false)
|
||||||
|
const [mainTechnicalIndicators, setMainTechnicalIndicators] = useState<
|
||||||
|
string[]
|
||||||
|
>([])
|
||||||
|
const [resolution, setResultion] = useState(RES_NAME_TO_RES_VAL['1H'])
|
||||||
|
const [chart, setChart] = useState<klinecharts.Chart | null>(null)
|
||||||
|
const [baseChartQuery, setQuery] = useState<BASE_CHART_QUERY | null>(null)
|
||||||
|
const clearTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
const fetchData = async (baseQuery: BASE_CHART_QUERY, from: number) => {
|
||||||
|
try {
|
||||||
|
const query: CHART_QUERY = {
|
||||||
|
...baseQuery,
|
||||||
|
from,
|
||||||
|
}
|
||||||
|
const response = await axios.get(`${CHART_DATA_FEED}/history`, {
|
||||||
|
params: query,
|
||||||
|
})
|
||||||
|
const newData = response.data as HISTORY
|
||||||
|
const dataSize = newData.t.length
|
||||||
|
const dataList = []
|
||||||
|
for (let i = 0; i < dataSize; i++) {
|
||||||
|
const kLineModel = {
|
||||||
|
open: parseFloat(newData.o[i]),
|
||||||
|
low: parseFloat(newData.l[i]),
|
||||||
|
high: parseFloat(newData.h[i]),
|
||||||
|
close: parseFloat(newData.c[i]),
|
||||||
|
volume: parseFloat(newData.v[i]),
|
||||||
|
timestamp: newData.t[i] * 1000,
|
||||||
|
}
|
||||||
|
dataList.push(kLineModel)
|
||||||
|
}
|
||||||
|
return dataList
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateData(
|
||||||
|
kLineChart: klinecharts.Chart,
|
||||||
|
baseQuery: BASE_CHART_QUERY
|
||||||
|
) {
|
||||||
|
if (clearTimerRef.current) {
|
||||||
|
clearInterval(clearTimerRef.current)
|
||||||
|
}
|
||||||
|
clearTimerRef.current = setTimeout(async () => {
|
||||||
|
if (kLineChart) {
|
||||||
|
const from = baseQuery.to - resolution.seconds
|
||||||
|
const newData = (await fetchData(baseQuery!, from))[0]
|
||||||
|
if (newData) {
|
||||||
|
newData.timestamp += 10000
|
||||||
|
kLineChart.updateData(newData)
|
||||||
|
updateData(kLineChart, baseQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 10000)
|
||||||
|
}
|
||||||
|
const fetchFreshData = async (daysToSubtractFromToday: number) => {
|
||||||
|
const from =
|
||||||
|
Math.floor(Date.now() / 1000) - ONE_DAY_SECONDS * daysToSubtractFromToday
|
||||||
|
const data = await fetchData(baseChartQuery!, from)
|
||||||
|
if (chart) {
|
||||||
|
chart.applyNewData(data)
|
||||||
|
//after we fetch fresh data start to update data every x seconds
|
||||||
|
updateData(chart, baseChartQuery!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (width !== prevWidth && chart) {
|
||||||
|
//wait for event que to be empty
|
||||||
|
//to have current width
|
||||||
|
setTimeout(() => {
|
||||||
|
chart.resize()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}, [width])
|
||||||
|
|
||||||
|
//when base query change we refetch fresh data
|
||||||
|
useEffect(() => {
|
||||||
|
if (chart && baseChartQuery) {
|
||||||
|
fetchFreshData(14)
|
||||||
|
//add callback to fetch more data when zoom out
|
||||||
|
chart.loadMore(() => {
|
||||||
|
fetchFreshData(365)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [baseChartQuery])
|
||||||
|
|
||||||
|
//change query based on market and resolution
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedMarketName && resolution) {
|
||||||
|
setQuery({
|
||||||
|
resolution: resolution.val,
|
||||||
|
symbol: selectedMarketName,
|
||||||
|
to: Math.floor(Date.now() / 1000),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [selectedMarketName, resolution])
|
||||||
|
|
||||||
|
//init chart without data
|
||||||
|
useEffect(() => {
|
||||||
|
const initKline = async () => {
|
||||||
|
const style = getComputedStyle(document.body)
|
||||||
|
const gridColor = style.getPropertyValue('--bkg-3')
|
||||||
|
const kLineChart = init('update-k-line')
|
||||||
|
kLineChart!.setStyleOptions({
|
||||||
|
grid: {
|
||||||
|
show: true,
|
||||||
|
horizontal: {
|
||||||
|
style: 'solid',
|
||||||
|
color: gridColor,
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
style: 'solid',
|
||||||
|
color: gridColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
candle: {
|
||||||
|
tooltip: {
|
||||||
|
labels: ['T: ', 'O: ', 'C: ', 'H: ', 'L: ', 'V: '],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
color: gridColor,
|
||||||
|
size: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
color: gridColor,
|
||||||
|
size: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setChart(kLineChart)
|
||||||
|
}
|
||||||
|
initKline()
|
||||||
|
return () => {
|
||||||
|
dispose('update-k-line')
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex">
|
||||||
|
{Object.keys(RES_NAME_TO_RES_VAL).map((key) => (
|
||||||
|
<div
|
||||||
|
className={`cursor-pointer py-1 px-2 ${
|
||||||
|
resolution === RES_NAME_TO_RES_VAL[key] ? 'text-th-active' : ''
|
||||||
|
}`}
|
||||||
|
key={key}
|
||||||
|
onClick={() => setResultion(RES_NAME_TO_RES_VAL[key])}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div
|
||||||
|
className="cursor-pointer py-1 px-2 "
|
||||||
|
onClick={() => setIsTechnicalModalOpen(true)}
|
||||||
|
>
|
||||||
|
Indicator
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ height: 'calc(100% - 30px)', width: '100%' }}
|
||||||
|
id="update-k-line"
|
||||||
|
className="k-line-chart"
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
isOpen={isTechnicalModalOpen}
|
||||||
|
onClose={() => setIsTechnicalModalOpen(false)}
|
||||||
|
>
|
||||||
|
<div className="flex max-h-96 flex-col overflow-auto text-left">
|
||||||
|
<h2 className="pb-4">Main Indicator</h2>
|
||||||
|
{mainTechnicalIndicatorTypes.map((type) => {
|
||||||
|
return (
|
||||||
|
<IndicatorSwitch
|
||||||
|
key={type}
|
||||||
|
type={type}
|
||||||
|
chart={chart}
|
||||||
|
mainTechnicalIndicators={mainTechnicalIndicators}
|
||||||
|
setMainTechnicalIndicators={setMainTechnicalIndicators}
|
||||||
|
></IndicatorSwitch>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<h2 className="pb-4">Sub Indicator</h2>
|
||||||
|
{subTechnicalIndicatorTypes.map((type) => {
|
||||||
|
return (
|
||||||
|
<IndicatorSwitch
|
||||||
|
key={type}
|
||||||
|
type={type}
|
||||||
|
chart={chart}
|
||||||
|
mainTechnicalIndicators={mainTechnicalIndicators}
|
||||||
|
setMainTechnicalIndicators={setMainTechnicalIndicators}
|
||||||
|
></IndicatorSwitch>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const IndicatorSwitch = ({
|
||||||
|
type,
|
||||||
|
mainTechnicalIndicators,
|
||||||
|
chart,
|
||||||
|
setMainTechnicalIndicators,
|
||||||
|
}: {
|
||||||
|
type: string
|
||||||
|
mainTechnicalIndicators: string[]
|
||||||
|
chart: klinecharts.Chart | null
|
||||||
|
setMainTechnicalIndicators: (indicators: string[]) => void
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex justify-between border-t border-th-bkg-3 p-4 text-th-fgd-4"
|
||||||
|
key={type}
|
||||||
|
>
|
||||||
|
{type}
|
||||||
|
<Switch
|
||||||
|
checked={!!mainTechnicalIndicators.find((x) => x === type)}
|
||||||
|
onChange={(check) => {
|
||||||
|
let newInidicatorsArray = [...mainTechnicalIndicators]
|
||||||
|
if (check) {
|
||||||
|
newInidicatorsArray.push(type)
|
||||||
|
chart?.createTechnicalIndicator(type, true, {
|
||||||
|
id: 'candle_pane',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
newInidicatorsArray = newInidicatorsArray.filter((x) => x !== type)
|
||||||
|
chart?.removeTechnicalIndicator('candle_pane', type)
|
||||||
|
}
|
||||||
|
setMainTechnicalIndicators(newInidicatorsArray)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TradingViewChartKline
|
|
@ -33,7 +33,7 @@
|
||||||
"howler": "^2.2.3",
|
"howler": "^2.2.3",
|
||||||
"html-react-parser": "^3.0.4",
|
"html-react-parser": "^3.0.4",
|
||||||
"immer": "^9.0.12",
|
"immer": "^9.0.12",
|
||||||
"klinecharts": "^8.6.3",
|
"klinecharts": "8.5.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"next": "^13.0.0",
|
"next": "^13.0.0",
|
||||||
"next-i18next": "^11.1.1",
|
"next-i18next": "^11.1.1",
|
||||||
|
|
|
@ -4435,10 +4435,10 @@ keyvaluestorage-interface@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz#13ebdf71f5284ad54be94bd1ad9ed79adad515ff"
|
resolved "https://registry.yarnpkg.com/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz#13ebdf71f5284ad54be94bd1ad9ed79adad515ff"
|
||||||
integrity sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==
|
integrity sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==
|
||||||
|
|
||||||
klinecharts@^8.6.3:
|
klinecharts@8.5.0:
|
||||||
version "8.6.3"
|
version "8.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/klinecharts/-/klinecharts-8.6.3.tgz#9ff2c40e31d86ca0600abc5fb8bf546c61daf130"
|
resolved "https://registry.yarnpkg.com/klinecharts/-/klinecharts-8.5.0.tgz#48337c79c76100738e307267cd4d3c43578ce195"
|
||||||
integrity sha512-hGDtWiMNywEDneZFmt+vZ6tOYutCDWV5FPBcXcn7L8kGwe73Q5yJayk8UzP9pIQSBWyxswWIySKh/BVFA6GhuQ==
|
integrity sha512-7F34LO3N1F/2qK4xlDSFQ4UXFE1Skvo754lXHfOdI4KtkV0Fo65lJzdpShTCdB6r1BzWxIfBOHufzRjTPfrLHA==
|
||||||
|
|
||||||
language-subtag-registry@~0.3.2:
|
language-subtag-registry@~0.3.2:
|
||||||
version "0.3.21"
|
version "0.3.21"
|
||||||
|
|
Loading…
Reference in New Issue