Files
Multi_Swarm_Coevolutive/docs/superpowers/specs/2026-05-11-temporal-features-design.md
T
Adriano c6cb32325e docs: design spec feature temporali Phase 2
Aggiunge hour/dow/is_weekend/minute_of_hour come FeatureNode nella
grammatica esistente. Universal access (non passa da feature_access),
riuso di FeatureNode (no nuovo tipo AST), few-shot examples nel prompt
Hypothesis. Cinque file toccati, ~120 LOC, backward-compatible.

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

11 KiB
Raw Blame History

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:

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:

_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_typoFeatureNode("weekday") solleva ValidationError.

Test esistenti non devono cambiare. L'aggiunta è puramente additiva.


4. Contratto delle feature

Feature Tipo Range Derivazione pandas
hour int64 023 df.index.hour
dow int64 06 (lun=0) df.index.dayofweek
is_weekend int64 0 o 1 (df.index.dayofweek >= 5).astype(int)
minute_of_hour int64 059 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":

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).