# 10 — Config Spec (`strategy.yaml`)

Tutti i parametri della strategia sono esposti in un file YAML
versionato. Modificare il file = decisione esplicita di Adriano.
Modifiche richiedono nuovo `config_version`, ricalcolo `config_hash`
e commit con motivazione.

## Schema completo (golden default)

```yaml
# strategy.yaml — Cerberus Bite golden config v1.0.0
# Riferimento: docs/01-strategy-rules.md
# NB: hash auto-calcolato dal CLI `cerbero-bite config hash`

config_version: "1.0.0"
config_hash: "0000000000000000000000000000000000000000000000000000000000000000"  # placeholder
last_review: "2026-04-26"
last_reviewer: "Adriano"

asset:
  symbol: "ETH"
  exchange: "deribit"

# === ENTRY ===

entry:
  # finestra di valutazione giornaliera (crypto 24/7)
  cron: "0 14 * * *"            # ogni giorno 14:00 UTC
  skip_holidays_country: "IT"

  # filtri di accesso (vedi 01-strategy-rules.md §2)
  capital_min_usd: 720
  dvol_min: 35
  dvol_max: 90
  funding_perp_abs_max_annualized: 0.80
  eth_holdings_pct_max: 0.30
  no_position_concurrent: true   # 1 sola posizione alla volta
  exclude_macro_severity: ["high"]
  exclude_macro_countries: ["US", "EU"]

  # bias direzionale (§3.1)
  trend_window_days: 30
  trend_bull_threshold_pct: 0.05
  trend_bear_threshold_pct: -0.05
  funding_bull_threshold_annualized: 0.20
  funding_bear_threshold_annualized: -0.20
  iron_condor_dvol_min: 55
  iron_condor_adx_max: 20
  iron_condor_trend_neutral_band_pct: 0.05

# === STRUCTURE ===

structure:
  dte_target: 18
  dte_min: 14
  dte_max: 21

  short_strike:
    delta_target: 0.12
    delta_min: 0.10
    delta_max: 0.15
    distance_otm_pct_min: 0.15
    distance_otm_pct_max: 0.25

  spread_width:
    target_pct_of_spot: 0.04
    min_pct_of_spot: 0.03
    max_pct_of_spot: 0.05

  credit_to_width_ratio_min: 0.30

# === LIQUIDITY GATE ===

liquidity:
  open_interest_min: 100
  volume_24h_min: 20
  bid_ask_spread_pct_max: 0.15
  book_depth_top3_min: 5
  slippage_pct_of_credit_max: 0.08

# === SIZING ===

sizing:
  kelly_fraction: 0.13          # Quarter Kelly basato su MC

  # cap nominali (Cerbero v4 hard prohibitions)
  cap_per_trade_eur: 200
  cap_aggregate_open_eur: 1000
  max_concurrent_positions: 1   # da 2+ solo via override esplicito

  # soft cap difensivi (interni a Cerbero Bite)
  max_contracts_per_trade: 4

  # aggiustamento volatilità
  dvol_adjustment:
    - {dvol_under: 45, multiplier: 1.00}
    - {dvol_under: 60, multiplier: 0.85}
    - {dvol_under: 80, multiplier: 0.65}
    # >= 80 → no entry (gestito da entry filters)

# === EXIT ===

exit:
  # ordine di valutazione vincolante
  profit_take_pct_of_credit: 0.50
  stop_loss_mark_x_credit: 2.50    # mark = 250% credito → perdita 1.5×
  vol_stop_dvol_increase: 10
  time_stop_dte_remaining: 7
  time_stop_skip_if_close_to_profit_pct: 0.70  # skip se mark ≤ 70% di profit_take
  delta_breach_threshold: 0.30
  adverse_move_4h_pct: 0.05

  monitor_cron: "0 2,14 * * *"     # 02 + 14 UTC
  user_confirmation_timeout_min: 30

  # escalation per urgenza alta
  escalate_on_timeout:
    - "CLOSE_STOP"
    - "CLOSE_VOL"
    - "CLOSE_DELTA"

# === EXECUTION ===

execution:
  combo_only: true                  # mai due ordini separati
  initial_limit: "mid"
  reprice_step_ticks: 1
  reprice_max_steps: 3
  reprice_max_steps_urgent: 5       # per chiusure urgenti
  order_tif: "GTC"
  order_expiry_min: 30
  ack_timeout_s: 300                # ack Cerbero core

# === MONITORING ===

monitoring:
  health_check_interval_s: 300
  health_failures_before_kill: 3
  health_failures_before_restart: 5

  daily_digest_cron: "0 8 * * *"
  monthly_report_cron: "0 12 1 * *"

# === STORAGE ===

storage:
  sqlite_path: "data/state.sqlite"
  log_path: "data/log/"
  log_retention_days: 365
  backup_path: "data/backups/"
  backup_retention_days: 30

# === MCP ===

mcp:
  config_file: "~/.config/cerbero-suite/mcp.json"
  call_timeout_s: 8
  retry_max: 3
  retry_base_delay_s: 1

  required_versions:
    cerbero-deribit: "^2.0.0"
    cerbero-hyperliquid: "^1.5.0"
    cerbero-memory: "^4.0.0"
    cerbero-portfolio: "^1.2.0"
    cerbero-macro: "^1.0.0"
    cerbero-sentiment: "^1.0.0"
    cerbero-telegram: "^1.0.0"
    cerbero-brain-bridge: "^1.0.0"

# === TELEGRAM ===

telegram:
  parse_mode: "MarkdownV2"
  confirmation_timeout_min: 60
  exit_confirmation_timeout_min: 30
  backup_channel_on_critical: true   # canale BotPapà su severity critical

# === KELLY RECALIBRATION ===

kelly_recalibration:
  lookback_days: 365
  min_sample_low_confidence: 30
  min_sample_high_confidence: 100
  weight_when_medium_confidence: 0.50
```

