rilascio versione stabile
This commit is contained in:
@@ -13,6 +13,8 @@ import os
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import yaml
|
||||
import logging
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
@@ -25,10 +27,13 @@ from pypfopt.exceptions import OptimizationError
|
||||
OUTPUT_DIR = "Output"
|
||||
PLOT_DIR = "Plot"
|
||||
INPUT_DIR = "Input"
|
||||
CONFIG_FILE = "config.yaml"
|
||||
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
os.makedirs(PLOT_DIR, exist_ok=True)
|
||||
os.makedirs(INPUT_DIR, exist_ok=True)
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def excel_path(filename: str) -> str:
|
||||
"""Costruisce il percorso completo per un file Excel nella cartella di output."""
|
||||
@@ -38,6 +43,87 @@ def plot_path(filename: str) -> str:
|
||||
"""Costruisce il percorso completo per un file PNG nella cartella Plot."""
|
||||
return os.path.join(PLOT_DIR, filename)
|
||||
|
||||
DEFAULT_VOL_TARGETS = [
|
||||
{"years": 5, "target_vol": 0.06, "name": "VAR3_5Y"},
|
||||
{"years": 1, "target_vol": 0.12, "name": "VAR6_1Y"},
|
||||
{"years": 3, "target_vol": 0.12, "name": "VAR6_3Y"},
|
||||
{"years": 5, "target_vol": 0.12, "name": "VAR6_5Y"},
|
||||
{"years": 5, "target_vol": 0.18, "name": "VAR9_5Y"},
|
||||
]
|
||||
|
||||
DEFAULT_ASSET_CLASS_LIMITS = {
|
||||
'Azionari': 0.75, 'Obbligazionari': 0.75,
|
||||
'Metalli Preziosi': 0.20, 'Materie Prime': 0.05,
|
||||
'Immobiliare': 0.05, 'Criptovalute': 0.05, 'Monetari': 0.1
|
||||
}
|
||||
|
||||
|
||||
def load_targets_and_limits(config_file: str):
|
||||
"""Legge target di volatilità e limiti asset class dal file di configurazione."""
|
||||
try:
|
||||
with open(config_file, "r", encoding="utf-8") as f:
|
||||
cfg = yaml.safe_load(f) or {}
|
||||
except FileNotFoundError:
|
||||
logger.error("File di configurazione mancante: %s", config_file)
|
||||
sys.exit(1)
|
||||
|
||||
vt_cfg = cfg.get("volatility_targets", {})
|
||||
vt_list = []
|
||||
if isinstance(vt_cfg, dict):
|
||||
vt_list = vt_cfg.get("default") or []
|
||||
elif isinstance(vt_cfg, list):
|
||||
vt_list = vt_cfg
|
||||
if not vt_list:
|
||||
logger.error("Sezione 'volatility_targets' mancante o vuota nel file di configurazione.")
|
||||
sys.exit(1)
|
||||
|
||||
volatility_targets_local = {
|
||||
(int(item["years"]), float(item["target_vol"])): item["name"]
|
||||
for item in vt_list
|
||||
if "years" in item and "target_vol" in item and "name" in item
|
||||
}
|
||||
if not volatility_targets_local:
|
||||
logger.error("Nessun target di volatilita valido trovato in configurazione.")
|
||||
sys.exit(1)
|
||||
|
||||
asset_limits_cfg = cfg.get("asset_class_limits") or {}
|
||||
if not asset_limits_cfg:
|
||||
logger.error("Sezione 'asset_class_limits' mancante o vuota nel file di configurazione.")
|
||||
sys.exit(1)
|
||||
asset_class_limits_local = {k: float(v) for k, v in asset_limits_cfg.items()}
|
||||
|
||||
return volatility_targets_local, asset_class_limits_local
|
||||
|
||||
|
||||
def validate_universe(df_universe: pd.DataFrame):
|
||||
"""Verifica colonne obbligatorie e duplicati ISIN nel file universo."""
|
||||
required_cols = ['ISIN', 'Nome', 'Categoria', 'Asset Class']
|
||||
missing_cols = [c for c in required_cols if c not in df_universe.columns]
|
||||
if missing_cols:
|
||||
logger.error("Colonne mancanti nel file universo: %s", ", ".join(missing_cols))
|
||||
sys.exit(1)
|
||||
dup_isin = df_universe['ISIN'][df_universe['ISIN'].duplicated()].unique().tolist()
|
||||
if dup_isin:
|
||||
logger.warning("ISIN duplicati nel file universo: %s", dup_isin)
|
||||
empty_isin = df_universe['ISIN'].isna().sum()
|
||||
if empty_isin:
|
||||
logger.warning("Righe con ISIN mancante nel file universo: %d", int(empty_isin))
|
||||
|
||||
|
||||
def validate_returns_frame(df_returns: pd.DataFrame, threshold: float = 0.2):
|
||||
"""Avvisa se i rendimenti hanno molte celle NaN prima del riempimento."""
|
||||
if df_returns.empty:
|
||||
logger.error("Nessun dato di rendimento recuperato: final_df vuoto.")
|
||||
sys.exit(1)
|
||||
na_ratio = df_returns.isna().mean()
|
||||
high_na = na_ratio[na_ratio > threshold]
|
||||
if not high_na.empty:
|
||||
logger.warning(
|
||||
"Colonne con >%.0f%% di NaN prima del fill: %s",
|
||||
threshold * 100,
|
||||
", ".join([f"{c} ({v:.0%})" for c, v in high_na.items()])
|
||||
)
|
||||
|
||||
# ---------------------------------
|
||||
# Utility per R^2 sull’equity line
|
||||
# ---------------------------------
|
||||
@@ -291,6 +377,7 @@ df = pd.read_excel(
|
||||
usecols=['ISIN', 'Nome', 'Categoria', 'Asset Class', 'PesoMax', 'PesoFisso', 'Codice Titolo'],
|
||||
dtype={'Codice Titolo': str}
|
||||
)
|
||||
validate_universe(df)
|
||||
|
||||
# =========================
|
||||
# SERIE STORICHE RENDIMENTI
|
||||
@@ -320,6 +407,7 @@ for isin in df['ISIN'].unique():
|
||||
except SQLAlchemyError as e:
|
||||
print(f"Errore durante l'esecuzione della stored procedure per {isin}:", e)
|
||||
|
||||
validate_returns_frame(final_df)
|
||||
final_df.fillna(0, inplace=True)
|
||||
|
||||
# -------- H_min sempre su 5 anni (21 gg = 1 mese) --------
|
||||
@@ -328,17 +416,7 @@ five_year_df = final_df.loc[end_date - pd.DateOffset(years=5): end_date]
|
||||
# =========================
|
||||
# CONFIGURAZIONE OBIETTIVI
|
||||
# =========================
|
||||
volatility_targets = {
|
||||
# (1, 0.06): 'VAR3_1Y',
|
||||
# (3, 0.06): 'VAR3_3Y',
|
||||
(5, 0.06): 'VAR3_5Y',
|
||||
(1, 0.12): 'VAR6_1Y',
|
||||
(3, 0.12): 'VAR6_3Y',
|
||||
(5, 0.12): 'VAR6_5Y',
|
||||
# (1, 0.18): 'VAR9_1Y',
|
||||
# (3, 0.18): 'VAR9_3Y',
|
||||
(5, 0.18): 'VAR9_5Y'
|
||||
}
|
||||
volatility_targets, asset_class_limits = load_targets_and_limits(CONFIG_FILE)
|
||||
days_per_year = 252
|
||||
riskfree_rate = 0.02
|
||||
|
||||
@@ -432,11 +510,6 @@ for (years, target_vol), name in volatility_targets.items():
|
||||
ef.add_constraint(lambda w, idxs=idxs, maxw=maxw: sum(w[i] for i in idxs) <= maxw)
|
||||
|
||||
# Vincoli per Asset Class
|
||||
asset_class_limits = {
|
||||
'Azionari': 0.75, 'Obbligazionari': 0.75,
|
||||
'Metalli Preziosi': 0.20, 'Materie Prime': 0.05,
|
||||
'Immobiliare': 0.05, 'Criptovalute': 0.05, 'Monetari': 0.1
|
||||
}
|
||||
for ac, maxw in asset_class_limits.items():
|
||||
isin_list = df[df['Asset Class'] == ac]['ISIN'].tolist()
|
||||
idxs = [period_df.columns.get_loc(isin) for isin in isin_list if isin in period_df.columns]
|
||||
@@ -25,23 +25,29 @@ def plot_path(filename: str) -> str:
|
||||
"""Percorso completo per i file di grafico."""
|
||||
return os.path.join(PLOT_DIR, filename)
|
||||
|
||||
# Configurazione della connessione al database
|
||||
username = 'readonly'
|
||||
password = 'e8nqtSa39L4Le3'
|
||||
host = '26.69.45.60'
|
||||
database = 'FirstSolutionDB'
|
||||
port = 1433
|
||||
connection_string = f"mssql+pyodbc://{username}:{password}@{host}:{port}/{database}?driver=ODBC+Driver+17+for+SQL+Server"
|
||||
# Configurazione della connessione al database (da connection.txt)
|
||||
params = {}
|
||||
with open("connection.txt", "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
key, value = line.split('=', 1)
|
||||
params[key.strip()] = value.strip()
|
||||
|
||||
username = params.get('username')
|
||||
password = params.get('password')
|
||||
host = params.get('host')
|
||||
port = params.get('port', '1433')
|
||||
database = params.get('database')
|
||||
connection_string = (
|
||||
f"mssql+pyodbc://{username}:{password}@{host}:{port}/{database}?driver=ODBC+Driver+17+for+SQL+Server"
|
||||
)
|
||||
|
||||
try:
|
||||
# Crea l'Engine
|
||||
engine = create_engine(connection_string)
|
||||
|
||||
# Usa il contesto con la connessione e il metodo text() per eseguire la query
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(text("SELECT 1"))
|
||||
_ = connection.execute(text("SELECT 1"))
|
||||
print("Connessione al database riuscita.")
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
print("Errore durante la connessione al database:", e)
|
||||
sys.exit()
|
||||
@@ -98,6 +104,7 @@ riskfree_rate = 0.02
|
||||
|
||||
# Ottimizzazione per ciascun target di volatilità e salvataggio dei risultati
|
||||
optimized_weights = pd.DataFrame()
|
||||
summary_data = []
|
||||
for (years, target_vol), name in volatility_targets.items():
|
||||
period_start_date = end_date - pd.DateOffset(years=years)
|
||||
period_df = final_df.loc[period_start_date:end_date]
|
||||
@@ -135,8 +142,16 @@ for (years, target_vol), name in volatility_targets.items():
|
||||
ef.efficient_risk(target_volatility=target_vol)
|
||||
weights = ef.clean_weights()
|
||||
optimized_weights[name] = pd.Series(weights)
|
||||
ef.portfolio_performance(verbose=True, risk_free_rate=riskfree_rate)
|
||||
|
||||
exp_ret, exp_vol, sharpe = ef.portfolio_performance(verbose=True, risk_free_rate=riskfree_rate)
|
||||
summary_data.append({
|
||||
"Portafoglio": name,
|
||||
"Years": years,
|
||||
"Target Vol": f"{target_vol:.2%}",
|
||||
"Expected annual return": f"{exp_ret:.2%}",
|
||||
"Annual volatility": f"{exp_vol:.2%}",
|
||||
"Sharpe Ratio": f"{sharpe:.2f}",
|
||||
})
|
||||
|
||||
# Creazione del DataFrame per i risultati
|
||||
results = []
|
||||
for isin, weight in weights.items():
|
||||
@@ -181,10 +196,12 @@ for (years, target_vol), name in volatility_targets.items():
|
||||
optimized_weights_with_names = optimized_weights.copy()
|
||||
optimized_weights_with_names['Nome ETF'] = [df.loc[df['ISIN'] == isin, 'Nome'].values[0] for isin in optimized_weights.index]
|
||||
|
||||
# Esportazione dei pesi ottimizzati in un file Excel con il nome dell'ETF
|
||||
summary_df = pd.DataFrame(summary_data)
|
||||
output_path = excel_path("optimized_weights_GBP.xlsx")
|
||||
optimized_weights_with_names.to_excel(output_path)
|
||||
with pd.ExcelWriter(output_path, engine='openpyxl', mode='w') as writer:
|
||||
optimized_weights_with_names.to_excel(writer, sheet_name='Pesi Ottimizzati', index=True)
|
||||
summary_df.to_excel(writer, sheet_name='metriche', index=False)
|
||||
|
||||
print(f"All optimized weights saved to '{output_path}'.")
|
||||
print(f"All optimized weights saved to '{output_path}' (with metriche).")
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import os
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import yaml
|
||||
import logging
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
@@ -23,10 +25,13 @@ from pypfopt.exceptions import OptimizationError
|
||||
OUTPUT_DIR = "Output"
|
||||
INPUT_DIR = "Input"
|
||||
PLOT_DIR = "Plot"
|
||||
CONFIG_FILE = "config.yaml"
|
||||
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
os.makedirs(INPUT_DIR, exist_ok=True)
|
||||
os.makedirs(PLOT_DIR, exist_ok=True)
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def excel_path(filename: str) -> str:
|
||||
"""Costruisce il percorso completo per un file Excel nella cartella di output."""
|
||||
@@ -36,6 +41,69 @@ def plot_path(filename: str) -> str:
|
||||
"""Costruisce il percorso completo per un file PNG nella cartella Plot."""
|
||||
return os.path.join(PLOT_DIR, filename)
|
||||
|
||||
|
||||
def load_targets_and_limits(config_file: str):
|
||||
"""Legge target di volatilità e limiti asset class dal file di configurazione (nessun fallback)."""
|
||||
try:
|
||||
with open(config_file, "r", encoding="utf-8") as f:
|
||||
cfg = yaml.safe_load(f) or {}
|
||||
except FileNotFoundError:
|
||||
logger.error("File di configurazione mancante: %s", config_file)
|
||||
sys.exit(1)
|
||||
|
||||
vt_cfg = cfg.get("volatility_targets", {})
|
||||
vt_list = []
|
||||
if isinstance(vt_cfg, dict):
|
||||
vt_list = vt_cfg.get("default") or []
|
||||
elif isinstance(vt_cfg, list):
|
||||
vt_list = vt_cfg
|
||||
if not vt_list:
|
||||
logger.error("Sezione 'volatility_targets' mancante o vuota nel file di configurazione.")
|
||||
sys.exit(1)
|
||||
|
||||
volatility_targets_local = {
|
||||
(int(item["years"]), float(item["target_vol"])): item["name"]
|
||||
for item in vt_list
|
||||
if "years" in item and "target_vol" in item and "name" in item
|
||||
}
|
||||
if not volatility_targets_local:
|
||||
logger.error("Nessun target di volatilita valido trovato in configurazione.")
|
||||
sys.exit(1)
|
||||
|
||||
asset_limits_cfg = cfg.get("asset_class_limits") or {}
|
||||
if not asset_limits_cfg:
|
||||
logger.error("Sezione 'asset_class_limits' mancante o vuota nel file di configurazione.")
|
||||
sys.exit(1)
|
||||
asset_class_limits_local = {k: float(v) for k, v in asset_limits_cfg.items()}
|
||||
|
||||
return volatility_targets_local, asset_class_limits_local
|
||||
|
||||
|
||||
def validate_universe(df_universe: pd.DataFrame):
|
||||
"""Verifica colonne obbligatorie e duplicati ISIN nel file universo."""
|
||||
required_cols = ['ISIN', 'Nome', 'Categoria', 'Asset Class']
|
||||
missing_cols = [c for c in required_cols if c not in df_universe.columns]
|
||||
if missing_cols:
|
||||
print(f"[warn] Colonne mancanti nel file universo: {', '.join(missing_cols)}")
|
||||
dup_isin = df_universe['ISIN'][df_universe['ISIN'].duplicated()].unique().tolist()
|
||||
if dup_isin:
|
||||
print(f"[warn] ISIN duplicati nel file universo: {dup_isin}")
|
||||
empty_isin = df_universe['ISIN'].isna().sum()
|
||||
if empty_isin:
|
||||
print(f"[warn] Righe con ISIN mancante nel file universo: {int(empty_isin)}")
|
||||
|
||||
|
||||
def validate_returns_frame(df_returns: pd.DataFrame, threshold: float = 0.2):
|
||||
"""Avvisa se i rendimenti hanno molte celle NaN prima del riempimento."""
|
||||
if df_returns.empty:
|
||||
print("[errore] Nessun dato di rendimento recuperato: final_df vuoto.")
|
||||
sys.exit(1)
|
||||
na_ratio = df_returns.isna().mean()
|
||||
high_na = na_ratio[na_ratio > threshold]
|
||||
if not high_na.empty:
|
||||
cols = ", ".join([f"{c} ({v:.0%})" for c, v in high_na.items()])
|
||||
print(f"[warn] Colonne con >{threshold:.0%} di NaN prima del fill: {cols}")
|
||||
|
||||
# ---------------------------------
|
||||
# Utility per R^2 sull’equity line
|
||||
# ---------------------------------
|
||||
@@ -233,6 +301,7 @@ df = pd.read_excel(
|
||||
usecols=['ISIN', 'Nome', 'Categoria', 'Asset Class', 'PesoMax', 'PesoFisso', 'Codice Titolo'],
|
||||
dtype={'Codice Titolo': str}
|
||||
)
|
||||
validate_universe(df)
|
||||
|
||||
# =========================
|
||||
# SERIE STORICHE RENDIMENTI
|
||||
@@ -262,6 +331,7 @@ for isin in df['ISIN'].unique():
|
||||
except SQLAlchemyError as e:
|
||||
print(f"Errore durante l'esecuzione della stored procedure per {isin}:", e)
|
||||
|
||||
validate_returns_frame(final_df)
|
||||
final_df.fillna(0, inplace=True)
|
||||
|
||||
# -------- H_min sempre su 5 anni (21 gg = 1 mese) --------
|
||||
@@ -270,17 +340,7 @@ five_year_df = final_df.loc[end_date - pd.DateOffset(years=5): end_date]
|
||||
# =========================
|
||||
# CONFIGURAZIONE OBIETTIVI
|
||||
# =========================
|
||||
volatility_targets = {
|
||||
# (1, 0.06): 'VAR3_1Y',
|
||||
# (3, 0.06): 'VAR3_3Y',
|
||||
(5, 0.06): 'VAR3_5Y',
|
||||
(1, 0.12): 'VAR6_1Y',
|
||||
(3, 0.12): 'VAR6_3Y',
|
||||
(5, 0.12): 'VAR6_5Y',
|
||||
# (1, 0.18): 'VAR9_1Y',
|
||||
# (3, 0.18): 'VAR9_3Y',
|
||||
(5, 0.18): 'VAR9_5Y'
|
||||
}
|
||||
volatility_targets, asset_class_limits = load_targets_and_limits(CONFIG_FILE)
|
||||
days_per_year = 252
|
||||
riskfree_rate = 0.02
|
||||
|
||||
@@ -374,11 +434,6 @@ for (years, target_vol), name in volatility_targets.items():
|
||||
ef.add_constraint(lambda w, idxs=idxs, maxw=maxw: sum(w[i] for i in idxs) <= maxw)
|
||||
|
||||
# Vincoli per Asset Class
|
||||
asset_class_limits = {
|
||||
'Azionari': 0.75, 'Obbligazionari': 0.75,
|
||||
'Metalli Preziosi': 0.20, 'Materie Prime': 0.05,
|
||||
'Immobiliare': 0.05, 'Criptovalute': 0.05, 'Monetari': 0.1
|
||||
}
|
||||
for ac, maxw in asset_class_limits.items():
|
||||
isin_list = df[df['Asset Class'] == ac]['ISIN'].tolist()
|
||||
idxs = [period_df.columns.get_loc(isin) for isin in isin_list if isin in period_df.columns]
|
||||
@@ -429,7 +484,7 @@ for (years, target_vol), name in volatility_targets.items():
|
||||
print(f"File {output_file_path} saved successfully.")
|
||||
|
||||
# --- Pie chart asset allocation: salva in Output senza mostrare ---
|
||||
asset_allocations = {asset: 0 for asset in ['Azionari', 'Obbligazionari', 'Metalli Preziosi', 'Materie Prime', 'Immobiliare', 'Criptovalute', 'Monetari']}
|
||||
asset_allocations = {asset: 0 for asset in asset_class_limits}
|
||||
for isin, weight in weights.items():
|
||||
r_sel = df.loc[df['ISIN'] == isin]
|
||||
if r_sel.empty:
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,39 +0,0 @@
|
||||
# Traccia improvements Ottimizzatore
|
||||
|
||||
## Struttura e stack
|
||||
- Repository piatto con script versionati (2.6, 2.5.2, Lite 1.0, 2.2 UK); nessun package/moduli, logica procedurale.
|
||||
- Stack: Python, pandas, numpy, matplotlib; SQLAlchemy/pyodbc (MSSQL); PyPortfolioOpt; opzionale cvxpy; Excel I/O via pandas; nessun requirements/lockfile.
|
||||
- IO: Excel input (universo titoli, template), output Excel pesi/metriche in `Output/`, grafici PNG in `Plot/`; credenziali DB in `connection.txt`.
|
||||
|
||||
## Flusso v2.6 (principale)
|
||||
- Setup cartelle e target volatilita' (`volatility_targets`), costanti (`days_per_year`, `riskfree_rate`, `mu_heal_floor`).
|
||||
- Utility metriche: R2 equity, drawdown (profondita', durata, TTR), Heal index (AAW/AUW), H_min_100, serie/metriche portafoglio.
|
||||
- Connessione MSSQL via `connection.txt`, stored proc `opt_RendimentoGiornaliero1_ALL` per ciascun ISIN; missing riempiti con 0.
|
||||
- Per target: metriche per-asset (ann return/vol, CAGR, R2, drawdown, Heal, H_min_100 su 5Y); ottimizzazione EfficientFrontier con vincoli PesoFisso/PesoMax, per Categoria e Asset Class; export Excel gestionale (peso*99) e pie chart.
|
||||
- Riepilogo portafogli: return/vol attesi, Sharpe, diversificazione, path metrics (Heal, TTR, H_min_100); overlay equity/underwater 5Y.
|
||||
- Variante PH1+HealProxy (se cvxpy): obiettivo massimizzare Heal proxy con floor rendimento >=85% del baseline; grafici comparativi.
|
||||
|
||||
## Pattern e criticita'
|
||||
- Script monolitici, funzioni locali solo utility; forte duplicazione tra versioni (2.5.x, Lite, 2.6).
|
||||
- Config hard-coded (target vol, vincoli per categoria/asset class, naming file); nessuna parametrizzazione esterna.
|
||||
- Credenziali DB in chiaro (`connection.txt`); host/porta/db esposti.
|
||||
- Dati: scarsa validazione; `fillna(0)` su rendimenti distorce metriche; assenza di controllo su duplicati/tipi/periodi mancanti.
|
||||
- Performance: chiamata stored proc per ogni ISIN in serie; calcolo covarianze/ottimizzazioni per ogni target; niente caching/parallelismo.
|
||||
- Resilienza: gestione errori minima; cvxpy opzionale ma fallback silenzioso; logging solo via print; niente test.
|
||||
- Codice morto: alcune utility (path metrics) non usate altrove.
|
||||
- Output: scrittura massiva di Excel/PNG senza controllo overwrite/versioning.
|
||||
|
||||
## Collo di bottiglia
|
||||
- IO/DB: loop sequenziale sugli ISIN per fetch rendimenti.
|
||||
- CPU: covarianze e Solvers EfficientFrontier per ogni combinazione durata/volatilita'.
|
||||
- IO file: generazione multipla di Excel e plot per portafoglio.
|
||||
|
||||
## Direzioni di miglioramento (linee guida, non implementate)
|
||||
- Sicurezza: rimuovere/securizzare `connection.txt`, usare variabili ambiente o secret store; separare credenziali dal repo.
|
||||
- Configurazione: estrarre target/vincoli in file di config (yaml/json); centralizzare costanti.
|
||||
- Gestione dipendenze: aggiungere requirements/lockfile e script setup.
|
||||
- Architettura: modularizzare (data fetch, metriche, optimizer, export), riuso tra versioni, eliminare duplicati.
|
||||
- Dati: validazione input, gestione missing diversa da fillna(0), log warning sui buchi/ISIN mancanti.
|
||||
- Performance: fetch batch/caching, eventuale parallelizzazione o memoization di covarianze/metriche.
|
||||
- Testing/qualita': introdurre test per metriche e vincoli, logging strutturato, controlli su overwrite output.
|
||||
- Observability: report riepilogo a schermo/logs strutturati piu' dei soli print.
|
||||
Reference in New Issue
Block a user