Commit Graph

95 Commits

Author SHA1 Message Date
Adriano 67ae6ff74e feat(hypothesis): pattern guidance — forma curve + ripetibilità nel system prompt
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>
2026-05-13 16:49:35 +02:00
Adriano 1a1dfb7a73 feat(fitness): multi-objective combined = alpha*IS + (1-alpha)*OOS opt-in
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>
2026-05-13 16:47:53 +02:00
Adriano 3fcad79f8d fix(llm): timeout esplicito 60s sul client OpenAI (no più hang infinito)
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>
2026-05-12 22:15:31 +02:00
Adriano 242724ba05 feat(phase-2.6): Walk-Forward Validation + min-trades filter parametrico
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>
2026-05-12 17:31:22 +02:00
Adriano 4c184bb5f7 feat(scripts): backtest_strategy.py — esegue una strategia standalone su range esteso
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>
2026-05-12 16:56:47 +02:00
Adriano cf42dd85f3 feat(fitness): v2 soft-kill opt-in (HIGH non hard non azzerano, applicano penalty)
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>
2026-05-12 13:52:22 +02:00
Adriano bf70acc322 feat(adversarial): flat_too_long_threshold parametrico (CLI ablation)
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>
2026-05-12 13:45:38 +02:00
Adriano 597815a106 docs(plan): Phase 2.5 task 6 spuntato + status finale tutti i task completati
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>
2026-05-12 10:43:02 +02:00
Adriano ba4eb09a71 feat(phase-2.5): Task 6 cost_kind attribution + fees_eat_alpha threshold CLI
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>
2026-05-12 10:42:13 +02:00
Adriano 0e01de156f fix(llm): RateLimitError retryable + retry tenacity 3→5 + backoff fino a 30s
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>
2026-05-12 00:44:32 +02:00
Adriano 4c119a109e fix(llm): exclude OpenRouter Novita provider via extra_body (Qwen 2.x rotto)
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>
2026-05-12 00:10:27 +02:00
Adriano a12aead3e5 fix(hypothesis): catch EmptyCompletionError dentro propose() invece di propagare
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>
2026-05-12 00:07:53 +02:00
Adriano ec80af9f26 feat(phase-2.5): population_prompt_diversity metric + piano aggiornato
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>
2026-05-11 23:52:09 +02:00
Adriano c38311e5fa feat(phase-2.5): mutate_prompt_llm operator + weighted dispatcher + GA wiring
Implementazione completa Phase 2.5 (LLM prompt mutator) seguendo il piano in
docs/superpowers/plans/2026-05-11-mutate-prompt-llm-phase-2-5.md.

Nuovo modulo src/multi_swarm/genome/mutation_prompt_llm.py:
- 6 mutation instructions (tighten_threshold, swap_comparator, add_condition,
  remove_condition, change_timeframe, add_temporal_gate)
- mutate_prompt_llm(g, llm, rng, mutator_tier=ModelTier.B): clona genome con
  tier B per la call mutator, costruisce system+user prompt con istruzione
  scelta random, estrae prompt da tag <prompt>...</prompt>, valida
- is_valid_prompt(): 3 check (lunghezza >= 50, keyword tecnica, diff > 5%
  Levenshtein-like via difflib.SequenceMatcher)
- Fallback random_mutate su qualsiasi validation fail O LLM exception

Esteso src/multi_swarm/genome/mutation.py con weighted_random_mutate dispatcher:
con probabilità prompt_mutation_weight invoca mutate_prompt_llm, altrimenti
random_mutate. Backward compat: llm=None oppure weight=0 → solo scalare.

Integrazione GA loop + RunConfig:
- GAConfig.prompt_mutation_weight: float = 0.0 (default off)
- next_generation(llm=...) propagato all'invocazione mutator
- RunConfig.prompt_mutation_weight con stesso default
- run_phase1: passa llm a next_generation solo se weight > 0
- scripts/run_phase1.py: flag CLI --prompt-mutation-weight

