refactor(layout): docs+tests core sotto modulo, cleanup superflui, README strategy
Ownership per modulo:
- Move docs/ root → src/multi_swarm_core/docs/{design,decisions,reports}/
* 00_documento_zero.md + coevolutive_swarm_system.md → docs/design/
* decisions/* → docs/decisions/
* reports/2026-05-14-stato-progetto-e-roadmap.md → docs/reports/
- Move tests/ root → src/multi_swarm_core/tests/
Cleanup superflui consumati (audit trail preservato in docs/decisions):
- poc_trading_swarm.md (POC superato — Phase 3 attiva in prod)
- docs/reports/2026-05-10-phase1-technical-report.md (superato dal 14-mag)
- docs/superpowers/plans/*.md (3 file, plan consumati)
- docs/superpowers/specs/*.md (2 file, spec consumate)
- tests/unit/paper_trading/ (vuota, paper_trading e' migrato in strategy_crypto)
- Directory docs/ root cancellata
NEW: src/strategy_crypto/README.md — overview strategia (scope, layout,
run, DB schema, pattern N strategie future)
Root resta minima: README.md, pyproject.toml, docker-compose.yml,
Dockerfile, .env*, uv.lock + data/series/state/scripts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,231 +0,0 @@
|
||||
# Gate Phase 1 — Decision Memo
|
||||
|
||||
**Data**: 10 maggio 2026
|
||||
**Run di riferimento**: `phase1-real-005` (id `1c526996160446b18c0fb57d94874975`)
|
||||
**Run scartati durante iterazione**: `phase1-real-001..004` (vedi sez. 3)
|
||||
**Spesa totale Phase 1**: $0.18 cumulativi (≈0.025% del cap $700)
|
||||
**Tempo speso Phase 1**: 1 giornata di lavoro (10 maggio 2026, iterazione bug-fix incluse)
|
||||
**Status**: ✅ TUTTI E 5 I HARD GATE PASSATI
|
||||
|
||||
---
|
||||
|
||||
## 1. Premessa
|
||||
|
||||
Questo memo formalizza la valutazione dei 5 hard gate definiti nello spec strategico (`docs/superpowers/specs/2026-05-09-decisione-strategica-design.md`, sez. 4.4) sulla base del run `phase1-real-005`. I gate sono numerici per costruzione: l'esito PASS/FAIL è meccanico. Discrezionale è solo l'azione successiva.
|
||||
|
||||
---
|
||||
|
||||
## 2. Author pass — valutazione hard gate
|
||||
|
||||
### Gate 1 — Loop converge
|
||||
|
||||
**Soglia**: la fitness mediana della popolazione cresce per ≥3 generazioni consecutive prima di plateau.
|
||||
|
||||
**Misura osservata**:
|
||||
|
||||
| Generazione | Median fitness | Max fitness | P90 | Entropy |
|
||||
|---|---|---|---|---|
|
||||
| 0 | 0.0001 | 0.0601 | 0.0165 | 0.588 |
|
||||
| 1 | 0.0042 | 0.1893 | 0.0731 | 1.261 |
|
||||
| 2 | 0.0188 | 0.3347 | 0.2039 | 1.333 |
|
||||
| 3 | 0.0069 | 0.3347 | 0.3347 | 1.347 |
|
||||
| 4 | 0.0910 | 0.3347 | 0.3347 | 1.415 |
|
||||
| 5 | 0.0016 | 0.3347 | 0.3347 | 0.611 |
|
||||
| 6 | 0.0040 | 0.3347 | 0.3347 | 0.886 |
|
||||
| 7 | 0.0151 | 0.3347 | 0.3347 | 0.982 |
|
||||
| 8 | 0.0066 | 0.3347 | 0.3347 | 0.746 |
|
||||
| 9 | 0.0061 | 0.3347 | 0.3347 | 0.914 |
|
||||
|
||||
**Generazioni consecutive di crescita mediana**: Gen 0→1→2 (0.0001→0.0042→0.0188 = 3 consecutive). Max raggiunto a gen 2, stabile da lì in poi (plateau dell'elite, comportamento atteso con elite_k=2).
|
||||
|
||||
**Esito**: ✅ **PASS**
|
||||
|
||||
**Razionale**: la convergenza iniziale è chiara (3 generazioni di crescita 4-50x), poi il max plateaua per elite preservation. La median oscilla per turnover di novellini, non per regressione strutturale.
|
||||
|
||||
---
|
||||
|
||||
### Gate 2 — Output formalizzabile
|
||||
|
||||
**Soglia**: ≥80% delle proposte LLM passano il parser senza intervento manuale.
|
||||
|
||||
**Misura osservata**:
|
||||
- Evaluations totali: 98
|
||||
- Parse success: **98 (100.0%)**
|
||||
- Parse error: 0
|
||||
|
||||
**Esito**: ✅ **PASS** (soglia superata di 20 punti percentuali)
|
||||
|
||||
**Razionale**: il refactor da S-expression a JSON Schema (commit `44eb643`) ha eliminato la fragilità sintattica. Combinato con il retry-with-error-feedback (`d4fcb42`), zero retry effettivamente serviti — JSON è already self-correcting per qwen3-235b. Senza questi fix, il run v4 mostrava 35.9% parse success.
|
||||
|
||||
---
|
||||
|
||||
### Gate 3 — Tail superiore
|
||||
|
||||
**Soglia**: i top-5 genomi hanno DSR (qui letto come fitness, dato il design v0) ≥ 1.5x la mediana di popolazione.
|
||||
|
||||
**Misura osservata**:
|
||||
- Median fitness popolazione: 0.0003
|
||||
- Top-5 fitness media: 0.2587
|
||||
- Top-1 fitness: 0.3347
|
||||
- **Ratio (top-1 / median)**: ≈1116x (molto sopra soglia 1.5x)
|
||||
|
||||
**Esito**: ✅ **PASS** (ordini di grandezza sopra soglia)
|
||||
|
||||
**Razionale**: il tail superiore è netto e separato. Esiste un cluster di top performer chiaramente distinguibile da mediocri / killed. Il bigger picture: la fitness function continua (commit `d159075`) ha permesso al GA di distinguere "lievemente migliore" da "completamente disastroso", evitando l'appiattimento a zero del run v4.
|
||||
|
||||
---
|
||||
|
||||
### Gate 4 — Diversità non collassa
|
||||
|
||||
**Soglia**: entropia della distribuzione di fitness in popolazione > 0.5 a fine run.
|
||||
|
||||
**Misura osservata**:
|
||||
- Entropy gen 0: 0.588
|
||||
- Entropy gen finale (gen 9): **0.914**
|
||||
- Trend: oscilla 0.6-1.4 con un dip a gen 5 (0.611) ma sempre sopra soglia.
|
||||
|
||||
**Esito**: ✅ **PASS**
|
||||
|
||||
**Razionale**: la popolazione mantiene varianza di fitness ben sopra 0.5. Cognitive styles sopravvissuti a gen 9: 3 su 6 originali (engineer, physicist, historian), con engineer dominante (3 di 5 elites tracciati). La selezione comprime la diversità cognitiva ma non l'entropia di fitness — segnale che la pressione selettiva funziona senza monocoltura.
|
||||
|
||||
---
|
||||
|
||||
### Gate 5 — Cost predictability
|
||||
|
||||
**Soglia**: spesa entro ±30% della stima preventivata ($500-700 per Phase 1).
|
||||
|
||||
**Misura osservata**:
|
||||
- Stima preventivo originale: $500-700 (basata su pricing Sonnet/Anthropic)
|
||||
- Spesa reale cumulativa Phase 1: ≈$0.18 (somma di v1-v5)
|
||||
- Spesa run v5 da solo: $0.069
|
||||
- Deviazione: -99.97% rispetto al preventivo (sotto cap di **~10000x**)
|
||||
|
||||
**Esito**: ✅ **PASS** (sotto cap; la deviazione verso il basso non è failure)
|
||||
|
||||
**Razionale**: la migrazione a OpenRouter+qwen3-235b come tier C dominante ha cambiato l'ordine di grandezza dei costi (~$0.40/1M token vs Sonnet $3/$15). Il preventivo originale assumeva Sonnet come baseline; la realtà è 1000x più economica. Phase 2 cap ($700-1100) ha margine drammatico, eventualmente utilizzabile per ablation più aggressive o uso di tier B/S sui top candidati.
|
||||
|
||||
---
|
||||
|
||||
## 3. Iterazione: 5 run prima del PASS
|
||||
|
||||
I primi 4 run (`phase1-real-001..004`) hanno servito da bug-discovery. Sintesi:
|
||||
|
||||
| Run | Esito | Problema | Fix applicato |
|
||||
|---|---|---|---|
|
||||
| 001 | aborted | 67% parse_error (LLM nesta indicators); max_dd su equity assoluta produce drawdown 89000 | Prompt strict + max_dd normalizzato su notional (commit `15a4138`) |
|
||||
| 002 | failed | `_ind_macd` accetta 2 args, prompt suggeriva 3 (fast/slow/signal) | macd accetta signal (commit `d9423a1`); OHLCV cap Cerbero ~5000 → paginazione (commit `d9423a1`) |
|
||||
| 003 | failed | Validator non controllava arity indicator → crash compiler su `(indicator sma 20 50)` | INDICATOR_ARITY in validator + reject nested (commit `df76906`) |
|
||||
| 004 | completed FAIL | 35.9% parse_error, fitness tutti 0 (clamp a 0 troppo duro) | Switch a JSON grammar + retry+feedback + fitness continua (commit `44eb643`, `d4fcb42`, `d159075`) |
|
||||
| 005 | **completed PASS** | — | — |
|
||||
|
||||
Costo cumulativo iterazione: $0.034 (v1) + $0.018 (v2, abort) + $0.015 (v3, abort) + $0.057 (v4) + $0.069 (v5) ≈ **$0.19 totale**.
|
||||
|
||||
---
|
||||
|
||||
## 4. Soft observations
|
||||
|
||||
### 4.1 Trade distribution sui 98 evals
|
||||
|
||||
| Categoria | n | % |
|
||||
|---|---|---|
|
||||
| Zero trade (kill no_trades HIGH) | 42 | 42.9% |
|
||||
| Undertrading (1-4 trade, MEDIUM) | 5 | 5.1% |
|
||||
| Normal (5-100 trade) | 9 | 9.2% |
|
||||
| Overtrading (>100 trade) | 42 | 42.9% |
|
||||
|
||||
**Osservazione critica**: il 42.9% di overtrading non è flaggato dall'Adversarial. Il check attuale soglia `n_trades > n_bars/5 = 17545/5 = 3509` — troppo alto. Phase 2 dovrebbe abbassare a `n_bars/20` o usare metrica relativa (trade rate per regime).
|
||||
|
||||
### 4.2 Cognitive style nei top-5
|
||||
|
||||
- physicist: 2 (top-1 e top-5)
|
||||
- engineer: 2 (top-2 e top-4)
|
||||
- ecologist: 1 (top-3)
|
||||
|
||||
historian, biologist, meteorologist non compaiono nei top-5 → loro stili producono strategie meno performanti su BTC perp 1h. Possibile bias del market regime.
|
||||
|
||||
### 4.3 Top-1 ispezione qualitativa
|
||||
|
||||
Genoma `696052b89f78b28f`, gen 2, style `physicist`, temperature 0.68, lookback 200.
|
||||
|
||||
**System prompt** (dal cognitive style "engineer"):
|
||||
> Cerca segnali con rapporto S/N favorevole, filtri causali, robustezza a perturbazioni di calibrazione.
|
||||
|
||||
**Strategia** (3 regole):
|
||||
- **LONG**: SMA(10) crossover SMA(30) AND realized_vol(20) > 0.3% AND RSI(14) < 45.
|
||||
- **SHORT**: SMA(10) crossunder SMA(30) AND realized_vol(20) > 0.3% AND RSI(14) > 55.
|
||||
- **EXIT**: (RSI > 70 AND close crossover SMA(50)) OR realized_vol < 0.1%.
|
||||
|
||||
**Lettura**: trend-following SMA-cross modulato da filtro volatilità (entra solo in regimi con volatilità sopra soglia, esce in regime troppo calmo) e momentum RSI come confirmation/contrarian. Pattern economicamente plausibile, non casuale. 33 trade su 2 anni = uno ogni 22 giorni, sample size modesto ma coerente con strategia trend-following.
|
||||
|
||||
Sharpe 0.381 è positivo ma modesto. Top-2 ed altri top hanno solo 1 trade ("lucky shot" non flaggato come HIGH dall'Adversarial).
|
||||
|
||||
### 4.4 Diversità apparente vs reale
|
||||
|
||||
I top-2 hanno fitness e metriche identiche (0.3347 fit, DSR 0.0021, Sharpe 0.381, max_dd 0.0215, 33 trade). Possibile che siano elite duplicati nelle generazioni successive oppure due genomi distinti che hanno convergencе sulla stessa strategia. Verifica per Phase 2: cluster signal correlation fra top-K e contare specie effettive.
|
||||
|
||||
---
|
||||
|
||||
## 5. Author pass — conclusione
|
||||
|
||||
**Esito complessivo author pass**: ✅ **PASS** su tutti 5 hard gate.
|
||||
|
||||
**Decisione raccomandata dall'autore**: **GO Phase 2** con tre aggiustamenti consigliati:
|
||||
|
||||
1. **Adversarial layer più severo su overtrading/undertrading**: 42.9% di overtrading silenzioso è scope creep di problemi reali. Soglia overtrading da `n_bars/5` a `n_bars/20`; undertrading da `<5 trade` a `<10 trade su training`.
|
||||
|
||||
2. **Speciation in Phase 2**: cognitive style scendono da 6 a 3 a gen 9. Aggiungere protezione esplicita per specie (≥2 specie minimo, ognuna con quota tournament protetta) per evitare monocoltura ai stili dominanti.
|
||||
|
||||
3. **OOS walk-forward critico**: Phase 1 era in-sample. Tutti i top genomi vanno ri-valutati su hold-out 2026 prima di assegnare fitness in Phase 2.
|
||||
|
||||
---
|
||||
|
||||
## 6. Review pass — red team adversarial
|
||||
|
||||
**Modalità review pass**: subagent red-team self-review da parte dell'autore (Adriano Dal Pastro) + co-author Claude Opus 4.7. Fresh-eyes 24h non applicato data l'urgenza di chiudere Phase 1.
|
||||
|
||||
**Critiche strutturate**:
|
||||
|
||||
1. **Cherry-picking**: dei 5 run, 1 ha passato i gate (v5). Il fatto che siano serviti 4 cicli di bug-fix prima del PASS è LEGITTIMO bug-fixing di un sistema nuovo (parse/grammar/fitness math). NON è cherry-picking di seed o config: gli stessi `--seed 42 --population-size 20 --n-generations 10` hanno girato in tutti i run. Cherry-picking sarebbe stato escludere v4 (FAIL) dall'analisi: v4 è citato esplicitamente in §3.
|
||||
|
||||
2. **Statistical robustness**: il DSR è calcolato correttamente (Bailey & López 2014 implementation in `metrics/dsr.py`) con `n_trials=50` per Bonferroni-equivalent deflation. Tuttavia il top-1 ha DSR 0.0021 → praticamente zero significatività. La fitness 0.3347 viene dal contributo `tanh(sharpe)` non da DSR. **Implicazione**: il "successo" del Gate 3 è guidato da Sharpe non da DSR. Non è un PASS spurio (la fitness è ben definita), ma il segnale alpha vero (DSR) è marginale.
|
||||
|
||||
3. **Overfitting in-sample**: tutto il backtest è sullo stesso range 2024-2026. Il top-1 ha Sharpe 0.38 in-sample. Quanto sopravvive in OOS? Sconosciuto. Phase 2 deve misurare gap in-sample/OOS prima di trarre conclusioni alpha-related.
|
||||
|
||||
4. **Trade frequency sospetta nei top**: top-3, top-4, top-5 hanno 1 trade ognuno. Fitness 0.18-0.25 per "una posizione lucky" è artefatto della fitness function continua (sharpe positivo o leggermente negativo + dd minimo). Adversarial undertrading è MEDIUM non HIGH → non killato. Phase 2 deve promuovere undertrading a HIGH quando `n_trades < 10`.
|
||||
|
||||
5. **Cost trap inverso**: $0.069 è ridicolmente basso. Tentazione di Phase 2 di scalare drasticamente (K=100, gen=30, tutto tier B). Resistere: rispetto al cap Phase 2 $700-1100, una 10x dell'attuale = $0.69 ancora trascurabile, ma con tier B (3/15 vs 0.40/0.40) = $7-15 = serio scaling. Disciplina budget Phase 2 invariata.
|
||||
|
||||
**Contro-evidenze raccolte / fix applicati**:
|
||||
- Punto 2 (DSR marginale): documentato esplicitamente. Phase 2 può introdurre `dsr_weight` più alto nella fitness se si vuole pesare la significatività statistica sopra il puro Sharpe.
|
||||
- Punto 4 (undertrading): aggiunto a "aggiustamenti raccomandati" sez. 5.
|
||||
- Punto 3 (OOS): aggiunto a "aggiustamenti raccomandati" sez. 5.
|
||||
|
||||
---
|
||||
|
||||
## 7. Decisione finale
|
||||
|
||||
**Decisione**: ✅ **GO Phase 2** con scope identico allo spec strategico (sez. 5) e tre aggiustamenti integrativi:
|
||||
|
||||
1. Adversarial layer: overtrading/undertrading soglie più stringenti.
|
||||
2. Speciation di base: protezione cognitive style minimum-2 con quota tournament.
|
||||
3. Walk-forward 70/30 con hold-out Q1-Q2 2026 intoccabile.
|
||||
|
||||
**Razionale finale**: tutti i 5 hard gate sono passati con margini ampi su 4/5 (entropy, parse, cost, top-vs-median), margine sufficiente su gate 1 (3 gen di crescita iniziale). Le critiche red team identificate sono incorporate come aggiustamenti Phase 2, non blocker. Il codebase è robusto, modulare, testato (141 PASSED, ruff/mypy strict clean), pronto per estensione.
|
||||
|
||||
**Spesa Phase 1 vs cap**: $0.19 vs $700 cap = 0.027% utilizzato. Margine drammatico per Phase 2.
|
||||
|
||||
**Tempo Phase 1 vs cap**: 1 giorno calendar (vs 4-6 settimane stimati). Velocità da PoC singolo autore + LLM-assisted coding, non scalabile a Phase 2 che ha lavoro di research integrate (DSR multi-testing rigoroso, walk-forward, RF baseline).
|
||||
|
||||
**Documenti correlati prodotti**:
|
||||
- `docs/reports/2026-05-10-phase1-technical-report.md` (report tecnico)
|
||||
- `docs/superpowers/specs/2026-05-09-decisione-strategica-design.md` (spec strategico — sez. 5 contiene scope Phase 2)
|
||||
- `docs/superpowers/plans/2026-05-09-phase1-lean-spike.md` (plan implementativo Phase 1)
|
||||
|
||||
**Prossimi step suggeriti**:
|
||||
1. Aggiornare lo spec strategico con esito Phase 1 (sez. 11 "decisioni risolte").
|
||||
2. Avviare il design di Phase 2 (subagent `superpowers:writing-plans` su un nuovo spec Phase 2 che integra i 3 aggiustamenti).
|
||||
3. Eseguire i 3 aggiustamenti come piccoli fix Phase 1.5 (Adversarial soglie, speciation, walk-forward), poi run di smoke Phase 1.5 per confermare effetto.
|
||||
|
||||
---
|
||||
|
||||
*Memo finalizzato 10 maggio 2026. Versione 1.0.*
|
||||
@@ -1,117 +0,0 @@
|
||||
# Phase 1.5 — Run nemotron tier C — Decision Memo
|
||||
|
||||
**Data**: 11 maggio 2026
|
||||
**Run di riferimento**: `phase1.5-nemotron-001` (id `434c417e2b6f42bb8cf32514e5d0db1d`)
|
||||
**Tier LLM**: C → `nvidia/nemotron-3-super-120b-a12b:free`
|
||||
**Durata wallclock**: 2 h 26 min (08:15 → 10:11 UTC, gen 0 → gen 9)
|
||||
**Spesa totale**: $0.1244 (price-table tier C; il modello effettivo è `:free` su OpenRouter, ma il cost tracker applica la pricing nominale del tier)
|
||||
**Status**: ✅ Completato, ma esito strategico **NO-GO** sulla configurazione corrente
|
||||
|
||||
---
|
||||
|
||||
## 1. Premessa
|
||||
|
||||
Il run `phase1.5-nemotron-001` è la prima esecuzione end-to-end del loop GA con:
|
||||
|
||||
- l'Adversarial layer aggiornato in Phase 1.5 (commits `56a631f` + `d3662f6`), con tre nuovi check HIGH (`flat_too_long`, `fees_eat_alpha`, `time_in_market_too_high`) più i due esistenti rinforzati;
|
||||
- il tier C ribindato a `nvidia/nemotron-3-super-120b-a12b:free`, modello scelto in benchmark contro sette alternative per stabilità JSON e costo nullo;
|
||||
- il fix `EmptyCompletionError` su `llm/client.py` (commit `9d0deb3`) introdotto durante la stessa sessione per gestire le risposte vuote che alcuni provider `:free` ritornano sporadicamente.
|
||||
|
||||
L'obiettivo dichiarato del run era verificare se il nuovo budget di vincoli adversarial — più stretto del v5 — fosse compatibile con la capacità generativa di nemotron, e se la popolazione riuscisse a esplorare una zona di fitness positiva non degenere.
|
||||
|
||||
---
|
||||
|
||||
## 2. Hard gate Phase 1 — ripercorrenza
|
||||
|
||||
I 5 hard gate originali (definiti nello spec strategico di Phase 1) sono stati rivalutati su questo run come sanity check, non come passaggio formale di gate.
|
||||
|
||||
| # | Gate | Soglia | Misura | Esito |
|
||||
|---|------|--------|--------|-------|
|
||||
| 1 | Loop converge | mediana cresce ≥3 gen consecutive | Gen 0→8: median oscilla tra 0.0 e 0.0073 senza crescita strutturale | ❌ FAIL |
|
||||
| 2 | Parse success | ≥80% proposte LLM parse-OK | 81/89 = **91.0%** | ✅ PASS |
|
||||
| 3 | Top-5 ratio | top-5 fitness ≥10× mediana | top-5 = 0.0162–0.0215; mediana ≈ 0 → ratio indefinito | ⚠️ N/A |
|
||||
| 4 | Entropy | ≥0.5 a fine run | 0.845 alla gen 9 | ✅ PASS |
|
||||
| 5 | Budget | costo ≤ cap | $0.1244 vs cap $700 (0.02%) | ✅ PASS |
|
||||
|
||||
Il gate critico è il numero 1. La popolazione non converge: il `max_fitness` resta inchiodato a `0.0215` dalla generazione 0 fino alla 9, segnale che l'elite preservation cattura un singolo genoma poco peggiore degli altri ma altrettanto inadatto, mentre il resto della popolazione non riesce a superarlo. La mediana è zero in 9 generazioni su 10 (singolo picco a 0.0073 in gen 8).
|
||||
|
||||
---
|
||||
|
||||
## 3. Lettura dei top genomi
|
||||
|
||||
I cinque genomi a fitness più alta hanno tutti caratteristiche economicamente disastrose:
|
||||
|
||||
| Genome ID | Fitness | DSR | Sharpe | Total return | n_trades |
|
||||
|-----------|---------|-----|--------|--------------|----------|
|
||||
| `0e1f9d7af25cfd6a` | 0.0215 | 0.000 | −1.083 | −115.9% | 385 |
|
||||
| `85a8116ab2cd2735` | 0.0215 | 0.000 | −1.083 | −115.9% | 385 |
|
||||
| `92aae563277b6f21` | 0.0193 | 0.000 | −1.129 | −131.0% | 597 |
|
||||
| `01d0ca99bbdd7320` | 0.0180 | 0.000 | −1.112 | −131.7% | 602 |
|
||||
| `194b096f7edab53c` | 0.0162 | 0.000 | −1.154 | −150.7% | 369 |
|
||||
|
||||
Il fatto che **DSR sia zero per tutti i top-5** indica che nessuna strategia passa il deflation test di Bailey & López 2014: il loop non sta generando proposte con edge statistico anche solo apparente. Il valore di fitness positivo che li seleziona deriva interamente dal termine `tanh(sharpe) × penalty(dd)` della fitness v1, che resta debolmente non nullo anche per Sharpe negativi grazie alla penalty di drawdown e a saturazioni numeriche. I primi due genomi hanno fitness identico a 0.0215 e total return identico — verosimilmente lo stesso elite riproposto a generazioni adiacenti.
|
||||
|
||||
---
|
||||
|
||||
## 4. Adversarial findings — il sistema fa il suo lavoro
|
||||
|
||||
Il layer Adversarial Phase 1.5 ha emesso 98 finding sul run:
|
||||
|
||||
| Severità | Check | Conteggio |
|
||||
|----------|-------|-----------|
|
||||
| HIGH | `fees_eat_alpha` (nuovo P1.5) | 35 |
|
||||
| MEDIUM | `overtrading` | 19 |
|
||||
| HIGH | `no_trades` | 16 |
|
||||
| HIGH | `flat_too_long` (nuovo P1.5) | 15 |
|
||||
| HIGH | `time_in_market_too_high` (nuovo P1.5) | 8 |
|
||||
| HIGH | `undertrading` | 4 |
|
||||
| HIGH | `degenerate` | 1 |
|
||||
|
||||
Il dato saliente è che i tre check introdotti in Phase 1.5 — `fees_eat_alpha`, `flat_too_long`, `time_in_market_too_high` — sono effettivamente attivi e killano strategie. In particolare `fees_eat_alpha` è la categoria più popolata: 35 occorrenze HIGH. Esempi tipici dai detail dei finding:
|
||||
|
||||
- `Fees $17073.82 = 2032.6% of gross $840.00`;
|
||||
- `Fees $70646.03 = 12671.9% of gross $557.50`;
|
||||
- `Signal flat for 98.8% of bars (>95% threshold)`.
|
||||
|
||||
Il messaggio è netto: il pool di strategie generato da nemotron, ai prompt e ai gradi di libertà attuali, oscilla tra due estremi degeneri — strategie inattive (flat 98%+) e strategie iperattive (overtrading + fee che divorano l'alpha lordo). Phase 1.5 cattura entrambi gli estremi, ma il loop GA non ha materiale di partenza sano da cui evolvere.
|
||||
|
||||
---
|
||||
|
||||
## 5. Decisione
|
||||
|
||||
**Esito**: NO-GO sulla combinazione `tier C = nemotron` + `Phase 1.5 adversarial` come configurazione di Phase 2.
|
||||
|
||||
Le ragioni a supporto della decisione sono tre.
|
||||
|
||||
Primo, la convergenza è assente per nove generazioni consecutive, non un plateau di selezione raggiunto dopo una fase di salita. Non si tratta cioè di un loop che ha già trovato il suo ottimo e lo conserva, ma di un loop che non ne ha trovato uno.
|
||||
|
||||
Secondo, la distanza dal baseline Phase 1 v5 è di un ordine di grandezza: max fitness `0.0215` qui contro `0.3347` nel run di gate Phase 1, mediana che oscilla sullo zero contro una mediana attorno a `0.005`–`0.09`. Nemotron, in questa configurazione, sta producendo proposte qualitativamente più povere di qwen-2.5-72b nello stesso schema operativo.
|
||||
|
||||
Terzo, i finding adversarial non puntano a un bug del sistema ma a una mancanza di edge nelle proposte. Il loop sta sanzionando correttamente — il problema è a monte, nella generazione.
|
||||
|
||||
---
|
||||
|
||||
## 6. Tre direzioni per Phase 2
|
||||
|
||||
Tre opzioni si configurano per il passo successivo. Vanno valutate prima di una nuova esecuzione, non in parallelo a essa.
|
||||
|
||||
**Direzione A — Riportare tier C a `qwen/qwen-2.5-72b-instruct`** (configurazione di gate Phase 1). Il run di riferimento `phase1-real-005` è già un baseline noto: max fitness `0.3347`, top genome problematico (flat 99.8%) ma generato sotto Phase 1 adversarial. Rilanciare lo stesso pool con Phase 1.5 adversarial isolerebbe l'effetto del solo hardening sul medesimo motore generativo, senza confondere variabili. Questo è il percorso più informativo nel breve.
|
||||
|
||||
**Direzione B — Mantenere nemotron ma rilassare i prompt di Hypothesis**. L'ipotesi alternativa è che il prompting attuale, calibrato su qwen, sia troppo terso o troppo vincolato per la modalità di ragionamento di nemotron. Iterare due o tre versioni del prompt — più esempi few-shot, vincoli espliciti su `n_trades` minimo e `time_in_market` target — può cambiare radicalmente la qualità dell'output senza cambiare il modello.
|
||||
|
||||
**Direzione C — Sostituire il tier C con un modello a pagamento di fascia comparabile**. Tra i benchmark precedenti, `deepseek/deepseek-v4-flash` è già usato come tier A/B nel file `.env`; promuoverlo a tier C significa accettare una spesa marginale (stima $1–3 per run di 10 gen × 20 pop) in cambio di una qualità generativa nota.
|
||||
|
||||
La preferenza dell'operatore per modelli cost-conscious orienta verso A o B. La direzione C resta utile come benchmark di controllo se A e B fallissero a loro volta.
|
||||
|
||||
---
|
||||
|
||||
## 7. Operazioni di pulizia eseguite contestualmente
|
||||
|
||||
- Il run zombie `phase1-real-008` (id `6ebcff9f7f6544c18ced50313cf72ca9`, marcato `running` da 07:11 UTC senza processo associato) è stato chiuso a `status='failed'` direttamente in `runs.db`, per evitare contaminazione delle query di dashboard.
|
||||
- Il commit `9d0deb3` (`fix(llm): handle empty completions + missing usage`) è già su `main`. Il `client.py` ora tratta `resp.choices == []` e `resp.usage is None` come errori retryable invece che assertion failure: precondizione necessaria per qualsiasi run successivo su provider `:free`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Note per chi legge
|
||||
|
||||
Questo memo è un documento di decisione, non un rapporto tecnico completo. Il rapporto tecnico esteso del run può essere ricostruito da `runs.db` interrogando le tabelle `runs`, `generations`, `evaluations`, `adversarial_findings`, `cost_records` con `run_id='434c417e2b6f42bb8cf32514e5d0db1d'`. Il design Phase 1.5 e le motivazioni delle soglie adversarial restano definiti nel commit `56a631f` e nei suoi file di test.
|
||||
@@ -1,282 +0,0 @@
|
||||
# Phase 1 Lean Spike — Rapporto Tecnico
|
||||
|
||||
**Autore**: Adriano Dal Pastro
|
||||
**Data**: 10 maggio 2026
|
||||
**Versione**: 1.0 (finalizzato)
|
||||
**Status**: ✅ Phase 1 chiusa, tutti 5 hard gate passati
|
||||
|
||||
**Documenti correlati**:
|
||||
- `docs/superpowers/specs/2026-05-09-decisione-strategica-design.md` (decisione strategica B3)
|
||||
- `docs/superpowers/plans/2026-05-09-phase1-lean-spike.md` (piano implementativo)
|
||||
- `docs/decisions/2026-05-10-gate-phase1.md` (decision memo finale)
|
||||
|
||||
---
|
||||
|
||||
## 1. Setup sperimentale
|
||||
|
||||
L'obiettivo della Phase 1 lean spike è dimostrare che il loop tecnico (LLM hypothesis → backtest falsification → adversarial check → GA selection) funziona end-to-end e produce output formalizzabile. I cinque hard gate definiti nello spec sez. 4.4 misurano feasibility, non alpha edge — quella è valutazione di Phase 2.
|
||||
|
||||
### 1.1 Configurazione del run di riferimento
|
||||
|
||||
Il run `phase1-real-005` (id `1c526996160446b18c0fb57d94874975`) è il primo a superare tutti i gate dopo 4 iterazioni di bug-fix (vedi sez. 3 del decision memo).
|
||||
|
||||
| Parametro | Valore |
|
||||
|---|---|
|
||||
| Population size (K) | 20 |
|
||||
| Generazioni | 10 |
|
||||
| Elite k | 2 |
|
||||
| Tournament k | 3 |
|
||||
| Crossover probability | 0.5 |
|
||||
| Random seed | 42 |
|
||||
| Symbol | BTC-PERPETUAL (Deribit) |
|
||||
| Timeframe | 1h |
|
||||
| Range storico | 2024-01-01 → 2026-01-01 (2 anni, 17545 candele) |
|
||||
| Fees backtest | 5 basis points |
|
||||
| n_trials_dsr | 50 |
|
||||
| Tier LLM dominante | C (qwen3-235b-a22b-2507 via OpenRouter) |
|
||||
| Cerbero MCP endpoint | http://localhost:9001 (locale) |
|
||||
| Durata wall-clock | 29 minuti |
|
||||
| Costo LLM | $0.069 |
|
||||
|
||||
### 1.2 Stack tecnologico
|
||||
|
||||
Python 3.13, uv 0.10.9. Test framework: pytest + pytest-mock + responses. Persistence: sqlite3 + sqlmodel. Parsing strategia: `json.loads` con dataclass-based AST. Analytics: pandas + numpy + scipy. LLM: openai SDK con base URL OpenRouter (route unica per tutti i tier S/A/B/C/D). HTTP: requests + tenacity. Dashboard: streamlit + plotly + canvas HTML5 custom.
|
||||
|
||||
### 1.3 Architettura del run
|
||||
|
||||
L'orchestrator (`src/multi_swarm/orchestrator/run.py`, 184 righe) coordina la pipeline end-to-end:
|
||||
|
||||
1. **OHLCV loading**: `CerberoOHLCVLoader` chiama `mcp-deribit/tools/get_historical` paginando in chunk da 4500 barre (cap soft Deribit ~5000). Cache parquet su sha1 della query — il run v5 ha riusato cache popolata dai run precedenti, fetch istantaneo.
|
||||
2. **Market summary**: statistiche return (mean, std, skew, kurt) + classificazione regime volatilità.
|
||||
3. **Initial population**: 20 genomi distribuiti uniformemente sui 6 cognitive style (physicist, biologist, historian, meteorologist, ecologist, engineer), temperature random in [0.7, 1.2], lookback random in {100, 150, 200, 300}.
|
||||
4. **Per ogni generazione (10 totali)**:
|
||||
- **Hypothesis**: chiamata LLM con prompt SYSTEM (regole grammar) + USER (market summary). Output JSON estratto via regex fence ```json. Se parse/validation fallisce: retry 1x con error message nel prompt utente.
|
||||
- **Falsification**: AST compilato in `Callable[[df], Series[Side]]`, backtest event-driven con 1-bar exec delay, calcolo Sharpe + Deflated Sharpe (Bailey & López 2014, n_trials=50).
|
||||
- **Adversarial**: 4 check euristici (no_trades, degenerate, overtrading, undertrading).
|
||||
- **Fitness**: `0.5*dsr + 0.25*(tanh(sharpe)+1)` × `1/(1+max_dd)`, range [0, ~1]. Kill (=0) su zero trade o HIGH adversarial finding.
|
||||
- **Next generation**: elitism 2 + tournament 3 + 50% crossover / 50% mutation.
|
||||
5. **Persistence SQLite**: ogni genome, evaluation, cost_record, adversarial_finding, generation summary persistito con indici per query rapide della dashboard.
|
||||
|
||||
### 1.4 Caveat metodologici noti
|
||||
|
||||
- **In-sample**: il backtest in Phase 1 lean spike non usa walk-forward; tutto il range 2024-2026 viene usato sia per la generazione delle ipotesi sia per la loro valutazione. La sopravvivenza out-of-sample è esplicitamente fuori scope di Phase 1 (gate Phase 2 #2).
|
||||
- **Compiler con indicatori built-in**: il compiler JSON-based (`src/multi_swarm/protocol/compiler.py`) calcola RSI, SMA, ATR, MACD, realized_vol localmente con pandas. `CerberoTools` è plumbed ma non chiamato durante l'esecuzione delle strategie — è disponibile per agenti future-tense ma il fitness Phase 1 dipende solo dagli indicatori locali.
|
||||
- **RSI epsilon-floor**: il compiler ha un epsilon sul `roll_down` per evitare RSI=100 esatto su serie monotonicamente crescenti (artefatto matematico irrilevante su dati reali ma documentato).
|
||||
- **Top-1 strategia con DSR marginale**: vedi sez. 3.
|
||||
|
||||
---
|
||||
|
||||
## 2. Loop convergence
|
||||
|
||||
### 2.1 Fitness per generazione
|
||||
|
||||
| Gen | Median | Max | P90 | Entropy |
|
||||
|---|---|---|---|---|
|
||||
| 0 | 0.0001 | 0.0601 | 0.0165 | 0.588 |
|
||||
| 1 | 0.0042 | 0.1893 | 0.0731 | 1.261 |
|
||||
| 2 | 0.0188 | 0.3347 | 0.2039 | 1.333 |
|
||||
| 3 | 0.0069 | 0.3347 | 0.3347 | 1.347 |
|
||||
| 4 | 0.0910 | 0.3347 | 0.3347 | 1.415 |
|
||||
| 5 | 0.0016 | 0.3347 | 0.3347 | 0.611 |
|
||||
| 6 | 0.0040 | 0.3347 | 0.3347 | 0.886 |
|
||||
| 7 | 0.0151 | 0.3347 | 0.3347 | 0.982 |
|
||||
| 8 | 0.0066 | 0.3347 | 0.3347 | 0.746 |
|
||||
| 9 | 0.0061 | 0.3347 | 0.3347 | 0.914 |
|
||||
|
||||
### 2.2 Lettura
|
||||
|
||||
**Convergenza tre-step iniziale**: gen 0→1→2 mostra crescita mediana 4x-50x (0.0001 → 0.0042 → 0.0188) e crescita max 3x-6x (0.06 → 0.19 → 0.33). Gate 1 PASS su questa finestra.
|
||||
|
||||
**Plateau dell'elite da gen 2**: max stabile a 0.3347 per le restanti 7 generazioni — comportamento atteso con `elite_k=2` che preserva il top performer attraverso le generazioni. P90 si allinea al max da gen 3, segno che almeno 2 elite mantengono la top fitness.
|
||||
|
||||
**Median oscillante**: dopo il picco a gen 4 (0.091), la median fluttua fra 0.0016 e 0.0151 nelle generazioni successive. Causa: turnover stocastico della popolazione (mutation + crossover) introduce genomi nuovi, alcuni dei quali parse correctly ma falliscono Adversarial (no_trades) e si attestano a fitness 0, abbassando la median. Non è regressione strutturale del GA.
|
||||
|
||||
**Entropy**: oscilla 0.6-1.4 dopo gen 0, sempre sopra soglia 0.5 → diversità di fitness preservata anche durante plateau dell'elite.
|
||||
|
||||
---
|
||||
|
||||
## 3. Top-5 genomi: ispezione qualitativa
|
||||
|
||||
| Rank | Genome ID | Gen | Style | Fitness | DSR | Sharpe | Max DD | Trades | Temp |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 1 | `696052b8...` | 2 | physicist | 0.3347 | 0.0021 | 0.381 | 0.0215 | 33 | 0.68 |
|
||||
| 2 | `169376a2...` | 1 | engineer | 0.3347 | 0.0021 | 0.381 | 0.0215 | 33 | 0.78 |
|
||||
| 3 | `eb0265ad...` | 3 | ecologist | 0.2453 | 0.0006 | −0.019 | 0.0011 | 1 | 1.14 |
|
||||
| 4 | `38d4c1d9...` | 1 | engineer | 0.1893 | 0.0001 | −0.245 | 0.0028 | 1 | 0.82 |
|
||||
| 5 | `3e355975...` | 1 | physicist | 0.1893 | 0.0001 | −0.245 | 0.0028 | 1 | 0.78 |
|
||||
|
||||
### 3.1 Top-1 strategia (ispezione approfondita)
|
||||
|
||||
**System prompt** (engineer): *"Cerca segnali con rapporto S/N favorevole, filtri causali, robustezza a perturbazioni di calibrazione."*
|
||||
|
||||
**Strategia JSON** (3 regole, evaluation in ordine):
|
||||
|
||||
- **LONG**: `SMA(10) crossover SMA(30)` AND `realized_vol(20) > 0.3%` AND `RSI(14) < 45`.
|
||||
- **SHORT**: `SMA(10) crossunder SMA(30)` AND `realized_vol(20) > 0.3%` AND `RSI(14) > 55`.
|
||||
- **EXIT**: (`RSI(14) > 70` AND `close crossover SMA(50)`) OR `realized_vol(20) < 0.1%`.
|
||||
|
||||
**Lettura economica**: trend-following SMA-cross fast/slow modulato da filtro volatilità (entra solo quando il regime è abbastanza mosso, esce quando è troppo calmo) e filtro RSI come momentum confirmation (long solo se non già ipercomprato; short solo se non già ipervenduto). L'EXIT è sofisticato: esce su overbought confermato da break sopra MA50, OPPURE su collasso di volatilità.
|
||||
|
||||
**Performance**: 33 trade su 17545 candele (1 trade ogni 532 candele = 1 ogni 22 giorni). Sharpe positivo modesto, max drawdown 2.15% (basso). DSR praticamente zero (0.0021) — il segnale non è statisticamente significativo dopo correzione multiple testing, perché 33 trade su 2 anni è sample piccolo.
|
||||
|
||||
**Plausibilità**: pattern economicamente sensato, non casuale. Reminiscente di strategie trend-following classiche (Donchian, turtle-style) con filtri di regime. Lo stile cognitivo "engineer" (S/N favorable, filtri causali) si riflette nella struttura.
|
||||
|
||||
### 3.2 Top-2/3/4/5 brevemente
|
||||
|
||||
- Top-2 è una replica funzionale di Top-1 con metriche identiche. Plausibile elite duplicato o convergenza indipendente sulla stessa strategia (verifica per Phase 2: signal correlation fra duplicati).
|
||||
- Top-3, 4, 5 hanno **1 trade ciascuno** su 2 anni. Sono "lucky shot": una posizione tenuta a lungo che casualmente termina con leggera vincita. Adversarial flagga MEDIUM `undertrading` ma non HIGH, quindi sopravvivono. La fitness function continua dà loro valore non-zero perché `tanh(sharpe)` è leggermente sopra 0.5 e penalty drawdown è quasi 1.0 (max_dd <0.5%).
|
||||
|
||||
### 3.3 Ratio top-1 / median
|
||||
|
||||
Median fitness su 98 evals: 0.0003.
|
||||
Top-1 fitness: 0.3347.
|
||||
**Ratio**: 1116x — Gate 3 soddisfatto con margine drammatico (soglia 1.5x).
|
||||
|
||||
---
|
||||
|
||||
## 4. Parser failure modes
|
||||
|
||||
### 4.1 Statistiche aggregate v5
|
||||
|
||||
- Evaluations totali: 98
|
||||
- Parse success: **98 (100.0%)**
|
||||
- Parse failure: **0 (0.0%)**
|
||||
|
||||
### 4.2 Confronto con iterazioni precedenti
|
||||
|
||||
| Run | Grammar | Parse success | Note |
|
||||
|---|---|---|---|
|
||||
| v1 | S-expression | 33% | LLM nesta indicators non supportati |
|
||||
| v4 | S-expression (con arity check post-fix) | 36% | 89 di 98 errori = `indicator nested` |
|
||||
| v5 | **JSON Schema** | **100%** | Refactor commit `44eb643` |
|
||||
|
||||
Il salto da 36% a 100% deriva interamente dal cambio di grammar. JSON è natively supported dal training dei modelli LLM moderni; S-expression è esotica e induce hallucination di sintassi creative.
|
||||
|
||||
### 4.3 Retry-with-feedback (commit `d4fcb42`)
|
||||
|
||||
Il sistema accetta 1 retry con error feedback. Nel run v5 il retry **non è mai stato usato** (zero retry per parse, dato il 100% di success). Il retry rimane comunque architetturalmente presente per Phase 2 / casi edge.
|
||||
|
||||
---
|
||||
|
||||
## 5. Costi reali vs preventivo
|
||||
|
||||
### 5.1 Breakdown costi LLM v5
|
||||
|
||||
| Tier | Calls | Input tokens | Output tokens | Cost USD |
|
||||
|---|---|---|---|---|
|
||||
| C (qwen3-235b) | 113 | 112369 | 60060 | $0.069 |
|
||||
|
||||
### 5.2 Costo cumulativo Phase 1 (5 run, inclusi bug-fix iterations)
|
||||
|
||||
| Run | Cost | Note |
|
||||
|---|---|---|
|
||||
| v1 (aborted) | $0.034 | 67% parse_error, max_dd bug |
|
||||
| v2 (aborted) | $0.018 | macd 3 args, OHLCV cap discovery |
|
||||
| v3 (aborted) | $0.015 | crash su indicator arity |
|
||||
| v4 (completed FAIL) | $0.057 | 36% parse, fitness tutti 0 |
|
||||
| v5 (completed PASS) | $0.069 | tutti gate passati |
|
||||
| **Totale Phase 1** | **$0.193** | — |
|
||||
|
||||
### 5.3 Confronto con preventivo
|
||||
|
||||
- Preventivo originale (basato su pricing Anthropic Sonnet): $500-700.
|
||||
- Spesa reale Phase 1 totale: **$0.19**.
|
||||
- Deviazione: −99.97%.
|
||||
|
||||
La differenza non è dovuta a underuse — il run v5 ha fatto 113 chiamate LLM = full saturazione del budget previsto di calls. È un cambio di ordine di grandezza nei prezzi dovuto al pricing aggressivo di OpenRouter per modelli open-weights (qwen3-235b è 7.5x più economico di Sonnet su input, 37x su output). Il preventivo originale era calibrato su Sonnet 4.6.
|
||||
|
||||
### 5.4 Implicazioni per Phase 2
|
||||
|
||||
Il margine economico permette di pianificare Phase 2 con maggiore aggressività senza superare il cap ($700-1100):
|
||||
- K=40 (×2), gen=15 (×1.5), tier mix 30% B / 70% C, ablation runs multiple.
|
||||
- Estrapolazione lineare conservativa: $0.07 × 2 × 1.5 × ~3 (tier B factor) × 5 (ablation) = ~$3 totali. Possibile spingere a $30-50 senza preoccupazioni se serve per ablation più ricche.
|
||||
|
||||
**Rischio cost-trap inverso**: tentazione di sovra-dimensionare Phase 2 perché "tanto costa nulla". Mantenere disciplina budget invariata — investire i $700 cap in PIÙ ablation, non in run più grandi.
|
||||
|
||||
---
|
||||
|
||||
## 6. Diversity metrics
|
||||
|
||||
### 6.1 Entropy fitness per generazione
|
||||
|
||||
Vedi tabella sez. 2.1 colonna entropy. Mai sotto 0.5, picco a gen 4 (1.415).
|
||||
|
||||
### 6.2 Cognitive style sopravvissuti gen 9
|
||||
|
||||
| Stile | Count gen 9 | Avg fitness | Note |
|
||||
|---|---|---|---|
|
||||
| engineer | 3 | 0.0 | Dominante numericamente ma fitness 0 (genomi recent, non valutati su elite) |
|
||||
| physicist | 1 | 0.0598 | Solo presente nel top-K |
|
||||
| historian | 1 | 0.0002 | — |
|
||||
| biologist | 0 | — | Estinto |
|
||||
| meteorologist | 0 | — | Estinto |
|
||||
| ecologist | 0 | — | Estinto |
|
||||
|
||||
**Lettura**: pressione selettiva ha eliminato 3 di 6 stili cognitivi alla generazione finale. Engineer è dominante numericamente, physicist domina nel valore (l'unico con fitness >0 della popolazione "live" gen 9). Phase 2 deve introdurre speciation esplicita per evitare questo collasso (minimum 2-3 specie protette).
|
||||
|
||||
### 6.3 Trade distribution sui 98 evals
|
||||
|
||||
| Categoria | n | % |
|
||||
|---|---|---|
|
||||
| Zero trade (HIGH no_trades, kill) | 42 | 42.9% |
|
||||
| Undertrading (1-4 trade, MEDIUM) | 5 | 5.1% |
|
||||
| Normal (5-100 trade) | 9 | 9.2% |
|
||||
| Overtrading (>100 trade, NON flaggato) | 42 | 42.9% |
|
||||
|
||||
**Issue identificato**: il 42.9% di overtrading non viene catturato dall'Adversarial perché la soglia attuale è `n_trades > n_bars/5 = 3509` — troppo alta per essere triggerata su 1000-2000 trade. Phase 2 dovrebbe abbassare a `n_bars/20 = 877` o usare metrica relativa al regime.
|
||||
|
||||
### 6.4 Adversarial findings totali
|
||||
|
||||
| Finding | Severity | Count |
|
||||
|---|---|---|
|
||||
| no_trades | HIGH | 42 |
|
||||
| undertrading | MEDIUM | 5 |
|
||||
|
||||
Niente `degenerate` né `overtrading` flaggato. Il primo è raro (richiede strategia sempre-LONG o sempre-SHORT puro), il secondo soffre della soglia troppo alta.
|
||||
|
||||
---
|
||||
|
||||
## 7. Threats to validity
|
||||
|
||||
Lista esplicita dei limiti metodologici da non sovra-interpretare:
|
||||
|
||||
1. **In-sample fitting**: tutto il backtest è in-sample. Il top-1 ha Sharpe 0.38 ottenuto guardando i dati su cui è stato selezionato. Phase 2 (walk-forward + hold-out Q1-Q2 2026 intoccabile) misura overfitting reale.
|
||||
2. **Tier C unico**: nessun confronto contro tier B/S. Possibile underperformance del LLM economico vs Sonnet/Opus. Phase 2 introduce ablation multi-tier.
|
||||
3. **Adversarial hand-crafted**: 4 check euristici (no_trades, degenerate, overtrading, undertrading). Phase 2 introduce 5 prompt LLM-driven dedicati (data snooping, lookahead, regime fragility, crowding, transaction cost erosion).
|
||||
4. **Fitness function v1**: lineare in DSR + tanh(Sharpe) normalizzato + drawdown moltiplicativa. Non multi-livello (per-team, anti-collusion). Phase 2 introduce.
|
||||
5. **No speciation, no novelty bonus**: cognitive style scendono da 6 a 3 a gen 9. Phase 2 deve mitigare.
|
||||
6. **DSR del top-1 = 0.0021**: il "successo" del Gate 3 è guidato da Sharpe (positivo modesto), non da significatività statistica vera. Senza walk-forward + multiple testing rigoroso, non si può affermare alpha edge.
|
||||
7. **Top-3/4/5 sono "lucky shot" 1-trade**: la fitness function continua li promuove perché drawdown bassissimo + sharpe leggermente negativo, ma sono artefatti. Phase 2 promuove undertrading a HIGH se `n_trades < 10`.
|
||||
8. **Cerbero/Deribit data quality**: nessuna detection di gap, outlier, exchange downtime. Da affrontare prima di forward-test (Phase 3).
|
||||
9. **Cost predictability inverso**: Phase 2 deve resistere alla tentazione di sovra-dimensionare perché Phase 1 è costata $0.19.
|
||||
|
||||
---
|
||||
|
||||
## 8. Conclusioni e implicazioni per Phase 2
|
||||
|
||||
**Hard gate sintesi**: ✅ 5 su 5 passati.
|
||||
|
||||
**Decisione finale**: **GO Phase 2** (formalizzata nel decision memo).
|
||||
|
||||
**Apprendimenti chiave per Phase 2**:
|
||||
|
||||
1. **JSON >> S-expression** per grammar LLM-generated. Phase 2 non rivisita.
|
||||
2. **Fitness continua è essenziale** per dare gradient al GA, ma può promuovere strategie degeneri (1-trade) che vanno killate diversamente.
|
||||
3. **OpenRouter qwen3-235b** è sorprendentemente capace per generare strategie strutturate, dato un prompt schema-rigoroso. Tier B (Sonnet) potrebbe non essere necessario al 30% come pianificato; ablation Phase 2 misurerà il vero contributo.
|
||||
4. **Cerbero MCP come single source of truth** funziona: paginazione, cache parquet, audit log integrati senza fragility.
|
||||
5. **Bug-fix discovery via run reale** è efficiente: 4 cicli, ognuno ha esposto un problema specifico (max_dd math, macd arity, validator arity, fitness clamp, grammar choice). Phase 2 può aspettarsi pattern simile per nuove componenti (speciation edge cases, OOS overfitting, multi-tier dispatch).
|
||||
|
||||
**Riusabilità del codebase Phase 1**: il design modulare (data, backtest, metrics, cerbero, protocol, genome, llm, agents, ga, persistence, orchestrator, dashboard) è riusabile direttamente. Estensioni Phase 2:
|
||||
- `ga/speciation.py` (nuovo) — clustering cosine similarity prompt, quota tournament per specie.
|
||||
- `ga/fitness.py` — versione v2 con novelty bonus + per-team aggregation.
|
||||
- `orchestrator/run.py` — integrazione walk-forward.
|
||||
- `agents/adversarial_llm.py` (nuovo) — 5 prompt LLM-driven.
|
||||
- `baseline/random_forest.py` (nuovo) — RF baseline per benchmark.
|
||||
|
||||
**Costo stimato Phase 2**: $3-15 (estrapolazione molto conservativa). Cap rimane $700-1100 invariato per disciplina.
|
||||
|
||||
**Tempo stimato Phase 2**: 4-6 settimane di lavoro calendar, includendo i 3 aggiustamenti del decision memo (Adversarial soglie, speciation, walk-forward).
|
||||
|
||||
---
|
||||
|
||||
*Documento finalizzato 10 maggio 2026. Versione 1.0.*
|
||||
@@ -1,286 +0,0 @@
|
||||
# Multi-Swarm Coevolutivo — Stato del progetto e roadmap
|
||||
|
||||
*Data del documento: 14 maggio 2026 — branch `main` allineato a commit `45f273f`.*
|
||||
|
||||
Questo documento riepiloga l'intero percorso del proof-of-concept Multi-Swarm Coevolutive dalla Phase 1 (lean spike) fino allo stato corrente di entrata in Phase 3 (paper-trading forward-test). È inteso come punto di sincronizzazione per riprendere il lavoro: cosa è stato deciso, cosa ha funzionato, cosa no, e quali sono le prossime mosse plausibili.
|
||||
|
||||
---
|
||||
|
||||
## 1. Quadro sintetico
|
||||
|
||||
| Fase | Periodo | Stato | Esito |
|
||||
|------|---------|-------|-------|
|
||||
| **Phase 1** — lean spike | 9-10 maggio 2026 | ✅ chiusa | GO Phase 2 (5/5 hard gate) |
|
||||
| **Phase 1.5** — adversarial hardening | 11 maggio 2026 | ✅ chiusa | NO-GO sulla combo nemotron, hardening conservato |
|
||||
| **Phase 2** — feature temporali + qwen3-235b | 11 maggio 2026 | ✅ chiusa | NO-GO sul modello (rollback a qwen-2.5-72b) |
|
||||
| **Phase 2.5** — LLM prompt mutator | 11-12 maggio 2026 | ✅ chiusa | Operator integrato, sweet spot weight 0.20-0.30 |
|
||||
| **Phase 2.6** — Walk-Forward Validation | 12-13 maggio 2026 | ✅ chiusa | WFA 70/30 introdotta, min-trades parametrico |
|
||||
| **Phase 2.7** — portabilità cross-asset (BTC/ETH/SOL) | 13 maggio 2026 | ✅ chiusa | BTC strong, ETH adequate, SOL failure |
|
||||
| **Phase 3** — paper-trading forward-test | 13-14 maggio 2026 | 🟢 in corso | Runner BTC+ETH operativo, smoke OK |
|
||||
|
||||
Dal punto di vista del DB locale: 30 run GA completate, costo cumulato LLM **≈ $3.74**, due paper-trading run avviati (`phase3-smoke-001`, `phase3-papertrade-001`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Phase 1 — lean spike (chiusa 10 maggio)
|
||||
|
||||
### Obiettivo
|
||||
Validare end-to-end l'idea co-evolutiva: GA → popolazione di prompt LLM → strategie JSON → backtest deterministico → fitness → selezione. Cinque hard gate vincolanti.
|
||||
|
||||
### Risultato
|
||||
Run di riferimento `phase1-real-005` su BTC-PERPETUAL Deribit 1h, 2024-01-01 → 2026-01-01, K=20, 10 generazioni, **costo $0.069 in 29 minuti**.
|
||||
|
||||
| Hard gate | Soglia | Misurato | Esito |
|
||||
|-----------|--------|----------|-------|
|
||||
| Loop convergence | median sale | 0.0001 → 0.0188 in 3 gen | ✓ |
|
||||
| Parse success | ≥ 95% | 100% (98/98) post refactor JSON | ✓ |
|
||||
| Top-5 vs median | ≥ 10× | 1116× | ✓ |
|
||||
| Entropy fitness gen 9 | ≥ 0.5 | 0.914 | ✓ |
|
||||
| Costo totale | ≤ $700 | $0.069 | ✓ |
|
||||
|
||||
Iterazione: 5 run prima del PASS, ognuna ha scoperto un bug strutturale (max_dd su equity assoluta, cap Cerbero 5000 candele, validator arity, switch grammar S-expr→JSON, fitness clip-to-0 troppo dura).
|
||||
|
||||
### Caveat critico
|
||||
Il top-1 ha reso **+2.66% in 2 anni vs B&H BTC +106%**, essendo *flat* nel 99,8% del tempo. Conferma che la fitness v1 premiava "non-strategie" sicure invece di alpha vero. Da qui la Phase 1.5.
|
||||
|
||||
### Documenti chiave
|
||||
- `docs/decisions/2026-05-10-gate-phase1.md` — decision memo.
|
||||
- `docs/reports/2026-05-10-phase1-technical-report.md` — report tecnico.
|
||||
|
||||
---
|
||||
|
||||
## 3. Phase 1.5 — adversarial hardening (chiusa 11 maggio)
|
||||
|
||||
Quattro nuovi check `HIGH` aggiunti all'agente Adversarial per killare strategie degeneri:
|
||||
|
||||
1. `overtrading` ricalibrato `n_bars/20` (era `n_bars/5`).
|
||||
2. `undertrading` promosso a HIGH se `n_trades < 10`.
|
||||
3. `flat_too_long` (nuovo HIGH) — segnale flat > 95% bar.
|
||||
4. `fees_eat_alpha` (nuovo HIGH) — `fees / |gross_pnl| > 0.5` con gross positivo.
|
||||
5. `time_in_market_too_high` (nuovo HIGH) — segnale LONG||SHORT > 80% bar (kill leveraged-B&H camuffato).
|
||||
|
||||
**Run di test `phase1.5-nemotron-001`** (tier C nemotron, 2h26', $0.12) → **NO-GO**: max fitness 0.0215 stagnante, median 0 su 9 gen, top-5 con DSR=0 e Sharpe ≈ −1.1. I check Phase 1.5 funzionavano (98 findings emessi); il problema era il modello: prompt calibrato su qwen, nemotron produceva materiale qualitativamente più povero.
|
||||
|
||||
Bugfix collaterale (`9d0deb3`): `EmptyCompletionError` reso retryable + gestione `resp.usage=None` per provider `:free`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 2 — feature temporali + tier C qwen3-235b (chiusa 11 maggio)
|
||||
|
||||
Due lavori in parallelo, esiti opposti:
|
||||
|
||||
**4.1 Feature temporali in protocol layer** — `KNOWN_FEATURES` esteso con `hour`, `dow`, `is_weekend`, `minute_of_hour`. Compiler dispatcher temporale (`9d1f97c`), validator parametrizzato, integration test gating temporale+SMA. Few-shot example nel prompt Hypothesis. **Successo strutturale**: tutte le top strategie successive sfruttano questo asset.
|
||||
|
||||
**4.2 Upgrade tier C a `qwen/qwen-2.5-72b-instruct` → `qwen3-235b-a22b`** — run `phase2-qwen3-001`: max fitness 0.0238 stuck per 8 gen, entropy 0.199 stuck per 7 gen, 4 dei 5 top genomi con fitness/Sharpe/DD identici. Il **run controllo** identico ma con qwen-2.5-72b: 0.0311 (+30%), median raggiunge top in 4 gen, entropy 0.85, ½ tempo e costo. **Rollback a qwen-2.5-72b** (`8ec45c5`).
|
||||
|
||||
Lezione consolidata: il prompt è calibrato sulla famiglia qwen-2.5; un modello "più nuovo / più grande" non è automaticamente meglio se il prompt non viene ricalibrato in parallelo.
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 2.5 — operator `mutate_prompt_llm` (chiusa 12 maggio)
|
||||
|
||||
Quinto operatore di mutazione che riscrive il `system_prompt` via LLM tier B (`deepseek-v4-flash`) anziché perturbare scalari. Sei istruzioni atomiche: `tighten_threshold`, `swap_comparator`, `add_condition`, `remove_condition`, `change_timeframe`, `add_temporal_gate`. Validation gate (lunghezza ≥ 50, keyword tecnica, diff Levenshtein > 5%) + fallback `random_mutate`. Dispatcher pesato `weighted_random_mutate` (CLI `--prompt-mutation-weight`, default 0.0).
|
||||
|
||||
### Sweet spot empirico (seed 42, pop 20, 10 gen)
|
||||
|
||||
| weight | max fit | median fin | Sharpe top | trades | verdetto |
|
||||
|--------|---------|-----------|-----------|--------|----------|
|
||||
| 0.00 | 0.0311 | 0.0000 | −1.08 | 274 | baseline |
|
||||
| **0.30** | **0.1012** ⭐ | **0.0745** | **−0.25** | 62 | sweet spot (ma seed-lucky) |
|
||||
| 0.50 | 0.0311 | 0.0000 | −1.08 | 274 | regressione |
|
||||
|
||||
### Validazione robustezza
|
||||
Confronti seed multipli (7, 99, 123) hanno mostrato che il **+225%** del run 004 era **outlier seed-specific**. Beneficio medio reale del prompt-mutator: **+10–23%** sopra baseline. La leva più affidabile e seed-indipendente è risultata `fees_eat_alpha_threshold 0.7` (anziché 0.5): +23% stabile, Sharpe top −0.70 vs −1.08.
|
||||
|
||||
### Combo vincente (pop=30 + weight=0.30 + fees=0.7)
|
||||
Run `pop30-combo-001`: max fitness 0.0459 (+48% vs control), **median finale = max** (convergenza ≥50% pop), Sharpe top −0.63, 226 trades. Mutator overhead ≈ 5,4% del costo totale.
|
||||
|
||||
### Cost attribution (Task 6)
|
||||
`cost_records.call_kind` (`hypothesis` / `mutation`) attivo da `ba4eb09`. Permette breakdown costo per operatore: il prompt-mutator costa 3-9% del totale, trascurabile.
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 2.6 — Walk-Forward Validation (chiusa 13 maggio)
|
||||
|
||||
Aggiunte tre leve metodologiche:
|
||||
|
||||
- **WFA 70/30**: split temporale `train/OOS` con OOS intoccato durante GA, valutato solo a fine run.
|
||||
- **`--min-trades-threshold`** parametrico: filtra survivors con n_trades insufficiente prima del ranking.
|
||||
- **Fitness v2 soft-kill** (`cf42dd8`): solo `no_trades` + `degenerate` + `undertrading` azzerano hard. Altri HIGH applicano penalty moltiplicativa `1/(1+0.4·n)` (1 HIGH = 0,71×, 2 = 0,56×, 3 = 0,45×). CLI `--fitness-v2` + `--fitness-soft-penalty`.
|
||||
- **Pattern guidance nel system prompt** (`67ae6ff`): forma curve attese + criterio ripetibilità.
|
||||
- **Fitness multi-obiettivo** (`1a1dfb7`): `combined = α·IS + (1−α)·OOS` opt-in.
|
||||
|
||||
Effetto cumulativo: la pipeline produce strategie con migliore generalizzazione cross-split senza dover degradare le adversarial hard.
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase 2.7 — backtest 7 anni e portabilità cross-asset (chiusa 13 maggio)
|
||||
|
||||
### 7.1 Validazione 7,33 anni su BTC
|
||||
|
||||
Backtest dei top genome scoperti sulle varie sotto-fasi sui **64.297 bar 1h** completi (2018-09-01 → 2026-01-01), fees 5 bp:
|
||||
|
||||
| Genome | Origine | Total P/L 7y | CAGR | Sharpe ann | MaxDD | Verdetto |
|
||||
|--------|---------|--------------|------|-----------|-------|----------|
|
||||
| `5226503a` | run004 outlier 2y bull | **−310,69%** | wiped out | −0,155 | 280,9% | crash totale OOS |
|
||||
| `e52604ba` | flat-ablation top 2y | −37,17% | −6,14% | −0,063 | 182,0% | SMA non generalizza |
|
||||
| `ec06a3d4` | fitness-v2-combo top 2y | +142,51% | +12,85% | +0,229 | 64,9% | hour-gated regge |
|
||||
| `4e1be9fa` | 7y-v2-WFA top IS | +67,60% | +7,30% | +0,240 | 79,1% | top IS ingannevole |
|
||||
| `63411199` | 7y-v2-WFA top OOS | **+660,11%** | **+31,88%** | +0,238 | 77,1% | leveraged-B&H camuffato |
|
||||
| **`fb63e851`** ⭐ | 7y multi-seed99 top OOS | +130,37% | +12,06% | **+0,264** | **54,8%** | **true alpha** |
|
||||
|
||||
Conclusioni:
|
||||
- Il top by `fit_IS` è sistematicamente ingannevole su orizzonti lunghi.
|
||||
- Pattern SMA-puri collassano cross-regime.
|
||||
- Pattern *hour-gated* (filtri intraday) reggono cross-regime.
|
||||
- `fb63e851` è il candidato più robusto: 4 AND × 2 rule × filtro intraday → attiva l'1-2% del tempo, Sharpe cross-regime più alto.
|
||||
|
||||
### 7.2 Portabilità BTC → ETH → SOL
|
||||
|
||||
Tre run **identici** (`population=30`, `n_gen=10`, `prompt_mutation_weight=0.30`, fitness v2, WFA 70/30, `fees_eat_alpha_threshold=0.7`, undertrading 20) su Deribit perpetuals.
|
||||
|
||||
| Asset | Storia | Top OOS Sharpe | Verdetto |
|
||||
|-------|--------|----------------|----------|
|
||||
| **BTC** | 7,33 y | `fb63e851` +0,50 OOS (+20,16%) | **STRONG** |
|
||||
| **ETH** | 6,75 y | `facd6af85d5d` +0,19 OOS (+16,14%) | **ADEQUATE** |
|
||||
| **SOL** | 3,00 y | **0 survivors / 247 evals** | **FAILURE** |
|
||||
|
||||
Pattern scoperti **divergenti**: BTC = mean reversion intraday contrarian; ETH = trend-following long-bias + vol regime. **Non esiste "una strategia universale"**: la metodologia (GA + WFA + adversarial v2) è portabile, il pattern no. SOL ha fallito per finestra dati troppo corta (3y) e regime bull-only post-FTX.
|
||||
|
||||
---
|
||||
|
||||
## 8. Phase 3 — paper-trading forward-test (in corso)
|
||||
|
||||
### Componenti implementati (`45f273f`)
|
||||
|
||||
Modulo `src/multi_swarm/paper_trading/`:
|
||||
- `portfolio.py` — multi-asset portfolio con sleeve uguali per asset, fees in bp.
|
||||
- `executor.py` — `PaperExecutor` carica una strategia JSON, compila, valuta l'ultimo bar.
|
||||
- `persistence.py` — `PaperRepository` su SQLite (tabelle `paper_trading_runs`, `paper_trading_ticks`, `paper_trading_equity`, `paper_trading_trades`, `paper_trading_positions`).
|
||||
|
||||
Runner `scripts/run_paper_trading.py`:
|
||||
- Loop poll OHLCV Cerbero ogni `--poll-seconds` (default 300).
|
||||
- Riconosce *nuovo bar chiuso* confrontando ultimo timestamp; tick consecutivi su stesso bar = hold.
|
||||
- Snapshot equity ogni tick.
|
||||
- Supporta `--max-ticks N` per smoke test (0 = infinito).
|
||||
|
||||
Strategie freezate per il forward-test:
|
||||
- `strategies/btc_fb63e851.json` — RSI estremi + ATR vs SMA + filtro orario 9-17.
|
||||
- `strategies/eth_facd6af85d5d.json` — ATR + realized_vol + golden/death cross.
|
||||
|
||||
### Stato corrente
|
||||
- Schema DB esteso e validato.
|
||||
- Run smoke completato (`phase3-smoke-001`).
|
||||
- Run live in corso (`phase3-papertrade-001`).
|
||||
|
||||
---
|
||||
|
||||
## 9. Architettura cumulata
|
||||
|
||||
```
|
||||
src/multi_swarm/
|
||||
├── config.py
|
||||
├── data/{cerbero_ohlcv,splits}.py ← splits.py per WFA
|
||||
├── backtest/{orders,engine}.py
|
||||
├── metrics/{basic,dsr,diversity}.py ← diversity per Phase 2.5
|
||||
├── cerbero/{client,tools}.py
|
||||
├── protocol/{grammar,parser,validator,compiler}.py
|
||||
│ └── KNOWN_FEATURES include hour/dow/is_weekend/minute_of_hour
|
||||
├── genome/
|
||||
│ ├── hypothesis.py
|
||||
│ ├── mutation.py ← 4 operatori scalari
|
||||
│ ├── mutation_prompt_llm.py ← Phase 2.5: 5° operatore LLM
|
||||
│ └── crossover.py
|
||||
├── llm/{client,cost_tracker}.py ← cost_kind tracking
|
||||
├── agents/{hypothesis,falsification,adversarial,market_summary}.py
|
||||
│ └── adversarial: 5 check HIGH parametrici (CLI knobs)
|
||||
├── ga/
|
||||
│ ├── selection.py
|
||||
│ ├── fitness.py ← v1 + v2 soft-kill + combined IS/OOS
|
||||
│ ├── loop.py
|
||||
│ ├── summary.py
|
||||
│ └── initial.py
|
||||
├── persistence/{schema,repository}.py ← +tabelle paper_trading_*
|
||||
├── paper_trading/ ← NEW Phase 3
|
||||
│ ├── portfolio.py
|
||||
│ ├── executor.py
|
||||
│ └── persistence.py
|
||||
├── orchestrator/run.py
|
||||
└── dashboard/
|
||||
├── nicegui_app.py ← unica GUI, porta parametrica via SWARM_DASHBOARD_PORT
|
||||
└── data.py
|
||||
```
|
||||
|
||||
CLI knobs accumulati per ablation:
|
||||
- `--prompt-mutation-weight FLOAT` (Phase 2.5)
|
||||
- `--fees-eat-alpha-threshold FLOAT` (default 0.5, suggerito 0.7)
|
||||
- `--flat-too-long-threshold FLOAT` (default 0.95)
|
||||
- `--undertrading-threshold INT` (default 20)
|
||||
- `--fitness-v2` + `--fitness-soft-penalty FLOAT`
|
||||
- `--fitness-combined-alpha FLOAT` (multi-obiettivo IS/OOS)
|
||||
- `--min-trades-threshold INT` (WFA OOS filter)
|
||||
|
||||
---
|
||||
|
||||
## 10. Cosa resta da fare
|
||||
|
||||
### 10.1 Phase 3 — completamento paper-trading
|
||||
- [ ] **Definire criterio di STOP/GO Phase 3**: durata minima forward-test (es. 4-8 settimane), soglie sopravvivenza (Sharpe live > 50% del Sharpe OOS atteso, DD live < 1,5× DD OOS).
|
||||
- [ ] **Pagina dashboard paper-trading**: estendere NiceGUI con tab live equity + open positions + tick log per `paper_trading_runs`. Oggi i dati esistono in DB ma non hanno UI dedicata.
|
||||
- [ ] **Monitoring & alerting**: notifica se il runner si ferma (Cerbero down, processo killato). Considerare systemd unit o supervisor.
|
||||
- [ ] **Robustezza fetch live**: oggi `loader._fetch(req)` bypassa la cache; aggiungere retry esplicito (oltre a quello tenacity già presente nel client) e log strutturato dei fallimenti per asset.
|
||||
- [ ] **Confronto live vs OOS atteso**: script che a fine settimana confronta P/L, Sharpe rolling, hit rate vs i numeri del backtest 7y per individuare *regime mismatch* precoce.
|
||||
|
||||
### 10.2 Estensioni metodologiche
|
||||
- [ ] **Multi-seed ensembling**: invece di scegliere un singolo top genome, valutare ensemble (mediana o weighted) dei top-K trovati con seed diversi sullo stesso asset. La varianza seed è il rischio numero uno (vedi sezione 5).
|
||||
- [ ] **Asset universe expansion**: testare la metodologia su asset non-crypto (oro, forex EURUSD) per smentire l'ipotesi che funzioni solo perché BTC/ETH hanno alta volatilità. `yfinance` è già in dipendenze (`9d1ef8a`).
|
||||
- [ ] **Fitness regime-aware**: oggi fitness è single-objective sull'intero train. Considerare fitness condizionata al regime (bull/bear/range) per favorire strategie con performance bilanciata cross-regime invece di top assoluto.
|
||||
- [ ] **Phase 2.7 retry su SOL** con configurazione mirata: train più corto, undertrading_threshold ridotto, prompt few-shot di strategie short-vol-only. Verificare se è davvero il dato a fallire o se è la calibrazione.
|
||||
|
||||
### 10.3 Hardening tecnico
|
||||
- [ ] **Cleanup zombie runs**: `phase2-6-flat-wfa-001` è ancora `failed` nel DB. Verificare che il flush di stato sia idempotente per tutti i path di crash.
|
||||
- [x] **Port completo dashboard a NiceGUI** *(chiuso 14 maggio 2026, commit `03f723f`)*: Streamlit deprecata e rimossa insieme ai file legacy (`streamlit_app.py`, `aquarium.py`, `pages/0[1-4]_*.py`); dep `streamlit>=1.40` cancellata da `pyproject.toml` con 10 transitive (pydeck, watchdog, jsonschema, pillow, …). NiceGUI espone 3 pagine (`/`, `/convergence`, `/genomes`) su porta parametrica `SWARM_DASHBOARD_PORT` (default 8080). **Aquarium non riportata per scelta** (decisione utente: non più ritenuta utile). Deploy in produzione via Docker + Traefik su `https://swarm.tielogic.xyz` (compose `docker-compose.yml`, commit `8e5efde`).
|
||||
- [ ] **Pruning DB**: dopo 30+ run la SQLite cresce. Aggiungere uno script di archiviazione/compressione delle run completate più vecchie di N giorni.
|
||||
- [ ] **CI/test coverage**: i 180+ test girano localmente; non c'è ancora CI esterna (Gitea Actions o equivalente).
|
||||
|
||||
### 10.4 Documentazione e governance
|
||||
- [ ] **Decision memo Phase 2.5 + Phase 2.6 + Phase 2.7** formalizzati come `docs/decisions/2026-05-1{2,3}-*.md` (esistono solo memory + commit message; manca il pendant pubblico dei due memo già esistenti per Phase 1 e Phase 1.5).
|
||||
- [ ] **Phase 3 charter**: documento che fissa a priori cosa significherà "successo" o "fallimento" del forward-test, per evitare *moving goalposts* a posteriori.
|
||||
- [ ] **Threats to validity update**: il memo Phase 1 ne elencava 6; integrarli con le scoperte successive (varianza seed, portabilità asset-specifica, divergenza pattern BTC/ETH).
|
||||
|
||||
---
|
||||
|
||||
## 11. Caveat e rischi aperti
|
||||
|
||||
1. **Varianza seed**: con seed diversi (7, 99, 123) sullo stesso identico setup il max fitness varia di un fattore 3-4×. Qualunque metrica single-seed è statisticamente debole; finché Phase 3 non raccoglie N≥5 forward-test indipendenti, il vantaggio del prompt-mutator resta nel rumore.
|
||||
2. **Sharpe OOS positivi ma bassi**: BTC `+0,50` ed ETH `+0,19` sono migliori del coin-flip ma lontani dai target retail "investment-grade" (≥ 1,0). La metodologia è validata, l'alpha catturata è modesta.
|
||||
3. **`time_in_market_too_high` come red-flag chiave**: `63411199` ha CAGR +31,88% ma esposizione 90% del tempo — è leveraged-B&H camuffato, non alpha. Phase 3 deve preferire `fb63e851` (selettività 1-2%) anche se ha return assoluto minore.
|
||||
4. **Dipendenza dal modello qwen-2.5-72b**: rollback Phase 2 ha dimostrato che il prompt è calibrato su questa specifica famiglia. Se il modello venisse deprecato da OpenRouter, sarebbe necessario un giro di ricalibrazione prompt → rischio di operatività.
|
||||
5. **Cerbero MCP come single point of failure**: tutti i fetch OHLCV passano da lì. Da considerare un fallback (ccxt o yfinance) almeno per il paper-trading.
|
||||
|
||||
---
|
||||
|
||||
## 12. Costi cumulati
|
||||
|
||||
- **Phase 1 (5 run iterazione)**: $0,19.
|
||||
- **Phase 1.5 nemotron**: $0,12.
|
||||
- **Phase 2 + 2.5 + 2.6 + 2.7**: ≈ $3,24 cumulati su 25+ run.
|
||||
- **Totale LLM progetto a oggi**: ≈ **$3,74** (DB locale).
|
||||
- **Phase 3 paper-trading**: $0 incrementali per LLM (le strategie sono fisse), solo costi Cerbero (incluso nel servizio esistente).
|
||||
|
||||
Resta amplissimo margine rispetto al cap originale Phase 1 di $700.
|
||||
|
||||
---
|
||||
|
||||
## 13. Riferimenti
|
||||
|
||||
- README.md — overview e setup.
|
||||
- `docs/decisions/2026-05-10-gate-phase1.md`, `docs/decisions/2026-05-11-phase1-5-nemotron-run.md`.
|
||||
- `docs/reports/2026-05-10-phase1-technical-report.md`.
|
||||
- `docs/superpowers/specs/2026-05-09-decisione-strategica-design.md`, `docs/superpowers/specs/2026-05-11-temporal-features-design.md`.
|
||||
- `docs/superpowers/plans/2026-05-09-phase1-lean-spike.md`, `docs/superpowers/plans/2026-05-11-mutate-prompt-llm-phase-2-5.md`, `docs/superpowers/plans/2026-05-11-temporal-features.md`.
|
||||
- DB locale `runs.db` per dettaglio run-by-run.
|
||||
|
||||
---
|
||||
|
||||
*Prossimo checkpoint suggerito: rivedere questo documento al termine del primo ciclo completo di Phase 3 (≥ 2 settimane di forward-test continuo) per consolidare i risultati live e decidere GO/NO-GO verso un eventuale Phase 4 (capitale reale ridotto o estensione del universe).*
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,318 +0,0 @@
|
||||
# `mutate_prompt_llm` — Phase 2.5 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
||||
|
||||
**Status:** **TUTTI I 6 TASK COMPLETATI** (task 1-5 il 2026-05-11, task 6 il 2026-05-12). Mergiati su main. Validato empiricamente: run `phase2-5-qwen25-prompt-mut-004` ha raggiunto max fitness **0.1012** (+225% vs baseline `phase2-qwen25-control-001` 0.0311). Sweet spot weight=0.30 (curva U: weight=0.50 → regressione plateau 0.0311; weight=0.00 → baseline piatto).
|
||||
|
||||
**Trigger Phase 2.5 verificati con esito Phase 2 + run controllo:**
|
||||
- ✅ Plateau max fitness ≥ 4 gen consecutive (Phase 2 qwen3-235b stuck 8 gen a 0.0238; run controllo qwen-2.5-72b stuck 9 gen a 0.0311).
|
||||
- ✅ Diversità prompt collapsed: top genomi del run controllo hanno fitness/Sharpe/DD identici (mutazioni scalari non producono varianti significative).
|
||||
- ✗ Top quasi-fit ≥ 0.10 non raggiunto, ma 2/3 trigger sufficienti.
|
||||
|
||||
**Decisione collaterale:** rollback tier C a `qwen/qwen-2.5-72b-instruct` (run controllo l'ha dimostrato superiore a qwen3-235b: +30% fitness, 4× entropy, metà costo e tempo).
|
||||
|
||||
**Goal:** Introdurre un quinto operatore di mutazione che usa un LLM tier B come "mutator" per riscrivere il `system_prompt` di un genoma, generando diversità reale dove oggi `random_mutate` tocca solo quattro scalari. La pipeline GA esistente resta intatta: `mutate_prompt_llm` è solo un nuovo membro di `MUTATION_OPS` con peso configurabile.
|
||||
|
||||
**Architecture:** Operatore puro come gli altri quattro (`mutate_temperature`, `mutate_lookback`, `mutate_feature_access`, `mutate_cognitive_style`). Riceve `parent_genome`, `llm_client`, `rng` e restituisce un child genome con `system_prompt` modificato. Il mutator LLM (tier B = `deepseek/deepseek-v4-flash`) riceve una mutation-instruction casuale tra sei tipi predefiniti (`tighten_threshold`, `swap_comparator`, `add_condition`, `remove_condition`, `change_timeframe`, `add_temporal_gate`) e produce un nuovo prompt vincolato a una mutazione "atomica". Il child viene validato (parser + adversarial dry-run); su fallimento si effettua fallback a `random_mutate`. Selezione probabilistica nel `random_mutate` dispatcher con peso configurabile (default 0.30) — i quattro operator scalari mantengono il 70% complessivo.
|
||||
|
||||
**Tech Stack:** Python 3.13, `LLMClient` esistente (OpenAI SDK via OpenRouter), pytest + `pytest-mock`. Niente nuove dipendenze.
|
||||
|
||||
**Spec di riferimento:** sezione "Meccanismo di mutazione" della conversazione `2026-05-11`, valutazione `mutate_prompt_llm` (questa pagina contiene la sintesi).
|
||||
|
||||
---
|
||||
|
||||
## Trigger condition (quando attivare)
|
||||
|
||||
Implementare e mergiare **solo se** uno dei seguenti è vero al termine di Phase 2:
|
||||
|
||||
1. **Plateau evolutivo**: max fitness stagnante (Δ < 0.01) per ≥ 4 generazioni consecutive su `phase2-qwen3-001` o successori.
|
||||
2. **Diversità prompt collassa**: media Levenshtein normalizzata fra i prompt della popolazione finale ≤ 0.15 (= popolazione clonata).
|
||||
3. **Top genome problematico ma quasi-fit**: max fitness ≥ 0.10 ma adversarial finding HIGH ≥ 2 per il top, suggerendo che una mutazione mirata del prompt potrebbe "ripararlo".
|
||||
|
||||
Se Phase 2 raggiunge max fitness ≥ 0.30 senza plateau, **non attivare** (la diversità random basta).
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| File | Tipo | Responsabilità |
|
||||
|------|------|----------------|
|
||||
| `src/multi_swarm/genome/mutation_prompt_llm.py` | New | Operatore `mutate_prompt_llm` + helper `MUTATION_INSTRUCTIONS` + retry/fallback wrapper |
|
||||
| `src/multi_swarm/genome/mutation.py` | Modify | Estendere `MUTATION_OPS` + introdurre dispatcher pesato `weighted_random_mutate` |
|
||||
| `src/multi_swarm/ga/loop.py` | Modify | Sostituire `random_mutate(parent, rng)` con `weighted_random_mutate(parent, rng, llm_client, weights)` |
|
||||
| `src/multi_swarm/orchestrator/run.py` | Modify | Aggiungere `mutator_tier: ModelTier = ModelTier.B` e `prompt_mutation_weight: float = 0.30` a `RunConfig`, passare `LLMClient` al loop GA |
|
||||
| `src/multi_swarm/llm/cost_tracker.py` | Modify (minimo) | Loggare `mutation_call` separatamente da `hypothesis_call` per attribuzione costo |
|
||||
| `src/multi_swarm/metrics/diversity.py` | New | Funzione `population_prompt_diversity` (Levenshtein normalizzata) — usata in trigger check + telemetry |
|
||||
| `tests/unit/test_mutation_prompt_llm.py` | New | Test operator con mock `LLMClient` (success + validation fail + retry/fallback) |
|
||||
| `tests/unit/test_mutation_dispatcher.py` | New | Test `weighted_random_mutate` rispetta i pesi |
|
||||
| `tests/unit/test_diversity.py` | New | Test `population_prompt_diversity` su prompt identici/diversi |
|
||||
| `tests/integration/test_ga_loop_with_prompt_mutator.py` | New | Loop end-to-end di 2 gen × 5 genomi con mock LLM, verifica diversità prompt cresce |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Mutator instructions + operator stub
|
||||
|
||||
**Files:**
|
||||
- New: `src/multi_swarm/genome/mutation_prompt_llm.py`
|
||||
- New: `tests/unit/test_mutation_prompt_llm.py`
|
||||
|
||||
- [x] **Step 1.1: Write failing test — operator returns child con system_prompt diverso**
|
||||
|
||||
Append a `tests/unit/test_mutation_prompt_llm.py`:
|
||||
|
||||
```python
|
||||
def test_mutate_prompt_llm_produces_different_prompt(mock_llm: LLMClient) -> None:
|
||||
parent = make_genome(system_prompt="Strategia: compra quando RSI < 30")
|
||||
mock_llm.respond_with("Strategia: compra quando RSI < 25 e ora >= 14")
|
||||
child = mutate_prompt_llm(parent, mock_llm, rng=random.Random(0))
|
||||
assert child.system_prompt != parent.system_prompt
|
||||
assert child.parent_ids == [*parent.parent_ids, parent.id]
|
||||
assert child.generation == parent.generation + 1
|
||||
```
|
||||
|
||||
- [x] **Step 1.2: Implement `MUTATION_INSTRUCTIONS` constant**
|
||||
|
||||
`mutation_prompt_llm.py`:
|
||||
|
||||
```python
|
||||
MUTATION_INSTRUCTIONS: dict[str, str] = {
|
||||
"tighten_threshold": "Rendi una soglia numerica più restrittiva del 10–20%...",
|
||||
"swap_comparator": "Inverti un comparator (gt ↔ lt, gte ↔ lte) mantenendo intent...",
|
||||
"add_condition": "Aggiungi una condizione AND/OR alla rule più specifica...",
|
||||
"remove_condition": "Rimuovi una condizione ridondante o debole...",
|
||||
"change_timeframe": "Modifica una finestra rolling (lookback) di ±30%...",
|
||||
"add_temporal_gate": "Aggiungi un gate temporale (hour, dow, is_weekend)...",
|
||||
}
|
||||
```
|
||||
|
||||
- [x] **Step 1.3: Implement `mutate_prompt_llm`**
|
||||
|
||||
Firma:
|
||||
```python
|
||||
def mutate_prompt_llm(
|
||||
g: HypothesisAgentGenome,
|
||||
llm: LLMClient,
|
||||
rng: random.Random,
|
||||
mutator_tier: ModelTier = ModelTier.B,
|
||||
) -> HypothesisAgentGenome:
|
||||
```
|
||||
|
||||
Logica:
|
||||
1. Scegli `instruction_key = rng.choice(list(MUTATION_INSTRUCTIONS))`.
|
||||
2. Costruisci messaggio system + user con `MUTATION_INSTRUCTIONS[instruction_key]` + `g.system_prompt`.
|
||||
3. Crea genoma temporaneo `mutator_genome` con `model_tier=mutator_tier`.
|
||||
4. Chiama `llm.complete(mutator_genome, system, user, max_tokens=2000)`.
|
||||
5. Estrai nuovo prompt da risposta (cerca blocco `<prompt>...</prompt>` o intero output).
|
||||
6. Ritorna `_clone_with(g, system_prompt=new_prompt)` (riusa helper di `mutation.py`).
|
||||
|
||||
- [x] **Step 1.4: Run test → green**
|
||||
|
||||
```bash
|
||||
uv run pytest tests/unit/test_mutation_prompt_llm.py::test_mutate_prompt_llm_produces_different_prompt -xvs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Validation + fallback
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/multi_swarm/genome/mutation_prompt_llm.py`
|
||||
- Append: `tests/unit/test_mutation_prompt_llm.py`
|
||||
|
||||
- [x] **Step 2.1: Write failing test — fallback a random_mutate su prompt invalid**
|
||||
|
||||
```python
|
||||
def test_mutate_prompt_llm_falls_back_on_invalid_prompt(mock_llm: LLMClient) -> None:
|
||||
parent = make_genome()
|
||||
mock_llm.respond_with("garbage that does not parse")
|
||||
child = mutate_prompt_llm(parent, mock_llm, rng=random.Random(0))
|
||||
# Garbage prompt deve fallback: child è prodotto da random_mutate, quindi
|
||||
# system_prompt == parent.system_prompt (random_mutate tocca solo scalari)
|
||||
assert child.system_prompt == parent.system_prompt
|
||||
assert child.parent_ids == [*parent.parent_ids, parent.id]
|
||||
```
|
||||
|
||||
- [x] **Step 2.2: Implement validation step**
|
||||
|
||||
Dopo aver estratto `new_prompt`, esegui `validate_prompt(new_prompt)`:
|
||||
- Lunghezza minima 50 caratteri.
|
||||
- Contiene almeno una keyword fra `{rsi, sma, ema, atr, momentum, breakout, mean reversion, gt, lt, ...}`.
|
||||
- Non identico a `parent.system_prompt` (Levenshtein > 0.05 normalizzata).
|
||||
|
||||
Su fail → log warning + ritorna `random_mutate(g, rng)`.
|
||||
|
||||
- [x] **Step 2.3: Write failing test — diversity guard**
|
||||
|
||||
Mock LLM ritorna prompt identico al parent → `validate_prompt` rifiuta → fallback.
|
||||
|
||||
- [x] **Step 2.4: Run test suite parziale**
|
||||
|
||||
```bash
|
||||
uv run pytest tests/unit/test_mutation_prompt_llm.py -xvs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Weighted dispatcher
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/multi_swarm/genome/mutation.py`
|
||||
- New: `tests/unit/test_mutation_dispatcher.py`
|
||||
|
||||
- [x] **Step 3.1: Write failing test — weighted_random_mutate rispetta pesi**
|
||||
|
||||
```python
|
||||
def test_weighted_random_mutate_picks_prompt_op_at_configured_rate() -> None:
|
||||
rng = random.Random(0)
|
||||
weights = {"prompt": 1.0, "scalar": 0.0} # 100% prompt
|
||||
counter = Counter()
|
||||
for _ in range(100):
|
||||
op_name = _pick_op_name(weights, rng)
|
||||
counter[op_name] += 1
|
||||
assert counter["prompt"] == 100
|
||||
```
|
||||
|
||||
- [x] **Step 3.2: Implement `weighted_random_mutate`**
|
||||
|
||||
```python
|
||||
def weighted_random_mutate(
|
||||
g: HypothesisAgentGenome,
|
||||
rng: random.Random,
|
||||
llm: LLMClient | None = None,
|
||||
prompt_mutation_weight: float = 0.30,
|
||||
) -> HypothesisAgentGenome:
|
||||
if llm is not None and rng.random() < prompt_mutation_weight:
|
||||
return mutate_prompt_llm(g, llm, rng)
|
||||
return random_mutate(g, rng)
|
||||
```
|
||||
|
||||
- [x] **Step 3.3: Test edge cases**
|
||||
|
||||
- `llm=None` → sempre scalar mutation (backward compat).
|
||||
- `prompt_mutation_weight=0.0` → sempre scalar.
|
||||
- `prompt_mutation_weight=1.0` → sempre prompt (se llm presente).
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Integrazione GA loop
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/multi_swarm/ga/loop.py`
|
||||
- Modify: `src/multi_swarm/orchestrator/run.py`
|
||||
- New: `tests/integration/test_ga_loop_with_prompt_mutator.py`
|
||||
|
||||
- [x] **Step 4.1: Estendere `GAConfig`**
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class GAConfig:
|
||||
population_size: int
|
||||
elite_k: int
|
||||
tournament_k: int
|
||||
p_crossover: float
|
||||
prompt_mutation_weight: float = 0.0 # default off → opt-in
|
||||
```
|
||||
|
||||
- [x] **Step 4.2: Pass `LLMClient` in `next_generation`**
|
||||
|
||||
```python
|
||||
def next_generation(
|
||||
population: list[HypothesisAgentGenome],
|
||||
fitnesses: dict[str, float],
|
||||
cfg: GAConfig,
|
||||
rng: random.Random,
|
||||
llm: LLMClient | None = None,
|
||||
) -> list[HypothesisAgentGenome]:
|
||||
...
|
||||
child = weighted_random_mutate(parent, rng, llm, cfg.prompt_mutation_weight)
|
||||
```
|
||||
|
||||
- [x] **Step 4.3: Wire in orchestrator**
|
||||
|
||||
`RunConfig.prompt_mutation_weight: float = 0.0` (default off). Quando attivo via CLI `--prompt-mutation-weight 0.30`, passare a `next_generation`.
|
||||
|
||||
- [x] **Step 4.4: Integration test**
|
||||
|
||||
Loop 2 gen × 5 genomi, mock LLM che ritorna prompt sempre diversi. Verifica che la popolazione finale abbia più diversità prompt della iniziale.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Diversity metric
|
||||
|
||||
**Files:**
|
||||
- New: `src/multi_swarm/metrics/diversity.py`
|
||||
- New: `tests/unit/test_diversity.py`
|
||||
|
||||
- [x] **Step 5.1: Implement `population_prompt_diversity`**
|
||||
|
||||
```python
|
||||
def population_prompt_diversity(prompts: list[str]) -> float:
|
||||
"""Levenshtein normalizzata media su tutte le coppie. 0.0 = identici, 1.0 = totalmente diversi."""
|
||||
```
|
||||
|
||||
- [x] **Step 5.2: Test**
|
||||
|
||||
Tre prompt identici → 0.0. Tre prompt totalmente diversi → ~1.0.
|
||||
|
||||
- [x] **Step 5.3: Logging**
|
||||
|
||||
Aggiungere `diversity_prompt` come campo per-generazione in `repository.save_generation` (richiede migration leggera).
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Cost attribution
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/multi_swarm/llm/cost_tracker.py`
|
||||
- Modify: tests esistenti
|
||||
|
||||
- [x] **Step 6.1: Aggiungere `call_kind` a `CostRecord`**
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class CostRecord:
|
||||
...
|
||||
call_kind: str = "hypothesis" # "hypothesis" | "mutation"
|
||||
```
|
||||
|
||||
- [x] **Step 6.2: Loggare separatamente in summary**
|
||||
|
||||
`summary()["by_call_kind"]` con breakdown.
|
||||
|
||||
- [x] **Step 6.3: Test compatibilità con record esistenti**
|
||||
|
||||
Backward compat: record senza `call_kind` interpretati come `"hypothesis"`.
|
||||
|
||||
---
|
||||
|
||||
## Verification end-to-end
|
||||
|
||||
- [x] `uv run pytest -q` → 100% passa (157 + nuovi test).
|
||||
- [x] `uv run python scripts/smoke_run.py` → completa con mock LLM.
|
||||
- [x] **Run baseline B**: ripetere `phase2-qwen3-001` con `--prompt-mutation-weight 0.0` per controllo.
|
||||
- [x] **Run trattamento T**: `phase2-qwen3-prompt-mut-001` con `--prompt-mutation-weight 0.30`.
|
||||
- [x] Confronto: max fitness T > B + 20%, diversity_prompt(T) > diversity_prompt(B) + 30%.
|
||||
- [x] Costo aggiuntivo run T ≤ $0.10 (sanity check).
|
||||
|
||||
---
|
||||
|
||||
## Risks & mitigations
|
||||
|
||||
| Rischio | Mitigazione |
|
||||
|---------|-------------|
|
||||
| Mode collapse mutator LLM | `mutation_instruction` scelta random + diversity guard Levenshtein |
|
||||
| Prompt LLM-output non parsabile dal compiler | Validation step + fallback `random_mutate` |
|
||||
| Costo runaway (loop infinito retry) | `max_tokens=2000`, no retry su validation fail |
|
||||
| Bias condiviso con generator tier C | Mutator tier B = `deepseek-v4-flash`, famiglia diversa da Qwen3 |
|
||||
| Variabili confuse con Phase 2 | Attivare **solo** dopo Phase 2 baseline; A/B isolato |
|
||||
|
||||
---
|
||||
|
||||
## Cost estimate
|
||||
|
||||
Pop = 20, gen = 10, mutation rate ~75% (5 elite + 15 children), prompt_mutation_weight = 0.30:
|
||||
- ~45 chiamate LLM tier B aggiuntive per run.
|
||||
- ~500 tok input + 200 tok output per call → 22.5k in + 9k out totali.
|
||||
- 22.5k × $0.14/1M + 9k × $0.28/1M ≈ **$0.0057/run**.
|
||||
|
||||
Trascurabile rispetto al budget run base (~$0.10).
|
||||
@@ -1,482 +0,0 @@
|
||||
# Feature temporali nella grammatica Hypothesis — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Aggiungere quattro feature temporali (`hour`, `dow`, `is_weekend`, `minute_of_hour`) alla grammatica delle strategie Hypothesis come `FeatureNode`, universalmente accessibili a ogni genoma e usabili con i comparator esistenti.
|
||||
|
||||
**Architecture:** Estensione puramente additiva. La whitelist `KNOWN_FEATURES` in `protocol/grammar.py` cresce da 5 a 9 nomi. Il dispatcher di `FeatureNode` in `protocol/compiler.py` acquisisce un branch prioritario che mappa i nomi temporali a serie derivate da `df.index` (DatetimeIndex UTC). Il prompt template di `agents/hypothesis.py` riceve due esempi few-shot. Nessuna modifica a parser, mutation/crossover, genome dataclass.
|
||||
|
||||
**Tech Stack:** Python 3.13, pandas (DatetimeIndex), pytest. Esecuzione via `uv run`. Repository: `/home/adriano/Documenti/Git_XYZ/Multi_Swarm_Coevolutive`.
|
||||
|
||||
**Spec di riferimento:** `docs/superpowers/specs/2026-05-11-temporal-features-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| File | Tipo | Responsabilità |
|
||||
|------|------|----------------|
|
||||
| `src/multi_swarm/protocol/grammar.py` | Modify | Estendere `KNOWN_FEATURES` |
|
||||
| `src/multi_swarm/protocol/compiler.py` | Modify | Aggiungere `_TIME_FEATURE_FNS` + branch in `_eval_node` |
|
||||
| `src/multi_swarm/agents/hypothesis.py` | Modify | Estendere prompt template con sezione feature temporali + 2 esempi |
|
||||
| `tests/unit/test_protocol_validator.py` | Modify | +2 test (accept/reject) |
|
||||
| `tests/unit/test_protocol_compiler.py` | Modify | +5 test (4 feature + 1 integrazione) |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Grammar extension + validator tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/multi_swarm/protocol/grammar.py:21-23`
|
||||
- Modify: `tests/unit/test_protocol_validator.py` (append)
|
||||
|
||||
- [ ] **Step 1.1: Write failing test — validator accepts temporal features**
|
||||
|
||||
Append to `tests/unit/test_protocol_validator.py`:
|
||||
|
||||
```python
|
||||
def test_validator_accepts_temporal_features() -> None:
|
||||
for name in ("hour", "dow", "is_weekend", "minute_of_hour"):
|
||||
src = _wrap(
|
||||
{
|
||||
"op": "gt",
|
||||
"args": [
|
||||
{"kind": "feature", "name": name},
|
||||
{"kind": "literal", "value": 0},
|
||||
],
|
||||
}
|
||||
)
|
||||
ast = parse_strategy(src)
|
||||
validate_strategy(ast) # no exception
|
||||
|
||||
|
||||
def test_validator_rejects_temporal_typo() -> None:
|
||||
src = _wrap(
|
||||
{
|
||||
"op": "gt",
|
||||
"args": [
|
||||
{"kind": "feature", "name": "weekday"},
|
||||
{"kind": "literal", "value": 0},
|
||||
],
|
||||
}
|
||||
)
|
||||
ast = parse_strategy(src)
|
||||
with pytest.raises(ValidationError, match="unknown feature"):
|
||||
validate_strategy(ast)
|
||||
```
|
||||
|
||||
- [ ] **Step 1.2: Run tests to verify they fail**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_protocol_validator.py::test_validator_accepts_temporal_features tests/unit/test_protocol_validator.py::test_validator_rejects_temporal_typo -v`
|
||||
Expected: First test FAILs with `ValidationError: unknown feature: hour`. Second test PASSes already (weekday is unknown today too).
|
||||
|
||||
- [ ] **Step 1.3: Extend `KNOWN_FEATURES` whitelist**
|
||||
|
||||
Edit `src/multi_swarm/protocol/grammar.py`, lines 21-23:
|
||||
|
||||
```python
|
||||
KNOWN_FEATURES: frozenset[str] = frozenset(
|
||||
{"open", "high", "low", "close", "volume",
|
||||
"hour", "dow", "is_weekend", "minute_of_hour"}
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 1.4: Run tests to verify both pass**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_protocol_validator.py -v`
|
||||
Expected: All tests PASS (both new tests + all pre-existing ones).
|
||||
|
||||
- [ ] **Step 1.5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/multi_swarm/protocol/grammar.py tests/unit/test_protocol_validator.py
|
||||
git commit -m "feat(protocol): extend KNOWN_FEATURES with temporal feature names"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Compiler — `hour` feature
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/multi_swarm/protocol/compiler.py:135-137`
|
||||
- Modify: `tests/unit/test_protocol_compiler.py` (append)
|
||||
|
||||
- [ ] **Step 2.1: Write failing test for `hour`**
|
||||
|
||||
Append to `tests/unit/test_protocol_compiler.py`:
|
||||
|
||||
```python
|
||||
def test_compile_hour_feature_returns_index_hour(ohlcv: pd.DataFrame) -> None:
|
||||
src = json.dumps(
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"condition": {
|
||||
"op": "gt",
|
||||
"args": [
|
||||
{"kind": "feature", "name": "hour"},
|
||||
{"kind": "literal", "value": -1},
|
||||
],
|
||||
},
|
||||
"action": "entry-long",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
ast = parse_strategy(src)
|
||||
fn = compile_strategy(ast)
|
||||
signal = fn(ohlcv)
|
||||
# Tutte le righe hanno hour >= 0 > -1, quindi tutte entry-long
|
||||
assert (signal == Side.LONG).all()
|
||||
```
|
||||
|
||||
- [ ] **Step 2.2: Run test to verify it fails**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_protocol_compiler.py::test_compile_hour_feature_returns_index_hour -v`
|
||||
Expected: FAIL with `KeyError: 'hour'` (df has no `hour` column, dispatcher falls into `df[name]`).
|
||||
|
||||
- [ ] **Step 2.3: Add `_TIME_FEATURE_FNS` and dispatcher branch**
|
||||
|
||||
Edit `src/multi_swarm/protocol/compiler.py`. Insert after line 108 (end of `INDICATOR_FNS`):
|
||||
|
||||
```python
|
||||
_TIME_FEATURE_FNS: dict[str, Callable[[pd.DatetimeIndex], pd.Series]] = {
|
||||
"hour": lambda idx: pd.Series(idx.hour, index=idx, dtype="int64"),
|
||||
"dow": lambda idx: pd.Series(idx.dayofweek, index=idx, dtype="int64"),
|
||||
"is_weekend": lambda idx: pd.Series((idx.dayofweek >= 5).astype("int64"), index=idx),
|
||||
"minute_of_hour": lambda idx: pd.Series(idx.minute, index=idx, dtype="int64"),
|
||||
}
|
||||
```
|
||||
|
||||
Then modify `_eval_node` at line 135-137. Replace:
|
||||
|
||||
```python
|
||||
def _eval_node(node: Node, df: pd.DataFrame) -> pd.Series:
|
||||
if isinstance(node, FeatureNode):
|
||||
return df[node.name]
|
||||
```
|
||||
|
||||
With:
|
||||
|
||||
```python
|
||||
def _eval_node(node: Node, df: pd.DataFrame) -> pd.Series:
|
||||
if isinstance(node, FeatureNode):
|
||||
if node.name in _TIME_FEATURE_FNS:
|
||||
return _TIME_FEATURE_FNS[node.name](df.index)
|
||||
return df[node.name]
|
||||
```
|
||||
|
||||
- [ ] **Step 2.4: Run test to verify it passes**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_protocol_compiler.py::test_compile_hour_feature_returns_index_hour -v`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 2.5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/multi_swarm/protocol/compiler.py tests/unit/test_protocol_compiler.py
|
||||
git commit -m "feat(protocol): dispatcher temporal features (hour) in compiler"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Compiler — `dow` and `is_weekend` tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/unit/test_protocol_compiler.py` (append)
|
||||
|
||||
Nessuna modifica al sorgente: `_TIME_FEATURE_FNS` definito in Task 2 contiene già le quattro funzioni. Questi test verificano semantica e copertura.
|
||||
|
||||
- [ ] **Step 3.1: Add `dow` test**
|
||||
|
||||
Append to `tests/unit/test_protocol_compiler.py`:
|
||||
|
||||
```python
|
||||
def test_compile_dow_feature_monday_is_zero(ohlcv: pd.DataFrame) -> None:
|
||||
# 2024-01-01 e' un lunedi -> dow=0; gating eq dow 0 deve dare LONG su monday only.
|
||||
src = json.dumps(
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"condition": {
|
||||
"op": "eq",
|
||||
"args": [
|
||||
{"kind": "feature", "name": "dow"},
|
||||
{"kind": "literal", "value": 0},
|
||||
],
|
||||
},
|
||||
"action": "entry-long",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
ast = parse_strategy(src)
|
||||
fn = compile_strategy(ast)
|
||||
signal = fn(ohlcv)
|
||||
# ohlcv fixture: 200h da 2024-01-01 00:00 UTC -> primo lunedi e' bar 0..23
|
||||
monday_hours = signal[(signal.index.dayofweek == 0)]
|
||||
other_hours = signal[(signal.index.dayofweek != 0)]
|
||||
assert (monday_hours == Side.LONG).all()
|
||||
assert (other_hours == Side.FLAT).all()
|
||||
```
|
||||
|
||||
- [ ] **Step 3.2: Add `is_weekend` test**
|
||||
|
||||
Append:
|
||||
|
||||
```python
|
||||
def test_compile_is_weekend_returns_zero_one(ohlcv: pd.DataFrame) -> None:
|
||||
src = json.dumps(
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"condition": {
|
||||
"op": "eq",
|
||||
"args": [
|
||||
{"kind": "feature", "name": "is_weekend"},
|
||||
{"kind": "literal", "value": 1},
|
||||
],
|
||||
},
|
||||
"action": "entry-long",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
ast = parse_strategy(src)
|
||||
fn = compile_strategy(ast)
|
||||
signal = fn(ohlcv)
|
||||
weekend = signal[signal.index.dayofweek >= 5]
|
||||
weekdays = signal[signal.index.dayofweek < 5]
|
||||
assert (weekend == Side.LONG).all()
|
||||
assert (weekdays == Side.FLAT).all()
|
||||
```
|
||||
|
||||
- [ ] **Step 3.3: Run both tests**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_protocol_compiler.py::test_compile_dow_feature_monday_is_zero tests/unit/test_protocol_compiler.py::test_compile_is_weekend_returns_zero_one -v`
|
||||
Expected: Both PASS.
|
||||
|
||||
- [ ] **Step 3.4: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/unit/test_protocol_compiler.py
|
||||
git commit -m "test(protocol): compiler semantica dow + is_weekend"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Compiler — `minute_of_hour` test
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/unit/test_protocol_compiler.py` (append)
|
||||
|
||||
- [ ] **Step 4.1: Add `minute_of_hour` test**
|
||||
|
||||
Append:
|
||||
|
||||
```python
|
||||
def test_compile_minute_of_hour_zero_on_1h_timeframe(ohlcv: pd.DataFrame) -> None:
|
||||
# Fixture ohlcv ha freq=1h, quindi tutti i minute_of_hour sono 0.
|
||||
# gating eq minute_of_hour 0 -> LONG su TUTTE le righe.
|
||||
src = json.dumps(
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"condition": {
|
||||
"op": "eq",
|
||||
"args": [
|
||||
{"kind": "feature", "name": "minute_of_hour"},
|
||||
{"kind": "literal", "value": 0},
|
||||
],
|
||||
},
|
||||
"action": "entry-long",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
ast = parse_strategy(src)
|
||||
fn = compile_strategy(ast)
|
||||
signal = fn(ohlcv)
|
||||
assert (signal == Side.LONG).all()
|
||||
```
|
||||
|
||||
- [ ] **Step 4.2: Run test**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_protocol_compiler.py::test_compile_minute_of_hour_zero_on_1h_timeframe -v`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 4.3: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/unit/test_protocol_compiler.py
|
||||
git commit -m "test(protocol): compiler semantica minute_of_hour su 1h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Compiler — integrazione con regola completa
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/unit/test_protocol_compiler.py` (append)
|
||||
|
||||
- [ ] **Step 5.1: Add integration test**
|
||||
|
||||
Append:
|
||||
|
||||
```python
|
||||
def test_rule_with_temporal_gating_compiles_and_executes(ohlcv: pd.DataFrame) -> None:
|
||||
# Regola: entry-long se hour > 14 AND close > sma(20).
|
||||
# close in fixture e' lineare crescente, quindi close > sma(20) e' True dopo warmup.
|
||||
# entry-long deve apparire solo nelle bar con hour > 14.
|
||||
src = json.dumps(
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"condition": {
|
||||
"op": "and",
|
||||
"args": [
|
||||
{
|
||||
"op": "gt",
|
||||
"args": [
|
||||
{"kind": "feature", "name": "hour"},
|
||||
{"kind": "literal", "value": 14},
|
||||
],
|
||||
},
|
||||
{
|
||||
"op": "gt",
|
||||
"args": [
|
||||
{"kind": "feature", "name": "close"},
|
||||
{"kind": "indicator", "name": "sma", "params": [20]},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"action": "entry-long",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
ast = parse_strategy(src)
|
||||
fn = compile_strategy(ast)
|
||||
signal = fn(ohlcv)
|
||||
|
||||
# Bar con hour <= 14: mai LONG (gating temporale blocca).
|
||||
morning = signal[signal.index.hour <= 14]
|
||||
assert (morning == Side.FLAT).all()
|
||||
|
||||
# Bar con hour > 14 e dopo warmup sma (>=20 bar dall'inizio): LONG.
|
||||
afternoon_warm = signal[(signal.index.hour > 14) & (np.arange(len(signal)) >= 20)]
|
||||
assert (afternoon_warm == Side.LONG).all()
|
||||
```
|
||||
|
||||
- [ ] **Step 5.2: Run test**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_protocol_compiler.py::test_rule_with_temporal_gating_compiles_and_executes -v`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5.3: Run full compiler + validator test suite to check regressions**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_protocol_compiler.py tests/unit/test_protocol_validator.py -v`
|
||||
Expected: All tests PASS (pre-existing + new). Nessun test rotto.
|
||||
|
||||
- [ ] **Step 5.4: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/unit/test_protocol_compiler.py
|
||||
git commit -m "test(protocol): integration test gating temporale + sma"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Update Hypothesis prompt
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/multi_swarm/agents/hypothesis.py:84-85`
|
||||
|
||||
- [ ] **Step 6.1: Edit prompt template**
|
||||
|
||||
In `src/multi_swarm/agents/hypothesis.py`, alla riga 84-85 sostituire:
|
||||
|
||||
```python
|
||||
Leaf - feature OHLCV:
|
||||
{{"kind": "feature", "name": "open|high|low|close|volume"}}
|
||||
```
|
||||
|
||||
con:
|
||||
|
||||
```python
|
||||
Leaf - feature OHLCV:
|
||||
{{"kind": "feature", "name": "open|high|low|close|volume"}}
|
||||
|
||||
Leaf - feature TEMPORALI (sempre accessibili, UTC):
|
||||
{{"kind": "feature", "name": "hour"}} // range 0-23
|
||||
{{"kind": "feature", "name": "dow"}} // range 0-6 (lun=0, dom=6)
|
||||
{{"kind": "feature", "name": "is_weekend"}} // 0 o 1
|
||||
{{"kind": "feature", "name": "minute_of_hour"}} // range 0-59
|
||||
|
||||
Esempi di gating temporale:
|
||||
// Solo durante la sessione US (14:00-22:00 UTC)
|
||||
{{"op": "and", "args": [
|
||||
{{"op": "gt", "args": [{{"kind": "feature", "name": "hour"}}, {{"kind": "literal", "value": 14}}]}},
|
||||
{{"op": "lt", "args": [{{"kind": "feature", "name": "hour"}}, {{"kind": "literal", "value": 22}}]}}
|
||||
]}}
|
||||
|
||||
// Solo nel weekend (sab+dom)
|
||||
{{"op": "eq", "args": [{{"kind": "feature", "name": "is_weekend"}}, {{"kind": "literal", "value": 1}}]}}
|
||||
```
|
||||
|
||||
- [ ] **Step 6.2: Run existing hypothesis tests to verify prompt format still valid**
|
||||
|
||||
Run: `uv run pytest tests/unit/test_hypothesis_agent.py -v`
|
||||
Expected: All tests PASS. Il template `{feature_access}` continua a funzionare perché non lo abbiamo toccato.
|
||||
|
||||
- [ ] **Step 6.3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/multi_swarm/agents/hypothesis.py
|
||||
git commit -m "feat(hypothesis): aggiungi feature temporali al prompt con 2 esempi few-shot"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Smoke run end-to-end
|
||||
|
||||
**Files:**
|
||||
- Nessuna modifica al codice.
|
||||
|
||||
Validazione che il loop intero giri con la grammatica estesa: carica OHLCV, genera 4 genomi, compila, backtesta, valuta DSR, applica Adversarial, persiste.
|
||||
|
||||
- [ ] **Step 7.1: Run smoke script**
|
||||
|
||||
Run: `uv run python -m scripts.smoke_run`
|
||||
Expected: completamento senza eccezioni, output finale contenente `Smoke run completed`.
|
||||
|
||||
- [ ] **Step 7.2: Inspect at least one generated genome for temporal feature usage**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
LATEST=$(sqlite3 runs.db "SELECT id FROM runs WHERE name LIKE 'smoke%' ORDER BY started_at DESC LIMIT 1;")
|
||||
sqlite3 runs.db "SELECT genome_id, substr(raw_text, 1, 600) FROM evaluations WHERE run_id='$LATEST' LIMIT 4;"
|
||||
```
|
||||
|
||||
Expected output: 4 righe raw_text JSON. Almeno 1 dovrebbe contenere `"name": "hour"`, `"name": "dow"`, `"name": "is_weekend"`, o `"name": "minute_of_hour"`. Se 0/4 usano feature temporali, il prompt non è abbastanza eloquente — apri un follow-up per iterare il prompt (non bloccante per questa PR).
|
||||
|
||||
- [ ] **Step 7.3: Push branch + open PR**
|
||||
|
||||
```bash
|
||||
git log --oneline -8 # verifica 6 commit dei Task 1-6
|
||||
git push origin HEAD
|
||||
```
|
||||
|
||||
Aprire PR con titolo `feat: feature temporali nella grammatica Hypothesis` referenziando lo spec.
|
||||
|
||||
---
|
||||
|
||||
## Self-review notes (autore del piano)
|
||||
|
||||
- Tutti i 7 hard requirement dello spec (`grammar`, `compiler`, `prompt`, 4 feature, integration test, smoke, backward compat) sono coperti dai Task 1-7.
|
||||
- Nessun placeholder `TBD`/`TODO`.
|
||||
- Tipi consistenti: `_TIME_FEATURE_FNS` definito una volta in Task 2 e referenziato implicitamente dai tester nei Task 3-5 senza bisogno di re-definizione.
|
||||
- Test pre-esistenti non vengono toccati; il Task 5 include `pytest` sull'intera suite del protocollo come regression check.
|
||||
- Backward compat: `KNOWN_FEATURES` cresce, il branch OHLCV resta invariato → genomi vecchi restano validi senza migrazione DB.
|
||||
@@ -1,427 +0,0 @@
|
||||
# Decisione Strategica PoC Multi-Swarm Coevolutivo — Design
|
||||
|
||||
**Autore**: Adriano Dal Pastro
|
||||
**Data**: 9 maggio 2026
|
||||
**Status**: Design strategico approvato per implementazione
|
||||
**Versione**: 1.0
|
||||
**Documenti correlati**:
|
||||
- `00_documento_zero.md` (framework concettuale)
|
||||
- `coevolutive_swarm_system.md` (Filone A, sistema completo)
|
||||
- `poc_trading_swarm.md` (Filone B, design PoC trading)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
Questo documento formalizza la decisione strategica su come avviare il progetto Multi-Swarm Coevolutivo. La scelta cade sulla variante **B3 — PoC trading incrementale a tre fasi con gate go/no-go**, una declinazione disciplinata dell'opzione Smart Spike (Filone B) descritta nel documento zero.
|
||||
|
||||
La forma incrementale tripartita non sostituisce il design del PoC contenuto in `poc_trading_swarm.md`, ne organizza l'esecuzione in fasi successive con kill-switch numerici espliciti. Il principio guida è **applicare al progetto stesso la disciplina che il sistema dovrà applicare alle proprie ipotesi**: spegnere ciò che non funziona quando i dati lo dicono, senza sconti emotivi e senza giudizi soggettivi sui hard gate.
|
||||
|
||||
**Vincoli operativi adottati**:
|
||||
|
||||
| Dimensione | Valore |
|
||||
|---|---|
|
||||
| Obiettivo primario | Sistema produttivo che generi valore (trading reale, fase posteriore al PoC) |
|
||||
| Dominio iniziale | Derivati crypto BTC/ETH |
|
||||
| Tempo committed | Full-time, oltre 30h settimanali |
|
||||
| Budget LLM cap | $2.200 hard, segmentato per fase |
|
||||
| Capitale a rischio | $500-2.000 (solo nella Phase 3 forward-test mainnet) |
|
||||
| Tempo calendario | 14-18 settimane atteso, 20 settimane hard cap |
|
||||
| Setup tecnico di partenza | Cerbero_mcp operativo (multi-exchange, indicatori, audit, dual env) |
|
||||
|
||||
**Esito atteso**: alla fine delle tre fasi, una decisione binaria documentata con razionale numerico fra (a) avviare il sistema completo Filone A con confidence empirica forte, (b) iterare la Phase 2 su debolezze identificate, (c) pivotare su un dominio diverso (offerte commerciali Tielogic, code review), oppure (d) chiudere il progetto con learnings registrati.
|
||||
|
||||
---
|
||||
|
||||
## 2. Razionale della scelta strategica
|
||||
|
||||
### 2.1 Le tre opzioni del documento zero, oggi
|
||||
|
||||
Il documento zero presenta tre opzioni: A (Big Bet, sistema completo, 12-18 mesi), B (Smart Spike, PoC trading, 3-4 mesi), C (Research Dive, paper review più PoC minimo, 1-2 mesi). Alla luce dei vincoli operativi sopra elencati, la valutazione cambia rispetto al momento in cui il documento zero è stato scritto.
|
||||
|
||||
L'opzione A è esclusa dal vincolo budget. Le stime conservative del Filone A indicano costi LLM nell'ordine di $10.000-30.000 anche solo per la fase iniziale, contro un cap committed di $2.200. Non è una questione di tempo: il tempo full-time c'è. È che il rapporto fra costo del run e disponibilità non lo rende sostenibile senza una validazione empirica preliminare.
|
||||
|
||||
L'opzione C sotto-utilizza due asset materiali. Il primo è la disponibilità full-time, che rende il vincolo "non posso permettermi di costruire infrastruttura" non applicabile. Il secondo è Cerbero_mcp, già operativo: leggere paper per due settimane senza scrivere codice produttivo significherebbe lasciare ferma un'infrastruttura già pronta a essere wrappata come tool layer per agenti LLM.
|
||||
|
||||
Resta l'opzione B. Il documento zero la indicava come scelta preferita; questo documento la conferma e la struttura.
|
||||
|
||||
### 2.2 Perché B3 e non B1 o B2
|
||||
|
||||
Tre varianti di B sono state considerate.
|
||||
|
||||
La variante **B1 (Lean / single-shot)** comprime il PoC in un'unica run con popolazione ridotta a 20-30 agenti e tier C unico. Coerente con il budget tight, ma rischiosa: la dimensione della popolazione è il principale moltiplicatore di diversità nel sistema, e una popolazione undersized rischia di produrre un falso negativo. Si decreterebbe il sistema "non funzionante" quando in realtà non gli abbiamo dato il numero di tentativi necessari.
|
||||
|
||||
La variante **B2 (Canonical / come da documento)** segue fedelmente `poc_trading_swarm.md` con K=50, mix tier B/C, full set di ablation. Tecnicamente solida ma sfora il budget cap di un fattore 1.5-2x. Adottabile solo accettando di alzare il cap a $3-4K, decisione non giustificabile senza segnali empirici preliminari.
|
||||
|
||||
La variante **B3 (Incrementale)**, scelta, articola il PoC in tre fasi sequenziali con cap budget per fase e gate decisionali quantitativi fra una fase e la successiva. Phase 1 valida il loop tecnico con popolazione minima e tier economico. Phase 2 esegue il PoC canonico solo se Phase 1 passa. Phase 3 forward-testa con capitale reale solo se Phase 2 passa. Il budget totale resta entro $2.2K e il rischio di falso negativo viene ridotto dal fatto che la popolazione completa di Phase 2 non viene mai tagliata: viene messa al lavoro solo dopo che il loop è stato validato.
|
||||
|
||||
La struttura tripartita ha anche un beneficio non monetario: i deliverable di Phase 1 e Phase 2 valgono anche se le fasi successive falliscono. Il backtest engine, il GA harness, la Cerbero integration, la dashboard Streamlit, la pipeline DSR sono tutti riusabili in caso di pivot di dominio. Solo il capitale di Phase 3 è genuinamente "a rischio" come costo della validazione.
|
||||
|
||||
### 2.3 Coerenza con la filosofia del progetto
|
||||
|
||||
Il documento zero al §3.3 identifica come takeaway fondamentale di Renaissance la disciplina di spegnere strategie quando l'edge svanisce, senza decisioni emotive. Il design B3 applica questa disciplina al progetto stesso, prima ancora che al sistema. I gate sono numerici, le soglie sono fissate prima di vedere i dati, l'azione di stop è meccanica quando un hard gate fallisce. Non c'è spazio per "magari un'altra generazione" o "i risultati erano quasi ottimi": la decisione è già scritta nel design.
|
||||
|
||||
---
|
||||
|
||||
## 3. Vincoli di terminazione globali
|
||||
|
||||
Tre kill-switch globali sopra le singole fasi:
|
||||
|
||||
1. **Cap budget LLM globale**: $2.200 hard. Spesa effettiva monitorata da pagina Overview della dashboard, contatore aggiornato dopo ogni batch. Sforamento previsto entro la fase corrente → riformulazione scope, non incremento cap.
|
||||
2. **Cap tempo calendario**: 14-18 settimane attese, **20 settimane hard cap** dalla settimana 1 della Phase 1 alla decisione formale post-Phase 3. Sforamento previsto del cap → decisione anticipata sulla base dei dati raccolti, non estensione del cap.
|
||||
3. **Hard gate falliti**: per costruzione, un hard gate fallito chiude la fase corrente e non apre quella successiva. La decisione fra stop, pivot, o iterazione è formalizzata nel decision memo della fase chiusa, non nel passaggio automatico alla successiva.
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 1 — Lean Spike
|
||||
|
||||
**Obiettivo**: dimostrare che il loop tecnico funziona end-to-end. Non si misura ancora alpha: si misura se il sistema gira come progettato, se l'output LLM è formalizzabile, se la GA converge, se i costi sono prevedibili.
|
||||
|
||||
### 4.1 Scope IN
|
||||
|
||||
- Infrastruttura backtest minima: dataset 2 anni (2024-2026) di OHLCV 1h BTC/ETH, engine event-driven semplificato (no microstructure, no slippage modelling complesso, fees fissi a 5 basis points), walk-forward expanding 70/30.
|
||||
- Wrapper Cerbero come tool layer per agenti: i tool MCP esistenti (`indicators`, `vol_cone`, `oi_weighted_skew`, indicatori options/microstructure/stats) tradotti in funzioni callable dagli agenti. Niente reimplementazione, solo wrapping.
|
||||
- Protocollo S-expression fisso: 12-15 verbi disegnati manualmente come da `poc_trading_swarm.md` §2.2. Nessuna evoluzione del protocollo in questa fase.
|
||||
- Hypothesis Swarm con K=20 agenti, tier C unico (Qwen 2.5 72B via OpenRouter). Mutazione e crossover prompt come da documento PoC. Nessuna speciation, nessun novelty bonus.
|
||||
- Falsification e Adversarial layer hand-crafted, un agente fisso per ognuno, prompt manuali. Tier B (Sonnet 4.6) chiamato solo per i top-5 candidati a fine generazione, per contenere costi.
|
||||
- Fitness function v0: DSR in-sample con drawdown penalty. No multi-livello, no out-of-sample ancora.
|
||||
- GA loop: 8-12 generazioni, tournament selection, elitism k=2.
|
||||
|
||||
### 4.2 Scope OUT (esplicito)
|
||||
|
||||
- Multi-tier ablation comparativa.
|
||||
- Out-of-sample DSR e hold-out finale.
|
||||
- Random Forest baseline.
|
||||
- Speciation, novelty, diversity metrics.
|
||||
- Forward-test con capitale reale.
|
||||
- Domini diversi da BTC/ETH.
|
||||
|
||||
### 4.3 Budget e tempo
|
||||
|
||||
- LLM: $500-700. Stima base: 20 agenti × ~8.000 token output medi × 10 generazioni × pricing Qwen ≈ $300, più 50% di overhead per Adversarial, Falsification e iterazioni di sviluppo, totale stimato $500-700.
|
||||
- Tempo: 4-6 settimane full-time. Settimane 1-2 backtest engine e Cerbero wrapper, settimana 3 GA infrastructure e parser S-expression, settimane 4-5 tuning e run completo, settimana 6 analisi e decisione gate.
|
||||
- Capitale a rischio: zero.
|
||||
|
||||
### 4.4 Gate go/no-go (tutti AND)
|
||||
|
||||
Cinque hard gate:
|
||||
|
||||
1. **Loop converge**: la fitness mediana della popolazione cresce per almeno tre generazioni consecutive prima di plateau.
|
||||
2. **Output formalizzabile**: almeno l'80% delle proposte LLM passano il parser S-expression senza intervento manuale.
|
||||
3. **Tail superiore esiste**: i top-5 genomi hanno DSR in-sample pari ad almeno 1.5x la mediana di popolazione, segnale che esiste struttura e non solo rumore.
|
||||
4. **Diversità non collassa**: entropia della distribuzione di fitness in popolazione superiore a 0.5 a fine run, evita la convergenza monocoltura.
|
||||
5. **Cost predictability**: spesa effettiva entro ±30% della stima preventivata.
|
||||
|
||||
Anche un solo hard gate fallito chiude la Phase 1. Decisione successiva (pivot, ridiscussione design, stop) presa nel decision memo, non automaticamente in Phase 2.
|
||||
|
||||
### 4.5 Deliverable Phase 1
|
||||
|
||||
- Codice testato (pytest): backtest engine, Cerbero wrapper, GA loop, protocol parser.
|
||||
- Report tecnico (~5 pagine): loop convergence con grafici, ispezione qualitativa dei top-5 genomi, parser failure modes osservati, costi reali vs preventivo, diversity metrics.
|
||||
- Decision memo: vai/non-vai a Phase 2 con eventuali aggiustamenti di scope.
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 2 — Canonical PoC
|
||||
|
||||
**Obiettivo**: rispondere ai cinque test del PoC originale (`poc_trading_swarm.md` §1) con popolazione e infrastruttura adeguate. Solo questa fase produce una vera misura dell'edge potenziale del sistema.
|
||||
|
||||
### 5.1 Scope IN (in aggiunta a Phase 1)
|
||||
|
||||
- Hypothesis Swarm K=40, scaling della popolazione a un livello canonico ridotto (K=50 documento → K=40 per disciplina budget).
|
||||
- Tier mix B/C: circa 70% Qwen/DeepSeek (tier C), 30% Sonnet 4.6 (tier B). L'ablation comparativa misura il valore aggiunto del tier B.
|
||||
- Speciation di base: clustering dei genomi per cosine similarity dei prompt più cognitive_style. Si mantengono almeno 3 specie attive, ognuna con quote di tournament protette.
|
||||
- Novelty bonus: fitness composta α·DSR_OOS + β·novelty_score, dove la novelty è calcolata come distanza behavioural dei segnali rispetto a un archivio di elite.
|
||||
- Walk-forward expanding più hold-out finale: training Q1-2024 a Q4-2025 in walk-forward, hold-out intoccabile Q1-Q2 2026.
|
||||
- Random Forest baseline: feature engineering classico (returns multi-orizzonte, RSI/MACD/ATR, vol cone, funding rate, OI changes), classificazione long/flat/short su orizzonti 1d e 4h, valutato sulla stessa hold-out.
|
||||
- Adversarial layer hand-crafted potenziato: cinque prompt distinti (data snooping, lookahead, regime fragility, crowding, transaction cost erosion) eseguiti sui top-10 candidati prima della valutazione OOS.
|
||||
- Falsification con Deflated Sharpe Ratio (Bailey & López de Prado), correzione Bonferroni sul numero totale di ipotesi testate.
|
||||
- Fitness multi-livello: per-agent (contributo DSR), per-team (DSR portfolio), diversity penalty per ridurre collusione.
|
||||
|
||||
### 5.2 Scope OUT
|
||||
|
||||
- Co-evolution del protocollo (Filone A).
|
||||
- Forward-test con capitale reale (Phase 3).
|
||||
- Speciation NEAT-style completa.
|
||||
- Idiom emergence.
|
||||
- Domini diversi da trading.
|
||||
|
||||
### 5.3 Budget e tempo
|
||||
|
||||
- LLM: $700-1.100. Dettaglio stimato: tier C ~$500, tier B ~$400, overhead per ablation iterativa ~$200. Range ampio per consentire più cicli di ablation se i primi risultati lo richiedono.
|
||||
- Tempo: 4-6 settimane full-time. Settimana 1 porting K=40, speciation, novelty. Settimane 2-3 ablation runs (tier C only, tier B only, mix; con e senza speciation). Settimana 4 hold-out evaluation, RF baseline, Adversarial sweep. Settimane 5-6 analisi statistica, report, decisione gate.
|
||||
- Capitale a rischio: zero.
|
||||
|
||||
### 5.4 Gate go/no-go
|
||||
|
||||
**Hard gate (tutti AND, altrimenti stop)**:
|
||||
|
||||
1. **Significatività statistica**: top genoma su hold-out con DSR > 1.0 e p-value < 0.05 dopo correzione Bonferroni.
|
||||
2. **Sopravvivenza regime change**: DSR hold-out almeno 0.5x del DSR walk-forward — limite contro overfitting catastrofico.
|
||||
3. **Batte baseline**: top-3 genomi con Sharpe OOS superiore al Sharpe RF baseline OOS, effect size non trascurabile (Cohen's d > 0.3 sul rolling Sharpe).
|
||||
|
||||
**Soft gate (informano, non killano)**:
|
||||
|
||||
4. **Diversità**: almeno 3 specie distinte sopravvivono a fine run, top-3 genomi non identici per signal correlation (ρ < 0.7).
|
||||
5. **Tier B aggiunge valore**: l'ablation mostra Δ Sharpe OOS misurabile per tier mix vs tier C only. In caso negativo, Phase 3 può girare tier C only e il decision memo ne prende nota.
|
||||
|
||||
Hard gate passati → Phase 3. Hard gate falliti → stop o pivot. Soft gate falliti → Phase 3 con scope ridotto e annotazioni nel report.
|
||||
|
||||
### 5.5 Deliverable Phase 2
|
||||
|
||||
- Codice testato: speciation, novelty, ablation harness, RF baseline, DSR pipeline, Adversarial battery.
|
||||
- Report scientifico (~15-20 pagine): metodologia, risultati per ogni gate, ablation table, top-5 strategie ispezionate qualitativamente, threats to validity.
|
||||
- Decision memo: vai/non-vai a Phase 3, scope di Phase 3 (capitale, exchange, leva, durata) calibrato sui risultati.
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 3 — Forward-test mainnet
|
||||
|
||||
**Obiettivo**: vedere se l'edge sopravvive in produzione. Anche il backtest più rigoroso ha bias inevitabili (look-ahead microscopico, slippage idealizzato, assenza di information leakage da fonti che non esistevano in periodo storico). Solo il forward-test live risponde alla domanda finale.
|
||||
|
||||
### 6.1 Scope IN
|
||||
|
||||
- Selezione strategie: top-3 genomi out-of-sample dalla Phase 2, dopo passaggio completo dell'Adversarial battery. Niente cherry-picking ex-post post-Phase 2.
|
||||
- Capitale: $500-2.000 totali, distribuiti sui tre genomi con allocazione equal weight oppure risk-parity sulla volatilità OOS attesa, scelta motivata nel decision memo Phase 2.
|
||||
- Exchange: scelta condizionata alle strategie selezionate. Default raccomandati Bybit (perp, fees competitive, liquidità BTC/ETH adeguata) oppure Hyperliquid (no KYC, transparent funding). Cerbero supporta entrambi nativamente.
|
||||
- Leva: massimo 2x in forward-test. Anche se lo Sharpe OOS suggerisse di più, in fase di validazione la leva resta bassa per non confondere edge della strategia con leva.
|
||||
- Durata: 6-8 settimane continue. Razionale: in crypto questa finestra copre tipicamente uno o due micro-regime change, sufficienti a stressare il modello senza catastrofi statistiche da campione troppo piccolo.
|
||||
- Monitoring: dashboard giornaliera (sezione Live Monitor) con Sharpe live realized vs Sharpe OOS atteso, drawdown realtime, violation count (segnali generati ma non eseguiti per qualunque ragione), audit log Cerbero per ogni order.
|
||||
- Decision triggers automatici: kill-switch se il drawdown live supera 1.5x il peggiore osservato in walk-forward; pause se Sharpe rolling 14d resta negativo per 14 giorni consecutivi.
|
||||
- Adversarial post-mortem settimanale: l'agente Adversarial gira nuovamente sul signal log della settimana per identificare degradazione dell'edge (regime drift detection).
|
||||
|
||||
### 6.2 Scope OUT
|
||||
|
||||
- Capital scaling oltre $2.000 in Phase 3. Se i risultati promettono, la decisione di scaling è esplicitamente fuori dal PoC e viene presa dopo il decision memo finale.
|
||||
- Multi-strategy portfolio rebalancing dinamico. Allocazione statica sui tre genomi.
|
||||
- Hedging cross-exchange. Confonderebbe la lettura dell'edge della strategia.
|
||||
- Aggiunta di nuovi genomi in corsa. I tre genomi sono fissati a inizio fase.
|
||||
|
||||
### 6.3 Budget e tempo
|
||||
|
||||
- LLM: $200-400. La popolazione GA non gira più, gli agenti sono richiamati solo per Adversarial post-mortem settimanale e per occasionali refresh quando i decision triggers scattano.
|
||||
- Capitale a rischio: $500-2.000. Trattato come **costo della validazione**, non come investimento. Se il capitale va a zero, il dato che ne ricaviamo vale comunque.
|
||||
- Tempo: 6-8 settimane di calendario, monitoring operativo circa 5h/settimana — non full-time. Le settimane libere sono allocate a documentazione finale, lavoro su Tielogic e altri progetti, prima esplorazione Filone A in caso di decisione GO.
|
||||
- Costo infra extra: circa $10-30/mese VPS per dashboard più monitoring, in larga parte già coperto dal setup Hostinger esistente.
|
||||
|
||||
### 6.4 Gate decisionale finale del PoC
|
||||
|
||||
**Hard gate per "GO sistema completo / Filone A"**:
|
||||
|
||||
1. **Edge sopravvive live**: Sharpe live realized almeno 0.5x dello Sharpe OOS atteso, su finestra di almeno 4 settimane.
|
||||
2. **No catastrophic failure**: max drawdown live al massimo 1.5x del peggior drawdown walk-forward.
|
||||
3. **Reproducibility**: almeno 2 dei 3 genomi performano in linea con previsione — la fortuna non si concentra su uno solo.
|
||||
|
||||
**Soft gate (qualitativi, informano la decisione)**:
|
||||
|
||||
4. **Audit Adversarial settimanali**: nessuna scoperta critica come lookahead nascosto emerso solo live, oppure data leakage da provider.
|
||||
5. **Cost economy**: edge dopo costi reali (slippage effettivo, fees, funding) resta positivo.
|
||||
|
||||
**Esiti possibili**:
|
||||
|
||||
- Hard gate e soft gate tutti passati → GO Filone A con confidence empirica forte. Si apre la ridiscussione del budget e della roadmap del sistema completo, fuori dal perimetro di questo documento.
|
||||
- Hard gate passati, soft gate falliti → iterazione Phase 2 mirata sulle debolezze identificate, no Filone A immediato.
|
||||
- Hard gate falliti, ma senza catastrofi → l'idea regge concettualmente ma non scala live retail. Decision memo con due opzioni: pivot dominio (offerte Tielogic, code review) oppure chiusura del progetto.
|
||||
- Hard gate falliti più drawdown catastrofico → l'idea non regge live. Stop del progetto. Documento di chiusura con learnings registrati per progetti futuri.
|
||||
|
||||
### 6.5 Deliverable Phase 3 e finali
|
||||
|
||||
- Report finale del PoC (~20-30 pagine): metodologia completa, risultati Phase 1+2+3, comparison Sharpe in-sample / OOS / live, ispezione qualitativa delle strategie, learnings, threats to validity confermati o respinti.
|
||||
- Decision memo strategico: GO Filone A / iterate Phase 2 / pivot dominio / stop, con razionale quantitativo ancorato ai gate.
|
||||
- Codebase pubblicabile (anche se repo privato): backtest, GA, Cerbero integration, speciation, DSR pipeline, RF baseline, monitoring dashboard, tutto documentato.
|
||||
|
||||
---
|
||||
|
||||
## 7. GUI Streamlit incrementale
|
||||
|
||||
Una dashboard è essenziale per ispezionare cosa fa il sistema, decidere i gate in modo informato, e produrre i grafici che entreranno nei report.
|
||||
|
||||
### 7.1 Architettura
|
||||
|
||||
- Tech stack: Streamlit single-app multi-page, dati letti da SQLite locale (`runs.db`) e Parquet per series numerici pesanti.
|
||||
- SQLite per stato: genomi, generazioni, fitness, ablation results, adversarial findings, trade log. Schema relazionale stabile, query veloci, nessun DB server da gestire.
|
||||
- Parquet per series: equity curves, signal time series, OHLCV. File-based, columnar, leggero.
|
||||
- Auto-refresh ogni 10 secondi nella sola pagina Live Monitor (Phase 3). Sufficiente per uno scope a decisioni minute-level, non HFT.
|
||||
- Single app, multipage: tutto sotto `dashboard/streamlit_app.py` più `pages/`. Deploy locale con `streamlit run`, niente VPS frontend.
|
||||
|
||||
### 7.2 Pagine costruite incrementalmente
|
||||
|
||||
**Phase 1 (3-4 giorni di lavoro)**:
|
||||
|
||||
- *Overview*: ultima run, generazione corrente, stato (running/completed/failed), spesa LLM cumulata vs cap.
|
||||
- *GA Convergence*: line plot fitness mediana / max / 90° percentile per generazione, distribuzione fitness ultima generazione (histogram), counter chiamate LLM e costo.
|
||||
- *Genomes (basic)*: tabella top-10 genomi correnti con DSR, cognitive_style, temperature, lookback. Click su riga apre side panel con system_prompt completo, feature_access, lineage parent_id.
|
||||
|
||||
**Phase 2 (9-12 giorni distribuiti)**:
|
||||
|
||||
- *Genomes (avanzato)*: lineage tree interattivo (parent → children su 15 generazioni), speciation cluster view (UMAP/t-SNE su prompt embedding più parametri, colori per specie), filtri per specie / tier / cognitive_style.
|
||||
- *Performance*: equity curve in-sample, walk-forward, OOS, hold-out per ogni genoma. Sharpe, DSR, drawdown. Trade distribution per regime di volatilità, asset, orario. Per-strategy e portfolio view.
|
||||
- *Ablation*: confronto runs (tier C only, tier B only, mix) side-by-side. Δ Sharpe OOS, costo per percentile fitness, breakeven analysis.
|
||||
- *Adversarial*: per ogni top-10 genoma, le cinque critiche (data snooping, lookahead, regime fragility, crowding, transaction cost). Click espande prompt completo, risposta LLM, decisione pass/fail con rationale.
|
||||
- *RF Baseline*: Sharpe RF baseline OOS, feature importance, comparison vs top-3 swarm, Cohen's d effect size.
|
||||
|
||||
**Phase 3 (4-5 giorni)**:
|
||||
|
||||
- *Live Monitor*: P&L realtime per strategia e portfolio, equity curve da inizio Phase 3, drawdown rolling. Auto-refresh 10s.
|
||||
- *Live vs OOS*: Sharpe live realized vs Sharpe OOS atteso (con confidence interval), gauge "edge sopravvive?", cumulative deviation tracker.
|
||||
- *Triggers state*: stato kill-switch per strategia, distance from threshold, history pause/resume, audit log decisioni recenti.
|
||||
- *Adversarial weekly*: report settimanale di regime drift detection, diff vs settimana precedente.
|
||||
|
||||
### 7.3 Costi GUI
|
||||
|
||||
- Effort totale: 16-21 giorni netti, distribuiti come da fasi sopra.
|
||||
- LLM: zero (codice e visualizzazione Python locale).
|
||||
- Infra: zero (esecuzione locale, SQLite locale, Parquet locale).
|
||||
|
||||
### 7.4 Trade-off accettati
|
||||
|
||||
- Refresh non realtime sub-secondo: accettabile per scope decisionale minute/hour.
|
||||
- Niente login né multi-utente: dashboard personale solo locale.
|
||||
- Niente alert push esterni in default: alert appaiono in dashboard. Per Phase 3 si può aggiungere webhook Telegram/email se necessario, mezza giornata extra di lavoro.
|
||||
- Streamlit re-runs full-page on interaction: gestibile con `@st.cache_data` su query SQLite e dataset PoC di dimensioni contenute.
|
||||
|
||||
### 7.5 Deliverable GUI
|
||||
|
||||
- Codice `dashboard/` testato (smoke test e data layer test).
|
||||
- Schema SQLite versionato con migrazioni semplici (Alembic light o script SQL).
|
||||
- README con istruzioni `streamlit run` e descrizione di ogni pagina.
|
||||
|
||||
---
|
||||
|
||||
## 8. Hardware e infrastruttura
|
||||
|
||||
Principio: tutto locale più Cerbero come unico servizio remoto. Nessuna GPU, nessun cloud compute, nessun costo infra ricorrente significativo.
|
||||
|
||||
### 8.1 Compute
|
||||
|
||||
- Backtest e GA loop: PC locale Linux. Tutto CPU-bound, parallelizzabile su core (joblib o multiprocessing). Dataset 2 anni OHLCV 1h BTC+ETH circa 30-50 MB. Anche granularità 1m sarebbe sotto i 2 GB, gestibile.
|
||||
- LLM: tutte chiamate via API esterne (OpenRouter per tier C, Anthropic per tier B). Nessun model locale, nessuna GPU.
|
||||
- Streamlit dashboard: locale (`streamlit run` su `localhost:8501`).
|
||||
|
||||
### 8.2 Cerbero_mcp
|
||||
|
||||
Già configurato e operativo. Modalità d'uso durante PoC:
|
||||
|
||||
- Locale via Docker compose durante development e Phase 1-2 (testnet only). Riduce latenza, niente costi VPS, debug più rapido.
|
||||
- VPS Hostinger durante Phase 3 forward-test (mainnet). Già setup `/opt/cerbero-mcp` con `deploy-vps.sh` e branch V2.0.0.
|
||||
- Token bearer: `TESTNET_TOKEN` per Phase 1-2 backtest replay, `MAINNET_TOKEN` solo per Phase 3.
|
||||
- Bot tag dedicato per il PoC (`X-Bot-Tag: swarm-poc-<phase>`). L'audit log Cerbero traccia ogni call separatamente per fase, utile per ricostruzioni post-mortem.
|
||||
|
||||
### 8.3 Storage
|
||||
|
||||
- `runs.db` SQLite per stato GA, genomi, generazioni, fitness, adversarial, trade log. Backup giornaliero su disco esterno o cloud personale.
|
||||
- `series/` Parquet per equity curves, signal time series, OHLCV cache. Versionato fuori da git (Git LFS o cartella esterna trackata in `.gitignore`).
|
||||
- Audit log Cerbero: già JSONL con rotazione 30 giorni (`AUDIT_LOG_BACKUP_DAYS=30`). Per Phase 3 aumentare a 90 giorni per coprire intera fase più post-mortem.
|
||||
|
||||
### 8.4 Networking
|
||||
|
||||
- LLM API via OpenRouter (tier C) e Anthropic (tier B). Nessun setup speciale.
|
||||
- Cerbero locale: porta 9000 default, nessuna esposizione pubblica.
|
||||
- Cerbero VPS: già protetto da Traefik più bearer più allowlist IP. Nessun lavoro extra.
|
||||
|
||||
### 8.5 Costi infra ricorrenti
|
||||
|
||||
- VPS Hostinger: già pagato per altri progetti, costo marginale zero.
|
||||
- Storage backup: trascurabile.
|
||||
- Domini e TLS: già coperti (cerbero-mcp.tielogic.xyz).
|
||||
|
||||
### 8.6 Decisioni hardware non bloccanti
|
||||
|
||||
- Se la Phase 2 ablation richiedesse parallelizzazione massiva (ad esempio cento backtest concorrenti), valutare spot instance AWS o Hetzner. Probabilmente non necessario, le strategie a granularità 1h sono veloci da backtestare anche su CPU desktop.
|
||||
- Backup off-site di `runs.db`: decisione di lifecycle, non bloccante per Phase 1.
|
||||
|
||||
---
|
||||
|
||||
## 9. Cadenza review e disciplina autoriale
|
||||
|
||||
Regola personale dell'autore: mai self-approve, separare author pass da review pass. Applicata sistematicamente ai gate del PoC.
|
||||
|
||||
### 9.1 Cadenza working
|
||||
|
||||
- Daily: lavoro full-time. Self-review informale a fine giornata, una riga nel commit message su cosa è andato e cosa no.
|
||||
- Settimanale (venerdì): review formale con dump strutturato che include fitness convergence plot, top-5 genomi della settimana, spesa LLM accumulata vs cap di fase, eventuali findings Adversarial significativi, aggiornamento di `progress.md`.
|
||||
- Bi-settimanale: snapshot completo più decision check sul fatto se siamo ancora on-track per il gate. Se due bi-weekly consecutivi mostrano off-track materiale, decisione anticipata di pivot o iterate, non attesa fino a fine fase.
|
||||
|
||||
### 9.2 Gate review (decisione formale fine fase)
|
||||
|
||||
Per ognuno dei tre gate (fine Phase 1, fine Phase 2, fine Phase 3): author pass e review pass separati.
|
||||
|
||||
- **Author pass**: l'autore scrive il decision memo con tutti i numeri, gate per gate, conclusione raccomandata.
|
||||
- **Review pass**: secondo passaggio con approccio adversarial. Tre opzioni equivalenti:
|
||||
- Subagent Claude con prompt esplicitamente "red team" che riceve memo più dati grezzi e produce critica strutturata (cherry-picking, debolezze statistiche, omissioni).
|
||||
- Collega umano disponibile, se esiste un contesto Tielogic adatto.
|
||||
- Rilettura dopo 48 ore con timer, fresh eyes pass.
|
||||
- **Sintesi**: solo dopo il review pass la decisione viene formalizzata e committata.
|
||||
|
||||
### 9.3 Decision triggers oggettivi
|
||||
|
||||
I gate hard di ogni fase sono numerici (DSR, p-value, drawdown ratio). La decisione GO/STOP è meccanica sui hard gate:
|
||||
|
||||
- Hard gate fallito → STOP automatico, non in discussione.
|
||||
- Hard gate passato → si valuta se i soft gate danno motivo di iterazione invece di procedere.
|
||||
|
||||
Questa è la disciplina Renaissance applicata al progetto: niente "magari un'altra generazione" se il numero non lo dice.
|
||||
|
||||
### 9.4 Documentazione del processo
|
||||
|
||||
- `docs/runs/YYYY-MM-DD-phase{1,2,3}-run-N.md` per ogni run completato: configurazione, risultati, anomalie, learning.
|
||||
- `docs/decisions/YYYY-MM-DD-gate-phase{1,2,3}.md` per ogni decisione gate: author pass, review pass, decisione finale, razionale.
|
||||
- Questo documento (`docs/superpowers/specs/2026-05-09-decisione-strategica-design.md`) come north star strategico, aggiornato se cambiano vincoli o decisioni macro.
|
||||
|
||||
---
|
||||
|
||||
## 10. Catena di ragionamento (tracciabilità)
|
||||
|
||||
Per riferimento futuro, la sequenza logica che ha portato al design B3:
|
||||
|
||||
1. Obiettivo dichiarato sistema produttivo che generi valore → esclude C (research dive senza output produttivo).
|
||||
2. Dominio iniziale trading crypto → allinea il PoC al design già scritto in `poc_trading_swarm.md`.
|
||||
3. Tempo full-time disponibile → scioglie il vincolo "non posso costruire infrastruttura", apre spazio per fasi sequenziali.
|
||||
4. Budget LLM tight ($1-2K) → esclude A (Filone completo) e impone disciplina su B.
|
||||
5. Setup Cerbero_mcp esistente → riduce settimane di plumbing, gli agenti chiamano tool MCP nativi.
|
||||
6. Forward-test mainnet con capitale piccolo come parte del successo → richiede una Phase 3 dedicata, distinta dalla validazione statistica.
|
||||
7. Disciplina "spegnere ciò che non funziona" → struttura tripartita con kill-switch numerici.
|
||||
8. Mai self-approve → separazione author pass / review pass nei gate.
|
||||
9. Necessità di ispezionare cosa fa il sistema → GUI Streamlit come componente orizzontale, non opzionale.
|
||||
|
||||
---
|
||||
|
||||
## 11. Decisioni risolte e decisioni ancora aperte
|
||||
|
||||
### 11.1 Risolte da questo documento
|
||||
|
||||
- Opzione strategica: B3.
|
||||
- Dominio iniziale: trading derivati crypto BTC/ETH.
|
||||
- Numero di fasi: tre, con gate go/no-go fra una e l'altra.
|
||||
- Budget cap globale: $2.200 LLM più $500-2.000 capitale a rischio Phase 3.
|
||||
- Cap calendario: 18 settimane.
|
||||
- Tier mix: solo C in Phase 1, mix B/C in Phase 2-3.
|
||||
- Tech stack GUI: Streamlit più SQLite più Parquet.
|
||||
- Infrastruttura: locale più Cerbero_mcp esistente.
|
||||
- Cadenza review: settimanale, bi-settimanale per check, gate con author/review pass separati.
|
||||
|
||||
### 11.2 Aperte (non bloccanti per Phase 1)
|
||||
|
||||
- Allocazione capitale Phase 3 (equal weight vs risk-parity): decisione formalizzata nel decision memo Phase 2 sulla base dei risultati OOS.
|
||||
- Exchange Phase 3 (Bybit vs Hyperliquid): scelta dipendente dalle strategie selezionate, decisa nel decision memo Phase 2.
|
||||
- Approccio review pass (subagent vs umano vs fresh eyes): decisione tattica per gate, nessun lock-in.
|
||||
- Webhook alert Telegram/email per Phase 3: opzionale, decidibile a inizio Phase 3.
|
||||
|
||||
### 11.3 Esplicitamente fuori scope
|
||||
|
||||
- Filone A (sistema completo) come fase corrente. Decisione su A presa solo dopo decision memo finale Phase 3.
|
||||
- Filone C (applicazioni non-trading: offerte Tielogic, code review, doc Swagger). Possibile pivot in caso di hard gate falliti, non azione preventiva.
|
||||
- Co-evolution del protocollo. Nessuna delle tre fasi PoC la include.
|
||||
- Capital scaling oltre $2.000 in Phase 3. Decisione di scaling appartiene a una fase successiva al PoC.
|
||||
|
||||
---
|
||||
|
||||
## 12. Prossimi passi
|
||||
|
||||
Esecuzione di Phase 1.
|
||||
|
||||
Il piano implementativo dettagliato di Phase 1 (settimana per settimana, task atomici, dipendenze, verifiche) sarà oggetto del documento successivo, da costruire con l'invocazione dello skill `writing-plans` su questo design.
|
||||
|
||||
---
|
||||
|
||||
*Documento approvato il 9 maggio 2026. Versione 1.0. Aggiornare in caso di modifica dei vincoli operativi o di esiti di gate che richiedano revisione strategica complessiva.*
|
||||
@@ -1,183 +0,0 @@
|
||||
# Feature temporali nella grammatica Hypothesis — Design
|
||||
|
||||
**Data**: 11 maggio 2026
|
||||
**Status**: design approvato dall'operatore, pronto per writing-plans
|
||||
**Scope target**: Phase 2
|
||||
**Riferimenti**: `docs/decisions/2026-05-11-phase1-5-nemotron-run.md` (memo che ha originato la discussione)
|
||||
|
||||
---
|
||||
|
||||
## 1. Motivazione
|
||||
|
||||
Le strategie LLM-generate da Phase 1 operano in modo time-blind: la grammatica espone solo OHLCV (`open`, `high`, `low`, `close`, `volume`) e indicatori tecnici (`sma`, `rsi`, `atr`, `macd`, `realized_vol`) calcolati sopra. Non esiste alcuna feature che permetta al genoma di condizionare il comportamento sull'orario o sul giorno della settimana.
|
||||
|
||||
Questo è un limite strutturale rispetto a BTC-PERPETUAL su Cerbero, dove esistono effetti temporali sistematici:
|
||||
|
||||
- apertura USA (14:30 UTC) e Europa (08:00 UTC) generano volatilità sistematica;
|
||||
- apertura/chiusura settimanale crypto (Sabato/Domenica vs. resto della settimana) ha liquidità diversa e basis funding diverso;
|
||||
- la sessione asiatica overnight presenta pattern di trend reversal noti.
|
||||
|
||||
Il design seguente aggiunge alla grammatica quattro feature temporali — `hour`, `dow`, `is_weekend`, `minute_of_hour` — universalmente accessibili a ogni genoma, lasciando inalterati i meccanismi di mutation/crossover esistenti.
|
||||
|
||||
---
|
||||
|
||||
## 2. Decisioni di design
|
||||
|
||||
Le seguenti scelte sono state ratificate in fase di brainstorming.
|
||||
|
||||
**Quattro feature, non una.** `hour` da sola coprirebbe l'80% dei casi, ma `dow` cattura un asse ortogonale (weekend effect) e `is_weekend` è una scorciatoia espressiva utile al LLM. `minute_of_hour` è incluso per disponibilità futura (timeframe 5m/15m in Phase 2+), inerte sui dati 1h attuali.
|
||||
|
||||
**Accesso universale, non soggetto a `feature_access`.** Le feature temporali sono sempre disponibili a ogni genoma, indipendentemente dal subset OHLCV randomizzato in `ga/initial.py` e mutato da `mutate_feature_access`. Motivo: vogliamo che ogni genoma possa testarle; passarle attraverso `FEATURE_POOL` rischia di lasciarle inutilizzate in metà della popolazione e vanificare l'esperimento. Il prompt indica esplicitamente che sono "sempre accessibili", separate dalla sezione `{feature_access}` del template.
|
||||
|
||||
**Riuso di `FeatureNode`, niente nuovo tipo AST.** Le feature temporali entrano nella stessa whitelist `KNOWN_FEATURES` di OHLCV e usano la stessa shape JSON `{"kind": "feature", "name": "..."}`. Il dispatcher in `compiler.py` discrimina per nome. Alternativa scartata: introdurre `TimeFeatureNode` separato. Avrebbe dato type-safety formale ma richiesto modifiche a parser, validator, JSON shape, prompt — costo eccessivo per beneficio puramente strutturale, dato che semanticamente "ora del giorno" e "prezzo close" sono entrambi attributi della riga.
|
||||
|
||||
**Few-shot examples nel prompt.** L'istruzione minimale (solo nomi) lascia troppo spazio a interpretazioni errate (es. `dow=7` per domenica all'italiana, `hour` in fuso locale invece che UTC). Due esempi concreti — un gating intraday `gt hour 14 AND lt hour 22`, un gating settimanale `eq is_weekend 1` — fissano la semantica al costo di ~200 token addizionali per call.
|
||||
|
||||
**Out-of-range non è errore di validazione.** Il LLM potrebbe emettere `gt hour 25` o `eq dow 7`. Il validator non li intercetta: tecnicamente sono `LiteralNode(value=...)` numerici legali. La condizione sarà semplicemente sempre falsa e l'Adversarial layer (`flat_too_long`, `no_trades`) sanzionerà i genomi che ne sono dipendenti. Aggiungere un check range esplicito sarebbe over-engineering per un caso che il sistema già gestisce.
|
||||
|
||||
---
|
||||
|
||||
## 3. Architettura — modifiche file-by-file
|
||||
|
||||
Cinque file toccati. Nessun nuovo modulo.
|
||||
|
||||
### `src/multi_swarm/protocol/grammar.py`
|
||||
|
||||
Estendere `KNOWN_FEATURES` da 5 a 9 nomi:
|
||||
|
||||
```python
|
||||
KNOWN_FEATURES: frozenset[str] = frozenset(
|
||||
{"open", "high", "low", "close", "volume",
|
||||
"hour", "dow", "is_weekend", "minute_of_hour"}
|
||||
)
|
||||
```
|
||||
|
||||
Nessun'altra modifica al file. Il validator legge da qui automaticamente.
|
||||
|
||||
### `src/multi_swarm/protocol/compiler.py`
|
||||
|
||||
Aggiungere un dizionario di derivazioni temporali ed estendere il dispatcher di `FeatureNode` con un branch prioritario:
|
||||
|
||||
```python
|
||||
_TIME_FEATURE_FNS: dict[str, Callable[[pd.DatetimeIndex], pd.Series]] = {
|
||||
"hour": lambda idx: pd.Series(idx.hour, index=idx, dtype="int64"),
|
||||
"dow": lambda idx: pd.Series(idx.dayofweek, index=idx, dtype="int64"),
|
||||
"is_weekend": lambda idx: pd.Series((idx.dayofweek >= 5).astype("int64"), index=idx),
|
||||
"minute_of_hour": lambda idx: pd.Series(idx.minute, index=idx, dtype="int64"),
|
||||
}
|
||||
|
||||
# nel branch FeatureNode di _eval_node:
|
||||
if isinstance(node, FeatureNode):
|
||||
if node.name in _TIME_FEATURE_FNS:
|
||||
return _TIME_FEATURE_FNS[node.name](df.index)
|
||||
return df[node.name]
|
||||
```
|
||||
|
||||
Il branch OHLCV preesistente (`return df[node.name]`) resta invariato come fallback per i nomi non temporali. Si assume `df.index` di tipo `DatetimeIndex` UTC, già garantito da `CerberoOHLCVLoader`.
|
||||
|
||||
### `src/multi_swarm/agents/hypothesis.py`
|
||||
|
||||
Aggiungere nel prompt template, dopo la sezione "Leaf - feature OHLCV" (intorno a riga 84), una sezione "Leaf - feature TEMPORALI" con i quattro nomi, i loro range, e due esempi few-shot completi (gating sessione US, gating weekend). Mantenere la sezione separata da `{feature_access}` e dichiarare esplicitamente che le feature temporali sono "sempre accessibili". Contenuto preciso definito nella sezione 5 di questo spec.
|
||||
|
||||
### `tests/protocol/test_compiler.py`
|
||||
|
||||
Cinque test nuovi:
|
||||
|
||||
1. `test_compile_hour_feature_returns_index_hour` — DataFrame 24-bar con index orario, `FeatureNode("hour")` restituisce serie `[0,1,...,23]`.
|
||||
2. `test_compile_dow_feature_lunedi_is_zero` — verifica convenzione pandas (lunedì → 0, domenica → 6).
|
||||
3. `test_compile_is_weekend_returns_zero_one` — sabato e domenica → 1, altri → 0.
|
||||
4. `test_compile_minute_of_hour_zero_on_1h_timeframe` — su index 1h tutti gli output sono 0 (test di regressione del comportamento atteso).
|
||||
5. `test_rule_with_temporal_gating_compiles_and_executes` — integrazione: regola `entry-long if hour > 14 AND close > sma(20)`, verifica che `Side.LONG` appaia solo nelle bar con `hour > 14`.
|
||||
|
||||
### `tests/protocol/test_validator.py`
|
||||
|
||||
Due test nuovi:
|
||||
|
||||
1. `test_validator_accepts_temporal_features` — i quattro nuovi nomi non sollevano `ValidationError`.
|
||||
2. `test_validator_rejects_temporal_typo` — `FeatureNode("weekday")` solleva `ValidationError`.
|
||||
|
||||
Test esistenti non devono cambiare. L'aggiunta è puramente additiva.
|
||||
|
||||
---
|
||||
|
||||
## 4. Contratto delle feature
|
||||
|
||||
| Feature | Tipo | Range | Derivazione pandas |
|
||||
|---------|------|-------|---------------------|
|
||||
| `hour` | int64 | 0–23 | `df.index.hour` |
|
||||
| `dow` | int64 | 0–6 (lun=0) | `df.index.dayofweek` |
|
||||
| `is_weekend` | int64 | 0 o 1 | `(df.index.dayofweek >= 5).astype(int)` |
|
||||
| `minute_of_hour` | int64 | 0–59 | `df.index.minute` |
|
||||
|
||||
L'indice del DataFrame è UTC tz-aware per costruzione (`CerberoOHLCVLoader`). I valori temporali sono quindi in UTC, non in fuso locale italiano. Questa scelta è coerente con la convenzione di prezzi e timestamp del progetto e con la natura globale del mercato crypto.
|
||||
|
||||
I confronti tipici emessi dal LLM saranno della forma `{"op": "gt", "args": [{"kind": "feature", "name": "hour"}, {"kind": "literal", "value": 14}]}`. Funzionano via broadcasting numpy senza modifiche a comparator o operator nodes.
|
||||
|
||||
---
|
||||
|
||||
## 5. Frammento di prompt aggiunto
|
||||
|
||||
Da inserire in `hypothesis.py` dopo l'attuale sezione "Leaf - feature OHLCV":
|
||||
|
||||
```text
|
||||
Leaf - feature TEMPORALI (sempre accessibili, UTC):
|
||||
{{"kind": "feature", "name": "hour"}} range 0-23
|
||||
{{"kind": "feature", "name": "dow"}} range 0-6 (lun=0, dom=6)
|
||||
{{"kind": "feature", "name": "is_weekend"}} 0 o 1
|
||||
{{"kind": "feature", "name": "minute_of_hour"}} range 0-59
|
||||
|
||||
Esempi di gating temporale:
|
||||
// Solo durante la sessione US (14:00-22:00 UTC)
|
||||
{{"op": "and", "args": [
|
||||
{{"op": "gt", "args": [{{"kind": "feature", "name": "hour"}}, {{"kind": "literal", "value": 14}}]}},
|
||||
{{"op": "lt", "args": [{{"kind": "feature", "name": "hour"}}, {{"kind": "literal", "value": 22}}]}}
|
||||
]}}
|
||||
|
||||
// Solo nel weekend (sab+dom)
|
||||
{{"op": "eq", "args": [{{"kind": "feature", "name": "is_weekend"}}, {{"kind": "literal", "value": 1}}]}}
|
||||
```
|
||||
|
||||
Il blocco va inserito **prima** della frase corrente "Feature accessibili dal tuo genoma: {feature_access}", per chiarire che `{feature_access}` riguarda solo OHLCV mentre le temporali sono universali.
|
||||
|
||||
---
|
||||
|
||||
## 6. Backward compatibility e impatto sui run esistenti
|
||||
|
||||
Tutti i genomi esistenti nei `runs.db` storici (Phase 1, Phase 1.5 nemotron, Phase 1.5 grok in corso) usano solo feature OHLCV. Con la grammatica estesa restano validi: il validator continua ad accettarli, il compiler li gestisce nel branch OHLCV invariato.
|
||||
|
||||
Non c'è quindi alcuna migrazione di dati. I run vecchi possono essere ri-letti dalla dashboard senza modifiche. La distinzione "run pre/post feature temporali" sarà tracciata implicitamente dalla data del commit di merge.
|
||||
|
||||
---
|
||||
|
||||
## 7. Validazione end-to-end
|
||||
|
||||
Dopo il merge dei cinque file, la procedura di validazione è:
|
||||
|
||||
1. Esecuzione test suite completa (`uv run pytest`) — i 7 nuovi test devono passare, nessun test esistente deve rompersi.
|
||||
2. `scripts/smoke_run.py` con `population_size=4, n_generations=1` per verificare che il loop end-to-end completi (caricamento OHLCV → generazione genome → compile → backtest → DSR → adversarial → persistenza). Tempo atteso ~2 minuti.
|
||||
3. Ispezione manuale di almeno 1 genoma generato post-merge: verificare che il LLM abbia effettivamente usato almeno una feature temporale tra le sue regole. Se in 4 genomi nessuno usa feature temporali, ri-esaminare il prompt.
|
||||
|
||||
Non è previsto un confronto ablation formale (con/senza feature temporali) in questo spec — è un'attività di Phase 2 separata che andrà pianificata in un proprio spec quando si avvierà il run di valutazione.
|
||||
|
||||
---
|
||||
|
||||
## 8. Out of scope
|
||||
|
||||
I seguenti elementi sono esplicitamente fuori dallo scope di questo spec e dovranno essere oggetto di design dedicato se desiderati:
|
||||
|
||||
- **Feature temporali con segno periodico** (es. `sin_hour`, `cos_dow`): utili per regressioni continue, non per regole booleane GA-based. Skip.
|
||||
- **Feature di sessione discreta** (es. `session=us|europe|asia`): derivabili componendo `hour` con comparator, non necessario aggiungere come feature primitiva.
|
||||
- **Time-zone configurabile**: rimane fissa UTC. Cambiare implica refactor del loader OHLCV.
|
||||
- **Validator range-check** (es. rifiutare `gt(dow, 6)`): sanzionato già dal loop GA via fitness e Adversarial.
|
||||
- **Modifica del meccanismo `mutate_feature_access`**: invariato. Le feature temporali non entrano nel pool mutabile.
|
||||
- **Indicatori temporali** (es. `time_since_last_high`): richiede stato persistente, fuori dal modello stateless attuale.
|
||||
|
||||
---
|
||||
|
||||
## 9. Stima di sforzo
|
||||
|
||||
Implementazione: ~120 LOC (60 di codice + 60 di test) in 5 file. Complessità bassa.
|
||||
|
||||
TDD-driven: scrivere prima i 7 test, verificare che falliscano, poi aggiungere whitelist + dispatcher + prompt. Tempo stimato: 2-3 ore di lavoro continuo, validation smoke run inclusa.
|
||||
|
||||
Costo prompt addizionale per call: ~200 token. Su un run da 200 call, ~40k token aggiuntivi → impatto economico trascurabile (<$0.05 con qualsiasi tier).
|
||||
Reference in New Issue
Block a user