From 4d7c8d4ee9ddbe6233724be9306d027bca7ddd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Delacotte?= Date: Wed, 7 May 2025 20:48:40 +0200 Subject: [PATCH] =?UTF-8?q?Affichage=20graph=20des=20donn=C3=A9es=20/=20s?= =?UTF-8?q?=C3=A9lection=20des=20indicateurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- requirements.txt | 1 + src/app.py | 55 ++++++++- src/static/js/functions.js | 234 +++++++++++++++++++++++++++---------- src/templates/index.html | 8 +- 5 files changed, 233 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index a461076..95fe459 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ docker build -t flask-web-app . ## Lancement - -docker run -it -p 5000:5000 -v $(pwd)/src/:/src -v /home/jerome/Perso/freqtradeDocker/user_data/backtest_results:/mnt/external flask-web-app bash +docker run -it -p 5000:5000 -v $(pwd)/src/:/src -v /home/jerome/Perso/freqtradeDocker/user_data/:/mnt/external flask-web-app bash puis : python3 app.py diff --git a/requirements.txt b/requirements.txt index 8d8fbda..615cb11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ Flask==2.2.3 Werkzeug==2.2.3 joblib==1.4.2 pyarrow +pandas-ta diff --git a/src/app.py b/src/app.py index 04cbdac..44afb24 100644 --- a/src/app.py +++ b/src/app.py @@ -1,4 +1,4 @@ -from flask import Flask, jsonify, abort, render_template, send_from_directory +from flask import Flask, jsonify, abort, render_template, send_from_directory,request import pandas as pd import json import zipfile @@ -102,14 +102,59 @@ def read_json(filename): def read_feather(filename): path = os.path.join(FREQTRADE_USERDATA_DIR + "/data/binance/", filename) try: - print(path) - df = pd.read_feather(path) - print(df) - return df.to_json(orient="records") + dataframe = pd.read_feather(path) + # dataframe['min'] = talib.MIN(dataframe['close'], timeperiod=200) + # dataframe['min12'] = talib.MIN(dataframe['close'], timeperiod=12) + # + # dataframe['min50'] = talib.MIN(dataframe['close'], timeperiod=50) + # dataframe['min200'] = talib.MIN(dataframe['close'], timeperiod=200) + # dataframe['max200'] = talib.MAX(dataframe['close'], timeperiod=200) + + return dataframe.to_json(orient="records") except Exception as e: print(e) return jsonify({"error": str(e)}), 500 +@app.route('/get_chart_data') +def get_chart_data(): + filename = request.args.get('filename', '') + path = os.path.join(FREQTRADE_USERDATA_DIR + "/data/binance/", filename) + print(path) + indicators = request.args.get('indicators', '').split(',') + df = pd.read_feather(path) + print(df) + + # # Calculs conditionnels + # if 'sma' in indicators: + # df['sma'] = df['close'].rolling(window=20).mean() + # if 'rsi' in indicators: + # delta = df['close'].diff() + # gain = delta.where(delta > 0, 0) + # loss = -delta.where(delta < 0, 0) + # avg_gain = gain.rolling(14).mean() + # avg_loss = loss.rolling(14).mean() + # rs = avg_gain / avg_loss + # df['rsi'] = 100 - (100 / (1 + rs)) + # if 'bollinger' in indicators: + # sma = df['close'].rolling(window=20).mean() + # std = df['close'].rolling(window=20).std() + # df['bb_upper'] = sma + 2 * std + # df['bb_lower'] = sma - 2 * std + # + # df = df.dropna() + + # Simplifier les données pour le JSON + # chart_data = { + # 'date': df['date'].astype(str).tolist(), + # 'close': df['close'].tolist(), + # 'sma': df.get('sma', pd.Series()).tolist(), + # 'rsi': df.get('rsi', pd.Series()).tolist(), + # 'bb_upper': df.get('bb_upper', pd.Series()).tolist(), + # 'bb_lower': df.get('bb_lower', pd.Series()).tolist(), + # } + + return df.to_json(orient="records") #jsonify(chart_data) + if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/src/static/js/functions.js b/src/static/js/functions.js index b1898f6..a6c9f68 100644 --- a/src/static/js/functions.js +++ b/src/static/js/functions.js @@ -79,20 +79,33 @@ function graph() { chart.setOption(option); } -function loadFeather(filename) { - fetch(`/read_feather/${filename}`) - .then(response => response.json()) - .then(data => { - // Table - const table = document.getElementById('data-table'); - if (data.length > 0) { - let header = '' + Object.keys(data[0]).map(k => `${k}`).join('') + ''; - let rows = data.map(row => '' + Object.values(row).map(v => `${v}`).join('') + ''); - table.innerHTML = header + rows.join(''); - } +function renderChart(data, filename, create_columns) { - // ECharts (Close price over time) - const chart = echarts.init(document.getElementById('chart')); + // Table +// const table = document.getElementById('data-table'); +// if (data.length > 0) { +// let header = '' + Object.keys(data[0]).map(k => `${k}`).join('') + ''; +// let rows = data.map(row => '' + Object.values(row).map(v => `${v}`).join('') + ''); +// table.innerHTML = header + rows.join(''); +// } + const cols = Object.keys(data[0]) + if (create_columns === true) { + string = "" + const indicators = document.getElementById('indicators'); + indicators.innerHTML = string + } + + +// const label = document.createElement('label'); +// label.innerHTML = ` ${col}
`; +// indicators.appendChild(label); + + let totalPoints = data.length; + let visiblePoints = 100; + let startPercent = ((totalPoints - visiblePoints) / totalPoints) * 100; + + // ECharts (Close price over time) + const chart = echarts.init(document.getElementById('chart')); // const option = { // xAxis: { // type: 'category', @@ -108,52 +121,153 @@ function loadFeather(filename) { // }] // }; // specify chart configuration item and data - var option = { - title: { - text: filename - }, - grid: { - left: '3%', - right: '4%', - bottom: '3%', - containLabel: true - }, - toolbox: { - feature: { - dataView: {show: true, readOnly: false}, - restore: {show: true}, - saveAsImage: {show: true} - } - }, - tooltip: { - trigger: 'axis', - axisPointer: { - type: 'cross', - crossStyle: { - color: '#999' - } - } - }, - // legend: { - // data:[] - // }, - xAxis: { - type: 'category', - data: data.map(d => { - const date = new Date(d.date); - return date.toLocaleDateString('fr-FR'); // ex : 07/05/2025 - }) - }, - yAxis: { - type: 'value' - }, - series: [{ - type: 'line', - name: 'Close', - data: data.map(d => d.close) - }] - }; - chart.setOption(option); - }); + const series = [{ + type: 'line', + name: 'Close', + data: data.map(d => d.close) + }] +// df_ohlc = data +// df_ohlc['date'] = pd.to_datetime(data['date']).dt.strftime('%Y-%m-%d') +// df_ohlc = data[['date', 'open', 'close', 'low', 'high']] +// series.push({ +// type: 'candlestick', +// data: { +// 'dates': df_ohlc['date'].tolist(), +// 'ohlc': df_ohlc[['open', 'close', 'low', 'high']].values.tolist() +// }, +// itemStyle: { +// color: '#ec0000', // bougie haussière (fermée plus haut que ouverte) +// color0: '#00da3c', // bougie baissière +// borderColor: '#8A0000', +// borderColor0: '#008F28' +// } +// } +// ) + + for (var key in cols) { + var value = cols[key]; + element=document.getElementById(value) + if (element) { + if (element.checked) { + series.push({ + name: value, + type: 'line', + data: data.map(d => d[value]), + smooth: true, + lineStyle: { color: stringToColor(value) } + }) + } + } + } + var option = { + title: { + text: filename + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true + }, + toolbox: { + feature: { + dataView: {show: true, readOnly: false}, + restore: {show: true}, + saveAsImage: {show: true} + } + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + crossStyle: { + color: '#999' + } + } + }, + dataZoom: [ + { + type: 'slider', // slider visible en bas + start: startPercent, + end: 100 + }, + { + type: 'inside', // zoom avec la molette de la souris + start: startPercent, + end: 100 + } + ], + legend: { + data: cols, // Affiche les noms de chaque série avec leur couleur + }, + xAxis: { + type: 'category', + data: data.map(d => { + const date = new Date(d.date); + return date.toLocaleDateString('fr-FR'); // ex : 07/05/2025 + }) + }, + yAxis: { + type: 'value', + min: 'dataMin', // <-- commence à la plus petite valeur + max: 'dataMax' // <-- adapte aussi le maximum + }, + series: series + } + + chart.setOption(option) + } + +function loadFeather(filename) { + const element = document.getElementById('current_file_name'); + element.value = filename + fetch(`/read_feather/${filename}`) + .then(response => response.json()) + .then(data => renderChart(data, filename, true) ); +} + + +let selectedIndicators = new Set(); + +function toggleIndicator(checkbox) { + const indicator = checkbox.value; + if (checkbox.checked) { + selectedIndicators.add(indicator); + } else { + selectedIndicators.delete(indicator); + } + loadChartWithIndicators(); +} + +function loadChartWithIndicators() { + const element = document.getElementById('current_file_name'); + let filename = element.value + const indicators = Array.from(selectedIndicators).join(','); + fetch(`/get_chart_data?filename=${filename}&indicators=${indicators}`) + .then(response => response.json()) + .then(data => renderChart(data, filename, false)); +} + +//function createListeners() { +// // Écouteurs d'événement +// document.querySelectorAll('input[type=checkbox]').forEach(cb => { +// cb.addEventListener('change', () => { +// chart.setOption({ series: getSeries() }); +// }); +// }); +//} + +function stringToColor(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + + // Convertir le hash en une teinte HSL (0-360°) + const hue = hash % 360; + // Saturation et luminosité réglées pour contraste + return `hsl(${hue}, 70%, 50%)`; +} + diff --git a/src/templates/index.html b/src/templates/index.html index a2c13d3..c9cf43d 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -29,12 +29,18 @@ +
+ + + +
-

Données

+
+