Tests (+18, 175 totale):
- tests/unit/test_mutation_prompt_llm.py (12): extract_prompt, is_valid_prompt
  3 check, operator success + fallback su 3 modi (invalid/identical/exception),
  tier B per LLM call, istruzione scelta dal pool
- tests/unit/test_mutation_dispatcher.py (4): weight 0/1/None + distribuzione
  30/70 su 1000 estrazioni con tolleranza ±5%
- tests/integration/test_ga_loop_with_prompt_mutator.py (2): loop con
  weight=1.0 produce prompt evoluti; backward compat weight=0 invariato

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 23:49:46 +02:00
Adriano 8ec45c5c1b revert(config): rollback tier C a qwen-2.5-72b-instruct (qwen3-235b inferiore)
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>
2026-05-11 23:45:52 +02:00
Adriano 9344395760 fix(dashboard): cost live durante run (era 0 fino a complete_run)
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>
2026-05-11 22:58:03 +02:00
Adriano 6f6fbb30a0 fix(dashboard): JSON config renderizzato come HTML colorato custom (no Prism/hljs)
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>
2026-05-11 22:52:50 +02:00
Adriano 171f554916 fix(dashboard): icone Material rese come testo + JSON keys invisibili
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>
2026-05-11 22:48:53 +02:00
Adriano 9e740cbcbd fix(dashboard): leggibilità code block JSON config con syntax highlighting custom
Background code più chiaro (#1A1A24) e font 13.5px line-height 1.6 per
leggibilità migliore.

Override token highlight.js coordinati con palette neon:
- .hljs-attr (chiavi JSON) → cyan #00D9FF
- .hljs-string (valori stringa) → green #00E676
- .hljs-number, .hljs-literal (numeri/bool/null) → pink #FF2D87
- .hljs-keyword, .hljs-built_in → amber #FFB800
- .hljs-punctuation, .hljs-meta → muted #7A7A8C

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:45:18 +02:00
Adriano 2acba2077b style(dashboard): redesign Neon Trading Dashboard ispirato a screenshot riferimento
Sostituita palette Modern Fintech con look "neon trading" pink+cyan:
- BG #0A0A0F near-black con dual radial glow (pink top-left, cyan bottom-right)
- Surface #13131A card base, hover #1C1C26
- Primary pink #FF2D87 (max fitness, key metrics)
- Secondary cyan #00D9FF (median, entropy)
- Accent amber #FFB800 (p90, warnings)
- Success neon green #00E676, danger neon red #FF3D60

Effetti visivi aggiunti:
- Card top-border glow gradient (linea pink luminosa)
- Card hover: pink shadow esterno + border 0.5 opacity
- Inset shadow per dare profondità (subtle highlight top)
- Brand-dot pulsing animation 2s (glow pink pulsato)
- Header glassmorphic blur(20px) saturate(180%)
- Chart fill area gradient sotto le curve max/entropy
- Linee Plotly spline smoothing 0.6 (curve morbide)
- Marker bordo bianco per pop

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:43:48 +02:00
Adriano 0486e19829 style(dashboard): redesign moderno con palette Modern Fintech Dark
Sostituita palette Inter FC con design language fintech moderno tipo
Linear/Tremor/Plane:

Palette:
- BG #0A0E1A deep blue-black con radial gradient sottile
- Surface #131829 / surface-2 #1A2138 per hover/active
- Primary cyan #06B6D4 (highlight performance)
- Secondary purple #A78BFA (median curves)
- Accent amber #F59E0B (warnings, p90)
- Success #10B981, danger #EF4444

Typography:
- Inter da Google Fonts (peso 400-700)
- JetBrains Mono per code/numeri tabulari
- letter-spacing negativo + tnum font-feature

Component refinement:
- Card: border sottile rgba + hover cyan glow, no gradient pesante
- Header: backdrop-blur 12px + glass effect
- Nav link: underline animato bottom su active
- Brand: dot pulsante cyan con glow
- Progress bar: 8px rounded, track surface-2
- Metric card: layout left-aligned, label uppercase tracked, value 26px
  con accent class (cyan/purple/amber/green) per coding semantico
- Tabella: header uppercase tracked, row hover cyan-tinted
- Code block: surface scuro + border + JetBrains Mono

Plot Plotly:
- paper_bgcolor → surface (no più sfondo full-bleed)
- gridcolor neutral rgba(slate, 0.08)
- Legend bg surface 0.95 opacity

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:39:59 +02:00
Adriano 2f38562e23 feat(dashboard): NiceGUI Genomes page con top-K + ispezione singolo genoma
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>
2026-05-11 22:35:52 +02:00
Adriano 56e22584d9 feat(dashboard): NiceGUI GA Convergence + Inter FC dark theme
Aggiunta route /convergence con due grafici Plotly live:
- Fitness convergence (max, p90, median) — auto-refresh ogni 3s
- Diversity entropy con gate threshold 0.5
- Tabella generazioni ordinabile

Applicata palette Inter FC su entrambe le pagine:
- Sfondo nero #000000
- Surface dark navy #010E80 (heritage Inter)
- Primary blu #1E5BC6, secondary blu #0068A8, accent oro #FFD700

Custom CSS via ui.add_head_html + ui.colors() quasar override.
Header navigazione condiviso con link attivo evidenziato.
Plotly: template plotly_dark + paper/plot bg neri + gridcolor rgba.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:33:53 +02:00
Adriano 5f28884974 feat(dashboard): NiceGUI port — pagina Overview con auto-refresh live
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>
2026-05-11 22:28:19 +02:00
Adriano 7b790b1bc3 feat(dashboard): progress bar live + top fitness sulla pagina Overview
Aggiunto blocco "Progresso run" sopra le metriche statiche con:
- progress bar generazioni (gens_done / n_generations)
- progress bar evaluations (evals_done / pop × gen) con percentuale
- metric top fitness / median fitness / cost so far
- pulsante Refresh manuale + timestamp ultimo update
- emoji status (🟢 running /  completed /  failed)

Niente nuove dipendenze: solo st.progress + st.rerun standard Streamlit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:21:04 +02:00
Adriano 41e26cbe5b docs(plan): Phase 2.5 mutate_prompt_llm — operator + dispatcher + integration
Piano in tasca: NON attivare finché Phase 2 (qwen3 + temporal) non raggiunge
una delle condizioni di trigger documentate:
- plateau max fitness < 0.01 per >= 4 gen consecutive
- diversità prompt Levenshtein <= 0.15
- top genome quasi-fit (>= 0.10) con adversarial HIGH >= 2

Sei tipi di mutazione (tighten_threshold, swap_comparator, add/remove_condition,
change_timeframe, add_temporal_gate) via mutator tier B (deepseek-v4-flash),
weighted_random_mutate dispatcher con default 0.0 (opt-in), diversity guard
Levenshtein, fallback random_mutate su validation fail.

Costo stimato +$0.006/run. A/B con baseline B = phase2-qwen3-001 vs
trattamento T = phase2-qwen3-prompt-mut-001 per attribuzione effetto.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:16:30 +02:00
Adriano 9c53995f23 feat(config): align tier defaults to cost-conscious models + qwen3-235b on tier C
- Tier S → google/gemini-3-flash-preview ($0.50/$3.00)
- Tier A/B → deepseek/deepseek-v4-flash ($0.14/$0.28)
- Tier C → qwen/qwen3-235b-a22b-2507 ($0.071/$0.10) — Phase 2 target
- Tier D → openai/gpt-oss-20b ($0.03/$0.14)

Aggiornato cost_tracker con prezzi reali per tier. Defaults config.py
ora rispecchiano .env corrente per evitare divergenze dead-code.

Tier S/A/B/D restano cablati ma non ancora invocati nel loop Phase 2
(solo Hypothesis tier C attivo).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:13:49 +02:00
Adriano 68637d1102 feat(hypothesis): aggiungi feature temporali al prompt con 2 esempi few-shot 2026-05-11 17:04:07 +02:00
Adriano 36cbfadb40 test(protocol): integration test gating temporale + sma 2026-05-11 17:03:05 +02:00
Adriano 2014ed3815 test(protocol): compiler semantica minute_of_hour su 1h 2026-05-11 17:02:02 +02:00
Adriano 22a934a6cf test(protocol): compiler semantica dow + is_weekend 2026-05-11 17:01:02 +02:00
Adriano 9d1f97cff3 feat(protocol): dispatcher temporal features (hour) in compiler 2026-05-11 16:59:26 +02:00
Adriano 0e9489bf88 test(protocol): parametrize temporal feature validator tests + float literals
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 16:58:08 +02:00
Adriano 3e9a4efcc2 feat(protocol): extend KNOWN_FEATURES with temporal feature names 2026-05-11 16:56:36 +02:00
Adriano 30dbba4d74 docs: piano implementativo feature temporali
7 task TDD-driven: estensione grammar, dispatcher compiler per 4
feature temporali (hour/dow/is_weekend/minute_of_hour), aggiornamento
prompt Hypothesis con few-shot, smoke run end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:54:55 +02:00
Adriano c6cb32325e docs: design spec feature temporali Phase 2
Aggiunge hour/dow/is_weekend/minute_of_hour come FeatureNode nella
grammatica esistente. Universal access (non passa da feature_access),
riuso di FeatureNode (no nuovo tipo AST), few-shot examples nel prompt
Hypothesis. Cinque file toccati, ~120 LOC, backward-compatible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:52:28 +02:00
Adriano 1a171acfb2 docs: decision memo Phase 1.5 nemotron run (NO-GO)
Run phase1.5-nemotron-001 completato in 2h26min, costo $0.1244.
Max fitness 0.0215 stagnante (15x peggio del baseline qwen 0.3347),
DSR=0 universale, Sharpe -1.08/-1.15. Loop non converge.

Adversarial Phase 1.5 attivo: 98 finding totali, 35 fees_eat_alpha
HIGH + 15 flat_too_long + 8 time_in_market — i 3 nuovi check killano
correttamente, ma popolazione non ha materiale sano da cui evolvere.

Tre direzioni candidate per Phase 2: A) rollback qwen-2.5-72b,
B) prompt re-tuning nemotron, C) promuovere deepseek-v4-flash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:46:35 +02:00
Adriano 9d0deb3ae0 fix(llm): handle empty completions + missing usage (nemotron-friendly)
EmptyCompletionError ora retryable. resp.usage può essere None su
provider :free (es. nemotron-3-super-120b-a12b:free) → no assert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:45:49 +02:00
Adriano d3662f6098 feat(adversarial): time_in_market_too_high HIGH (>80% always-in-market)
Simmetrico opposto di flat_too_long: penalizza strategie LONG/SHORT su
piu' dell'80% delle bar. Una sempre-in-market e' leveraged B&H camuffato,
esposto a funding cumulato (perp ogni 8h), tail risk eventi notturni e
nessuna opportunity-cost flexibility. Sweet spot fitness positiva: 5-80%
time in market.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:54:46 +02:00
Adriano 23c9e37f94 docs: aggiungi clone URL Gitea + nota Phase 1.5 in corso
- Sezione 'Repository' con clone SSH ssh://git@git.tielogic.xyz:222/Adriano/Multi_Swarm_Coevolutive.git
- Stato Phase 1.5 (Adversarial hardening) in corso, commit 56a631f gia' applicato.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:48:19 +02:00
Adriano 56a631f38a feat(adversarial): phase 1.5 hardening (tighter thresholds + flat_too_long + fees_eat_alpha)
Stringe le soglie esistenti e aggiunge due check HIGH per killare le
strategie degeneri scoperte nel run v5 (top-1 +2.66% vs BTC B&H +106%,
flat 99.8% del tempo, fees 69% del lordo).

