diff --git a/apps/backtest-report/components/ReportDetails.tsx b/apps/backtest-report/components/ReportDetails.tsx index bd037c990..d10a0cf95 100644 --- a/apps/backtest-report/components/ReportDetails.tsx +++ b/apps/backtest-report/components/ReportDetails.tsx @@ -186,8 +186,6 @@ const ReportDetails = (props: ReportDetailsProps) => { const volumeUnit = reportSummary.symbolReports.length == 1 ? reportSummary.symbolReports[0].market.baseCurrency : ''; - // reportSummary.startTime - return
RUN {props.runID} diff --git a/apps/backtest-report/components/TradingViewChart.js b/apps/backtest-report/components/TradingViewChart.js index 17077cffa..0e6810e11 100644 --- a/apps/backtest-report/components/TradingViewChart.js +++ b/apps/backtest-report/components/TradingViewChart.js @@ -1,6 +1,6 @@ import React, {useEffect, useRef, useState} from 'react'; import {tsvParse} from "d3-dsv"; -import { Button } from '@mantine/core'; +import {Button} from '@mantine/core'; // https://github.com/tradingview/lightweight-charts/issues/543 // const createChart = dynamic(() => import('lightweight-charts')); @@ -194,6 +194,7 @@ const removeDuplicatedKLines = (klines) => { const k = klines[i]; if (i > 0 && k.time === klines[i - 1].time) { + console.warn(`duplicated kline at index ${i}`, k) continue } @@ -234,6 +235,11 @@ const positionBaseHistoryToLineData = (interval, hs) => { const intervalSeconds = parseInterval(interval); for (let i = 0; i < hs.length; i++) { const pos = hs[i]; + if (!pos.time) { + console.warn('position history record missing time field', pos) + continue + } + let t = pos.time.getTime() / 1000; t = (t - t % intervalSeconds) @@ -255,6 +261,12 @@ const positionAverageCostHistoryToLineData = (interval, hs) => { const intervalSeconds = parseInterval(interval); for (let i = 0; i < hs.length; i++) { const pos = hs[i]; + + if (!pos.time) { + console.warn('position history record missing time field', pos) + continue + } + let t = pos.time.getTime() / 1000; t = (t - t % intervalSeconds) @@ -279,14 +291,45 @@ const positionAverageCostHistoryToLineData = (interval, hs) => { return avgCosts; } +const createBaseChart = (chartContainerRef) => { + return createChart(chartContainerRef.current, { + width: chartContainerRef.current.clientWidth, + height: chartContainerRef.current.clientHeight, + timeScale: { + timeVisible: true, + borderColor: '#D1D4DC', + }, + rightPriceScale: { + borderColor: '#D1D4DC', + }, + leftPriceScale: { + visible: true, + borderColor: 'rgba(197, 203, 206, 1)', + }, + layout: { + backgroundColor: '#ffffff', + textColor: '#000', + }, + crosshair: { + mode: CrosshairMode.Normal, + }, + grid: { + horzLines: { + color: '#F0F3FA', + }, + vertLines: { + color: '#F0F3FA', + }, + }, + }); +}; + + const TradingViewChart = (props) => { const chartContainerRef = useRef(); const chart = useRef(); const resizeObserver = useRef(); const [data, setData] = useState(null); - const [orders, setOrders] = useState(null); - const [markers, setMarkers] = useState(null); - const [positionHistory, setPositionHistory] = useState(null); const [currentInterval, setCurrentInterval] = useState('5m'); const intervals = props.intervals || []; @@ -296,111 +339,86 @@ const TradingViewChart = (props) => { return; } - if (!data) { - const fetchers = []; - const ordersFetcher = fetchOrders(props.basePath, props.runID, (orders) => { - const markers = ordersToMarkets(currentInterval, orders); - setOrders(orders); - setMarkers(markers); - }); - fetchers.push(ordersFetcher); + const chartData = {}; + const fetchers = []; + const ordersFetcher = fetchOrders(props.basePath, props.runID, (orders) => { + const markers = ordersToMarkets(currentInterval, orders); + chartData.orders = orders; + chartData.markers = markers; + }); + fetchers.push(ordersFetcher); - if (props.reportSummary && props.reportSummary.manifests && props.reportSummary.manifests.length === 1) { - const manifest = props.reportSummary?.manifests[0]; - if (manifest && manifest.type === "strategyProperty" && manifest.strategyProperty === "position") { - const positionHistoryFetcher = fetchPositionHistory(props.basePath, props.runID, manifest.filename).then((data) => { - setPositionHistory(data); - }); - fetchers.push(positionHistoryFetcher); - } + if (props.reportSummary && props.reportSummary.manifests && props.reportSummary.manifests.length === 1) { + const manifest = props.reportSummary?.manifests[0]; + if (manifest && manifest.type === "strategyProperty" && manifest.strategyProperty === "position") { + const positionHistoryFetcher = fetchPositionHistory(props.basePath, props.runID, manifest.filename).then((data) => { + chartData.positionHistory = data; + }); + fetchers.push(positionHistoryFetcher); + } + } + + const kLinesFetcher = fetchKLines(props.basePath, props.runID, props.symbol, currentInterval).then((klines) => { + chartData.klines = removeDuplicatedKLines(klines) + }); + fetchers.push(kLinesFetcher); + + Promise.all(fetchers).then(() => { + console.log("createChart") + + if (chart.current) { + chart.current.remove(); } - Promise.all(fetchers).then(() => { - fetchKLines(props.basePath, props.runID, props.symbol, currentInterval).then((data) => { - setData(removeDuplicatedKLines(data)); - }) + chart.current = createBaseChart(chartContainerRef); + + const series = chart.current.addCandlestickSeries({ + upColor: 'rgb(38,166,154)', + downColor: 'rgb(255,82,82)', + wickUpColor: 'rgb(38,166,154)', + wickDownColor: 'rgb(255,82,82)', + borderVisible: false, }); + series.setData(chartData.klines); + series.setMarkers(chartData.markers); - return; - } - - console.log("createChart") - - chart.current = createChart(chartContainerRef.current, { - width: chartContainerRef.current.clientWidth, - height: chartContainerRef.current.clientHeight, - timeScale: { - timeVisible: true, - borderColor: '#D1D4DC', - }, - rightPriceScale: { - borderColor: '#D1D4DC', - }, - leftPriceScale: { - visible: true, - borderColor: 'rgba(197, 203, 206, 1)', - }, - layout: { - backgroundColor: '#ffffff', - textColor: '#000', - }, - crosshair: { - mode: CrosshairMode.Normal, - }, - grid: { - horzLines: { - color: '#F0F3FA', + const volumeData = klinesToVolumeData(chartData.klines); + const volumeSeries = chart.current.addHistogramSeries({ + color: '#182233', + lineWidth: 2, + priceFormat: { + type: 'volume', }, - vertLines: { - color: '#F0F3FA', + overlay: true, + scaleMargins: { + top: 0.8, + bottom: 0, }, - }, - }); - - const series = chart.current.addCandlestickSeries({ - upColor: 'rgb(38,166,154)', - downColor: 'rgb(255,82,82)', - wickUpColor: 'rgb(38,166,154)', - wickDownColor: 'rgb(255,82,82)', - borderVisible: false, - }); - series.setData(data); - series.setMarkers(markers); - - if (positionHistory) { - const lineSeries = chart.current.addLineSeries(); - const costLine = positionAverageCostHistoryToLineData(currentInterval, positionHistory); - lineSeries.setData(costLine); - - const baseLineSeries = chart.current.addLineSeries({ - priceScaleId: 'left', - color: '#98338C', }); - const baseLine = positionBaseHistoryToLineData(currentInterval, positionHistory) - baseLineSeries.setData(baseLine); - } + volumeSeries.setData(volumeData); - const volumeData = klinesToVolumeData(data); - const volumeSeries = chart.current.addHistogramSeries({ - color: '#182233', - lineWidth: 2, - priceFormat: { - type: 'volume', - }, - overlay: true, - scaleMargins: { - top: 0.8, - bottom: 0, - }, + if (chartData.positionHistory) { + const lineSeries = chart.current.addLineSeries(); + const costLine = positionAverageCostHistoryToLineData(currentInterval, chartData.positionHistory); + lineSeries.setData(costLine); + + const baseLineSeries = chart.current.addLineSeries({ + priceScaleId: 'left', + color: '#98338C', + }); + const baseLine = positionBaseHistoryToLineData(currentInterval, chartData.positionHistory) + baseLineSeries.setData(baseLine); + } + + chart.current.timeScale().fitContent(); }); - volumeSeries.setData(volumeData); - chart.current.timeScale().fitContent(); return () => { - chart.current.remove(); - setData(null); + if (chart.current) { + chart.current.remove(); + } }; - }, [props.runID, props.reportSummary, currentInterval, data]) + }, [props.runID, props.reportSummary, currentInterval]) // see: // https://codesandbox.io/s/9inkb?file=/src/styles.css diff --git a/apps/backtest-report/next.config.js b/apps/backtest-report/next.config.js index cc5aa7597..bd686c991 100644 --- a/apps/backtest-report/next.config.js +++ b/apps/backtest-report/next.config.js @@ -27,7 +27,7 @@ const withTM = require('next-transpile-modules')([ /** @type {import('next').NextConfig} */ const nextConfig = { - reactStrictMode: true, + reactStrictMode: false, } module.exports = withTM(nextConfig);