function loadJson(filename) { fetch(`/read_json/${filename}`) .then(res => res.json()) .then(data => { const tabButtons = document.getElementById('tab-buttons'); const tabContents = document.getElementById('tab-contents'); tabButtons.innerHTML = ''; tabContents.innerHTML = ''; const isMulti = typeof data === 'object' && !Array.isArray(data); if (isMulti) { let count = 0 Object.entries(data).forEach(([name, content], index) => { 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) } }); } else { const div = document.createElement('div'); div.innerHTML = `
${JSON.stringify(data, null, 2)}
`; tabContents.appendChild(div); } }) .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(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]); const thead = `${headers.map(h => `${h}`).join('')}`; const tbody = rows.map(r => `${headers.map(h => `${r[h]}`).join('')}`).join(''); return `${thead}${tbody}
`; } function showTab(tabId) { document.querySelectorAll('.tab-content').forEach(div => { div.style.display = 'none'; }); document.getElementById(tabId).style.display = 'block'; } function graph() { // Initialiser l'instance de graphique var chart = echarts.init(document.getElementById('chart')); // Définir les options du graphique var option = { title: { text: 'Exemple de Graphique Linéaire' }, tooltip: {}, legend: { data: ['Ventes'] }, xAxis: { data: ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'] }, yAxis: {}, series: [{ name: 'Ventes', type: 'line', data: [5, 20, 36, 10, 10, 20, 30] }] }; // Utiliser les options pour afficher le graphique chart.setOption(option); } function renderChart(data, filename, create_columns) { 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 = ""; const indicators = document.getElementById('indicators'); indicators.innerHTML = string } let totalPoints = data.length; let visiblePoints = 100; let startPercent = ((totalPoints - visiblePoints) / totalPoints) * 100; // ECharts (Close price over time) if (create_columns === true) { var tab_element = document.getElementById('tab-contents') var index_tabs = tab_element.childNodes.length addTab('Graph', 'chart' /*+ index_tabs*/, "{}") addTab('Données', 'data' /*+ index_tabs*/, JSON.stringify(data)) } const chart = echarts.init(document.getElementById('tab-chart' /*_' + index_tabs*/), null, { width: window.innerWidth - 250, height: window.innerHeight - 150 }); window.addEventListener('resize', function() {chart.resize();}); const series = [{ type: 'line', name: 'Close', data: data.map(d => d.close) }] 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)' } }) } // Bougies const result = data.map(({ open, close, low, high }) => [open, close, low, high]); series.push({ type: 'candlestick', data: result, yAxisIndex: 0, itemStyle: { color: '#00da3c', // hausse (close > open) => vert color0: '#ec0000', // baisse (close < open) => rouge borderColor: '#008F28', borderColor0: '#8A0000' }, markPoint: { label: { formatter: function (param) { return param != null ? Math.round(param.value) + '' : ''; } }, data: marks, tooltip: { formatter: function (param) { return param.name + '
' + (param.data.coord || ''); } } } } ) // Achat series.push({ name: 'Buy', type: 'scatter', symbolSize: 10, itemStyle: { color: '#00aa00' }, // label: { // show: true, // position: 'top', // ou 'right', 'inside', etc. // formatter: function (param) { // return param.value[2]; // ou par ex. param.value[1] pour afficher le prix // }, // fontSize: 12, // color: '#000' // }, data: data .filter(d => d.enter_long === 1) .map(d => { const date = new Date(d.date).toLocaleString('fr-FR', options); return [date, d.close, d.enter_tag]; }) }) // Volume series.push({ name: 'Volume', type: 'bar', xAxisIndex: 1, yAxisIndex: 2, itemStyle: { color: '#7fbe9e' }, emphasis: { itemStyle: { color: '#140' } }, data: data.map(d => d.volume) }) for (var key in cols) { var value = cols[key]; element=document.getElementById(value) if (element) { if (element.checked) { // RSI if (value.toLowerCase().indexOf('rsi') >= 0) { series.push({ name: value, type: 'line', yAxisIndex: 1, lineStyle: { color: stringToColor(value) }, data: data.map(d => d[value]) }) } else if (value.toLowerCase().indexOf('pct') >= 0 | value.indexOf('percent') >= 0) { series.push({ name: value, type: 'line', yAxisIndex: 3, lineStyle: { color: stringToColor(value) }, data: data.map(d => d[value]) }) } else { series.push({ name: value, type: 'line', yAxisIndex: 0, data: data.map(d => d[value]), smooth: true, lineStyle: { color: stringToColor(value) } }) } } } } var option = { title: { text: strategy_name }, 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.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: [ // yAxisIndex: 0 { type: 'value', min: 'dataMin', // <-- commence à la plus petite valeur max: 'dataMax' // <-- adapte aussi le maximum }, // yAxisIndex: 1 { type: 'value', // Axe pour le RSI name: 'RSI', position: 'right', min: 0, max: 100 }, // yAxisIndex: 2 { gridIndex: 1, min: 'dataMin', // <-- commence à la plus petite valeur max: 'dataMax' // <-- adapte aussi le maximum }, // yAxisIndex: 3 { type: 'value', // Axe pour le RSI name: 'PCT', position: 'right', min: -1, max: 1 }, ], grid: [ { left: '10%', right: '8%', height: '70%' }, { left: '10%', right: '8%', top: '80%', height: '10%' } ], series: series } chart.setOption(option) document.querySelectorAll('.indicators li').forEach(li => { if (li.textContent.trim().endsWith('_1h')) { li.classList.add('is-1h'); } if (li.textContent.trim().endsWith('_1d')) { li.classList.add('is-1d'); } }); document.querySelectorAll('.indicatorsReport li').forEach(li => { if (li.textContent.trim().endsWith('_1h')) { li.classList.add('is-1h'); } if (li.textContent.trim().endsWith('_1d')) { li.classList.add('is-1d'); } }); } 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) initReport(data) }) } function initReport(data) { string = "" const indicators = document.getElementById('indicatorsReport') indicators.innerHTML = string } function generateModel() { const element = document.getElementById('current_file_name'); let filename = element.value const indicators = Array.from(selectedReportIndicators).join(','); fetch(`/generate_model?filename=${filename}`) .then(alert('Generation en cours')); } function generateReport() { const element = document.getElementById('current_file_name'); let filename = element.value const indicators = Array.from(selectedReportIndicators).join(','); fetch(`/generate_report?filename=${filename}&indicators=${indicators}`) .then(alert('Generation en cours')); } let selectedIndicators = new Set(); let selectedReportIndicators = new Set(); function toggleIndicator(checkbox) { const indicator = checkbox.value; if (checkbox.checked) { selectedIndicators.add(indicator); } else { selectedIndicators.delete(indicator); } loadChartWithIndicators(); } function toggleReportIndicator(checkbox) { const indicator = checkbox.value; if (checkbox.checked) { selectedReportIndicators.add(indicator); } else { selectedReportIndicators.delete(indicator); } } 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 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%)`; } function init() { const dialog = document.getElementById('indicatorDialog'); document.getElementById('openIndicatorsBtn').addEventListener('click', () => { dialog.showModal(); }); document.getElementById('closeIndicatorsBtn').addEventListener('click', () => { dialog.close(); }); const dialogReport = document.getElementById('indicatorReportDialog'); document.getElementById('openIndicatorsReportBtn').addEventListener('click', () => { dialogReport.showModal(); }); document.getElementById('closeIndicatorsReportBtn').addEventListener('click', () => { dialogReport.close(); }); }