From c6cb32325e625f4b9d8b7edc06e7ba7d17032a77 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Mon, 11 May 2026 16:52:28 +0200 Subject: [PATCH] docs: design spec feature temporali Phase 2 Aggiunge hour/dow/is_weekend/minute_of_hour come FeatureNode nella grammatica esistente. Universal access (non passa da feature_access), riuso di FeatureNode (no nuovo tipo AST), few-shot examples nel prompt Hypothesis. Cinque file toccati, ~120 LOC, backward-compatible. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-11-temporal-features-design.md | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-11-temporal-features-design.md diff --git a/docs/superpowers/specs/2026-05-11-temporal-features-design.md b/docs/superpowers/specs/2026-05-11-temporal-features-design.md new file mode 100644 index 0000000..4545ed0 --- /dev/null +++ b/docs/superpowers/specs/2026-05-11-temporal-features-design.md @@ -0,0 +1,183 @@ +# 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).