Skip to content

Commit 1dc3be4

Browse files
committed
feat(core): implement SettingsManager and JSON config
- New: Aggiunto 'src/settings_manager.py' con logica Fail-Fast (Strict). - Config: Aggiunto 'config/strategies.json' con parametri di default. - Test: Aggiunti unit test per SettingsManager (I/O, Error Handling). - Ref: Issue #1 completata.
1 parent 380f732 commit 1dc3be4

3 files changed

Lines changed: 197 additions & 0 deletions

File tree

config/strategies.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"active_strategy": "RSI",
3+
"strategies_params": {
4+
"EMA": {
5+
"short_window": 50,
6+
"long_window": 200,
7+
"atr_period": 14
8+
},
9+
"RSI": {
10+
"rsi_period": 14,
11+
"rsi_lower": 30,
12+
"rsi_upper": 70,
13+
"atr_period": 14
14+
}
15+
}
16+
}

src/settings_manager.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import json
2+
import os
3+
from pathlib import Path
4+
from typing import Dict, Any
5+
from src.logger import get_logger
6+
7+
class SettingsManager:
8+
"""
9+
Gestisce la configurazione dinamica delle strategie (JSON).
10+
Policy: FAIL FAST. Se il file manca o è corrotto, solleva eccezioni.
11+
"""
12+
13+
def __init__(self, config_path: str = "config/strategies.json"):
14+
self.logger = get_logger(self.__class__.__name__)
15+
16+
# Risolviamo il path assoluto
17+
base_path = Path(__file__).parent.parent
18+
self.file_path = base_path / config_path
19+
20+
# Check esistenza file all'avvio
21+
if not self.file_path.exists():
22+
msg = f"CRITICAL: File di configurazione non trovato in {self.file_path}"
23+
self.logger.critical(msg)
24+
raise FileNotFoundError(msg)
25+
26+
def load_config(self) -> Dict[str, Any]:
27+
"""Legge la configurazione dal disco."""
28+
try:
29+
with open(self.file_path, 'r') as f:
30+
return json.load(f)
31+
except json.JSONDecodeError as e:
32+
self.logger.error(f"Il file {self.file_path} non è un JSON valido: {e}")
33+
raise
34+
35+
def save_config(self, new_config: Dict[str, Any]):
36+
"""Salva la nuova configurazione su disco."""
37+
try:
38+
# Creiamo la cartella se non esiste (utile solo al primo deploy se file iniettato)
39+
self.file_path.parent.mkdir(parents=True, exist_ok=True)
40+
41+
with open(self.file_path, 'w') as f:
42+
json.dump(new_config, f, indent=4)
43+
self.logger.info("Configurazione salvata correttamente.")
44+
except Exception as e:
45+
self.logger.error(f"Errore salvataggio config: {e}")
46+
raise
47+
48+
def get_active_strategy_name(self) -> str:
49+
"""Ritorna il nome della strategia attiva. Errore se manca."""
50+
cfg = self.load_config()
51+
if "active_strategy" not in cfg:
52+
raise ValueError(f"Chiave 'active_strategy' mancante in {self.file_path}")
53+
return cfg["active_strategy"]
54+
55+
def get_strategy_params(self, strategy_name: str = None) -> Dict[str, Any]:
56+
"""
57+
Ritorna i parametri per una specifica strategia.
58+
Se strategy_name è None, usa quella attiva.
59+
Errore se la strategia non è configurata.
60+
"""
61+
cfg = self.load_config()
62+
63+
# Determina quale strategia cercare
64+
target = strategy_name if strategy_name else cfg.get("active_strategy")
65+
66+
if not target:
67+
raise ValueError("Impossibile determinare la strategia target (active_strategy mancante?)")
68+
69+
# Cerca i parametri
70+
all_params = cfg.get("strategies_params", {})
71+
if target not in all_params:
72+
raise ValueError(f"Parametri per strategia '{target}' non trovati in 'strategies_params'.")
73+
74+
return all_params[target]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import pytest
2+
import json
3+
from src.settings_manager import SettingsManager
4+
5+
# --- FIXTURES ---
6+
7+
@pytest.fixture
8+
def valid_config_data():
9+
"""Dati validi per i test."""
10+
return {
11+
"active_strategy": "EMA",
12+
"strategies_params": {
13+
"EMA": {"short": 10, "long": 20},
14+
"RSI": {"period": 14}
15+
}
16+
}
17+
18+
@pytest.fixture
19+
def temp_config_file(tmp_path, valid_config_data):
20+
"""
21+
Crea un file JSON reale in una directory temporanea isolata.
22+
Ritorna il percorso completo (Path object).
23+
"""
24+
d = tmp_path / "config"
25+
d.mkdir()
26+
f = d / "strategies.json"
27+
28+
# Scriviamo dati validi iniziali
29+
with open(f, "w") as file:
30+
json.dump(valid_config_data, file)
31+
32+
return f
33+
34+
# --- TESTS ---
35+
36+
def test_init_fail_if_missing(tmp_path):
37+
"""Deve crashare se il file non esiste (nessun default automatico)."""
38+
fake_path = tmp_path / "non_existent.json"
39+
40+
# Verifichiamo che alzi l'eccezione giusta
41+
with pytest.raises(FileNotFoundError):
42+
SettingsManager(config_path=str(fake_path))
43+
44+
def test_load_success(temp_config_file):
45+
"""Deve caricare correttamente un file valido."""
46+
# Passiamo il path temporaneo al manager
47+
manager = SettingsManager(config_path=str(temp_config_file))
48+
49+
# Test active strategy
50+
assert manager.get_active_strategy_name() == "EMA"
51+
52+
# Test params
53+
params = manager.get_strategy_params()
54+
assert params["short"] == 10
55+
assert params["long"] == 20
56+
57+
def test_missing_active_strategy_key(temp_config_file):
58+
"""Deve alzare ValueError se manca la chiave 'active_strategy'."""
59+
# Sovrascriviamo il file con dati incompleti
60+
bad_data = {"strategies_params": {}}
61+
with open(temp_config_file, "w") as f:
62+
json.dump(bad_data, f)
63+
64+
manager = SettingsManager(config_path=str(temp_config_file))
65+
66+
with pytest.raises(ValueError, match="active_strategy"):
67+
manager.get_active_strategy_name()
68+
69+
def test_missing_strategy_params(temp_config_file):
70+
"""Deve alzare ValueError se la strategia attiva non ha parametri definiti."""
71+
# Configuriamo EMA come attiva, ma non mettiamo i parametri per EMA
72+
bad_data = {
73+
"active_strategy": "EMA",
74+
"strategies_params": {
75+
"RSI": {"period": 14}
76+
# Manca EMA!
77+
}
78+
}
79+
with open(temp_config_file, "w") as f:
80+
json.dump(bad_data, f)
81+
82+
manager = SettingsManager(config_path=str(temp_config_file))
83+
84+
with pytest.raises(ValueError, match="non trovati"):
85+
manager.get_strategy_params()
86+
87+
def test_save_config(temp_config_file):
88+
"""Deve salvare correttamente le modifiche su disco."""
89+
manager = SettingsManager(config_path=str(temp_config_file))
90+
91+
# 1. Modifichiamo la configurazione
92+
new_config = {
93+
"active_strategy": "RSI",
94+
"strategies_params": {
95+
"RSI": {"period": 99}
96+
}
97+
}
98+
99+
# 2. Salviamo
100+
manager.save_config(new_config)
101+
102+
# 3. Rileggiamo direttamente dal file (bypassando il manager per sicurezza)
103+
with open(temp_config_file, "r") as f:
104+
content = json.load(f)
105+
106+
assert content["active_strategy"] == "RSI"
107+
assert content["strategies_params"]["RSI"]["period"] == 99

0 commit comments

Comments
 (0)