Tre fix di deploy emersi al primo build/up del nuovo compose:
1. Dockerfile: rimosso PYTHONPATH=/app/src
Con layout uv workspace doppio-nest, PYTHONPATH=/app/src faceva ombra
alla venv risolvendo le member-dir (multi_swarm_core/, strategy_crypto/)
come namespace packages PEP 420 senza i sub-package del codice
(cerbero, persistence, frontend, ecc.). I package sono installati come
editable dal `uv sync --frozen` nella venv: niente PYTHONPATH necessario.
2. pyproject root: aggiunto [project] dependencies = [multi-swarm-core, strategy-crypto]
I due member workspace erano in [dependency-groups.dev], escluse da
`uv sync --frozen --no-dev` del builder Docker -> "ModuleNotFoundError:
No module named multi_swarm_core". Spostati come dipendenze runtime del
deployable app root; dev group ora contiene solo pytest/ruff/mypy.
uv.lock rigenerato.
3. docker-compose.yml: aggiunto Traefik middleware StripPrefix
NiceGUI con root_path="/strategy_crypto_gui" assume che il proxy
strippi il prefix prima di girare al container (FastAPI route restano
"/", "/paper", ecc.). Senza StripPrefix, NiceGUI riceveva
"/strategy_crypto_gui/" e rispondeva 404. Aggiunte 2 label:
- strategy-crypto-stripprefix middleware
- router.middlewares = strategy-crypto-stripprefix
Verifica end-to-end:
- https://swarm.tielogic.xyz/strategy_crypto_gui/ -> HTTP 200 (31KB)
- https://swarm.tielogic.xyz/ -> HTTP 404 (root libera, atteso)
- paper run phase3-baseline-001 (fcf271d0...) tick=1 OK, $1000 equity
- state/strategy_crypto.db creato con 5 tabelle paper_trading_*
NB: permission fix per `src/strategy_crypto/strategy_crypto/strategies/`
fatto manualmente (chown 1000:1000) — i JSON migrati da git mv erano
root:root, container gira come uid 1000. Annotare per future strategies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Il LLM ora genera strategie sapendo distinguere quando usare atr (unità
prezzo) vs atr_pct (frazione del close) per evitare il bug protocol_unit.
agents/hypothesis.py:
- Lista indicatori: aggiunto atr_pct con annotazione unità inline
- NEW sezione "UNITÀ — REGOLA CRITICA": esempi positivi/negativi su
literal frazionali (atr_pct > 0.02 ✓) vs literal in unità prezzo
(atr > 0.02 ✗ sempre TRUE su asset >$1)
- PATTERN GUIDANCE: "Compressione di volatilità" ora suggerisce atr_pct(N) < 0.01
invece di "ATR(N) < soglia bassa" generico
- "Espansione di volatilità": atr_pct(N) > 0.03 OPPURE confronto relativo
genome/mutation_prompt_llm.py:
- _VALID_KEYWORDS: aggiunti atr_pct + realized_vol (per validazione prompt mutato)
Closes open item da commit f875df3. Prima del prossimo Phase 2.x run il
modello qwen-2.5-72b ricevera' il prompt aggiornato e genera strategie
unit-aware. Vedi src/multi_swarm_core/docs/decisions/2026-05-15-atr-pct-fix.md.
Verifica: 188 test pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Sezione "Layout monorepo (uv workspace)": ridisegna la struttura per chiarire
i due workspace member (multi-swarm-core + strategy-crypto), DB separati,
pattern per N strategie future.
- Comandi aggiornati: paper runner ora importa da strategy_crypto.backend,
dashboard via 'python -m strategy_crypto.frontend.nicegui_app'.
- Backtest cmd punta al nuovo path strategie src/strategy_crypto/strategy_crypto/strategies/.
- Variabili .env: GA_DB_PATH + STRATEGY_CRYPTO_DB_PATH + DASHBOARD_ROOT_PATH.
Mantenuto nota backcompat su DB_PATH legacy.
- Dashboard: nuova pagina /paper + URL prod /strategy_crypto_gui/.
- Deploy: servizi rinominati strategy-crypto-paper / strategy-crypto-gui,
bind strategies dal package, image rinominata multi-swarm-coevolutive:dev.
- Rimossi link a doc cancellati (poc_trading_swarm.md, superpowers/, phase1-technical-report).
- Doc rimanenti riposizionati sotto src/multi_swarm_core/docs/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NEW src/strategy_crypto/tests/test_imports.py:
- test_backend_imports — verifica re-export PaperExecutor/Portfolio/PaperRepository + schema
- test_frontend_imports — strategy_crypto.frontend.{data,nicegui_app} importabili
- test_strategies_json_loadable — i JSON sono in importlib.resources e ben formati
- test_init_schema_creates_tables — PaperRepository.init_schema() crea 5 tabelle
Fix pytest collection: add --import-mode=importlib agli addopts per evitare
collisione dei due tests/__init__.py (core + strategy_crypto) sotto stesso
nome module 'tests'.
Verifica: uv run pytest → 186 passed (182 core + 4 strategy_crypto)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- git mv strategies/{btc,eth}_*.json → src/strategy_crypto/strategy_crypto/strategies/
- Cancellata directory root strategies/ (ora package data del member strategy_crypto)
- I JSON sono inclusi nella wheel via force-include nel pyproject del member
- scripts/run_paper_trading.py:
* Import paper_trading.* → strategy_crypto.backend
* Default --strategies-dir letto da importlib.resources.files('strategy_crypto') / 'strategies'
* PaperRepository(settings.strategy_crypto_db_path) + init_schema()
* Rimosso Repository(settings.db_path).init_schema() (GA init non e' responsabilita' del paper)
- Verifica: importlib.resources trova i 2 JSON nel package
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- multi_swarm_core/persistence/schema.py: rimuove tabelle paper_trading_*
(5 CREATE TABLE + 3 indici idx_paper_*). Restano solo le tabelle GA universali
(runs, generations, genomes, evaluations, cost_records, adversarial_findings).
- strategy_crypto/backend/schema.py NEW: PAPER_SCHEMA_SQL standalone + init_schema()
con mkdir parent, scrive su state/strategy_crypto.db.
Ownership: ogni strategia possiede il proprio schema, isolato dal core.
Pattern replicabile per strategy_forex, strategy_equity, ecc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- mv src/multi_swarm → src/multi_swarm_core/multi_swarm_core (member layout)
- sed-replace globale degli import: from/import multi_swarm.* → multi_swarm_core.*
- 115 occorrenze aggiornate in src/ scripts/ tests/
- multi_swarm_coevolutive (nome repo) preservato dal word boundary
Pre-condizione per il setup uv workspace della Fase 3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-condizione per la riorganizzazione in uv workspace: crea il layout
member del nuovo pacchetto strategy_crypto con __init__.py vuoti.
Il contenuto (backend, frontend, strategies, tests) arriva nelle fasi
successive del piano di migrazione.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cristallizza la library surface che il nuovo repo figlio
Swarm_Strategy_Crypto consumerà via git submodule:
- multi_swarm.protocol.{compile,parse,validate}_strategy + AST nodes
- multi_swarm.backtest.{Side, Trade, Order, Position, BacktestEngine}
- multi_swarm.cerbero.CerberoClient
- multi_swarm.data.{CerberoOHLCVLoader, OHLCVRequest}
Cambi a queste signature dovranno essere considerati breaking
per il consumer downstream.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Il callback registrato via top_table.on("selection", ...) riceve un
GenericEventArguments di NiceGUI, non un TableSelectionEventArguments,
quindi l'attributo .selection non esiste e il click su una riga della
tabella "Top genomi" generava AttributeError. Le righe selezionate
arrivano nel payload Quasar come e.args["rows"].
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nuova pagina NiceGUI "Paper" che legge le tabelle paper_trading_*:
- 4 metric card: Equity, P/L cumulato %, Trades chiusi, Open/Tick count
- Equity curve plotly con hline initial capital
- Tre tabelle: open positions, ultimi 30 tick (ts/bar/symbol/signal/action),
trades chiusi (entry/exit/pnl/fees)
- Run selector dropdown + status badge + auto-refresh REFRESH_INTERVAL_S
dashboard/data.py: aggiunti 6 helper read-only su SQLite (paper_runs_df,
paper_equity_df, paper_positions_df, paper_trades_df, paper_ticks_df,
paper_run_summary). Connessione separata da Repository per usare
direttamente lo schema paper_trading_* senza passare per la classe di
write PaperRepository.
dashboard/nicegui_app.py: aggiunto import pandas (necessario per
to_datetime nell'equity curve), nav link "Paper" nell'header,
@ui.page("/paper") con helper _paper_runs_options + _paper_equity_figure.
Chiude il primo TODO della roadmap sez 10.1 ("Pagina dashboard
paper-trading").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- "Stato del progetto" riscritto: Phase 3 paper-trading in corso, link al
report di sintesi del 14 maggio, strategie freezate BTC/ETH e costo
cumulato $3.74 su 30 run GA.
- Architettura: aggiunti splits.py (WFA), diversity.py, mutation_prompt_llm.py
(5° operatore), paper_trading/ (portfolio/executor/persistence); commenti
su fitness.py/adversarial.py aggiornati a v2 soft-kill + 5 check HIGH.
- Nuova lista CLI knobs accumulati Phase 2.5 → 2.7.
- Setup: test count ~180, .env include DOMAIN_NAME e SWARM_DASHBOARD_PORT.
- Comandi: aggiunti backtest_strategy.py e run_paper_trading.py; esempio
run_phase1.py ora usa --prompt-mutation-weight e --fitness-v2.
- Nuova sezione Deploy: docker-compose con due servizi su rete traefik
external, bind-mount + chown 1000:1000, override paper via env.
- Costi: da Phase 1 only a cumulato $3.74 + Phase 3 LLM-free.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggiorna stato-progetto-e-roadmap riflettendo le modifiche del 14 maggio:
- diagramma architettura (sec 9): dashboard/ ora elenca solo nicegui_app.py
+ data.py; rimosso Streamlit legacy e aquarium.py
- item 10.3 "Port completo dashboard a NiceGUI": marcato [x] con
riferimenti ai commit 03f723f (cleanup) e 8e5efde (deploy Docker
via Traefik su swarm.tielogic.xyz). Annotata la scelta esplicita di
non riportare l'Aquarium su NiceGUI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Riepilogo cumulato delle Phase 1 → 3: 30 run GA completate, $3.74 di
costo LLM, paper-trading runner operativo su BTC+ETH con strategie
freezate fb63e851 (BTC, true alpha hour-gated) e facd6af85d5d (ETH,
trend-following long-bias). Documento di sincronizzazione con caveat
aperti (varianza seed, dipendenza qwen-2.5-72b, Cerbero SPOF) e roadmap
suddivisa in completamento Phase 3, estensioni metodologiche, hardening
tecnico e documentazione.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NiceGUI è la dashboard ufficiale (port 8080, dark/neon palette, 3 pagine:
/, /convergence, /genomes). La porta è ora parametrica via env
SWARM_DASHBOARD_PORT, letta in ui.run() — Docker la usa anche per
healthcheck e label Traefik.
docker-compose.yml: entrypoint del servizio dashboard cambiato da
streamlit a python -m multi_swarm.dashboard.nicegui_app. Default porta
8080 ovunque (.env, .env.example, compose, healthcheck).
Rimossi i file legacy della vecchia GUI Streamlit:
- src/multi_swarm/dashboard/streamlit_app.py
- src/multi_swarm/dashboard/aquarium.py (helper canvas HTML5)
- src/multi_swarm/dashboard/pages/{01_overview,02_ga_convergence,
03_genomes,04_aquarium}.py
- tests/integration/test_streamlit_smoke.py
pyproject.toml: rimossa la dep streamlit; uv.lock rigenerato (10 deps
transitive eliminate: pydeck, watchdog, jsonschema, pillow, ecc.).
README aggiornato (architettura, comando dashboard, sezione Dashboard
ora descrive NiceGUI con riferimento al deploy Docker via Traefik).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dockerfile multi-stage (python:3.13-slim + uv) e docker-compose con due
servizi che condividono l'immagine:
- multi-swarm-paper: runner long-running scripts/run_paper_trading.py
- multi-swarm-dashboard: Streamlit su https://swarm.${DOMAIN_NAME}
Cerbero raggiunto via rete docker interna (http://cerbero-mcp:9000)
saltando il giro pubblico traefik+TLS. Persistenza via bind mount su
data/, series/, state/ (runs.db con WAL), strategies/ in read-only.
.env.example aggiornato con DOMAIN_NAME, SWARM_DASHBOARD_PORT (porta
interna parametrizzabile) e i PAPER_* per override del command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2.7 portabilità: tentativo backtest top genome BTC/ETH su asset
tradFi via yfinance per stress-test universale del setup.
Esito documentato in memoria: yfinance 1h limitato a 730 giorni,
incompatibile con hour-gated strategy su 5+ anni.
Aggiunto .claude/ a .gitignore (scheduled_tasks.lock runtime artifact).
Aggiunta sezione 'PATTERN GUIDANCE' nel SYSTEM_TEMPLATE che guida il LLM a
generare strategie basate non solo su threshold di indicatori isolati, ma
anche su:
- Forme di curva (trend asc/desc, compressione/espansione vol, mean reversion strutturale)
- Ripetibilità dell'andamento (crossover ricorrenti, pattern intraday/weekly, doppio top, range breakout)
La grammar JSON resta invariata (no nuove primitive); il prompt insegna al
LLM a comporre i nodi esistenti per approssimare pattern di chart analysis
classica. Obiettivo: spostare la generazione dalla soglia statica RSI<30
verso pattern shape-aware che si replicano nei dati storici.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggiunti due meccanismi per selection multi-objective:
1) Helper compute_combined_fitness(fit_train, fit_oos, alpha):
formula = alpha*IS + (1-alpha)*OOS, fallback a IS se OOS è None/NaN.
2) RunConfig.eval_oos_during_loop (default False) + fitness_combined_alpha
(default 0.5). Quando True E wfa_train_split attivo, ogni genome con
fitness IS > 0 viene rivalutato su test_ohlcv DURANTE il loop GA e la
fitness usata per tournament_select/elite_select è quella combinata.
2x costo backtest engine (richiede 2 evaluation per genome).
3) CLI flags --eval-oos-during-loop e --fitness-combined-alpha.
Motivazione: il run phase2-7-max7y-v2-wfa-001 ha mostrato che il top by
fitness_IS (4e1be9fa, ratio OOS 0.31) NON è il top per performance OOS reale
(634111992702, ratio 1.42, ret_OOS +105% / 2.2y). Selezionare durante GA
con combined fitness orienta l'evoluzione verso strategie OOS-robust by
design invece di filtrarle a posteriori.
Backward compat: default eval_oos_during_loop=False → comportamento
invariato per run senza il flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run phase2-6-flat-wfa-001 è andato in hang per 4h+ a gen 3/10 su una
chiamata OpenRouter (process stuck in do_poll syscall, 0% CPU). Il SDK
OpenAI ha default timeout 600s ma evidentemente il TLS/socket handshake
può bloccarsi senza interrompere; senza timeout esplicito i retry tenacity
non scattano mai e il run resta pending.
Fix: OpenAI(timeout=60.0). Se una chiamata supera 60s, raise
APITimeoutError (già in _RETRYABLE_EXCEPTIONS) e tenacity ritenta fino a 5
volte con backoff esponenziale fino a 30s. Worst case totale: ~5×60s =
5min per call (vs 4h+ hang precedente).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Due fondamenta scientifiche per filtrare overfit e lucky-shot:
1) undertrading_threshold parametrico (era hardcoded 10):
- AdversarialAgent.__init__(undertrading_threshold=10)
- CLI flag --undertrading-threshold
- Aggiunto a hard_kill_findings v2 default
{"no_trades", "degenerate", "undertrading"}: ora un genome con 1 trade
fortunato (es. genome 80be6bcc-1trade-fit-0.21 di fitness-v2-combo) viene
killato anche sotto fitness v2 soft-kill.
- Test parametric: undertrading_threshold=25 → 15 trade triggerano HIGH.
2) Walk-Forward Validation (WFA):
- RunConfig.wfa_train_split (None=off, 0<x<1=on) + wfa_top_k=5
- run_phase1: split ohlcv in train/test; GA usa solo train; a fine GA
i top_k genomi (by fitness in-sample, fitness>0) vengono rivalutati
sul test_ohlcv via falsification+adversarial+compute_fitness.
- Schema migration: evaluations + fitness_oos, sharpe_oos, return_oos,
max_dd_oos, n_trades_oos (ALTER TABLE con try/except per DB pre-2.6).
- Repository.update_evaluation_oos helper per popolare colonne OOS.
- CLI flags --wfa-train-split, --wfa-top-k.
- Test integration: train_split=0.7 → fitness_oos popolato per top_k.
Motivazione: la fase 2.5 ha generato 17 run con fitness fino a 0.36 + DSR
positivo, ma OOS test su 7 anni mostra che flat-ablation top crolla -37%
mentre fitness-v2 top regge (+143%). WFA in-run permette ora di vedere
direttamente il degradation train→test senza eseguire backtest separati,
rendendo possibile filtrare overfit early durante l'ottimizzazione.
Tests (+2 → 193 totale):
- test_undertrading_threshold_parametric
- test_e2e_wfa_populates_fitness_oos
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Script utility per validare OOS strategie discovered durante run Phase 2.5.
Carica un JSON strategia (formato Hypothesis output), fetcha OHLCV via
Cerbero, esegue BacktestEngine + FalsificationReport + AdversarialReport,
stampa metriche annualizzate (CAGR, Sharpe, max DD, Calmar).
Esempio:
uv run python scripts/backtest_strategy.py /tmp/strategy.json \
--start 2018-09-01 --end 2026-01-01 --label my-strategy
Validato sui top 2 genomi Phase 2.5 (flat-ablation e fitness-v2-combo):
flat-ablation top overfit su 7y (-37%), fitness-v2 top regge (+143% in 7y,
CAGR +12.8%). Conferma che strategie con time gate temporal feature
generalizzano meglio di strategie con SMA crossover hard-tied al regime
del training period.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggiunto parametro hard_kill_findings opzionale a compute_fitness.
None (default) = v1 backward-compat: ogni HIGH azzera la fitness.
tuple non vuota = v2: solo finding con name nel set azzerano; gli altri
HIGH applicano penalità moltiplicativa
adv_penalty = 1 / (1 + soft_penalty * n_soft_high)
(default soft_penalty=0.4 → 1 HIGH soft = 0.71x, 2 = 0.56x, 3 = 0.45x).
Motivazione: tutti i run Phase 2/2.5 mostrano 55-87 finding HIGH dominanti
da fees_eat_alpha + flat_too_long. La fitness v1 azzera ogni genome con
anche solo 1 HIGH → median sempre 0 perché quasi tutti i genomi sopravvivono
in modo binario (top integro vs zerati). v2 fornisce gradient continuo:
strategie 'quasi-buone' restano valutabili e il GA può evolverle.
Hard kill v2 default: {"no_trades", "degenerate"} (la strategia letteralmente
non funziona — niente da salvare). Tutti gli altri HIGH (fees_eat_alpha,
flat_too_long, time_in_market_too_high, undertrading, overtrading) → soft.
RunConfig: fitness_hard_kill_findings (None = v1, tuple = v2) +
fitness_adversarial_soft_penalty (default 0.4). CLI flag --fitness-v2 imposta
hard_kill_findings = ("no_trades", "degenerate") e
--fitness-soft-penalty per il fattore custom.
+5 test (12 totali in test_fitness, 191 totale suite):
- v2 soft HIGH non azzera
- v2 hard kill ancora azzera
- v2 cumulativo: più soft HIGH = penalty più severa
- v2 senza findings = identico a v1
- backward compat hard_kill_findings=None = v1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Estende AdversarialAgent con flat_too_long_threshold (default 0.95)
configurabile, simmetrico a fees_eat_alpha_threshold. Propagato a
RunConfig.flat_too_long_threshold e flag CLI --flat-too-long-threshold.
Motivazione: pop30-combo ha registrato 75 finding flat_too_long HIGH
(secondo killer dopo fees_eat_alpha 87). Rilassare la soglia 0.95→0.98
ammette strategie più passive ma marginalmente attive — analogo
all'ablation fees già verificata (+23% stabile).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark task 6 (cost attribution) come done dopo commit ba4eb09. Aggiornato
header status con sweet spot empirico weight=0.30 (curva U validata su
run 004 vs control vs weight-0.50).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 6 del piano Phase 2.5 (deferito → ora completato):
- CostRecord: nuovo campo call_kind (default "hypothesis")
- CostTracker.record: accetta call_kind opzionale, summary include
by_call_kind breakdown (hypothesis vs mutation)
- Schema cost_records: aggiunta colonna call_kind TEXT NOT NULL DEFAULT
'hypothesis' + migration soft via ALTER TABLE in init_schema (silently
catched per DB pre-Task 6)
- Repository.save_cost_record: nuova arg call_kind opzionale
- mutate_prompt_llm: accetta cost_tracker/repo/run_id opzionali e logga
la call mutator con call_kind="mutation" quando sink presente
- weighted_random_mutate, next_generation: propagano cost sink
- orchestrator.run_phase1: passa cost_tracker+repo+run_id a
next_generation solo se prompt_mutation_weight > 0
Esposto fees_eat_alpha_threshold come parametro AdversarialAgent
(default 0.5 = comportamento Phase 1.5 invariato), propagato via
RunConfig.fees_eat_alpha_threshold e flag CLI
--fees-eat-alpha-threshold. Abilita ablation con soglia 0.7-0.8 senza
modificare codice — adversarial finding dominante in tutti i run
Phase 2/2.5 (50+ HIGH per run).
Tests (+4 → 186 totale):
- test_cost_tracker: default call_kind="hypothesis"; breakdown
by_call_kind con hypothesis+mutation
- test_mutation_prompt_llm: logging mutation cost con sink completo;
backward compat senza sink (no errore)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run phase2-5-qwen25-prompt-mut-003 fallito a gen 5 (76 evals done, $0.061
spesi) per HTTP 429 RateLimit upstream da DeepInfra su qwen-2.5-72b.
RateLimitError NON era in _RETRYABLE_EXCEPTIONS quindi tenacity falliva
subito, propagando il 429 a propose() e all'orchestrator (run failed).
Tre fix:
1) Aggiunto openai.RateLimitError a _RETRYABLE_EXCEPTIONS.
2) Bumpato stop_after_attempt(3) → 5 e wait_exponential max 10s → 30s.
Più tempo per il rate limit upstream di sbloccare prima di rinunciare.
3) hypothesis.py: try/except RateLimitError in propose() come per
EmptyCompletionError — anche se tenacity esaurisce i 5 retry, il genome
viene marcato fitness=0 e il loop esterno continua senza crash totale.
Test: aggiornato test_completion_retries_on_connection_error per
assert call_count == 5 (era 3). Tutti 182 verdi.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OpenRouter ha cominciato a routare qwen/qwen-2.5-72b-instruct attraverso
Novita, che rifiuta /chat/completions con HTTP 400:
"model: qwen/qwen-2.5-72b-instruct does not support endpoint: completions".
Aggiunto LLMClient.DEFAULT_PROVIDER_IGNORE = ("Novita",) e flag opzionale
provider_ignore al constructor. Le request includono ora
extra_body={"provider": {"ignore": ["Novita"]}} per default.
Verificato manualmente: con ignore Novita, qwen-2.5-72b risponde
normalmente via altri provider (DeepInfra, Together, ecc).
Mantenuta backward compat: passando provider_ignore=() si disabilita
l'esclusione (utile per test o se in futuro Novita si sistema).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run phase2-5-qwen25-prompt-mut-001 fallito perché qwen-2.5-72b ha emesso
empty completion ripetutamente; le 3 retry tenacity in LLMClient.complete
si sono esaurite e l'exception è bollata fino a run_phase1, marcando l'intero
run come failed (perso ~$0.017 di tier C).
Fix: HypothesisAgent.propose() ora cattura EmptyCompletionError nel loop
max_attempts trattandola come parse-fail "empty_completion" e ritentando
con max_retries+1 tentativi (cumulativo: max_retries tenacity × max_attempts
loop esterno, default 3×3 = 9 retry effettive). Se TUTTI i tentativi
falliscono con empty, ritorna proposal con strategy=None e
parse_error="empty_completion: ...", lasciando l'orchestrator continuare
con quel genome marcato come "fitness=0" senza crash totale del run.
+2 test: success dopo 1 empty + retry; failure totale dopo 3 empty consecutive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 5 del piano Phase 2.5: nuovo modulo src/multi_swarm/metrics/diversity.py
con population_prompt_diversity(prompts) che ritorna la diversità media
1 - SequenceMatcher.ratio() su tutte le coppie distinte. 0.0 identici,
fino a ~0.9 totalmente diversi (SequenceMatcher considera spazi/lunghezza).
5 test: edge case empty/single, identici, diversi, intermediate, simmetria.
Piano aggiornato a stato "IMPLEMENTATO 2026-05-11": checkbox task 1-5
spuntate, task 6 (cost attribution per call_kind) deferito con motivazione.
Header preambolo aggiornato con trigger verificati e decisione collaterale
rollback tier C.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run controllo phase2-qwen25-control-001 (seed 42, stessa pipeline Phase 2,
solo tier C switched) ha dimostrato che qwen-2.5-72b è qualitativamente
SUPERIORE a qwen3-235b sul nostro workload:
| metrica | qwen3-235b | qwen-2.5-72b | delta |
| ----------------- | ---------- | ------------ | ----- |
| max fitness | 0.0238 | 0.0311 | +30% |
| median > 0 in gen | mai | 4 gen su 10 | -- |
| entropy media | 0.199 | 0.85 | 4.3x |
| genomi fit > 0 | 5 | 10 | 2x |
| parse success | 97.7% | 100% | + |
| durata | 50 min | 28 min | 0.56x |
| LLM calls | 148 | 90 | 0.61x |
| cost USD | 0.0223 | 0.0122 | 0.55x |
Controintuitivo: 235B con context 262k era atteso superiore al 72B legacy.
In pratica qwen3-235b in tier C produce strategie meno diverse,
meno parsabili e meno ottimizzabili dal GA.
Ripristinati prezzi cost_tracker tier C a 0.40/0.40 (qwen-2.5-72b).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: la dashboard mostrava \$0.0000 per Cost (USD) durante i run in corso
perché leggeva runs.total_cost_usd, che viene aggiornato solo dentro
Repository.complete_run a fine run. I record per-call esistevano già in
cost_records (124 record / \$0.019 sul run phase2-qwen3-001 attivo).
Fix: in _snapshot() se il run è status=running uso Repository.total_cost(run_id)
che fa SUM(cost_usd) live su cost_records. Per i run completed/failed continuo
a leggere total_cost_usd dal record runs (storico autoritativo).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Soluzione robusta al bug "chiavi JSON invisibili": invece di tentare
override CSS su Prism.js/highlight.js (che nelle build NiceGUI possono
non essere attivi o variare), il JSON viene serializzato server-side con
span colorati espliciti e renderizzato via ui.html.
Implementato helper _json_to_html(obj) che produce HTML con classi:
- .cb-key (chiavi) → cyan #00D9FF
- .cb-string → green #00E676
- .cb-number → pink #FF2D87
- .cb-bool / .cb-null → amber #FFB800
- .cb-punct (graffe, virgole, due punti) → muted
Stessa logica per system_prompt e raw LLM output in /genomes: sostituito
ui.code(language=text) con ui.html('<pre class="raw-block">') e
html.escape() per safety XSS. Background dedicato #1A1A24 e font
JetBrains Mono 13.5px, line-height 1.7, ottimo contrasto su qualsiasi
browser senza dipendere da syntax highlighter esterno.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Due bug visivi distinti:
1) Icone Material rotte ("arrow_drop_down" come testo nel selector):
Il selettore CSS '*' applicava font Inter a tutti gli elementi inclusi
gli .material-icons che richiedono il loro proprio font ligature.
Sostituito '*' con whitelist esplicita di elementi UI; aggiunto override
esplicito per .material-icons / .q-icon che forza font 'Material Icons'.
2) JSON keys invisibili nel code block config:
NiceGUI ui.code usa Prism.js (non highlight.js) per il syntax highlighting.
Aggiunti selettori .token.* coordinati con la palette neon:
- .token.property / .token.attr-name → cyan (chiavi JSON)
- .token.string → green (valori string)
- .token.number / .token.boolean / .token.null → pink
- .token.punctuation / .token.operator → muted
- .token.keyword / .token.builtin → amber
Più reset text-shadow per pre[class*=language-].
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggiunta route /genomes:
- Run selector + Top K (10/25/50)
- Tabella sortable con fitness, DSR, Sharpe, max DD, trades, style, T, lookback
- Selezione riga → pannello ispezione con 6 metric card e blocchi code per
system_prompt + raw_text + parse_error eventuale
- Auto-refresh ogni 3s (mantiene selezione corrente se ancora valida)
- Header nav aggiornato con link Genomes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prima fase migrazione da Streamlit a NiceGUI. Pagina indice riproduce
l'Overview con:
- run selector reattivo
- 2 progress bar live (generations, evaluations) con WebSocket push
- 4 metric card (top fitness, median, parse success %, cost)
- timer auto-refresh ogni 3s (no click manuale)
- status badge colorato (running/completed/failed)
- config JSON code block
Avvio: uv run python -m multi_swarm.dashboard.nicegui_app (porta 8080)
Streamlit resta attivo su 8501 durante la migrazione. Backend invariato:
riusa dashboard/data.py senza modifiche.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>