Affichage graph des données
This commit is contained in:
@@ -7,4 +7,4 @@ matplotlib
|
|||||||
Flask==2.2.3
|
Flask==2.2.3
|
||||||
Werkzeug==2.2.3
|
Werkzeug==2.2.3
|
||||||
joblib==1.4.2
|
joblib==1.4.2
|
||||||
|
pyarrow
|
||||||
|
|||||||
54
src/app.py
54
src/app.py
@@ -9,19 +9,22 @@ from io import TextIOWrapper
|
|||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
FILES_DIR = '/mnt/external'
|
FREQTRADE_USERDATA_DIR = '/mnt/external'
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def home():
|
def home():
|
||||||
# Liste les fichiers dans le répertoire monté
|
# 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)
|
# 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
|
# 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'])
|
@app.route('/process', methods=['POST'])
|
||||||
@@ -32,7 +35,7 @@ def process():
|
|||||||
|
|
||||||
@app.route('/read_json/<path:filename>')
|
@app.route('/read_json/<path:filename>')
|
||||||
def read_json(filename):
|
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'):
|
if filename.endswith('.json'):
|
||||||
with open(full_path) as f:
|
with open(full_path) as f:
|
||||||
@@ -66,16 +69,28 @@ def read_json(filename):
|
|||||||
try:
|
try:
|
||||||
data = joblib.load(f)
|
data = joblib.load(f)
|
||||||
if isinstance(data, pd.DataFrame):
|
if isinstance(data, pd.DataFrame):
|
||||||
return data.to_json(orient='split'), 200, {'Content-Type': 'application/json'}
|
print("dataframe")
|
||||||
if isinstance(data, dict):
|
zip_contents[name] = data.to_csv() #orient='split'), 200, {'Content-Type': 'application/json'}
|
||||||
df = pd.DataFrame.from_dict(data)
|
elif isinstance(data, dict):
|
||||||
return df.to_json(orient='split'), 200, {'Content-Type': 'application/json'}
|
# On suppose qu’il y a un seul modèle/clé au premier niveau
|
||||||
if isinstance(data, list):
|
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)
|
df = pd.DataFrame(data)
|
||||||
return df.to_json(orient='split'), 200, {'Content-Type': 'application/json'}
|
zip_contents[name] = df.to_html() #orient='split'), 200, {'Content-Type': 'application/json'}
|
||||||
return json.dumps({"error": f"Type {type(data)} non géré."}), 200
|
else:
|
||||||
|
zip_contents[name] = json.dumps({"error": f"Type {type(data)} non géré."}), 200
|
||||||
except Exception as e:
|
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)
|
return json.dumps(zip_contents)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json.dumps({"error": str(e)}), 500
|
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
|
return json.dumps({"error": "Fichier non pris en charge"}), 400
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/read_feather/<path: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")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||||
|
|||||||
87
src/static/css/style.css
Normal file
87
src/static/css/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -77,4 +77,83 @@ function graph() {
|
|||||||
|
|
||||||
// Utiliser les options pour afficher le graphique
|
// Utiliser les options pour afficher le graphique
|
||||||
chart.setOption(option);
|
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 = '<tr>' + Object.keys(data[0]).map(k => `<th>${k}</th>`).join('') + '</tr>';
|
||||||
|
let rows = data.map(row => '<tr>' + Object.values(row).map(v => `<td>${v}</td>`).join('') + '</tr>');
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,96 +6,7 @@
|
|||||||
<title>Statistiques Freqtrade</title>
|
<title>Statistiques Freqtrade</title>
|
||||||
<script src="{{ url_for('static', filename='js/echarts.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/echarts.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/functions.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/functions.js') }}"></script>
|
||||||
<style>
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}"/>
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Colonne gauche avec les fichiers -->
|
<!-- Colonne gauche avec les fichiers -->
|
||||||
@@ -105,6 +16,10 @@
|
|||||||
{% for file in files %}
|
{% for file in files %}
|
||||||
<li onclick="loadJson('{{ file }}')">{{ file }}</li>
|
<li onclick="loadJson('{{ file }}')">{{ file }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for file in files2 %}
|
||||||
|
<li onclick="loadFeather('{{ file }}')">{{ file }}</li>
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -114,16 +29,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="chart" style="width: 100%; height: 1024px;"></div>
|
||||||
|
<h3>Données</h3>
|
||||||
|
<table border="1" id="data-table"></table>
|
||||||
|
|
||||||
|
<!--<div id="data-table" style="margin-left: 220px; padding: 20px;"></div>-->
|
||||||
|
|
||||||
|
|
||||||
<!-- Colonne droite avec le graphique (remplacez avec votre graphique)
|
<!-- Colonne droite avec le graphique (remplacez avec votre graphique)
|
||||||
<div class="graph-container">
|
<div class="graph-container">
|
||||||
<h3>Graphique</h3>
|
<h3>Graphique</h3>
|
||||||
<div id="chart" style="width: 600px; height: 400px;"></div>
|
<div id="chart" style="width: 600px; height: 400px;"></div>
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
<form action="/process" method="post">
|
|
||||||
<input type="text" name="data" placeholder="Entrez des données">
|
|
||||||
<button type="submit">Envoyer</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user