technical indc
This commit is contained in:
parent
72844038db
commit
2ec44f1742
|
@ -23,7 +23,7 @@ function Modal({
|
|||
className="relative z-50 overflow-y-auto"
|
||||
>
|
||||
<div
|
||||
className={`fixed inset-0 backdrop-blur-sm backdrop-brightness-75 ${
|
||||
className={`fixed inset-0 bg-black opacity-50 ${
|
||||
disableOutsideClose ? 'pointer-events-none' : ''
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
|
|
|
@ -16,7 +16,11 @@ import { useWallet } from '@solana/wallet-adapter-react'
|
|||
import TradeOnboardingTour from '@components/tours/TradeOnboardingTour'
|
||||
import FavoriteMarketsBar from './FavoriteMarketsBar'
|
||||
|
||||
const TradingViewChart = dynamic(() => import('./TradingViewChart'), {
|
||||
//const TradingViewChart = dynamic(() => import('./TradingViewChart'), {
|
||||
// ssr: false,
|
||||
//})
|
||||
|
||||
const TradingViewChartKline = dynamic(() => import('./TradingViewChartKline'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
|
@ -179,7 +183,7 @@ const TradeAdvancedPage = () => {
|
|||
className="h-full border border-x-0 border-th-bkg-3"
|
||||
>
|
||||
<div className={`relative h-full overflow-auto`}>
|
||||
<TradingViewChart />
|
||||
<TradingViewChartKline />
|
||||
</div>
|
||||
</div>
|
||||
<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 { CHART_DATA_FEED } from 'utils/constants'
|
||||
import { init, dispose } from 'klinecharts'
|
||||
import axios from 'axios'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { CHART_DATA_FEED, DEFAULT_MARKET_NAME } from 'utils/constants'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { COLORS } from 'styles/colors'
|
||||
|
||||
const ONE_HOUR_MINS = 60
|
||||
|
||||
const RES_NAME_TO_RES_VAL: {
|
||||
[key: string]: {
|
||||
val: string
|
||||
seconds: number
|
||||
}
|
||||
} = {
|
||||
'1m': { val: '1', seconds: 60 },
|
||||
'5m': { val: '5', seconds: 5 * 60 },
|
||||
'30m': {
|
||||
val: `${ONE_HOUR_MINS / 2}`,
|
||||
seconds: (ONE_HOUR_MINS / 2) * 60,
|
||||
},
|
||||
'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[]
|
||||
export interface ChartContainerProps {
|
||||
container: ChartingLibraryWidgetOptions['container']
|
||||
symbol: ChartingLibraryWidgetOptions['symbol']
|
||||
interval: ChartingLibraryWidgetOptions['interval']
|
||||
datafeedUrl: string
|
||||
libraryPath: ChartingLibraryWidgetOptions['library_path']
|
||||
chartsStorageUrl: ChartingLibraryWidgetOptions['charts_storage_url']
|
||||
chartsStorageApiVersion: ChartingLibraryWidgetOptions['charts_storage_api_version']
|
||||
clientId: ChartingLibraryWidgetOptions['client_id']
|
||||
userId: ChartingLibraryWidgetOptions['user_id']
|
||||
fullscreen: ChartingLibraryWidgetOptions['fullscreen']
|
||||
autosize: ChartingLibraryWidgetOptions['autosize']
|
||||
studiesOverrides: ChartingLibraryWidgetOptions['studies_overrides']
|
||||
theme: string
|
||||
}
|
||||
|
||||
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 [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) => {
|
||||
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
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
const defaultProps = useMemo(
|
||||
() => ({
|
||||
symbol: DEFAULT_MARKET_NAME,
|
||||
interval: '60' as ResolutionString,
|
||||
theme: 'Dark',
|
||||
container: 'tv_chart_container',
|
||||
datafeedUrl: CHART_DATA_FEED,
|
||||
libraryPath: '/charting_library/',
|
||||
fullscreen: false,
|
||||
autosize: true,
|
||||
studiesOverrides: {
|
||||
'volume.volume.color.0': COLORS.DOWN[theme],
|
||||
'volume.volume.color.1': COLORS.UP[theme],
|
||||
'volume.precision': 4,
|
||||
},
|
||||
}),
|
||||
[theme]
|
||||
)
|
||||
|
||||
const tvWidgetRef = useRef<IChartingLibraryWidget | null>(null)
|
||||
|
||||
let chartStyleOverrides = {
|
||||
'paneProperties.background': 'rgba(0,0,0,0)',
|
||||
'paneProperties.backgroundType': 'solid',
|
||||
'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(
|
||||
kLineChart: klinecharts.Chart,
|
||||
baseQuery: BASE_CHART_QUERY
|
||||
) {
|
||||
if (clearTimerRef.current) {
|
||||
clearInterval(clearTimerRef.current)
|
||||
const mainSeriesProperties = [
|
||||
'candleStyle',
|
||||
'hollowCandleStyle',
|
||||
'haStyle',
|
||||
'barStyle',
|
||||
]
|
||||
|
||||
mainSeriesProperties.forEach((prop) => {
|
||||
chartStyleOverrides = {
|
||||
...chartStyleOverrides,
|
||||
[`mainSeriesProperties.${prop}.barColorsOnPrevClose`]: true,
|
||||
[`mainSeriesProperties.${prop}.drawWick`]: true,
|
||||
[`mainSeriesProperties.${prop}.drawBorder`]: true,
|
||||
[`mainSeriesProperties.${prop}.upColor`]: COLORS.UP[theme],
|
||||
[`mainSeriesProperties.${prop}.downColor`]: COLORS.DOWN[theme],
|
||||
[`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],
|
||||
}
|
||||
clearTimerRef.current = setTimeout(async () => {
|
||||
if (kLineChart) {
|
||||
const from = baseQuery.to - resolution.seconds
|
||||
const newData = (await fetchData(baseQuery!, from))[0]
|
||||
newData.timestamp += 10000
|
||||
kLineChart.updateData(newData)
|
||||
updateData(kLineChart, baseQuery)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (tvWidgetRef.current && chartReady && selectedMarketName) {
|
||||
tvWidgetRef.current.setSymbol(
|
||||
selectedMarketName!,
|
||||
tvWidgetRef.current.activeChart().resolution(),
|
||||
() => {
|
||||
return
|
||||
}
|
||||
)
|
||||
}
|
||||
}, [selectedMarketName, chartReady])
|
||||
|
||||
useEffect(() => {
|
||||
if (window) {
|
||||
const widgetOptions: ChartingLibraryWidgetOptions = {
|
||||
// debug: true,
|
||||
symbol: defaultProps.symbol,
|
||||
// BEWARE: no trailing slash is expected in feed URL
|
||||
// tslint:disable-next-line:no-any
|
||||
datafeed: new (window as any).Datafeeds.UDFCompatibleDatafeed(
|
||||
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,
|
||||
|
||||
...chartStyleOverrides,
|
||||
},
|
||||
}
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fetchFreshData = async () => {
|
||||
const from = Math.floor(Date.now() / 1000) - 60 * 60 * 24 * 365
|
||||
const data = await fetchData(baseChartQuery!, from)
|
||||
chart?.applyNewData(data)
|
||||
updateData(chart!, baseChartQuery!)
|
||||
}
|
||||
if (chart && baseChartQuery) {
|
||||
fetchFreshData()
|
||||
}
|
||||
}, [baseChartQuery])
|
||||
const tvWidget = new widget(widgetOptions)
|
||||
tvWidgetRef.current = tvWidget
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedMarketName && resolution) {
|
||||
setQuery({
|
||||
resolution: resolution.val,
|
||||
symbol: selectedMarketName,
|
||||
to: Math.floor(Date.now() / 1000),
|
||||
tvWidgetRef.current.onChartReady(function () {
|
||||
setChartReady(true)
|
||||
})
|
||||
//eslint-disable-next-line
|
||||
}
|
||||
}, [selectedMarketName, resolution])
|
||||
}, [theme, isMobile, defaultProps])
|
||||
|
||||
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: {
|
||||
color: gridColor,
|
||||
},
|
||||
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)
|
||||
}
|
||||
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"
|
||||
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"
|
||||
/>
|
||||
</>
|
||||
<div id={defaultProps.container as string} className="tradingview-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",
|
||||
"html-react-parser": "^3.0.4",
|
||||
"immer": "^9.0.12",
|
||||
"klinecharts": "^8.6.3",
|
||||
"klinecharts": "8.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "^13.0.0",
|
||||
"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"
|
||||
integrity sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==
|
||||
|
||||
klinecharts@^8.6.3:
|
||||
version "8.6.3"
|
||||
resolved "https://registry.yarnpkg.com/klinecharts/-/klinecharts-8.6.3.tgz#9ff2c40e31d86ca0600abc5fb8bf546c61daf130"
|
||||
integrity sha512-hGDtWiMNywEDneZFmt+vZ6tOYutCDWV5FPBcXcn7L8kGwe73Q5yJayk8UzP9pIQSBWyxswWIySKh/BVFA6GhuQ==
|
||||
klinecharts@8.5.0:
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/klinecharts/-/klinecharts-8.5.0.tgz#48337c79c76100738e307267cd4d3c43578ce195"
|
||||
integrity sha512-7F34LO3N1F/2qK4xlDSFQ4UXFE1Skvo754lXHfOdI4KtkV0Fo65lJzdpShTCdB6r1BzWxIfBOHufzRjTPfrLHA==
|
||||
|
||||
language-subtag-registry@~0.3.2:
|
||||
version "0.3.21"
|
||||
|
|
Loading…
Reference in New Issue