659 lines
20 KiB
JavaScript
659 lines
20 KiB
JavaScript
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 = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
|
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 "<p>Aucune donnée</p>";
|
|
const headers = Object.keys(rows[0]);
|
|
const thead = `<thead><tr>${headers.map(h => `<th>${h}</th>`).join('')}</tr></thead>`;
|
|
const tbody = rows.map(r => `<tr>${headers.map(h => `<td>${r[h]}</td>`).join('')}</tr>`).join('');
|
|
return `<table border="1">${thead}<tbody>${tbody}</tbody></table>`;
|
|
}
|
|
|
|
|
|
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 = "<ul class='indicators'>" +
|
|
Object.keys(data[0])
|
|
.filter(key => !['date', 'open', 'close', 'volume', 'high', 'low'].includes(key))
|
|
.map(cols => `<li><label><input id="${cols}" type="checkbox" value="${cols}" onchange="toggleIndicator(this)">${cols}</label></li>`)
|
|
.join('') +
|
|
"</ul>";
|
|
|
|
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: d.enter_tag,
|
|
coord: [date.toLocaleString('fr-FR', options), d.open_rate],
|
|
value: d.open_rate,
|
|
itemStyle: {
|
|
color: 'rgb(0,90,0)'
|
|
}
|
|
})
|
|
|
|
let count = 0
|
|
for (var key2 in d.orders) {
|
|
if (count == 0) {
|
|
count ++
|
|
}
|
|
else {
|
|
var order = d.orders[key2]
|
|
date = new Date(order.order_filled_timestamp);
|
|
marks.push({
|
|
name: d.enter_tag,
|
|
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: d.exit_reason,
|
|
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 + '<br>' + (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];
|
|
})
|
|
})
|
|
|
|
// # prediction
|
|
// series.push({
|
|
// name: 'Buy',
|
|
// type: 'scatter',
|
|
// symbolSize: 5,
|
|
// itemStyle: {
|
|
// color: '#aa0000'
|
|
// },
|
|
// data: data
|
|
// .filter(d => d['poly_pred_t+12'] === 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.toLowerCase().indexOf('diff') >= 0
|
|
| value.indexOf('percent') >= 0
|
|
| value.indexOf('deriv') >= 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 = "<ul class='indicators'>" +
|
|
Object.keys(data[0])
|
|
// .filter(key => !['date', 'open', 'close', 'volume', 'high', 'low'].includes(key))
|
|
.map(cols => `<li><label><input id="${cols}" type="checkbox" value="${cols}" onchange="toggleReportIndicator(this)">${cols}</label></li>`)
|
|
.join('') +
|
|
"</ul>"
|
|
|
|
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();
|
|
});
|
|
} |