Skip to content

Commit 3db5afb

Browse files
committed
Merge branch 'dev'
2 parents 36707bb + 748d66b commit 3db5afb

12 files changed

Lines changed: 610 additions & 97 deletions

File tree

.dockerignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# .dockerignore
2+
.git
3+
.gitignore
4+
.env
5+
__pycache__
6+
*.pyc
7+
*.pyo
8+
*.pyd
9+
10+
# Ignora cartelle dati e log (fondamentale per evitare errori permessi)
11+
data/
12+
logs/
13+
config/credentials/

dashboard/home.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# dashboard/Home.py
2+
import streamlit as st
3+
import pandas as pd
4+
from dashboard.utils import load_portfolio_data, load_ohlc_summary
5+
6+
st.set_page_config(page_title="Petunia Dashboard", page_icon="🌸", layout="wide")
7+
8+
st.title("🌸 Petunia | System Overview")
9+
st.markdown("---")
10+
11+
# 1. KPI Portafoglio
12+
data = load_portfolio_data()
13+
df_cash = data.get('cash', pd.DataFrame())
14+
df_port = data.get('portfolio', pd.DataFrame())
15+
16+
col1, col2, col3, col4 = st.columns(4)
17+
18+
# Calcolo Totali
19+
cash = float(df_cash.iloc[0]['cash']) if not df_cash.empty else 0.0
20+
invested = (df_port['price'] * df_port['size']).sum() if not df_port.empty else 0.0
21+
total_equity = cash + invested
22+
active_tickers = len(df_port) if not df_port.empty else 0
23+
24+
col1.metric("Total Equity", f"€ {total_equity:,.2f}")
25+
col2.metric("Cash Available", f"€ {cash:,.2f}")
26+
col3.metric("Invested Capital", f"€ {invested:,.2f}")
27+
col4.metric("Active Positions", f"{active_tickers}")
28+
29+
st.markdown("---")
30+
31+
# 2. Stato Sistema
32+
st.subheader("📡 System Status")
33+
34+
# Tabella riassuntiva Ticker
35+
df_ohlc = load_ohlc_summary()
36+
37+
c1, c2 = st.columns([2, 1])
38+
39+
with c1:
40+
if not df_ohlc.empty:
41+
# Ci assicuriamo che 'count' sia un intero
42+
df_ohlc['count'] = df_ohlc['count'].astype(int)
43+
44+
st.dataframe(
45+
df_ohlc,
46+
column_config={
47+
"ticker": "Ticker",
48+
"last_date": "Last Update",
49+
"count": st.column_config.ProgressColumn(
50+
"Data Points",
51+
help="Numero di candele OHLC salvate",
52+
format="%d",
53+
min_value=0,
54+
max_value=int(df_ohlc['count'].max())
55+
),
56+
},
57+
use_container_width=True,
58+
hide_index=True
59+
)
60+
else:
61+
st.warning("Nessun dato OHLC nel database.")
62+
63+
with c2:
64+
st.info("ℹ️ **Quick Actions**")
65+
if st.button("🔄 Refresh Data View"):
66+
st.rerun()
67+
68+
st.markdown("Per operazioni di scrittura o log, vai al **Control Panel**.")