## Validazione (`config/schema.py`)

```python
from pydantic import BaseModel, Field, model_validator
from decimal import Decimal

class StrategyConfig(BaseModel):
    config_version: str
    config_hash: str
    last_review: str
    last_reviewer: str
    asset: AssetConfig
    entry: EntryConfig
    structure: StructureConfig
    liquidity: LiquidityConfig
    sizing: SizingConfig
    exit: ExitConfig
    execution: ExecutionConfig
    monitoring: MonitoringConfig
    storage: StorageConfig
    mcp: McpConfig
    telegram: TelegramConfig
    kelly_recalibration: KellyConfig

    @model_validator(mode="after")
    def validate_consistency(self) -> "StrategyConfig":
        # short delta range coerente
        s = self.structure.short_strike
        if not (s.delta_min <= s.delta_target <= s.delta_max):
            raise ValueError("delta_target outside [delta_min, delta_max]")
        # OTM range coerente
        if s.distance_otm_pct_min >= s.distance_otm_pct_max:
            raise ValueError("OTM range invalid")
        # Kelly range
        if not (0 < self.sizing.kelly_fraction < 0.5):
            raise ValueError("kelly_fraction out of safe range (0, 0.5)")
        # spread width range
        sw = self.structure.spread_width
        if not (sw.min_pct_of_spot <= sw.target_pct_of_spot <= sw.max_pct_of_spot):
            raise ValueError("spread_width target outside min/max")
        # exit thresholds sensati
        if self.exit.profit_take_pct_of_credit >= 1.0:
            raise ValueError("profit_take >= 100% impossible")
        if self.exit.stop_loss_mark_x_credit <= 1.0:
            raise ValueError("stop_loss multiple <= 1× makes no sense")
        return self
```

## Override locale

Se esiste `strategy.local.yaml` (gitignored), i suoi campi sovrascrivono
`strategy.yaml`. Usato per:

