diff --git a/data/charts.html b/data/charts.html
new file mode 100644
index 0000000..315d9c4
--- /dev/null
+++ b/data/charts.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Charts
+
+
+
+
+
+
+
+
+
diff --git a/data/charts.js b/data/charts.js
new file mode 100644
index 0000000..267bcf5
--- /dev/null
+++ b/data/charts.js
@@ -0,0 +1,342 @@
+const urlParams = new URLSearchParams(window.location.search);
+const symbol = urlParams.get('symbol');
+const time = urlParams.get('time');
+
+//window.dispatchEvent(new Event('resize'));
+
+function timeToLocal(originalTime) {
+ const d = new Date(originalTime * 1000);
+ return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()) / 1000;
+}
+
+ function parseCSV(data) {
+ const rows = data.split("\n");
+ const result = [];
+ let start = Math.max(rows.length - 298, 0);
+ let lastElements = rows.slice(start)
+ for (let i = start; i < rows.length; i++) {
+ const cols = rows[i].split(",");
+ if (cols.length >= 23 && cols.every(element => element !== undefined && element !== null)) { // check for existing lines
+ // parse the date so seconds since 1970
+ cols[0] = Date.parse(cols[0])/1000,result.push(cols);
+ cols[0] = timeToLocal(cols[0]);
+ // coloring for MACD-Histogram
+ if (cols[20] < 0) {
+ cols[100] = "orange";
+ if (cols[23] > 20) {
+ cols[100] = "red";
+ }
+ }
+ else {
+ cols[100] = "lightgreen";
+ if (cols[23] > 20) {
+ cols[100] = "green";
+ }
+ }
+ }
+ else {
+ console.log("invalid line on linenr " + i + ": " +rows[i]);
+ }
+ }
+ return result;
+ }
+
+ // Create the Lightweight Chart within the container element
+ const chart = LightweightCharts.createChart(document.getElementById('container'),
+ {
+ rightPriceScale: {
+ minimumWidth: 100,
+ borderVisible: false
+ },
+
+ height: 500,
+ crosshair: {
+ mode: 0,
+ },
+ timeScale: {
+ timeVisible: true,
+ secondsVisible: false,
+ },
+ layout: {
+ background: {
+ type: 'solid',
+ color: '#222',
+ },
+ textColor: '#DDD',
+ },
+ grid: {
+ vertLines: { color: '#444' },
+ horzLines: { color: '#444' },
+ },
+ });
+
+ chart.applyOptions({
+ watermark: {
+ visible: true,
+ fontSize: 18,
+ horzAlign: 'top',
+ vertAlign: 'left',
+ color: '#DDD',
+ text: symbol + " " + time,
+ }});
+
+
+ // define chart
+ const candleSeries = chart.addCandlestickSeries();
+ const lineSeriesEMA12 = chart.addLineSeries({ color: 'red', lineWidth: 1, priceLineVisible: false});
+ const lineSeriesEMA26 = chart.addLineSeries({ color: 'pink', lineWidth: 1, lineStyle: 2, priceLineVisible: false});
+ const lineSeriesEMA50 = chart.addLineSeries({ color: 'cyan', lineWidth: 1, priceLineVisible: false});
+ const lineSeriesEMA100 = chart.addLineSeries({ color: 'yellow', lineWidth: 1, priceLineVisible: false});
+ const lineSeriesEMA200 = chart.addLineSeries({ color: 'white', lineWidth: 1, priceLineVisible: false});
+ const lineSeriesEMA400 = chart.addLineSeries({ color: 'orange', lineWidth: 1, priceLineVisible: false});
+ const lineSeriesEMA800 = chart.addLineSeries({ color: 'purple', lineWidth: 1, priceLineVisible: false});
+
+
+ // RSI Chart
+ const chartrsi = LightweightCharts.createChart(document.getElementById("container"),
+ {
+ rightPriceScale: {
+ minimumWidth: 100,
+ borderVisible: false
+ },
+ height: 200,
+ timeScale: {
+ visible: false,
+ },
+ layout: {
+ background: {
+ type: 'solid',
+ color: '#222',
+ },
+ textColor: '#DDD',
+ },
+ grid: {
+ vertLines: { color: '#444' },
+ horzLines: { color: '#444' },
+ },
+ });
+
+ chartrsi.applyOptions({
+ watermark: {
+ visible: true,
+ fontSize: 18,
+ horzAlign: 'top',
+ vertAlign: 'left',
+ color: '#DDD',
+ text: 'RSI 5,14,21',
+ }});
+
+ const lineSeriesRSI5 = chartrsi.addLineSeries({ color: 'orange', lineWidth: 1, lineStyle: 2, priceLineVisible: false});
+ const lineSeriesRSI14 = chartrsi.addLineSeries({ color: 'yellow', lineWidth: 2, priceLineVisible: false});
+ const lineSeriesRSI21 = chartrsi.addLineSeries({ color: 'lightgreen', lineWidth: 1, lineStyle: 2, priceLineVisible: false});
+
+ // MACD Chart
+ const chartmacd = LightweightCharts.createChart(document.getElementById("container"),
+ {
+ rightPriceScale: {
+ minimumWidth: 100,
+ borderVisible: false
+ },
+
+ height: 200,
+ timeScale: {
+ timeVisible: true,
+ secondsVisible: false,
+ },
+ layout: {
+ background: {
+ type: 'solid',
+ color: '#222',
+ },
+ textColor: '#DDD',
+ },
+ grid: {
+ vertLines: { color: '#444' },
+ horzLines: { color: '#444' },
+ },
+ });
+
+ chartmacd.applyOptions({
+ watermark: {
+ visible: true,
+ fontSize: 18,
+ horzAlign: 'top',
+ vertAlign: 'left',
+ color: '#DDD',
+ text: 'MACD 12 26',
+ }});
+
+ const lineSeriesMACD = chartmacd.addLineSeries({ color: 'blue', lineWidth: 1, lineStyle: 0, priceLineVisible: false});
+ const lineSeriesMACDSignal = chartmacd.addLineSeries({ color: 'orange', lineWidth: 1, lineStyle: 0, priceLineVisible: false});
+ const histogramSeriesMACD = chartmacd.addHistogramSeries({
+ priceFormat: {
+ type: 'volume',
+ color: 'orange',
+ },
+ //priceScaleId: '', // set as an overlay by setting a blank priceScaleId
+ });
+
+
+
+ fetch("/botdata/asset-histories/" + symbol + ".history." + time + ".csv")
+ .then(response => response.text())
+ .then(data => {
+ const parsedData = parseCSV(data);
+
+ // OHLC Data
+ const bars = parsedData.map(item => ({
+ time: item[0],
+ open: item[1],
+ high: item[2],
+ low: item[3],
+ close: item[4]
+ }));
+ candleSeries.setData(bars);
+
+ // EMA Data
+ candleSeries.setData(bars);
+ const lineSeriesEMA12Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[8]
+ }));
+ lineSeriesEMA12.setData(lineSeriesEMA12Data);
+
+ const lineSeriesEMA26Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[9]
+ }));
+ lineSeriesEMA26.setData(lineSeriesEMA26Data);
+
+ const lineSeriesEMA50Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[10]
+ }));
+ lineSeriesEMA50.setData(lineSeriesEMA50Data);
+
+ const lineSeriesEMA100Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[11]
+ }));
+ lineSeriesEMA100.setData(lineSeriesEMA100Data);
+
+ const lineSeriesEMA200Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[12]
+ }));
+ lineSeriesEMA200.setData(lineSeriesEMA200Data);
+
+ const lineSeriesEMA400Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[13]
+ }));
+ lineSeriesEMA400.setData(lineSeriesEMA400Data);
+
+ const lineSeriesEMA800Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[14]
+ }));
+ lineSeriesEMA800.setData(lineSeriesEMA800Data);
+
+ // RSI Data
+ const lineSeriesRSI5Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[15]
+ }));
+ lineSeriesRSI5.setData(lineSeriesRSI5Data);
+
+ const lineSeriesRSI14Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[16]
+ }));
+ lineSeriesRSI14.setData(lineSeriesRSI14Data);
+
+ const lineSeriesRSI21Data = parsedData.map(item => ({
+ time: item[0],
+ value: item[17]
+ }));
+ lineSeriesRSI21.setData(lineSeriesRSI21Data);
+
+ // MACD Data
+ const lineSeriesMACDData = parsedData.map(item => ({
+ time: item[0],
+ value: item[18]
+ }));
+ lineSeriesMACD.setData(lineSeriesMACDData);
+
+ const lineSeriesMACDSignalData = parsedData.map(item => ({
+ time: item[0],
+ value: item[19]
+ }));
+ lineSeriesMACDSignal.setData(lineSeriesMACDSignalData);
+
+ const histogramSeriesMACDData = parsedData.map(item => ({
+ time: item[0],
+ value: item[20],
+ color: item[100]
+ }));
+ histogramSeriesMACD.setData(histogramSeriesMACDData);
+ });
+
+
+ // Lines for price levels
+ fetch("/botdata/asset-histories/" + symbol + ".history.csv.levels")
+ .then(response => response.text())
+ .then(text => {
+ const levels = text.split('\n');
+ levels.forEach(function(level) {
+ candleSeries.createPriceLine({price: level, color: "darkblue", lineWidth: 0.5, lineStyle: 0, axisLabelVisible: true, title: 'Level'});
+ });
+ });
+
+
+ // Sync charts timeScale
+ chart.timeScale().fitContent();
+ chart.timeScale().subscribeVisibleLogicalRangeChange(timeRange => {
+ chartrsi.timeScale().setVisibleLogicalRange(timeRange);
+ chartmacd.timeScale().setVisibleLogicalRange(timeRange);
+ });
+
+ chartrsi.timeScale().subscribeVisibleLogicalRangeChange(timeRange => {
+ chart.timeScale().setVisibleLogicalRange(timeRange);
+ });
+
+ chartmacd.timeScale().subscribeVisibleLogicalRangeChange(timeRange => {
+ chart.timeScale().setVisibleLogicalRange(timeRange);
+ });
+
+
+function getCrosshairDataPoint(series, param) {
+ if (!param.time) {
+ return null;
+ }
+ const dataPoint = param.seriesData.get(series);
+ return dataPoint || null;
+}
+
+function syncCrosshair(chart, series, dataPoint) {
+ if (dataPoint) {
+ chart.setCrosshairPosition(dataPoint.value, dataPoint.time, series);
+ return;
+ }
+ chart.clearCrosshairPosition();
+}
+chart.subscribeCrosshairMove(param => {
+ const dataPoint = getCrosshairDataPoint(lineSeriesEMA50, param);
+ syncCrosshair(chartrsi, lineSeriesRSI14, dataPoint);
+ const dataPointmacd = getCrosshairDataPoint(lineSeriesEMA50, param);
+ syncCrosshair(chartmacd, lineSeriesMACD, dataPointmacd);
+});
+chartrsi.subscribeCrosshairMove(param => {
+ const dataPoint = getCrosshairDataPoint(lineSeriesRSI14, param);
+ syncCrosshair(chart, lineSeriesEMA50, dataPoint);
+ const dataPointmacd = getCrosshairDataPoint(lineSeriesRSI14, param);
+ syncCrosshair(chartmacd, lineSeriesMACD, dataPointmacd);
+
+});
+chartmacd.subscribeCrosshairMove(param => {
+ const dataPoint = getCrosshairDataPoint(lineSeriesMACD, param);
+ syncCrosshair(chart, lineSeriesEMA50, dataPoint);
+ const dataPointrsi = getCrosshairDataPoint(lineSeriesMACD, param);
+ syncCrosshair(chartrsi, lineSeriesRSI14, dataPointrsi);
+});
+