From 586169748992f5acba1d6195a5afa791250f64e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Delacotte?= Date: Wed, 7 May 2025 16:45:28 +0200 Subject: [PATCH] =?UTF-8?q?Affichage=20graph=20des=20donn=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 2 +- src/app.py | 54 ++++++++++++++----- src/static/css/style.css | 87 ++++++++++++++++++++++++++++++ src/static/js/functions.js | 81 +++++++++++++++++++++++++++- src/templates/index.html | 106 +++++-------------------------------- 5 files changed, 221 insertions(+), 109 deletions(-) create mode 100644 src/static/css/style.css diff --git a/requirements.txt b/requirements.txt index a0c5309..8d8fbda 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,4 @@ matplotlib Flask==2.2.3 Werkzeug==2.2.3 joblib==1.4.2 - +pyarrow diff --git a/src/app.py b/src/app.py index 2c25dcd..04cbdac 100644 --- a/src/app.py +++ b/src/app.py @@ -9,19 +9,22 @@ from io import TextIOWrapper app = Flask(__name__) -FILES_DIR = '/mnt/external' +FREQTRADE_USERDATA_DIR = '/mnt/external' @app.route('/') def home(): # Liste les fichiers dans le répertoire monté - files = os.listdir('/mnt/external') + files = os.listdir(FREQTRADE_USERDATA_DIR + "/backtest_results") # Filtre pour obtenir uniquement les fichiers (pas les dossiers) - files = [f for f in files if os.path.isfile(os.path.join('/mnt/external', f))] + files = [f for f in files if os.path.isfile(os.path.join(FREQTRADE_USERDATA_DIR + "/backtest_results", f))] + + files2 = os.listdir(FREQTRADE_USERDATA_DIR + "/data/binance") + files2 = [f for f in files2 if os.path.isfile(os.path.join(FREQTRADE_USERDATA_DIR + "/data/binance", f))] # Retourne le template avec la liste des fichiers - return render_template('index.html', files=files) + return render_template('index.html', files=files, files2=files2) @app.route('/process', methods=['POST']) @@ -32,7 +35,7 @@ def process(): @app.route('/read_json/') def read_json(filename): - full_path = os.path.join(FILES_DIR, filename) + full_path = os.path.join(FREQTRADE_USERDATA_DIR + "/backtest_results", filename) if filename.endswith('.json'): with open(full_path) as f: @@ -66,16 +69,28 @@ def read_json(filename): try: data = joblib.load(f) if isinstance(data, pd.DataFrame): - return data.to_json(orient='split'), 200, {'Content-Type': 'application/json'} - if isinstance(data, dict): - df = pd.DataFrame.from_dict(data) - return df.to_json(orient='split'), 200, {'Content-Type': 'application/json'} - if isinstance(data, list): + print("dataframe") + zip_contents[name] = data.to_csv() #orient='split'), 200, {'Content-Type': 'application/json'} + elif isinstance(data, dict): + # On suppose qu’il y a un seul modèle/clé au premier niveau + outer_key = list(data.keys())[0] + inner_dict = data[outer_key] + + if isinstance(inner_dict, dict): + # On suppose qu’il y a une seule paire (ex: 'BTC/USDT') + inner_key = list(inner_dict.keys())[0] + df = inner_dict[inner_key] + print(df) + if isinstance(df, pd.DataFrame): + zip_contents[name] = df.to_html() #json(orient='split'), 200, {'Content-Type': 'application/json'} + elif isinstance(data, list): + print('list') df = pd.DataFrame(data) - return df.to_json(orient='split'), 200, {'Content-Type': 'application/json'} - return json.dumps({"error": f"Type {type(data)} non géré."}), 200 + zip_contents[name] = df.to_html() #orient='split'), 200, {'Content-Type': 'application/json'} + else: + zip_contents[name] = json.dumps({"error": f"Type {type(data)} non géré."}), 200 except Exception as e: - return json.dumps({"error": str(e)}), 500 + zip_contents[name] = json.dumps({"error": str(e)}), 500 return json.dumps(zip_contents) except Exception as e: return json.dumps({"error": str(e)}), 500 @@ -83,5 +98,18 @@ def read_json(filename): return json.dumps({"error": "Fichier non pris en charge"}), 400 +@app.route('/read_feather/') +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") + except Exception as e: + print(e) + return jsonify({"error": str(e)}), 500 + + if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/src/static/css/style.css b/src/static/css/style.css new file mode 100644 index 0000000..57f4b74 --- /dev/null +++ b/src/static/css/style.css @@ -0,0 +1,87 @@ + /* Style de la page */ + body { + font-family: Arial, sans-serif; + display: flex; + } + + /* Colonne gauche pour les fichiers */ + .file-list { + width: 200px; + padding: 10px; + border-right: 1px solid #ccc; + } + + /* Colonne pour le graphique */ + .graph-container { + flex-grow: 1; + padding: 10px; + } + + /* Liste des fichiers */ + ul { + list-style-type: none; + padding: 0; + } + + li { + padding: 5px; + border-bottom: 1px solid #ddd; + } + + li:hover { + background-color: #f0f0f0; + } + + + .file-list li { + cursor: pointer; + margin: 5px 0; + } + .content { + flex-grow: 1; + padding: 10px; + } + .tab { + display: none; + } + .tab.active { + display: block; + } + .tab-header { + font-weight: bold; + margin-top: 20px; + } + pre { + background: #f5f5f5; + padding: 10px; + overflow-x: auto; + } + +#json-tabs { + display: flex; + flex-direction: column; +} + +#tab-buttons { + list-style: none; + padding: 0; + display: flex; + gap: 0.5rem; +} + +#tab-buttons li { + display: inline; +} + +#tab-buttons button { + padding: 0.4rem 1rem; + cursor: pointer; +} + +.tab-content { + border: 1px solid #ccc; + padding: 1rem; + background: #f9f9f9; + margin-top: 1rem; + white-space: pre-wrap; +} diff --git a/src/static/js/functions.js b/src/static/js/functions.js index 069dc10..b1898f6 100644 --- a/src/static/js/functions.js +++ b/src/static/js/functions.js @@ -77,4 +77,83 @@ function graph() { // Utiliser les options pour afficher le graphique chart.setOption(option); - } \ No newline at end of file + } + +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(''); + } + + // ECharts (Close price over time) + const chart = echarts.init(document.getElementById('chart')); +// const option = { +// xAxis: { +// type: 'category', +// data: data.map(d => d.date) +// }, +// yAxis: { +// type: 'value' +// }, +// series: [{ +// type: 'line', +// name: 'Close', +// data: data.map(d => d.close) +// }] +// }; + // 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); + }); +} diff --git a/src/templates/index.html b/src/templates/index.html index 8a2784b..a2c13d3 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -6,96 +6,7 @@ Statistiques Freqtrade - + @@ -105,6 +16,10 @@ {% for file in files %}
  • {{ file }}
  • {% endfor %} + + {% for file in files2 %} +
  • {{ file }}
  • + {% endfor %}
    @@ -114,16 +29,19 @@
    +
    +

    Données

    +
    + + + + -
    - - -