diff --git a/apps/backtest-report/components/ReportDetails.tsx b/apps/backtest-report/components/ReportDetails.tsx index 5e7ef122e..81fa24377 100644 --- a/apps/backtest-report/components/ReportDetails.tsx +++ b/apps/backtest-report/components/ReportDetails.tsx @@ -185,8 +185,10 @@ const ReportDetails = (props: ReportDetailsProps) => { const volumeUnit = reportSummary.symbolReports.length == 1 ? reportSummary.symbolReports[0].market.baseCurrency : ''; - return
- + + + // size xl and padding xs + return
Strategy: {strategyName} {reportSummary.sessions.map((session) => Exchange: {session})} @@ -239,8 +241,8 @@ const ReportDetails = (props: ReportDetailsProps) => { }
-
-
; + ; }; + export default ReportDetails; diff --git a/apps/backtest-report/components/TradingViewChart.tsx b/apps/backtest-report/components/TradingViewChart.tsx index ea172c834..01849f5e9 100644 --- a/apps/backtest-report/components/TradingViewChart.tsx +++ b/apps/backtest-report/components/TradingViewChart.tsx @@ -5,7 +5,7 @@ import {Checkbox, Group, SegmentedControl} from '@mantine/core'; // https://github.com/tradingview/lightweight-charts/issues/543 // const createChart = dynamic(() => import('lightweight-charts')); -import {createChart, CrosshairMode} from 'lightweight-charts'; +import {createChart, CrosshairMode, MouseEventParams} from 'lightweight-charts'; import {ReportSummary} from "../types"; import moment from "moment"; @@ -429,6 +429,38 @@ const TradingViewChart = (props: TradingViewChartProps) => { series.setData(chartData.klines); series.setMarkers(chartData.markers); + [9, 27, 99].forEach((w, i) => { + const emaValues = calculateEMA(chartData.klines, w) + const emaColor = 'rgba(' + w + ', ' + (111 - w) + ', 232, 0.9)' + const emaLine = chart.current.addLineSeries({ + color: emaColor, + lineWidth: 1, + }); + emaLine.setData(emaValues); + + const legend = document.createElement('div'); + legend.className = 'ema-legend'; + legend.style.display = 'block'; + legend.style.position = 'absolute'; + legend.style.left = 3 + 'px'; + legend.style.zIndex = '99'; + legend.style.top = 3 + (i * 22) + 'px'; + chartContainerRef.current.appendChild(legend); + + const setLegendText = (priceValue: any) => { + let val = '∅'; + if (priceValue !== undefined) { + val = (Math.round(priceValue * 100) / 100).toFixed(2); + } + legend.innerHTML = 'EMA' + w + ' ' + val + ''; + } + + setLegendText(emaValues[emaValues.length - 1].value); + chart.current.subscribeCrosshairMove((param: MouseEventParams) => { + setLegendText(param.seriesPrices.get(emaLine)); + }); + }) + const volumeData = klinesToVolumeData(chartData.klines); const volumeSeries = chart.current.addHistogramSeries({ color: '#182233', @@ -468,6 +500,9 @@ const TradingViewChart = (props: TradingViewChartProps) => { if (chart.current) { chart.current.remove(); } + if (chartContainerRef.current) { + chartContainerRef.current.replaceChildren(); + } }; }, [props.runID, props.reportSummary, currentInterval, showPositionBase, showPositionAverageCost]) @@ -507,10 +542,30 @@ const TradingViewChart = (props: TradingViewChartProps) => { onChange={(event) => setShowPositionAverageCost(event.currentTarget.checked)}/> -
+
+
); }; +const calculateEMA = (a: KLine[], r: number) => { + return a.map((k) => { + return {time: k.time, value: k.close} + }).reduce((p: any[], n: any, i: number) => { + if (i) { + const last = p[p.length - 1] + const v = 2 * n.value / (r + 1) + last.value * (r - 1) / (r + 1) + return p.concat({value: v, time: n.time}) + } + + return p + }, [{ + value: a[0].close, + time: a[0].time + }]) +} + + export default TradingViewChart; +