- Test in dry-run con cap dimezzati
- Personalizzazioni dello sviluppatore senza inquinare il main
- Override emergenza ("max_concurrent_positions: 0" per congelare entry
  senza disabilitare l'engine)

L'override viene loggato all'avvio:

```
2026-04-27T10:00:00Z | OVERRIDE_APPLIED | sizing.cap_per_trade_eur: 200 → 100
```

## Hash integrity

```bash
# Calcolo manuale
cerbero-bite config hash

# Aggiornamento automatico (richiede review prompt)
cerbero-bite config update --reason "Reduce kelly to 0.10 after Apr drawdown"
```

Il comando:
1. Apre il file in editor.
2. All'esci, ricalcola SHA-256 escludendo la riga `config_hash`.
3. Aggiorna `config_hash`, `last_review`, `last_reviewer`.
4. Mostra diff ad Adriano.
5. Firma in audit chain.

Mismatch hash a runtime = kill switch immediato (vedi `07-risk-controls.md`).

## Esempi di modifiche valide

### Riduzione difensiva post-drawdown

```diff
sizing:
-  kelly_fraction: 0.13
+  kelly_fraction: 0.10
```
Commit: `chore(config): reduce kelly to 0.10 after April -20% DD`.

### Aggiunta nuovo evento macro escluso

```diff
entry:
   exclude_macro_severity: ["high"]
+  exclude_macro_keywords: ["Powell", "Lagarde", "ETF approval"]
```
Commit: `chore(config): exclude ETF-related events from entry window`.

### Disabilitazione temporanea iron condor

```diff
entry:
-  iron_condor_dvol_min: 55
+  iron_condor_dvol_min: 999    # de facto disabled
```
Commit: `chore(config): disable IC pending review of vol regime`.

## Cosa NON è in config

Non è permesso parametrizzare:

- L'**ordine** dei trigger di uscita (è una scelta strategica codificata).
- Le **strutture** ammesse (vietato vendita nuda, ecc., scolpito nel codice).
- Le **hard prohibitions** Cerbero v4 (cap 200/1000 EUR sono limiti
  superiori, non ulteriormente liberalizzabili).
- Lo **scheduler** per intervalli più stretti (un'ottimizzazione che
  non si fa via config).

## Variabili d'ambiente

`strategy.yaml` definisce **cosa** fa il bot quando è acceso. Le
variabili d'ambiente in `.env` definiscono **come** si collega al
mondo esterno e **quali interruttori operativi** sono attivi.
Queste vivono fuori da `strategy.yaml` perché cambiano per ambiente
(testnet vs mainnet, soak vs trading) ma non per regola di strategia.

| Variabile | Tipo | Default | Uso |
|---|---|---|---|
| `CERBERO_BITE_MCP_TOKEN` | string (obbligatoria) | — | Bearer token presentato a Cerbero MCP V2. Il valore decide l'ambiente upstream (testnet o mainnet). Cambia il valore = cambia l'ambiente. |
| `CERBERO_BITE_MCP_BOT_TAG` | string ≤ 64 char | `BOT__CERBERO_BITE` | Header `X-Bot-Tag` registrato nell'audit log del server MCP per ogni write. |
| `CERBERO_BITE_MCP_DERIBIT_URL` | URL | gateway pubblico | Override URL router Deribit. |
| `CERBERO_BITE_MCP_HYPERLIQUID_URL` | URL | gateway pubblico | Override URL router Hyperliquid. |
| `CERBERO_BITE_MCP_MACRO_URL` | URL | gateway pubblico | Override URL router Macro. |
| `CERBERO_BITE_MCP_SENTIMENT_URL` | URL | gateway pubblico | Override URL router Sentiment. |
| `CERBERO_BITE_ENABLE_DATA_ANALYSIS` | bool (`true`/`false`) | `true` | Abilita il job `market_snapshot` (raccolta dati MCP ogni 15 min). |
| `CERBERO_BITE_ENABLE_STRATEGY` | bool (`true`/`false`) | `false` | Abilita i job `entry` e `monitor` (esecuzione regole §2-§9). |
| `CERBERO_BITE_TELEGRAM_BOT_TOKEN` | string | — | Token bot Telegram (notify-only). Senza, il client è in modalità disabled. |
| `CERBERO_BITE_TELEGRAM_CHAT_ID` | string | — | Chat ID destinatario notifiche Telegram. |

I valori bool accettano in input `1`/`0`, `true`/`false`, `yes`/`no`,
`on`/`off`, `enabled`/`disabled` (case-insensitive). Qualunque altro
valore fa fallire il boot con `ValueError`.

Vedi `06-operational-flow.md` §"Modalità operativa" per i profili
canonici di `ENABLE_DATA_ANALYSIS` e `ENABLE_STRATEGY`.
