diff --git a/package-lock.json b/package-lock.json index d178a974..7a8d0e66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "vitest": "^0.28.4" }, "peerDependencies": { - "klinecharts": ">=9.0.0" + "klinecharts": "^10.0.0-alpha9" } }, "node_modules/@adobe/css-tools": { @@ -5224,9 +5224,9 @@ } }, "node_modules/klinecharts": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/klinecharts/-/klinecharts-9.1.1.tgz", - "integrity": "sha512-OnHeStm2zFf6iTPMPB720+gpD8psXmajZfd7eqZN+Z5FxnYF7sSsPE02J84JTJ0AVrXYOdLJShIdYkr7aNN5LA==", + "version": "10.0.0-alpha9", + "resolved": "https://registry.npmjs.org/klinecharts/-/klinecharts-10.0.0-alpha9.tgz", + "integrity": "sha512-cpI8x2TE7qLr36WKLFbD8chg5Un3i+o1JKy/H0IMxSh5YqhTvfth7PMB5okY4a37HfyIa53n26l/NT44usk6Fg==", "peer": true }, "node_modules/klona": { @@ -11421,9 +11421,9 @@ } }, "klinecharts": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/klinecharts/-/klinecharts-9.1.1.tgz", - "integrity": "sha512-OnHeStm2zFf6iTPMPB720+gpD8psXmajZfd7eqZN+Z5FxnYF7sSsPE02J84JTJ0AVrXYOdLJShIdYkr7aNN5LA==", + "version": "10.0.0-alpha9", + "resolved": "https://registry.npmjs.org/klinecharts/-/klinecharts-10.0.0-alpha9.tgz", + "integrity": "sha512-cpI8x2TE7qLr36WKLFbD8chg5Un3i+o1JKy/H0IMxSh5YqhTvfth7PMB5okY4a37HfyIa53n26l/NT44usk6Fg==", "peer": true }, "klona": { diff --git a/package.json b/package.json index 71098f39..35311006 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,6 @@ "solid-js": "^1.6.11" }, "peerDependencies": { - "klinecharts": ">=9.0.0" + "klinecharts": "^10.0.0-alpha9" } } diff --git a/src/Chart.ts b/src/Chart.ts new file mode 100644 index 00000000..0540673d --- /dev/null +++ b/src/Chart.ts @@ -0,0 +1,328 @@ +import { + ActionCallback, ActionType, BarSpace, Bounding, ConvertFilter, Coordinate, Crosshair, DataLoader, DecimalFold, + DeepPartial, DomPosition, Formatter, Indicator, IndicatorCreate, IndicatorFilter, init, KLineData, Nullable, + Options, Overlay, OverlayCreate, OverlayFilter, PaneOptions, Period, PickPartial, PickRequired, Point, Styles, + SymbolInfo, ThousandsSeparator, VisibleRange, Chart as KLineChart +} from "klinecharts"; +import { ProChart, UndoOptions } from "./types/types"; +import { OrderOverlay, OrderOverlayCreate } from "./types/overlayTypes"; +import { isArray } from "lodash"; + +export default class Chart implements ProChart +{ + private _chart: KLineChart; + private _charts: Map = new Map(); + + public id: string; + + get chart (): KLineChart { + return this._chart; + } + + get charts (): KLineChart[] { + return Array.from(this._charts.values()); + } + + constructor (ds: string | HTMLElement, options?: Options | undefined) { + const chart = init(ds, options); + + if (!chart) { + throw new Error('Failed to initialize chart'); + } + this._chart = chart; + this.id = chart.id; + } + + static init (ds: string | HTMLElement, options?: Options | undefined): Chart { + return new Chart(ds, options); + } + + /** + * Base Proxy Methods + */ + + getDom (paneId?: string, position?: DomPosition): Nullable { + return this._chart.getDom(paneId, position); + } + getSize (paneId?: string, position?: DomPosition): Nullable { + return this._chart.getSize(paneId, position); + } + createIndicator (value: string | IndicatorCreate, isStack?: boolean, paneOptions?: PaneOptions): Nullable { + return this._chart.createIndicator(value, isStack, paneOptions); + } + getIndicators (filter?: IndicatorFilter): Indicator[] { + return this._chart.getIndicators(filter); + } + createOverlay (value: string | OverlayCreate | Array): Nullable | Array> { + return this._chart.createOverlay(value); + } + getOverlays (filter?: OverlayFilter): Overlay[] { + return this._chart.getOverlays(filter); + } + setPaneOptions (options: PaneOptions): void { + return this._chart.setPaneOptions(options); + } + getPaneOptions (id?: string): Nullable | PaneOptions[] { + return this._chart.getPaneOptions(id); + } + scrollByDistance (distance: number, animationDuration?: number): void { + return this._chart.scrollByDistance(distance, animationDuration); + } + scrollToRealTime (animationDuration?: number): void { + return this._chart.scrollToRealTime(animationDuration); + } + scrollToDataIndex (dataIndex: number, animationDuration?: number): void { + return this._chart.scrollToDataIndex(dataIndex, animationDuration); + } + scrollToTimestamp (timestamp: number, animationDuration?: number): void { + return this._chart.scrollToTimestamp(timestamp, animationDuration); + } + zoomAtCoordinate (scale: number, coordinate?: Coordinate, animationDuration?: number): void { + return this._chart.zoomAtCoordinate(scale, coordinate, animationDuration); + } + zoomAtDataIndex (scale: number, dataIndex: number, animationDuration?: number): void { + return this._chart.zoomAtDataIndex(scale, dataIndex, animationDuration); + } + zoomAtTimestamp (scale: number, timestamp: number, animationDuration?: number): void { + return this._chart.zoomAtTimestamp(scale, timestamp, animationDuration); + } + convertToPixel (points: Partial | Array>, filter?: ConvertFilter): Partial | Array> { + return this._chart.convertToPixel(points, filter); + } + convertFromPixel (coordinates: Array>, filter?: ConvertFilter): Partial | Array> { + return this._chart.convertFromPixel(coordinates, filter); + } + executeAction (type: ActionType, data: Crosshair): void { + return this._chart.executeAction(type, data); + } + subscribeAction (type: ActionType, callback: ActionCallback): void { + return this._chart.subscribeAction(type, callback); + } + unsubscribeAction (type: ActionType, callback?: ActionCallback): void { + return this._chart.unsubscribeAction(type, callback); + } + getConvertPictureUrl (includeOverlay?: boolean, type?: "png" | "jpeg" | "bmp", backgroundColor?: string): string { + return this._chart.getConvertPictureUrl(includeOverlay, type, backgroundColor); + } + resize (): void { + return this._chart.resize(); + } + + + /** + * Store Proxy Methods + */ + setStyles (value: string | DeepPartial): void + { + return this._chart.setStyles(value); + } + getStyles (): Styles + { + return this._chart.getStyles(); + } + setFormatter (formatter: Partial): void + { + return this._chart.setFormatter(formatter); + } + getFormatter (): Formatter + { + return this._chart.getFormatter(); + } + setLocale (locale: string): void + { + return this._chart.setLocale(locale); + } + getLocale (): string + { + return this._chart.getLocale(); + } + setTimezone (timezone: string): void + { + return this._chart.setTimezone(timezone); + } + getTimezone (): string + { + return this._chart.getTimezone(); + } + setThousandsSeparator (thousandsSeparator: Partial): void + { + return this._chart.setThousandsSeparator(thousandsSeparator); + } + getThousandsSeparator (): ThousandsSeparator + { + return this._chart.getThousandsSeparator(); + } + setDecimalFold (decimalFold: Partial): void + { + return this._chart.setDecimalFold(decimalFold); + } + getDecimalFold (): DecimalFold + { + return this._chart.getDecimalFold(); + } + //Still deciding about using type from klinecharts or our own type + setSymbol (symbol: PickPartial): void + { + return this._chart.setSymbol(symbol); + } + //Still deciding about using type from klinecharts or our own type + getSymbol (): Nullable + { + return this._chart.getSymbol(); + } + //Still deciding about using type from klinecharts or our own type + setPeriod (period: Period): void + { + return this._chart.setPeriod(period); + } + //Still deciding about using type from klinecharts or our own type + getPeriod (): Nullable + { + return this._chart.getPeriod(); + } + getDataList (): KLineData[] + { + return this._chart.getDataList(); + } + setOffsetRightDistance (distance: number): void + { + return this._chart.setOffsetRightDistance(distance); + } + getOffsetRightDistance (): number + { + return this._chart.getOffsetRightDistance(); + } + setMaxOffsetLeftDistance (distance: number): void + { + return this._chart.setMaxOffsetLeftDistance(distance); + } + setMaxOffsetRightDistance (distance: number): void + { + return this._chart.setMaxOffsetRightDistance(distance); + } + setLeftMinVisibleBarCount (barCount: number): void + { + return this._chart.setLeftMinVisibleBarCount(barCount); + } + setRightMinVisibleBarCount (barCount: number): void + { + return this._chart.setRightMinVisibleBarCount(barCount); + } + setBarSpace (space: number): void + { + return this._chart.setBarSpace(space); + } + getBarSpace (): BarSpace + { + return this._chart.getBarSpace(); + } + getVisibleRange (): VisibleRange + { + return this._chart.getVisibleRange(); + } + setDataLoader (dataLoader: DataLoader): void + { + return this._chart.setDataLoader(dataLoader); + } + overrideIndicator (override: IndicatorCreate): boolean + { + return this._chart.overrideIndicator(override); + } + removeIndicator (filter?: IndicatorFilter): boolean + { + return this._chart.removeIndicator(filter); + } + overrideOverlay (override: Partial): boolean + { + return this._chart.overrideOverlay(override); + } + removeOverlay (filter?: OverlayFilter): boolean + { + return this._chart.removeOverlay(filter); + } + setZoomEnabled (enabled: boolean): void + { + return this._chart.setZoomEnabled(enabled); + } + isZoomEnabled (): boolean + { + return this._chart.isZoomEnabled(); + } + setScrollEnabled (enabled: boolean): void + { + return this._chart.setScrollEnabled(enabled); + } + isScrollEnabled (): boolean + { + return this._chart.isScrollEnabled(); + } + resetData (): void + { + return this._chart.resetData(); + } + + /** + * Custom methods + */ + + setActiveChart (id: string) { + const chart = this._charts.get(id); + if (chart) { + this._chart = chart; + } + + return this + } + + chartById (id: string): KLineChart | undefined { + return this._charts.get(id); + } + + getOverlay (filter?: PickRequired): Overlay[] { + return this._chart.getOverlays(filter); + } + + createOrderLine (options?: UndoOptions): Nullable { + const dataList = this._chart.getDataList() + const overlays = this._chart.createOverlay({ + name: 'orderLine', + paneId: 'candle_pane', + points: [{ + timestamp: dataList[dataList.length - 40].timestamp, + value: dataList[dataList.length - 40].close + }] + }); + if (!overlays) { + return null + } + + ///@ts-expect-error + return this._chart.getOverlays({ id: overlays as string, paneId: 'candle_pane' })[0] as Nullable; + } + + createOrderLines (nums: number, options?: UndoOptions): Array> { + const points: Array> = [] + const dataList = this._chart.getDataList() + const step = Math.floor(dataList.length / (nums + 1)) + for (let i = 1; i <= nums; i++) { + points.push({ + timestamp: dataList[step * i].timestamp, + value: dataList[step * i].close + }) + } + + const values: OverlayCreate = { + name: 'orderLine', + paneId: 'candle_pane', + points: points + } + const overlays = this._chart.createOverlay(values); + + if (!overlays || (isArray(overlays) && overlays.length === 0)) { + return []; + } + + ///@ts-expect-error + return (overlays as Array).map(o => this._chart.getOverlays({ id: o!, paneId: 'canlde_pane' })[0]) as Array>; + } +} \ No newline at end of file diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index 47696350..15701bdd 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -15,8 +15,9 @@ import { createSignal, createEffect, onMount, Show, onCleanup, startTransition, Component } from 'solid-js' import { - init, dispose, utils, Nullable, Chart, OverlayMode, Styles, - TooltipIconPosition, ActionType, PaneOptions, Indicator, DomPosition, FormatDateType + init, dispose, utils, Nullable, OverlayMode, Styles, + PaneOptions, Indicator, FormatDateParams, TooltipFeatureStyle, + IndicatorTooltipData } from 'klinecharts' import lodashSet from 'lodash/set' @@ -31,10 +32,13 @@ import { import { translateTimezone } from './widget/timezone-modal/data' -import { SymbolInfo, Period, ChartProOptions, ChartPro } from './types' +import { SymbolInfo, Period, ChartProOptions, ChartPro, ProChart } from './types/types' +import ChartDataLoader from './DataLoader' +import Chart from './Chart' -export interface ChartProComponentProps extends Required> { +export interface ChartProComponentProps extends Required> { ref: (chart: ChartPro) => void + dataloader: ChartDataLoader } interface PrevSymbolPeriod { @@ -42,43 +46,46 @@ interface PrevSymbolPeriod { period: Period } -function createIndicator (widget: Nullable, indicatorName: string, isStack?: boolean, paneOptions?: PaneOptions): Nullable { +function createIndicator (widget: ProChart, indicatorName: string, isStack?: boolean, paneOptions?: PaneOptions): Nullable { if (indicatorName === 'VOL') { - paneOptions = { gap: { bottom: 2 }, ...paneOptions } + paneOptions = { axis: { gap: { bottom: 2 } }, ...paneOptions } } - return widget?.createIndicator({ + const indi = widget.createIndicator({ name: indicatorName, - // @ts-expect-error - createTooltipDataSource: ({ indicator, defaultStyles }) => { - const icons = [] - if (indicator.visible) { - icons.push(defaultStyles.tooltip.icons[1]) - icons.push(defaultStyles.tooltip.icons[2]) - icons.push(defaultStyles.tooltip.icons[3]) - } else { - icons.push(defaultStyles.tooltip.icons[0]) - icons.push(defaultStyles.tooltip.icons[2]) - icons.push(defaultStyles.tooltip.icons[3]) + createTooltipDataSource: (param): IndicatorTooltipData => { + const indiStyles = param.chart.getStyles().indicator + const features = indiStyles.tooltip.features + const icons: TooltipFeatureStyle[] = [] + + icons.push(param.indicator.visible ? features[1] : features[0]) + icons.push(features[2]) + icons.push(features[3]) + + return { + name: `${indicatorName}_${indi}`, + calcParamsText: indicatorName, + features: icons, + legends: [] } - return { icons } - } - }, isStack, paneOptions) ?? null + }}, isStack, paneOptions) ?? null + + return indi } +export const [loadingVisible, setLoadingVisible] = createSignal(false) +export const [symbol, setSymbol] = createSignal>(null) +export const [period, setPeriod] = createSignal>(null) +export const [instanceapi, setInstanceapi] = createSignal>(null) + const ChartProComponent: Component = props => { let widgetRef: HTMLDivElement | undefined = undefined - let widget: Nullable = null let priceUnitDom: HTMLElement - let loading = false - const [theme, setTheme] = createSignal(props.theme) const [styles, setStyles] = createSignal(props.styles) const [locale, setLocale] = createSignal(props.locale) - const [symbol, setSymbol] = createSignal(props.symbol) - const [period, setPeriod] = createSignal(props.period) const [indicatorModalVisible, setIndicatorModalVisible] = createSignal(false) const [mainIndicators, setMainIndicators] = createSignal([...(props.mainIndicators!)]) const [subIndicators, setSubIndicators] = createSignal({}) @@ -95,124 +102,76 @@ const ChartProComponent: Component = props => { const [symbolSearchModalVisible, setSymbolSearchModalVisible] = createSignal(false) - const [loadingVisible, setLoadingVisible] = createSignal(false) - const [indicatorSettingModalParams, setIndicatorSettingModalParams] = createSignal({ visible: false, indicatorName: '', paneId: '', calcParams: [] as Array }) + setPeriod(props.period) + setSymbol(props.symbol) props.ref({ setTheme, getTheme: () => theme(), setStyles, - getStyles: () => widget!.getStyles(), + getStyles: () => instanceapi()!.getStyles(), setLocale, getLocale: () => locale(), setTimezone: (timezone: string) => { setTimezone({ key: timezone, text: translateTimezone(props.timezone, locale()) }) }, getTimezone: () => timezone().key, setSymbol, - getSymbol: () => symbol(), + getSymbol: () => symbol()!, setPeriod, - getPeriod: () => period() + getPeriod: () => period()!, + getInstanceApi: () => instanceapi(), + resize: () => instanceapi()?.resize(), + dispose: () => {} }) const documentResize = () => { - widget?.resize() - } - - const adjustFromTo = (period: Period, toTimestamp: number, count: number) => { - let to = toTimestamp - let from = to - switch (period.timespan) { - case 'minute': { - to = to - (to % (60 * 1000)) - from = to - count * period.multiplier * 60 * 1000 - break - } - case 'hour': { - to = to - (to % (60 * 60 * 1000)) - from = to - count * period.multiplier * 60 * 60 * 1000 - break - } - case 'day': { - to = to - (to % (60 * 60 * 1000)) - from = to - count * period.multiplier * 24 * 60 * 60 * 1000 - break - } - case 'week': { - const date = new Date(to) - const week = date.getDay() - const dif = week === 0 ? 6 : week - 1 - to = to - dif * 60 * 60 * 24 - const newDate = new Date(to) - to = new Date(`${newDate.getFullYear()}-${newDate.getMonth() + 1}-${newDate.getDate()}`).getTime() - from = count * period.multiplier * 7 * 24 * 60 * 60 * 1000 - break - } - case 'month': { - const date = new Date(to) - const year = date.getFullYear() - const month = date.getMonth() + 1 - to = new Date(`${year}-${month}-01`).getTime() - from = count * period.multiplier * 30 * 24 * 60 * 60 * 1000 - const fromDate = new Date(from) - from = new Date(`${fromDate.getFullYear()}-${fromDate.getMonth() + 1}-01`).getTime() - break - } - case 'year': { - const date = new Date(to) - const year = date.getFullYear() - to = new Date(`${year}-01-01`).getTime() - from = count * period.multiplier * 365 * 24 * 60 * 60 * 1000 - const fromDate = new Date(from) - from = new Date(`${fromDate.getFullYear()}-01-01`).getTime() - break - } - } - return [from, to] + instanceapi()?.resize() } onMount(() => { window.addEventListener('resize', documentResize) - widget = init(widgetRef!, { - customApi: { - formatDate: (dateTimeFormat: Intl.DateTimeFormat, timestamp, format: string, type: FormatDateType) => { - const p = period() - switch (p.timespan) { + setInstanceapi(Chart.init(widgetRef!, { + formatter: { + formatDate: (params: FormatDateParams) => { + const p = period()! + switch (p.type) { case 'minute': { - if (type === FormatDateType.XAxis) { - return utils.formatDate(dateTimeFormat, timestamp, 'HH:mm') + if (params.type === 'xAxis') { + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'HH:mm') } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD HH:mm') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD HH:mm') } case 'hour': { - if (type === FormatDateType.XAxis) { - return utils.formatDate(dateTimeFormat, timestamp, 'MM-DD HH:mm') + if (params.type === 'xAxis') { + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'MM-DD HH:mm') } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD HH:mm') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD HH:mm') } case 'day': - case 'week': return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD') + case 'week': return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD') case 'month': { - if (type === FormatDateType.XAxis) { - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM') + if (params.type === 'xAxis') { + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM') } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD') } case 'year': { - if (type === FormatDateType.XAxis) { - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY') + if (params.type === 'xAxis') { + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY') } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD') } } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD HH:mm') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD HH:mm') } } - }) + })) - if (widget) { - const watermarkContainer = widget.getDom('candle_pane', DomPosition.Main) + if (instanceapi()) { + console.info('ChartPro widget initialized') + const watermarkContainer = instanceapi()!.getDom('candle_pane', 'main') if (watermarkContainer) { let watermark = document.createElement('div') watermark.className = 'klinecharts-pro-watermark' @@ -225,71 +184,95 @@ const ChartProComponent: Component = props => { watermarkContainer.appendChild(watermark) } - const priceUnitContainer = widget.getDom('candle_pane', DomPosition.YAxis) + const priceUnitContainer = instanceapi()!.getDom('candle_pane', 'yAxis') priceUnitDom = document.createElement('span') priceUnitDom.className = 'klinecharts-pro-price-unit' priceUnitContainer?.appendChild(priceUnitDom) - } - mainIndicators().forEach(indicator => { - createIndicator(widget, indicator, true, { id: 'candle_pane' }) - }) - const subIndicatorMap = {} - props.subIndicators!.forEach(indicator => { - const paneId = createIndicator(widget, indicator, true) - if (paneId) { - // @ts-expect-error - subIndicatorMap[indicator] = paneId - } - }) - setSubIndicators(subIndicatorMap) - widget?.loadMore(timestamp => { - loading = true - const get = async () => { - const p = period() - const [to] = adjustFromTo(p, timestamp!, 1) - const [from] = adjustFromTo(p, to, 500) - const kLineDataList = await props.datafeed.getHistoryKLineData(symbol(), p, from, to) - widget?.applyMoreData(kLineDataList, kLineDataList.length > 0) - loading = false - } - get() - }) - widget?.subscribeAction(ActionType.OnTooltipIconClick, (data) => { - if (data.indicatorName) { - switch (data.iconId) { - case 'visible': { - widget?.overrideIndicator({ name: data.indicatorName, visible: true }, data.paneId) - break - } - case 'invisible': { - widget?.overrideIndicator({ name: data.indicatorName, visible: false }, data.paneId) - break - } - case 'setting': { - const indicator = widget?.getIndicatorByPaneId(data.paneId, data.indicatorName) as Indicator - setIndicatorSettingModalParams({ - visible: true, indicatorName: data.indicatorName, paneId: data.paneId, calcParams: indicator.calcParams - }) - break - } - case 'close': { - if (data.paneId === 'candle_pane') { - const newMainIndicators = [...mainIndicators()] - widget?.removeIndicator('candle_pane', data.indicatorName) - newMainIndicators.splice(newMainIndicators.indexOf(data.indicatorName), 1) - setMainIndicators(newMainIndicators) - } else { - const newIndicators = { ...subIndicators() } - widget?.removeIndicator(data.paneId, data.indicatorName) - // @ts-expect-error - delete newIndicators[data.indicatorName] - setSubIndicators(newIndicators) + instanceapi()?.subscribeAction('onCrosshairFeatureClick', (data) => { + console.info('onCrosshairFeatureClick', data) + }) + + instanceapi()?.subscribeAction('onIndicatorTooltipFeatureClick', (data) => { + console.info('onIndicatorTooltipFeatureClick', data) + const _data = data as { paneId: string, feature: TooltipFeatureStyle, indicator: Indicator } + // if (_data.indicatorName) { + switch (_data.feature.id) { + case 'visible': { + instanceapi()?.overrideIndicator({ name: _data.indicator.name, visible: true, paneId: _data.paneId }) + break + } + case 'invisible': { + instanceapi()?.overrideIndicator({ name: _data.indicator.name, visible: false, paneId: _data.paneId }) + break + } + case 'setting': { + const indicator = instanceapi()?.getIndicators({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }).at(0) + if (!indicator) return + setIndicatorSettingModalParams({ + visible: true, indicatorName: _data.indicator.name, paneId: _data.paneId, calcParams: indicator.calcParams + }) + break + } + case 'close': { + if (_data.paneId === 'candle_pane') { + const newMainIndicators = [...mainIndicators()] + instanceapi()?.removeIndicator({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }) + newMainIndicators.splice(newMainIndicators.indexOf(_data.indicator.name), 1) + setMainIndicators(newMainIndicators) + } else { + const newIndicators = { ...subIndicators() } + instanceapi()?.removeIndicator({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }) + // @ts-expect-error + delete newIndicators[_data.indicator.name] + setSubIndicators(newIndicators) + } } } - } + // } + }) + + instanceapi()?.subscribeAction('onCandleTooltipFeatureClick', (data) => { + console.info('onCandleTooltipFeatureClick', data) + }) + + instanceapi()?.subscribeAction('onZoom', (data) => { + console.info('chart zoomed: ', data) + }) + + instanceapi()?.subscribeAction('onCrosshairChange', (data) => { + console.info('crosshair change: ', data) + }) + + const s = symbol() + if (s?.priceCurrency) { + priceUnitDom.innerHTML = s?.priceCurrency.toLocaleUpperCase() + priceUnitDom.style.display = 'flex' + } else { + priceUnitDom.style.display = 'none' } - }) + instanceapi()?.setSymbol({ ticker: s!.ticker, pricePrecision: s?.pricePrecision ?? 2, volumePrecision: s?.volumePrecision ?? 0 }) + instanceapi()?.setPeriod(period()!) + instanceapi()?.setDataLoader(props.dataloader) + } + + const w = instanceapi() + + if (w) { + mainIndicators().forEach(indicator => { + if (w) + createIndicator(w, indicator, true, { id: 'candle_pane' }) + }) + const subIndicatorMap = {} + props.subIndicators!.forEach(indicator => { + const paneId = createIndicator(w, indicator, true) + if (paneId) { + // @ts-expect-error + subIndicatorMap[indicator] = paneId + } + }) + setSubIndicators(subIndicatorMap) + } }) onCleanup(() => { @@ -297,63 +280,61 @@ const ChartProComponent: Component = props => { dispose(widgetRef!) }) - createEffect(() => { - const s = symbol() - if (s?.priceCurrency) { - priceUnitDom.innerHTML = s?.priceCurrency.toLocaleUpperCase() - priceUnitDom.style.display = 'flex' - } else { - priceUnitDom.style.display = 'none' - } - widget?.setPriceVolumePrecision(s?.pricePrecision ?? 2, s?.volumePrecision ?? 0) - }) - createEffect((prev?: PrevSymbolPeriod) => { - if (!loading) { - if (prev) { - props.datafeed.unsubscribe(prev.symbol, prev.period) - } + console.info('symbol or period changed effect', symbol(), period(), prev) + + if (!props.dataloader.loading) { + console.info('setLoadingVisible false by effect') const s = symbol() const p = period() - loading = true - setLoadingVisible(true) - const get = async () => { - const [from, to] = adjustFromTo(p, new Date().getTime(), 500) - const kLineDataList = await props.datafeed.getHistoryKLineData(s, p, from, to) - widget?.applyNewData(kLineDataList, kLineDataList.length > 0) - props.datafeed.subscribe(s, p, data => { - widget?.updateData(data) - }) - loading = false - setLoadingVisible(false) + + if (prev?.period.span !== p!.span && prev?.period.type !== p!.type) { + console.info('period changed: set period', p) + instanceapi()?.setPeriod(p!) } - get() - return { symbol: s, period: p } + if (prev?.symbol?.ticker !== s!.ticker) + console.info('ticker changed: set symbol', s) + instanceapi()?.setSymbol({ + ticker: s!.ticker, + pricePrecision: s!.pricePrecision, + volumePrecision: s!.volumePrecision, + }) + + onCleanup(() => { + // Optional cleanup logic before re-run + }) + + return { symbol: s!, period: p! } } + console.info('props.dataloader.loading is true, skip setLoadingVisible false') + return prev }) createEffect(() => { const t = theme() - widget?.setStyles(t) + instanceapi()?.setStyles(t) const color = t === 'dark' ? '#929AA5' : '#76808F' - widget?.setStyles({ + instanceapi()?.setStyles({ indicator: { tooltip: { - icons: [ + features: [ { id: 'visible', - position: TooltipIconPosition.Middle, + position: 'middle', marginLeft: 8, - marginTop: 7, + marginTop: 1, marginRight: 0, marginBottom: 0, paddingLeft: 0, paddingTop: 0, paddingRight: 0, paddingBottom: 0, - icon: '\ue903', - fontFamily: 'icomoon', + type: 'icon_font', + content: { + code: '\ue903', + family: 'icomoon', + }, size: 14, color: color, activeColor: color, @@ -362,17 +343,20 @@ const ChartProComponent: Component = props => { }, { id: 'invisible', - position: TooltipIconPosition.Middle, + position: 'middle', marginLeft: 8, - marginTop: 7, + marginTop: 1, marginRight: 0, marginBottom: 0, paddingLeft: 0, paddingTop: 0, paddingRight: 0, paddingBottom: 0, - icon: '\ue901', - fontFamily: 'icomoon', + type: 'icon_font', + content: { + code: '\ue901', + family: 'icomoon', + }, size: 14, color: color, activeColor: color, @@ -381,17 +365,20 @@ const ChartProComponent: Component = props => { }, { id: 'setting', - position: TooltipIconPosition.Middle, + position: 'middle', marginLeft: 6, - marginTop: 7, + marginTop: 1, marginBottom: 0, marginRight: 0, paddingLeft: 0, paddingTop: 0, paddingRight: 0, paddingBottom: 0, - icon: '\ue902', - fontFamily: 'icomoon', + type: 'icon_font', + content: { + code: '\ue902', + family: 'icomoon', + }, size: 14, color: color, activeColor: color, @@ -400,17 +387,20 @@ const ChartProComponent: Component = props => { }, { id: 'close', - position: TooltipIconPosition.Middle, + position: 'middle', marginLeft: 6, - marginTop: 7, + marginTop: 1, marginRight: 0, marginBottom: 0, paddingLeft: 0, paddingTop: 0, paddingRight: 0, paddingBottom: 0, - icon: '\ue900', - fontFamily: 'icomoon', + type: 'icon_font', + content: { + code: '\ue900', + family: 'icomoon', + }, size: 14, color: color, activeColor: color, @@ -424,17 +414,17 @@ const ChartProComponent: Component = props => { }) createEffect(() => { - widget?.setLocale(locale()) + instanceapi()?.setLocale(locale()) }) createEffect(() => { - widget?.setTimezone(timezone().key) + instanceapi()?.setTimezone(timezone().key) }) createEffect(() => { if (styles()) { - widget?.setStyles(styles()) - setWidgetDefaultStyles(lodashClone(widget!.getStyles())) + instanceapi()?.setStyles(styles()) + setWidgetDefaultStyles(lodashClone(instanceapi()!.getStyles())) } }) @@ -444,7 +434,7 @@ const ChartProComponent: Component = props => { { setSymbol(symbol) }} onClose={() => { setSymbolSearchModalVisible(false) }}/> @@ -457,25 +447,26 @@ const ChartProComponent: Component = props => { onMainIndicatorChange={data => { const newMainIndicators = [...mainIndicators()] if (data.added) { - createIndicator(widget, data.name, true, { id: 'candle_pane' }) + createIndicator(instanceapi()!, data.name, true, { id: 'candle_pane' }) newMainIndicators.push(data.name) } else { - widget?.removeIndicator('candle_pane', data.name) + instanceapi()?.removeIndicator({name: data.name, paneId: 'candle_pane', id: data.id ?? undefined}) newMainIndicators.splice(newMainIndicators.indexOf(data.name), 1) } setMainIndicators(newMainIndicators) }} onSubIndicatorChange={data => { + console.info('onSubIndicatorChange', data) const newSubIndicators = { ...subIndicators() } if (data.added) { - const paneId = createIndicator(widget, data.name) - if (paneId) { + const id = createIndicator(instanceapi()!, data.name) + if (id) { // @ts-expect-error - newSubIndicators[data.name] = paneId + newSubIndicators[data.name] = id } } else { - if (data.paneId) { - widget?.removeIndicator(data.paneId, data.name) + if (data.id) { + instanceapi()?.removeIndicator({name: data.name, id: data.id}) // @ts-expect-error delete newSubIndicators[data.name] } @@ -494,10 +485,10 @@ const ChartProComponent: Component = props => { { setSettingModalVisible(false) }} onChange={style => { - widget?.setStyles(style) + instanceapi()?.setStyles(style) }} onRestoreDefault={(options: SelectDataSourceItem[]) => { const style = {} @@ -505,7 +496,7 @@ const ChartProComponent: Component = props => { const key = option.key lodashSet(style, key, utils.formatValue(widgetDefaultStyles(), key)) }) - widget?.setStyles(style) + instanceapi()?.setStyles(style) }} /> @@ -523,20 +514,20 @@ const ChartProComponent: Component = props => { onClose={() => { setIndicatorSettingModalParams({ visible: false, indicatorName: '', paneId: '', calcParams: [] }) }} onConfirm={(params)=> { const modalParams = indicatorSettingModalParams() - widget?.overrideIndicator({ name: modalParams.indicatorName, calcParams: params }, modalParams.paneId) + instanceapi()?.overrideIndicator({ name: modalParams.indicatorName, calcParams: params, paneId: modalParams.paneId }) }} /> { try { await startTransition(() => setDrawingBarVisible(!drawingBarVisible())) - widget?.resize() + instanceapi()?.resize() } catch (e) {} }} onSymbolClick={() => { setSymbolSearchModalVisible(!symbolSearchModalVisible()) }} @@ -545,8 +536,8 @@ const ChartProComponent: Component = props => { onTimezoneClick={() => { setTimezoneModalVisible((visible => !visible)) }} onSettingClick={() => { setSettingModalVisible((visible => !visible)) }} onScreenshotClick={() => { - if (widget) { - const url = widget.getConvertPictureUrl(true, 'jpeg', props.theme === 'dark' ? '#151517' : '#ffffff') + if (instanceapi()) { + const url = instanceapi()!.getConvertPictureUrl(true, 'jpeg', props.theme === 'dark' ? '#151517' : '#ffffff') setScreenshotUrl(url) } }} @@ -559,11 +550,11 @@ const ChartProComponent: Component = props => { { widget?.createOverlay(overlay) }} - onModeChange={mode => { widget?.overrideOverlay({ mode: mode as OverlayMode }) }} - onLockChange={lock => { widget?.overrideOverlay({ lock }) }} - onVisibleChange={visible => { widget?.overrideOverlay({ visible }) }} - onRemoveClick={(groupId) => { widget?.removeOverlay({ groupId }) }}/> + onDrawingItemClick={overlay => { instanceapi()?.createOverlay(overlay) }} + onModeChange={mode => { instanceapi()?.overrideOverlay({ mode: mode as OverlayMode }) }} + onLockChange={lock => { instanceapi()?.overrideOverlay({ lock }) }} + onVisibleChange={visible => { instanceapi()?.overrideOverlay({ visible }) }} + onRemoveClick={(groupId) => { instanceapi()?.removeOverlay({ groupId }) }}/>
{ + console.info('ChartDataLoader getBars', params); + const { type, timestamp: _t, symbol: _s, period: _p, callback } = params; + if (type === 'backward' || type === 'update') { + console.info('getBars: type is backward or update (no forward support yet)'); + callback([], false); + return; + } + this._loading = true + setLoadingVisible(true) + const timestamp = _t ?? new Date().getTime() + const get = async () => { + const p = period()! + const s =symbol()! + const [to] = this.adjustFromTo(p, timestamp!, 1) + const [from] = this.adjustFromTo(p, to, 500) + const kLineDataList = await this._datafeed.getHistoryKLineData(s, p, from, to) + callback(kLineDataList, kLineDataList.length > 0) + this._loading = false + setLoadingVisible(false) + } + await get(); + } + + subscribeBar (params: DataLoaderSubscribeBarParams): void { + console.info('ChartDataLoader subscribeBar', params); + const { symbol: _s, period: _p, callback } = params; + this._datafeed.subscribe(symbol()!, period()!, callback) + } + + unsubscribeBar (params: DataLoaderUnsubscribeBarParams): void { + console.info('ChartDataLoader unsubscribeBar', params); + const { symbol: _s, period: _p } = params; + this._datafeed.unsubscribe(symbol()!, period()!) + } + + searchSymbols(search?: string): Promise { + return this._datafeed.searchSymbols(search) + } + + get loading(): boolean { + return this._loading; + } + + set loading(value: boolean) { + this._loading = value; + } + + adjustFromTo(period: Period, toTimestamp: number, count: number) { + let to = toTimestamp + let from = to + + switch (period.type) { + case 'minute': + to -= to % (60 * 1000) + from = to - count * period.span * 60 * 1000 + break + + case 'hour': + to -= to % (60 * 60 * 1000) + from = to - count * period.span * 60 * 60 * 1000 + break + + case 'day': + to -= to % (24 * 60 * 60 * 1000) + from = to - count * period.span * 24 * 60 * 60 * 1000 + break + + case 'week': { + const date = new Date(to) + const day = date.getDay() || 7 // Sunday -> 7 + date.setHours(0, 0, 0, 0) + to = date.getTime() - (day - 1) * 24 * 60 * 60 * 1000 + from = to - count * period.span * 7 * 24 * 60 * 60 * 1000 + break + } + + case 'month': { + const date = new Date(to) + to = new Date(date.getFullYear(), date.getMonth(), 1).getTime() + const _from = new Date(to - count * period.span * 30 * 24 * 60 * 60 * 1000) + from = new Date(_from.getFullYear(), _from.getMonth(), 1).getTime() + break + } + + case 'year': { + const date = new Date(to) + to = new Date(date.getFullYear(), 0, 1).getTime() + const _from = new Date(to - count * period.span * 365 * 24 * 60 * 60 * 1000) + from = new Date(_from.getFullYear(), 0, 1).getTime() + break + } + } + + return [from, to] + } +} diff --git a/src/DefaultDatafeed.ts b/src/DefaultDatafeed.ts index 1267a7ee..27862ce5 100644 --- a/src/DefaultDatafeed.ts +++ b/src/DefaultDatafeed.ts @@ -14,7 +14,7 @@ import { KLineData } from 'klinecharts' -import { Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback } from './types' +import { Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback } from './types/types' export default class DefaultDatafeed implements Datafeed { @@ -44,7 +44,7 @@ export default class DefaultDatafeed implements Datafeed { } async getHistoryKLineData (symbol: SymbolInfo, period: Period, from: number, to: number): Promise { - const response = await fetch(`https://api.polygon.io/v2/aggs/ticker/${symbol.ticker}/range/${period.multiplier}/${period.timespan}/${from}/${to}?apiKey=${this._apiKey}`) + const response = await fetch(`https://api.polygon.io/v2/aggs/ticker/${symbol.ticker}/range/${period.span}/${period.type}/${from}/${to}?apiKey=${this._apiKey}`) const result = await response.json() return await (result.results || []).map((data: any) => ({ timestamp: data.t, diff --git a/src/DefaultOrderController.ts b/src/DefaultOrderController.ts new file mode 100644 index 00000000..23a08b3f --- /dev/null +++ b/src/DefaultOrderController.ts @@ -0,0 +1,159 @@ +import { OrderInfo, OrderModalType, OrderModifyInfo, OrderPlacedCallback, OrderResource, OrderType } from "./types/types"; + +type MethodType = 'POST'|'GET'|'DELETE'|'PUT' + +export default class DefaultOrderController implements OrderResource { + constructor (_apiurl: string, _apikey: string, _accound_id: number|string) { + this.apiurl = _apiurl + this.apikey = _apikey + this.testsesson_id = _accound_id + } + + private apikey: string + private apiurl: string + private testsesson_id: number|string + + async retrieveOrder(order_id: number): Promise { + const response = await this.makeFetchWithAuthAndBody('GET', `${this.apiurl}/positions/${order_id}`) + const resp = await response?.json() + return { + entryPoint: resp.data.entrypoint, + stopLoss: resp.data.stoploss, + takeProfit: resp.data.takeprofit, + lotSize: resp.data.lotsize, + pl: resp.data.pl, + accountId: resp.data.account_id, + orderId: resp.data.id, + entryTime: resp.data.entrytime, + exitTime: resp.data.exittime, + exitPoint: resp.data.exitpoint, + action: resp.data.action + } + } + + async retrieveOrders(action?: OrderType, session_id?: number): Promise { + try { + const response = await this.makeFetchWithAuthAndBody('GET', `${this.apiurl}/positions`) + const result = await response!.json() + return (result.data || []).map((data: any) => ({ + entryPoint: data.entrypoint, + stopLoss: data.stoploss, + takeProfit: data.takeprofit, + lotSize: data.lotsize, + pl: data.pl, + accountId: data.account_id, + orderId: data.id, + entryTime: data.entrytime, + exitTime: data.exittime, + exitPoint: data.exitpoint, + action: data.action + })) + } catch (err) { + return [] + } + } + + async openOrder(action: OrderType, lot_size: number, entry_price: number, stop_loss?: number, take_profit?: number): Promise { + const response = await this.makeFetchWithAuthAndBody('POST', `${this.apiurl}/positions`, { + account_id: this.testsesson_id, + action: action, + entrypoint: entry_price, + stoploss: stop_loss, + takeprofit: take_profit, + }) + + const data = await response?.json() + return { + orderId: data.id, + accountId: data.account_id, + action: data.action, + entryPoint: data.entrypoint, + exitPoint: data.exitpoint, + stopLoss: data.stoploss, + takeProfit: data.takeprofit, + lotSize: data.lotsize, + pips: data.pips, + pl: data.pl, + entryTime: data.entrytime, + exitTime: data.exittime, + exitType: data.exittype, + partials: data.partials + } + } + + async closeOrder(order_id: number, lotsize?: number): Promise { + try { + const response = await this.makeFetchWithAuthAndBody('PUT', `${this.apiurl}/positions/${order_id}`) + const data = await response?.json() + return data + } catch (err) { + return null + } + } + + async modifyOrder(order: OrderModifyInfo): Promise { + const response = await this.makeFetchWithAuthAndBody('PUT', `${this.apiurl}/positions/${order.id}`, order) + const data = await response?.json() + return { + orderId: data.id, + accountId: data.account_id, + action: data.action, + entryPoint: data.entrypoint, + exitPoint: data.exitpoint, + stopLoss: data.stoploss, + takeProfit: data.takeprofit, + lotSize: data.lotsize, + pips: data.pips, + pl: data.pl, + entryTime: data.entrytime, + exitTime: data.exittime, + exitType: data.exittype, + partials: data.partials + } + } + + async unsetSlOrTP(order_id: string|number, slortp: 'sl'|'tp'): Promise { + const response = await this.makeFetchWithAuthAndBody('PUT', `${this.apiurl}/positions/${order_id}/unset/${slortp}`) + const data = await response?.json() + return { + orderId: data.id, + accountId: data.account_id, + action: data.action, + entryPoint: data.entrypoint, + exitPoint: data.exitpoint, + stopLoss: data.stoploss, + takeProfit: data.takeprofit, + lotSize: data.lotsize, + pips: data.pips, + pl: data.pl, + entryTime: data.entrytime, + exitTime: data.exittime, + exitType: data.exittype, + partials: data.partials + } + } + + launchOrderModal(type: OrderModalType, callback: OrderPlacedCallback, order?: OrderModifyInfo): void { + return ; + } + + private async makeFetchWithAuthAndBody (method: MethodType, endpoint: string, params?: object): Promise { + const options: RequestInit = { + method: method, + credentials: "include", + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${this.apikey}` + }, + body: params ? JSON.stringify(params) : null + }; + try { + const res = await fetch(`${endpoint}`, options) + return res + } catch (err:any) { + alert(err.message ?? 'An error occured') + return null + } + } +} \ No newline at end of file diff --git a/src/DummyOrderController.ts b/src/DummyOrderController.ts new file mode 100644 index 00000000..5c9c0094 --- /dev/null +++ b/src/DummyOrderController.ts @@ -0,0 +1,137 @@ +import { Nullable } from "klinecharts"; +import { OrderInfo, OrderModalType, OrderModifyInfo, OrderPlacedCallback, OrderResource, OrderType } from "./types/types"; + +export default class DummyOrderController implements OrderResource { + constructor (storage_name = 'dummy_order_controller') { + this.storage_name = storage_name + } + + private storage_name: string + + async retrieveOrder(order_id: number): Promise> { + return this.retrieveOrderFromLs(order_id) + } + + async retrieveOrders(action?: OrderType, account_id?: number): Promise { + let orders = this.retrieveOrdersFromLs() + + if (action) + orders = orders.filter((o, i) => o.action == action) + + if (account_id) + orders = orders.filter(o => o.accountId == account_id) + + return orders + } + + async openOrder(action: OrderType, size: number, entry: number, stop_loss?: number, take_profit?: number): Promise { + const orders = this.retrieveOrdersFromLs() + + const order = { + orderId: orders.length ? orders.at(orders.length - 1)!.orderId + 1 : 0, + action: action, + entryPoint: entry, + exitPoint: undefined, + stopLoss: stop_loss, + takeProfit: take_profit, + lotSize: size, + pips: 0, + pl: 0, + entryTime: new Date().getTime().toString(), + exitTime: undefined, + exitType: undefined, + partials: undefined + } + this.storeOrder(order, orders) + return order + } + + async closeOrder(order_id: number, lotsize?: number): Promise { + try { + const orders = this.retrieveOrdersFromLs() + const index = orders.findIndex(o => o.orderId == order_id) + if (index < 0) + return null + + const order = orders[index] + + if (!lotsize || lotsize > order.lotSize) + lotsize = order.lotSize + + order.lotSize -= lotsize + this.storeOrder(order, orders, index) + return order + } catch (err) { + return null + } + } + + async modifyOrder(_order: OrderModifyInfo): Promise { + const orders = this.retrieveOrdersFromLs() + const index = orders.findIndex(o => o.orderId == _order.id) + if (index < 0) + return null + + const order = orders[index] + //TODO: add order modify validations to prevent performing some actions on already activated order + + order.action = _order.action ?? order.action + order.entryPoint = _order.entrypoint ?? order.entryPoint + order.exitPoint = _order.exitpoint ?? order.exitPoint + order.stopLoss = _order.stoploss ?? order.stopLoss + order.takeProfit = _order.takeprofit ?? order.takeProfit + order.lotSize = _order.lotsize ?? order.lotSize + order.pips = _order.pips ?? order.pips + order.pl = _order.pl ?? order.pl + order.entryTime = ['buy', 'sell'].includes(order.action) ? new Date().getTime().toString() : order.entryTime + order.exitTime = order.exitPoint && order.exitTime ? new Date().getTime().toString() : order.exitTime + + this.storeOrder(order, orders, index) + return order + } + + async unsetSlOrTP(order_id: string|number, slortp: 'sl'|'tp'): Promise { + const orders = this.retrieveOrdersFromLs() + const index = orders.findIndex(o => o.orderId == order_id) + if (index < 0) + return null + + const order = orders[index] + if (slortp == 'sl') + order.stopLoss = undefined + else + order.takeProfit = undefined + + this.storeOrder(order, orders, index) + return order + } + + launchOrderModal(type: OrderModalType, callback: OrderPlacedCallback, order?: OrderModifyInfo): void { + return ; + } + + private retrieveOrdersFromLs (): OrderInfo[] { + return JSON.parse(localStorage.getItem(this.storage_name) ?? '[]') as OrderInfo[] + } + + private retrieveOrderFromLs(id: number): OrderInfo|null + { + return this.retrieveOrdersFromLs().find(o => o.orderId == id) ?? null + } + + private storeOrder(order: OrderInfo, orders?: OrderInfo[], index?: number) + { + if (!orders) + orders = this.retrieveOrdersFromLs() + + if (index && index > orders.length) + throw new Error('storeOrder: index cannot be greater than total order length') + + if (index) + orders[index] = order + else + orders.push(order) + + localStorage.setItem(this.storage_name, JSON.stringify(orders)) + } +} \ No newline at end of file diff --git a/src/KLineChartPro.tsx b/src/KLineChartPro.tsx index bac9e893..3c2ea98c 100644 --- a/src/KLineChartPro.tsx +++ b/src/KLineChartPro.tsx @@ -14,11 +14,12 @@ import { render } from 'solid-js/web' -import { utils, Nullable, DeepPartial, Styles } from 'klinecharts' +import { utils, Nullable, DeepPartial, Styles, dispose } from 'klinecharts' -import ChartProComponent from './ChartProComponent' +import ChartProComponent, { instanceapi } from './ChartProComponent' -import { SymbolInfo, Period, ChartPro, ChartProOptions } from './types' +import { SymbolInfo, Period, ChartPro, ChartProOptions, ProChart } from './types/types' +import ChartDataLoader from './DataLoader' const Logo = ( ' + entity + '' + html; + } + var icons = { + 'icon-icon-close': '', + 'icon-icon-invisible': '', + 'icon-icon-setting': '', + 'icon-icon-visible': '', + 'icon-lock': '', + 'icon-unlocked': '', + 'icon-bin': '', + 'icon-plus': '', + 'icon-minus': '', + 'icon-cancel-circle': '', + 'icon-blocked': '', + 'icon-cross': '', + 'icon-checkmark': '', + 'icon-checkmark2': '', + 'icon-enter': '', + 'icon-exit': '', + 'icon-circle-down': '', + 'icon-circle-left': '', + '0': 0 + }, + els = document.getElementsByTagName('*'), + i, c, el; + for (i = 0; ; i += 1) { + el = els[i]; + if(!el) { + break; + } + c = el.className; + c = c.match(/icon-[^\s'"]+/); + if (c && icons[c[0]]) { + addIcon(el, icons[c[0]]); + } + } +}()); diff --git a/src/iconfonts/style.css b/src/iconfonts/style.css index cd63227f..81e61cd9 100644 --- a/src/iconfonts/style.css +++ b/src/iconfonts/style.css @@ -1,14 +1,15 @@ @font-face { font-family: 'icomoon'; - src: url('fonts/icomoon.eot?f4efml'); - src: url('fonts/icomoon.eot?f4efml#iefix') format('embedded-opentype'), - url('fonts/icomoon.ttf?f4efml') format('truetype'), - url('fonts/icomoon.woff?f4efml') format('woff'), - url('fonts/icomoon.svg?f4efml#icomoon') format('svg'); + src: url('fonts/icomoon.eot?mr6pu2'); + src: url('fonts/icomoon.eot?mr6pu2#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?mr6pu2') format('truetype'), + url('fonts/icomoon.woff?mr6pu2') format('woff'), + url('fonts/icomoon.svg?mr6pu2#icomoon') format('svg'); font-weight: normal; font-style: normal; font-display: block; } +@import url("./ie7/ie7.css"); [class^="icon-"], [class*=" icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ @@ -41,3 +42,59 @@ content: "\e903"; color: #fff; } +.icon-lock:before { + content: "\e98f"; + color: #fff; +} +.icon-unlocked:before { + content: "\e990"; + color: #fff; +} +.icon-bin:before { + content: "\e9ac"; + color: #fff; +} +.icon-plus:before { + content: "\ea0a"; + color: #fff; +} +.icon-minus:before { + content: "\ea0b"; + color: #fff; +} +.icon-cancel-circle:before { + content: "\ea0d"; + color: #fff; +} +.icon-blocked:before { + content: "\ea0e"; + color: #fff; +} +.icon-cross:before { + content: "\ea0f"; + color: #fff; +} +.icon-checkmark:before { + content: "\ea10"; + color: #fff; +} +.icon-checkmark2:before { + content: "\ea11"; + color: #fff; +} +.icon-enter:before { + content: "\ea13"; + color: #fff; +} +.icon-exit:before { + content: "\ea14"; + color: #fff; +} +.icon-circle-down:before { + content: "\ea43"; + color: #fff; +} +.icon-circle-left:before { + content: "\ea44"; + color: #fff; +} diff --git a/src/index.ts b/src/index.ts index bcb178a9..9ff0512d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,11 +17,16 @@ import { registerOverlay } from 'klinecharts' import overlays from './extension' import DefaultDatafeed from './DefaultDatafeed' +import DefaultOrderController from './DefaultOrderController' +import DummyOrderController from './DummyOrderController' import KLineChartPro from './KLineChartPro' import { load } from './i18n' -import { Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback, ChartProOptions, ChartPro } from './types' +import { OrderType, ExitType, OrderModalType, OrderInfo, OrderModifyInfo, OrderResource, Datafeed, + SymbolInfo, Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType, + ProChart +} from './types/types' import './index.less' @@ -29,10 +34,13 @@ overlays.forEach(o => { registerOverlay(o) }) export { DefaultDatafeed, + DefaultOrderController, + DummyOrderController, KLineChartPro, load as loadLocales } export type { - Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback, ChartProOptions, ChartPro -} + OrderInfo, OrderModifyInfo, OrderType, ExitType, OrderModalType, OrderResource, Datafeed, SymbolInfo, + Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType, ProChart +} \ No newline at end of file diff --git a/src/store/overlayStyle/positionStyleStore.ts b/src/store/overlayStyle/positionStyleStore.ts new file mode 100644 index 00000000..f000027f --- /dev/null +++ b/src/store/overlayStyle/positionStyleStore.ts @@ -0,0 +1,197 @@ +import { LineStyle, StateTextStyle } from 'klinecharts'; +import { createSignal } from 'solid-js'; + +export const [buyStyle, setBuyStyle] = createSignal<{ lineStyle: LineStyle, labelStyle: StateTextStyle}>({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#00698b', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'stroke_fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + borderDashedValue: [0,0], + borderRadius: 3, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#00698b', + show: true, + } +}) + +export const [buyLimitStyle, setBuyLimitStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#00698b', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#00698b' + } +}) + +export const [buyStopStyle, setBuyStopStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#00698b', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#00698b' + } +}) + +export const [sellStyle, setSellStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#fb7b50', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#fb7b50' + } +}) + +export const [sellLimitStyle, setSellLimitStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#fb7b50', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#fb7b50' + } +}) + +export const [sellStopStyle, setSellStopStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#fb7b50', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#fb7b50' + } +}) + +export const [takeProfitStyle, setTakeProfitStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#00698b', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#00698b' + } +}) + +export const [stopLossStyle, setStopLossStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#fb7b50', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#fb7b50' + } +}) \ No newline at end of file diff --git a/src/store/tickStore.ts b/src/store/tickStore.ts new file mode 100644 index 00000000..ad5e0026 --- /dev/null +++ b/src/store/tickStore.ts @@ -0,0 +1,5 @@ +import { KLineData, Nullable } from 'klinecharts'; +import { createSignal } from 'solid-js'; + +export const [currenttick, setCurrentTick] = createSignal>(null); +export const [tickTimestamp, setTickTimestamp] = createSignal() diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 8f1b97c9..00000000 --- a/src/types.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { KLineData, Styles, DeepPartial } from 'klinecharts' - -export interface SymbolInfo { - ticker: string - name?: string - shortName?: string - exchange?: string - market?: string - pricePrecision?: number - volumePrecision?: number - priceCurrency?: string - type?: string - logo?: string -} - -export interface Period { - multiplier: number - timespan: string - text: string -} - -export type DatafeedSubscribeCallback = (data: KLineData) => void - -export interface Datafeed { - searchSymbols (search?: string): Promise - getHistoryKLineData (symbol: SymbolInfo, period: Period, from: number, to: number): Promise - subscribe (symbol: SymbolInfo, period: Period, callback: DatafeedSubscribeCallback): void - unsubscribe (symbol: SymbolInfo, period: Period): void -} - -export interface ChartProOptions { - container: string | HTMLElement - styles?: DeepPartial - watermark?: string | Node - theme?: string - locale?: string - drawingBarVisible?: boolean - symbol: SymbolInfo - period: Period - periods?: Period[] - timezone?: string - mainIndicators?: string[] - subIndicators?: string[] - datafeed: Datafeed -} - -export interface ChartPro { - setTheme(theme: string): void - getTheme(): string - setStyles(styles: DeepPartial): void - getStyles(): Styles - setLocale(locale: string): void - getLocale(): string - setTimezone(timezone: string): void - getTimezone(): string - setSymbol(symbol: SymbolInfo): void - getSymbol(): SymbolInfo - setPeriod(period: Period): void - getPeriod(): Period -} diff --git a/src/types/overlayTypes.ts b/src/types/overlayTypes.ts new file mode 100644 index 00000000..18e437cf --- /dev/null +++ b/src/types/overlayTypes.ts @@ -0,0 +1,126 @@ +import { LineType, Overlay, OverlayEvent, OverlayTemplate, PickPartial, StateLineStyle } from "klinecharts" +import { FontWeights } from "./types" + +export interface OverlayEventListenerParams { + params: unknown, + callback: (params: unknown, event?: OverlayEvent) => void +} + +export interface OverlayProperties { + text?: string + color?: string + lineWidth?: number + lineStyle?: LineType + lineLength?: number + lineDashedValue?: number[] + tooltip?: string + backgroundColor?: string + borderColor?: string + borderWidth?: number +} + +export interface OrderLineProperties { + price?: number + text?: string + quantity?: number|string + modifyTooltip?: string + tooltip?: string + marginRight: number + + lineColor?: string + lineWidth?: number + lineStyle?: LineType + lineDashedValue?: number[] + + bodySize?: number + bodyWeight?: number | string + bodyFont?: string + bodyBackgroundColor?: string + bodyBorderColor?: string + bodyTextColor?: string + bodyPaddingLeft?: number + bodyPaddingRight?: number + bodyPaddingTop?: number + bodyPaddingBottom?: number + isBodyVisible: boolean + + quantitySize?: number + quantityWeight?: number | string + quantityFont?: string + quantityColor?: string + quantityBackgroundColor?: string + quantityBorderColor?: string + quantityPaddingLeft?: number + quantityPaddingRight?: number + quantityPaddingTop?: number + quantityPaddingBottom?: number + isQuantityVisible: boolean + + cancelButtonSize?: number + cancelButtonWeight?: number | string + cancelButtonIconColor?: string + cancelButtonBackgroundColor?: string + cancelButtonBorderColor?: string + cancelButtonPaddingLeft?: number + cancelButtonPaddingRight?: number + cancelButtonPaddingTop?: number + cancelButtonPaddingBottom?: number + isCancelButtonVisible: boolean + + borderStyle?: LineType, + borderSize?: number, + borderDashedValue?: number[], + borderRadius?: number + + onMoveStart?: OverlayEventListenerParams + onMove?: OverlayEventListenerParams + onMoveEnd?: OverlayEventListenerParams + onCancel?: OverlayEventListenerParams + onModify?: OverlayEventListenerParams +} + +type OrderOverlayAttributes = { + setPrice: (price: number) => OrderOverlay + setText: (text: string) => OrderOverlay + setQuantity: (quantity: string) => OrderOverlay + setModifyTooltip: (tooltip: string) => OrderOverlay + setTooltip: (tooltip: string) => OrderOverlay + + setLineColor: (color: string) => OrderOverlay + setLineWidth: (width: number) => OrderOverlay + setLineStyle: (style: LineType) => OrderOverlay + setLineLength: (length: number) => OrderOverlay + setLineDashedValue: (dashedValue: number[]) => OrderOverlay + + setBodyFont: (font: string) => OrderOverlay + setBodyFontWeight: (weight: FontWeights | number) => OrderOverlay + setBodyTextColor: (color: string) => OrderOverlay + setBodyBackgroundColor: (color: string) => OrderOverlay + setBodyBorderColor: (color: string) => OrderOverlay + + setQuantityFont: (font: string) => OrderOverlay + setQuantityFontWeight: (weight: FontWeights | number) => OrderOverlay + setQuantityColor: (color: string) => OrderOverlay + setQuantityBackgroundColor: (color: string) => OrderOverlay + setQuantityBorderColor: (color: string) => OrderOverlay + + setCancelButtonIconColor: (color: string) => OrderOverlay + setCancelButtonFontWeight: (weight: FontWeights | number) => OrderOverlay + setCancelButtonBackgroundColor: (color: string) => OrderOverlay + setCancelButtonBorderColor: (color: string) => OrderOverlay + + setBorderStyle: (style: LineType) => OrderOverlay + setBorderSize: (size: number) => OrderOverlay + setBorderDashedValue: (dashedValue: number[]) => OrderOverlay + setBorderRadius: (radius: number) => OrderOverlay + + onMoveStart: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay + onMove: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay + onMoveEnd: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay + onCancel: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay + onModify: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay +} + +export type OrderOverlay = Pick & OrderOverlayAttributes + +export interface OrderOverlayCreate extends OverlayTemplate, OrderOverlayAttributes {} \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts new file mode 100644 index 00000000..1f092cc1 --- /dev/null +++ b/src/types/types.ts @@ -0,0 +1,212 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { KLineData, Styles, DeepPartial, Nullable, Chart, DataLoader, Period as DefaultPeriod, IndicatorCreate, PaneOptions, OverlayCreate, FigureCreate, PickRequired, OverlayFilter, Overlay } from 'klinecharts' +import { OrderOverlay } from './overlayTypes' + + +export type FontWeights = 'thin' | 'extra-light' | 'light' | 'normal' | 'medium' | 'semi-bold' | 'bold' | 'extra-bold' | 'black' +export type OrderType = 'buy'|'sell'|'buystop'|'buylimit'|'sellstop'|'selllimit' +export type OrderModalType = 'placeorder'|'modifyorder'|'closepartial' +export type ExitType = 'stoploss'|'takeprofit'|'breakeven'|'manualclose'|'cancel' + +export type DatafeedSubscribeCallback = (data: KLineData, timestamp?: number) => void +export type OrderPlacedCallback = (data: OrderInfo|null) => void //this should be called when a user has successfully placed an order from consumer project side + +export interface UndoOptions { + /** + * A boolean flag. Controls if undo should be disabled. + */ + disableUndo?: boolean +} + +export interface SymbolInfo { + ticker: string + name?: string + shortName?: string + exchange?: string + market?: string + pricePrecision?: number + volumePrecision?: number + priceCurrency?: string + dollarPerPip?: number + type?: string + logo?: string +} + +export interface OrderInfo { + orderId: number + action: OrderType + entryPoint: number + exitPoint?: number + stopLoss?: number + takeProfit?: number + lotSize: number + pips?: number + pl?: number + entryTime?: string + exitTime?: string + exitType?: ExitType + partials?: string + accountId?: number +} + +export interface OrderModifyInfo { + id: number + action?: OrderType + entrypoint?: number + exitpoint?: number + stoploss?: number + takeprofit?: number + lotsize?: number + pips?: number + pl?: number + exittime?: string + exittype?: ExitType + partials?: string +} + +export interface Period extends DefaultPeriod { + text: string +} + +export interface ProChart extends Chart { + chart: Chart + charts: Array + + setActiveChart (id: string): void + chartById (id: string): Chart | undefined + getOverlay (filter?: PickRequired): Overlay[] + createOrderLine (options?: UndoOptions): Nullable + createOrderLines (nums: number, options?: UndoOptions): Array> +} + +type IndicatorsType = { + value?: IndicatorCreate, + isStack?: boolean, + paneOptions?: PaneOptions +} + +type OverlaysType = { + value?: OverlayCreate, + paneId: string +} + +type FiguresType = { + value?: string|FigureCreate, + ctx: CanvasRenderingContext2D +} + +type OrderStyleType = { + lineStyle?: { + style?: string, + size?: number, + color?: string, + dashedValue?: number[] + }, + labelStyle?: { + style?: string, + size?: number, + family?: string, + weight?: string, + paddingLeft?: number, + paddingRight?: number, + paddingBottom?: number, + paddingTop?: number, + borderStyle?: string, + borderSize?: number, + color?: string, + borderColor?: string, + backgroundColor?: string + } +} + +export type OrderStylesType = { + buyStyle?: OrderStyleType, + buyLimitStyle?: OrderStyleType, + buyStopStyle?: OrderStyleType, + sellStyle?: OrderStyleType, + sellLimitStyle?: OrderStyleType, + sellStopStyle?: OrderStyleType, + stopLossStyle?: OrderStyleType, + takeProfitStyle?: OrderStyleType +} + +export interface ChartObjType { + styleObj?: DeepPartial + overlays?: OverlaysType[] + figures?: FiguresType[] + indicators?: IndicatorsType[] + orderStyles?: OrderStylesType +} + +export interface Datafeed { + searchSymbols (search?: string): Promise + getHistoryKLineData (symbol: SymbolInfo, period: Period, from: number, to: number): Promise + subscribe (symbol: SymbolInfo, period: Period, callback: DatafeedSubscribeCallback): void + unsubscribe (symbol: SymbolInfo, period: Period): void +} + +export interface ChartDataLoaderType extends DataLoader { + searchSymbols (search?: string): Promise + loading: boolean +} + +export interface OrderResource { + retrieveOrder (order_id: number): Promise + retrieveOrders (action?: OrderType, account_id?: number|string): Promise + openOrder (action: OrderType, lot_size: number, entry_price: number, stop_loss?: number, take_profit?: number): Promise + closeOrder (order_id: number, lotsize?: number): Promise + modifyOrder (order: OrderModifyInfo): Promise + unsetSlOrTP (order_id: string|number, slortp: 'sl'|'tp'): Promise + launchOrderModal (type: OrderModalType, callback: OrderPlacedCallback, order?: OrderModifyInfo): void +} + +export interface ChartProOptions { + container: string | HTMLElement + rootElementId?: string + styles?: DeepPartial + watermark?: string | Node + theme?: string + locale?: string + drawingBarVisible?: boolean + orderPanelVisible?: boolean + symbol: SymbolInfo + period: Period + periods?: Period[] + timezone?: string + mainIndicators?: string[] + subIndicators?: string[] + datafeed: Datafeed + dataTimestamp?: number + orderController: OrderResource +} + +export interface ChartPro { + setTheme(theme: string): void + getTheme(): string + setStyles(styles: DeepPartial): void + getStyles(): Styles + setLocale(locale: string): void + getLocale(): string + setTimezone(timezone: string): void + getTimezone(): string + setSymbol(symbol: SymbolInfo): void + getSymbol(): SymbolInfo + setPeriod(period: Period): void + getPeriod(): Period + getInstanceApi(): Nullable + resize(): void + dispose(): void +} diff --git a/src/widget/drawing-bar/index.tsx b/src/widget/drawing-bar/index.tsx index 936c0a93..40a2581c 100644 --- a/src/widget/drawing-bar/index.tsx +++ b/src/widget/drawing-bar/index.tsx @@ -26,7 +26,7 @@ import { export interface DrawingBarProps { locale: string - onDrawingItemClick: (overlay: OverlayCreate) => void + onDrawingItemClick: (value: string | OverlayCreate) => void onModeChange: (mode: string) => void, onLockChange: (lock: boolean) => void onVisibleChange: (visible: boolean) => void diff --git a/src/widget/indicator-modal/index.tsx b/src/widget/indicator-modal/index.tsx index 0a36729d..1c80f3e5 100644 --- a/src/widget/indicator-modal/index.tsx +++ b/src/widget/indicator-modal/index.tsx @@ -21,7 +21,8 @@ import i18n from '../../i18n' type OnIndicatorChange = ( params: { name: string - paneId: string + paneId?: string + id: string added: boolean } ) => void @@ -54,7 +55,7 @@ const IndicatorModal: Component = props => {
  • { - props.onMainIndicatorChange({ name, paneId: 'candle_pane', added: !checked }) + props.onMainIndicatorChange({ name, id: 'candle_pane', added: !checked }) }}>
  • @@ -76,7 +77,7 @@ const IndicatorModal: Component = props => { class="row" onClick={_ => { // @ts-expect-error - props.onSubIndicatorChange({ name, paneId: props.subIndicators[name] ?? '', added: !checked }); + props.onSubIndicatorChange({ name, id: props.subIndicators[name] ?? '', added: !checked }); }}> diff --git a/src/widget/period-bar/index.tsx b/src/widget/period-bar/index.tsx index 974b7089..9d154ac1 100644 --- a/src/widget/period-bar/index.tsx +++ b/src/widget/period-bar/index.tsx @@ -14,7 +14,7 @@ import { Component, Show, createSignal, onMount, onCleanup } from 'solid-js' -import { SymbolInfo, Period } from '../../types' +import { SymbolInfo, Period } from '../../types/types' import i18n from '../../i18n' diff --git a/src/widget/symbol-search-modal/index.tsx b/src/widget/symbol-search-modal/index.tsx index 26f7b173..ccbb6ac6 100644 --- a/src/widget/symbol-search-modal/index.tsx +++ b/src/widget/symbol-search-modal/index.tsx @@ -18,11 +18,11 @@ import { Modal, List, Input } from '../../component' import i18n from '../../i18n' -import { SymbolInfo, Datafeed } from '../../types' +import { SymbolInfo, ChartDataLoaderType } from '../../types/types' export interface SymbolSearchModalProps { locale: string - datafeed: Datafeed + datafeed: ChartDataLoaderType onSymbolSelected: (symbol: SymbolInfo) => void onClose: () => void }