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:
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