dashboard/pages/backtest_lab.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import streamlit as st
2+
import pandas as pd
3+
from pathlib import Path
4+
import json
5+
from services.backtest import run_backtest # Importiamo la funzione backend
6+
7+
st.set_page_config(page_title="Backtest Lab", page_icon="🧪", layout="wide")
8+
9+
st.title("🧪 Backtest Laboratory")
10+
11+
# Sidebar: Configurazione
12+
with st.sidebar:
13+
st.header("⚙️ Configuration")
14+
15+
strategy = st.selectbox("Strategy", ["RSI", "MACD (Coming Soon)"])
16+
17+
st.subheader("Parameters")
18+
initial_cap = st.number_input("Initial Capital (€)", value=10000, step=1000)
19+
years = st.slider("Years History", 1, 5, 2)
20+
21+
# Parametri Dinamici per RSI
22+
params = {}
23+
if strategy == "RSI":
24+
params['rsi_period'] = st.slider("RSI Period", 5, 30, 14)
25+
params['rsi_lower'] = st.slider("Oversold (<)", 10, 45, 30)
26+
params['rsi_upper'] = st.slider("Overbought (>)", 55, 90, 70)
27+
params['atr_period'] = st.slider("ATR Period", 5, 30, 14)
28+
29+
run_btn = st.button("🚀 RUN BACKTEST", type="primary")
30+
31+
# Main Area
32+
tab_run, tab_history = st.tabs(["Current Run", "History Archive"])
33+
34+
with tab_run:
35+
if run_btn:
36+
with st.spinner("Running simulation... (This may take a moment)"):
37+
try:
38+
# Eseguiamo il backtest
39+
result_path = run_backtest(
40+
strategy_name=strategy,
41+
initial_capital=initial_cap,
42+
days_history=years*365,
43+
strategy_params=params
44+
)
45+
st.success(f"Simulation completed! Saved in: {result_path}")
46+
47+
# Visualizzazione Rapida Risultati
48+
p = Path(result_path)
49+
50+
# Immagine
51+
if (p / "chart.png").exists():
52+
# CORRETTO: use_container_width invece di width
53+
st.image(str(p / "chart.png"), use_container_width=True)
54+
55+
# Metriche da JSON
56+
if (p / "config.json").exists():
57+
with open(p / "config.json") as f:
58+
res = json.load(f)
59+
60+
m1, m2, m3 = st.columns(3)
61+
m1.metric("Final Equity", f"€ {res.get('final_equity', 0):,.2f}")
62+
m2.metric("Total Trades", res.get('total_trades', 0))
63+
# Calcolo ROI al volo
64+
roi = ((res.get('final_equity', 0) - initial_cap) / initial_cap) * 100
65+
m3.metric("Total ROI", f"{roi:+.2f}%")
66+
67+
except Exception as e:
68+
st.error(f"Errore durante il backtest: {e}")
69+
70+
with tab_history:
71+
st.write("📂 **Previous Runs Browser**")
72+
base_dir = Path("data/backtests")
73+
74+
if base_dir.exists():
75+
# Logica per esplorare le cartelle
76+
strategies = [x.name for x in base_dir.iterdir() if x.is_dir()]
77+
78+
# --- LAYOUT A COLONNE PER I SELETTORI ---
79+
c_strat, c_run = st.columns(2)
80+
81+
with c_strat:
82+
sel_strat = st.selectbox("Select Strategy Folder", strategies)
83+
84+
sel_run = None
85+
if sel_strat:
86+
with c_run:
87+
# Controllo se la cartella esiste e non è vuota
88+
strat_path = base_dir / sel_strat
89+
if strat_path.exists():
90+
runs = sorted([x.name for x in strat_path.iterdir()], reverse=True)
91+
sel_run = st.selectbox("Select Timestamp", runs)
92+
93+
if sel_run:
94+
run_path = base_dir / sel_strat / sel_run
95+
st.caption(f"Path: {run_path}")
96+
st.markdown("---")
97+
98+
# Visualizzazione Dati
99+
col_img, col_data = st.columns([1, 1])
100+
101+
with col_img:
102+
if (run_path / "chart.png").exists():
103+
# CORRETTO: use_container_width
104+
st.image(str(run_path / "chart.png"), use_container_width=True)
105+
106+
with col_data:
107+
if (run_path / "config.json").exists():
108+
with open(run_path / "config.json") as f:
109+
conf = json.load(f)
110+
st.json(conf, expanded=False)
111+
112+
if (run_path / "trades.csv").exists():
113+
# CORRETTO: use_container_width e height numerico
114+
st.dataframe(pd.read_csv(run_path / "trades.csv"), use_container_width=True, height=300)