- overtrading: soglia da n_bars/5 a n_bars/20 (MEDIUM)
- undertrading: HIGH se n_trades < 10 (era MEDIUM <5) — sample troppo
  piccolo per distinguere edge da rumore (lucky shot)
- flat_too_long (NEW, HIGH): signal attivo per <5% delle bar — la
  strategia ha mancato il regime, e' una non-strategia
- fees_eat_alpha (NEW, HIGH): gross_pnl > 0 ma fees > 50% del lordo —
  margine sottile non sostenibile in produzione

Test count: 141 -> 145 (+4 nuovi test deterministici via monkeypatch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:36:35 +02:00
Adriano 690da30272 docs: aggiorna README con architettura completa + esito Phase 1
- Stato Phase 1 completata (5/5 hard gate passati).
- Link a decision memo + technical report.
- Architettura modulare aggiornata (cerbero_ohlcv invece di ccxt, JSON
  parser, fitness v1 continua, dashboard aquarium).
- Variabili .env corrette (no ANTHROPIC_API_KEY, modelli per tier).
- Costi tipici reali ($0.07 per run, $0.19 Phase 1 totale).
- Cerbero MCP setup aggiornato (uv run cerbero-mcp, port 9001).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:20:42 +02:00
Adriano 943aa38cf2 docs: finalize Phase 1 decision memo + technical report
Phase 1 chiusa con tutti i 5 hard gate passati (run phase1-real-005):

- Loop converge: 3 gen consecutive crescita median 0.0001 -> 0.0188.
- Parse success: 100% (98/98) grazie a JSON grammar.
- Top-5 vs median: 1116x ratio (top-1 fit 0.3347 vs median 0.0003).
- Entropy fitness: 0.914 a gen 9 (sopra soglia 0.5).
- Cost: $0.069 reale vs $700 cap.

Decision: GO Phase 2 con 3 aggiustamenti (Adversarial soglie piu' strette,
speciation di base, walk-forward 70/30).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:56:42 +02:00
Adriano d159075182 feat(ga): fitness continua v1 con tanh(sharpe) + penalita' moltiplicativa di drawdown
Phase 1 v0 usava `max(0, dsr - 0.5*max_dd)` che azzerava brutalmente la fitness
quando max_dd > 2*dsr. Real run v4 aveva 55/55 strategie a fitness=0 (DSR ~0.001,
max_dd > 0.5), zero pressione selettiva sul GA.

v1: base = 0.5*dsr + 0.5*0.5*(tanh(sharpe)+1) in [0,1], modulata da penalty
moltiplicativa 1/(1+k*max_dd) in (0,1]. Hard kill (no-trade, HIGH adversarial)
preservati. Fitness sempre >0 per strategie con almeno 1 trade -> il GA
puo' preferire "meno cattivo" a "catastrofico" anche su sharpe negativo.

Tests: +3 nuovi (continuous mediocre, bounded, monotonic drawdown), 4 esistenti
restano verdi. Suite 138 -> 141 passed. ruff + mypy strict puliti.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:24:05 +02:00
Adriano d4fcb42fc5 feat(agents): hypothesis retry-with-error-feedback (max 1 retry)
HypothesisAgent.propose ora riprova una volta in caso di parse o
validation error: il prompt user del retry include l'output precedente
(troncato a 800 char) e il messaggio di errore, così l'LLM può
auto-correggersi. Configurabile via max_retries (default 1).

Cambia il modello dati di HypothesisProposal: completion (singolare)
diventa completions: list[CompletionResult] con n_attempts. L'orchestrator
itera su completions per registrare il costo di ogni chiamata LLM,
incluse le retry.

Phase 1 v4 mostrava 64% di parse failure recuperabili: il retry punta
a tagliare quel tasso senza inflazionare i token oltre 2x worst-case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:20:47 +02:00
Adriano 44eb6436c1 refactor(protocol): swap S-expression grammar for strict JSON Schema
Sostituisce la grammatica S-expression con uno schema JSON stretto. La
grammatica S-expression falliva il parsing nel 64% delle generazioni del
modello Qwen3-235B sul run reale; JSON e' nativo per gli LLM moderni e
si parsa con json.loads.

Cambiamenti principali:
- grammar.py: costanti rinominate LOGICAL_OPS / COMPARATOR_OPS /
  CROSSOVER_OPS / ACTION_VALUES / KIND_VALUES.
- parser.py: nuovo AST a dataclass tipizzato (OpNode, IndicatorNode,
  FeatureNode, LiteralNode, Rule, Strategy); parse_strategy ora consuma
  JSON tramite json.loads.
- validator.py: walk dispatchato per tipo (isinstance) invece di
  pattern-matching su 'kind'; arity check su operatori e indicator.
- compiler.py: traversal del nuovo AST tipizzato, dispatch per
  isinstance; logica indicator/feature/literal invariata.
- hypothesis.py: prompt SYSTEM riscritto con esempi JSON e vincoli
  espliciti su no-nesting; estrazione via fence ```json``` + fallback
  brace-balanced.
- __init__.py: re-export pubblico delle entita' del protocollo.
- Tutti i test (parser, validator, compiler, hypothesis_agent,
  falsification, adversarial, e2e, smoke_run) migrati a JSON.
- Rimossa dipendenza sexpdata da pyproject.toml + uv.lock.

Test: 135 passed (era 122; aggiunti casi parser/validator).
ruff + mypy strict clean. Smoke run end-to-end OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:17:26 +02:00
Adriano df76906505 fix(protocol): arity check stretto per indicator + reject nested expressions
Run reale phase1-real-003 ha rivelato: l'LLM genera occasionalmente
"(indicator sma 20 50)" o "(indicator sma (feature close) 20)". Il primo
crashava _ind_sma con TypeError. Il secondo passava attraverso il
validator ma non era supportato dal compiler.

Validator ora:
- Aggiunge INDICATOR_ARITY: sma/rsi/atr/realized_vol = 1 arg, macd = 0-3.
- Rifiuta esplicitamente Node fra gli args di indicator (no-nesting Phase 1).
- Rifiuta arity fuori range con messaggio chiaro.

Strategie con questi pattern vengono ora rigettate dal validator come
parse_error invece di crashare il run. Test suite resta 122 PASSED.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:35:54 +02:00
Adriano d9423a1ab5 fix(data,protocol): paginazione OHLCV + macd accetta signal param
Run reale phase1-real-002 ha rivelato:

1. Cerbero/Deribit cap ~5000 candele per call. Una richiesta di 2 anni
   1h (17500 candele) ritorna troncata. CerberoOHLCVLoader._fetch ora
   pagina in chunk da 4500 barre, concatena e dedupe.

2. _ind_macd accettava solo (df, fast, slow). Il prompt suggerisce
   "(indicator macd 12 26 9)" con 3 numeri (fast/slow/signal). Aggiunto
   signal=9 default e calcolo histogram (macd_line - signal_line).

Test suite 122 PASSED, ruff e mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:27:27 +02:00
Adriano 15a4138bbd fix(agents): tighten hypothesis prompt + normalize max_drawdown
Run reale phase1-real-001 ha rivelato due problemi:

1. 67% parse_error perche' qwen3 nestava indicatori non supportati
   (es. "(sma (indicator realized_vol 30) 150)"). Il prompt SYSTEM
   ora esplicita le regole strette: indicator non e' annidabile,
   sma/rsi/etc. esistono solo come 1o argomento di indicator,
   crossover/crossunder accetta espressioni-serie come (feature close)
   o (indicator sma N).

2. max_drawdown calcolato su equity assoluta (P&L in unita' BTC) +1.0
   produceva drawdown nominali enormi (>89000) per strategie con
   posizioni perdenti su BTC a $96k. Normalizziamo dividendo per il
   notional iniziale (close[0]), cosi' max_dd diventa drawdown
   relativo al wealth iniziale.

Test suite resta 122 PASSED, ruff e mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:23:50 +02:00
Adriano 6a201c7e49 docs: scaffolding decision memo + technical report Phase 1
Aggiunge i template per gate decision memo (sez. 4.4 spec) e technical
report (sez. 4.5 spec). Da popolare con numeri reali a chiusura del run
phase1-real-001 (in corso).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:21:26 +02:00