From e34897e15477f1776da9ec5140bdb3e0aca1d672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Delacotte?= Date: Thu, 8 May 2025 19:22:48 +0200 Subject: [PATCH] =?UTF-8?q?travail=20avec=20le=20contenu=20des=20backtests?= =?UTF-8?q?=20et=20am=C3=A9lioration=20graphs=20avec=20puces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.py | 27 +++- src/static/js/functions.js | 310 +++++++++++++++++++++++++++++++++---- src/templates/index.html | 30 +++- 3 files changed, 324 insertions(+), 43 deletions(-) diff --git a/src/app.py b/src/app.py index 6617420..9089614 100644 --- a/src/app.py +++ b/src/app.py @@ -18,10 +18,10 @@ def home(): 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(FREQTRADE_USERDATA_DIR + "/backtest_results", f))] + files = [f for f in files if os.path.isfile(os.path.join(FREQTRADE_USERDATA_DIR + "/backtest_results", f)) and f.lower().endswith('.zip')] 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))] + files2 = [f for f in files2 if os.path.isfile(os.path.join(FREQTRADE_USERDATA_DIR + "/data/binance", f)) and f.lower().endswith('.feather')] # Retourne le template avec la liste des fichiers return render_template('index.html', files=files, files2=files2) @@ -63,30 +63,41 @@ def read_json(filename): for name in z.namelist(): if name.endswith('.json'): with z.open(name) as f: + print(f"load_json {name}") zip_contents[name] = json.load(f) + elif name.endswith('.feather'): + with z.open(name) as f: + dataframe = pd.read_feather(f) + zip_contents[name] = dataframe.to_json(orient="records") + elif name.endswith('.pkl'): with z.open(name) as f: try: data = joblib.load(f) if isinstance(data, pd.DataFrame): - print("dataframe") - zip_contents[name] = data.to_csv() #orient='split'), 200, {'Content-Type': 'application/json'} + print(f"dataframe {name}") + zip_contents[name] = data.to_json(orient='records') #, 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): + print(f"dict {name}") # 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'} + type = name + # if "signals" in name: + # type = 'signals' + # elif "exited" in name: + # type = "exited" + zip_contents[type] = df.to_json(orient='records') #, 200, {'Content-Type': 'application/json'} elif isinstance(data, list): - print('list') + print(f"list {name}") df = pd.DataFrame(data) - zip_contents[name] = df.to_html() #orient='split'), 200, {'Content-Type': 'application/json'} + zip_contents[name] = df.to_json(orient='records') # , 200, {'Content-Type': 'application/json'} else: zip_contents[name] = json.dumps({"error": f"Type {type(data)} non géré."}), 200 except Exception as e: diff --git a/src/static/js/functions.js b/src/static/js/functions.js index f8c5aa9..de82351 100644 --- a/src/static/js/functions.js +++ b/src/static/js/functions.js @@ -10,22 +10,32 @@ function loadJson(filename) { const isMulti = typeof data === 'object' && !Array.isArray(data); if (isMulti) { + let count = 0 Object.entries(data).forEach(([name, content], index) => { - const tabId = `tab-${index}`; - const li = document.createElement('li'); - const btn = document.createElement('button'); - btn.textContent = name; - btn.onclick = () => showTab(tabId); - li.appendChild(btn); - tabButtons.appendChild(li); + count = count + 1 + if (name.includes("signals")) { + sessionStorage.setItem('signals', content); + addTab('signals', count, content) + } + if (name.includes("exited")) { + sessionStorage.setItem('exited', content); + addTab('exited', count, content) + } + if (name.includes("json")) { + sessionStorage.setItem('result_strategy', JSON.stringify(content.strategy)); + sessionStorage.setItem('strategy_comparison', JSON.stringify(content.strategy_comparison)); + addTab('result', count, JSON.stringify(content.strategy)) + count = count + 1 + addTab('comparaison', count, JSON.stringify(content.strategy_comparison)) + } + if (name.includes("market")) { + sessionStorage.setItem('market', content); + addTab('market', count, content) + } - const div = document.createElement('div'); - div.id = tabId; - div.className = 'tab-content'; - div.style.display = index === 0 ? 'block' : 'none'; - div.innerHTML = Array.isArray(content) ? renderTable(content) : `
${JSON.stringify(content, null, 2)}
`; - tabContents.appendChild(div); }); +// renderChart(data, '', true) + } else { const div = document.createElement('div'); div.innerHTML = `
${JSON.stringify(data, null, 2)}
`; @@ -35,6 +45,152 @@ function loadJson(filename) { .catch(err => alert("Erreur : " + err.message)); } +function jsonToUl(json) { + if (typeof json !== 'object' || json === null) return document.createTextNode(json); + + const ul = document.createElement('ul'); + + for (const key in json) { + const li = document.createElement('li'); + const value = json[key]; + + if (typeof value === 'object' && value !== null) { + li.textContent = key; + li.appendChild(jsonToUl(value)); // recurse + } else { + li.textContent = `${key}: ${value}`; + } + + ul.appendChild(li); + } + + return ul; +} +//function jsonToTable(obj, depth = 0) { +// const table = document.createElement('table'); +// table.border = 1; +// +// for (const key in obj) { +// const row = document.createElement('tr'); +// +// const cellKey = document.createElement('td'); +// cellKey.textContent = key; +// row.appendChild(cellKey); +// +// const cellValue = document.createElement('td'); +// const value = obj[key]; +// +// if (typeof value === 'object' && value !== null && depth < 4) { +// cellValue.appendChild(jsonToTable(value, depth + 1)); // recurse +// } else { +// cellValue.textContent = value; +// } +// +// row.appendChild(cellValue); +// table.appendChild(row); +// } +// +// return table; +//} + +function jsonToTable(data, depth = 0) { + if (Array.isArray(data)) { + // Cas: tableau d'objets + if (data.length > 0 && typeof data[0] === 'object' && !Array.isArray(data[0])) { + const table = document.createElement('table'); + table.border = 1; + + // En-tête + const headerRow = document.createElement('tr'); + Object.keys(data[0]).forEach(key => { + const th = document.createElement('th'); + th.textContent = key; + headerRow.appendChild(th); + }); + table.appendChild(headerRow); + + // Lignes de données + data.forEach(item => { + const row = document.createElement('tr'); + Object.values(item).forEach(value => { + const td = document.createElement('td'); + td.textContent = (typeof value === 'object') ? JSON.stringify(value) : value; + row.appendChild(td); + }); + table.appendChild(row); + }); + + return table; + } else { + // Liste simple : afficher horizontalement + const table = document.createElement('table'); + table.border = 1; + const row = document.createElement('tr'); + data.forEach(value => { + const td = document.createElement('td'); + td.textContent = value; + row.appendChild(td); + }); + table.appendChild(row); + return table; + } + + } else if (typeof data === 'object' && data !== null) { + // Cas: objet clé/valeur + const table = document.createElement('table'); + table.border = 1; + + for (const key in data) { + const row = document.createElement('tr'); + + const tdKey = document.createElement('td'); + tdKey.textContent = key; + row.appendChild(tdKey); + + const tdValue = document.createElement('td'); + const value = data[key]; + + if (typeof value === 'object') { + tdValue.appendChild(jsonToTable(value, depth + 1)); + } else { + tdValue.textContent = value; + } + + row.appendChild(tdValue); + table.appendChild(row); + } + + return table; + } else { + const span = document.createElement('span'); + span.textContent = data; + return span; + } +} + + + +function addTab(name, index, content) { + const tabButtons = document.getElementById('tab-buttons'); + const tabContents = document.getElementById('tab-contents'); + const tabId = `tab-${index}`; + const li = document.createElement('li'); + const btn = document.createElement('button'); + btn.textContent = name; + btn.onclick = () => showTab(tabId); + li.appendChild(btn); + tabButtons.appendChild(li); + + const div = document.createElement('div'); + div.id = tabId; + div.className = 'tab-content'; + div.style.display = index === 0 ? 'block' : 'none'; +// div.innerHTML = Array.isArray(content) ? renderTable(content) : jsonToUl(JSON.parse(content)); + ul = jsonToTable(JSON.parse(content)) + div.appendChild(ul); + tabContents.appendChild(div); +} + function renderTable(rows) { if (!rows.length) return "

Aucune donnée

"; const headers = Object.keys(rows[0]); @@ -88,6 +244,15 @@ function renderChart(data, filename, create_columns) { // let rows = data.map(row => '' + Object.values(row).map(v => `${v}`).join('') + ''); // table.innerHTML = header + rows.join(''); // } + let signals = JSON.parse(sessionStorage.getItem("signals")); + let exited = JSON.parse(sessionStorage.getItem("exited")); + let market = JSON.parse(sessionStorage.getItem("market")); + let result_strategy = JSON.parse(sessionStorage.getItem("result_strategy")); + let strategy_comparison = JSON.parse(sessionStorage.getItem("strategy_comparison")); + + strategy_name = strategy_comparison[0].key; + result_of_strategy = result_strategy[strategy_name] + const cols = Object.keys(data[0]) if (create_columns === true) { // string = "" @@ -102,7 +267,6 @@ function renderChart(data, filename, create_columns) { indicators.innerHTML = string } - // const label = document.createElement('label'); // label.innerHTML = ` ${col}
`; // indicators.appendChild(label); @@ -139,6 +303,68 @@ function renderChart(data, filename, create_columns) { // df_ohlc = data[['date', 'open', 'close', 'low', 'high']] const result = data.map(({ open, close, low, high }) => [open, close, low, high]); + marks = [] + + const options = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + } + + for (var key in result_of_strategy.trades) { + var d = result_of_strategy.trades[key]; + var date = new Date(d.open_date); + marks.push({ + name: 'Buy', + coord: [date.toLocaleString('fr-FR', options), d.open_rate], + value: d.open_rate, + itemStyle: { + color: 'rgb(0,90,0)' + } + }) + + for (var key2 in d.orders) { + var order = d.orders[key2] + date = new Date(order.order_filled_timestamp); + marks.push({ + name: 'Buy', + coord: [date.toLocaleString('fr-FR', options), order.safe_price], + value: order.safe_price, + itemStyle: { + color: 'rgb(0,0,90)' + } + }) + } + + date = new Date(d.close_date); + marks.push({ + name: 'Sell', + coord: [date.toLocaleString('fr-FR', options), d.close_rate], + value: d.close_rate, + itemStyle: { + color: 'rgb(90,0,0)' + } + }) + // { +// name: 'highest value', +// type: 'max', +// valueDim: 'highest' +// }, +// { +// name: 'lowest value', +// type: 'min', +// valueDim: 'lowest' +// }, +// { +// name: 'average value on close', +// type: 'average', +// valueDim: 'close' +// } + } + series.push({ type: 'candlestick', data: result, @@ -147,10 +373,22 @@ function renderChart(data, filename, create_columns) { color0: '#00da3c', // bougie baissière borderColor: '#8A0000', borderColor0: '#008F28' + }, + markPoint: { + label: { + formatter: function (param) { + return param != null ? Math.round(param.value) + '' : ''; + } + }, + data: marks, + tooltip: { + formatter: function (param) { + return param.name + '
' + (param.data.coord || ''); + } + } } } ) -// console.log(result) series.push({ name: 'Volume', @@ -167,6 +405,7 @@ function renderChart(data, filename, create_columns) { }, data: data.map(d => d.volume) }) + for (var key in cols) { var value = cols[key]; element=document.getElementById(value) @@ -182,6 +421,7 @@ function renderChart(data, filename, create_columns) { } } } + var option = { title: { text: filename @@ -208,7 +448,7 @@ function renderChart(data, filename, create_columns) { } } }, - dataZoom: [ + dataZoom: [ { type: 'slider', // slider visible en bas start: startPercent, @@ -224,20 +464,20 @@ function renderChart(data, filename, create_columns) { 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 - }) - }, - { - type: 'category', - data: data.map(d => { - const date = new Date(d.date); - return date.toLocaleDateString('fr-FR'); // ex : 07/05/2025 - }), - gridIndex: 1 - } + type: 'category', + data: data.map(d => { + const date = new Date(d.date); + return date.toLocaleString('fr-FR', options); // ex : 07/05/2025 + }) + }, + { + type: 'category', + data: data.map(d => { + const date = new Date(d.date); + return date.toLocaleString('fr-FR', options); // ex : 07/05/2025 + }), + gridIndex: 1 + } ], yAxis: [{ type: 'value', @@ -312,3 +552,13 @@ function stringToColor(str) { return `hsl(${hue}, 70%, 50%)`; } +function init() { + const dialog = document.getElementById('indicatorDialog'); + document.getElementById('openIndicatorsBtn').addEventListener('click', () => { + dialog.showModal(); + }); + + document.getElementById('closeIndicatorsBtn').addEventListener('click', () => { + dialog.close(); + }); +} \ No newline at end of file diff --git a/src/templates/index.html b/src/templates/index.html index 6d3e0d4..1dc2e83 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -12,15 +12,34 @@

Fichiers :

-
@@ -29,7 +48,6 @@
-
@@ -45,6 +63,8 @@ --> + +