dashboard/pages/control_panel.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import streamlit as st
2+
from pathlib import Path
3+
import time
4+
5+
# Import Servizi Backend
6+
from src.yfinance_manager import YFinanceManager
7+
from src.database_manager import DatabaseManager
8+
from src.drive_manager import DriveManager
9+
10+
st.set_page_config(page_title="Control Panel", page_icon="🕹️", layout="wide")
11+
st.title("🕹️ Control Panel & Maintenance")
12+
13+
# Layout a due colonne principali
14+
col_ops, col_logs = st.columns([1, 1.5])
15+
16+
# --- COLONNA SINISTRA: OPERAZIONI ---
17+
with col_ops:
18+
st.subheader("🛠️ Data Management")
19+
20+
with st.container(border=True):
21+
st.write("### 📥 Fetch Historical Data")
22+
st.caption("Scarica dati da Yahoo Finance e popola il DB.")
23+
24+
# Form per i parametri
25+
years_to_fetch = st.number_input("Years of History", min_value=1, max_value=10, value=1)
26+
27+
if st.button("🚀 Start Data Fetch", type="primary"):
28+
status_container = st.status("Avvio procedura di download...", expanded=True)
29+
try:
30+
# 1. Recupero Ticker da GSheets
31+
status_container.write("Lettura Universe da Google Sheets...")
32+
drive = DriveManager()
33+
tickers = drive.get_universe_tickers()
34+
35+
if not tickers:
36+
status_container.error("Nessun ticker trovato nel foglio 'Universe'!")
37+
else:
38+
status_container.write(f"Trovati {len(tickers)} ticker: {tickers}")
39+
40+
# 2. Download da YFinance
41+
status_container.write(f"Download di {years_to_fetch} anni di storico...")
42+
yf = YFinanceManager()
43+
data = yf.fetch_history(tickers, years=years_to_fetch)
44+
45+
if data:
46+
# 3. Salvataggio DB
47+
status_container.write(f"Salvataggio di {len(data)} righe nel Database...")
48+
db = DatabaseManager()
49+
db.upsert_ohlc(data)
50+
51+
status_container.update(label="✅ Operazione completata con successo!", state="complete", expanded=False)
52+
st.success(f"Database aggiornato con {len(data)} nuovi record!")
53+
time.sleep(2)
54+
st.rerun() # Ricarica per aggiornare eventuali metriche
55+
else:
56+
status_container.error("Nessun dato ricevuto da Yahoo Finance.")
57+
58+
except Exception as e:
59+
status_container.error(f"Errore critico: {e}")
60+
61+
st.markdown("---")
62+
63+
with st.container(border=True):
64+
st.write("### ⚠️ Danger Zone")
65+
if st.button("🗑️ Reset Database Schema"):
66+
if st.checkbox("Confermo di voler cancellare TUTTI i dati"):
67+
db = DatabaseManager()
68+
db.drop_schema()
69+
db.init_schema()
70+
st.warning("Database resettato e tabelle ricreate.")
71+
72+
# --- COLONNA DESTRA: LOGS ---
73+
with col_logs:
74+
st.subheader("📜 System Logs Inspector")
75+
76+
# 1. Trova tutti i file .log nella cartella logs/
77+
log_dir = Path("logs")
78+
if log_dir.exists():
79+
# Glob restituisce i file, ne prendiamo solo il nome
80+
log_files = [f.name for f in log_dir.glob("*.log")]
81+
82+
if log_files:
83+
# Selectbox per scegliere IL servizio
84+
selected_file = st.selectbox("Select Log File:", log_files, index=0)
85+
86+
# Slider per quante righe leggere
87+
lines_count = st.slider("Lines to view:", 10, 200, 50)
88+
89+
# Tasto refresh manuale
90+
if st.button("🔄 Refresh Logs"):
91+
st.rerun()
92+
93+
# Lettura e Visualizzazione
94+
log_path = log_dir / selected_file
95+
try:
96+
with open(log_path, "r", encoding="utf-8") as f:
97+
# Leggiamo tutto e prendiamo le ultime N righe
98+
all_lines = f.readlines()
99+
tail_lines = all_lines[-lines_count:]
100+
101+
# Mostriamo in un blocco di codice scrollabile
102+
log_content = "".join(tail_lines)
103+
if not log_content.strip():
104+
st.info("File di log vuoto.")
105+
else:
106+
st.code(log_content, language="log")
107+
108+
except Exception as e:
109+
st.error(f"Impossibile leggere il file: {e}")
110+
else:
111+
st.warning("Nessun file .log trovato. Esegui qualche operazione prima!")
112+
else:
113+
st.error("Cartella 'logs' non trovata. Verifica il volume Docker.")

0 commit comments

Comments
 (0)