feat(runner): gate feed CONGELATO — sospende gli sleeve ETH-leg finche' il feed non si sblocca
Audit "stato ordini": il feed ETH-PERPETUAL congelato a 1661.95 (36h+) generava perdite REALI (SH01_ETH -2.83 reali vs -0.09 sim su un close + riapertura; 4 pairs con gamba ETH entrati su z-score spuri -3/-5/+5.6 = artefatto del log-ratio con ETH pinnato mentre gli alt si muovono). _frozen_assets + _feed_gated_sids: quando il feed di decisione 1h di un asset e' congelato, gli sleeve concentrati (single/ml/pairs) che ne dipendono saltano il tick (entry+exit) finche' non si sblocca (come outage; disaster-SL on-book = coda). Auto-guarente: rilascio alla prima barra COMPLETA non-flat (NON l'entry-guard post-flat bocciata). Detector guasto-vs-illiquido: conta la run di close INVARIATE (ETH/BNB/DOGE run 40-64, 1-4 val/48h = morti; SOL/LTC/ADA run <=12, 5-31 val = vivi). Soglia feed_freeze_gate_bars=24 -> gatea le 9 gambe ETH esatte, PR_BTCLTC e i multi-asset restano attivi. Alert Telegram FEED_FROZEN_GATE GATED/RIPRESO. Test test_freeze_gate.py (6, detector+scope+rilascio). Suite portfolio 140/140. Cerotto testnet: il fix vero e' mainnet. Diario 2026-06-15-frozen-feed-gate.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -598,6 +598,24 @@ long A / short B sullo z-score del log-ratio, fee su 2 gambe).
|
||||
ogni formato (l'endpoint legacy `get_historical` è 404, rimosso). La voce `"30m"` in
|
||||
`runner._SUBHOURLY` era **speculativa, mai testata live** → qualsiasi TF sub-orario oltre 5m/15m
|
||||
va DERIVATO resamplando nel runner (come 4h/1d dal 1h), non fetchato diretto.
|
||||
- **GATE FEED CONGELATO — sleeve ETH-leg sospesi (v1.1.32, 2026-06-15, scelta utente "congela
|
||||
gli sleeve ETH-leg finché il feed non si sblocca").** Audit "stato ordini": il feed ETH congelato
|
||||
stava generando **perdite REALI** (SH01_ETH −2.83 reali vs −0.09 sim su un close, poi riapertura
|
||||
della stessa trappola; 4 pairs con gamba ETH entrati su z-score spuri −3/−5/+5.6 = artefatto del
|
||||
log-ratio con ETH pinnato). Fix: `runner._frozen_assets` + `_feed_gated_sids` — quando il feed di
|
||||
decisione 1h di un asset è CONGELATO, gli sleeve CONCENTRATI (single/ml/pairs) che ne dipendono
|
||||
**saltano il tick** (entry E exit) finché non si sblocca (come un outage; disaster-SL on-book =
|
||||
protezione di coda). **Auto-guarente**: si rilascia alla prima barra COMPLETA non-flat (NON è
|
||||
l'entry-guard post-flat bocciata: quella salta la candela-gap di ripresa, questo no). **Detector
|
||||
guasto-vs-illiquido**: conta la run di close **INVARIATE** (prezzo mai cambiato), NON le barre flat
|
||||
— sul feed reale ETH/BNB/DOGE hanno run 40-64 con 1-4 valori distinti/48h (MORTI) mentre
|
||||
SOL/LTC/ADA sono flat ma VIVI (run ≤12, 5-31 valori/48h: si muovono ogni ~10 barre). Soglia
|
||||
`feed_freeze_gate_bars=24` (1 giorno di prezzo immobile, configurabile, 0=off): gatea le **9 gambe
|
||||
ETH esatte** (3 fade ETH 15m + SH_ETH + 5 pairs con gamba ETH) lasciando attivi BTC-only,
|
||||
**PR_BTCLTC** (BTC vivo + LTC solo illiquido) e i multi-asset PAPER. Le posizioni ETH già aperte
|
||||
NON vengono flattate (freeze=pausa: un close forzato passerebbe per lo stesso feed morto). Alert
|
||||
Telegram `FEED_FROZEN_GATE` GATED/RIPRESO. Test `tests/portfolio/test_freeze_gate.py`, diario
|
||||
`docs/diary/2026-06-15-frozen-feed-gate.md`. **Resta un cerotto testnet: il fix vero è mainnet.**
|
||||
- **TIMING SWEEP pairs & honest (2026-06-14, NESSUN deploy).** Domanda utente: pairs/honest
|
||||
beneficiano di TF più veloci come le fade (swap 1h→15m)? Esito: **no, niente deployato.** Vincolo
|
||||
dati (alt 1h-only) → sweepabili solo **pairs ETH/BTC** e **DIP01 (BTC)**; TR01/ROT02 fuori scope
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# 2026-06-15 — Gate del feed CONGELATO (freeze gate)
|
||||
|
||||
## Problema (da "stato ordini")
|
||||
|
||||
Audit degli ordini live: 5 sleeve in posizione reale, **4-5 contaminati dal feed ETH
|
||||
congelato**. `ETH-PERPETUAL` (inverse, il feed di DECISIONE del runner) è fermo a
|
||||
**1661.95 da 36h+** (100% barre flat, 57/57 nel windows 2g, **1 solo valore distinto**),
|
||||
mentre il book reale dove si ESEGUE (`ETH_USDC-PERPETUAL`, lineare) si muove ~1717-1722.
|
||||
|
||||
Effetto misurato sui soldi reali (log container):
|
||||
- **SH01_ETH** ha aperto short a sim 1661.95 / real 1717.10 (slippage 332 bps), chiuso a
|
||||
time_limit con `sim_exit 1661.95` ma `real_fill 1719.21` → **−2.83 USD reali vs −0.09 sim**
|
||||
→ e ha **riaperto subito** lo stesso short sulla trappola.
|
||||
- I 4 pairs con gamba ETH (ETH/BTC 1h+15m, ETH/SOL, LTC/ETH, ADA/ETH) sono entrati con
|
||||
z-score **estremi** (−3.10, −2.96, +5.58, −2.56): artefatto MECCANICO del log-ratio con
|
||||
ETH pinnato a 1661.95 mentre gli alt si muovono → falsa divergenza → carico di spread long-ETH.
|
||||
|
||||
È un GUASTO testnet, non di strategia: in mainnet l'arbitraggio tiene inverse ≈ lineare ≈ realtà.
|
||||
|
||||
## Decisione utente
|
||||
|
||||
> "congela gli sleeve ETH-leg finché il feed non si sblocca"
|
||||
|
||||
## Soluzione: gate del feed congelato nel runner (auto-guarente)
|
||||
|
||||
`src/portfolio/runner.py`: quando il feed di decisione 1h di un asset è **congelato**, gli
|
||||
sleeve CONCENTRATI (single/ml/pairs) che ne dipendono **saltano il tick** (entry E exit)
|
||||
finché il feed non si sblocca. Come un outage (i worker non valutano gli exit, protezione =
|
||||
disaster-SL on-book). Si rilascia DA SOLO alla prima barra completa non-flat.
|
||||
|
||||
### Distinguere il GUASTO dall'ILLIQUIDITÀ (il punto critico)
|
||||
|
||||
Un detector flat-bar ingenuo sarebbe SBAGLIATO: sul feed reale (2026-06-15) gli alt sono
|
||||
flat ma VIVI. Firma misurata (run finale di close invariate / valori distinti in 48h):
|
||||
|
||||
| Asset | run | distinti/48h | natura |
|
||||
|-------|-----|--------------|--------|
|
||||
| ETH | 64 | **1** | MORTO (guasto) |
|
||||
| BNB | 64 | 2 | MORTO (testnet, solo paper) |
|
||||
| DOGE | 42 | 4 | MORTO (testnet, solo paper) |
|
||||
| XRP | 12 | 3 | illiquido |
|
||||
| ADA | 11 | 5 | illiquido ma VIVO |
|
||||
| LTC | 10 | 5 | illiquido ma VIVO |
|
||||
| SOL | 1 | 31 | VIVO |
|
||||
|
||||
→ il detector conta la **run di close INVARIATE** (prezzo che non cambia MAI), non le barre
|
||||
flat. Soglia **24** (`feed_freeze_gate_bars`, configurabile, 0=off): un giorno intero di
|
||||
prezzo immobile separa nettamente le due popolazioni. ETH/BNB/DOGE (run 40-64) gateati,
|
||||
ADA/LTC/XRP/SOL (run ≤12) no. **PR_BTCLTC resta attivo** (BTC vivo + LTC solo illiquido NON
|
||||
deve sospendere il pair: si perderebbero segnali reali sulla gamba BTC).
|
||||
|
||||
### NON è l'entry-guard post-flat (BOCCIATA)
|
||||
|
||||
Si gatea **DURANTE** il freeze (ultima barra completa ferma). La barra di RIPRESA è non-flat
|
||||
→ la run si azzera → il tick riprende SU di essa. NON si salta la candela-gap di ripresa
|
||||
(quella è l'overshoot che la fade fada con profitto — l'entry-guard post-flat è bocciata,
|
||||
CLAUDE.md / 2026-06-05). Rilascio solo su barra **completata** non-flat (la forming non basta,
|
||||
coerente con EXIT-16).
|
||||
|
||||
### Scope
|
||||
|
||||
Solo sleeve `single`/`ml`/`pairs`. I multi-asset (basket/rotation/tsmom/xsec, tutti PAPER)
|
||||
NON sono gateati: diversificati su 8 asset, un feed fermo non li compromette (e ROT02 ha ETH
|
||||
nell'universo ma resta attivo). Osservabilità: log `FEED_FROZEN_GATE` + alert Telegram
|
||||
GATED/RIPRESO, dedup per episodio.
|
||||
|
||||
## Esito (smoke feed reale, soglia 24)
|
||||
|
||||
- CONGELATI: ETH, BNB, DOGE.
|
||||
- **GATEATI: le 9 gambe ETH esatte** — MR01/MR02/MR07_ETH, SH_ETH, PR_ETHBTC, PR_ETHBTC_15M,
|
||||
PR_LTCETH, PR_ADAETH, PR_ETHSOL. BTC-only, PR_BTCLTC e i multi-asset restano attivi.
|
||||
|
||||
Le 5 posizioni ETH già aperte NON vengono flattate (freeze = pausa, non liquidazione): un
|
||||
close forzato passerebbe per lo stesso feed congelato (= il close −2.83 di SH01). Restano
|
||||
gestite alla ripresa del feed; protezione di coda = disaster-SL on-book a −30%.
|
||||
|
||||
Test: `tests/portfolio/test_freeze_gate.py` (detector guasto-vs-illiquido, rilascio su barra
|
||||
completata, scope sui veri sleeve PORT06). Suite portfolio: 140/140 verdi.
|
||||
|
||||
## Lezione
|
||||
|
||||
Tre livelli di osservabilità del feed ora distinti: STALE_FEED (≥2 barre flat, alert umano),
|
||||
FEED_BOOK_GAP (feed≠book d'esecuzione, alert), **FEED_FROZEN_GATE (≥24 barre invariate, AGISCE)**.
|
||||
La firma che separa guasto da illiquidità è la **run di prezzo invariato**, non la barra flat:
|
||||
gli alt illiquidi stampano barre flat ma il prezzo si muove ogni ~10 barre.
|
||||
@@ -14,6 +14,14 @@ overrides:
|
||||
leverage: 3
|
||||
rebalance: 1D
|
||||
poll_seconds: 60
|
||||
# Gate feed CONGELATO (2026-06-15): salta entry+exit degli sleeve concentrati
|
||||
# (single/ml/pairs) il cui feed di DECISIONE 1h e' fermo da >= N barre di close
|
||||
# INVARIATA (guasto testnet: ETH-PERPETUAL a 1661.95 per 36h+ -> z-score pairs
|
||||
# spuri e perdite reali, i fill avvengono al book vero). Distingue il guasto
|
||||
# dall'illiquidita' (alt flat ma che si muovono ogni ~10 barre). Auto-guarente:
|
||||
# si rilascia alla prima barra completa non-flat. 24 = un giorno di prezzo
|
||||
# immobile (margine pulito tra le due popolazioni). 0 = disattivo.
|
||||
feed_freeze_gate_bars: 24
|
||||
# SLEEVE PAPER (2026-06-08): fuori dal pool/pesi/ledger — i €2000 si dividono SOLO
|
||||
# tra i 14 sleeve REALI (fade+DIP01+pairs+SH01). I multi-asset (no esecuzione reale,
|
||||
# bloccati dal capitale) girano in data/portfolio_paper_stats/ con capitale nozionale
|
||||
|
||||
@@ -229,6 +229,68 @@ def _check_stale_feed(asset: str, df: pd.DataFrame, alerted: set[str]):
|
||||
"gap_pct": round(gap, 2), "prezzo": float(c[i])})
|
||||
|
||||
|
||||
# --- Gate feed CONGELATO (2026-06-15) -----------------------------------------
|
||||
# Distinto da STALE_FEED (osservabilita') e FEED_BOOK_GAP (divergenza esecuzione):
|
||||
# qui si AGISCE. Quando il feed di DECISIONE 1h di un asset e' CONGELATO (guasto
|
||||
# testnet: ETH-PERPETUAL inverse fermo a 1661.95 per 36h+, prezzo MAI cambiato) gli
|
||||
# sleeve CONCENTRATI che ne dipendono (single/ml/pairs) decidono entry/exit su un
|
||||
# prezzo morto -> segnali spuri (z-score pairs estremi -3/-5, SH01 short a prezzo
|
||||
# fermo) e perdite REALI (i fill avvengono al book vero ~1717, l'uscita sim al prezzo
|
||||
# congelato: SH01_ETH ha realizzato -2.83 reali vs -0.09 sim su un solo close). Si
|
||||
# SALTA il tick (entry E exit) finche' il feed non si sblocca: come un outage (i worker
|
||||
# non valutano gli exit, protezione = disaster-SL on-book), auto-guarente.
|
||||
#
|
||||
# DISTINGUERE guasto da ILLIQUIDITA': SOL/LTC/ADA stampano molte barre flat (O=H=L=C,
|
||||
# 50-95%) ma il prezzo SI muove ogni poche ore (run di close identiche CORTE, ~2-5). Il
|
||||
# guasto e' il prezzo che non cambia MAI: run di close INVARIATE >= soglia (il freeze
|
||||
# reale dura decine di barre). Un detector flat-bar ingenuo gaterebbe gli alt
|
||||
# illiquidi-ma-VIVI -> si conta la run di close invariate, NON le barre flat.
|
||||
#
|
||||
# NB POST-FLAT: si gatea DURANTE il freeze (ultima barra completa ferma). La barra di
|
||||
# RIPRESA e' non-flat -> la run si azzera -> il tick riprende SU di essa. NON e'
|
||||
# l'entry-guard post-flat (BOCCIATA: la candela-gap e' l'overshoot che la fade fada con
|
||||
# profitto, CLAUDE.md / 2026-06-05): quello salta la barra di ripresa, questo no.
|
||||
#
|
||||
# SOGLIA (misurata sul feed reale 2026-06-15): le due popolazioni sono ben separate ->
|
||||
# MORTO ETH run 64 (1 val distinto/48h), BNB 64, DOGE 42 (feed pinnato a un valore)
|
||||
# ILLIQUIDO LTC run 10, ADA 11, XRP 12, SOL 1 (5-31 val distinti/48h: SI muovono)
|
||||
# A 24 (un giorno intero di prezzo immobile) il guasto multi-giorno e' preso con ampio
|
||||
# margine senza gateare gli alt illiquidi-ma-VIVI (importante per PR_BTCLTC: BTC vivo +
|
||||
# LTC solo illiquido NON deve sospendere il pair). ETH (run 64) gatea comunque subito.
|
||||
_FREEZE_GATE_BARS = 24 # run di close INVARIATE sull'ultima barra completa = freeze
|
||||
|
||||
|
||||
def _frozen_assets(raw1h: dict[str, pd.DataFrame], threshold: int) -> set[str]:
|
||||
"""Asset col feed di decisione 1h CONGELATO: ultima barra completa flat (O=H=L=C)
|
||||
E close invariata da >= `threshold` barre complete consecutive. 0 = gate disattivo."""
|
||||
from src.live.bars import last_settled_idx
|
||||
frozen: set[str] = set()
|
||||
if threshold <= 0:
|
||||
return frozen
|
||||
for asset, df in raw1h.items():
|
||||
o, h, l, c = (df[k].values for k in ("open", "high", "low", "close"))
|
||||
i = len(c) + last_settled_idx(df["timestamp"].values)
|
||||
if i < 1 or not (o[i] == h[i] == l[i] == c[i]):
|
||||
continue # ultima barra completa non flat -> vivo
|
||||
run = 1
|
||||
while i - run >= 0 and c[i - run] == c[i]:
|
||||
run += 1
|
||||
if run >= threshold:
|
||||
frozen.add(asset)
|
||||
return frozen
|
||||
|
||||
|
||||
def _feed_gated_sids(live_specs, frozen: set[str]) -> set[str]:
|
||||
"""sid degli sleeve CONCENTRATI (single/ml/pairs) che dipendono da un asset col feed
|
||||
congelato. I multi-asset (basket/rotation/tsmom/xsec, tutti PAPER) NON sono gateati:
|
||||
diversificati su 8 asset, un singolo feed fermo non li compromette."""
|
||||
if not frozen:
|
||||
return set()
|
||||
return {s.sid for s in live_specs
|
||||
if s.kind in ("single", "ml", "pairs")
|
||||
and any(a in frozen for a in _spec_assets_tf(s)[0])}
|
||||
|
||||
|
||||
_GAP_BPS_DEFAULT = 150.0 # |close feed - mark book| oltre cui il feed non e' affidabile
|
||||
|
||||
|
||||
@@ -431,6 +493,11 @@ def run(config_path: str = "portfolios.yml"):
|
||||
# guard feed-vs-book (2026-06-12): soglia bps in overrides.feed_book_gap_bps (0 = off)
|
||||
gap_bps = float(_ov.get("feed_book_gap_bps", _GAP_BPS_DEFAULT))
|
||||
gap_alerted: set[str] = set()
|
||||
# gate feed CONGELATO (2026-06-15): salta gli sleeve concentrati su un asset col feed
|
||||
# fermo. Soglia (barre 1h di close invariata) in overrides.feed_freeze_gate_bars (0 = off).
|
||||
freeze_gate_bars = int(_ov.get("feed_freeze_gate_bars", _FREEZE_GATE_BARS))
|
||||
spec_by_id = {s.sid: s for s in live_specs}
|
||||
frozen_gated: set[str] = set() # sleeve gateati ora (dedup alert + recovery)
|
||||
# Osservabilita' outage (improvement-sweep 2026-06-06): il poll-loop intero e' in un
|
||||
# try/except → durante un outage i worker NON valutano gli exit. Alert Telegram dopo
|
||||
# _OUTAGE_POLLS poll falliti/DEGRADATI consecutivi + notifica di ripresa con durata.
|
||||
@@ -486,6 +553,23 @@ def run(config_path: str = "portfolios.yml"):
|
||||
if exec_enabled and gap_bps > 0:
|
||||
_check_feed_book_gap(client, raw1h, exec_instr, gap_bps, gap_alerted)
|
||||
|
||||
# gate feed CONGELATO: sleeve concentrati su un asset col feed fermo -> tick saltato
|
||||
frozen = _frozen_assets(raw1h, freeze_gate_bars)
|
||||
now_gated = _feed_gated_sids(live_specs, frozen)
|
||||
if now_gated != frozen_gated:
|
||||
from src.live.telegram_notifier import notify_event
|
||||
for sid in sorted(now_gated - frozen_gated):
|
||||
fa = sorted(set(_spec_assets_tf(spec_by_id[sid])[0]) & frozen)
|
||||
print(f"[runner] FEED_FROZEN_GATE {sid}: asset congelati {fa} -> tick sospeso")
|
||||
notify_event("FEED_FROZEN_GATE", {
|
||||
"sleeve": sid, "asset_congelati": ", ".join(fa), "stato": "GATED",
|
||||
"nota": "feed di decisione fermo: entry+exit sospesi finche' non si "
|
||||
"sblocca (disaster-SL on-book protegge le posizioni reali)"})
|
||||
for sid in sorted(frozen_gated - now_gated):
|
||||
print(f"[runner] FEED_FROZEN_GATE {sid}: feed ripreso -> tick riattivato")
|
||||
notify_event("FEED_FROZEN_GATE", {"sleeve": sid, "stato": "RIPRESO"})
|
||||
frozen_gated = now_gated
|
||||
|
||||
# fetch DIRETTO dei timeframe sub-orari (15m...) per (asset, tf)
|
||||
raw_sub: dict[tuple[str, str], pd.DataFrame] = {}
|
||||
for (asset, tf), days in subhourly_needs.items():
|
||||
@@ -553,6 +637,8 @@ def run(config_path: str = "portfolios.yml"):
|
||||
"nota": "exit NON valutati per questo worker"})
|
||||
|
||||
for s in live_specs:
|
||||
if s.sid in now_gated:
|
||||
continue # feed di decisione congelato -> entry+exit sospesi
|
||||
_tick_safe(s, workers[s.sid])
|
||||
# PAPER: ticcati per statistica, MAI nel ledger del portafoglio
|
||||
for s in paper_specs:
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Gate feed CONGELATO (2026-06-15): salta entry+exit degli sleeve concentrati
|
||||
(single/ml/pairs) il cui feed di DECISIONE 1h e' fermo (guasto, es. ETH-PERPETUAL
|
||||
a 1661.95 per 36h+). Distingue il GUASTO (close mai cambiata per decine di barre)
|
||||
dall'ILLIQUIDITA' (SOL/LTC/ADA: molte barre flat ma il prezzo si muove -> run corte).
|
||||
Auto-guarente: la barra di ripresa e' non-flat -> il gate si rilascia da solo."""
|
||||
import pandas as pd
|
||||
from src.portfolio.runner import _frozen_assets, _feed_gated_sids
|
||||
|
||||
|
||||
def _df(completed, forming):
|
||||
"""Serie 1h: barre complete (flat = O=H=L=C; non-flat quando il prezzo cambia)
|
||||
+ una candela in formazione (ultima riga, esclusa dal detector)."""
|
||||
end_ms = int(pd.Timestamp.now(tz="UTC").floor("h").timestamp() * 1000)
|
||||
vals = list(completed) + [forming]
|
||||
n = len(vals)
|
||||
rows = []
|
||||
for j, v in enumerate(vals):
|
||||
ts = end_ms - (n - 1 - j) * 3_600_000
|
||||
rows.append({"timestamp": ts, "open": v, "high": v, "low": v, "close": v, "volume": 0.0})
|
||||
for j in range(1, n): # bar NON-flat quando il prezzo si muove
|
||||
if rows[j]["close"] != rows[j - 1]["close"]:
|
||||
rows[j]["high"] = rows[j]["close"] * 1.001
|
||||
return pd.DataFrame(rows)
|
||||
|
||||
|
||||
def test_frozen_feed_detected():
|
||||
raw = {"ETH": _df([1661.95] * 40, 1661.95)} # prezzo MAI cambiato (guasto)
|
||||
assert _frozen_assets(raw, 12) == {"ETH"}
|
||||
|
||||
|
||||
def test_live_feed_not_frozen():
|
||||
raw = {"BTC": _df([65000 + i * 5 for i in range(40)], 65200.0)} # sempre in movimento
|
||||
assert _frozen_assets(raw, 12) == set()
|
||||
|
||||
|
||||
def test_illiquid_but_alive_not_frozen():
|
||||
# ultima barra completa FLAT ma il prezzo si e' mosso da poche barre (run < soglia):
|
||||
# alt illiquido (SOL/LTC/ADA), NON un guasto -> non gateato.
|
||||
raw = {"LTC": _df([44.0] * 30 + [45.0] * 5, 45.0)}
|
||||
assert _frozen_assets(raw, 12) == set()
|
||||
|
||||
|
||||
def test_resume_releases_gate():
|
||||
# rilascio solo su una barra COMPLETATA non-flat (la forming non basta -> coerente
|
||||
# con la valutazione su barre settled, EXIT-16). NON e' l'entry-guard post-flat: la
|
||||
# barra di ripresa NON viene saltata, il tick riprende su di essa.
|
||||
raw = {"ETH": _df([1661.95] * 40 + [1700.0], 1700.0)}
|
||||
assert _frozen_assets(raw, 12) == set()
|
||||
# finche' la ripresa e' solo nella candela in formazione, resta congelato
|
||||
assert _frozen_assets({"ETH": _df([1661.95] * 40, 1700.0)}, 12) == {"ETH"}
|
||||
|
||||
|
||||
def test_threshold_zero_disables():
|
||||
raw = {"ETH": _df([1661.95] * 40, 1661.95)}
|
||||
assert _frozen_assets(raw, 0) == set()
|
||||
|
||||
|
||||
def test_gated_scope_matches_eth_legs():
|
||||
"""Sui veri sleeve PORT06: con ETH congelato si gatano TUTTE e sole le gambe ETH
|
||||
(3 fade ETH + SH_ETH + 5 pairs con gamba ETH). BTC-only, BTC/LTC e i multi-asset
|
||||
(anche ROT02 che ha ETH nell'universo) restano attivi."""
|
||||
from scripts.portfolios._defs import FADE, PAIRS, SHAPE, HONEST
|
||||
specs = FADE + PAIRS + SHAPE + HONEST
|
||||
g = _feed_gated_sids(specs, {"ETH"})
|
||||
assert {"MR01_ETH", "MR02_ETH", "MR07_ETH", "SH_ETH",
|
||||
"PR_ETHBTC", "PR_ETHBTC_15M", "PR_LTCETH", "PR_ADAETH", "PR_ETHSOL"} <= g
|
||||
assert g.isdisjoint({"MR01_BTC", "MR02_BTC", "MR07_BTC", "SH_BTC",
|
||||
"DIP01_BTC", "PR_BTCLTC"})
|
||||
assert g.isdisjoint({"TR01_basket", "ROT02_rot"}) # multi-asset mai gateati
|
||||
assert _feed_gated_sids(specs, set()) == set() # nessun congelato -> nessun gate
|
||||
Reference in New Issue
Block a user